From 3eadafea27302142d045dbdf029398654a8be04c Mon Sep 17 00:00:00 2001 From: Packit Date: Aug 20 2020 14:27:33 +0000 Subject: sg3_utils-1.44 base --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d194ced --- /dev/null +++ b/.gitignore @@ -0,0 +1,102 @@ +# Please keep the entries in this file sorted with the following vi command: +# :3,$!LC_ALL=C sort -fu + +*.exe +*.la +*.lo +*.o +*~ +.deps +.libs +aclocal.m4 +ar-lib +autom4te.cache/ +compile +config.guess +config.h +config.log +config.status +config.sub +configure +depcomp +doc/Makefile +doc/sg_scan.8 +include/Makefile +INSTALL +install-sh +lib/Makefile +libtool +ltmain.sh +Makefile +Makefile.in +missing +src/Makefile +src/sginfo +src/sgm_dd +src/sgp_dd +src/sg_bg_ctl +src/sg_compare_and_write +src/sg_copy_results +src/sg_dd +src/sg_decode_sense +src/sg_emc_trespass +src/sg_format +src/sg_get_config +src/sg_get_lba_status +src/sg_ident +src/sg_inq +src/sg_logs +src/sg_luns +src/sg_map +src/sg_map26 +src/sg_modes +src/sg_opcodes +src/sg_persist +src/sg_prevent +src/sg_raw +src/sg_rbuf +src/sg_rdac +src/sg_read +src/sg_readcap +src/sg_read_attr +src/sg_read_block_limits +src/sg_read_buffer +src/sg_read_long +src/sg_reassign +src/sg_referrals +src/sg_rep_zones +src/sg_requests +src/sg_reset +src/sg_reset_wp +src/sg_rmsn +src/sg_rtpg +src/sg_safte +src/sg_sanitize +src/sg_sat_identify +src/sg_sat_phy_event +src/sg_sat_read_gplog +src/sg_sat_set_features +src/sg_scan +src/sg_scan.c +src/sg_senddiag +src/sg_ses +src/sg_ses_microcode +src/sg_start +src/sg_stpg +src/sg_sync +src/sg_test_rwbuf +src/sg_timestamp +src/sg_turs +src/sg_unmap +src/sg_verify +src/sg_vpd +src/sg_write_atomic +src/sg_write_buffer +src/sg_write_long +src/sg_write_same +src/sg_write_verify +src/sg_write_x +src/sg_wr_mode +src/sg_xcopy +src/sg_zone +stamp-h1 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f72f863 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Douglas Gilbert + +See the CREDITS file for the names of those who have contributed. diff --git a/BSD_LICENSE b/BSD_LICENSE new file mode 100644 index 0000000..7f1906b --- /dev/null +++ b/BSD_LICENSE @@ -0,0 +1,24 @@ + +Copyright (c) 1999-2018, Douglas Gilbert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3ef2d53 --- /dev/null +++ b/COPYING @@ -0,0 +1,32 @@ + +Upstream Authors: Douglas Gilbert , + Bruce Allen , + Peter Allworth , + James Bottomley , + Lars Marowsky-Bree , + Kurt Garloff , + Grant Grundler , + Christophe Varoqui , + Michael Weller , + Eric Youngdale + +Copyright: + +This software is copyright(c) 1994-2012 by the authors + +Most of the code in this package is covered by a BSD license. +On Debian systems, the complete text of the BSD License +can be found in `/usr/share/common-licenses/BSD'. All the code +in the library (usually called libsgutils) is covered by a +BSD license. + +Some of the older utilities are covered by the GPL. More precisely: +You are free to distribute this software under the terms of the +GNU General Public License either version 2, or (at your option) +any later version. On Debian systems, the complete text of the GNU +General Public License can be found in /usr/share/common-licenses/GPL-2 +file. The later GPL-3 is found in /usr/share/common-licenses/GPL-3 +file but no code in this package refers to that license. + +Douglas Gilbert +10th April 2012 diff --git a/COVERAGE b/COVERAGE new file mode 100644 index 0000000..84bd4d4 --- /dev/null +++ b/COVERAGE @@ -0,0 +1,161 @@ + Command coverage + ================ +The following table lists SCSI commands in alphabetical order on the +left and the sg3_utils (or related) utilities that implement invocations +of them on the right. The second table lists supported ATA commands. The +third table list supported NVMe commands. + +SCSI command sg3_utils utilities that use this SCSI command +------------ ------------------------------------------------- +ATA COMMAND PASS-THROUGH(12) sg_sat_identify, ++ +ATA COMMAND PASS-THROUGH(16) sg_sat_identify, sg_sat_set_features, + sg_sat_phy_event, sg_sat_read_gplog ++ + [sg_sat_chk_power, sg__sat_identify, + sg__sat_set_features, sg_sat_smart_rd_data + (previous four in the examples directory)] +ATA COMMAND PASS-THROUGH(32) sg_sat_identify, ++ +BACKGROUND CONTROL sg_bg_ctl +CLOSE ZONE sg_zone +COMPARE AND WRITE sg_compare_and_write +COPY OPERATION ABORT ddptctl, ++ +EXTENDED COPY(LID1) sg_xcopy, ddpt, ++ +GET CONFIGURATION sg_get_config, ++ +GET LBA STATUS sg_get_lba_status, ++ +GET STREAM STATUS sg_stream_ctl +INQUIRY sg_dd, sg_format, sg_inq, sginfo, + sg_logs, sg_map('-i'), sg_modes, sg_opcodes, + sg_persist, sg_scan, sg_ses, sg_vpd ++ +FINISH ZONE sg_zone +FORMAT MEDIUM sg_format, ++ [SSC] +FORMAT UNIT sg_format, ++ [SBC] +LOG SELECT sg_logs('-r' or '-select'), ++ +LOG SENSE sg_logs, ++ +MODE SELECT(6) sdparm, sg_wr_mode, sginfo, sg_format, + sg_emc_trespass, sg_rdac, ++ +MODE SELECT(10) sdparm, sg_wr_mode, sginfo, sg_format, + sg_emc_trespass, sg_rdac, ++ +MODE SENSE(6) sdparm, sg_modes, sg_wr_mode, sginfo, sg_format, + sg_senddiag('-e'), sg_rdac, ++ +MODE SENSE(10) sdparm, sg_modes, sg_wr_mode, sginfo, sg_format, + sg_senddiag('-e'), sg_rdac, ++ +OPEN ZONE sg_zone +ORWRITE(16) sg_write_x +ORWRITE(32) sg_write_x +PERSISTENT RESERVE IN sg_persist, ++ +PERSISTENT RESERVE OUT sg_persist, ++ +POPULATE TOKEN ddpt, ddptctl, ++ +PRE-FETCH(10) sg_seek +PRE-FETCH(16) sg_seek +PREVENT ALLOW MEDIUM REMOVAL sg_prevent, ++ +READ(6) sg_dd, sgm_dd, sgp_dd, sg_read +READ(10) sg_dd, sgm_dd, sgp_dd, sg_read +READ(12) sg_dd, sgm_dd, sgp_dd, sg_read +READ(16) sg_dd, sgm_dd, sgp_dd, sg_read +READ ATTRUBUTE sg_read_attr +READ BLOCK LIMITS sg_read_block_limits, ++ +READ BUFFER(10) sg_rbuf, sg_test_rwbuf, sg_read_buffer, sg_safte, ++ +READ BUFFER(16) sg_read_buffer +READ CAPACITY(10) sg_readcap, sg_dd, sgm_dd, sgp_dd, sg_format, ++ +READ CAPACITY(16) sg_readcap, sg_dd, sgm_dd, sgp_dd, sg_format, ++ +READ DEFECT(10) sginfo('-d' or '-G'), sg_reassign('-g'), smartmontools, ++ +READ DEFECT(12) sginfo('-d' or '-G'), smartmontools +READ LONG(10) sg_read_long, sg_dd, ++ +READ LONG(16) sg_read_long, ++ +READ MEDIA SERIAL NUMBER sg_rmsn, ++ +REASSIGN BLOCKS sg_reassign, ++ +RECEIVE COPY DATA(LID1) sg_copy_results, ++ +RECEIVE COPY FAILURE DETAILS(LID1) sg_copy_results, ++ +RECEIVE COPY OPERATING PARAMETERS ddpt, sg_copy_results, sg_xcopy, ++ +RECEIVE COPY STATUS(LID1) sg_copy_results, ++ +RECEIVE DIAGNOSTIC RESULTS sg_senddiag, sg_ses, sg_ses_microcode ++ +RECEIVE ROD TOKEN INFORMATION ddpt, ddptctl ++ +REPORT ALL ROD TOKENS ddptctl ++ +REPORT IDENTIFYING INFORMATION sg_ident, ++ (2) +REPORT LUNS sg_luns, ++ +REPORT REFERRALS sg_referrals, ++ +REPORT SUPPORTED OPERATION CODES sg_opcodes +REPORT SUPPORTED TASK MANAGEMENT FUNCTIONS sg_opcodes +REPORT TARGET PORT GROUPS sg_rtpg, sg_stpg ++ +REPORT TIMESTAMP sg_timestamp +REPORT ZONES sg_rep_zones +REQUEST SENSE sg_requests, ++ +RESET WRITE POINTER sg_reset_wp +SANITIZE sg_sanitize +SEEK(10) sg_seek ++ +SEND DIAGNOSTIC sg_senddiag, sg_ses, sg_ses_microcode ++ +SEQUENTIALIZE ZONE sg_zone +SET IDENTIFYING INFORMATION sg_ident, ++ (3) +SET TARGET PORT GROUPS sg_stpg, ++ +SET TIMESTAMP sg_timestamp +START STOP sg_start, ++ +STREAM CONTROL sg_stream_ctl +SYNCHRONIZE CACHE(10) sg_sync, sg_dd, sgm_dd, sgp_dd, ++ +SYNCHRONIZE CACHE(16) sg_sync++ +TEST UNIT READY sg_turs, sg_format, ++ +UNMAP sg_unmap, ++ +VERIFY(10) sg_verify, ++ +VERIFY(16) sg_verify, ++ +WRITE(6) sg_dd, sgm_dd, sgp_dd +WRITE(10) sg_dd, sgm_dd, sgp_dd +WRITE(12) sg_dd, sgm_dd, sgp_dd +WRITE(16) sg_dd, sgm_dd, sgp_dd, sg_write_x +WRITE(32) sg_write_x +WRITE AND VERIFY(10) sg_write_verify +WRITE AND VERIFY(16) sg_write_verify +WRITE ATOMIC(16) ddpt, sg_write_x +WRITE ATOMIC(32) sg_write_x +WRITE BUFFER sg_test_rwbuf, sg_write_buffer, ++ +WRITE LONG(10) sg_write_long, ++ +WRITE LONG(16) sg_write_long, ++ +WRITE SAME(10) sg_write_same +WRITE SAME(16) sg_write_same, sg_write_x +WRITE SAME(32) sg_write_same, sg_write_x +WRITE SCATTERED(16) sg_write_x +WRITE SCATTERED(32) sg_write_x +WRITE STREAM(16) sg_write_x +WRITE STREAM(32) sg_write_x +WRITE USING TOKEN ddpt, ddptctl, ++ + sg_raw + + +ATA command sg3_utils utilities that use this (S)ATA command +----------- ------------------------------------------------ +CHECK POWER MODE examples/sg_sat_chk_power +IDENTIFY DEVICE sg_inq, sg_scan, sg_sat_identify, + examples/sg__sat_identify +IDENTIFY PACKET DEVICE sg_inq, sg_sat_identify, + examples/sg__sat_identify +READ LOG EXT sg_sat_phy_event, examples/sg__sat_phy_event + sg_sat_read_gplog +READ LOG DMA EXT sg_sat_read_gplog +SET FEATURES sg_sat_set_features + examples/sg__sat_set_features +SMART READ DATA examples/sg_sat_smart_rd_data + + +NVMe command sg3_utils utilities that use this NVMe command +------------ ------------------------------------------------ +IDENTIFY sg_inq +SES READ sg_senddiag, sg_ses (NVME-MI command) +SES WRITE sg_senddiag, sg_ses (NVME-MI command) +Device self-test [SNTL of SEND DIAGNOSTIC] sg_senddiag +Get features(power management) [SNTL of REQUEST SENSE] sg_requests + + +++ command wrapper found in sg_cmds_basic.c, sg_cmds_mmc.c or + sg_cmds_extra.c for this command +(2) this command was known as REPORT DEVICE IDENTIFIER prior to spc4r07 +(3) this command was known as SET DEVICE IDENTIFIER prior to spc4r07 + +Note that any SCSI command, including bi-directional and variable length +commands (whose cdb size is > 16 bytes) can be issued by the sg_raw utility. + +The RECEIVE COPY * commands in SPC-4 were grouped as one command name +with 4 service actions in SPC-3 and earlier. The single SPC-3 command +name is RECEIVE COPY RESULTS. The two opcodes associated with all +EXTENDED COPY commands are now known as THIRD PARTY COPY IN (0x84) and +THIRD PARTY COPY IN (0x83). + + +Douglas Gilbert +6th May 2018 diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..beefb4c --- /dev/null +++ b/CREDITS @@ -0,0 +1,144 @@ +The author of sg3_utils would like to thank the following people who +have made contributions: + + +Andries Brouwer rewrite of isosize (original + written by Joerg Schilling). isosize is now found in the util-linux + package and in the archive directory of this package. + +Bart Van Assche + harden (improve) code in rescan-scsi-bus.sh [20160224] + configure.ac and Makefile.am cleanup plus sgp_dd code + to replace pthread_cancel with pthread_kill [20180102] + +Brian Bunker contributed + sg_read_block_limits and the target reset addition to sg_reset + [20090615] + +Christophe Varoqui original sg_rtpg + [20041229] + +Clayton Weaver contributed safe_strerror(). + +Dan Horak website support for this package and + others. Lot of fixes, recently man pages [20140128] + +Dave Johnson improved disk defect list + handling [20051218] + +Dave Williams help with + sgp_dd especially and "> 0x7fffffff" with sg*_dd [20060303] + +Eric Schwartz who wrote these man pages: + sg_readcap, sg_reset, sg_scan, sg_start, sg_test_rwbuf, + sg_turs and sginfo + +Eric Seppanen borrowed ideas from alternate + implementation of sg_compare_and_write [20130823] + +Eric Youngdale author of scsi_info on which sginfo + is based. + +Frank Jansen : additions to sg_scan; contributed + code for '--alloc-length=' option in sg_persist [20090402] + +Grant Grundler co-author of blk512-linux + that has become sg_format [20050201] + +Greg Inozemtsev + extensions to sg_xcopy [20130207+20130816] + +Hannes Reinecke + contributed sg_rdac, (and some corresponding VPD entries to + sg_vpd_vendor), sg_stpg and sg_safte [20071013+20130110] + sg_referrals [20100906] + sg_inq --export option [20120220+20130109] + sg_xcopy+sg_copy_results [20120322] + rescan-scsi-bus.sh patches to Kurt Garloff's v1.57 [20130715] + 55-scsi-sg3_id.rules + 58-scsi-sg3_symlink.rules [20140527] + sg_sat_read_gplog [20141107] + sg_inq --only option plus --inhex fixes [20180102] + +Hayashi Naoyuki + port to Tru64 [20060127] + +Heiko Eissfeldt sg based example + programs for the original sg driver + +Ilan Steinberg + sg_xcopy: contributed --on_src and --on_dst options [20130505] + +Ingo van Lil + contributed sg_raw [20070331] + +James Bottomley co-author of blk512-linux + that has become sg_format [20050201] + +Jan Engelhardt + autotools clean-up [20150216] + +Joe Krahn help with int64_t cleanup + [20071219] + +Kai Makisara help with tape + minor numbers in lk 2.6 plus earlier advice [20081008] + +Kurt Garloff original sg_start and sg_test_rwbuf. + Additions to sginfo and sg_map. Author of rescan-scsi-bus.sh with + latest update to v1.57 [20130331] + +Lars Marowsky-Brée contributed Unit Path Report VPD + page decoding in sg_inq (vendor specific: EMC) and sg_emc_trespass + utility + +Luben Tuikov + help with documentation and other suggestions [20061014] + contribution sg_read_buffer and sg_write_buffer [20061103] + +Marius Konitzer + suggested and tested oflag=sparse for sg_dd + +Martin Schwenke added the raw switch "-r" to sg_inq + +Nate Dailey < Nate dot Dailey at stratus dot com > extended sg_map for sparse + disk node names (e.g. /dev/sdaaa) [20050511] + +Pat LaVarre pointed out danger of negative bpt + values in sg_dd (and friends); also problems when reading /dev/null + +Peter Allworth original dd clone design used + by sg3_utils's dd variants (e.g. sg_dd). + +Roland Dreier + extension and correction to sg_xcopy [20120205] + +Ronnie Sahlberg has written libiscsi and a + set of external patches to add direct iSCSI support to this package. + See README.iscsi [20110518] + +Saeed Bishara contributed sg_write_long + +Sean Stewart various improvements + to rescan-scsi-bush.sh script [20130827] + +Shahar Salzman contributed + sg_compare_and_write [20121205] + +Thomas Kolbe + Solaris port help and testing [20070503] + +Tim Hunt increased number of (sd and sg) devices + that sginfo could detect. + +Tom Steudten sginfo addition: add '-Fhead' option + to sort defect list by head. + +Trent Piepho print out some "sense key specific" + data and "-6" switch for sg_modes + + +Douglas Gilbert +2nd January 2018 diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..08f598c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1601 @@ +Each utility has its own version number, date of last change and +some description at the top of its ".c" file. All utilities in the main +directory have their own "man" pages. There is also a sg3_utils man page. + +Changelog for sg3_utils-1.44 [20180912] [svn: r791] + - same code as release 1.43 20180911 svn rev 789; + new release due to sync problem with git mirror at: + https://github.com/hreinecke/sg3_utils + that has a v1.43 tag dated 20160217 [svn: r663] + +Changelog for sg3_utils-1.43 [20180911] [svn: r789] + - release 1.43 20180911 svn rev 789 + - sg_write_x: where x can be normal, atomic, or(write), + same, scattered, or stream writes with 16 or 32 byte + cdbs (sbc4r04 for atomic, sbc4r11 for scattered) + - sg_bg_ctl: new Background control command (sbc4r08) + - sg_seek: new SEEK(10) or PRE-FETCH(10 or 16) + - sg_stream_ctl: new, STREAM CONTROL or GET STREAM STATUS + - sg_senddiag: add --timeout=SECS option + - sg_sanitize: add --timeout=SECS option + - add --dry-run option + - sg_format: add --timeout=SECS option + - add --dry-run option to bypass modifying behaviour + - add --quick option to skip reconsideration time + - extend --wait timeout to 40 hours for disk sizes + > 4 TB and 80 hours if > 8 TB + - when changing block size allow for Mode Select + rejecting SP=1 (Save Page): repeat with SP=0 + - FFMT tweaks: default CMPLST to false, shorten poll + - make all data-in and data-out buffers page aligned + - sg_decode sense: add --cdb and --err=ES options + - sg_ses: handle 2 bit EIIOE field in aes dpage + - increase join array size from 260 to 520 elements + - add --quiet option to suppress messages + - expand join handling of SAS connectors and others + - expand join debug code + - allow multiple --clear=, --get= and --set= options + - allow individual index ranges (e.g. --index=3-5) + - allow --index=IIA with -ee to enumerate only fields + belonging to element type IIA + - --data=@FN with --status now decodes dpage(s) in FN + - add 'offset_temp' and 'rqst_override' to temperature + sensor element type + - add 'hw_reset' and 'sw_reset' to enclosure services + controller electronics element type (18-047r1) + - interpret '--join --page=aes' to only display join + rows that have a corresponding AES dpage element + - support NVMe attached enclosure via NVME-MI Send and + Receive SES commands + - decode array status diagnostic page (obsolete) + - sync to ses4r01 + - sg_ses_microcode: add --dry-run and -ealsd options + - sg_ses, sg_ses_microcode, sg_senddiag: make all access + buffer page size aligned (typically page_size=4096) + - sg_write_buffer: add --dry-run option + - sg_luns: resync with drafts (sam6r02+spc5r10) + - remove undocumented test "W" format + - accept and output on request "quad dashed" format + - sg_logs: fix volume statistics lpage when subpage + is zero (ssc5r02a); decode mount history log parameter + - add --vendor=VP and '--pdt=DT' options + - decode Requested recovery, TapeAlert response, and + Service buffer information lpages for tape + - add min+max 'mounted' temperature and rel. humidity + fields to Environmental reporting lpage (spc5r10) + - add last n Inquiry/Mode_page data changed log + pages (spc5r17) + - add zoned block device statistics lpage (zbc2r?, + 16-264r4) + - fixup enumeration in power condition transition + log page (from H. Reinecke, Suse) + - sg_inq: fix potential unbounded loop in --export + - add --only to stop standard inquiry decoding also + doing a serial number vpd page (0x80) fetch + - update version descriptor list to 20170114 + - add further checks so CDROM standard inquiry response + doesn't trick --inhex into thinking it's VPD pg 0x80 + - decode NVMe Identify controller/nsid commands + - with NVMe --only restricts to a single Identify + controller command + - add --long which decodes more of the NVMe Identify + command responses + - sg_inq+sg_vpd: update Extended inquiry data vpd + page (spc5r09 and 17-142r5) + - block limits and block limit extension VPD pages: + add extra info about corner cases + - add maximum inquiry|mode_page change logs fields + to extended inquiry vpd page (spc5r17) + - both now return EDOM (adjusted sg error code) when + requested page not in Supported VPD Pages page + - add --force option to bypass checking Supported + Vpd Pages page and fetch requested page directly + - sg_vpd: 3 party copy VPD page improvements + - fully implement Device constituents VPD page + - decode some WDC/Hitachi vendor VPD pages + - improve handling of unknown pages + - sg_reassign+sg_write_same: fix ULONG_MAX problem + - sg_rdac: add sanity checks for -f=lun value + - sg_turs+sg_requests: make both accept '--num=NUM' + and '--number=NUM' for mutual compatibility + - sg_turs: add --low option + - fix exit status when not ready in single case + - sg_zone: fix debug cdb naming + - add --sequentialize, --count=ZC options, zbc2r01b + - sg_reset_wp add --count=ZC option, zbc2r01b + - sg_persist: add --maxlen-LEN option, LEN defaults to + decimal, similar to --alloc-length= which takes hex + - add Replace lost reservation capable (RLR_C) bit + in Report Capabilities (spc4r36) + - sg_dd: add --dry-run and --verbose options + - allow multiple short options (e.g. -dvv ) + - sgp_dd: pthread_cancel() has issues in C++ (and + the Android multi-threaded library doesn't supply it) + so use pthread_kill() in its place [Linux only] + - add --dry-run and --verbose options + - sgm_dd: add --dry-run and --verbose options + - sg_opcode: add '--enumerate' and '--pdt=' options + - support CDLP (command duration limit page) + - support MLU, Multiple Logical Units (18-045r1) + - check resid and trim response if necessary + - report when --no-inquiry is ignored + - sg_raw: add --enumerate option + - add --cmdfile=CF option, permit 64 byte NVMe + admin commands to be sent + - add --raw option (for CF in binary) + - page align input and output buffers + - sg_get_lba_status: add --report-type= option (sbc4r12) + - add support for 32 byte cdb variant (sbc4r14) + - add support for --element-id= and --scan-len= + options (sbc4r14) + - decode response's RTP and two more provisioning + statuses and the additional status (sbc4r12) + - decode completion condition (sbc4r14) + - sg_modes: add Out of band management control mpage + - accept acronym for page/subpage codes + - sg_rep_zones: expand --help option information + - sg_unmap: add --all=ST,RN[,LA] option to unmap + large contiguous segments of a disk/ssd + - add --dry-run and --force options + - sg_wr_mode: add --rtd option for RTD bit + - sg_timestamp: add '--no-timestamp' option + - add --elapsed and --hex options + - sginfo: don't open /dev/snapshot + - introduce SG3_UTILS_DSENSE environment variable + - manpages and usage messages: corrections from + Gris Ge via github + - group_number: is 6 bit field allowing 0 to 63, + code in several utilities limited it to 31, fix + - convert many two valued 'int's to bool + - sg_lib: add SSC maintenance in/out sa names + - enhance exit status values and associated + strings, add SG_LIB_OS_BASE_ERR (50) + - add sg_ll_inquiry_v2(), sg_ll_inquiry_pt() and + sg_simple_inquiry_pt() + - add sg_ll_report_luns_pt() + - add sg_ll_log_sense_v2() + - add sg_ll_mode_sense10_v2() + - add sg_ll_mode_select6_v2() and + sg_ll_mode_select10_v2() for RTD bit + - add sg_ll_receive_diag_v2() + - add sg_ll_write_buffer_v2() + - add sg_get_llnum_nomult() + - add sg_ll_get_lba_status16() + - add sg_ll_get_lba_status32() + - add sg_ll_format_unit_v2() + - add sg_ll_test_unit_ready_progress_pt() + - add sg_ll_start_stop_unit_pt() + - add sg_ll_request_sense_pt() + - add sg_ll_send_diag_pt(), sg_ll_receive_diag_pt() + - add sg_get_sfs_name() for spc5r11 (Feature sets) + - add sg_decode_transportid_str() + - add sg_msense_calc_length() + - add sg_all_zeros(), sg_all_ffs() + - add sg_get_sense_cmd_spec_fld() + - add sg_is_scsi_cdb() + - add sg_get_nvme_cmd_status_str() + - add sg_nvme_status2scsi() + - add sg_nvme_desc2sense() + - add sg_build_sense_buffer() + - add sg_get_nvme_opcode_name() + - add sg_memalign() and sg_get_page_size() + - add sg_is_aligned() and pr2ws() + - add sg_get_big_endian(), sg_set_big_endian() + - add hex2stdout(), hex2stderr() and hex2str() + - add sg_convert_errno() + - add sg_if_can2stdout(), sg_if_can2stderr() and + sg_exit2str() + - implement 'format' argument in dStrHexStr() + - add read buffer(16) command mode names + - add Microcode activation sense descriptor spc5r10 + - add SG_LIB_OK_TRUE(0) and SG_LIB_OK_FALSE(36) + non "error" code defines for exit status + - add SG_LIB_LBA_OUT_OF_RANGE error code + - add SG_LIB_UNBOUNDED_32BIT (_16BIT and _64BIT) + defines to help with decoding corner cases + - identify vendor specific sense data (response + code 0x7f), print contents in hex + - sg_pr2serr.h: add sg_scnpr() [like lk scnprintf()] + - sg_pt: add construct_scsi_pt_obj_with_fd() + - add pt_device_is_nvme(), get_pt_nvme_nsid() + - add check_pt_file_handle() + - add get_pt_file_handle(), set_pt_file_handle() + - add small SNTL to support sg_ses on NVMe + - sg_lib_data: sync asc/ascq codes with T10 20170114 + - add write scattered (16+32) cdb names sbc4r11 + - sg_cmds_extra: expand sg_ll_ata_pt() to send new + Ata pass-through(32) command (sat4r05) + - sg_sat_identify: expand to take --len=32 + - sg_pt: add dummy pt_device_is_nvme() + - rescan-scsi-bus.sh: harden code + - fixes from Suse; bump version + - bump version to 20180615 + - add to install list in Makefile, hope it does + not clash with other package providing it + - add --ignore-rev to ignore revision change + - 55-scsi-sg3_id.rules: fixes from Suse + - https://github.com/hreinecke/sg3_utils branch + sles15 synced 20170914 + - move some testing utilities out of the + 'examples' and 'utils' directories into the new + 'testing' directory + - add testing/sg_tst_nvme utility + - clean Makefile.freebsd in examples/ and testing/ + - gcc 7.2 cleanups (sysmacros.h etc) + - clang --analyze static checker clean ups + - shellcheck cleanup on scripts + - ./configure automake utility: + - option --enable-debug added for testing + - option --disable-linuxbsg retired, still accepted + but now ignored, Linux sg v3 or v4 interface + decision made at runtime + - Info section now printed at end of ./configure + - automake: add AM_PROG_AR to configure.ac + - upgrade to version 1.15 + - various configure.ac and Makefile.am cleanups + - add SG_LIB_ANDROID build 'define'. If defined then + SG_LIB_LINUX is also defined, so test for Android + before Linux if need to differentiate + - update BSD license from 3 to 2 clause aka FreeBSD + license (without reference to FreeBSD project) + - debian: bump compat file contents from 7 to 10 + +Changelog for sg3_utils-1.42 [20160217] [svn: r663] + - sg_timestamp: new, to report or set timestamp + - sg_read_attr: new, supported by tape drives + - sg_stpg: fix truncation of target port field + - sg_inq: cope with unicode strings, udev fixes + - update version descriptor list to 20160125 + - '--export': new entries for UUID descriptor + - sg_ses: add more field acronyms (ses3r11) + - sg_logs: add Utilization lpage (sbc4r07) + - add Background operation lpage + - add Pending defects lpage + - add LPS misalignment lpage (sbc4r10) + - document '--All' ('-A') option + - rework lto tape vendor lpages + - sg_vpd: add Block limits extension VPD page + - add Device constituents VPD page + - add LB Protection VPD page (ssc ssc5r02a) + - LB provisioning VPD page: expand LBPRZ, add + Minimum and Threshold percentage fields + - rework lto tape vendor VPD pages + - sg_inq+sg_vpd+sg_xcopy: add support for locally + assigned UUIDs in VPD page 0x83 (spc5r08) + - sg_sanitize: add --znr option (sbc4r07) + - sg_rep_zones: add --partial option (zbc-r04) + - sg_format: add ffmt option (sbc4r10) + - add support for FORMAT MEDIUM (for tape) + - sg_raw: document length relationships + - rescan-scsi-bus.sh: updates from Suse + - sg_lib_data: sync asc/ascq codes with T10 20151126 + - sg_lib: add 'sense' categories for SCSI statuses: + condition met, busy, task set full, ACA active and + task aborted + - add pr2serr() extern + - change sg_get_sense_str() and dStrHexStr(), return + chars written (returned void previously) + - add sg_get_sense_descriptors_str() function + - add sg_get_designation_descriptor_str() function + - sg_get_desig_type_str()+sg_get_desig_assoc_str() + and sg_get_desig_code_set_str() added + - sg_get_opcode_sa_name() break out zoning in/out, + read attribute and read position service actions + - sg_cmds_extra: add sg_ll_format_unit2() for FFMT + - sg_pr2serr.h: new, to shorten fprintf(stderr, ...) + - sg_io_linux, sg_pt_linux: drop SUGGEST_* decoding + - sg_unaligned.h: add 48 bit support and gets for + variable length unsigned integers + - add specializations for little and big endian + - change sg_ll_*() function's 'int noisy' to bool + +Changelog for sg3_utils-1.41 [20150511] [svn: r644] + - sg_zone: new utility for open, close and finish + zone commands introduced in zbc-r02 + - sg_rep_zones and sg_reset_wp: change opcodes as + indicated in zbc-r02 + - sg_read_buffer: add READ BUFFER(16) support (spc5r02) + - sg_logs: add --enumerate and acronyms + - allow decode from hex or binary in file + - decode environmental reporting + limits lpages + - sg_write_buffer: add --timeout=TO option + - sg_lib interface: add sg_lib_pdt_decay(), TPROTO_PCIE + plus support for zoning service actions + - sg_lib: in Linux blocked devices yield ENXIO from + ioctl(SG_IO), map to SG_LIB_CAT_NOT_READY + - clean up sg_warnings_stream handling + - sg_inq+sg_vpd: fix SCSI name string decoding in + device identification VPD page (0x83) + - increase sanity on Unit Serial number VPD page + - improve rdac vpd page reporting (vendor) + - sg_inq: improve NAA handling in dev_id VPD page + - update version descriptor list to 20150126 + - sg_vpd: add atomic boundary values (sbc4r04) + - block limits VPD page: fix unmap granularity + alignment value; spc5r02 additions + - sg_readcap: add support for ZBC's rc_basis field + - sg_senddiag: fix bug with --raw option + - add support for -HHH for output suitable for --raw + - sg_ses: enclosure element: add failure and warning + acronyms, fix warning indication output + - additional element status dpage: add PCIe/NVMe + - handle element descriptor names that count + multiple trailing NULLs + - rescan-scsi-bus.sh: add --issue-lip-wait option and + improve error handling + - improve dm-multipath handling + - sg_modes: make '-HHH' output suitable as input to + 'sdparm --inhex=' + - sg_rdac: add '-6' option for mode sense/select(6) + - add support for reporting more SCSI transports + and accessing rdac extended mode page 0x2c + - sg_write_same: cleanup, mainly man page + - scsi_logging_level: replace use of tr command + - examples/sg_tst_async: cleanup + - examples/sg-simple_aio.c: remove + - sg_lib_data: sync asc/ascq codes with T10 20150423 + - Makefile cleanup + - autogen.sh: upgrade to buildconf 20091223 version + +Changelog for sg3_utils-1.40 [20141110] [svn: r620] + - sg_write_verify: new utility for WRITE AND VERIFY + - sg_ses_microcode: new utility + - sg_sat_read_gplog: new utility + - sg_senddiag: add --maxlen= option + - sg_copy_results: correct response length calculations + - sg_format: make '-FFF' bypass mode sense/select + - add --mode=MP to supply alternate mode page, + default remains read-write error recovery mpage + - output unit serial number and LU name prior to + - sg_inq: expand Block limits VPD page output + - fix --cmddt output if not supported by device + - more sanity checks on vendor supplied fields + - sg_vpd: add --all option + - more TPC VPD page decoding + - add zoned block device characteristics page + - more sanity checks on vendor supplied fields + - sg_ses: fix problem with --index=sse (and ssc) + - mask status element before using as control + - defeat previous item with --mask (ignore) option + - SAS connector status element: add overcurrent bit + - handle element descriptor names that count a + trailing NULL + - add --warn option mainly for broken joins + - add optional descriptions to -ee output + - sync with ses3r07 + - sg_sanitize: add --desc and --zero options + - output unit serial number and LU name prior to + - sg_rep_zones: corrections, sync with zbc-r01c + - sg_persist: split help into two pages, '-hh' for 2nd + - sg_logs: refine tape drive output + - sg_raw: with -vvv decode T10 CDB name + - do not output/print data-in if error + - sg_opcodes: add --compact field + - sg_senddiag: add --page=PG option + - sg_reset: add words for EAGAIN from reset ioctl + - sg_sat_*: mention t_type and multiple_count fields + - win32: sg_scan: handle larger configurations + - sg_lib: trim trailing spaces in dStrHex() and friends + - sg_lib_data: sync asc/ascq codes with T10 20140924 + - clean up service action string functions + - sg_ll_unmap_v2(): fix group number + - sg_ll_inquiry(), sg_ll_mode_sense*(), + sg_ll_log_sense(): use resid to clear unfilled + data-in buffer + - sg_unaligned.h: add header for building parameters + - examples/sg_tst_async: new Linux sg test utility + +Changelog for sg3_utils-1.39 [20140612] [svn: r588] + - sg_rep_zones: new utility for ZBC REPORT ZONES + - sg_reset_wp: new utility, ZBC RESET WRITE POINTER + - sg_ses: add --eiioe=auto|force option + - fix AES dpage element indexing problems + - add --readonly option + - sg_write_buffer: add --bpw=CS option to call + write buffer multiple times for big blobs + - sg_format: add --ip_def option to fully provision + - sg_opcodes: add --mask option + - sg_logs: add --in=FN option for log select params + - add --filter=PARC (parameter code) + - add --no_inq for suppress initial INQUIRY call + - add --readonly option + - sg_persist: add --readonly option, environment + variable SG_PERSIST_IN_RDONLY sets ro on prin cmds + - sg_inq: sync version descriptors dated 20105176 + - suppress dev-id VPD messages so they only appear + when --verbose is given + - add new SCSI_IDENT_*_ATA pair to --export output + - sg_luns: add decoding for conglomerate LUNS + - add --lu_cong option to simulate the LU_CONG bit + - sg_vpd: add --vendor=VP option, re-order vendor + specific pages, split lto into lto5 and lto6 + - add Supported block lengths and protection types + page (sbc4r01) + - add Block device characteristics extension + page (sbc4r02) + - sg_copy_results, sg_get_lba_status, sg_luns, + sg_read_buffer, sg_readcap, sg_referrals, sg_rtpg, + sg_sat_set_features, sg_sat_identify: + add --readonly option + - sginfo: strip trailing spaces from INQUIRY text + - sg_rbuf: add --echo option (to use echo buffer) + - sg_lib: add sanitize command service action names + - add 'sense' categories for reservation conflict, + data protect and protection information violations + - add sg_get_category_sense_str() to API + - change struct sg_simple_inquiry_resp::rmb to byte_1 + - add initial zbc service actions + - dStrHex(Err): fix output truncation error + - linux, sg: support SCSI_PT_FLAGS_QUEUE_AT_TAIL and + SCSI_PT_FLAGS_QUEUE_AT_HEAD (block layer queueing) + - sg_lib_data: sync asc/ascq codes with T10 20140516 + - sync operation code with T10 20140515 + - add id string for SPC-5 + - scripts/59-scsi-sg3_utils.rules: removed + - functionality split into two scripts: + 55-scsi-sg3_id.rules + 58-scsi-sg3_symlink.rules + - examples/sg_persist_tst.sh: add --exclusive option + - win32: sg_scan, sg_ses and sg_log fixes + - examples/sgq_dd: re-add old utility as example + +Changelog for sg3_utils-1.38 [20140401] [svn: r563] + - sg_ses: add --dev-slot-num= and --sas-addr= + - fix --data=- problem with large buffers + - new --data=@FN to read hex data from file FN + - error and warning message cleanup + - add --maxlen= option + - sg_inq: add --block=0|1 option to control opens + - add --inhex=FN to read response in ASCII hex from + a file; --inhex=FN --raw reads response in binary + - make -HHH (-HHHH for '-p ai') output suitable for + another sg_inq invocation to use --inhex to decode + - add LU_CONG to standard inquiry response output + - decode ASCII information VPD pages + - add HAW_ZBC in block dev char. VPD page (sbc4r01) + - sync version descriptors dated 20131126 + - allow --page=-1 to force std INQUIRY decoding + - fix overflow in encode_whitespaces + - improve unit serial number display (VPD page 0x80) + - sg_vpd: add LU_CONG to standard inquiry response output + - add --inhex=FN to read response in ASCII hex from + a file; --inhex=FN --raw reads response in binary + - decode Third Party Copy (tpc) page + - add HAW_ZBC in block dev char. VPD page (sbc4r01) + - add LTO and DDS vendor pages + - allow --page=num to restrict --enumerate output + - sg_persist: add PROUT: Replace Lost Reservation (spc4r36) + - add --transport-id= for SOP: 'sop,' + - sg_readcap: for --16 show physical block size if + different from logical block size + - sg_xcopy: environment variables: XCOPY_TO_SRC and + XCOPY_TO_DST indicate where xcopy command is sent + - change default to send xcopy to dst (was src) + - improve CL handling of short options (e.g. '-vv') + - sg_luns: guard against garbage response + - sg_decode_sense: with --nospace ignore spaces on + command line, so multiple arguments are concatenated + - sg_write_same: repeat if unit attention + - sg_rtpg: fix indexing bug with --extended option + - sg_logs: placeholder for pending defects lpage + - sg_unmap: fix another problem with --grpnum= option + - sg_lib.h: add PDT_ZBC define (spc4r36p) + - sg_lib_data: sync asc/ascq codes with T10 dated 20140320 + - add pdt string for ZBC (spc4r36p) + - sg_lib: extensions to sg_get_num() and sg_get_llnum() + - sg_cmds_extra: fix sa bug in sg_ll_3party_copy_out() + - scripts/rescan-scsi-bus.sh: check if FC driver exports + issue_lip before using it + - man page added (Linux only) + - scripts/59-scsi-sg3_utils.rules: linux specific udev rules + - examples: add sg_tst_excl3 for testing O_EXCL + - improve sg_tst_excl and sg_tst_excl2 + - add sg_tst_context for testing file handle contexts + - upgrade automake to version 1.13.3 + - add suse directory and 'spec' file to facilitate builds + +Changelog for sg3_utils-1.37 [20131014] [svn: r522] + - sg_compare_and_write: fix wrprotect setting + - add --quiet option to suppress miscompare report + - merge features from another implementation + - sg_inq: fix referrals VPD page + - dev_id VPD: T10 vendor id designator clean up + - sg_logs: improve for tape drives, general cleanup + - sg_persist: fix core dump on -Q option + - sg_unmap: fix core dump on -g option + - sg_vpd: dev_id VPD: T10 vendor id designator clean up + - cleanup up dev_id NAA-3: locally assigned + - sg_ses: add --nickname and --nickid options + - eiioe added to additional element status page (ses3r6) + - multiple --filter options to prune output + - sg_verify: improve miscompare handling + - rename --btychk=ndo option to --ndo=ndo (hide former) + - add --quiet option + - sg_xcopy: allow sg and bsg devices + - fix for bpt going negative + - limit each XCOPY(LID1) command to 65535 blocks + - fix for seek in multi-segment copies + - sg_sanitize: skip 15 second safety delay with --fail + - sg_libs: extended copy opcode renamed (spc4r34) + - sg_ll_receive_copy_results(): expand for all sa_s + - add sg_get_sense_key() + - add sg_ll_3party_copy_out() + - add dStrHexErr(): ascii hex to stderr + - add dStrHexStr(): ascii hex to string + - add SG_LIB_CAT_MISCOMPARE to categories + - clean header files + - sg_pt_freebsd: sanity check on sense_resid; fix leaks + - scripts/rescan-scsi-bus.sh KG's v1.57 + HR patch + - improve wlun handling, detect updated and resized + devices, better multipath support + - Makefile.am cleanup + - examples: add sg_tst_excl and sg_tst_excl2 + +Changelog for sg3_utils-1.36 [20130531] [svn: r497] + - sg_vpd: Protocol-specific port information VPD page + for SAS SSP, persistent connection (spl3r2), power + disable (spl3r3) + - block device characteristics: add FUAB bit + - sg_xcopy: handle more descriptor types; handle zero + maximum segment length; allow list IDs to be disabled; + improve skip/seek handling; allow xcopy on destination + - sg_reset: and --no-esc option to stop reset escalation + - clean up cli, add long option names + - sg_luns: add --test=ALUN option for decoding LUNs + - decoded luns output in decimal or hex (if -HH given) + - add '--linux' option to show Linux LUN after T10 + representation, can map one to the other + - sg_inq: add --vendor option to show standard inquiry's + vendor specific fields in ASCII + - take resid into account with response output + - sg_sync: add --16 (for 16 byte command) and --timeout= + - sg_logs: add data compression page (ssc4) + - sg_sat_set_features: increase --lba from 1 to 4 bytes + - sg_write_same: add --ndob option (sbc3r35d) + - sg_map: mark as deprecated + - sginfo: mark as deprecated, especially -l (list) + - sg_lib: improve snprintf handling + - sg_lib_data: sync asc/ascq codes with T10 20130117 + - sg_cmds (lib): if noisy given, give more UA info + - make code more C++ friendly + +Changelog for sg3_utils-1.35 [20130117] [svn: r476] + - sg_compare_and_write: new utility + - sg_inq+sg_vpd: block device characteristics VPD page: + add product_type, WABEREQ, WACEREQ and VBULS fields + - sg_inq: more --export option changes for udev + - sg_vpd: add more rdac vendor specific vpd pages + - sg_verify: add --ebytchk option for sbc3r34 changes + - sg_stpg: --offline option: fix 'Invalid state 0xe' + - sg_ses: Door Lock element changed to Door element and + abbreviation changed from 'dl' to 'do' (ses3r05) + - archive/rescan-scsi-bus.sh: upgrade to version 1.53hr + - move rescan-scsi-bus.sh to scripts directory + - sync to sbc3r34 + - sg_lib: sg_ll_verify10+16 expand BYTCHK to 2 bit field + - sg_pt_win32, sg_scan(win32): changes for cygwin 1.7.17 + - clean up man page summary lines + +Changelog for sg3_utils-1.34 [20121013] [svn: r461] + - sg_xcopy: new dd like utility for extended copy command + - sg_copy_results: new utility for receive copy results + - sg_verify: add 16 byte cdb, bytchk (data-out buffer) + and group number support + - sync to spc4r36 and sbc3r32 + - sg_inq: add --export so sg_inq can replace udev's scsi_id + - decode old EMC Symmetrix abuse of VPD page 0x83 + - sg_vpd: decode old EMC Symmetrix abuse of VPD page 0x83 + - sg_ses: increase max dpage response size to 64 KB + - allow ident,locate on enclosure controller + - more sanity for additional element status descriptor + - sg_sanitize: add --ause, --fail and --test= + - sg_luns: add long extended flat space addressing format + - sg_logs: add ATA pass-through results lpage (SAT-2) + - sg_rtpg: add --extended option + - sg_senddiag: list rebuild assist diag page name + - sg_pt_linux: expand DID_ (host_byte) codes + - cope with a transport error plus sense data + - prefer major() over MAJOR() macro + - sg_lib: fix sg_get_command_name() service actions + - report sdat_ovfl bit (if set) in sense data + - decode extended_copy and receive_copy service actions + - decode read_buffer and write_buffer modes + - decode ATA PT fixed format sense (SAT-2) + - sg_cmds_extra: add sg_ll_report_tgt_prt_grp2() + - ./configure options: + - change --enable-no-linux-bsg to --disable-linuxbsg + - add --disable-scsistrings to reduce utility sizes + +Changelog for sg3_utils-1.33 [20120118] [svn: r435] + - sg_ses: major rework of indexes (again), now two level + - sg_write_buffer: new --specific option for mode specific + field; new mode 13 (spc4r32) + - sg_vpd: add hp3par volume info vendor VPD page + - fix 'scsi ports' [0x88] page problem + - add 'sinq' pseudo page for standard inquiry response + - add power consumption page + - sg_format: add --poll= option for request sense polling + - improve handling of disks > 2 TB and DIF (protection) + - sg_logs: LB provision lpage extra (sbc3r28) + - sg_modes: application tag mpage subcode 0xf0->0x2 + - sg_write_same: no prot fields when wrprotect=0 + - sg_get_lba_status: reflect change in sbc3r25 to Parameter + Data Length response field (offset reduced from 8 to 4) + - sg_inq, sg_vpd: sync with spc4r33 + - win32: change DataBufferOffset type per MSDN; caused + problem with 64 bit machines (with buffered interface) + - sg_luns: tweak documentation for vendor specific reports + - add man pages for scsi_loging_level, scsi_mandat, + scsi_satl and scsi_temperature + +Changelog for sg3_utils-1.32 [20110730] [svn: r410] + - sg_sanitize: new utility for command added in sb3r27 + - sg_sat_identify: add '--ident' to output WWN + - sg_ses: major rework of descriptor output + - add --index, --descriptor, --join, --clear, --get, + and --set options + - sg_raw: exit status corrections + - sg_decode_sense: add --nospace and --hex options + - sg_logs: fix bug with large --maxlen + - zero response length when resid implies it is invalid + - add scope field to lb provisioning lpage (sb3r27) + - sg_inq: sync version descriptors with spc4r31 + - sg_lib_data: sync asc/ascq codes with spc4r31 + - sg_vpd: add LBPRZ field in LB provisioning VPD page + - sg_format: allow format of pdt 7 (some MO drives) + - sg_cmds_basic: sg_cmds_process_resp() handle status good + with a sense key other than no_sense (e.g. completed) + - add README.iscsi + +Changelog for sg3_utils-1.31 [20110216] [svn: r386] + - sg_decode_sense: new utility to decode sense data + - sg_vpd: LB provisioning + Block limits pages (sbc3r26) + - sync asc/ascq and version descriptors with spc4r28 + - sg_get_config, sg_rmsn, sg_verify: add --readonly option + - sg_lib: implement forwarded sense data descriptor + - decode user data segment referral sense data descriptor + - sg_lib, sg_turs, sg_format: more precision for progress + indication (two places after decimal point) + - sg_lib(win32): add runtime selection of SPT direct or + indirect interface + - sg_read_buffer+sg_write_buffer: set SPT direct + - add examples/forwarded_sense.txt + examples/ref_sense.txt + +Changelog for sg3_utils-1.30 [20101111] [svn: r363] + - sg_referrals: new utility for REPORT REFERRALS + - sbc3r25 renames 'thin' provisioning' to 'logical block + provisioning': changes in sg_format, sg_inq, sg_logs, + sg_modes, sg_readcap, sg_vpd + - sg_inq: update version descriptor list to spc4r27 + - extended inquiry vpd page add extended self test + completion minutes field + - sg_lib: sync asc/ascq list to spc4r27 + - dStrHex(): trim excess trailing spaces + - sg_read_long: add --readonly option (open() is rw) + - sg_raw: add --readonly option (open() is rw) + - allow bidirectional commands + - sg_vpd: rdac vendor page [0xc8] parse corrections + - extended inquiry vpd page add extended self test + completion minutes field + - sg_ses: expand --data (in) buffer to 2048 bytes + - sg_opcodes: add extended parameter data for TMFs (spc4r26) + - sg_dd: clean count calculation, document nocache flag + - treat bsg devices as implicit sg_io + - add more conversions + - sg_write_same: if READ CAPACITY(16) fails try 10 byte variant + - anticipate approval of proposal to allow UNMAP and ANCHOR + bits to be set on WRITE SAME(10) with '--10' option + - sg3_utils man page: sections added for OS device names + +Changelog for sg3_utils-1.29 [20100406] [svn: r334] + - sg_rtpg: new logical block dependent state and bit (spc4r23) + - sg_start: add '--readonly' option for ATA disks + - sg_lib: update asc/ascq list to spc4r23 + - sg_inq: update version descriptor list to spc4r23 + - sg_vpd: block device characteristics page: fix form factor + - update Extended Inquiry VPD page to spc4r23 + - update Block Limits VPD page to sbc3r22 + - update Thin Provisioning VPD page to sbc3r22 + - Automation device serial number and Data transfer device + element VPD pages (ssc4r01) + - add Referrals VPD page (sbc3r22) + - sg_logs: add thin provisioning and solid state media log pages + - addition of IBM LTO specific log pages + - sg_modes: new page names from ssc4r01 + - sg_ses: sync with ses3r02 (SAS-2.1 connector types) + - sg_unmap: add '--anchor' option (sbc3r22) + - sg_write_same: add '--anchor' option (sbc3r22) + - sg_pt interface: add set_scsi_pt_flags() to permit passing + through SCSI_PT_FLAGS_QUEUE_AT_TAIL and AT_HEAD flags + - add examples/sg_queue_tst+bsg_queue_tst for SG_FLAG_Q_AT_TAIL + - add AM_MAINTAINER_MODE to configure.ac to lessen build issues + - add BSD_LICENSE file to this and lib directories, refer to + it from source and header files. Some source has GPL license + +Changelog for sg3_utils-1.28 [20091002] [svn: r315] + - sg_unmap: new utility for thin provisioning + - add examples/sg_unmap_example.txt + - sg_get_lba_status: new utility for thin provisioning + - sg_read_block_limits: new utility for tape drives + - sg_logs: add cache memory statistics log (sub)page + - sg_vpd, sg_inq: extend Block limits VPD page (sbc3r19) + - sg_vpd: add Thin provisioning VPD page (sbc3r20) and + TapeAlert supported flags VPD page + - sg_inq: note VPD page support better in sg_vpd + - sg_persist: add transport specific transportID format + - allow transportIDs to be read from named file + - sg_opcodes: allow --opcode= option to take OP and SA + values (comma separated) + - tweak print format, remove test code + - sg_requests: remove test code in progress calculation + - sg_reset: add target reset option + - sg_luns: reduce default maxlen to 8192 (for FreeBSD) + - sg_raw: extend max cdb length from 16 to 256 bytes + - align heap allocs to page boundaries + - sg_lib: sg_set_binary_mode() needs config.h included + - add progress indication sense data descriptor (0xa) + - change SG3_UTILS_* constants to SG_LIB_* + - decode service actions within persistent reserve in/out + - sync with spc4r21 + - sg_cmds_extra: add sg_ll_unmap() and sg_ll_get_lba_status() + - sg_pt_linux: fix check condition but empty sense buffer; occurred + when sg v3 node used and /usr/include/linux/bsg.h visible + - major() macro grief, if present include and + use MAJOR() instead + - scripts/sas_disk_blink: moved from this package to sdparm + - utils/hxascdmp: in Windows set binary mode on read files + - examples/sg_persist_tst.sh: add PRIN read full status command + - sg_raw,sg_write_buffer,sg_write_long,sg_write_same: in Windows + set binary mode on read files + - sg_pt_win32: default to non-direct variant of SPT interface + - use './configure --enable-win32-spt-direct' to override + - non-direct data length set to 16 KB, extended if required + - debian: incorporate patch from debian sid + +Changelog for sg3_utils-1.27 [20090411] [svn: r250] + - sg_write_same: new utility: 10, 16 and 32 byte cdb variants + - sg_inq: sync version descriptors with spc4r18 + - add power condition VPD page + - expand block limits VPD page (sbc3r18) + - sg_vpd: add power condition VPD page + - expand block limits VPD page (sbc3r18) + - sg_map26: fix for lk 2.6.26 when CONFIG_SYSFS_DEPRECATED_V2 + is not defined + - output cdb when verbose option given + - correct tape minors >= 32 + - sg_dd: flock flag (does LOCK_EX|LOCK_NB) + - switch open on input for sg device nodes: first open + read-write and if that fails try opening read-only + - experiment with of2=OFILE2; add conv=sparse + - use posix_fadvise() to defeat caching of normal+block files + when new 'nocache' flag given + - sg_dd copied to own package called ddpt + - sg_dd, sgm_dd, sgp_dd: accept 'count=-1' for calculate count, + accept '-V' for version string + - sg_get_config: add OSSC feature [mmc6r02] + - sg_modes: add ATA power condition mode page + - sg_logs: protocol specific (SAS) lpage sync to sas2r15 + - power condition transitions lpage (added in spc4r18) + - extra parameters for start-stop cycle counter lpage + - sg_format: add '--fmtpinfo=' and '--pie=' options (sbc3r18) + - sg_readcap: more protection + thin provisioning (sbc3r18) + - add a '--16' option for 16 byte cdb version + - sg_persist: code clean up + - allow '--transport-id=' argument to use space as separator + - add '--alloc-length=' argument + - sg_scan: (win32) new format, scsi adapter scan optional + - sginfo: fix crash when 1024 sg device nodes (or more) + - sg_ses: allow '--data=' argument to use space as separator + - sg_senddiag: allow '--raw=' argument to use space as separator + - sg_reassign: allow '--address=' argument to use space as + separator + - sg_wr_mode: allow '--contents=' and '--mask=' arguments to + use space as separator + - sg3_utils.spec: correction to configure call + - sg_pt: add scsi_pt_open_device_flags() call + - add scsi_pt_version() and clear_scsi_pt_obj() calls + - clear os_err at start of do_scsi_pt() + - add linux bsg support via runtime detection + - sg_cmds: add sg_cmds_open_device_flags() + - sg_cmds_extra: sg_ll_format_unit: remove rto_req argument, + the expanded fmtpinfo argument subsumes it. + - clearer split between Linux and Windows only code and doc + - automake tools: change to what Ubuntu 8.10 provides + - Ubuntu 8.10 libtool problems -> Debian 4.0 + +Changelog for sg3_utils-1.26 [20080625] [svn: r183] + - sg_sat_phy_event: new utility; copied from examples + directory and enhanced, rename original to sg__sat_phy_event + - sg_ses: sync with ses2r19b, many nomenclature changes + - sg_get_config: sync with mmc6r01 + - allow Microcode upgrade and DVD read feature descriptors + to be 4 bytes long + - add '--raw' option + - sg_verify: add --vrprotect= option + - sg_vpd: add nominal form factor to block dev. char. VPD page + - add --maxlen= option to set allocation length in cdb + - sg_inq: add --maxlen= option that does same as --len= + - move version descriptors (spc4r15) to sg_inq_data.c file + - sg_inq+sg_vpd: logic for "NAA-3 Locally assigned" identifier + - update extended inquiry VPD page + - sg_modes: add --maxlen= option to specify allocation length + - sg_start: add '--noflush' and '--mod=PC_MOD' options (sbc3r14) + - sg_request: add a '--progress' option (similar to sg_turs) + - add --maxlen= option to set allocation length in cdb + - sg_luns: add --maxlen= option to specify allocation length + - sg_dd: improve MMC handling of 'illegal mode for this track' + read errors (with ILI and info field) + - sg_dd, sgm_dd, sgp_dd, sginfo, sg_rbuf, sg_read: replace + "%lld" and friends with PRI macros + - sg_opcodes: tmf name change in spc4r15 (async event) + - sg_turs: add more to man page about '--progress' indication + - sg_write_long: add examples section to man page + - '--raw' option: modify utilities that can send binary output + to call sg_set_binary_mode(). For MingGW port CR problem. + - sg_lib: update asc/ascq and command name strings to spc4r15 + - split sg_lib into sg_lib_data.[hc] and sg_lib.[hc] + - split sg_cmds_extra into sg_cmds_extra and sg_cmds_mmc + - add osd2r03 service actions (all different from osd-r10) + - add sg_get_trans_proto_str() + - add sg_get_sense_filemark_eom_ili() function (MMC uses ILI) + - add sense key specific unit attention condition queue + overflow decoding (added in spc4r13) + - add sg_set_text_mode() and sg_set_binary_mode() functions + for non-Unix OSes + - sg_cmds_mmc: add sg_ll_set_streaming() function + - sg_cmds_extra: add vrprotect argument to sg_ll_verify10() + - add sg_ll_get_performance() and sg_ll_set_cd_speed() + - change 'long long' to int64_t and 'unsigned long long' to + uint64_t to stress that 64 bit integer wanted, not larger + - audit of dangerous 'u64 = uch[24] << 24' code, replace most + 'unsigned long's + - multiple documentation corrections provided by Dan Horak + - win32/MinGW: define SG3_UTILS_MINGW when detected + - remove archive/pre_configure subdirectory + - move sg_io_linux.c into the lib subdirectory + - utils/hxascdmp: add hxascdmp(1) man page + - switch primary build to ubuntu environment, rename + library to libsgutils2 to avoid clash + +Changelog for sg3_utils-1.25 [20071016] [svn: r115] + - sg_stpg: new utility to Set Target Port Groups + - sg_safte: new utility to query SAF-TE processor (SES like) + - sg_sat_set_features: new utility (actually copied from examples + directory); renamed examples version to: sg__sat_set_features + - sg_read_buffer: restore (had fallen out of build scripts) + - sg_dd: add oflag=sparse to step over bs*bpt number of zeros; + - with oflag=sparse, write last bs*bpt segment at end or after + error so file length of OFILE is appropriate + - when coe>1 then SCSI READ LONG logic remembers extended block + length of first encountered error + - sg_dd, sgm_dd, sgp_dd: allow iflag=null and oflag=null both of + which do nothing (placeholders) + - sg_ses: sync with ses2r17 then r18 + - sg_vpd, sg_inq: add block device characteristics VPD page + - sg_inq: add '--vpd' option (or '-e') for backward compatibility + - sg_vpd: decode protocol specific lu information page for SAS + - add more RDAC vendor VPD pages + - sg_logs: update background scan results log page, sbc3r11 + - add generation code to protocol specific page for SAS SSP + - add media changer diagnostic data log page + - sg_raw: fix error message when do_scsi_pt() fails + - sg_lib: sync asc/ascq codes with spc4r11 + - add sg_get_num_nomult() + - add TPROTO_* protocol identifier constants to sg_lib.h + - sg_cmds_extra: add sg_ll_set_tgt_prt_grp() + - place source in subversion repository + - split code into src/ lib/ and include/ directories + - sync debian directory with their 1.24 version (sid unstable) + - convert build logic to use autotools (i.e. './configure ; make') + - rename this file from CHANGELOG to ChangeLog + - note: only code in lib/ and src/ directories built by + autotools; some other subdirectories still use hand-crafted + Makefiles + +Changelog for sg3_utils-1.24 [20070507] [svn: r77] + - sg_raw: new utility to send arbirary SCSI commands + - sg_luns: increase number of luns that can be fetched + - fix length of raw and hex output + - add '--quiet' option to output only ASCII hex + representation of each lun + - sg_rtpg: update for changes in spc4r09 + - sg_persist: update documentation, spc-4 references + - fix exit status values + - sg_inq: update version descriptors per spc4r09 + - fix '--id' and '--extended' + - extend block limits VPD page (sbc3r09) + - sg_vpd: extend block limits VPD page (sbc3r09) + - append relative target port identifier to SAS target + port address with '-iq' option + - sg_logs: add decoding for stats+performance log pages + - fix showing of page names for pdt > 0 + - implement '-HH' for single and all pages, fix '-r' + - when '--maxlen=' given, only do single fetch + - add Tape Alert (ssc), Media and Element statistics (smc) pages + - add '--brief' option + - sg_ses: sync with ses2r16 + - fix bay number for SAS + - sg_format: add '--dcrt' and '--security' options + - sgm_dd: add 'smmap' oflag for shared_mmap_io testing + - add 'dio' oflag + - sg_dd, sgp_dd: add 'dio' iflag and oflag + - sg_modes: change SAS mode page names per sas2r09 + - check validity of block descriptors length + - sg_pt: change opaque context object from 'void *' + to 'struct sg_pt_base *' + - sg_opcodes: anticipate extra tmfs from 07-159r0 + - sg_sat_set_features: add more usage information + - add man page + - sg_sat_phy_event: add to examples directory + - sg_lib: sync asc/ascq codes with spc4r10 + - Solaris port: using uscsi interface + - various .html files removed from doc directory + +Changelog for sg3_utils-1.23 [20070131] [svn: 75] + - sg_read_buffer: new utility + - sg_write_buffer: new utility + - sg_opcodes, sg_senddiag, sg_logs, sg_modes, sg_start, sg_inq, + sg_turs, sg_readcap, sg_rbuf: add getopt_long() based cli; + old and new cli selectable, new getopt_long cli is default + - scripts: new subdirectory containing some bash scripts + - add scripts/README file + - sg_reassign: add '--hex' option for grown and primary lists + - sg_rtpg: add '--raw' option + - sg_lib.h, sg_cmds_basic.h + sg_cmds_extra.h: add C++ + 'extern "C" ' wrappers + - cleanup C code so it will compile as C++ + - sg_lib: sync with spc4r08 + - include , use PRId64 instead of %lld form + - fix sg_get_sense_str() when empty sense buffer + - win32 port: add Makefile.mingw + related support for MinGW + - sg_cmds_extra: add sg_ll_read_buffer() and sg_ll_write_buffer() + - sg_dd, sgp_dd, sgm_dd, sg_read: use lseek64() instead of llseek.c + - sgm_dd: accept coe= for interworking with sg_dd + - sg_rdac: fix on non-linux ports + - sg_ses: fix spurious warning in additional element status page + - '-rr' option outputs a diagnostic page in binary to stdout + - sg_opcodes: add command timeout descriptor support (spc4r08) + - change linux specific pass through to generic pass through + - sg_logs: add 'name=value' decoding for SAS specific lpage + - examples+utils subdirectories: remove symlinks + - synchronize man pages with usage messages + - sg3_utils.spec: rework + +Changelog for sg3_utils-1.22 [20061016] [svn: 72] + - sgp_dd: accept verbose= as well as deb= to ease + interworking with sg_dd and sgm_dd + - sg_sat_set_features: added to examples directory + - sg_lib: sync asc/ascq text with spc4r06 + - move SG_LIB_CAT_NO_SENSE and SG_LIB_CAT_RECOVERED to + 20 and 21 respectively; add SG_LIB_CAT_ABORTED_COMMAND + at 11 (its sense key value) + - sg_vpd: tweak '--page=sp --quiet' output + - change '-HHH' so same as '-rr' (prepares ATA Information + (ai) response for hdparm) + - sg_requests: add '-s' option to set exit status from + parameter data + - sg_modes: exit quickly from '-e' if device not ready + - sg_logs: sync sas log pages with sas2r05a + - expand background scan results log page + - add '-m=' to limit response length + - drop '-scum' and '-sthr' options and add '-select' + - sg_write_long: add '--16' option to send 16 byte cdb + - add '--wr_uncor' and '--pblock' options + - sg_senddiag: cleanup and add sdiag_sas_p1_stop.txt + to examples directory + - sg_format: add '--cmplst=' option (default: 1) + - add '--pfu=' option + - expand man page to discuss P/D/C/GLISTs + - sg_reassign: add '--primary' option to fetch primary + defect list (PLIST) length + - sg_readcap: add '-H' option to output response in hex + and '-r' to output response in binary to stdout + - add logical blocks per physical block (sbc3r07) + - sginfo: add PLIST and GLIST designation to defect lists + - sg_cmds: split this support file into sg_cmds_basic.[hc] + and sg_cmds_extra.[hc] + - add sg_ll_ata_pt() (SATL ATA pass) to sg_cmds_extra.[hc] + - sg_rdac: fix includes for FreeBSD + - sg_dd: add 'coe_limit=' option to exit after + consecutive 'coe' type read errors + - sgm_dd: print out throughput information when signal arrives + if time=1 (like sg_dd does already) + - sg_inq: change '-HHH' so same as '-rr'. Now sg_inq, sg_vpd + and sdparm output for hdparm with '-HHH' + -add '-l=' option + - sg_read_long: add '--pblock' option for physical blocks + - sg_luns: add '--hex' and '--raw' options + - sg_requests: add '--hex' and '--raw' options + - sg_scan: windows version added (was previously linux only) + - 2 man pages: sg_scan.8l and sg_scan.8w that are installed + as sg_scan.8 + - archive directory: removed all but rescan-scsi-bus.sh + - README points to previous version in that directory + - sg_sat_identify: add to main directory + - rename earlier version to examples/sg__sat_identify.c + - sg_ident: rework as spc4r07 changed command names and + expanded functionality + +Changelog for sg3_utils-1.21 [20060706] [svn: 70] + - sg_vpd: new utility for decoding VPD pages. sg_inq's cli is + cluttered; also borrows from sdparm's VPD handling + - sg_rdac: new utility for vendor specific work + - sg_lib: add sg_vpd_dev_id_iter() to iterate over di VPD page + - add sg_ata_get_chars() to fetch chars from ATA words + - sync additional sense code strings with spc4r05a + - add SG_LIB_CAT_NOT_READY category when sense_key is NOT READY + - add SG_LIB_FILE_ERROR category for open problems + - add SG_LIB_SYNTAX_ERROR category for command line problems + - broaden SG_LIB_CAT_MEDIA_CHANGED to SG_LIB_CAT_UNIT_ATTENTION + - add SG_LIB_CAT_MALFORMED for bad responses + - BEWARE: these changes cause confusion if an executable from this + version is run with a libsgutils library from 1.20 or earlier + - sg_cmds: add SG_LIB_CAT_NOT_READY return to most "ll" functions + - alter many utilities to report SG_LIB_CAT_NOT_READY + - sg_dd: add retries= option for sg_io + - sg_logs: add '-T' option to output protocol specific port log page + - add support for log subpages (new in spc4r05) + - more sanity checks in Start Stop Cycle Counter page + - sg_cmds: add sg_ll_read_long16() + - add page_code and subpage_code to sg_ll_log_select() + - add subpage_code to sg_ll_log_sense() + - sg_read_long: do READ LONG(16) when '--16' given + - sg_read: accept and ignore 'of=' arguments + - sg_dd: expand medium/hardware error "coe' processing to include + the "blank check" sense key (for optical devices) + - sg_ses: expand display element (per 05-011r2) + - sg_format: clear 'cmplst' bit (for MO disks) + - add '--six' ('-6') option for mode sense/select(6) + - sg_format + sg_test_rwbuf: fix for when char is unsigned + - sg_inq: VPD page 0x89: output ATA IDENTIFY DEVICE strings + - for IDENTIFY (PACKET) DEVICE response use sg_ata_get_chars() + - sg3_utils.html : new name, was previously u_index.html. Copy + placed in doc subdirectory + - tools.html : SCSI and storage tools reference, copy placed in + doc subdirectory + - sg3_utils.8 : add a new man page containing general information + especially common exit status values + - sg_sat_identify: added to examples directory (SAT passthrough test) + - extend to pass through IDENTIFY PACKET DEVICE with '-p' option + - sg_sat_chk_power: added to examples directory + - sg_sat_smart_rd_data: added to examples directory + - sg_chk_asc: added to utils directory to check asc_ascq codes + - debian: stop placing archive directory under examples + - add build_debian.sh script + +Changelog for sg3_utils-1.20 [20060418] [svn: 68] + - sg_logs: decode phy event descriptors in SAS port specific + log page (sas2r03) + - new parameter control byte format (spc4r03), subpages to come + - update Makefile (linux) to install sg_io_linux.h + sg_linux_inc.h + - sg_map26: fix for block device mapping in lk 2.6.16-rc1 and beyond + - cope with sysfs removal of 'generic' symlink post lk 2.6.16, + anticipate removal of 'tape' symlink + - sg_dd, sgm_dd, sgp_dd: fix problem around 0x7fffffff blocks + - sg_dd: fix read_long processing error (when 'coe=2' or 3) + - expand 'coe=' to take 0...3 (invokes read long with 2 or 3) + - allow for SG_GET_RESERVED_SIZE yielding 0, lk 2.6.16 feature + - sgp_dd: add 'iflag=' and 'oflag=' arguments; signals (like sg_dd) + - sgm_dd: add 'iflag=' and 'oflag=' arguments; signals (like sg_dd) + - sg_get_config: double->dual renaming (mmc5r03) + - sg_read: add 'dpo=' and 'fua=' options + - allow 'count' < 0 (or 'bpt=0') for issuing zero block READs + - allow for SG_GET_RESERVED_SIZE yielding 0, lk 2.6.16 feature + - add 'no_dxfer=0|1' option + - sg_modes: fix exit value when MODE SENSE fails + - add '-e' to examine presence of page codes from 0x0 to 0x3e + - sg_requests: add '--num=' and '--time' options for timing multiple + invocations + - sg_inq: fix vpd 0x83 designator code 8 name: "scsi name string" + - sg_scan: if lk 2.6, use sysfs to find active sg device nodes + - sg_map: if lk 2.6, use sysfs to find active sg device nodes + - sg_ses: expand display element (per 05-011r1) + - sg_start: add an '-i' option which is equivalent to '--imm=1' + - sg_senddiag: update man page showing use of two scripts in + examples directory (sdiag_sas_p0_cjtpat.txt and _p1_) + - sg_lib: fix sg_get_sense_descriptors_str() case 9 (ATA Return) + +Changelog for sg3_utils-1.19 [20060127] [svn: 66] + - sg_start: accept '--' options (e.g. 'sg_start --stop') + - add '--fl=' option for jump to format layer (mmc5) + - sg_logs: background scan log page, resync with sbc3r03 + - add '-scum' and '-sthr' for setting defaults + - add device statistics log page (ssc + adc) + - fix "last n" deferred errors/error events incrementing + - partial addition of log subpages (spc4r03) + - sg_get_config: sync features with mmc5 rev 02b + - sg_wr_mode: mask out dpofua bit in mode select header + - sg_inq: try harder with '-A' to identify ATA device + - broaden meaning of '-d' option to decode ... + - decode software interface id VPD page ('-p=84 -d') + - decode device capabilities (ssc) VPD page ('-p=b0 -d') + - sginfo: correct defect list handling ('-d' and '-G') + - sg_verify: improve error processing (e.g. medium errors) + - sg_ses: scsi target_initiator port additional element + status (ses2r14) + - many: arguments that currently accept '0x' or '0X' to + indicate a hex number may alternatively take a trailing + 'h' or 'H' to indicate hex + - sg_lib: update asc/ascq strings (spc4r03) + - sg_lib+sg_cmds: make independent of linux via + sg_pt.h function based interface. + - linux pass through code placed in sg_pt_linux.c + - rename sg_include.h to sg_linux_inc.h + - linux specific code in sg_lib.[hc] moved to + sg_io_linux.[hc] + - port to FreeBSD: using sg_pt_freebsd.c + - port to Tru64: using sg_pt_osf1.c + - sg_cmds: add sg_ll_get_config(), sg_ll_format_unit(), + sg_ll_reassign_blocks(), sg_ll_persistent_reserve_in+out(), + sg_ll_read_long10(), sg_ll_verify10(), sg_ll_write_long10() + - sg_persist: add "allow commands" to report capabilities + - sg_persist_tst: (examples) takes device node as argument + - sg_luns: add "security protocol" wlun + +Changelog for sg3_utils-1.18 [20051118] [svn:63] + - sg_map26: new utility to map sg devices in lk 2.6 + - sg_luns: luns > 16,384 (sam-4 rev 4) + - sg_ses: bump fan speed field to 11 bits + - SAS connector names (ses2r13) + - sg_inq: add '-rr' option for "hdparm --Istdin" + - sg_get_config: tracking mmc-5 + - sg_write_long: add support for COR_DIS bit + - sg_cmds: add sg_ll_test_unit_ready_progress() + - sg_turs: '-p' option shows progress + - sg_dd: add 'iflag=' and 'oflag=' options + - remove output of mode page info when verbose > 0 + - add control of DPO bit via iflag/oflag + - sg_lib: add sg_get_pdt_str() + - update asc/ascq strings + - sg_modes + sginfo: add SAS(2) SSP shared mode subpage + - doc: rename "html" directory to "doc" + - Makefile: add 'libtool --finish' to install + +Changelog for sg3_utils-1.17 [20050922] [svn: 60] + - sg_inq: add '-a' option for ATA information VPD page + - add '-b' option for Block limits VPD page (SBC) + - add '-A' option for probing ATA or ATAPI device + - increase raw ('-r') and verbose ('-v') output for + ATA(PI) devices to 512 bytes (was 256 bytes) + - output hex ('-H') and verbose response for ATA(PI) + devices in 16 bit words (corrected for endianness) + - output bytes if '-HH' option given + - sync with spc4 rev 02 + - sg_lib: add dWordHex() and sg_is_big_endian() + - sync asc/ascq with spc4 rev 02 + - sg_cmds: defensive prefill for inquiry commands + - sg_opcodes: sync with spc4 rev 02 (add tmf I_T nexus reset) + - sginfo: add EBACKERR in Informational exception mode page + - add Background control mode page (SBC-3) + - sgm_dd: add 'verbose=' option + +Changelog for sg3_utils-1.16 [20050810] [svn: 58] + - sg_ident: new utility to report+set device identifier + - sg_map: increase MAX_SG_DEVS from 256 to 2048 + - debian: new directory to support deb package builds + - sg_get_config: add '--current' option, same as '--rt=1' + - update for DVD+RW Dual Layer + - sg_inq: add notes in source about use of SCSI INQUIRY + - decode Management network addresses VPD page ('-m') + - decode Mode page policy VPD page ('-M') + - sginfo: increase device mapping capability (> 78 disks) + - add '-r' option to scan /dev/raw* device nodes [Tim Hunt] + - sg_dd: change bpt default value to 32 when bs >= 2048 bytes + - sg_ses: mention SAF-TE in man page + - sg_readcap: add '-b' option for brief output (2 hex numbers) + - sg_cmds: add sg_ll_start_stop_unit(), sg_ll_prevent_allow(), + sg_ll_report_dev_id() and sg_ll_set_dev_id() + - sg_lib: add extra argument to sense print functions to enable + the suppression of the raw output of the sense buffer + - resid > 0 warnings now includes number actually fetched + - sg_start: add '-load' and '-eject' options + - default to start action when no other indication given + - change -imm=0|1 option default to 0 (was 1) + - gcc 4.0: cleanup warnings (apart from sgp_dd: revisit later) + +Changelog for sg3_utils-1.15 [20050605] [svn: 56] + - sg_cmds: sg_get_mode_page_controls(): improve error processing, + add double fetch + - sg_turs, sg_rbuf, sg_requests, sg_test_rwbuf, sg_format, + sg_dd and sgm_dd: add O_NONBLOCK to open() + - sgm_dd: switch to use SG_IO ioctl (that leaves only + sgp_dd using the asynchronous sg write()/read() sequence) + - sg_ses: sync with rev 12 changes + - sg_map: extend to cope with sparse disk device names with + up to 3 letters (e.g. /dev/sdaaa) [Nate Dailey] + - sg_modes: add '-f' option to flexibly decode broken mode + sense responses. + - zero prefill response; stop decoding response after 3 + unit attention mode pages seen (i.e. malformed) + - add '-L' option for LLBAA bit in msense 10 cdb + - sg_reset: update man page + - sg_inq: VPD page 0x83: output eui addresses in hex as well + - Makefile: fix bug in rules for sgp_dd (when 'make dep' used) + - sg_format: expand explanations in its man page + - sg_inq, sg_logs, sg_modes, sg_opcodes, sg_rbuf, sg_readcap, + sg_scan, sg_senddiag, sg_start and sg_turs: allow command + line to take concaternated options + - sg_start: add -start and -stop to parallel "1" and "0" + - sg_senddiag: set pf bit with '-l' option + +Changelog for sg3_utils-1.14 [20050506] [svn: 54] + - sg_rmsn: new utility to read media serial number + - sg_rtpg: add T_SUP bit report + - sg_ses: ses-2 rev 11 changes (mainly to additional element status) + - add 'bay number' to SAS additional element status + - sg_modes: recognise attached enclosure and medium changer + - sg_inq: spell out non-zero peripheral qualifiers + - note VS bit preceding MultiP(ort) when latter set + - VPD page 0x83: output naa addresses in hex as well + - sginfo: recognise attached enclosure and medium changer + - increase device mapping capability (to 78 disks) + [Tim Hunt] + - sg_senddiag: add option to send raw diagnostic page + - sg_get_config: update some BD information + - sg_reasign: add '-g' option to give grown defect list length + - sg_dd: note default bpt value (128) may be too high for cd/dvds + - sg_lib: sync with SPC-3 rev 22a [opcodes + asc/q] + - add DID_IMM_RETRY and DID_REQUEUE [linux specific "host" bytes] + - sg_cmds: add send+receive diagnostic, read defect data commands + - add duration output on some commands when verbose > 2 + - spec: change to produce libsgutils (and -devel variant) as well as + sg3_utils binary rpms + - sdparm: new utility like hdparm but for SCSI disks (or other devices) + - moved to its own package called: sdparm + +Changelog for sg3_utils-1.13 [20050313] [svn: 52] + - sg_format: new utility to format disks (perhaps change block size) + - sg_ses: rename "device element" to "additional element" [SES-2 rev 10] + - add SAS expander and connector elements; add download + microcode and subenclosure nickname diagnostic pages + - fix additional element descriptor for SAS + - off by 1 error when no type descriptor text in config page + + - sg_logs: log page for background media scan results + - sginfo: add "-flba64" option for outputting 64 bit lba defect lists + - sg_get_config: additions for BD from MMC-5 rev 1b + - sg_lib: add SG_LIB_CAT_ILLEGAL_REQ sense category + - add sg_get_sense_progress_fld() + - SPC-3 rev21d updates: report + set timestamp + - sg_get_num() + sg_get_llnum(): switch to multipliers that + are compatible with SI and with IEC 60027-2. Used modern + GNU's dd command as guide. + - report field replaceable unit code in fixed format + - sg_dd: add logic to use read_long on unrecovered read errors when + 'coe' set, read just prior to error if 'coe' clear + - both 'odir' and 'blk_sgio" are honoured on block devices + - add 'verbose' switch, output some mode page info when verbose + - print out elapsed time/throughput when signal received + - add new web page discussing sg_dd, copy in html subdirectory + - sg_read: add 'blk_sgio' and 'odir' options + - sg_wr_mode: clear mode data length in mode select(10) + - sg_test_rwbuf: add long options, allow test to run multiple times + - sg_cmds: add sg_get_mode_page_types() [get current, changeable, etc] + - llseek.c: add Makefile rule without "-std=c99", breaks on some archs + +Changelog for sg3_utils-1.12 [20050121] [svn: 50] + - sg_wr_mode: new utility to modify (i.e. write to) mode pages + - sg_reassign: new utility: issues Reassign Blocks command + - sg_rtpg: new utility: issues Report Target Port Groups command + [Christophe Varoqui] + - ATA IDENTIFY command misspelt as "IDENTITY" in several places + - sginfo: tweak SAS mode pages to match sas 1.1 rev 07 + - add NV_DIS bit to disk caching mode page + - sg_map: open /dev/nst* rather than /dev/st* (to stop spurious rewinds) + - sg_lib: ATA return sense descriptor + - add sg_get_sense_info_fld() to fetch info field from sense data + - fix bug in sg_scsi_sense_desc_find() + - add sense key specific decoding for fixed format sense data + - sg_modes: extend '-p' option to allow '-p=,' + - add '-A' option to output all mode pages and subpages + - extend '-l' option to show subpages, selected command set pages + - sg_inq: fix LUN WWN output in unit path report VPD page (emc) + [Hergen Lange] + - sg_get_config: some additions for DVD-R dual layer + - sg_modes: show write protect (WP) and DpoFua flags for disks + - sg_cmds: add llbaa argument to sg_ll_mode_sense10() + +Changelog for sg3_utils-1.11 [20041126] [svn: 48] + - sg_sync: new utility: invokes the synchronize cache command + - sg_prevent: new utility: invokes the prevent allow medium removal command + - sg_get_config: new utility: get configuration command for dvds and cds + - sg_request: fix, allocation length wasn't set + - sg_start: remove '-s', as start_stop_unit implicitly syncs caches + - sg_ses: add SAS expander element type + - sg_inq: add sanity check to unit serial number (VPD page 0x80) + - output ANSI version string (e.g. "SPC-2", previously was number) + - add '-s' option to decode SCSI Ports VPD page + - sg_logs: decoding of format status and non-volatile cache log + pages (0x8 and 0x17 in sbc-2) + - sg_dd: handle compile error when O_DIRECT not defined + - sginfo: tighten sanity checks around Unit serial number VPD page + +Changelog for sg3_utils-1.10 [20041030] [svn: 46] + - sg_readcap, sg_dd, sgm_dd, sgp_dd: fix sg_ll_readcap_10+16 (sg_cmds.c) + - sg_luns: new utility to report luns + - sg_logs: with '-t' (show temperature) ignore extra parameters in + temperature log page (still show them with '-p=d') + - sg_ses: clean argument sanity checks + - sg_cmds: add more common command wrappers + +Changelog for sg3_utils-1.09 [20041022] [svn: 44] + - sg_ses: new utility to get status and set control on SES devices + - sg_verify: new utility to verify block devices + - sg_emc_trespass: new utility for EMC specific trespass mode page + - sg_request: new utility that sends a REQUEST SENSE command + - sg_logs: '-r' to reset to manufacturer's defaults + - decode last n error events and last n deferred errors pages + - add names of ADC log pages + - sg_inq: update to SPC-3 rev 21 + - decode Extended INQUIRY data VPD page [0x86] {'-x'} + - decode Unit Path Report VPD page [0xc0] (EMC) {'-P'} + - sginfo: decode SAS protocol specific lu mode page + - sg_err: convert to sg_lib + update to SPC-3 rev 21 + - change GPL to FreeBSD license + - flag vendor specific asc/ascq ranges + - libsgutils: library made from sg_lib.c and sg_cmds.c + - rpm "spec" file additionally builds a "-devel" rpm with + static libsgutils.a and sg_lib.h and sg_cmds.h + - utils/hxascdmp.c: add FreeBSD license + - sg_persist: additions to man page + - add sg_persist_tst.sh example script to examples directory + - sg_turs: add '-v' and '-V' options + - sg_senddiag: add '-v' option + +Changelog for sg3_utils-1.08 [20040831] [svn: 42] + - sg_inq: fix noisy message when EVPD and raw modes set + - for VPD page 0, list supported page names if known {'-e'} + - add '-d' option to list version descriptors + - sg_opcodes: numerically sort list of opcodes unless '-u' given + - add '-a' to sort alphabetically list of opcode names + - add '-t' to report supported task management functions + - sg_persist: add 'register and move" PROUT service action + - add transportId support, document in sg_persist.8 and example + - sg_modes: handle subpage code for known pages (e.g. control extension) + - clean up sense buffer handling (allow for descriptor format) + - SPC-3 draft revision 20a updates + - sg_write_long: new utility to exercise WRITE LONG command + - sg_read_long: new utility to exercise READ LONG command + - sg_err.c: fix compile errors when SG_KERNEL_INCLUDES defined in lk 2.6 + - sg_includes.h typedef of u64 for BLKGETSIZE64 ioctl in lk 2.4 + - add safe_strerror(), sg_scsi_normalize_sense(), sg_normalize_sense() + and sg_scsi_sense_desc_find() functions + - add more sense data descriptor format decoding + - move multiple implementations of dStrHex() into sg_err.c + - sg_logs: exit if SCSI INQUIRY fails (e.g. when applied to ATA disk) + - sginfo: bug fixes and SPC-3 revision 20a updates + - add '-E' option to access Control Extension mode (sub)page + - sg_start: change '-d' switch to '-v' (verbose) switch for consistency + - document extra power condition states in man page + - sg_readcap: output rto_en and prot_en bits from long read capacity data + - add COVERAGE file to list SCSI command coverage + +Changelog for sg3_utils-1.07 [20040708] [svn: 40] + - sginfo: clean up inquiry vendor,product,revision strings + - '-Fhead': sort defect list by head + Tom Steudten + - rework sg_err for better command name coverage: service actions, + variable length and peripheral device type + - update asc,ascq codes to SPC-3 revision 19 + - move scsi_devfs_scan to archive directory + - add sg_opcodes utility to list supported operation codes + - add sg_persist utility to support persistent reservations + - add '-i' option to sg_inq to decode device identification VPD page (0x83) + - sg_inq tries an ATA IDENTIFY if SCSI INQUIRY fails + - sg_dd, sgm_dd and sgp_dd calculate block device sizes (if count not given) + - drop SG_GET_VERSION_NUM ioctl guard in most utilities + +Changelog for sg3_utils-1.06 [20040426] [svn: 37] + - sg_logs: some HBAs don't like odd transfer lengths so increment + - do INQUIRY and output product strings + - add ASCII rendering of the Protocol specific SAS page + - add '-v' verbose option to output cdb + - sg_scan: optionally take device file names (e.g. /dev/hdc and /dev/sda) + - only request 36 byte INQUIRY responses + - sg_err: add sg_decode_sense() function + - sg_inq: update output (ref: SPC-3 t10/1416-d rev 17, 28 January 2004) + - remove '-p' option to print out PCI address of host + - add '-v' verbose option to output cdb + - sginfo: allow '-u' to take hex arguments (prefixed by '0x'), + when subpage value is 255 show multiple subpages + - accept /dev/hd? ATAPI devices directly in lk 2.6 + - add '-t [,]' argument; like '-u' but decodes page + if it recognizes it + - drop '-L' argument + - add cd/dvd, tape, SES, more disk and more SPC-3 decoded mode pages + - add transport protocol decoded mode pages for SPI-4, FCP and SAS + - sg_modes: print all subpages when '-subp=ff' is selected + - do INQUIRY and output product strings + - add '-v' verbose option to output cdb + - Makefile: add -W compile flag and fix exposed warnings + - .spec file: change to build on Mandrake without errors + +Changelog for sg3_utils-1.05 [20031112] [svn: 35] + - sginfo: major rework; add IE page, clean up control, cache + + disconnect pages (as per SPC-3 and SBC-2). + - when storing, update saved page (change from previous version) + - use 10 byte mode sense and select by default (override with '-6') + - mode subpage support + - sg_dd, sgm_dd + sgp_dd: + - 64 bit capable (read capacity; count, skip and seek values). + - numerical arguments accept hex (prefixed by '0x' or '0X') + - require bpt > 0 + - fix problem when reading /dev/null + - sg_dd: Treat SIGUSR1 properly: print stats and continue; + - sgp_dd: reduce READ CAPACITY response size to 8 bytes + - sg_read: require bpt > 0 + - sg_test_rwbuf: switch from sg_header to sg_io_hdr interface + N.B. After these changes no sg3_utils utilities (in the main directory) + use the sg_header interface + - sg_scan: switch from sg_header to sg_io_hdr interface + - sg_senddiag: increase extended foreground timeout to 60 minutes + - sg_inq: add names of peripheral device types + - sg_readcap: show total size in bytes, MB, GB + - sg_logs: read log pages twice (first time to get response length), for + fragile HBAs; decode Seagate 0x37 + 0x3e pages; display pcbs + - sg_modes: fix core dump when corrupted response, don't print extra pages + - sg_map: increase sg device scanning from 128 to 256 + - change man page references from lk 2.5 to lk 2.6 + - examples/sg_iovec_tst: added testing sg_iovec (sg_io_hdr iovec's) + [retrospective addition to this log: "#define __user" added into + sg_include.h so user space programs aren't broken if they choose + to include kernel header.] + - utils/hxascdmp: add utility for displaying ASCII hex + +Changelog for sg3_utils-1.04 [20030513] [svn: 33] + - all remaining utilities in the main directory have man pages [thanks + to Eric Schwartz for 7 man pages] + - add CREDITS file + - sg_simple1, sg_simple2, sg_simple3, sg_simple4, sg_simple16 and + scsi_inquiry: moved to the examples directory + - sg_debug: moved to the archive directory + - sg_modes: add '-subp=' for sub page code, suggests 6/10 byte + alternative if bad opcode sense received, flip -cpf flag to -pf, + add page names for most peripheral types + - sg_turs: default '-n=' argument to 1, only when '-n=1' print error + message in full + - sg_logs: print temperature "" for 255, '-t' switch + for temperature (from either temperature or IE log page) + - sg_dd: add '-odir=0|1' switch for O_DIRECT on block devices + - sg_start: add '-imm', '-loej' and 'pc=' switches plus man page + - sg_readcap: add '-pmi' and 'lba=' switches + - open files O_NONBLOCK in sg_inq, sg_modes and sg_logs so they + can be used on cd/dvd drivers when there is no disk present + +Changelog for sg3_utils-1.03 [20030402] [svn: 30] + - sg_senddiag: added, allows self tests and listing of diag pages + - sg_start: changed to use SG_IO so works on block devices + - sg_err: print out some "sense key specific" data [Trent Piepho] + - sg_modes: add "-6" switch for force 6 byte MODE SENSE [Trent Piepho] + - sg_modes: more information on page codes and controls + - sg_inq, sg_modes, sg_logs, sg_senddiag: add man pages + - sg_dd: add "append=0|1" switch for glueing together large files + - note in README about utilities offered by scsirastools package + +Changelog for sg3_utils-1.02 [20030101] [svn: 28] + - sg_inq: check if cmddt or evpd bits ignored + - sg_inq: warn -o= not used for standard INQUIRY + - sg_turs: add -t option to time Test Unit Ready commands + - sg_errs: (used by most utilities) warn if sense buffer empty + - sg_modes: make safe with block SG_IO (bypass SG_GET_SCSI_ID ioctl) + - sg_logs: make safe with block SG_IO, self-test page work + - sg_dd: add "blksg_io=" switch to experiment with block SG_IO + - sg_read: now use SG_IO ioctl rather than sg write/read + - sginfo: fix writing parameters, check for block devices that answer + sg's ioctls, recognize "scd" device names + - sg_map: stop close error report on tape devices + - sg_readcap: make safe with block SG_IO + - sg_start: make safe with block SG_IO + - sg_test_rwbuf: make safe with block SG_IO + +Changelog for sg3_utils-1.01 [20020814] [svn: 27] +---------------------------- + - add raw switch ("-r") to sg_inq [Martin Schwenke] + +Changelog for sg3_utils-1.00 [20020728] [svn: 26] +---------------------------- + - update sg_err [to SPC-3 T10/1416-D Rev 07 3 May 2002] + - "sg_inq -cl" now outputs opcode names + - add "continue on error" option to sg_dd + - add _LARGEFILE64_SOURCE _FILE_OFFSET_BITS=64 defines to Makefile + - drop 'gen' argument from sg_dd and friends, allow any file types + except scsi tape device file names + - treat of=/dev/null as special (skip write). Accept of=. as alias + for of=/dev/null + - decode various log pages in sg_logs + - add 'dio' argument to sgm_dd for testing "zero copy" copies + +Changelog for sg3_utils-0.99 [20020317] +---------------------------- + - add 'fua' and 'sync' arguments to sg_dd, sgp_dd and sgm_dd + - improve sg_inq, add "-cl" and "-36" arguments + - add sg_modes + sg_logs for MODE SENSE and LOG SENSE queries + - add rescan-scsi-bus.sh [Kurt Garloff] to archive directory + +Changelog for sg3_utils-0.98 [20020216] +---------------------------- + - move sg_reset back from archive to main directory + build + - sprintf() to snprintf() changes + - add "time=" argument to sg_dd, sgp_dd and sgm_dd to time transfer + - add man pages for sgm_dd and sg_read, update sg_dd and sgp_dd man pages + - add "cdbsz=" argument to sg_dd, sgp_dd, sgm_dd + sg_read + - add "gen=0|1" argument to sg_dd + +Changelog for sg3_utils-0.97 [20011223] +---------------------------- + - move isosize to archive since introduced into util-linux-2.10s + +Changelog for sg3_utils-0.96 [20011221] +---------------------------- + - add '-p' switch to sg_inq to provide PCI slot_name + - add '-n' switch to scsi_inquiry for non-blocking open + - new sgm_dd (dd variant) using mmap-ed IO + - sg_rbuf now has a '-m' argument to select mmap-ed IO + - sg_rbuf now has a '-t' switch to do timing + throughput calculation + - add sg_simple4 to demonstrate mmap-ed IO on an INQUIRY response + - add sg_simple16 to do a READ_16 (16 byte SCSI READ command) + - mmap-ed IO requires sg version 3.1.22 or later + - add sg_read to read multiple times starting at same offset + +Changelog for sg3_utils-0.95 [20010915] +---------------------------- + - make sg_dd, sgp_dd and archive/sgq_dd warn if dio selected but + /proc/scsi/sg/allow_dio is '0' + - sg_map can now do any INQUIRY (when '-i' argument given) + - expand example in scsi_inquiry + +Changelog for sg3_utils-0.94 [20010419] +---------------------------- + - add sg_start, documented in README.sg_start [Kurt Garloff] + - add osst support in sg_map [Kurt Garloff] + - improvements to sginfo [Kurt Garloff] + +Changelog for sg3_utils-0.93 [20010415] +---------------------------- + - more include file fine tuning + - some "dio" work sg_rbuf + - extend sgp_dd so "continue on error" works on normal files + - introduce sg_include.h to encapsulate sg include problems + - add scsi_devfs_scan + - add sg_bus_xfer to archive directory + - more error info in sginfo + +Changelog for sg3_utils-0.92 [20010116] +---------------------------- + - change sg_err.c output from stdout to stderr + - change sg_debug to call system("cat /proc/scsi/sg/debug"); + - fix in+out totals in sg_dd and sgp_dd when partial transfers + - lower include dependencies in sg_err.h + - add sgq_dd + Makefile to archive directory + +Changelog for sg3_utils-0.91 [20001221] +---------------------------- + - signalling handling added to sg_dd (and documented in sg_dd.8) + - add man page for sg_rbuf (and a small change to its code) + - add "-d" switch to isosize and cope with > 2 GB (and man page) + +Changelog for sg3_utils-0.90 +---------------------------- + - switch from dated versioning. Previous version was sg3_utils001012. + Arbitrarily start at package version 0.90 . Start Changelog. + - incorporate Kurt Garloff's patches from Suse scsi.spm source rpm + compilation: + - add Kurt Garloff's sg_test_rwbuf utility to read and write to disk + buffer + - clean up Makefile to include a "make install" (and also add a + "make uninstall"). + - add "-uno" switch to sginfo + - make raw and sg devices equally acceptable to sg_dd and sgp_dd. + [Raw devices still not as fast as sg devices doing disk to disk + copies in sgp_dd but this may be improved soon. Still faster than + using dd!] + - change lseek() in sg_dd and sgp_dd to _llseek() [using code borrowed + from fdisk] so big disks can be properly offset with 'skip' and + 'seek' arguments. [This change is significant for raw devices and + normal files since sg devices already use 31 bit block addressing.] + - rename sg_s3_inq to sg_inq. This utility allows the INQUIRY response + to be decoded as per SCSI 3 and 4. Also can probe VPD and CmdDt pages. + - change multiplier suffixes on sg_dd, sgp_dd and sg_turs so lower case + "k, m, g" are powers of 2 while "K, M, G" are powers of 10. This idea + borrowed from lmdd (lmbench suite) + - retire a few more less used utilities into the archive directory. + - add man pages for sg_dd, sgp_dd and sg_map diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..0417ee9 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,13 @@ + +SUBDIRS = include \ + lib \ + src \ + doc \ + scripts + +EXTRA_DIST=autogen.sh COVERAGE CREDITS + +distclean-local: + rm -rf autom4te.cache + rm -f build-stamp configure-stamp + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..6a95914 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See the ChangeLog file. diff --git a/README b/README new file mode 100644 index 0000000..8a741e3 --- /dev/null +++ b/README @@ -0,0 +1,522 @@ + README for sg3_utils + ==================== +Introduction +============ +This package contains low level command line utilities for devices that use +the SCSI command set. Originally the SCSI command set was associated +exclusively with the SCSI Parallel Interface (SPI) transport. SPI has now +almost been completely replaced by the Serial Attached SCSI (SAS) transport +which also accepts the SCSI command set. Additionally many other storage +related transports use the SCSI command set (amongst others); examples are +ATAPI devices (CD/DVDs and tapes), USB mass storage devices (including those +using the newer UAS[P]), Fibre Channel disks, IEEE 1394 storage devices (SBP +protocol), iSCSI, FCoE and SOP devices. Even NVMe which has its own command +set accepts SCSI commands in some contexts; one example is for enclosure +management where NVME-MI has SES Send and SES Receive commands. SES refers +to the SCSI Enclosure Services command set. + +This package originally targeted the Linux SCSI subsystem. Since most +operating systems contain a SCSI command pass-through mechanism, many +utilities within this package have been ported. This README mainly +concentrates on Linux: see the README.freebsd file for the FreeBSD port, +README.solaris for the Solaris port, the README.tru64 file for the Tru64 +(OSF) port and README.win32 for the Windows ports (of which there are two +variants). + +Most utilities within the sg3_utils package work at the SCSI command level. +For example the sg_inq utility issues a SCSI INQUIRY command and decodes the +response. The COVERAGE file has a table containing a row for each SCSI +command issued by this package; to the right of each row is the utility +(sometimes more than one) that issue that SCSI command. The COVERAGE file +has a second table for ATA commands usage. + +Some utilities interface at a slightly higher level, for example: sg_dd, +sgm_dd and sgp_dd. These are closely related to the Unix dd command and +typically issue a sequence of SCSI READ and WRITE commands to copy data. +These utilities are relatively tightly bound to Linux and are not ported to +other Operating Systems. A new utility called ddpt (in a package of the same +name) is more generic while still allowing a copy to be done in terms of +SCSI READ and WRITE commands. ddpt has been ported to other OSes. + +License +======= +All utilities and libraries have either a "2 clause" BSD license or are +"GPL-2ed". The "2 clause" BSD license is taken from the FreeBSD project but +drops the last paragraph that directly refers to the "FreeBSD project". +That BSD license was updated from the "3 clause" to the newer "2 clause" +version on 20180119. To save space various source code files refer to a +file called "BSD_LICENSE" in the main, src and lib directories. The author's +intention is that users may incorporate all or part of the code in their work +as they please. Attribution is encouraged. Please check the code as other +contributors (apart from the author) may also have copyright notices. For a +list of contributors see the CREDITS file. + + +Description +=========== +A web site supporting the sg3_utils package can be found at +http://sg.danny.cz/sg/sg3_utils.html . That page has a table of released +versions for download. The most recent release or beta of sg3_utils may +be found on this page: http://sg.danny.cz/sg in the News section. + +The predecessor to this package was called sg_utils. It is described in +http://sg.danny.cz/sg/uu_index.html and old versions can be downloaded +from the Downloads section of http://sg.danny.cz/sg . + +In the Linux 2.4 kernel series these utilities need to use the SCSI generic +(sg) driver to access SCSI devices. The name of this package (i.e. sg3_utils) +refers to version 3 of the SCSI generic (sg) driver which was introduced at +the beginning of the 2.4 Linux kernel series. Significantly this added a new +SCSI command interface structure (i.e. struct sg_io_hdr) that is more +flexible than the older "sg_header" structure found in the sg driver in the +2.2 and earlier Linux kernel series. The sg_io_hdr structure is also more +flexible than the awkward (and limiting) interface to the +SCSI_IOCTL_SEND_COMMAND ioctl supported by the Linux SCSI mid level. The +version 3 sg driver also added the SG_IO ioctl that is synchronous (i.e. it +issues the requested SCSI command and waits for the response (or a timeout) +before the ioctl returns to the user space program that invoked it). The +SG_IO ioctl is now supported in other parts of the Linux kernel in the 2.6 +series. + +In sg3_utils version 1.27 support has been added for the Linux bsg driver +which use the sg version 4 interface. There seems no point in renaming +this package sg4_utils. The existing utilities just silently support either. +Currently the source build must be able to see the /usr/include/linux/bsg.h +file. Then at run time the /proc/devices pseudo file needs to have an entry +for the bsg driver (appeared around lk 2.6.28). With this in place each +utility at run time checks the device it has been given and if it is a char +device whose major number matches the bsg entry in /proc/devices then the +sg v4 interface is used. Otherwise the sg v3 interface is used. + +Utilities that wish to use the asynchronous SCSI command interface (i.e. via +a write() read() sequence) or issue special "commands" (e.g. bus and device +resets) still need to use the Linux sg driver. Note that various +drivers (e.g. cdrom/sr) have different open() flag and permissions policies +that the user may need to take into account. + +If users have problems or questions about them please contact the author. +Documentation for the Linux sg device driver can be found at: +http://sg.danny.cz/sg/p/sg_v3_ho.html . This is written in DocBook and the +original xml can be found in the same directory with the ".xml" extension. +Postscript and pdf renderings are also in that directory. Older documentation +for the sg version 3 driver can be found at: +http://sg.danny.cz/sg/p/scsi_generic_v3.txt . + +To save the repetition of common code (e.g. SCSI error processing) and +reduce the size of the executable files, a shared library called +libsgutils.so (its Linux name) is created during the build process. +That library is built from the contents of the include and lib +subdirectories. The header files in the include subdirectory can be seen +as the API of libsgutils and are commented with that in mind. The SCSI +pass-through code for the supported operating systems is found in the lib +subdirectory with names like sg_pt_linux.c and sg_pt_win32.c . + +Various distributions (of Linux mainly) distribute sg3_utils as 3 +installable packages. One is a package containing the shared library +discussed above (e.g. libsgutils2-2_1.33-0.1_i386.deb). A second package +contains the utilities (e.g. sg3-utils_1.33-0.1_i386.deb) and depends on the +first package). Finally there is an optional package that contains header +files and a static library (e.g. libsgutils2-dev_1.33-0.1_i386.deb). This +final package is only needed to build other packages (e.g. sdparm) that +wish to use the sg3_utils shared library. + +All the utilities in the src subdirectory have "man" pages that are +placed in the doc subdirectory. There is also a sg3_utils (8) man page that +summarizes common facilities including exit statuses. Additional +information (including each utility's version number) can be found towards +the top of each ".c" file corresponding to the utility name. + +The sg driver in Linux can be seen as having 3 distinct versions: + + v1 lk < 2.2.6 sg_header based relatively unchanged since 1992 + v2 lk >= 2.2.6 enhanced sg_header interface structure [1999/4/16] + v3 lk >= 2.4 additional sg_io_hdr interface structure [2001/1/4] + v3 lk >= 2.6 same interface as found in lk 2.4 [2.6.0: 2003/12/18] + +and the bsg driver supports the sg v4 interface and was added around +lk 2.6.28 . This package is targeted at "v3" and "v4". Another package called +"sg_utils" is targeted at "v2" and to a lesser extent "v1". The "sg_utils" +package has a subset of the utilities found in this package. + +In Linux some sg driver ioctls (notably SG_IO) are defined for many block +devices in lk 2.6 series. In practice this means all SCSI block devices, +ATAPI block devices (mainly CD, DVD and BD optical devices) but _not_ ATA +disks, depending on which kernel configuration options, can be accessed by +the utilities in this package. SATA disks that use the libata kernel library +(or some other SCSI to ATA Translation (SAT) Layer (SATL)) accept SCSI +commands and thus are supported. Support for the SG_IO as been added to the +scsi tape driver (st) in lk 2.6.6 . + +In the src directory the bulk of the utilities are written in relatively +clean POSIX compliant C code with Linux specific system calls and structures +removed and placed in Linux specific files in the lib directory. A small +number of utilities in the src directory do contain Linux specific logic +and are not ported to other OSes (e.g. sg_dd). One utility, sg_scan, has +two separate implementations, one for Linux (sg_scan_linux.c) and one for +Windows (sg_scan_win32.c). The src-lib directory split approach allows +FreeBSD, Solaris, Tru64 and Windows specific code to be isolated to a few +files in the lib directory whose interfaces match those of the Linux +specific code. + +Darwin is not supported because the Apple folks do not want to give their +users a pass-through SCSI interface. The author has read about creative +hackers using a VM containing a real OS to circumvent the Apple restriction. + +C standard is C11 +================== +The C code in this package is written for portability rather than speed. +It assumes a level of C99 compliance (the C standard prior to C11) and +favours POSIX system and library calls over OS specific calls. + +The C code is written in a C++ friendly way and is checked from time to +time that it compiles clean with C++. To accommodate C++ certain C99 +constructs such as designated initializers cannot be used. + +The author has not seriously attempted to build this code on MSVC (aka +Visual Studio). There are a few roadblocks (that may be overcome in the +future) that include MSVC being basically a C++ compiler, not a C/C++ +compiler. For some reason MSVC only claims C89 compliance (i.e. the first +C standard from 1989). MSVC 2013 and 2015 are moving closer to C99 +compliance and may be sufficient to compile this package. Another problem +is the assumption of the availability of basic Unix system calls such as +open(). Nearly 20 years ago Microsoft indicated (promised ?) that it +would move in the direction of POSIX compliance, but very little ever +happened. "Talk is cheap, there should be a tax on it." + +Building +======== +This package is designed to be built with the usual: + "./configure ; make ; make install" +sequence. In some situations that may need to be prefixed by a call to +the "./autogen.sh" script which invokes autoconf and automake. That in turn +may require packages containing those utilities to be installed. The +libtool utility is also required. Naturally a C compiler is required +and due to the vagaries of libtool a C++ compiler also. + +The "./configure" takes many command line options with the defaults +being usually sufficient to start with. One quirk is that the location +of the installation is under the /usr/local directory. So the sg_inq +utility will be installed at /usr/local/bin/sg_inq . This is controlled +by the "--prefix=" option which defaults to +"--prefix=/usr/local". As an example to install the executables in /usr/bin +and disable the creation of the shared library (libsgutils.so) this +invocation could be used: "./configure --prefix=/usr --disable-shared". +To reduce the size of an executable as well try this: +"./configure --prefix=/usr --disable-shared --disable-scsistrings". +Also --disable-shared will produce (realtively) "static" executables in +the src directory that are easier to debug. And +"./configure --enable-debug" will compile with more debug type options, +including more compiler checks and defining "DEBUG" within the src and +lib source files. Most utilities in the src directory set '-vv' (i.e. +equivalent to calling "--verbose" twice) when "DEBUG" is set. + +In Linux there are package build files for "rpm" based and for "deb" based +systems. The 'sg3_utils.spec' file in the main directory can be used like +this: 'rpmbuild -ba sg3_utils.spec' in a rpmbuild tree SPECS directory. +To cross build or make a more widely distributable package then the --target +option may be useful: 'rpmbuild --target=i386 -ba sg3_utils.spec' or +'rpmbuild --target=x86_64 -ba sg3_utils.spec' . The sg3_utils.spec file +in the main directory targets Red Hat systems, an alternative "spec" file +for Suse systems has been placed under the 'suse' directory. + +The 'build_debian.sh' script should build several "deb" packages and place +them in the parent directory. In debian based systems doing +a 'apt-get install build-essential' is one way to get most of build +environment needed if it has not already been loaded. There are now some +problems with this script and the superseded Debian 4.0 ("etch"). See +debian/README.debian4 for a workaround. Amongst other things debian +builds are sensitive to the value in the debian/compat file. If it +contains "7" then it works on lenny and gives warning on squeeze (but +fails on the earlier etch). + +Warning +======= +Many devices use SCSI command sets over transport protocols not normally +associated with SCSI (as defined at http://www.t10.org ). Some of these +devices react poorly (e.g. lock up) when sent SCSI commands that they don't +support. Even sending a supported SCSI command with a field set to an +unexpected value can cause problems. [The author is talking about billions +of USB devices with horrible SCSI implementations.] + +For example, all "SCSI" devices must support the INQUIRY command which the +SCSI-2 standard says should request a 36 byte response. However later SCSI +standards (e.g. SPC-2) have increased that length but some SCSI devices lock +up when they receive a request for anything other than a 36 byte response. + +Any well implemented "SCSI" device should react sensibly when a utility in +sg3_utils sends a SCSI command that it doesn't support. Unfortunately this +cannot be guaranteed. + +Prior to lk 2.6.29 USB mass storage limited sense data to 18 bytes which +caused problems for certain types of descriptor based sense data. An +example of this is the SCSI ATA PASS-THROUGH command with the CK_COND bit +set. + + +Utilities +========= +Here is list in alphabetical order of utilities found in the 'src' +subdirectory of the sg3_utils package: + sginfo, sg_bt_ctl, sg_compare_and_write, sg_copy_results, sgm_dd, sgp_dd, + sg_dd, sg_decode_sense, sg_emc_trespass, sg_format, sg_get_config, + sg_get_lba_status, sg_ident, sg_inq, sg_logs, sg_luns, sg_map, sg_map26, + sg_modes, sg_opcodes, sg_persist, sg_prevent, sg_raw, sg_rbuf, sg_rdac, + sg_read, sg_read_attr, sg_readcap, sg_read_block_limits, sg_read_buffer, + sg_read_long, sg_reassign, sg_referrals, sg_request, sg_reset, sg_rmsn, + sg_rtpg, sg_safte, sg_sanitize, sg_sat_identify, sg_sat_phy_event, + sg_sat_read_gplog, sg_sat_set_features, sg_scan, sg_seek, sg_senddiag, + sg_ses, sg_ses_microcode, sg_start, sg_stpg, sg_stream_ctl, sg_sync, + sg_test_rwbuff, sg_timestamp, sg_turs, sg_unmap, sg_verify, sg_vpd, + sg_write_buffer, sg_write_long, sg_write_same, sg_write_verify, + sg_write_x, sg_wr_mode, sg_xcopy, sg_zone + +Each of the above utilities depends on header files found in the 'include' +subdirectory and library code found in the 'lib' subdirectory. Associated +man pages are found in the 'doc' subdirectory. Additional programs found +in the 'archive', 'examples' and 'utils' subdirectories in not build by the +top level build infrastructure. Linux binary distributions of the sg3_utils +package (e.g. "rpm" and debian packages) typically contain the shared +library, the utilities found in the 'src' subdirectory, their associated man +pages and some documentation files (e.g. README, INSTALL, CREDITS, COPYING +and COVERAGE). See the INSTALL file for generic instructions about building +with autotools (e.g. ./configure ). + +Man pages can be read (without building and installing the package) by +going to the 'doc' subdirectory and executing something like this: + $ man ./sg_dd.8 + +To see which SCSI commands (and ATA commands) are used by these utilities +refer to the COVERAGE file. + +Here is a list in alphabetical order of utilities found in the 'examples' +subdirectory: + - sg_excl, scsi_inquiry, sg_sat_chk_power, sg__sat_identify, + sg__sat_phy_event, sg__sat_set_features, sg_sat_smart_rd_data, + sg_simple1, sg_simple2, sg_simple3, sg_simple4, sg_simple5, + sg_simple16 + +Also in that subdirectory is a script to test sg_persist, an example data +file for sg_persist (called "transport_ids.txt") and an example data file for +sg_reassign (called "reassign_addr.txt"). There are several scripts +for 'sg_senddiag -pf -raw=-' that will put some SAS disk phys into +a "compliant jitter tolerance pattern" (CJTPAT). + +The 'testing' subdirectory contains source and a Makefiles to test +kernel pass-through and associated drivers, mainly for Linux. There is +both C code (with the extension ".c") and C++ code (with the extension +".cpp"). There is a "Makefile" to build the C code and a "Makefile.cplus" +to build the C++ code. Both depend on some object files from the "lib" +subdirectory. So a sequence like this may be required prior to invoking +one of the Makefiles in the directory: "cd ; +./configure ; cd lib ; make ; cd ../testing". + +Here is a list in alphabetical order of utilities found in the 'testing' +subdirectory: + - bsg_queue_tst, sg_iovec_tst, sg_queue_tst, sg_sense_tst, + sg_tst_async (C++), sg_tst_context (C++), sg_tst_excl (C++), + sg_tst_excl2 (C++), sg_tst_excl3 (C++) + +The 'utils' subdirectory contains source and a Makefile to build "hxascdmp" +which accepts binary data from stdin (or a file on the command line) and +outputs an ASCII-HEX and ASCII representation of it. It is similar to the +Unix od command. There is also code to sg_chk_asc.c which checks a given +text file (typically a copy of http://www.t10.org/lists/asc-num.txt ) and +checks it against the asc/ascq text strings held in sg_lib_data.c . + +The 'doc' subdirectory contains a README file containing the urls of +various related documents. + +The 'scripts' subdirectory contains some Bourne (bash) shell scripts that +rely on utilities in the main directory. One script uses the sdparm utility. +These scripts are described in the scripts/README file and have usage +messages. + + +Notes for utilities without man pages +===================================== +These utils are found in the 'examples' subdirectory. + +The "scsi_inquiry" program shows the use of the SCSI_IOCTL_SEND_COMMAND +ioctl to send a SCSI INQUIRY command. That ioctl() is supported by the +SCSI sub system mid level and so is common to all sd, sr, st and sg devices. +That ioctl is deprecated in the lk 2.6 series. This program has been placed +in the "examples" subdirectory. + +"sg_simple1" and "sg_simple2" are example programs demonstrating calls +to the SCSI INQUIRY and TEST UNIT READY commands. They only differ in their +error processing: sg_simple1 uses sg_lib.[hc] for error processing while +sg_simple2 does its own more primitive checks. + +"sg_simple3" tests out user space scatter gather added to the version 3 +sg driver. + +"sg_simple4" shows the INQUIRY command using mmap-ed IO to obtain its +response buffer. + +"sg_simple5" also sends and INQUIRY and TEST UNIT READY commands. It +uses the generic pass through mechanism based on sg_pt.h . It will +currently build in Linux and FreeBSD (with "make -f Makefile.freebsd"). +It has extensive error checking code. + +"sg_simple16" attempts to send a 16 byte SCSI command, READ_16, to the +scsi device. This is only supported for lk >= 2.4.15 and for adapter +drivers that indicate that they have 16 byte CDB capability (otherwise +DID_ABORT will appear in the host_status). + +"sg_sat_chk_power" attempts to push an ATA CHECK POWER MODE command +through the SAT-defined ATA PASS_THROUGH (16) SCSI command. That +ATA command needs to read the "FIS" registers after the command is +completed which involves using the ATA Status Return (sense data) +descriptor (as defined in SAT). + +"sg_sat_smart_rd_data" attempts to push an ATA SMART/READ DATA command +through the SAT-defined ATA PASS_THROUGH (16) SCSI command. If +successful, the 256 word (512 byte) response is output. + +"sg_tst_excl" and "sg_tst_excl2" use multiple threads to bombard the +given device with O_EXCL open flags, so only one should succeed at a +time. While holding O_EXCL control a thread attempts a double increment +on an integer in the given LBA. If the integer starts even (after the +first read) then it should remain even if the O_EXCL flag is doing its job. +The "sg_tst_excl" variant uses the Linux SG_IO v3 interface while the +"sg_tst_excl2" uses the more generic sg_pt infrastructure. + +"sg_tst_excl3" is a variant of "sg_tst_excl2". "sg_tst_excl3" only does +the double increment from the first thread, each time using O_EXCL on +open. The remaining threads check the value is even, each time doing +an open without the O_EXCL flag. + +"bsg_queue_tst" sends an INQUIRY command via the Linux SG_IO v4 interface +which is used by the bsg driver. So it will take device names like +"/dev/bsg/6:0:0:0". It tests if sending repeated INQUIRYs with +the BSG_FLAG_Q_AT_HEAD or BSG_FLAG_Q_AT_TAIL flag makes any difference. + +"sg_tst_async" is a test harness for the Linux sg driver. It is multi +threaded, submitting either TEST UNIT READY, READ(16) or WRITE(16) SCSI +commands asynchronously. Each thread opens a file descriptor and submits +those commands up to the queue limit (sg driver has a per file descriptor +queue limit of 16). Multiple threads doing the same thing act as a +multiplier to that queue limit. + + +Command line processing +======================= +These utilities can be divided into 3 groups when their handling of command +line arguments is considered: + - ad hoc, typically in a short form only, sometimes longer (e.g. + "sg_logs -pcb /dev/sdc") + - inspired by the dd Unix command (e.g. sg_dd, sgm_dd, sgp_dd, sg_read) + - recent utilities use "getopt_long" (see "man getopt_long") + type command lines. These have short form (starting with "-") + and corresponding longer form (starting with "--") options. + +The older utilities that use ad hoc options, in alphabetical order: + - sg_emc_trespass, sginfo(1/2), sg_inq, sg_logs, sg_map, sg_modes, + sg_opcodes, sg_rbuf, sg_rdac, sg_readcap, sg_reset, sg_scan (Linux), + sg_senddiag, sg_start, sg_test_rwbuf, sg_turs +In sg3_utils version 1.23 the following utilities from this group were +converted to have a dual getopt_long/ad_hoc interface, defaulting to +the getop_long interface: + - sg_inq, sg_logs, sg_modes, sg_opcodes, sg_rbuf, sg_readcap, + sg_senddiag, sg_start, sg_turs +These can be switched back to the older (backward compatible) ad hoc +interface by defining the SG3_UTILS_OLD_OPTS environment variable +or using '-O' as the first command line option. + +The more recent utilities that use "getopt_long" only are: + - sg_bt_ctl, sg_compare_and_write, sg_decode_sense, sg_format, + sg_get_config, sg_get_lba_status, sg_ident, sg_luns, sg_map26, + sg_persist, sg_prevent, sg_raw, sg_read_attr, sg_read_block_limits, + sg_read_buffer, sg_read_long, sg_reassign, sg_referrals, sg_requests, + sg_rmsn, sg_rtpg, sg_safte, sg_sanitize, sg_sat_identify, + sg_sat_phy_event, sg_sat_read_gplog, sg_sat_set_features, sg_scan(w), + sg_seek, sg_ses, sg_ses_microcode, sg_stpg, sg_stream_ctl, sg_sync, + sg_test_rwbuf, sg_timestamp, sg_unmap, sg_verify, sg_vpd, + sg_write_buffer, sg_write_long, sg_write_same, sg_write_verify, + sg_write_x, sg_wr_mode, sg_zone + + +Dangerous code +============== +This C code snippet: + unsigned char uc = 0x80; + uint64_t ull; + ull = (uc << 24); +Somewhat surprisingly sets ull to: + ull: 0xffffffff80000000 +This result is due to the 'unary conversion' of uc to a (32 bit signed) +'int' before the shift. The resultant type from the shift is also an int +and it has its top bit set so there is sign extension when it is assigned +into a 64 bit unsigned integer. Making sure there is no conversion to 'int' +solves the problem. In this case if uc is declared as unsigned int the +result will be as expected (i.e. 0x80000000). + + +Bypassing the somewhat dangerous shift operators +================================================ +The shift operators in C are "<<" and ">>". They can be dangerous (as shown +in the above section) or tedious and hence error prone to use. However they +are often needed to cope with the translation of integers on the host OS to +the corresponding representation within a SCSI command or parameter data +moved to or from a SCSI device. The Logical Block Address (LBA) is a good +example; it is either 32 or 64 bits long typically (i.e. 4 or 8 bytes +respectively). The host machine representation may be big or little endian +and may prefer or require alignment to a particular memory address boundary +(e.g. module 4 (or in 'C' code: "(lba % 4) == 0")). For SCSI commands and +the parameter data moved to or from a SCSI device, the integer +representation is big endian and it is unaligned. + +Recent versions of this package have replaced the explicit use of the C +shift operators with a group of functions modelled on those found in the +Linux kernel. These functions contain either "get_unaligned" or +"put_unaligned" in their names and are found in the asm/unaligned.h +header. This package contains the sg_unaligned.h header that implements +a similar set of functions. The current implementation favours correctness +over speed. The functions in the package use a "sg_" prefix but otherwise +use the same function name as the Linux kernel for the same action. + +An example of the change made to a snippet of sg_write_buffer.c may +clarify this change. The old code was: + + wbufCmdBlk[3] = (unsigned char)((buffer_offset >> 16) & 0xff); + wbufCmdBlk[4] = (unsigned char)((buffer_offset >> 8) & 0xff); + wbufCmdBlk[5] = (unsigned char)(buffer_offset & 0xff); + +and it has been replaced by: + + sg_put_unaligned_be24(buffer_offset, wbufCmdBlk + 3); + +The Linux kernel only supplies "unaligned" functions for 16, 32 and 64 +bit quantities. SCSI commands also have cases of 24 and 48 bit numbers +so sg_unaligned.h contains support for those plus a variant where the +byte length is passed as an argument. + +The unaligned functions are inlined for speed (at the possible expense of +space) and now have specializations depending whether the host is big or +(more likely) little endian. These functions can be broken down to a +memcpy() and optionally a byte-swap for 16, 32 and 64 bit operations. +The memcpy() takes care of alignment while the byte-swap (bswap_16(), +bswap_32() and bswap_64() ) addresses integer endianness. If the host is +little endian and a little endian variant of the unaligned functions is +requested, then no byte-swap is required. These specializations can be +"compiled out" with this configure option: './configure +--disable-fast-lebe' in which case the classic "C shifting" technique is +used to implement all the unaligned functions. + +Associated with the above change, fixed length integer types seem a better +fit for SCSI command and parameter integers than the traditional integer +types in the C language. Fixed length integer types were standardized in +C99 and require the inclusion of . For example this means for +an integer that will represent a 64 bit LBA, to favour using "uint64_t" +over the "unsigned long long" type. Also "unsigned char" has mostly been +replaced by "uint8_t" as the 8 bit (unsigned) byte type; "char" is still +used for ASCII text. + + +Other SCSI and storage tools +============================ +See http://sg.danny.cz/sg/tools.html + + +Douglas Gilbert +28th June 2018 diff --git a/README.freebsd b/README.freebsd new file mode 100644 index 0000000..7ca25e9 --- /dev/null +++ b/README.freebsd @@ -0,0 +1,145 @@ +Introduction +============ +The FreeBSD port of sg3_utils contains those utilities that are _not_ +specific to Linux. In some cases the FreeBSD camcontrol command supplies +similar functionality; for example 'sg_map' is similar to +'camcontrol devlist'. + +The dd variants from the sg3_utils package (e.g. sg_dd) rely on too many +Linux idiosyncrasies to be easily ported. A new package called 'ddpt' +contains a utility with similar functionality to sg_dd and ddpt is available +for FreeBSD. + +Supported Utilities +=================== +Here is a list of utilities that have been ported: + sg_bg_ctl + sg_compare_and_write + sg_decode_sense + sg_format + sg_get_config + sg_get_lba_status + sg_ident + sg_inq [dropped ATA IDENTIFY DEVICE capability] + sg_logs + sg_luns + sg_modes + sg_opcodes + sg_persist + sg_prevent + sg_raw + sg_rdac + sg_read_block_limits + sg_read_buffer + sg_read_long + sg_readcap + sg_reassign + sg_referrals + sg_rep_zones + sg_requests + sg_rmsn + sg_rtpg + sg_safte + sg_sanitize + sg_sat_identify + sg_sat_phy_event + sg_sat_set_features + sg_seek + sg_senddiag + sg_ses + sg_start + sg_stpg + sg_stream_ctl + sg_sync + sg_turs + sg_verify + sg_unmap + sg_vpd + sg_wr_mode + sg_write_buffer + sg_write_long + sg_write_same + sg_write_verify + sg_write_x + sg_zone + +Most utility names are indicative of the main SCSI command +that they execute. Some utilities are slightly higher level, for +example sg_ses fetches SCSI Enclosure Services (SES) status pages and +can send control pages. Each utility has a man page (placed in +section 8). An overview of sg3_utils can be found at: +http://sg.danny.cz/sg/sg3_utils.html . +A copy of the "sg3_utils.html" file is in the "doc" subdirectory. + + +The executables and library can be built from the source code in +the tarball and installed with the familiar +"./configure ; make ; make install" sequence. If this fails try +running the "./autogen.sh" script prior to that sequence. There +are generic instruction on configure and friend in the INSTALL file. + +Some man pages have examples which use Linux device names which +hopefully will not confuse the FreeBSD users. + +Device naming +============= +In FreeBSD disks have block names like '/dev/da0' with a corresponding +pass-through device name like '/dev/pass0'. Use this command +"camcontrol devlist" to see that SCSI devices available. + +FreeBSD installation +==================== +The traditional './configure ; make ; make install' sequence from the +top level of the unpacked tarball will work on FreeBSD. But the man pages +will be placed under the /usr/local/share/man directory which unfortunately +is not on the standard manpath. One solution is to add this path by +creating a file with a name like local_share.conf in the +/usr/local/etc/man.d/ directory and placing this line in it: + MANPATH /usr/local/share/man + +FreeBSD 9.0 has a "ports" entry for sg3_utils under the +/usr/ports/sysutils directory. It points to version 1.28 of sg3_utils +which is now a bit dated. It could be used as a template to point +to more recent versions. + +kFreeBSD +======== +sg3_utils can be built into a Debian package for kFreeBSD using the +./build_debian.sh script in the top level directory. This has been tested +with Debian 6.0 release. + +Details +======= +Most of the ported utilities listed above use SCSI command functions +declared in sg_cmds_*.h headers . Those SCSI command functions are +implemented in the corresponding ".c" files. The ".c" files pass SCSI +commands to the host operating system via an interface declared in sg_pt.h . +There are currently five implementations of that interface depending on +the host operating system: + - sg_pt_linux.c + - sg_pt_freebsd.c + - sg_pt_osf1.c [Tru64] + - sg_pt_win32.c + - sg_pt_solaris.c + +The sg_pt_freebsd.c file uses the FreeBSD CAM SCSI pass through mechanism. +Hence only FreeBSD device nodes that support CAM can be used. These can be +viewed with the "camcontrol devlist" command. To access ATAPI devices (e.g. +ATAPI DVD drives) the kernel may need to be configured with the "atapicam" +device. + +Attempts to send SCSI commands with data-in or data-out buffers around 64 KB +and larger failed on a FreeBSD 7.0 with an "argument list too long" error +message. There is an associated kernel message (viewable with dmesg) that an +attempt has been made to map bytes which is greater than +DFLTPHYS(65536). Still a problem in FreeBSD 8.1 . Due to CAM overhead the +largest power of 2 that can fit through with one command is 32768 bytes (32 +KB). + +FreeBSD 9.0 is the most recent version of FreeBSD tested with these +utilities. + + + +Douglas Gilbert +27th January 2018 diff --git a/README.iscsi b/README.iscsi new file mode 100644 index 0000000..917190a --- /dev/null +++ b/README.iscsi @@ -0,0 +1,37 @@ +iSCSI support for sg3-utils is available from external patches. + +To build sg3-utils from sources and activate built-in iSCSI support +you need both sg3-utils and the external user-space iSCSI library hosted at : + +https://github.com/sahlberg/libiscsi + +This library provides a client library for accessing remote iSCSI +devices and also comes with patches to the sg3-utils source code +distribution to compile a special version of sg3-utils with iSCSI +support. + +No support for iSCSI is provided by the sg3-utils maintainer. + + + +Once sg3-utils is compiler and installed with libiscsi support, you +can specify remote iSCSI devices through a special URL format instead +of the normal /dev/* syntax. + +Example: + +sg_inq iscsi://ronnie%password@10.1.1.27/iqn.ronnie.test/1 +standard INQUIRY: + PQual=0 Device_type=0 RMB=0 version=0x05 [SPC-3] + [AERC=0] [TrmTsk=1] NormACA=0 HiSUP=0 Resp_data_format=2 + SCCS=0 ACC=0 TPGS=0 3PC=0 Protect=0 BQue=0 + EncServ=0 MultiP=0 [MChngr=0] [ACKREQQ=0] Addr16=0 + [RelAdr=0] WBus16=0 Sync=0 Linked=0 [TranDis=0] CmdQue=1 + [SPI: Clocking=0x0 QAS=0 IUS=0] + length=66 (0x42) Peripheral device type: disk + Vendor identification: IET + Product identification: VIRTUAL-DISK + Product revision level: 0001 + Unit serial number: beaf11 + + diff --git a/README.sg_start b/README.sg_start new file mode 100644 index 0000000..c4b663f --- /dev/null +++ b/README.sg_start @@ -0,0 +1,38 @@ +Hi, + +you can use sg_start to start (spin-up, 1) and stop (spin-down, 0) devices. +I also offers a parameter (-s) to send a synchronize cache command to a +device, so it should write back its internal buffers to the medium. + +Be aware that the Linux SCSI subsystem at this time does not automatically +starts stopped devices, so stopping a device which is in use may have fatal +results for you. + +So, you should apply with care. +I use it in my shutdown script at the end (before the poweroff command): + +# SG_SHUG_NOS is set in my config file rc.config +# SG_SHUT_NOS="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15" +if test -x /bin/sg_start; then + if test "`basename $command`" = "reboot"; then + for no in $SG_SHUT_NOS; + do /bin/sg_start /dev/sg$no -s >/dev/null 2>&1; + done + else + for no in $SG_SHUT_NOS; + do /bin/sg_start /dev/sg$no -s 0 >/dev/null 2>&1; + done + fi +fi + +Enjoy! + Kurt Garloff + + +Postscript +========== +sg_start has been reworked to allow a block device (e.g. /dev/sda) in +addition to the sg device name (e.g. /dev/sg0) in the lk 2.6 series. +sg_start now has more command line options, see its man page. + + Douglas Gilbert 2004/5/8 diff --git a/README.solaris b/README.solaris new file mode 100644 index 0000000..15e6fec --- /dev/null +++ b/README.solaris @@ -0,0 +1,166 @@ +Please Note: +>>> Up to and including sg3_utils-1.33 the Solaris code was built +>>> and tested on an OpenSolaris VM run with VirtualBox on Ubuntu +>>> 11.10 . Now with Ubuntu 12.04 those VMs crash immediately when +>>> started with VirtualBox. Further, Oracle (who owns SUN and thus +>>> Solaris) no longer supports OpenSolaris and its package +>>> repository has been withdrawn. The author can find no generic VMs +>>> for Oracle Solaris 11 that run on VirtualBox or VMWare. The author +>>> is also displeased with the withdrawal of the Open Software +>>> OS and is disinclined to build a Solaris 11 system just to +>>> virtualize it. +>>> So as of sg3_utils-1.34 the Solaris port is provided "as-is" without +>>> testing on a Solaris platform. + +Douglas Gilbert +13th October 2012 + + + +Introduction +============ +The Solaris port of sg3_utils contains those utilities that are +_not_ specific to Linux. + +The dd variants from the sg3_utils package (e.g. sg_dd) rely on too many +Linux idiosyncrasies to be easily ported. A new package called 'ddpt' +contains a utility with similar functionality to sg_dd and is available +for Solaris. + +Supported Utilities +=================== +Here is a list of utilities that have been ported: + sg_bg_ctl + sg_compare_and_write + sg_decode_sense + sg_format + sg_get_config + sg_get_lba_status + sg_ident + sg_inq [dropped ATA IDENTIFY DEVICE capability] + sg_logs + sg_luns + sg_modes + sg_persist + sg_opcodes + sg_prevent + sg_raw + sg_rdac + sg_read_block_limts + sg_read_buffer + sg_read_long + sg_readcap + sg_reassign + sg_referrals + sg_rep_zones + sg_requests + sg_rmsn + sg_rtpg + sg_safte + sg_sanitize + sg_sat_identify + sg_sat_phy_event + sg_sat_set_features + sg_seek + sg_senddiag + sg_ses + sg_start + sg_stpg + sg_stream_ctl + sg_sync + sg_turs + sg_unmap + sg_verify + sg_vpd + sg_wr_mode + sg_write_buffer + sg_write_long + sg_write_same + sg_write_verify + sg_write_x + sg_zone + +Most utility names are indicative of the main SCSI command +that they execute. Some utilities are slightly higher level, for +example sg_ses fetches SCSI Enclosure Services (SES) status pages and +can send control pages. Each utility has a man page (placed in +section 8). An overview of sg3_utils can be found at: +http://sg.danny.cz/sg/sg3_utils.html . +A copy of the "sg3_utils.html" file is in the "doc" subdirectory. + + +The executables and library can be built from the source code in +the tarball and installed with the familiar +"./configure ; make ; make install" sequence. If this fails try +running the "./autogen.sh" script prior to that sequence. There +are generic instruction on configure and friend in the INSTALL file. + +Some man pages have examples which use Linux device names which +hopefully will not confuse the Solaris users. + +Device naming +============= +In Solaris, SCSI device names below the '/dev' directory have a +form like: c5t4d3s2 where the number following "c" is the controller +(HBA) number, the number following "t" is the target number (from +the SCSI parallel interface days) and the number following "d" is +the LUN. Following the "s" is the slice number which is related to +a partition and by convention "s2" is the whole disk. + +OpenSolaris also has a c5t4d3p2 form where the number following +the "p" is the partition number apart from "p0" which is the whole +disk. So a whole disk may be referred to as either: + - c5t4d3 + - c5t4d3s2 + - c5t4d3p0 + +And these device names are duplicated in the /dev/dsk and /dev/rdsk +directories. The former is the block device name and the latter +is for "raw" (or char device) access which is what sg3_utils needs. +So in OpenSolaris something of the form: + sg_inq /dev/rdsk/c5t4d3p0 +should work. If it doesn't add a '-vvv' option. If that is attempted +on the /dev/dsk/c5t4d3p0 variant an inappropriate ioctl for device +error will result. + +The device names within the /dev directory are typically symbolic +links to much longer topological names in the /device directory. + +In Solaris cd/dvd/bd players seem to be treated as disks and so are +found in the /dev/rdsk directory. Tape drives appear in the /dev/rmt +directory. + +There is also a sgen (SCSI generic) driver which by default does not +attach to any device. See the /kernel/drv/sgen.conf file to control +what is attached. Any attached device will have a device name of +the form /dev/scsi/c5t4d3 . + +Listing available SCSI devices in Solaris seems to be a challenge. +"Use the 'format' command" advice works but seems a very dangerous +way to list devices. [It does prompt again before doing any damage.] +'devfsadm -Cv' cleans out the clutter in the /dev/rdsk directory, +only leaving what is "live". The "cfgadm -v" command looks promising. + +Details +======= +The ported utilities listed above, all use SCSI command functions +declared in sg_cmds_basic.h and sg_cmds_extra.h . Those SCSI command +functions are implemented in the corresponding ".c" files. The ".c" +files pass SCSI commands to the host operating system via an interface +declared in sg_pt.h . There are currently five implementations of that +interface depending on the host operating system: + - sg_pt_linux.c + - sg_pt_freebsd.c + - sg_pt_osf1.c [Tru64] + - sg_pt_solaris.c + - sg_pt_win32.c + +The sg_pt_solaris.c file uses the "uscsi" SCSI pass through mechanism. There +seems to be no corresponding ATA pass through and recent SATA disks do not +seem to have a SAT layer in front of them (within Solaris). If SAT is +present (perhaps externally or within a HBA) then that would allow SATA +disks to accept SCSI commands including the SCSI ATA PASS THROUGH commands. + + +Douglas Gilbert +27th January 2018 diff --git a/README.tru64 b/README.tru64 new file mode 100644 index 0000000..6ebd026 --- /dev/null +++ b/README.tru64 @@ -0,0 +1,97 @@ +Introduction +============ +The Tru64 port of sg3_utils contains those utilities that are _not_ +specific to Linux. In some cases a utility could be ported but +requires more work. An example is sg_dd which needs more work +beyond the SCSI command pass through mechanism. + +Supported Utilities +=================== +Here is a list of utilities that have been ported: + sg_compare_and_write + sg_decode_sense + sg_format + sg_get_config + sg_get_lba_status + sg_ident + sg_inq [dropped ATA IDENTIFY DEVICE capability] + sg_logs + sg_luns + sg_modes + sg_opcodes + sg_persist + sg_prevent + sg_raw + sg_rdac + sg_read_block_limits + sg_read_buffer + sg_read_long + sg_readcap + sg_reassign + sg_referrals + sg_requests + sg_rmsn + sg_rtpg + sg_safte + sg_sanitize + sg_sat_identify + sg_sat_phy_event + sg_sat_set_features + sg_senddiag + sg_ses + sg_start + sg_stpg + sg_sync + sg_turs + sg_unmap + sg_verify + sg_vpd + sg_wr_mode + sg_write_buffer + sg_write_long + sg_write_same + +Most utility names are indicative of the main SCSI command +that they execute. Some utilities are slightly higher level, for +example sg_ses fetches SCSI Enclosure Services (SES) status pages and +can send control pages. Each utility has a man page (placed in +section 8). An overview of sg3_utils can be found at: +http://sg.danny.cz/sg/sg3_utils.html . +A copy of the "sg3_utils.html" file is in the "doc" subdirectory. + +This package uses autotools infrastructure with the now common +"./configure ; make ; make install" sequence needed to build and install +from the source found in the tarball. If the "./configure" sequence +fails try using the ./autogen.sh prior to that sequence. + +Some man pages have examples which use Linux device names which hopefully +will not confuse Tru64 users. + + +Details +======= +Most of the ported utilities listed above use SCSI command functions +declared in sg_cmds_*.h headers . Those SCSI command functions are +implemented in the corresponding ".c" files. The ".c" files pass SCSI +commands to the host operating system via an interface declared in sg_pt.h . +There are currently five implementations of that interface depending on +the host operating system: +system: + - sg_pt_linux.c + - sg_pt_osf1.c [Tru64] + - sg_pt_freebsd.c + - sg_pt_solaris.c + - sg_pt_win32.c + +The sg_pt_osf1.c file uses the Tru64 CAM SCSI pass through mechanism. + +Tru64 does not have general library support for "long" options +(e.g. "--verbose") which are used extensively by most of the +utilities in this package. Rather than change all the utilities +and their man/web pages a local implementation of the missing +function "getopt_long()" has been placed in the "getopt_long" +subdirectory. Currently only the Tru64 port uses it. + + +Douglas Gilbert +14th January 2013 diff --git a/README.win32 b/README.win32 new file mode 100644 index 0000000..41173b6 --- /dev/null +++ b/README.win32 @@ -0,0 +1,245 @@ +Introduction +============ +The win32 port of sg3_utils contains those utilities that are _not_ specific +to Linux. One utility for listing available devices, sg_scan, has a +Windows-specific version for this port. + +The dd variants from the sg3_utils package (e.g. sg_dd) rely on too many +Linux idiosyncrasies to be easily ported. A new package called 'ddpt' +contains a utility with similar functionality to sg_dd and is available +for Windows. + +The Windows port uses the Microsoft SCSI Pass Through (SPT) interface. +It has two variants: "SPT" where data is double buffered; and "SPTD" +where data pointers to the user space are passed to the OS. Only Windows +2000 and later (i.e. not 95, 98 or ME) support SPT. + +Two build environments are catered for: cygwin (see www.cygwin.com) and +MinGW ("Minimalist GNU for Windows", see www.mingw.org). Both are based in +the gcc compiler (although other C compilers should have little problem with +the source code). Cygwin is a more sophisticated, commercial product that +results in executables that depend on cygwin1.dll . No licensing is required +since sg3_utils is open source (with either BSD or GPL licenses) but users +will need to fetch that dll. On the other hand MinGW (and its companion MSYS +shell) builds freestanding console executables. The Unix library support is +not as advanced with MinGW which has led to some timing functions being +compiled out when sg3_utils is built for MinGW. + +In later versions of Windows these utilities may need to be "run as +Administrator" for disks and other devices to be seen. If not those devices +will simply not be found as calls to query them fail with access permission +problems. + +Supported Utilities +=================== +Here is a list of utilities that have been ported: + sg_bg_ctl + sg_compare_and_write + sg_decode_sense + sg_format + sg_get_config + sg_get_lba_status + sg_ident + sg_inq [dropped ATA IDENTIFY DEVICE capability] + sg_logs + sg_luns + sg_modes + sg_opcodes + sg_persist + sg_prevent + sg_raw + sg_rdac + sg_read_attr + sg_read_block_limits + sg_read_buffer + sg_read_long + sg_readcap + sg_reassign + sg_referrals + sg_rep_zones + sg_requests + sg_reset_wp + sg_rmsn + sg_rtpg + sg_safte + sg_sanitize + sg_sat_identify + sg_sat_phy_event + sg_sat_read_gplog + sg_sat_set_features + sg_scan [this is Windows specific] + sg_seek + sg_senddiag + sg_ses + sg_ses_microcode + sg_start + sg_stpg + sg_stream_ctl + sg_sync + sg_timestamp + sg_turs + sg_unmap + sg_verify + sg_vpd + sg_wr_mode + sg_write_buffer + sg_write_long + sg_write_same + sg_write_verify + sg_write_x + sg_zone + +Most utility names are indicative of the main SCSI command that they execute. +Some utilities are slightly higher level, for example sg_ses fetches SCSI +Enclosure Services (SES) status pages and can send control pages. Each +utility has a man page (placed in section 8). There is summary of the mapping +between utility names and the SCSI commands they execute in the COVERAGE +file. An overview of sg3_utils can be found at: +http://sg.danny.cz/sg/sg3_utils.html . +A copy of the "sg3_utils.html" file is in the "doc" subdirectory. + +Some man pages have examples which use Linux device names which hopefully +will not confuse Windows users. + +Two pass-through variants +========================= +The sg_pt_win32.c file uses the Windows SCSI Pass Through interface. +That is often shortened to SPT or SPTI. There are two DeviceIoControl() +ioctl variants provided: IOCTL_SCSI_PASS_THROUGH and +IOCTL_SCSI_PASS_THROUGH_DIRECT. The former involves double handling of +data (and perhaps an upper limit on the data length associated with +one SCSI command; MS documentation mentions 16 KB). The "direct" +variant passes a pointer from the user space and to be faster looks +and more versatile. + +However the "direct" variant has potentially (unquantified) alignment +requirements and may not be (well) implemented by the hardware driver. +In practice some users have reported errors (e.g. 1117: a non-descript +IO error) when the direct variant is used. + +Hence the non-direct variant is the default. The default size limit +on the data buffer is set at 16 KB but if the user asks for more +the data buffer will be extended. The OS or the hardware drivers +may reject the extended data buffer but we tried. + +The package can be built using the direct variant with: + ./configure --enable-win32-spt-direct +rather than: + ./configure +prior to the 'make' call. + +In sg3_utils version 1.31 run-time selection of the direct or indirect +interface was added with the scsi_pt_win32_direct(int state_direct) +function declared in sg_pt.h. The default is indirect unless +'./configure --enable-win32-spt-direct' was used in the build. If +'state_direct' is 1 then the direct interface is used and if it is 0 +the indirect interface is used. + +Both sg_read_buffer and sg_write_buffer can transfer buffers larger +than 16 KB. So in sg3_utils version 1.31, they use this new function +to set direct interface mode. This is regardless of whether or +not "--enable-win32-spt-direct" is given to ./configure . + +Details +======= +Most of the ported utilities listed above use SCSI command functions +declared in sg_cmds_*.h headers . Those SCSI command functions are +implemented in the corresponding ".c" files. The ".c" files pass SCSI +commands to the host operating system via an interface declared in sg_pt.h . +There are currently five implementations of that interface depending on +the host operating system: + - sg_pt_linux.c + - sg_pt_freebsd.c + - sg_pt_osf1.c [Tru64] + - sg_pt_solaris.c + - sg_pt_win32.c + +The ASPI32 interface which requires a dll from Adaptec is not supported. + +The sg_scan utility is a special version for Windows and it attempts to show +the various available storage device names, one per line. Here is an example +of sg_scan's output: + +# sg_scan +PD0 [C] FUJITSU MHY2160BH 0000 +PD1 [DF] WD 2500BEV External 1.05 WD-WXE90 +CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03 + +Here is an example with added bus type: + +# sg_scan -b +PD0 [C] FUJITSU MHY2160BH 0000 +PD1 [DF] WD 2500BEV External 1.05 WD-WXE90 +CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03 + +Here is an example with added SCSI adapter scan: + +# sg_scan -b -s +PD0 [C] ST380011A 8.01 +PD1 SEAGATE ST373455SS 2189 +PD2 ATA ST3160812AS D +PD3 SEAGATE ST336754SS 0003 +CDROM0 [F] HL-DT-ST DVDRAM GSA-4163B A103 +TAPE0 SONY SDT-7000 0192 + +SCSI0:0,0,0 claimed=1 pdt=0h dubious ST380011 A 8.01 +SCSI1:0,0,0 claimed=1 pdt=5h HL-DT-ST DVDRAM GSA-4163B A103 +SCSI2:0,6,0 claimed=1 pdt=1h SONY SDT-7000 0192 +SCSI5:0,17,0 claimed=1 pdt=0h SEAGATE ST373455SS 2189 +SCSI5:0,19,0 claimed=1 pdt=0h ATA ST3160812AS D +SCSI5:0,21,0 claimed=1 pdt=0h SEAGATE ST336754SS 0003 +SCSI5:0,112,0 claimed=0 pdt=10h LSI PSEUDO DEVICE 2.34 + +The storage devices scanned are PhysicalDrive (shortened form PD used), +CDROM (which includes DVD and BD drives) and TAPE. There is also an +optional SCSI adapter scan with device names of the form SCSI::: . +These only come into play for devices that are not claimed by one of the +storage class drivers. The "LSI PSEUDO DEVICE" device above is an example +of an unclaimed device. The SCSI adapter scan does not show USB and IEEE +1394 connected devices. + +Volume names (e.g. "C:") that match a storage device (or perhaps a +partition within that device) are shown in brackets. Notice there can be +zero, one or more volume names for each storage device. Up to four volume +names are listed in brackets, if there are more a "+" is added after the +fourth. + +Several utilities have conditional compilation sections based on +the SG_LIB_MINGW define. For those who want to try native C compilers +on Windows setting the SG_LIB_MINGW define may help. + +Build environments +================== +This package uses autotools infrastructure with the now common +"./configure ; make ; make install" sequence needed to build and install +from the source found in the tarball. Two Windows environments for building +Unix code are supported: cygwin and MinGW. If the "./configure" sequence +fails try using the ./autogen.sh prior to that sequence. The executables +produced are console applications that can be executed in either a cygwin, +MSYS or "cmd" shell. Various build options are available by giving +command line options to "./configure", see the INSTALL file for generic +information about the build infrastructure. + +MinGW can be used to cross built on some Redhat (Linux) platforms. After +loading the cross build packages, the ./configure call in the normal +autotools sequence should be replaced by either mingw32-configure or +mingw64-configure. These scripts will set up the environment for +the cross build and then call ./configure (so this invocation should be +made in the top level of the untarred source). Options given to either +script (e.g. --enable-win32-spt-direct) will be passed through to +./configure . + +Binary and Text files +===================== +A problem has been reported with binary output being written in a MinGW +environment (or executables build by MinGW). Windows has a concept of text +and binary files which is not found in Unix. Recent versions of MinGW +default to opening files in text mode. This can lead to binary output +(such as when the '--raw' option is given) having 0xa (i.e. LF) translated +to 0xd,0xa (i.e. CR,LF). sg3_utils version 1.26 attempts to fix this +problem by changing what it knows to be binary output files to "binary +mode" with the setmode() Windows command. + + +Douglas Gilbert +27th January 2018 diff --git a/archive/README b/archive/README new file mode 100644 index 0000000..e0e3220 --- /dev/null +++ b/archive/README @@ -0,0 +1,16 @@ +The code and scripts in this directory may be removed at some later +date. The last cleanup (i.e. purge of unused files) of this +directory occurred between sg3_utils version 1.22 and 1.23 . +No other code or script in this package currently uses the contents +of this directory. The contents of this directory are not +maintained by the author. + +The rescan-scsi-bus.sh script was copied long ago from +http://www.garloff.de/kurt/linux (under the "Rescan SCSI bus" +heading) and was later placed in this directory. Since others +do use the version of this script found in this package then +rescan-scsi-bus.sh was updated in sg3_utils version 1.35 and +was moved to the scripts directory. + +Douglas Gilbert +9th January 2013 diff --git a/archive/align_b4_memalign.c b/archive/align_b4_memalign.c new file mode 100644 index 0000000..8760202 --- /dev/null +++ b/archive/align_b4_memalign.c @@ -0,0 +1,19 @@ +/* Code fragment of how to get a buffer of heap that has a specific + * alignment, typically 'page' size which is 4096 bytes. */ + + uint8_t * wrkBuff; /* will get pointer to heap allocation */ + uint8_t * wrkPos; /* will get aligned pointer within wrkBuff */ + uint32_t sz_of_aligned = 1234; /* number of aligned bytes required */ + + int psz; + +#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) + psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */ +#else + psz = 4096; /* give up, pick likely figure */ +#endif + + + /* perhaps use posix_memalign() instead. Yes but not always available */ + wrkBuff = (uint8_t *)malloc(sz_of_aligned + psz); + wrkPos = (uint8_t *)(((sg_uintptr_t)wrkBuff + psz - 1) & (~(psz - 1))); diff --git a/archive/llseek.c b/archive/llseek.c new file mode 100644 index 0000000..fcc53fa --- /dev/null +++ b/archive/llseek.c @@ -0,0 +1,128 @@ +/* + * llseek.c -- stub calling the llseek system call + * + * Copyright (C) 1994 Remy Card. This file may be redistributed + * under the terms of the GNU Public License. + * + * This file is borrowed from the util-linux-2.11z tarball's implementation + * of fdisk. It allows seeks to 64 bit offsets, if supported. + * Changed "ext2_" prefix to "llse". + */ + +#include "config.h" + +#define _XOPEN_SOURCE 500 +#define _GNU_SOURCE + +#include + +#include +#include + +#if defined(__GNUC__) || defined(HAS_LONG_LONG) +typedef int64_t llse_loff_t; +#else +typedef long llse_loff_t; +#endif + +extern llse_loff_t llse_llseek (unsigned int, llse_loff_t, unsigned int); + +#ifdef __linux__ + +#ifdef HAVE_LLSEEK +#include + +#else /* HAVE_LLSEEK */ + +#if defined(__alpha__) || defined(__ia64__) || defined(__s390x__) || defined (__x86_64__) || defined (__powerpc64__) + +#define my_llseek lseek + +#else +#include /* for __NR__llseek */ + +static int _llseek (unsigned int, unsigned long, + unsigned long, llse_loff_t *, unsigned int); + +#ifdef __NR__llseek + +static _syscall5(int,_llseek,unsigned int,fd,unsigned long,offset_high, + unsigned long, offset_low,llse_loff_t *,result, + unsigned int, origin) + +#else + +/* no __NR__llseek on compilation machine - might give it explicitly */ +static int _llseek (unsigned int fd, unsigned long oh, + unsigned long ol, llse_loff_t *result, + unsigned int origin) { + errno = ENOSYS; + return -1; +} + +#endif + +static llse_loff_t my_llseek (unsigned int fd, llse_loff_t offset, + unsigned int origin) +{ + llse_loff_t result; + int retval; + +#ifdef HAVE_LSEEK64 + return lseek64 (fd, offset, origin); +#else + retval = _llseek (fd, ((uint64_t) offset) >> 32, + ((uint64_t) offset) & 0xffffffff, + &result, origin); + return (retval == -1 ? (llse_loff_t) retval : result); +#endif +} + +#endif /* __alpha__ */ + +#endif /* HAVE_LLSEEK */ + +llse_loff_t llse_llseek (unsigned int fd, llse_loff_t offset, + unsigned int origin) +{ + llse_loff_t result; + static int do_compat = 0; + + if (!do_compat) { + result = my_llseek (fd, offset, origin); + if (!(result == -1 && errno == ENOSYS)) + return result; + + /* + * Just in case this code runs on top of an old kernel + * which does not support the llseek system call + */ + do_compat = 1; + /* + * Now try ordinary lseek. + */ + } + + if ((sizeof(off_t) >= sizeof(llse_loff_t)) || + (offset < ((llse_loff_t) 1 << ((sizeof(off_t)*8) -1)))) + return lseek(fd, (off_t) offset, origin); + + errno = EINVAL; + return -1; +} + +#else /* !linux */ + +llse_loff_t llse_llseek (unsigned int fd, llse_loff_t offset, + unsigned int origin) +{ + if ((sizeof(off_t) < sizeof(llse_loff_t)) && + (offset >= ((llse_loff_t) 1 << ((sizeof(off_t)*8) -1)))) { + errno = EINVAL; + return -1; + } + return lseek (fd, (off_t) offset, origin); +} + +#endif /* linux */ + diff --git a/archive/llseek.h b/archive/llseek.h new file mode 100644 index 0000000..61c12e4 --- /dev/null +++ b/archive/llseek.h @@ -0,0 +1,14 @@ +#ifndef LLSEEK_H +#define LLSEEK_H + +#if defined(__GNUC__) || defined(HAS_LONG_LONG) +typedef int64_t llse_loff_t; +#else +typedef long llse_loff_t; +#endif + +extern llse_loff_t llse_llseek(unsigned int fd, + llse_loff_t offset, + unsigned int origin); + +#endif diff --git a/archive/o_scsi_logging_level b/archive/o_scsi_logging_level new file mode 100755 index 0000000..ecbc827 --- /dev/null +++ b/archive/o_scsi_logging_level @@ -0,0 +1,295 @@ +#! /bin/bash +############################################################################### +# Conveniently create and set scsi logging level, show SCSI_LOG fields in human +# readable form. +# +# Copyright (C) IBM Corp. 2006 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +############################################################################### + +# Contributed by Andreas Herrmann 2006/08/18 + +SCRIPTNAME="scsi_logging_level" + +declare -i LOG_ERROR=0 +declare -i LOG_TIMEOUT=0 +declare -i LOG_SCAN=0 +declare -i LOG_MLQUEUE=0 +declare -i LOG_MLCOMPLETE=0 +declare -i LOG_LLQUEUE=0 +declare -i LOG_LLCOMPLETE=0 +declare -i LOG_HLQUEUE=0 +declare -i LOG_HLCOMPLETE=0 +declare -i LOG_IOCTL=0 + +declare -i LEVEL=0 + +_ERROR_SHIFT=0 +_TIMEOUT_SHIFT=3 +_SCAN_SHIFT=6 +_MLQUEUE_SHIFT=9 +_MLCOMPLETE_SHIFT=12 +_LLQUEUE_SHIFT=15 +_LLCOMPLETE_SHIFT=18 +_HLQUEUE_SHIFT=21 +_HLCOMPLETE_SHIFT=24 +_IOCTL_SHIFT=27 + +SET=0 +GET=0 +CREATE=0 + +OPTS=`getopt -o hvcgsa:E:T:S:I:M:L:H: --long \ +help,version,create,get,set,all:,error:,timeout:,scan:,ioctl:,\ +midlevel:,mlqueue:,mlcomplete:,lowlevel:,llqueue:,llcomplete:,\ +highlevel:,hlqueue:,hlcomplete: -n \'$SCRIPTNAME\' -- "$@"` +eval set -- "$OPTS" + +# print version info +printversion() +{ + cat <>$_ERROR_SHIFT) & 7)) + LOG_TIMEOUT=$((($LEVEL>>$_TIMEOUT_SHIFT) & 7)) + LOG_SCAN=$((($LEVEL>>$_SCAN_SHIFT) & 7)) + LOG_MLQUEUE=$((($LEVEL>>$_MLQUEUE_SHIFT) & 7)) + LOG_MLCOMPLETE=$((($LEVEL>>$_MLCOMPLETE_SHIFT) & 7)) + LOG_LLQUEUE=$((($LEVEL>>$_LLQUEUE_SHIFT) & 7)) + LOG_LLCOMPLETE=$((($LEVEL>>$_LLCOMPLETE_SHIFT) & 7)) + LOG_HLQUEUE=$((($LEVEL>>$_HLQUEUE_SHIFT) & 7)) + LOG_HLCOMPLETE=$((($LEVEL>>$_HLCOMPLETE_SHIFT) & 7)) + LOG_IOCTL=$((($LEVEL>>$_IOCTL_SHIFT) & 7)) + + echo "SCSI_LOG_ERROR=$LOG_ERROR" + echo "SCSI_LOG_TIMEOUT=$LOG_TIMEOUT" + echo "SCSI_LOG_SCAN=$LOG_SCAN" + echo "SCSI_LOG_MLQUEUE=$LOG_MLQUEUE" + echo "SCSI_LOG_MLCOMPLETE=$LOG_MLCOMPLETE" + echo "SCSI_LOG_LLQUEUE=$LOG_LLQUEUE" + echo "SCSI_LOG_LLCOMPLETE=$LOG_LLCOMPLETE" + echo "SCSI_LOG_HLQUEUE=$LOG_HLQUEUE" + echo "SCSI_LOG_HLCOMPLETE=$LOG_HLCOMPLETE" + echo "SCSI_LOG_IOCTL=$LOG_IOCTL" +} + +set_logging_level() +{ + echo "New scsi logging level:" + sysctl -q -w dev.scsi.logging_level=$LEVEL + if [ $? != 0 ] + then + echo "$SCRIPTNAME: could not write scsi logging level" \ + "(kernel probably without SCSI_LOGGING support)" + exit 1 + fi +} + +create_logging_level() +{ + LEVEL=$((($LOG_ERROR & 7)<<$_ERROR_SHIFT)) + LEVEL=$(($LEVEL|(($LOG_TIMEOUT & 7)<<$_TIMEOUT_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_SCAN & 7)<<$_SCAN_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_MLQUEUE & 7)<<$_MLQUEUE_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_MLCOMPLETE & 7)<<$_MLCOMPLETE_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_LLQUEUE & 7)<<$_LLQUEUE_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_LLCOMPLETE & 7)<<$_LLCOMPLETE_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_HLQUEUE & 7)<<$_HLQUEUE_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_HLCOMPLETE & 7)<<$_HLCOMPLETE_SHIFT))) + LEVEL=$(($LEVEL|(($LOG_IOCTL & 7)<<$_IOCTL_SHIFT))) +} + +check_cmdline $* + +if [ $SET = "1" ] +then + create_logging_level + set_logging_level + show_logging_level +elif [ $GET = "1" ] +then + get_logging_level + show_logging_level +elif [ $CREATE = "1" ] +then + create_logging_level + show_logging_level +else + invalid_cmdline missing option \'-g\', \'-s\' or \'-c\' +fi + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..5bce9ba --- /dev/null +++ b/autogen.sh @@ -0,0 +1,1578 @@ +#!/bin/sh +# a u t o g e n . s h +# +# Copyright (c) 2005-2009 United States Government as represented by +# the U.S. Army Research Laboratory. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of the author may not be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +### +# +# Script for automatically preparing the sources for compilation by +# performing the myriad of necessary steps. The script attempts to +# detect proper version support, and outputs warnings about particular +# systems that have autotool peculiarities. +# +# Basically, if everything is set up and installed correctly, the +# script will validate that minimum versions of the GNU Build System +# tools are installed, account for several common configuration +# issues, and then simply run autoreconf for you. +# +# If autoreconf fails, which can happen for many valid configurations, +# this script proceeds to run manual preparation steps effectively +# providing a POSIX shell script (mostly complete) reimplementation of +# autoreconf. +# +# The AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER +# environment variables and corresponding _OPTIONS variables (e.g. +# AUTORECONF_OPTIONS) may be used to override the default automatic +# detection behaviors. Similarly the _VERSION variables will override +# the minimum required version numbers. +# +# Examples: +# +# To obtain help on usage: +# ./autogen.sh --help +# +# To obtain verbose output: +# ./autogen.sh --verbose +# +# To skip autoreconf and prepare manually: +# AUTORECONF=false ./autogen.sh +# +# To verbosely try running with an older (unsupported) autoconf: +# AUTOCONF_VERSION=2.50 ./autogen.sh --verbose +# +# Author: +# Christopher Sean Morrison +# +# Patches: +# Sebastian Pipping +# +###################################################################### + +# set to minimum acceptable version of autoconf +if [ "x$AUTOCONF_VERSION" = "x" ] ; then + AUTOCONF_VERSION=2.52 +fi +# set to minimum acceptable version of automake +if [ "x$AUTOMAKE_VERSION" = "x" ] ; then + AUTOMAKE_VERSION=1.6.0 +fi +# set to minimum acceptable version of libtool +if [ "x$LIBTOOL_VERSION" = "x" ] ; then + LIBTOOL_VERSION=1.4.2 +fi + + +################## +# ident function # +################## +ident ( ) { + # extract copyright from header + __copyright="`grep Copyright $AUTOGEN_SH | head -${HEAD_N}1 | awk '{print $4}'`" + if [ "x$__copyright" = "x" ] ; then + __copyright="`date +%Y`" + fi + + # extract version from CVS Id string + __id="$Id: autogen.sh 33925 2009-03-01 23:27:06Z brlcad $" + __version="`echo $__id | sed 's/.*\([0-9][0-9][0-9][0-9]\)[-\/]\([0-9][0-9]\)[-\/]\([0-9][0-9]\).*/\1\2\3/'`" + if [ "x$__version" = "x" ] ; then + __version="" + fi + + echo "autogen.sh build preparation script by Christopher Sean Morrison" + echo " + config.guess download patch by Sebastian Pipping (2008-12-03)" + echo "revised 3-clause BSD-style license, copyright (c) $__copyright" + echo "script version $__version, ISO/IEC 9945 POSIX shell script" +} + + +################## +# USAGE FUNCTION # +################## +usage ( ) { + echo "Usage: $AUTOGEN_SH [-h|--help] [-v|--verbose] [-q|--quiet] [-d|--download] [--version]" + echo " --help Help on $NAME_OF_AUTOGEN usage" + echo " --verbose Verbose progress output" + echo " --quiet Quiet suppressed progress output" + echo " --download Download the latest config.guess from gnulib" + echo " --version Only perform GNU Build System version checks" + echo + echo "Description: This script will validate that minimum versions of the" + echo "GNU Build System tools are installed and then run autoreconf for you." + echo "Should autoreconf fail, manual preparation steps will be run" + echo "potentially accounting for several common preparation issues. The" + + echo "AUTORECONF, AUTOCONF, AUTOMAKE, LIBTOOLIZE, ACLOCAL, AUTOHEADER," + echo "PROJECT, & CONFIGURE environment variables and corresponding _OPTIONS" + echo "variables (e.g. AUTORECONF_OPTIONS) may be used to override the" + echo "default automatic detection behavior." + echo + + ident + + return 0 +} + + +########################## +# VERSION_ERROR FUNCTION # +########################## +version_error ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided a version" + exit 1 + fi + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version_error was not provided an application name" + exit 1 + fi + $ECHO + $ECHO "ERROR: To prepare the ${PROJECT} build system from scratch," + $ECHO " at least version $1 of $2 must be installed." + $ECHO + $ECHO "$NAME_OF_AUTOGEN does not need to be run on the same machine that will" + $ECHO "run configure or make. Either the GNU Autotools will need to be installed" + $ECHO "or upgraded on this system, or $NAME_OF_AUTOGEN must be run on the source" + $ECHO "code on another system and then transferred to here. -- Cheers!" + $ECHO +} + +########################## +# VERSION_CHECK FUNCTION # +########################## +version_check ( ) { + if [ "x$1" = "x" ] ; then + echo "INTERNAL ERROR: version_check was not provided a minimum version" + exit 1 + fi + _min="$1" + if [ "x$2" = "x" ] ; then + echo "INTERNAL ERROR: version check was not provided a comparison version" + exit 1 + fi + _cur="$2" + + # needed to handle versions like 1.10 and 1.4-p6 + _min="`echo ${_min}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + _cur="`echo ${_cur}. | sed 's/[^0-9]/./g' | sed 's/\.\././g'`" + + _min_major="`echo $_min | cut -d. -f1`" + _min_minor="`echo $_min | cut -d. -f2`" + _min_patch="`echo $_min | cut -d. -f3`" + + _cur_major="`echo $_cur | cut -d. -f1`" + _cur_minor="`echo $_cur | cut -d. -f2`" + _cur_patch="`echo $_cur | cut -d. -f3`" + + if [ "x$_min_major" = "x" ] ; then + _min_major=0 + fi + if [ "x$_min_minor" = "x" ] ; then + _min_minor=0 + fi + if [ "x$_min_patch" = "x" ] ; then + _min_patch=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_major=0 + fi + if [ "x$_cur_minor" = "x" ] ; then + _cur_minor=0 + fi + if [ "x$_cur_patch" = "x" ] ; then + _cur_patch=0 + fi + + $VERBOSE_ECHO "Checking if ${_cur_major}.${_cur_minor}.${_cur_patch} is greater than ${_min_major}.${_min_minor}.${_min_patch}" + + if [ $_min_major -lt $_cur_major ] ; then + return 0 + elif [ $_min_major -eq $_cur_major ] ; then + if [ $_min_minor -lt $_cur_minor ] ; then + return 0 + elif [ $_min_minor -eq $_cur_minor ] ; then + if [ $_min_patch -lt $_cur_patch ] ; then + return 0 + elif [ $_min_patch -eq $_cur_patch ] ; then + return 0 + fi + fi + fi + return 1 +} + + +###################################### +# LOCATE_CONFIGURE_TEMPLATE FUNCTION # +###################################### +locate_configure_template ( ) { + _pwd="`pwd`" + if test -f "./configure.ac" ; then + echo "./configure.ac" + elif test -f "./configure.in" ; then + echo "./configure.in" + elif test -f "$_pwd/configure.ac" ; then + echo "$_pwd/configure.ac" + elif test -f "$_pwd/configure.in" ; then + echo "$_pwd/configure.in" + elif test -f "$PATH_TO_AUTOGEN/configure.ac" ; then + echo "$PATH_TO_AUTOGEN/configure.ac" + elif test -f "$PATH_TO_AUTOGEN/configure.in" ; then + echo "$PATH_TO_AUTOGEN/configure.in" + fi +} + + +################## +# argument check # +################## +ARGS="$*" +PATH_TO_AUTOGEN="`dirname $0`" +NAME_OF_AUTOGEN="`basename $0`" +AUTOGEN_SH="$PATH_TO_AUTOGEN/$NAME_OF_AUTOGEN" + +LIBTOOL_M4="${PATH_TO_AUTOGEN}/misc/libtool.m4" + +if [ "x$HELP" = "x" ] ; then + HELP=no +fi +if [ "x$QUIET" = "x" ] ; then + QUIET=no +fi +if [ "x$VERBOSE" = "x" ] ; then + VERBOSE=no +fi +if [ "x$VERSION_ONLY" = "x" ] ; then + VERSION_ONLY=no +fi +if [ "x$DOWNLOAD" = "x" ] ; then + DOWNLOAD=no +fi +if [ "x$AUTORECONF_OPTIONS" = "x" ] ; then + AUTORECONF_OPTIONS="-i -f" +fi +if [ "x$AUTOCONF_OPTIONS" = "x" ] ; then + AUTOCONF_OPTIONS="-f" +fi +if [ "x$AUTOMAKE_OPTIONS" = "x" ] ; then + AUTOMAKE_OPTIONS="-a -c -f" +fi +ALT_AUTOMAKE_OPTIONS="-a -c" +if [ "x$LIBTOOLIZE_OPTIONS" = "x" ] ; then + LIBTOOLIZE_OPTIONS="--automake -c -f" +fi +ALT_LIBTOOLIZE_OPTIONS="--automake --copy --force" +if [ "x$ACLOCAL_OPTIONS" = "x" ] ; then + ACLOCAL_OPTIONS="" +fi +if [ "x$AUTOHEADER_OPTIONS" = "x" ] ; then + AUTOHEADER_OPTIONS="" +fi +if [ "x$CONFIG_GUESS_URL" = "x" ] ; then + CONFIG_GUESS_URL="http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=build-aux/config.guess;hb=HEAD" +fi +for arg in $ARGS ; do + case "x$arg" in + x--help) HELP=yes ;; + x-[hH]) HELP=yes ;; + x--quiet) QUIET=yes ;; + x-[qQ]) QUIET=yes ;; + x--verbose) VERBOSE=yes ;; + x-[dD]) DOWNLOAD=yes ;; + x--download) DOWNLOAD=yes ;; + x-[vV]) VERBOSE=yes ;; + x--version) VERSION_ONLY=yes ;; + *) + echo "Unknown option: $arg" + echo + usage + exit 1 + ;; + esac +done + + +##################### +# environment check # +##################### + +# sanity check before recursions potentially begin +if [ ! -f "$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: $AUTOGEN_SH does not exist" + if [ ! "x$0" = "x$AUTOGEN_SH" ] ; then + echo "INTERNAL ERROR: dirname/basename inconsistency: $0 != $AUTOGEN_SH" + fi + exit 1 +fi + +# force locale setting to C so things like date output as expected +LC_ALL=C + +# commands that this script expects +for __cmd in echo head tail pwd ; do + echo "test" | $__cmd > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "INTERNAL ERROR: '${__cmd}' command is required" + exit 2 + fi +done +echo "test" | grep "test" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: grep command is required" + exit 1 +fi +echo "test" | sed "s/test/test/" > /dev/null 2>&1 +if test ! x$? = x0 ; then + echo "INTERNAL ERROR: sed command is required" + exit 1 +fi + + +# determine the behavior of echo +case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in + *c*,-n*) ECHO_N= ECHO_C=' +' ECHO_T=' ' ;; + *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;; + *) ECHO_N= ECHO_C='\c' ECHO_T= ;; +esac + +# determine the behavior of head +case "x`echo 'head' | head -n 1 2>&1`" in + *xhead*) HEAD_N="n " ;; + *) HEAD_N="" ;; +esac + +# determine the behavior of tail +case "x`echo 'tail' | tail -n 1 2>&1`" in + *xtail*) TAIL_N="n " ;; + *) TAIL_N="" ;; +esac + +VERBOSE_ECHO=: +ECHO=: +if [ "x$QUIET" = "xyes" ] ; then + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output quelled by quiet option. Further output disabled." + fi +else + ECHO=echo + if [ "x$VERBOSE" = "xyes" ] ; then + echo "Verbose output enabled" + VERBOSE_ECHO=echo + fi +fi + + +# allow a recursive run to disable further recursions +if [ "x$RUN_RECURSIVE" = "x" ] ; then + RUN_RECURSIVE=yes +fi + + +################################################ +# check for help arg and bypass version checks # +################################################ +if [ "x`echo $ARGS | sed 's/.*[hH][eE][lL][pP].*/help/'`" = "xhelp" ] ; then + HELP=yes +fi +if [ "x$HELP" = "xyes" ] ; then + usage + $ECHO "---" + $ECHO "Help was requested. No preparation or configuration will be performed." + exit 0 +fi + + +####################### +# set up signal traps # +####################### +untrap_abnormal ( ) { + for sig in 1 2 13 15; do + trap - $sig + done +} + +# do this cleanup whenever we exit. +trap ' + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # restore/delete backup files + if test "x$PFC_INIT" = "x1" ; then + recursive_restore + fi +' 0 + +# trap SIGHUP (1), SIGINT (2), SIGPIPE (13), SIGTERM (15) +for sig in 1 2 13 15; do + trap ' + $ECHO "" + $ECHO "Aborting $NAME_OF_AUTOGEN: caught signal '$sig'" + + # start from the root + if test -d "$START_PATH" ; then + cd "$START_PATH" + fi + + # clean up on abnormal exit + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + + if test -f "acinclude.m4.$$.backup" ; then + $VERBOSE_ECHO "cat acinclude.m4.$$.backup > acinclude.m4" + chmod u+w acinclude.m4 + cat acinclude.m4.$$.backup > acinclude.m4 + + $VERBOSE_ECHO "rm -f acinclude.m4.$$.backup" + rm -f acinclude.m4.$$.backup + fi + + { (exit 1); exit 1; } +' $sig +done + + +############################# +# look for a configure file # +############################# +if [ "x$CONFIGURE" = "x" ] ; then + CONFIGURE="`locate_configure_template`" + if [ ! "x$CONFIGURE" = "x" ] ; then + $VERBOSE_ECHO "Found a configure template: $CONFIGURE" + fi +else + $ECHO "Using CONFIGURE environment variable override: $CONFIGURE" +fi +if [ "x$CONFIGURE" = "x" ] ; then + if [ "x$VERSION_ONLY" = "xyes" ] ; then + CONFIGURE=/dev/null + else + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi +fi + +#################### +# get project name # +#################### +if [ "x$PROJECT" = "x" ] ; then + PROJECT="`grep AC_INIT $CONFIGURE | grep -v '.*#.*AC_INIT' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_INIT(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if [ "x$PROJECT" = "xAC_INIT" ] ; then + # projects might be using the older/deprecated arg-less AC_INIT .. look for AM_INIT_AUTOMAKE instead + PROJECT="`grep AM_INIT_AUTOMAKE $CONFIGURE | grep -v '.*#.*AM_INIT_AUTOMAKE' | tail -${TAIL_N}1 | sed 's/^[ ]*AM_INIT_AUTOMAKE(\([^,)]*\).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + fi + if [ "x$PROJECT" = "xAM_INIT_AUTOMAKE" ] ; then + PROJECT="project" + fi + if [ "x$PROJECT" = "x" ] ; then + PROJECT="project" + fi +else + $ECHO "Using PROJECT environment variable override: $PROJECT" +fi +$ECHO "Preparing the $PROJECT build system...please wait" +$ECHO + + +######################## +# check for autoreconf # +######################## +HAVE_AUTORECONF=no +if [ "x$AUTORECONF" = "x" ] ; then + for AUTORECONF in autoreconf ; do + $VERBOSE_ECHO "Checking autoreconf version: $AUTORECONF --version" + $AUTORECONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + HAVE_AUTORECONF=yes + break + fi + done +else + HAVE_AUTORECONF=yes + $ECHO "Using AUTORECONF environment variable override: $AUTORECONF" +fi + + +########################## +# autoconf version check # +########################## +_acfound=no +if [ "x$AUTOCONF" = "x" ] ; then + for AUTOCONF in autoconf ; do + $VERBOSE_ECHO "Checking autoconf version: $AUTOCONF --version" + $AUTOCONF --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _acfound=yes + break + fi + done +else + _acfound=yes + $ECHO "Using AUTOCONF environment variable override: $AUTOCONF" +fi + +_report_error=no +if [ ! "x$_acfound" = "xyes" ] ; then + $ECHO "ERROR: Unable to locate GNU Autoconf." + _report_error=yes +else + _version="`$AUTOCONF --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Autoconf version $_version" + version_check "$AUTOCONF_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOCONF_VERSION" "GNU Autoconf" + exit 1 +fi + + +########################## +# automake version check # +########################## +_amfound=no +if [ "x$AUTOMAKE" = "x" ] ; then + for AUTOMAKE in automake ; do + $VERBOSE_ECHO "Checking automake version: $AUTOMAKE --version" + $AUTOMAKE --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + _amfound=yes + break + fi + done +else + _amfound=yes + $ECHO "Using AUTOMAKE environment variable override: $AUTOMAKE" +fi + + +_report_error=no +if [ ! "x$_amfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Automake." + _report_error=yes +else + _version="`$AUTOMAKE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Automake version $_version" + version_check "$AUTOMAKE_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$AUTOMAKE_VERSION" "GNU Automake" + exit 1 +fi + + +######################## +# check for libtoolize # +######################## +HAVE_LIBTOOLIZE=yes +HAVE_ALT_LIBTOOLIZE=no +_ltfound=no +if [ "x$LIBTOOLIZE" = "x" ] ; then + LIBTOOLIZE=libtoolize + $VERBOSE_ECHO "Checking libtoolize version: $LIBTOOLIZE --version" + $LIBTOOLIZE --version > /dev/null 2>&1 + if [ ! $? = 0 ] ; then + HAVE_LIBTOOLIZE=no + $ECHO + if [ "x$HAVE_AUTORECONF" = "xno" ] ; then + $ECHO "Warning: libtoolize does not appear to be available." + else + $ECHO "Warning: libtoolize does not appear to be available. This means that" + $ECHO "the automatic build preparation via autoreconf will probably not work." + $ECHO "Preparing the build by running each step individually, however, should" + $ECHO "work and will be done automatically for you if autoreconf fails." + fi + + # look for some alternates + for tool in glibtoolize libtoolize15 libtoolize14 libtoolize13 ; do + $VERBOSE_ECHO "Checking libtoolize alternate: $tool --version" + _glibtoolize="`$tool --version > /dev/null 2>&1`" + if [ $? = 0 ] ; then + $VERBOSE_ECHO "Found $tool --version" + _glti="`which $tool`" + if [ "x$_glti" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool with which" + continue; + fi + if test ! -f "$_glti" ; then + $VERBOSE_ECHO "Cannot use $tool, $_glti is not a file" + continue; + fi + _gltidir="`dirname $_glti`" + if [ "x$_gltidir" = "x" ] ; then + $VERBOSE_ECHO "Cannot find $tool path with dirname of $_glti" + continue; + fi + if test ! -d "$_gltidir" ; then + $VERBOSE_ECHO "Cannot use $tool, $_gltidir is not a directory" + continue; + fi + HAVE_ALT_LIBTOOLIZE=yes + LIBTOOLIZE="$tool" + $ECHO + $ECHO "Fortunately, $tool was found which means that your system may simply" + $ECHO "have a non-standard or incomplete GNU Autotools install. If you have" + $ECHO "sufficient system access, it may be possible to quell this warning by" + $ECHO "running:" + $ECHO + sudo -V > /dev/null 2>&1 + if [ $? = 0 ] ; then + $ECHO " sudo ln -s $_glti $_gltidir/libtoolize" + $ECHO + else + $ECHO " ln -s $_glti $_gltidir/libtoolize" + $ECHO + $ECHO "Run that as root or with proper permissions to the $_gltidir directory" + $ECHO + fi + _ltfound=yes + break + fi + done + else + _ltfound=yes + fi +else + _ltfound=yes + $ECHO "Using LIBTOOLIZE environment variable override: $LIBTOOLIZE" +fi + + +############################ +# libtoolize version check # +############################ +_report_error=no +if [ ! "x$_ltfound" = "xyes" ] ; then + $ECHO + $ECHO "ERROR: Unable to locate GNU Libtool." + _report_error=yes +else + _version="`$LIBTOOLIZE --version | head -${HEAD_N}1 | sed 's/[^0-9]*\([0-9\.][0-9\.]*\)/\1/'`" + if [ "x$_version" = "x" ] ; then + _version="0.0.0" + fi + $ECHO "Found GNU Libtool version $_version" + version_check "$LIBTOOL_VERSION" "$_version" + if [ $? -ne 0 ] ; then + _report_error=yes + fi +fi +if [ "x$_report_error" = "xyes" ] ; then + version_error "$LIBTOOL_VERSION" "GNU Libtool" + exit 1 +fi + + +##################### +# check for aclocal # +##################### +if [ "x$ACLOCAL" = "x" ] ; then + for ACLOCAL in aclocal ; do + $VERBOSE_ECHO "Checking aclocal version: $ACLOCAL --version" + $ACLOCAL --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using ACLOCAL environment variable override: $ACLOCAL" +fi + + +######################## +# check for autoheader # +######################## +if [ "x$AUTOHEADER" = "x" ] ; then + for AUTOHEADER in autoheader ; do + $VERBOSE_ECHO "Checking autoheader version: $AUTOHEADER --version" + $AUTOHEADER --version > /dev/null 2>&1 + if [ $? = 0 ] ; then + break + fi + done +else + $ECHO "Using AUTOHEADER environment variable override: $AUTOHEADER" +fi + + +######################### +# check if version only # +######################### +$VERBOSE_ECHO "Checking whether to only output version information" +if [ "x$VERSION_ONLY" = "xyes" ] ; then + $ECHO + ident + $ECHO "---" + $ECHO "Version requested. No preparation or configuration will be performed." + exit 0 +fi + + +################################# +# PROTECT_FROM_CLOBBER FUNCTION # +################################# +protect_from_clobber ( ) { + PFC_INIT=1 + + # protect COPYING & INSTALL from overwrite by automake. the + # automake force option will (inappropriately) ignore the existing + # contents of a COPYING and/or INSTALL files (depending on the + # version) instead of just forcing *missing* files like it does + # for AUTHORS, NEWS, and README. this is broken but extremely + # prevalent behavior, so we protect against it by keeping a backup + # of the file that can later be restored. + + for file in COPYING INSTALL ; do + if test -f ${file} ; then + if test -f ${file}.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "Already backed up ${file} in `pwd`" + else + $VERBOSE_ECHO "Backing up ${file} in `pwd`" + $VERBOSE_ECHO "cp -p ${file} ${file}.$$.protect_from_automake.backup" + cp -p ${file} ${file}.$$.protect_from_automake.backup + fi + fi + done +} + + +############################## +# RECURSIVE_PROTECT FUNCTION # +############################## +recursive_protect ( ) { + + # for projects using recursive configure, run the build + # preparation steps for the subdirectories. this function assumes + # START_PATH was set to pwd before recursion begins so that + # relative paths work. + + # git 'r done, protect COPYING and INSTALL from being clobbered + protect_from_clobber + + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + # $VERBOSE_ECHO "Looking for configure template found `pwd`/$_configure" + + # look for subdirs + # $VERBOSE_ECHO "Looking for subdirs in `pwd`" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Protecting files from automake in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r done + recursive_protect + done + fi +} # end of recursive_protect + + +############################# +# RESTORE_CLOBBERED FUNCION # +############################# +restore_clobbered ( ) { + + # The automake (and autoreconf by extension) -f/--force-missing + # option may overwrite COPYING and INSTALL even if they do exist. + # Here we restore the files if necessary. + + spacer=no + + for file in COPYING INSTALL ; do + if test -f ${file}.$$.protect_from_automake.backup ; then + if test -f ${file} ; then + # compare entire content, restore if needed + if test "x`cat ${file}`" != "x`cat ${file}.$$.protect_from_automake.backup`" ; then + if test "x$spacer" = "xno" ; then + $VERBOSE_ECHO + spacer=yes + fi + # restore the backup + $VERBOSE_ECHO "Restoring ${file} from backup (automake -f likely clobbered it)" + $VERBOSE_ECHO "rm -f ${file}" + rm -f ${file} + $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" + mv ${file}.$$.protect_from_automake.backup ${file} + fi # check contents + elif test -f ${file}.$$.protect_from_automake.backup ; then + $VERBOSE_ECHO "mv ${file}.$$.protect_from_automake.backup ${file}" + mv ${file}.$$.protect_from_automake.backup ${file} + fi # -f ${file} + + # just in case + $VERBOSE_ECHO "rm -f ${file}.$$.protect_from_automake.backup" + rm -f ${file}.$$.protect_from_automake.backup + fi # -f ${file}.$$.protect_from_automake.backup + done + + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + return + fi + + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + fi + + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "rm -f \"${_aux_dir}/${file}.backup\"" + rm -f "${_aux_dir}/${file}.backup" + fi + done +} # end of restore_clobbered + + +############################## +# RECURSIVE_RESTORE FUNCTION # +############################## +recursive_restore ( ) { + + # restore COPYING and INSTALL from backup if they were clobbered + # for each directory recursively. + + # git 'r undone + restore_clobbered + + # find configure template + _configure="`locate_configure_template`" + if [ "x$_configure" = "x" ] ; then + return + fi + + # look for subdirs + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $_configure | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + CHECK_DIRS="" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + CHECK_DIRS="$CHECK_DIRS \"`pwd`/$dir\"" + fi + done + + # process subdirs + if [ ! "x$CHECK_DIRS" = "x" ] ; then + $VERBOSE_ECHO "Recursively scanning the following directories:" + $VERBOSE_ECHO " $CHECK_DIRS" + for dir in $CHECK_DIRS ; do + $VERBOSE_ECHO "Checking files for automake damage in $dir" + cd "$START_PATH" + eval "cd $dir" + + # recursively git 'r undone + recursive_restore + done + fi +} # end of recursive_restore + + +####################### +# INITIALIZE FUNCTION # +####################### +initialize ( ) { + + # this routine performs a variety of directory-specific + # initializations. some are sanity checks, some are preventive, + # and some are necessary setup detection. + # + # this function sets: + # CONFIGURE + # SEARCH_DIRS + # CONFIG_SUBDIRS + + ################################## + # check for a configure template # + ################################## + CONFIGURE="`locate_configure_template`" + if [ "x$CONFIGURE" = "x" ] ; then + $ECHO + $ECHO "A configure.ac or configure.in file could not be located implying" + $ECHO "that the GNU Build System is at least not used in this directory. In" + $ECHO "any case, there is nothing to do here without one of those files." + $ECHO + $ECHO "ERROR: No configure.in or configure.ac file found in `pwd`" + exit 1 + fi + + ##################### + # detect an aux dir # + ##################### + _aux_dir="`grep AC_CONFIG_AUX_DIR $CONFIGURE | grep -v '.*#.*AC_CONFIG_AUX_DIR' | tail -${TAIL_N}1 | sed 's/^[ ]*AC_CONFIG_AUX_DIR(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + if test ! -d "$_aux_dir" ; then + _aux_dir=. + else + $VERBOSE_ECHO "Detected auxillary directory: $_aux_dir" + fi + + ################################ + # detect a recursive configure # + ################################ + CONFIG_SUBDIRS="" + _det_config_subdirs="`grep AC_CONFIG_SUBDIRS $CONFIGURE | grep -v '.*#.*AC_CONFIG_SUBDIRS' | sed 's/^[ ]*AC_CONFIG_SUBDIRS(\(.*\)).*/\1/' | sed 's/.*\[\(.*\)\].*/\1/'`" + for dir in $_det_config_subdirs ; do + if test -d "`pwd`/$dir" ; then + $VERBOSE_ECHO "Detected recursive configure directory: `pwd`/$dir" + CONFIG_SUBDIRS="$CONFIG_SUBDIRS `pwd`/$dir" + fi + done + + ########################################################### + # make sure certain required files exist for GNU projects # + ########################################################### + _marker_found="" + _marker_found_message_intro='Detected non-GNU marker "' + _marker_found_message_mid='" in ' + for marker in foreign cygnus ; do + _marker_found_message=${_marker_found_message_intro}${marker}${_marker_found_message_mid} + _marker_found="`grep 'AM_INIT_AUTOMAKE.*'${marker} $CONFIGURE`" + if [ ! "x$_marker_found" = "x" ] ; then + $VERBOSE_ECHO "${_marker_found_message}`basename \"$CONFIGURE\"`" + break + fi + if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then + _marker_found="`grep 'AUTOMAKE_OPTIONS.*'${marker} Makefile.am`" + if [ ! "x$_marker_found" = "x" ] ; then + $VERBOSE_ECHO "${_marker_found_message}Makefile.am" + break + fi + fi + done + if [ "x${_marker_found}" = "x" ] ; then + _suggest_foreign=no + for file in AUTHORS COPYING ChangeLog INSTALL NEWS README ; do + if [ ! -f $file ] ; then + $VERBOSE_ECHO "Touching ${file} since it does not exist" + _suggest_foreign=yes + touch $file + fi + done + + if [ "x${_suggest_foreign}" = "xyes" ] ; then + $ECHO + $ECHO "Warning: Several files expected of projects that conform to the GNU" + $ECHO "coding standards were not found. The files were automatically added" + $ECHO "for you since you do not have a 'foreign' declaration specified." + $ECHO + $ECHO "Considered adding 'foreign' to AM_INIT_AUTOMAKE in `basename \"$CONFIGURE\"`" + if test -f "`dirname \"$CONFIGURE\"/Makefile.am`" ; then + $ECHO "or to AUTOMAKE_OPTIONS in your top-level Makefile.am file." + fi + $ECHO + fi + fi + + ################################################## + # make sure certain generated files do not exist # + ################################################## + for file in config.guess config.sub ltmain.sh ; do + if test -f "${_aux_dir}/${file}" ; then + $VERBOSE_ECHO "mv -f \"${_aux_dir}/${file}\" \"${_aux_dir}/${file}.backup\"" + mv -f "${_aux_dir}/${file}" "${_aux_dir}/${file}.backup" + fi + done + + ############################ + # search alternate m4 dirs # + ############################ + SEARCH_DIRS="" + for dir in m4 ; do + if [ -d $dir ] ; then + $VERBOSE_ECHO "Found extra aclocal search directory: $dir" + SEARCH_DIRS="$SEARCH_DIRS -I $dir" + fi + done + + ###################################### + # remove any previous build products # + ###################################### + if test -d autom4te.cache ; then + $VERBOSE_ECHO "Found an autom4te.cache directory, deleting it" + $VERBOSE_ECHO "rm -rf autom4te.cache" + rm -rf autom4te.cache + fi +# tcl/tk (and probably others) have a customized aclocal.m4, so can't delete it +# if test -f aclocal.m4 ; then +# $VERBOSE_ECHO "Found an aclocal.m4 file, deleting it" +# $VERBOSE_ECHO "rm -f aclocal.m4" +# rm -f aclocal.m4 +# fi + +} # end of initialize() + + +############## +# initialize # +############## + +# stash path +START_PATH="`pwd`" + +# Before running autoreconf or manual steps, some prep detection work +# is necessary or useful. Only needs to occur once per directory, but +# does need to traverse the entire subconfigure hierarchy to protect +# files from being clobbered even by autoreconf. +recursive_protect + +# start from where we started +cd "$START_PATH" + +# get ready to process +initialize + + +######################################### +# DOWNLOAD_GNULIB_CONFIG_GUESS FUNCTION # +######################################### + +# TODO - should make sure wget/curl exist and/or work before trying to +# use them. + +download_gnulib_config_guess () { + # abuse gitweb to download gnulib's latest config.guess via HTTP + config_guess_temp="config.guess.$$.download" + ret=1 + for __cmd in wget curl fetch ; do + $VERBOSE_ECHO "Checking for command ${__cmd}" + ${__cmd} --version > /dev/null 2>&1 + ret=$? + if [ ! $ret = 0 ] ; then + continue + fi + + __cmd_version=`${__cmd} --version | head -n 1 | sed -e 's/^[^0-9]\+//' -e 's/ .*//'` + $VERBOSE_ECHO "Found ${__cmd} ${__cmd_version}" + + opts="" + case ${__cmd} in + wget) + opts="-O" + ;; + curl) + opts="-o" + ;; + fetch) + opts="-t 5 -f" + ;; + esac + + $VERBOSE_ECHO "Running $__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" + eval "$__cmd \"${CONFIG_GUESS_URL}\" $opts \"${config_guess_temp}\"" > /dev/null 2>&1 + if [ $? = 0 ] ; then + mv -f "${config_guess_temp}" ${_aux_dir}/config.guess + ret=0 + break + fi + done + + if [ ! $ret = 0 ] ; then + $ECHO "Warning: config.guess download failed from: $CONFIG_GUESS_URL" + rm -f "${config_guess_temp}" + fi +} + + +############################## +# LIBTOOLIZE_NEEDED FUNCTION # +############################## +libtoolize_needed () { + ret=1 # means no, don't need libtoolize + for feature in AC_PROG_LIBTOOL AM_PROG_LIBTOOL LT_INIT ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + ret=0 # means yes, need to run libtoolize + break + fi + done + return ${ret} +} + + + +############################################ +# prepare build via autoreconf or manually # +############################################ +reconfigure_manually=no +if [ "x$HAVE_AUTORECONF" = "xyes" ] ; then + $ECHO + $ECHO $ECHO_N "Automatically preparing build ... $ECHO_C" + + $VERBOSE_ECHO "$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS" + autoreconf_output="`$AUTORECONF $SEARCH_DIRS $AUTORECONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoreconf_output" + + if [ ! $ret = 0 ] ; then + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + if [ ! "x`echo \"$autoreconf_output\" | grep libtoolize | grep \"No such file or directory\"`" = "x" ] ; then + $ECHO + $ECHO "Warning: autoreconf failed but due to what is usually a common libtool" + $ECHO "misconfiguration issue. This problem is encountered on systems that" + $ECHO "have installed libtoolize under a different name without providing a" + $ECHO "symbolic link or without setting the LIBTOOLIZE environment variable." + $ECHO + $ECHO "Restarting the preparation steps with LIBTOOLIZE set to $LIBTOOLIZE" + + export LIBTOOLIZE + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi + + $ECHO "Warning: $AUTORECONF failed" + + if test -f ltmain.sh ; then + $ECHO "libtoolize being run by autoreconf is not creating ltmain.sh in the auxillary directory like it should" + fi + + $ECHO "Attempting to run the preparation steps individually" + reconfigure_manually=yes + else + if [ "x$DOWNLOAD" = "xyes" ] ; then + if libtoolize_needed ; then + download_gnulib_config_guess + fi + fi + fi +else + reconfigure_manually=yes +fi + + +############################ +# LIBTOOL_FAILURE FUNCTION # +############################ +libtool_failure ( ) { + + # libtool is rather error-prone in comparison to the other + # autotools and this routine attempts to compensate for some + # common failures. the output after a libtoolize failure is + # parsed for an error related to AC_PROG_LIBTOOL and if found, we + # attempt to inject a project-provided libtool.m4 file. + + _autoconf_output="$1" + + if [ "x$RUN_RECURSIVE" = "xno" ] ; then + # we already tried the libtool.m4, don't try again + return 1 + fi + + if test -f "$LIBTOOL_M4" ; then + found_libtool="`$ECHO $_autoconf_output | grep AC_PROG_LIBTOOL`" + if test ! "x$found_libtool" = "x" ; then + if test -f acinclude.m4 ; then + rm -f acinclude.m4.$$.backup + $VERBOSE_ECHO "cat acinclude.m4 > acinclude.m4.$$.backup" + cat acinclude.m4 > acinclude.m4.$$.backup + fi + $VERBOSE_ECHO "cat \"$LIBTOOL_M4\" >> acinclude.m4" + chmod u+w acinclude.m4 + cat "$LIBTOOL_M4" >> acinclude.m4 + + # don't keep doing this + RUN_RECURSIVE=no + export RUN_RECURSIVE + untrap_abnormal + + $ECHO + $ECHO "Restarting the preparation steps with libtool macros in acinclude.m4" + $VERBOSE_ECHO sh $AUTOGEN_SH "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + sh "$AUTOGEN_SH" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + exit $? + fi + fi +} + + +########################### +# MANUAL_AUTOGEN FUNCTION # +########################### +manual_autogen ( ) { + + ################################################## + # Manual preparation steps taken are as follows: # + # aclocal [-I m4] # + # libtoolize --automake -c -f # + # aclocal [-I m4] # + # autoconf -f # + # autoheader # + # automake -a -c -f # + ################################################## + + ########### + # aclocal # + ########### + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $ACLOCAL failed" && exit 2 ; fi + + ############## + # libtoolize # + ############## + if libtoolize_needed ; then + if [ "x$HAVE_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + else + if [ "x$HAVE_ALT_LIBTOOLIZE" = "xyes" ] ; then + $VERBOSE_ECHO "$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS" + libtoolize_output="`$LIBTOOLIZE $ALT_LIBTOOLIZE_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$libtoolize_output" + + if [ ! $ret = 0 ] ; then $ECHO "ERROR: $LIBTOOLIZE failed" && exit 2 ; fi + fi + fi + + ########### + # aclocal # + ########### + # re-run again as instructed by libtoolize + $VERBOSE_ECHO "$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS" + aclocal_output="`$ACLOCAL $SEARCH_DIRS $ACLOCAL_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$aclocal_output" + + # libtoolize might put ltmain.sh in the wrong place + if test -f ltmain.sh ; then + if test ! -f "${_aux_dir}/ltmain.sh" ; then + $ECHO + $ECHO "Warning: $LIBTOOLIZE is creating ltmain.sh in the wrong directory" + $ECHO + $ECHO "Fortunately, the problem can be worked around by simply copying the" + $ECHO "file to the appropriate location (${_aux_dir}/). This has been done for you." + $ECHO + $VERBOSE_ECHO "cp -p ltmain.sh \"${_aux_dir}/ltmain.sh\"" + cp -p ltmain.sh "${_aux_dir}/ltmain.sh" + $ECHO $ECHO_N "Continuing build preparation ... $ECHO_C" + fi + fi # ltmain.sh + + if [ "x$DOWNLOAD" = "xyes" ] ; then + download_gnulib_config_guess + fi + fi # libtoolize_needed + + ############ + # autoconf # + ############ + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF $AUTOCONF_OPTIONS" + autoconf_output="`$AUTOCONF $AUTOCONF_OPTIONS 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # retry without the -f and check for usage of macros that are too new + ac2_59_macros="AC_C_RESTRICT AC_INCLUDES_DEFAULT AC_LANG_ASSERT AC_LANG_WERROR AS_SET_CATFILE" + ac2_55_macros="AC_COMPILER_IFELSE AC_FUNC_MBRTOWC AC_HEADER_STDBOOL AC_LANG_CONFTEST AC_LANG_SOURCE AC_LANG_PROGRAM AC_LANG_CALL AC_LANG_FUNC_TRY_LINK AC_MSG_FAILURE AC_PREPROC_IFELSE" + ac2_54_macros="AC_C_BACKSLASH_A AC_CONFIG_LIBOBJ_DIR AC_GNU_SOURCE AC_PROG_EGREP AC_PROG_FGREP AC_REPLACE_FNMATCH AC_FUNC_FNMATCH_GNU AC_FUNC_REALLOC AC_TYPE_MBSTATE_T" + + macros_to_search="" + ac_major="`echo ${AUTOCONF_VERSION}. | cut -d. -f1 | sed 's/[^0-9]//g'`" + ac_minor="`echo ${AUTOCONF_VERSION}. | cut -d. -f2 | sed 's/[^0-9]//g'`" + + if [ $ac_major -lt 2 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + else + if [ $ac_minor -lt 54 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros $ac2_54_macros" + elif [ $ac_minor -lt 55 ] ; then + macros_to_search="$ac2_59_macros $ac2_55_macros" + elif [ $ac_minor -lt 59 ] ; then + macros_to_search="$ac2_59_macros" + fi + fi + + configure_ac_macros=__none__ + for feature in $macros_to_search ; do + $VERBOSE_ECHO "Searching for $feature in $CONFIGURE" + found="`grep \"^$feature.*\" $CONFIGURE`" + if [ ! "x$found" = "x" ] ; then + if [ "x$configure_ac_macros" = "x__none__" ] ; then + configure_ac_macros="$feature" + else + configure_ac_macros="$feature $configure_ac_macros" + fi + fi + done + if [ ! "x$configure_ac_macros" = "x__none__" ] ; then + $ECHO + $ECHO "Warning: Unsupported macros were found in $CONFIGURE" + $ECHO + $ECHO "The `basename \"$CONFIGURE\"` file was scanned in order to determine if any" + $ECHO "unsupported macros are used that exceed the minimum version" + $ECHO "settings specified within this file. As such, the following macros" + $ECHO "should be removed from configure.ac or the version numbers in this" + $ECHO "file should be increased:" + $ECHO + $ECHO "$configure_ac_macros" + $ECHO + $ECHO $ECHO_N "Ignorantly continuing build preparation ... $ECHO_C" + fi + + ################### + # autoconf, retry # + ################### + $VERBOSE_ECHO + $VERBOSE_ECHO "$AUTOCONF" + autoconf_output="`$AUTOCONF 2>&1`" + ret=$? + $VERBOSE_ECHO "$autoconf_output" + + if [ ! $ret = 0 ] ; then + # test if libtool is busted + libtool_failure "$autoconf_output" + + # let the user know what went wrong + cat < header file. */ +#undef HAVE_BYTESWAP_H + +/* Define to 1 if you have the `clock_gettime' function. */ +#undef HAVE_CLOCK_GETTIME + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the `getopt_long' function. */ +#undef HAVE_GETOPT_LONG + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_BSG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_KDEV_T_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_NVME_IOCTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_TYPES_H + +/* Define to 1 if you have the `lseek64' function. */ +#undef HAVE_LSEEK64 + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Found NVMe */ +#undef HAVE_NVME + +/* Define to 1 if you have the `posix_fadvise' function. */ +#undef HAVE_POSIX_FADVISE + +/* Define to 1 if you have the `posix_memalign' function. */ +#undef HAVE_POSIX_MEMALIGN + +/* Define to 1 if you have the `pthread_cancel' function. */ +#undef HAVE_PTHREAD_CANCEL + +/* Define to 1 if you have the `pthread_kill' function. */ +#undef HAVE_PTHREAD_KILL + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `sysconf' function. */ +#undef HAVE_SYSCONF + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* use generic little-endian/big-endian instead */ +#undef IGNORE_FAST_LEBE + +/* option ignored */ +#undef IGNORE_LINUX_BSG + +/* compile out NVMe support */ +#undef IGNORE_NVME + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#undef LT_OBJDIR + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* sg3_utils on android */ +#undef SG_LIB_ANDROID + +/* sg3_utils Build Host */ +#undef SG_LIB_BUILD_HOST + +/* sg3_utils on FreeBSD */ +#undef SG_LIB_FREEBSD + +/* sg3_utils on linux */ +#undef SG_LIB_LINUX + +/* also MinGW environment */ +#undef SG_LIB_MINGW + +/* sg3_utils on Tru64 UNIX */ +#undef SG_LIB_OSF1 + +/* sg3_utils on Solaris */ +#undef SG_LIB_SOLARIS + +/* sg3_utils on Win32 */ +#undef SG_LIB_WIN32 + +/* full SCSI sense strings and NVMe status strings */ +#undef SG_SCSI_STRINGS + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* enable Win32 SPT Direct */ +#undef WIN32_SPT_DIRECT diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..1b4df71 --- /dev/null +++ b/configure.ac @@ -0,0 +1,163 @@ +AC_INIT(sg3_utils, 1.44, dgilbert@interlog.com) + +AM_INIT_AUTOMAKE([-Wall -Werror foreign]) +AM_MAINTAINER_MODE +AM_CONFIG_HEADER(config.h) + +AC_PROG_CC +# AC_PROG_CXX +AC_PROG_INSTALL + +# AM_PROG_AR is supported and needed since automake v1.12+ +ifdef([AM_PROG_AR], [AM_PROG_AR], []) + +# Adding libtools to the build seems to bring in C++ environment +AC_PROG_LIBTOOL + +# check for headers +AC_HEADER_STDC +AC_CHECK_HEADERS([byteswap.h], [], [], []) + +# check for functions +AC_CHECK_FUNCS(getopt_long, + GETOPT_O_FILES='', + GETOPT_O_FILES='getopt_long.o') +AC_CHECK_FUNCS(posix_fadvise) +AC_CHECK_FUNCS(posix_memalign) +AC_CHECK_FUNCS(gettimeofday) +AC_CHECK_FUNCS(sysconf) +AC_CHECK_FUNCS(lseek64) +SAVED_LIBS=$LIBS +AC_SEARCH_LIBS([pthread_create], [pthread]) +# AC_SEARCH_LIBS adds libraries at the start of $LIBS so remove $SAVED_LIBS +# from the end of $LIBS. +pthread_lib=${LIBS%${SAVED_LIBS}} +AC_CHECK_FUNCS([pthread_cancel pthread_kill]) +LIBS=$SAVED_LIBS +AC_SUBST(PTHREAD_LIB, [$pthread_lib]) + +SAVED_LIBS=$LIBS +AC_SEARCH_LIBS([clock_gettime], [rt]) +rt_lib=${LIBS%${SAVED_LIBS}} +AC_CHECK_FUNCS(clock_gettime) +LIBS=$SAVED_LIBS +AC_SUBST(RT_LIB, [$rt_lib]) + +AC_SUBST(GETOPT_O_FILES) + + +AC_CANONICAL_HOST + +AC_DEFINE_UNQUOTED(SG_LIB_BUILD_HOST, "${host}", [sg3_utils Build Host]) + +check_for_linux_nvme_headers() { + AC_CHECK_HEADERS([linux/nvme_ioctl.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], []) + AC_CHECK_HEADERS([linux/types.h linux/bsg.h linux/kdev_t.h], [], [], + [[#ifdef HAVE_LINUX_TYPES_H + # include + #endif + ]]) +} + +case "${host}" in + *-*-android*) + AC_DEFINE_UNQUOTED(SG_LIB_ANDROID, 1, [sg3_utils on android]) + AC_DEFINE_UNQUOTED(SG_LIB_LINUX, 1, [sg3_utils on linux]) + check_for_linux_nvme_headers;; + *-*-freebsd*|*-*-kfreebsd*-gnu*) + AC_DEFINE_UNQUOTED(SG_LIB_FREEBSD, 1, [sg3_utils on FreeBSD]) + AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe]) + LIBS="$LIBS -lcam";; + *-*-solaris*) + AC_DEFINE_UNQUOTED(SG_LIB_SOLARIS, 1, [sg3_utils on Solaris]);; + *-*-osf*) + AC_DEFINE_UNQUOTED(SG_LIB_OSF1, 1, [sg3_utils on Tru64 UNIX]);; + *-*-cygwin*) + AC_DEFINE_UNQUOTED(SG_LIB_WIN32, 1, [sg3_utils on Win32]) + # AC_CHECK_HEADERS([nvme.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], []) + AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe]) + CFLAGS="$CFLAGS -Wno-char-subscripts";; + *-*-mingw*) + AC_DEFINE_UNQUOTED(SG_LIB_WIN32, 1, [sg3_utils on Win32]) + AC_DEFINE_UNQUOTED(SG_LIB_MINGW, 1, [also MinGW environment]) + # AC_CHECK_HEADERS([nvme.h], [AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe])], [], []) + AC_DEFINE_UNQUOTED(HAVE_NVME, 1, [Found NVMe]) + CFLAGS="$CFLAGS -D__USE_MINGW_ANSI_STDIO";; + *-*-linux-gnu* | *-*-linux* | *) + AC_DEFINE_UNQUOTED(SG_LIB_LINUX, 1, [sg3_utils on linux]) + check_for_linux_nvme_headers;; +esac + +# Define platform-specific symbol. +AM_CONDITIONAL(OS_FREEBSD, [echo $host_os | grep 'freebsd' > /dev/null]) +AM_CONDITIONAL(OS_LINUX, [echo $host_os | grep '^linux' > /dev/null]) +AM_CONDITIONAL(OS_OSF, [echo $host_os | grep '^osf' > /dev/null]) +AM_CONDITIONAL(OS_SOLARIS, [echo $host_os | grep '^solaris' > /dev/null]) +AM_CONDITIONAL(OS_WIN32_MINGW, [echo $host_os | grep '^mingw' > /dev/null]) +AM_CONDITIONAL(OS_WIN32_CYGWIN, [echo $host_os | grep '^cygwin' > /dev/null]) +AM_CONDITIONAL(OS_ANDROID, [echo $host_os | grep 'android' > /dev/null]) + +AC_ARG_ENABLE([debug], + [ --enable-debug Turn on debugging], + [case "${enableval}" in + yes) debug=true ;; + no) debug=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;; + esac],[debug=false]) +AM_CONDITIONAL([DEBUG], [test x$debug = xtrue]) + +AC_ARG_ENABLE([linuxbsg], + AC_HELP_STRING([--disable-linuxbsg], [option ignored, this is placeholder]), + [AC_DEFINE_UNQUOTED(IGNORE_LINUX_BSG, 1, [option ignored], )], []) + +AC_ARG_ENABLE([win32-spt-direct], + AC_HELP_STRING([--enable-win32-spt-direct], [enable Win32 SPT Direct]), + AC_DEFINE_UNQUOTED(WIN32_SPT_DIRECT, 1, [enable Win32 SPT Direct], ) +) + +AC_ARG_ENABLE([scsistrings], + [AS_HELP_STRING([--disable-scsistrings], + [Disable full SCSI sense strings and NVMe status strings])], + [], [AC_DEFINE_UNQUOTED(SG_SCSI_STRINGS, 1, [full SCSI sense strings and NVMe status strings], )]) + +AC_ARG_ENABLE([nvme-supp], + AC_HELP_STRING([--disable-nvme-supp], [remove all or most NVMe code]), + [AC_DEFINE_UNQUOTED(IGNORE_NVME, 1, [compile out NVMe support], )], []) + +AC_ARG_ENABLE([fast-lebe], + AC_HELP_STRING([--disable-fast-lebe], [use generic little-endian/big-endian code instead]), + [AC_DEFINE_UNQUOTED(IGNORE_FAST_LEBE, 1, [use generic little-endian/big-endian instead], )], []) + + +AC_OUTPUT(Makefile include/Makefile lib/Makefile src/Makefile doc/Makefile scripts/Makefile) + + +# Borrowed from smartmontools configure.ac +# Note: Use `...` here as some shells do not properly parse '$(... case $x in X) ...)' +info=` + echo "-----------------------------------------------------------------------------" + echo "${PACKAGE}-${VERSION} configuration:" + echo "host operating system: $host" + echo "default C compiler: $CC" + + case "$host_os" in + mingw*) + echo "application manifest: ${os_win32_manifest:-built-in}" + echo "resource compiler: $WINDRES" + echo "message compiler: $WINDMC" + echo "NSIS compiler: $MAKENSIS" + ;; + + *) + echo "binary install path: \`eval eval eval echo $bindir\`" + echo "scripts install path: \`eval eval eval echo $bindir\`" + echo "man page install path: \`eval eval eval echo $mandir\`" + ;; + esac + echo "-----------------------------------------------------------------------------" +` + +AC_MSG_NOTICE([ +$info +]) + diff --git a/debian/README.debian4 b/debian/README.debian4 new file mode 100644 index 0000000..900b04e --- /dev/null +++ b/debian/README.debian4 @@ -0,0 +1,14 @@ +For whatever reason Debian build scripts (e.g. debhelper and dbclean) +seem to have changed in such a way to be Debian 4.0 ("etch") unfriendly. + +So when the ./build_debian.sh script is called on a Debian 4.0 system, +it fails saying the debhelper is too old. That can be fixed by editing +the 'control' file, changing this line: + Build-Depends: debhelper (>> 7), libtool, libcam-dev [kfreebsd-i386 kfreebsd-amd64] +to: + Build-Depends: debhelper, libtool, libcam-dev [kfreebsd-i386 kfreebsd-amd64] + +The script then dies in dbclean and the hack to get around that is to +edit the 'compat' file. It contains "7" which needs to be changed to +"4". Evidently "4" is deprecated and "5" is preferable and should work. + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..04d1cfe --- /dev/null +++ b/debian/changelog @@ -0,0 +1,254 @@ +sg3-utils (1.44-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Wed, 12 Sep 2018 14:00:00 -0400 + +sg3-utils (1.43-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Tue, 11 Sep 2018 09:00:00 -0400 + +sg3-utils (1.42-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Wed, 17 Feb 2016 15:00:00 -0500 + +sg3-utils (1.41-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Tue, 28 Apr 2015 11:00:00 -0400 + +sg3-utils (1.40-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Mon, 10 Nov 2014 23:00:00 -0500 + +sg3-utils (1.39-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Thu, 12 Jun 2014 09:00:00 -0400 + +sg3-utils (1.38-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Tue, 01 Apr 2014 15:00:00 -0400 + +sg3-utils (1.37-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Mon, 14 Oct 2013 15:00:00 -0400 + +sg3-utils (1.36-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Fri, 31 May 2013 10:00:00 -0400 + +sg3-utils (1.35-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Thu, 17 Jan 2013 19:00:00 -0500 + +sg3-utils (1.34-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Sat, 13 Oct 2012 19:00:00 -0400 + +sg3-utils (1.33-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Wed, 18 Jan 2012 14:00:00 -0500 + +sg3-utils (1.32-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Wed, 22 Jun 2011 16:00:00 -0400 + +sg3-utils (1.31-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Wed, 16 Feb 2011 16:00:00 -0500 + +sg3-utils (1.30-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Fri, 05 Nov 2010 10:30:00 -0400 + +sg3-utils (1.29-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Wed, 31 Mar 2010 23:00:00 -0400 + +sg3-utils (1.28-0.1) unstable; urgency=low + + * New upstream version + + -- Douglas Gilbert Fri, 02 Oct 2009 00:20:00 -0400 + +sg3-utils (1.27-0.1) unstable; urgency=low + + [ Martin Pitt ] + * Non-maintainer upload; this package blocks DeviceKit, and maintainer is + apparently not active any more. Also clean up and modernize the package + somewhat while we are at it. + * New upstream release (required for current devicekit-disks). + (Closes: #532546). Upstream original tarball repacked to not contain + debian/ directory. + * Rename libsgutils1{,-dev} to libsgutils2-2{,-dev}, upstream bumped SONAME. + Also call the library libgsutils2-2 to match SONAME. Add + Conflicts/Replaces for "libsgutils2" to provide a clean upgrade from the + packages as provided by Upstream and Ubuntu. + * debian/rules: Update build rules for upstream Makefile → autotools switch. + * debian/rules: Fix cleaning a clean source package. + * Demote Recommends to Suggests; the library doesn't actually call the + binaries in sg3-utils. (Closes: #532547) + * Drop debian/*.dirs, unnecessary with dh_install. + * Drop sg3-utils.preinst, not necessary to deal with kernel 2.4 any more. + * Drop libsgutils2-0.install, libsgutils2-0-dev.install, these packages + don't exist. + * Drop libsgutils2.post{inst,rm}: Basically empty, debhelper will create its + own. + * libsgutils2-dev.install: Drop *.lo. + * debian/compat: 4 -> 7. Bump debhelper build-depends accordingly. + * debian/control: Bump Standards-Version to 3.8.2. + * debian/control: Modernize package description for Linux 2.6. + (Closes: #506578) + * debian/rules: Drop -k argument from dh_clean (thanks lintian). + + [ Frank Lichtenheld ] + * debian/control, debian/rules: Add dependency of libsgutils-dev on + libcam-dev on kfreebsd-*. (Closes: #519460) + + -- Martin Pitt Mon, 22 Jun 2009 12:04:20 +0200 + +sg3-utils (1.24-2) unstable; urgency=low + + * Cleaned up package description (Closes: #445920). + * Don't make libtool think rpath is necessary (Closes: #451153) + * Capitalized Linux in extended description (Closes: #457526). + * Completed sentence in libsgutils1 long description (Closes: #421391). + * Added patch from Aurelian Jarno to build on kfreebsd-* (Closes: #455430). + * Symlinks in examples directory cleaned up (Closes: #372610). + + -- Eric Schwartz (Skif) Sun, 30 Dec 2007 11:52:58 -0700 + +sg3-utils (1.24-1) unstable; urgency=low + + * New upstream release + * Conflicts with upstream libsgutils package libsgutils-1-0 (closes: #391077) + + -- Eric Schwartz (Skif) Tue, 5 Jun 2007 17:04:21 -0600 + +sg3-utils (1.21-2.1) unstable; urgency=medium + + * Non-maintainer upload. + * Fix FTBFS due to old syscall usage (Closes: #395512). + + -- Luk Claes Sun, 5 Nov 2006 17:23:29 +0100 + +sg3-utils (1.21-2) unstable; urgency=low + + * Added Depends on libsgutils1 to libsgutils1-dev (closes: #387798) + + -- Eric Schwartz (Skif) Fri, 22 Sep 2006 00:20:28 -0600 + +sg3-utils (1.21-1) unstable; urgency=low + + * New upstream release + + -- Eric Schwartz (Skif) Wed, 13 Sep 2006 21:54:30 -0600 + +sg3-utils (1.20-1) unstable; urgency=low + + * New upstream release + + -- Eric Schwartz (Skif) Wed, 26 Apr 2006 22:31:15 -0600 + +sg3-utils (1.17-3) unstable; urgency=low + + * Cleaned up sg_read(8) manpage (Closes: #294521) + + -- Eric Schwartz (Skif) Mon, 13 Feb 2006 17:59:46 -0700 + +sg3-utils (1.17-2) unstable; urgency=low + + * Add libtool to build-depends + + -- Eric Schwartz (Skif) Tue, 4 Oct 2005 19:40:00 -0600 + +sg3-utils (1.17-1) unstable; urgency=low + + * New upstream version + + -- Eric Schwartz (Skif) Sat, 1 Oct 2005 13:26:16 -0600 + +sg3-utils (1.08-2) unstable; urgency=low + + * Fix packaging bug that accidentally left off binaries. Sigh. + (closes: #271906) + + -- Eric Schwartz (Skif) Wed, 15 Sep 2004 22:40:06 -0600 + +sg3-utils (1.08-1) unstable; urgency=low + + * New upstream version + * Unified package description with list of tools actually installed + (closes: #271093) + + -- Eric Schwartz (Skif) Sun, 12 Sep 2004 21:22:42 -0600 + +sg3-utils (1.05-1) unstable; urgency=low + + * New upstream release + * updated description to match tools in package (closes: #221143) + + -- Eric Schwartz Tue, 18 Nov 2003 22:22:29 -0700 + +sg3-utils (1.03-1) unstable; urgency=low + + * New upstream release (closes: #181999) + + -- Eric Schwartz Tue, 29 Apr 2003 20:18:30 -0600 + +sg3-utils (0.95-4) unstable; urgency=low + + * Only warns if installed on a kernel version < 2.4 (closes: #136434) + + -- Eric Schwartz Tue, 28 May 2002 22:55:29 -0600 + +sg3-utils (0.95-3) unstable; urgency=low + + * Extended description to include descriptions of all tools included in + the package. (closes: #121968) + + -- Eric Schwartz Sun, 13 Jan 2002 17:09:27 -0700 + +sg3-utils (0.95-2) unstable; urgency=low + + * Packaging manpages (closes: #122692) + * Conflicts with cdwrite (closes: #123779) + + -- Eric Schwartz Wed, 2 Jan 2002 01:05:08 -0700 + +sg3-utils (0.95-1) unstable; urgency=low + + * Initial Release. + * Adjusted Makefile to include $DESTDIR + + -- Eric Schwartz Wed, 14 Nov 2001 17:05:56 -0700 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..8364643 --- /dev/null +++ b/debian/control @@ -0,0 +1,43 @@ +Source: sg3-utils +Section: admin +Priority: optional +Maintainer: Eric Schwartz (Skif) +Build-Depends: debhelper (>> 7), libtool, libcam-dev [kfreebsd-i386 kfreebsd-amd64] +Standards-Version: 3.8.2 + +Package: sg3-utils +Architecture: any +Depends: ${shlibs:Depends} +Conflicts: sg-utils, cdwrite +Replaces: sg-utils +Description: utilities for devices using the SCSI command set. + Most OSes have SCSI pass-through interfaces that enable user space programs + to send SCSI commands to a device and fetch the response. With SCSI to ATA + Translation (SAT) many ATA disks now can process SCSI commands. Typically + each utility in this package implements one SCSI command. See the draft + standards at www.t10.org for SCSI command definitions plus SAT. ATA + commands are defined in the draft standards at www.t13.org . For a mapping + between supported SCSI and ATA commands and utility names in this package + see the COVERAGE file. Also some support for NVMe devices, especially via + sg_ses to NVMe enclsoures. + +Package: libsgutils2-2 +Section: libs +Depends: ${shlibs:Depends} +Architecture: any +Conflicts: libsgutils2 +Replaces: libsgutils2 +Suggests: sg3-utils +Description: utilities for devices using the SCSI command set (shared libraries) + Shared library used by the utilities in the sg3-utils package. + +Package: libsgutils2-dev +Section: libdevel +Architecture: any +Depends: libsgutils2-2 (= ${binary:Version}), ${shlibs:Depends}, ${kfreebsd:Depends} +Conflicts: libsgutils1-dev +Suggests: sg3-utils +Description: utilities for devices using the SCSI command set (developer files) + Developer files (i.e. headers and a static library) which are associated with + the utilities in the sg3-utils package. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..7f1906b --- /dev/null +++ b/debian/copyright @@ -0,0 +1,24 @@ + +Copyright (c) 1999-2018, Douglas Gilbert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..67fe85a --- /dev/null +++ b/debian/docs @@ -0,0 +1,7 @@ +README +README.sg_start +AUTHORS +COVERAGE +CREDITS +INSTALL +ChangeLog diff --git a/debian/libsgutils2-2.install b/debian/libsgutils2-2.install new file mode 100644 index 0000000..093956b --- /dev/null +++ b/debian/libsgutils2-2.install @@ -0,0 +1 @@ +usr/lib/*.so.* diff --git a/debian/libsgutils2-dev.install b/debian/libsgutils2-dev.install new file mode 100644 index 0000000..5d09f30 --- /dev/null +++ b/debian/libsgutils2-dev.install @@ -0,0 +1,4 @@ +usr/include/scsi +usr/lib/*.so +usr/lib/*.la +usr/lib/*.a diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..d3a2b0c --- /dev/null +++ b/debian/rules @@ -0,0 +1,83 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 by Joey Hess. +# +# This version is for a hypothetical package that builds an +# architecture-dependant package, as well as an architecture-independent +# package. + +# Uncomment this to turn on verbose mode. +# export DH_VERBOSE=1 + +DEB_HOST_ARCH_OS := $(shell dpkg-architecture -qDEB_HOST_ARCH_OS 2>/dev/null) + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) --bindir=/usr/bin --prefix=/usr --mandir=\$${prefix}/share/man + touch configure-stamp + +build: configure-stamp build-stamp +build-stamp: + dh_testdir + + # Add here commands to compile the package. + PREFIX=/usr MANDIR=/usr/share/man $(MAKE) -e + + touch build-stamp + +clean: + dh_testdir + dh_testroot + + # Add here commands to clean up after the build process. + -$(MAKE) distclean + + rm -f build-stamp configure-stamp debian/substvars + + dh_clean + +install: DH_OPTIONS= +install: build + dh_testdir + dh_testroot + dh_clean + dh_installdirs + + # Add here commands to install the package into debian/tmp + $(MAKE) -e install DESTDIR=$(CURDIR)/debian/tmp PREFIX=/usr + + dh_install --autodest --sourcedir=debian/tmp + + dh_installman + +# Build architecture-independent files here. +# Pass -i to all debhelper commands in this target to reduce clutter. +binary-indep: build install +# nothing to do here + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir -a + dh_testroot -a + dh_installdocs -a + dh_installexamples -a + dh_installmenu -a + dh_installchangelogs ChangeLog -a + dh_strip -a + dh_link -a + dh_compress -a -X archive -X .c -X .h + dh_fixperms -a + dh_makeshlibs -V -v + dh_installdeb -a +ifeq ($(DEB_HOST_ARCH_OS),kfreebsd) + echo kfreebsd:Depends=libcam-dev >>debian/libsgutils2-dev.substvars +endif + dh_shlibdeps -ldebian/tmp/usr/lib -L libsgutils2 + dh_gencontrol -a + dh_md5sums -a + dh_builddeb -a + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/debian/sg3-utils.examples b/debian/sg3-utils.examples new file mode 100644 index 0000000..a9da205 --- /dev/null +++ b/debian/sg3-utils.examples @@ -0,0 +1,2 @@ +examples/* +archive diff --git a/debian/sg3-utils.install b/debian/sg3-utils.install new file mode 100644 index 0000000..6161376 --- /dev/null +++ b/debian/sg3-utils.install @@ -0,0 +1,2 @@ +usr/bin/* +usr/share/man/man8/* diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..9a1816e --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,41 @@ + +man_MANS = \ + scsi_mandat.8 scsi_readcap.8 scsi_ready.8 scsi_satl.8 scsi_start.8 \ + scsi_stop.8 scsi_temperature.8 sg3_utils.8 sg_bg_ctl.8 \ + sg_compare_and_write.8 sg_decode_sense.8 sg_format.8 sg_get_config.8 \ + sg_get_lba_status.8 sg_ident.8 sg_inq.8 sg_logs.8 sg_luns.8 \ + sg_modes.8 sg_opcodes.8 sg_persist.8 sg_prevent.8 sg_raw.8 sg_rdac.8 \ + sg_read_attr.8 sg_read_block_limits.8 sg_read_buffer.8 \ + sg_read_long.8 sg_readcap.8 sg_reassign.8 sg_referrals.8 \ + sg_rep_zones.8 sg_requests.8 sg_reset_wp.8 sg_rmsn.8 sg_rtpg.8 \ + sg_safte.8 sg_sanitize.8 sg_sat_identify.8 sg_sat_phy_event.8 \ + sg_sat_read_gplog.8 sg_sat_set_features.8 sg_seek.8 sg_senddiag.8 \ + sg_ses.8 sg_ses_microcode.8 sg_start.8 sg_stpg.8 sg_stream_ctl.8 \ + sg_sync.8 sg_timestamp.8 sg_turs.8 sg_unmap.8 sg_verify.8 sg_vpd.8 \ + sg_wr_mode.8 sg_write_buffer.8 sg_write_long.8 sg_write_same.8 \ + sg_write_verify.8 sg_write_x.8 sg_zone.8 +CLEANFILES = + +if OS_LINUX +man_MANS += \ + rescan-scsi-bus.sh.8 scsi_logging_level.8 sg_copy_results.8 sg_dd.8 \ + sg_emc_trespass.8 sg_map.8 sg_map26.8 sg_rbuf.8 sg_read.8 sg_reset.8 \ + sg_scan.8 sg_test_rwbuf.8 sg_xcopy.8 sginfo.8 sgm_dd.8 sgp_dd.8 +CLEANFILES += sg_scan.8 +sg_scan.8: sg_scan.8.linux + cp -p $< $@ +endif + +if OS_WIN32_MINGW +man_MANS += sg_scan.8 +CLEANFILES += sg_scan.8 +sg_scan.8: sg_scan.8.win32 + cp -p $< $@ +endif + +if OS_WIN32_CYGWIN +man_MANS += sg_scan.8 +CLEANFILES += sg_scan.8 +sg_scan.8: sg_scan.8.win32 + cp -p $< $@ +endif diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..8920562 --- /dev/null +++ b/doc/README @@ -0,0 +1,34 @@ +Various html files used to be included in this directory but they were +beginning to bloat the size of the source tarball so they have been +removed in version 1.24 . + +Here are the urls of the files that were previously here plus a brief +summary: + +http://sg.danny.cz/sg/sg3_utils.html + - overview and examples of this package + +http://sg.danny.cz/sg/sg_dd.html + - discussion and examples of the sg_dd utility which is a dd variant + (also discusses the sgm_dd, sgp_dd and sg_read utilities) + +http://sg.danny.cz/sg/sg_ses.html + - discussion and examples of the sg_ses utility. SCSI Enclosure + Services (SES) devices may contain a lot of information, + structured in a non-trivial way. + +http://sg.danny.cz/sg/sg_io.html + - discussion of Linux SG_IO ioctl (SCSI pass-through) + +http://sg.danny.cz/sg/tools.html + - overview of around 25 storage and SCSI tools + + +There are two versions of sg_scan: one for Linux and the other for Windows. +They have different man pages: sg_scan.8.linux and sg_scan.8.win32 with +the Makefile logic (rule in Makefile.am) copying the appropriate one to +sg_scan.8 . sg_scan is not supported for other ports. + + +Douglas Gilbert +10th February 2010 diff --git a/doc/rescan-scsi-bus.sh.8 b/doc/rescan-scsi-bus.sh.8 new file mode 100644 index 0000000..4435c7d --- /dev/null +++ b/doc/rescan-scsi-bus.sh.8 @@ -0,0 +1,127 @@ +.TH RESCAN\-SCSI\-BUS.SH "1" "July 2018" "rescan\-scsi\-bus.sh" "User Commands" +.SH NAME +rescan-scsi-bus.sh \- script to add and remove SCSI devices without rebooting +.SH SYNOPSIS +.B rescan\-scsi\-bus.sh +[\fI\-\-alltargets\fR] [\fI\-\-attachpq3\fR] [\fI\-c\fR] +[\fI\-\--channels=CLIST\fR] [\fI\-\-color\fR] [\fI\-d\fR] [\fI\-\-flush\fR] +[\fI\-f\fR] [\fI\-\-forceremove\fR] [\fI\-\-forcerescan\fR] [\fI\-\-help\fR] +[\fI\-\-hosts=HLIST\fR] [\fI\-\-ids=TLIST\fR] [\fI\-\-ignore\-rev\fR] +[\fI\-\-issue\-lip\fR] [\fI\-i\fR] [\fI\-\-issue\-lip\-wait=SECS\fR] [\fI\-I SECS\fR] +[\fI\-l\fR] +[\fI\-L NUM\fR] [\fI\-\-largelun\fR] [\fI\-\-luns=LLIST\fR] [\fI\-m\fR] +[\fI\-\-multipath\fR] [\fI\-\-nooptscan\fR] [\fI\-\-nosync\fR] +[\fI\-\-remove\fR] [\fI\-\-removelun2\fR] [\fI\-\-resize\fR] +[\fI\-\-sparselun\fR] [\fI\-\-sync\fR] [\fI\-\-update\fR] [\fI\-\-version\fR] +[\fI\-\-wide\fR] [\fIHOST1 \fR[\fIHOST2 \fR...]] +.SH OPTIONS +Option are ordered by their long name. Those without a long name are ordered +as if their single letter was a long name. +.TP +\fB\-a\fR, \fB\-\-alltargets\fR +scan all targets, not just currently existing [default: disabled] +.TP +\fB\-\-attachpq3\fR +tell kernel to attach sg to LUN 0 that reports PQ=3 +.TP +\fB\-c\fR +enables scanning of channels 0 1 [default: 0 / all detected ones] +.TP +\fB\-\-channels\fR=\fICLIST\fR +scan only channel(s) in \fICLIST\fR +.TP +\fB\-\-color\fR +use coloured prefixes OLD/NEW/DEL +.TP +\fB\-d\fR +enable debug [default: 0] +.TP +\fB\-f\fR, \fB\-\-flush\fR +flush failed multipath devices [default: disabled] +.TP +\fB\-\-forceremove\fR +remove and readd every device (DANGEROUS) +.TP +\fB\-\-forcerescan\fR +rescan existing devices +.TP +\fB\-h\fR, \fB\-\-help\fR +print usage message then exit +.TP +\fB\-\-hosts\fR=\fIHLIST\fR +scan only host(s) in \fIHLIST\fR +.TP +\fB\-\-ids\fR=\fITLIST\fR +scan only target ID(s) in \fITLIST\fR +.TP +\fB\-\-ignore\-rev\fR +ignore (firmware) revision change. This is the third text field (4 bytes +long) in a standard INQUIRY response. +.TP +\fB\-i\fR, \fB\-\-issue\-lip\fR +issue a FibreChannel LIP reset [default: disabled] +.TP +\fB\-I SECS\fR, \fB\-\-issue\-lip\-wait=SECS\fR +issue a FibreChannel LIP reset and then wait SECS seconds. +.TP +\fB\-L\fR NUM +activates scanning for LUNs 0\-\-NUM [default: 0] +.TP +\fB\-l\fR +activates scanning for LUNs 0\-\-7 [default: 0] +.TP +\fB\-\-largelun\fR +tell kernel to support LUNs > 7 even on SCSI2 devs +.TP +\fB\-\-luns\fR=\fINLIST\fR +scan only lun(s) in \fINLIST\fR +.TP +\fB\-m\fR, \fB\-\-multipath\fR +update multipath devices [default: disabled] +.TP +\fB\-\-nooptscan\fR +don't stop looking for LUNs is 0 is not found +.TP +\fB\-\-nosync\fR +do not issue a sync [default: sync if remove] +.TP +\fB\-r\fR, \fB\-\-remove\fR +enables removing of devices [default: disabled] +.TP +\fB\-\-reportlun2\fR +tell kernel to try REPORT_LUN even on SCSI2 devices +.TP +\fB\-s\fR, \fB\-\-resize\fR +look for resized disks and reload associated multipath devices, if applicable +.TP +\fB\-\-sparselun\fR +tell kernel to support sparse LUN numbering +.TP +\fB\-\-sync\fR +issue a sync [default: sync if remove] +.TP +\fB\-u\fR, \fB\-\-update\fR +look for existing disks that have been remapped +.TP +\fB\-V\fR, \fB\-\-version\fR +shows version string then exits. The version string is a numeric datestamp +of the form YYYYMMDD. +.TP +\fB\-w\fR, \fB\-\-wide\fR +scan for target device IDs 0\-\-15 [default: 0\-\-7] +.IP +Host numbers may thus be specified either directly on cmd line (deprecated) +or with the \fB\-\-hosts\fR=\fILIST\fR parameter (recommended). +.PP +Arguments to options that end in \fILIST\fR (e.g. \fITLIST\fR) can have this +form: +.br + A[\-B][,C[\-D]]... +.br +which is a comma separated list of single values and/or ranges (no spaces +allowed). +.SH SEE ALSO +There is a brief descripion here: +http://fibrevillage.com/storage/585-rescan-scsi-bus-sh-script-for-adding-and-removing-scsi-devices-without-rebooting +.PP +\fBsg3_utils\fR Homepage: \fBhttp://sg.danny.cz/sg\fR diff --git a/doc/scsi_logging_level.8 b/doc/scsi_logging_level.8 new file mode 100644 index 0000000..10e946b --- /dev/null +++ b/doc/scsi_logging_level.8 @@ -0,0 +1,119 @@ +.TH SCSI_LOGGING_LEVEL "8" "January 2014" "sg3_utils\-1.41" SG3_UTILS +.SH NAME +scsi_logging_level \- access Linux SCSI logging level information +.SH SYNOPSIS +.B scsi_logging_level +[\fI\-\-all=LEV\fR] [\fI\-\-create\fR] [\fI\-\-error=LEV\fR] [\fI\-\-get\fR] +[\fI\-\-help\fR] [\fI\-\-highlevel=LEV\fR] [\fI\-\-hlcomplete=LEV\fR] +[\fI\-\-hlqueue=LEV\fR] [\fI\-\-ioctl=LEV\fR] [\fI\-\-llcomplete=LEV\fR] +[\fI\-\-llqueue=LEV\fR] [\fI\-\-lowlevel=LEV\fR] [\fI\-\-midlevel=LEV\fR] +[\fI\-\-mlcomplete=LEV\fR] [\fI\-\-mlqueue=LEV\fR] [\fI\-\-scan=LEV\fR] +[\fI\-\-set\fR] [\fI\-\-timeout=LEV\fR] [\fI\-\-version\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script accesses the Linux SCSI subsystem logging +level. The current values can be shown (e.g. with \fI\-\-get\fR) +or changed (e.g. with \fI\-\-set\fR). Superuser permissions will +typically be required to set the logging level. +.PP +One of these options: \fI\-\-create\fR, \fI\-\-get\fR or \fI\-\-set\fR +is required. Only one of them can be given. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-a\fR, \fB\-\-all\fR=\fILEV\fR +\fILEV\fR is used for all SCSI_LOG fields. +.TP +\fB\-c\fR, \fB\-\-create\fR +Options are parsed and placed in internal fields that are displayed but +no logging levels are changed within the Linux kernel. +.TP +\fB\-E\fR, \fB\-\-error\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_ERROR field. +.TP +\fB\-g\fR, \fB\-\-get\fR +Fetches the current SCSI logging levels from the Linux kernel and +displays them. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-highlevel\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_HLQUEUE and SCSI_LOG_HLCOMPLETE fields. +.TP +\fB\-\-hlcomplete\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_HLCOMPLETE field. +.TP +\fB\-\-hlqueue\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_HLQUEUE field. +.TP +\fB\-I\fR, \fB\-\-ioctl\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_IOCTL field. +.TP +\fB\-\-llcomplete\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_LLCOMPLETE field. +.TP +\fB\-\-llqueue\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_LLQUEUE field. +.TP +\fB\-L\fR, \fB\-\-lowlevel\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_LLQUEUE and SCSI_LOG_LLCOMPLETE fields. +.TP +\fB\-M\fR, \fB\-\-midlevel\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_MLQUEUE and SCSI_LOG_MLCOMPLETE fields. +.TP +\fB\-\-mlcomplete\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_MLCOMPLETE field. +.TP +\fB\-\-mlqueue\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_MLQUEUE field. +.TP +\fB\-S\fR, \fB\-\-scan\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_SCAN field. +.TP +\fB\-s\fR, \fB\-\-set\fR +Uses the fields specified in this command's options and attempts to +apply them to the Linux SCSI subsystem logging levels. Typically superuser +permissions will be required to do this. +.TP +\fB\-T\fR, \fB\-\-timeout\fR=\fILEV\fR +\fILEV\fR is placed in the SCSI_LOG_TIMEOUT field. +.TP +\fB\-v\fR, \fB\-\-version\fR +Outputs the version information and then exits. +.SH NOTES +The \fI\-\-get\fR and \fI\-\-set\fR options access the +/proc/sys/dev/scsi/logging_level pseudo file. +.SH EXIT STATUS +The exit status of this script is 0 when it is successful. Any other +exit status indicates that an error has occurred. +.SH EXAMPLES +The following will set SCSI_LOG_ERROR to level 5 in the Linux kernel. It +requires root permissions: +.PP + scsi_logging_level \-s \-E 5 +.PP +So as to not interfere with other SCSI subsystem upper level drivers (ULDs) +which most likely will be active at the same time, the Linux sg driver uses +SCSI_LOG_TIMEOUT for logging purposes. To see full debugging and trace from +the sg driver use: +.PP + scsi_logging_level \-s \-T 7 +.PP +The output from the sg driver caused by this will go to the system +logs (e.g. /var/log/syslog). To reduce the amount of output use a number +lower than 7. Using 0 will turn off the tracing and debug. +.SH AUTHORS +Written by IBM. Small alterations by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co IBM Corp. 2006 +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.PP +The software was obtained from an IBM package called s390\-tools\-1.6.2 +found on that company's "developerworks" site. The most recent version of +that package at this time is 1.8.3 . diff --git a/doc/scsi_mandat.8 b/doc/scsi_mandat.8 new file mode 100644 index 0000000..7606861 --- /dev/null +++ b/doc/scsi_mandat.8 @@ -0,0 +1,44 @@ +.TH SCSI_MANDAT "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_mandat \- check SCSI device support for mandatory commands +.SH SYNOPSIS +.B scsi_mandat +[\fI\-\-help\fR] [\fI\-\-log\fR] [\fI\-\-quiet\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls several SCSI commands on the given +\fIDEVICE\fR. These SCSI commands are considered mandatory (although +that varies a little depending on which standard/draft the \fIDEVICE\fR +complies with). The results of each test and a pass/fail count are +output. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-L\fR, \fB\-\-log\fR +the output to stderr (from each SCSI command executed) is appended to +a file called 'scsi_mandat.err' in the current working directory. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +the amount of output is reduced and typically only the pass/fail +count is output. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.SH EXIT STATUS +The exit status of this script is the number of "bad" errors found. +So an exit status of 0 means all mandatory SCSI commands worked as +expected. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2011\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq,sg_luns,sg_turs,sg_requests,sg_vpd,sg_senddiag (sg3_utils) diff --git a/doc/scsi_readcap.8 b/doc/scsi_readcap.8 new file mode 100644 index 0000000..40532b0 --- /dev/null +++ b/doc/scsi_readcap.8 @@ -0,0 +1,51 @@ +.TH SCSI_READCAP "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_readcap \- do SCSI READ CAPACITY command on disks +.SH SYNOPSIS +.B scsi_readcap +[\fI\-\-brief\fR] [\fI\-\-help\fR] [\fI\-\-long\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR [\fIDEVICE\fR]* +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls the sg_readcap utility on each given +\fIDEVICE\fR. This will send a SCSI READ CAPACITY command to each +\fIDEVICE\fR. +.PP +The default action of this script is to send the 10 byte cdb READ +CAPACITY(10) command to each \fIDEVICE\fR. If a response indicates +the number of blocks is greater than or equal to '2**32 \- 1' then +the READ CAPACITY(16) is sent and its response is output. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-brief\fR +shortens the output to two hexadecimal numbers, both prefixed by '0x'. +The first number is the number of blocks available and the second is +the size of each blocks in bytes (e.g. '0x12a19eb0 0x200'). If an error +is detected '0x0 0x0' is output and the script continues if there are +more \fIDEVICE\fRs. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-l\fR, \fB\-\-long\fR +the default is to send the READ CAPACITY(10) command (i.e. the 10 byte +cdb variant). When this option is given the READ CAPACITY(16) command +is sent. The latter command yields more information in its response. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.SH EXIT STATUS +The exit status of this script is 0 when it is successful. Otherwise the +exit status is that of the last sg_readcap utility called. See +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2009\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_readcap (sg3_utils) diff --git a/doc/scsi_ready.8 b/doc/scsi_ready.8 new file mode 100644 index 0000000..50a6ed7 --- /dev/null +++ b/doc/scsi_ready.8 @@ -0,0 +1,40 @@ +.TH SCSI_READY "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_ready \- do SCSI TEST UNIT READY on devices +.SH SYNOPSIS +.B scsi_ready +[\fI\-\-brief\fR] [\fI\-\-help\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR [\fIDEVICE\fR]* +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls the sg_turs utility on each given +\fIDEVICE\fR. This will send a SCSI TEST UNIT READY command to each +\fIDEVICE\fR. Disks, tape drives and DVD/BD players amongst others +may respond to this SCSI command. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-brief\fR +for each \fIDEVICE\fR given output a line containing either ' ready' +or ' device not ready'. If \fIDEVICE\fR is not found or there is +another serious error then an error message will appear instead. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.SH EXIT STATUS +The exit status of this script is 0 when it is successful. Otherwise the +exit status is that of the last sg_turs utility called. See +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2009\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_turs (sg3_utils) diff --git a/doc/scsi_satl.8 b/doc/scsi_satl.8 new file mode 100644 index 0000000..da6c43a --- /dev/null +++ b/doc/scsi_satl.8 @@ -0,0 +1,44 @@ +.TH SCSI_SATL "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_satl \- check SCSI to ATA Translation (SAT) device support +.SH SYNOPSIS +.B scsi_satl +[\fI\-\-help\fR] [\fI\-\-log\fR] [\fI\-\-quiet\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls several SCSI commands on the given +\fIDEVICE\fR that is assumed to be an ATA device behind a SCSI +to ATA Translation (SAT) layer (SATL). The results of each test +and a pass/fail count are output. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-L\fR, \fB\-\-log\fR +the output to stderr (from each SCSI command executed) is appended to +a file called 'scsi_satl.err' in the current working directory. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +the amount of output is reduced and typically only the pass/fail +count is output. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.SH EXIT STATUS +The exit status of this script is the number of "bad" errors found. +So an exit status of 0 means all mandatory SCSI commands worked as +expected. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2011\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq, sg_luns, sg_turs, sg_requests, sg_vpd, sg_senddiag, sg_modes, +.B sg_sat_identify (sg3_utils) diff --git a/doc/scsi_start.8 b/doc/scsi_start.8 new file mode 100644 index 0000000..f98985b --- /dev/null +++ b/doc/scsi_start.8 @@ -0,0 +1,40 @@ +.TH SCSI_START "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_start \- start one or more SCSI disks +.SH SYNOPSIS +.B scsi_start +[\fI\-\-help\fR] [\fI\-\-verbose\fR] [\fI\-\-wait\fR] +\fIDEVICE\fR [\fIDEVICE\fR]* +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls the sg_start utility on each given +\fIDEVICE\fR. The purpose is to spin up (start) each given \fIDEVICE\fR. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.TP +\fB\-w\fR, \fB\-\-wait\fR +wait for the spin up (start) on each given \fIDEVICE\fR to complete. +The default action is to do each start in immediate mode. +.SH NOTES +If a large number of disks are spun up at the same time (i.e. without +the \fI\-\-wait\fR option) then the power supply may be overloaded. +.SH EXIT STATUS +The exit status of this script is 0 when it is successful. Otherwise the +exit status is that of the last sg_start utility called. See +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2009\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_start (sg3_utils) diff --git a/doc/scsi_stop.8 b/doc/scsi_stop.8 new file mode 100644 index 0000000..488c7cf --- /dev/null +++ b/doc/scsi_stop.8 @@ -0,0 +1,41 @@ +.TH SCSI_STOP "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_stop \- stop (spin down) one or more SCSI disks +.SH SYNOPSIS +.B scsi_stop +[\fI\-\-help\fR] [\fI\-\-verbose\fR] [\fI\-\-wait\fR] +\fIDEVICE\fR [\fIDEVICE\fR]* +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls the sg_start utility on each given +\fIDEVICE\fR. The purpose is to spin down (stop) each given \fIDEVICE\fR. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.TP +\fB\-w\fR, \fB\-\-wait\fR +wait for the spin down (stop) on each given \fIDEVICE\fR to complete. +The default action is to do each stop in immediate mode. +.SH NOTES +The sg_start utility calls the SCSI START STOP UNIT command and can +either start (spin up) or stop (spin down) a SCSI disk depending +on the given command line options. +.SH EXIT STATUS +The exit status of this script is 0 when it is successful. Otherwise the +exit status is that of the last sg_start utility called. See +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2009\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_start (sg3_utils) diff --git a/doc/scsi_temperature.8 b/doc/scsi_temperature.8 new file mode 100644 index 0000000..e9ff181 --- /dev/null +++ b/doc/scsi_temperature.8 @@ -0,0 +1,35 @@ +.TH SCSI_TEMPERATURE "8" "May 2011" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +scsi_temperature \- fetch the temperature of a SCSI device +.SH SYNOPSIS +.B scsi_temperature +[\fI\-\-help\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR [\fIDEVICE\fR]* +.SH DESCRIPTION +.\" Add any additional description here +.PP +This bash shell script calls the sg_logs utility on each given +\fIDEVICE\fR in order to find the device's temperature. The Temperature +log page is checked first and if it is not available then the Informational +Exceptions log page is checked. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.SH EXIT STATUS +The exit status of this script is 0 when it is successful. Otherwise the +exit status is that of the last sg_logs utility called. See +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2011\-2013 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_logs (sg3_utils) diff --git a/doc/sg3_utils.8 b/doc/sg3_utils.8 new file mode 100644 index 0000000..eaf6489 --- /dev/null +++ b/doc/sg3_utils.8 @@ -0,0 +1,718 @@ +.TH SG3_UTILS "8" "September 2018" "sg3_utils\-1.44" SG3_UTILS +.SH NAME +sg3_utils \- a package of utilities for sending SCSI commands +.SH SYNOPSIS +.B sg_* +[\fI\-\-dry\-run\fR] [\fI\-\-enumerate\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-in=FN\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-raw\fR] +[\fI\-\-timeout=SECS\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +[\fIOTHER_OPTIONS\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +sg3_utils is a package of utilities that send SCSI commands to the given +\fIDEVICE\fR via a SCSI pass through interface provided by the host +operating system. +.PP +The names of all utilities start with "sg" and most start with "sg_" often +followed by the name, or a shortening of the name, of the SCSI command that +they send. For example the "sg_verify" utility sends the SCSI VERIFY +command. A mapping between SCSI commands and the sg3_utils utilities that +issue them is shown in the COVERAGE file. The sg_raw utility can be used to +send an arbitrary SCSI command (supplied on the command line) to the +given \fIDEVICE\fR. +.PP +sg_decode_sense can be used to decode SCSI sense data given on the command +line or in a file. sg_raw \-vvv will output the T10 name of a given SCSI +CDB which is most often 16 bytes or less in length. +.PP +SCSI draft standards can be found at http://www.t10.org . The standards +themselves can be purchased from ANSI and other standards organizations. +A good overview of various SCSI standards can be seen in +http://www.t10.org/scsi\-3.htm with the SCSI command sets in the upper part +of the diagram. The highest level (i.e. most abstract) document is the SCSI +Architecture Model (SAM) with SAM\-5 being the most recent standard (ANSI +INCITS 515\-2016) with the most recent draft being SAM\-6 revision 4 . SCSI +commands in common with all device types can be found in SCSI Primary +Commands (SPC) of which SPC\-4 is the most recent standard (ANSI INCITS +513-2015). The most recent SPC draft is SPC\-5 revision 19. Block device +specific commands (e.g. as used by disks) are in SBC, those for tape drives +in SSC, those for SCSI enclosures in SES and those for CD/DVD/BD drives in +MMC. +.PP +It is becoming more common to control ATA disks with the SCSI command set. +This involves the translation of SCSI commands to their corresponding ATA +equivalents (and that is an imperfect mapping in some cases). The relevant +standard is called SCSI to ATA Translation (SAT, SAT\-2 and SAT\-3) are +now standards at INCITS(ANSI) and ISO while SAT\-4 is at the draft stage. +The logic to perform the command translation is often called a SAT Layer or +SATL and may be within an operating system, in host bus adapter firmware or +in an external device (e.g. associated with a SAS expander). See +http://www.t10.org for more information. +.PP +There is some support for SCSI tape devices but not for their basic +operation. The reader is referred to the "mt" utility. +.PP +There are two generations of command line option usage. The newer +utilities (written since July 2004) use the getopt_long() function to parse +command line options. With that function, each option has two representations: +a short form (e.g. '\-v') and a longer form (e.g. '\-\-verbose'). If an +argument is required then it follows a space (optionally) in the short form +and a "=" in the longer form (e.g. in the sg_verify utility '\-l 2a6h' +and '\-\-lba=2a6h' are equivalent). Note that with getopt_long(), short form +options can be elided, for example: '\-all' is equivalent to '\-a \-l \-l'. +The \fIDEVICE\fR argument may appear after, between or prior to any options. +.PP +The older utilities, including as sg_inq, sg_logs, sg_modes, sg_opcode, +sg_rbuff, sg_readcap, sg_senddiag, sg_start and sg_turs had individual +command line processing code typically based on a single "\-" followed by one +or more characters. If an argument is needed then it follows a "=" ( +e.g. '\-p=1f' in sg_modes with its older interface). Various options can be +elided as long as it is not ambiguous (e.g. '\-vv' to increase the verbosity). +.PP +Over time the command line interface of these older utilities became messy +and overloaded with options. So in sg3_utils version 1.23 the command line +interface of these older utilities was altered to have both a cleaner +getopt_long() interface and their older interface for backward compatibility. +By default these older utilities use their getopt_long() based interface. +The getopt_long() is a GNU extension (i.e. not yet POSIX certified) but +more recent command line utilities tend to use it. That can be overridden +by defining the SG3_UTILS_OLD_OPTS environment variable or using '\-O' +or '\-\-old' as the first command line option. The man pages of the older +utilities documents the details. +.PP +Several sg3_utils utilities are based on the Unix dd command (e.g. sg_dd) +and permit copying data at the level of SCSI READ and WRITE commands. sg_dd +is tightly bound to Linux and hence is not ported to other OSes. A more +generic utility (than sg_dd) called ddpt in a package of the same name has +been ported to other OSes. +.SH ENVIRONMENT VARIABLES +The SG3_UTILS_OLD_OPTS environment variable is explained in the previous +section. It is only for backward compatibility of the command line options +for older utilities. +.PP +The SG3_UTILS_DSENSE environment variable may be set to a number. If that +number is non\-zero then descriptor sense is set in the SNTL (the small +SCSI to NVMe Translation Layer within the underlying library). +.PP +Several utilities have their own environment variable setting (e.g. +sg_persist has SG_PERSIST_IN_RDONLY). See individual utility man pages +for more information. +.SH LINUX DEVICE NAMING +Most disk block devices have names like /dev/sda, /dev/sdb, /dev/sdc, etc. +SCSI disks in Linux have always had names like that but in recent Linux +kernels it has become more common for many other disks (including SATA +disks and USB storage devices) to be named like that. Partitions within a +disk are specified by a number appended to the device name, starting at +1 (e.g. /dev/sda1 ). +.PP +Tape drives are named /dev/st or /dev/nst where starts +at zero. Additionally one letter from this list: "lma" may be appended to +the name. CD, DVD and BD readers (and writers) are named /dev/sr +where start at zero. There are less used SCSI device type names, +the dmesg and the lsscsi commands may help to find if any are attached to +a running system. +.PP +There is also a SCSI device driver which offers alternate generic access +to SCSI devices. It uses names of the form /dev/sg where starts +at zero. The "lsscsi \-g" command may be useful in finding these and which +generic name corresponds to a device type name (e.g. /dev/sg2 may +correspond to /dev/sda). In the lk 2.6 series a block SCSI generic +driver was introduced and its names are of the form +/dev/bsg/ where h, c, t and l are numbers. Again see the lsscsi +command to find the correspondence between that SCSI tuple (i.e. ) +and alternate device names. +.PP +Prior to the Linux kernel 2.6 series these utilities could only use +generic device names (e.g. /dev/sg1 ). In almost all cases in the Linux +kernel 2.6 series, any device name can be used by these utilities. +.PP +Very little has changed in Linux device naming in the Linux kernel 3 +and 4 series. +.SH WINDOWS DEVICE NAMING +Storage and related devices can have several device names in Windows. +Probably the most common in the volume name (e.g. "D:"). There are also +a "class" device names such as "PhysicalDrive", "CDROM" +and "TAPE". is an integer starting at 0 allocated in ascending +order as devices are discovered (and sometimes rediscovered). +.PP +Some storage devices have a SCSI lower level device name which starts +with a SCSI (pseudo) adapter name of the form "SCSI:". To this is added +sub\-addressing in the form of a "bus" number, a "target" identifier and +a LUN (Logical Unit Number). The "bus" number is also known as a "PathId". +These are assembled to form a device name of the +form: "SCSI:,,". The trailing "," may be omitted +in which case a LUN of zero is assumed. This lower level device name cannot +often be used directly since Windows blocks attempts to use it if a class +driver has "claimed" the device. There are SCSI device types (e.g. +Automation/Drive interface type) for which there is no class driver. At +least two transports ("bus types" in Windows jargon): USB and IEEE 1394 do +not have a "scsi" device names of this form. +.PP +In keeping with DOS file system conventions, the various device names +can be given in upper, lower or mixed case. Since "PhysicalDrive" is +tedious to write, a shortened form of "PD" is permitted by all +utilities in this package. +.PP +A single device (e.g. a disk) can have many device names. For +example: "PD0" can also be "C:", "D:" and "SCSI0:0,1,0". The two volume names +reflect that the disk has two partitions on it. Disk partitions that are +not recognized by Windows are not usually given a volume name. However +Vista does show a volume name for a disk which has no partitions recognized +by it and when selected invites the user to format it (which may be rather +unfriendly to other OSes). +.PP +These utilities assume a given device name is in the Win32 device namespace. +To make that explicit "\\\\.\\" can be prepended to the device names mentioned +in this section. Beware that backslash is an escape character in Unix like +shells and the C programming language. In a shell like Msys (from MinGW) +each backslash may need to be typed twice. +.PP +The sg_scan utility within this package lists out Windows device names in +a form that is suitable for other utilities in this package to use. +.SH FREEBSD DEVICE NAMING +SCSI disks have block names of the form /dev/da where is an +integer starting at zero. The "da" is replaced by "sa" for SCSI tape +drives and "cd" for SCSI CD/DVD/BD drives. Each SCSI device has a +corresponding pass\-through device name of the form /dev/pass +where is an integer starting at zero. The "camcontrol devlist" +command may be useful for finding out which SCSI device names are +available and the correspondence between class and pass\-through names. +.SH SOLARIS DEVICE NAMING +SCSI device names below the /dev directory have a form like: c5t4d3s2 +where the number following "c" is the controller (HBA) number, the number +following "t" is the target number (from the SCSI parallel interface days) +and the number following "d" is the LUN. Following the "s" is the slice +number which is related to a partition and by convention "s2" is the whole +disk. +.PP +OpenSolaris also has a c5t4d3p2 form where the number following the "p" is +the partition number apart from "p0" which is the whole disk. So a whole +disk may be referred to as either c5t4d3, c5t4d3s2 or c5t4d3p0 . +.PP +And these device names are duplicated in the /dev/dsk and /dev/rdsk +directories. The former is the block device name and the latter is +for "raw" (or char device) access which is what sg3_utils needs. So in +OpenSolaris something of the form 'sg_inq /dev/rdsk/c5t4d3p0' should work. +If it doesn't work then add a '\-vvv' option for more debug information. +Trying this form 'sg_inq /dev/dsk/c5t4d3p0' (note "rdsk" changed to "dsk") +will result in an "inappropriate ioctl for device" error. +.PP +The device names within the /dev directory are typically symbolic links to +much longer topological names in the /device directory. In Solaris cd/dvd/bd +drives seem to be treated as disks and so are found in the /dev/rdsk +directory. Tape drives appear in the /dev/rmt directory. +.PP +There is also a sgen (SCSI generic) driver which by default does not attach +to any device. See the /kernel/drv/sgen.conf file to control what is +attached. Any attached device will have a device name of the +form /dev/scsi/c5t4d3 . +.PP +Listing available SCSI devices in Solaris seems to be a challenge. "Use +the 'format' command" advice works but seems a very dangerous way to list +devices. [It does prompt again before doing any damage.] 'devfsadm \-Cv' +cleans out the clutter in the /dev/rdsk directory, only leaving what +is "live". The "cfgadm \-v" command looks promising. +.SH NVME SUPPORT +NVMe (or NVM Express) is a relatively new storage transport and command +set. The level of abstraction of the NVMe command set is somewhat lower +the SCSI command sets, closer to the level of abstraction of ATA (and SATA) +command sets. NVMe claims to be designed with flash and modern "solid +state" storage in mind, something unheard of when SCSI was originally +developed in the 1980s. +.PP +The SCSI command sets' advantage is the length of time they have been in +place and the existing tools (like these) to support it. Plus SCSI command +sets level of abstraction is both and advantage and disadvantage. Recently +the NVME\-MI (Management Interface) designers decide to use the SCSI +Enclosure Services (SES\-3) standard "as is" with the addition of two +tunnelling NVME\-MI commands: SES Send and SES Receive. This means after the +OS interface differences are taken into account, the sg_ses, sg_ses_microcode +and sg_senddiag utilities can be used on a NVMe device that supports a newer +version of NVME\-MI. +.PP +The NVME\-MI SES Send and SES Receive commands correspond to the SCSI +SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS commands respectively. +There are however a few other commands that need to be translated, the +most important of which is the SCSI INQUIRY command to the NVMe Identify +controller/namespace. Version 1.43 of these utilities contain a small +SNTL (SCSI to NVMe Translation Layer) to take care of these details. +.PP +As a side effect of this "juggling" if the sg_inq utility is used (without +the \-\-page= option) on a NVMe \fIDEVICE\fR then the actual NVMe +Identifier (controller and possibly namespace) responses are decoded and +output. However if 'sg_inq \-\-page=sinq ' is given for the +same \fIDEVICE\fR then parts of the NVMe Identify controller and namespace +response are translated to a SCSI standard INQUIRY response which is then +decoded and output. +.PP +Apart from the special case with the sg_inq, all other utilities in the +package assume they are talking to a SCSI device and decode any response +accordingly. One easy way for users to see the underlying device is a +NVMe device is the standard INQUIRY response Vendor Identification field +of "NVMe " (an 8 character long string with 4 spaces to the right). +.SH EXIT STATUS +To aid scripts that call these utilities, the exit status is set to indicate +success (0) or failure (1 or more). Note that some of the lower values +correspond to the SCSI sense key values. +.PP +The exit status values listed below can be given to the sg_decode_sense +utility (which is found in this package) as follows: +.br + sg_decode_sense \-\-err= +.br +and a short explanatory string will be output to stdout. +.PP +The exit status values are: +.TP +.B 0 +success. Also used for some utilities that wish to return a boolean value +for the "true" case (and that no error has occurred). The false case is +conveyed by exit status 36. +.TP +.B 1 +syntax error. Either illegal command line options, options with bad +arguments or a combination of options that is not permitted. +.TP +.B 2 +the \fIDEVICE\fR reports that it is not ready for the operation requested. +The \fIDEVICE\fR may be in the process of becoming ready (e.g. spinning up +but not at speed) so the utility may work after a wait. In Linux the +\fIDEVICE\fR may be temporarily blocked while error recovery is taking place. +.TP +.B 3 +the \fIDEVICE\fR reports a medium or hardware error (or a blank check). For +example an attempt to read a corrupted block on a disk will yield this value. +.TP +.B 5 +the \fIDEVICE\fR reports an "illegal request" with an additional sense code +other than "invalid command operation code". This is often a supported +command with a field set requesting an unsupported capability. For commands +that require a "service action" field this value can indicate that the +command with that service action value is not supported. +.TP +.B 6 +the \fIDEVICE\fR reports a "unit attention" condition. This usually indicates +that something unrelated to the requested command has occurred (e.g. a device +reset) potentially before the current SCSI command was sent. The requested +command has not been executed by the device. Note that unit attention +conditions are usually only reported once by a device. +.TP +.B 7 +the \fIDEVICE\fR reports a "data protect" sense key. This implies some +mechanism has blocked writes (or possibly all access to the media). +.TP +.B 9 +the \fIDEVICE\fR reports an illegal request with an additional sense code +of "invalid command operation code" which means that it doesn't support the +requested command. +.TP +.B 10 +the \fIDEVICE\fR reports a "copy aborted". This implies another command or +device problem has stopped a copy operation. The EXTENDED COPY family of +commands (including WRITE USING TOKEN) may return this sense key. +.TP +.B 11 +the \fIDEVICE\fR reports an aborted command. In some cases aborted +commands can be retried immediately (e.g. if the transport aborted +the command due to congestion). +.TP +.B 14 +the \fIDEVICE\fR reports a miscompare sense key. VERIFY and COMPARE AND +WRITE commands may report this. +.TP +.B 15 +the utility is unable to open, close or use the given \fIDEVICE\fR or some +other file. The given file name could be incorrect or there may be +permission problems. Adding the '\-v' option may give more information. +.TP +.B 17 +a SCSI "Illegal request" sense code received with a flag indicating the +Info field is valid. This is often a LBA but its meaning is command specific. +.TP +.B 18 +the \fIDEVICE\fR reports a medium or hardware error (or a blank check) +with a flag indicating the Info field is valid. This is often a LBA (of +the first encountered error) but its meaning is command specific. +.TP +.B 20 +the \fIDEVICE\fR reports it has a check condition but "no sense" +and non\-zero information in its additional sense codes. Some polling +commands (e.g. REQUEST SENSE) can receive this response. There may +be useful information in the sense data such as a progress indication. +.TP +.B 21 +the \fIDEVICE\fR reports a "recovered error". The requested command +was successful. Most likely a utility will report a recovered error +to stderr and continue, probably leaving the utility with an exit +status of 0 . +.TP +.B 22 +the \fIDEVICE\fR reports that the current command or its parameters imply +a logical block address (LBA) that is out of range. This happens surprisingly +often when trying to access the last block on a storage device; either a +classic "off by one" logic error or a misreading of the response from READ +CAPACITY(10 or 16) in which the address of the last block rather than the +number of blocks on the \fIDEVICE\fR is returned. Since LBAs are origin zero +they range from 0 to n\-1 where n is the number of blocks on the \fIDEVICE\fR, +so the LBA of the last block is one less than the total number of blocks. +.TP +.B 24 +the \fIDEVICE\fR reports a SCSI status of "reservation conflict". This +means access to the \fIDEVICE\fR with the current command has been blocked +because another machine (HBA or SCSI "initiator") holds a reservation on +this \fIDEVICE\fR. On modern SCSI systems this is related to the use of +the PERSISTENT RESERVATION family of commands. +.TP +.B 25 +the \fIDEVICE\fR reports a SCSI status of "condition met". Currently only +the PRE\-FETCH command (see SBC\-4) yields this status. +.TP +.B 26 +the \fIDEVICE\fR reports a SCSI status of "busy". SAM\-6 defines this status +as the logical unit is temporarily unable to process a command. It is +recommended to re\-issue the command. +.TP +.B 27 +the \fIDEVICE\fR reports a SCSI status of "task set full". +.TP +.B 28 +the \fIDEVICE\fR reports a SCSI status of "ACA active". ACA is "auto +contingent allegiance" and is seldom used. +.TP +.B 29 +the \fIDEVICE\fR reports a SCSI status of "task aborted". SAM\-5 says: +"This status shall be returned if a command is aborted by a command or task +management function on another I_T nexus and the Control mode page TAS bit +is set to one". +.TP +.B 31 +error involving two or more command line options. They may be contradicting, +select an unsupported mode, or a required option (given the context) is +missing. +.TP +.B 32 +there is a logic error in the utility. It corresponds to code comments +like "shouldn't/can't get here". Perhaps the author should be informed. +.TP +.B 33 +the command sent to \fIDEVICE\fR has timed out. +.TP +.B 36 +no error has occurred plus the utility wants to convey a boolean value +of false. The corresponding true value is conveyed by a 0 exit status. +.TP +.B 40 +the command sent to \fIDEVICE\fR has received an "aborted command" sense +key with an additional sense code of 0x10. This group is related to +problems with protection information (PI or DIF). For example this error +may occur when reading a block on a drive that has never been written (or +is unmapped) if that drive was formatted with type 1, 2 or 3 protection. +.TP +.B 41 +the command sent to \fIDEVICE\fR has received an "aborted command" sense +key with an additional sense code of 0x10 (as with error code) plus a flag +indicating the Info field is valid. +.TP +.B 48 +this is an internal message indicating a NVMe status field (SF) is other +than zero after a command has been executed (i.e. something went wrong). +Work in this area is currently experimental. +.TP +.B 49 +low level driver reports a response's residual count (i.e. number of bytes +actually received by HBA is 'requested_bytes \- residual_count') that is +.TP +.B 50 +OS system calls that fail often return a small integer number to help. In +Unix these are called "errno" values where 0 implies no error. These error +codes set aside 51 to 96 for mapping these errno values but that may not be +sufficient. Higher errno values that cannot be mapped are all mapped to +this value (i.e. 50). +.br +Note that an errno value of 0 is mapped to error code 0. +.TP +.B 50 + +OS system calls that fail often return a small integer number to help +indicate what the error is. For example in Unix the inability of a system +call to allocate memory returns (in 'errno') ENOMEM which often is +associated with the integer 12. So 62 (i.e. '50 + 12') may be returned +by a utility in this case. It is also possible that a utility in this +package reports 50+ENOMEM when it can't allocate memory, not necessarily +from an OS system call. In recent versions of Linux the file showing the +mapping between symbolic constants (e.g. ENOMEM) and the corresponding +integer is in the kernel source code file: +include/uapi/asm\-generic/errno\-base.h +.br +Note that errno values that are greater than or equal to 47 cannot fit in +range provided. Instead they are all mapped to 50 as discussed in the +previous entry. +.TP +.B 97 +a SCSI command response failed sanity checks. +.TP +.B 98 +the \fIDEVICE\fR reports it has a check condition but the error +doesn't fit into any of the above categories. +.TP +.B 99 +any errors that can't be categorized into values 1 to 98 may yield +this value. This includes transport and operating system errors +after the command has been sent to the device. +.TP +.B 100\-125 +these error codes are used by the ddpt utility which uses the sg3_utils +library. They are mainly specialized error codes associated with offloaded +copies. +.TP +.B 126 +the utility was found but could not be executed. That might occur if the +executable does not have execute permissions. +.TP +.B 127 +This is the exit status for utility not found. That might occur when a +script calls a utility in this package but the PATH environment variable +has not been properly set up, so the script cannot find the executable. +.TP +.B 128 + +If a signal kills a utility then the exit status is 128 plus the signal +number. For example if a segmentation fault occurs then a utility is +typically killed by SIGSEGV which according to 'man 7 signal' has an +associated signal number of 11; so the exit status will be 139 . +.TP +.B 255 +the utility tried to yield an exit status of 255 or larger. That should +not happen; given here for completeness. +.PP +Most of the error conditions reported above will be repeatable (an example +of one that is not is "unit attention") so the utility can be run again with +the '\-v' option (or several) to obtain more information. +.SH COMMON OPTIONS +Arguments to long options are mandatory for short options as well. In the +short form an argument to an option uses zero or more spaces as a +separator (i.e. the short form does not use "=" as a separator). +.PP +If an option takes a numeric argument then that argument is assumed to +be decimal unless otherwise indicated (e.g. with a leading "0x", a +trailing "h" or as noted in the usage message). +.PP +Some options are used uniformly in most of the utilities in this +package. Those options are listed below. Note that there are some +exceptions. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +utilities that can cause lots of user data to be lost or overwritten +sometimes have a \fI\-\-dry\-run\fR option. Device modifying actions are +typically bypassed (or skipped) to implement a policy of "do no harm". +This allows complex command line invocations to be tested before the +action required (e.g. format a disk) is performed. The \fI\-\-dry\-run\fR +option has become a common feature of many command line utilities (e.g. +the Unix 'patch' command), not just those from this package. +.br +Note that most hyphenated option names in this package also can be given +with an underscore rather than a hyphen (e.g. \fI\-\-dry_run\fR). +.TP +\fB\-e\fR, \fB\-\-enumerate\fR +some utilities (e.g. sg_ses and sg_vpd) store a lot of information in +internal tables. This option will output that information in some readable +form (e.g. sorted by an acronym or by page number) then exit. Note that +with this option \fIDEVICE\fR is ignored (as are most other options) and no +SCSI IO takes place, so the invoker does not need any elevated permissions. +.TP +\fB\-h\fR, \fB\-?\fR, \fB\-\-help\fR +output the usage message then exit. In a few older utilities the '\-h' +option requests hexadecimal output. In these cases the '\-?' option will +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +for SCSI commands that yield a non\-trivial response, print out that +response in ASCII hexadecimal. To produce hexadecimal that can be parsed +by other utilities (e.g. without a relative address to the left and without +trailing ASCII) use this option three or four times. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIFN\fR +many SCSI commands fetch a significant amount of data (returned in the +data\-in buffer) which several of these utilities decode (e.g. sg_vpd and +sg_logs). To separate the two steps of fetching the data from a SCSI device +and then decoding it, this option has been added. The first step (fetching +the data) can be done using the \fI\-\-hex\fR or \fI\-\-raw\fR option and +redirecting the command line output to a file (often done with ">" in Unix +based operating systems). The difference between \fI\-\-hex\fR and +\fI\-\-raw\fR is that the former produces output in ASCII hexadecimal +while \fI\-\-raw\fR produces its output in "raw" binary. +.br +The second step (i.e. decoding the SCSI response data now held in a file) +can be done using this \fI\-\-in=FN\fR option where the file name is +\fIFN\fR. If "\-" is used for \fIFN\fR then stdin is assumed, again this +allows for command line redirection (or piping). That file (or stdin) +is assumed to contain ASCII hexadecimal unless the \fI\-\-raw\fR option is +also given in which case it is assumed to be binary. Notice that the meaning +of the \fI\-\-raw\fR option is "flipped" when used with \fI\-\-in=FN\fR to +act on the input, typically it acts on the output data. +.br +Since the structure of the data returned by SCSI commands varies +considerably then the usage information or the manpage of the utility being +used should be checked. In some cases \fI\-\-hex\fR may need to be used +multiple times (and is more conveniently given as '\-HH' or '\-HHH). In +other cases the name of this option is \fI\-\-inhex=FN\fR. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +several important SCSI commands (e.g. INQUIRY and MODE SENSE) have response +lengths that vary depending on many factors, only some of which these +utilities take into account. The maximum response length is typically +specified in the 'allocation length' field of the cdb. In the absence of +this option, several utilities use a default allocation length (sometimes +recommended in the SCSI draft standards) or a "double fetch" strategy. +See sg_logs(8) for its description of a "double fetch" strategy. These +techniques are imperfect and in the presence of faulty SCSI targets can +cause problems (e.g. some USB mass storage devices freeze if they receive +an INQUIRY allocation length other than 36). Also use of this option +disables any "double fetch" strategy that may have otherwise been used. +.TP +\fB\-r\fR, \fB\-\-raw\fR +for SCSI commands that yield a non\-trivial response, output that response +in binary to stdout. If any error messages or warning are produced they are +usually sent to stderr so as to not interfere with the output from this +option. +.br +Some utilities that consume data to send to the \fIDEVICE\fR along with the +SCSI command, use this option. Alternatively the \fI\-\-in=FN\fR option causes +\fIDEVICE\fR to be ignored and the response data (to be decoded) fetched +from a file named \fIFN\fR. In these cases this option may indicate that +binary data can be read from stdin or from a nominated file (e.g. \fIFN\fR). +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR +utilities that issue potentially long\-running SCSI commands often have a +\fI\-\-timeout=SECS\fR option. This typically instructs the operating system +to abort the SCSI command in question once the timeout expires. Aborting +SCSI commands is typically a messy business and in the case of format like +commands may leave the device in a "format corrupt" state requiring another +long\-running re\-initialization command to be sent. The argument, \fISECS\fR, +is usually in seconds and the short form of the option may be something +other than \fI\-t\fR since the timeout option was typically added later as +storage devices grew in size and initialization commands took longer. Since +many utilities had relatively long internal command timeouts before this +option was introduced, the actual command timeout given to the operating +systems is the higher of the internal timeout and \fISECS\fR. +.br +Many long running SCSI commands have an IMMED bit which causes the command +to finish relatively quickly but the initialization process to continue. In +such cases the REQUEST SENSE command can be used to monitor progress with +its progress indication field (see the sg_requests and sg_turs utilities). +Utilities that send such SCSI command either have an \fI\-\-immed\fR option +or a \fI\-\-wait\fR option which is the logical inverse of the "immediate" +action. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). Can be used multiple +times to further increase verbosity. The additional output caused by this +option is almost always sent to stderr. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. Each utility has its own version +number and date of last code change. +.SH NUMERIC ARGUMENTS +Many utilities have command line options that take numeric arguments. These +numeric arguments can be large values (e.g. a logical block address (LBA) on +a disk) and can be inconvenient to enter in the default decimal +representation. So various other representations are permitted. +.PP +Multiplicative suffixes are accepted. They are one, two or three letter +strings appended directly after the number to which they apply: +.PP + c C *1 +.br + w W *2 +.br + b B *512 +.br + k K KiB *1024 +.br + KB kB *1000 +.br + m M MiB *1048576 +.br + MB mB *1000000 +.br + g G GiB *(2^30) +.br + GB gB *(10^9) +.br + t T TiB *(2^40) +.br + TB *(10^12) +.br + p P PiB *(2^50) +.br + PB *(10^15) +.PP +An example is "2k" for 2048. The large tera and peta suffixes are only +available for numeric arguments that might require 64 bits to represent +internally. +.PP +A suffix of the form "x" multiplies the leading number by . An +example is "2x33" for "66". The leading number cannot be "0" (zero) as +that would be interpreted as a hexadecimal number (see below). +.PP +These multiplicative suffixes are compatible with GNU's dd command (since +2002) which claims compliance with SI and with IEC 60027\-2. +.PP +Alternatively numerical arguments can be given in hexadecimal. There are +two syntaxes. The number can be preceded by either "0x" or "0X" as found +in the C programming language. The second hexadecimal representation is a +trailing "h" or "H" as found in (storage) standards. When hex numbers are +given, multipliers cannot be used. For example the decimal value "256" can +be given as "0x100" or "100h". +.SH MICROCODE AND FIRMWARE +There are two standardized methods for downloading microcode (i.e. device +firmware) to a SCSI device. The more general way is with the SCSI WRITE +BUFFER command, see the sg_write_buffer utility. SCSI enclosures have +their own method based on the Download microcode control/status diagnostic +page, see the sg_ses_microcode utility. +.SH SCRIPTS, EXAMPLES and UTILS +There are several bash shell scripts in the 'scripts' subdirectory that +invoke compiled utilities (e.g. sg_readcap). Several of the scripts start +with 'scsi_' rather than 'sg_'. One purpose of these scripts is to call the +same utility (e.g. sg_readcap) on multiple devices. Most of the basic +compiled utilities only allow one device as an argument. Some distributions +install these scripts in a more visible directory (e.g. /usr/bin). Some of +these scripts have man page entries. See the README file in the 'scripts' +subdirectory. +.PP +There is some example C code plus examples of complex invocations in +the 'examples' subdirectory. There is also a README file. The example C +may be a simpler example of how to use a SCSI pass\-through in Linux +than the main utilities (found in the 'src' subdirectory). This is due +to the fewer abstraction layers (e.g. they don't worry the MinGW in +Windows may open a file in text rather than binary mode). +.PP +Some utilities that the author has found useful have been placed in +the 'utils' subdirectory. +.SH WEB SITE +There is a web page discussing this package at +http://sg.danny.cz/sg/sg3_utils.html . The device naming used by this +package on various operating systems is discussed at: +http://sg.danny.cz/sg/device_name.html . There is a git code mirror at +https://github.com/hreinecke/sg3_utils . The principle code repository +uses subversion and is on the author's equipment. The author keeps track +of this via the subversion revision number which is an ascending integer +(currently at 774 for this package). The github mirror gets updated +periodically from the author's repository. Depending on the time of +update, the above Downloads section at sg.danny.cz may be more up to +date than the github mirror. +.SH AUTHORS +Written by Douglas Gilbert. Some utilities have been contributed, see the +CREDITS file and individual source files (in the 'src' directory). +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 1999\-2018 Douglas Gilbert +.br +Some utilities are distributed under a GPL version 2 license while +others, usually more recent ones, are under a FreeBSD license. The files +that are common to almost all utilities and thus contain the most reusable +code, namely sg_lib.[hc], sg_cmds_basic.[hc] and sg_cmds_extra.[hc] are +under a FreeBSD license. There is NO warranty; not even for MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sdparm(sdparm), ddpt(ddpt), lsscsi(lsscsi), dmesg(1), mt(1) diff --git a/doc/sg_bg_ctl.8 b/doc/sg_bg_ctl.8 new file mode 100644 index 0000000..0aca9ef --- /dev/null +++ b/doc/sg_bg_ctl.8 @@ -0,0 +1,72 @@ +.TH SG_BG_CTL "8" "May 2016" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_bg_ctl \- send SCSI BACKGROUND CONTROL command +.SH SYNOPSIS +.B sg_bg_ctl +[\fI\-\-ctl=CTL\fR] [\fI\-\-help\fR] [\fI\-\-time=TN\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI BACKGROUND CONTROL command to the \fIDEVICE\fR. This command +was first found in the SBC\-4 draft standard revision 8 (sbc4r08.pdf). It can +be used to start and stop 'advanced background operations' on the +\fIDEVICE\fR. Only resource or thin provisioned devices (logical units which +are typically (solid state) disks) support this command. Those advanced +background operations often include garbage collection type operations which +may degrade the disk's performance while they are being performed. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-c\fR, \fB\-\-ctl\fR=\fICTL\fR +\fICTL\fR is the value placed in the BO_CTL field of the BACKGROUND CONTROL +command (cdb). It is a two bit field so has 4 variants: 0 does not change +the host initiated advanced background operations; 1 starts these operations; +2 stops these operations and 3 is reserved. The default value is 0. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-t\fR, \fB\-\-time\fR=\fITN\fR +\fITN\fR is a maximum time (with a unit of 100 ms or 1/10 second) that +advanced background operations can occur. This value is ignored if the +\fICTL\fR argument is other than 1. The default value is 0 which means there +is no maximum time limit. Only values 0 to 255 (which is 25.5 seconds) can +be given. This value is place in the BO_TIME field of the BACKGROUND CONTROL +command. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +According to T10, support for 'background control operations' is indicated by +the BOCS bit being set in the Block device characteristics VPD page [0xb1]. +The setting of the BOCS bit can be checked with the sg_vpd and sdparm +utilities (and it is read only). There is a Background operations control +mode page [0xa, 0x6] with a BO_MODE field for modifying the action of this +operation. The BO_MODE field can be accessed and possibly modified with the +sdparm utility. The BO_STATUS field can be found in the Background operation +log page [0x15, 0x2] and that can be viewed with the sg_logs utility. +.PP +The current draft describing this area is SBC\-4 revision 10 (sbc4r10.pdf) +in clause 4.33 . That contains the following example of a background +operation: "Advanced background operation may include NAND block erase +operations, media read operations, and media write operations (e.g., +garbage collection), which may impact response time for normal read requests +or write requests from the application client." +.SH EXIT STATUS +The exit status of sg_bg_ctl is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2016 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd,sg_logs(sg3_utils); sdparm(sdparm) diff --git a/doc/sg_compare_and_write.8 b/doc/sg_compare_and_write.8 new file mode 100644 index 0000000..54a6f09 --- /dev/null +++ b/doc/sg_compare_and_write.8 @@ -0,0 +1,176 @@ +.TH "COMPARE AND WRITE" "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_compare_and_write \- send the SCSI COMPARE AND WRITE command +.SH SYNOPSIS +.B sg_compare_and_write +[\fI\-\-dpo\fR] [\fI\-\-fua\fR] [\fI\-\-fua_nv\fR] [\fI\-\-grpnum=GN\fR] +[\fI\-\-help\fR] \fI\-\-in=IF\fR [\fI\-\-inw=WF\fR] \fI\-\-lba=LBA\fR +[\fI\-\-num=NUM\fR] [\fI\-\-quiet\fR] [\fI\-\-timeout=TO\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-wrprotect=WP\fR] +[\fI\-\-xferlen=LEN\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +Send the SCSI COMPARE AND WRITE command to \fIDEVICE\fR. This utility +reads a compare buffer and a write buffer from either one or two files. If +the \fI\-\-inw=WF\fR option is not given then the concatenated compare +and write buffers are read from the file indicated by the \fI\-\-in=IF\fR +option. If the \fI\-\-inw=WF\fR option is given then the compare buffer +is read from the file indicated by the \fI\-\-in=IF\fR while the write +buffer is read from the file indicated by the \fI\-\-inw=WF\fR. +.PP +Those buffers are expected to each contain \fINUM\fR blocks of data. The +compare starts at logical block address \fILBA\fR on the \fIDEVICE\fR +and if the comparison fails (i.e. the provided compare buffer does not +equal at \fILBA\fR on the \fIDEVICE\fR) then the COMPARE AND WRITE command +finishes with a sense key of MISCOMPARE. In this case this utility will +completes and set an exit status of 14 (which happens to be the sense key +value of MISCOMPARE). +.PP +If the comparison succeeds then the provided write buffer is written to +starting at \fILBA\fR for \fINUM\fR blocks on the \fIDEVICE\fR. +.PP +The actual number of bytes transferred in the data\-out buffer of the +COMPARE AND WRITE command may need to be given by the user with the +\fI\-\-xferlen=LEN\fR option. \fILEN\fR defaults to (2 * \fINUM\fR * 512) +which is 1024 for the default \fINUM\fR of 1. If the block size is +other than 512 then the user will need to use \fI\-\-xferlen=LEN\fR option. +If protection information is given (indicated by a value of \fIWP\fR +other than 0 (the default)) then for a \fINUM\fR of 1 \fILEN\fR should +be 1040 . Note that the SCSI READ CAPACITY command is not checked by +this utility (e.g. to find the block size). +.PP +The definition of the SCSI COMPARE AND WRITE command requires that the +\fIDEVICE\fR implement the compare and optional write as an uninterrupted +series of actions. Depending on some other \fIDEVICE\fR settings a +verify operation may occur prior to the compare. +.PP +When a mismatch occurs between the compare buffer and the blocks starting +at \fILBA\fR read from the \fIDEVICE\fR the sense buffer containing the +MISCOMPARE sense key causes several messages to be sent to stderr (including +the offset of the first byte mismatch). To suppress these messages use the +\fI\-\-quiet\fR option. With or without the \fI\-\-quiet\fR option the exit +status will be set to 14. +.PP +This command is defined in SBC\-3 whose most recent revision is 36. SBC\-3 +and other SCSI documents can be found at http://www.t10.org . +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long option name. +.TP +\fB\-d\fR, \fB\-\-dpo\fR +Set the DPO bit in the COMPARE AND WRITE CDB +.TP +\fB\-f\fR, \fB\-\-fua\fR +Set the FUA bit in the COMPARE AND WRITE CDB +.TP +\fB\-F\fR, \fB\-\-fua_nv\fR +Set the FUA_NV bit in the COMPARE AND WRITE CDB. This bit was removed in +SBC\-3 revision 35d and its position marked as "reserved". +.TP +\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR +where \fIGN\fR is the value to be placed in the group number field in the +COMPARE AND WRITE CDB. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR +read data (binary) from file named \fIIF\fR. This will either be the combined +compare and write buffers (when the \fI\-\-inw=WF\fR option is not given) or +just the compare buffer (when the \fI\-\-inw=WF\fR option is given). If +\fIIF\fR is '\-' then stdin (e.g. a pipe) is read. +.TP +\fB\-C\fR, \fB\-\-inc\fR=\fIIF\fR +The same as the \fB\-\-in\fR option. +.TP +\fB\-D\fR, \fB\-\-inw\fR=\fIWF\fR +read data (binary) from file named \fIWF\fR. This will the write buffer +that will become the second half of the data\-out buffer sent to the +\fIDEVICE\fR associated with the COMPARE AND WRITE command. Note that +when this option is given then the \fI\-\-in=IF\fR is expected to hold +the associated compare buffer. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the logical block address to start the COMPARE AND WRITE +command. Assumed to be in decimal unless prefixed with '0x' or has a +trailing 'h'. +.TP +\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR +where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to read +and compare with the verify instance. And given a match, the \fINUM\fR of +blocks to write starting \fILBA\fR. The default value for \fINUM\fR is 1. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +suppress the sense buffer messages associated with a MISCOMPARE sense key +that would otherwise be sent to stderr. Still set the exit status to 14 +which is the sense key value indicating a MISCOMPARE. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR +where \fITO\fR is the command timeout value in seconds. The default value is +60 seconds. If \fINUM\fR is large (or zero) a WRITE SAME command may require +considerably more time than 60 seconds to complete. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). +.TP +\fB\-V\fR, \fB\-\-version\fR +output version string then exit. +.TP +\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWP\fR +set the WRPROTECT field in the cdb to \fIWP\fR. The default value is 0 which +implies no protection information is sent (along with the user data) by this +utility. +.TP +\fB\-x\fR, \fB\-\-xferlen\fR=\fILEN\fR +where \fILEN\fR is the data out buffer length in byte. It defaults to (2 * +\fINUM\fR * 512) bytes. If the \fIDEVICE\fR block size is other than 512 +bytes or \fIWP\fR is non\-zero (implying additional protection information) +then this default will be incorrect; the use must supply the correct value +for \fILEN\fR +.SH NOTES +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.SH EXIT STATUS +The exit status of sg_compare_and_write is 0 when it is successful. If the +compare step fails then the exit status is 14. For other exit status values +see the EXIT STATUS section in the sg3_utils(8) man page. +.PP +Earlier versions of this utility set an exit status of 98 when there was a +MISCOMPARE. +.SH AUTHORS +Written by Shahar Salzman. Maintained by Douglas Gilbert. Additions by +Eric Seppanen. +.SH "REPORTING BUGS" +Report bugs to shahar.salzman@kaminario.com or dgilbert@interlog.com +.SH COPYRIGHT +Copyright \(co 2012\-2018 Kaminario Technologies LTD +.br +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +.br +* Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +.br +* Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. +.br +* Neither the name of the nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +.br +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Kaminario Technologies LTD BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +.SH "SEE ALSO" +.B sg_xcopy, sg_receive_copy_results(sg3_utils) diff --git a/doc/sg_copy_results.8 b/doc/sg_copy_results.8 new file mode 100644 index 0000000..9e67e52 --- /dev/null +++ b/doc/sg_copy_results.8 @@ -0,0 +1,126 @@ +.TH SG_COPY_RESULTS "8" "September 2014" "sg3_utils\-1.40" SG3_UTILS +.SH NAME +sg_copy_results \- send SCSI RECEIVE COPY RESULTS command (XCOPY related) +.SH SYNOPSIS +.B sg_copy_results +[\fI\-\-failed\fR|\fI\-\-params\fR|\fI\-\-receive\fR|\fI\-\-status\fR] +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-list_id=ID\fR] [\fI\-\-readonly\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-xfer_len=BTL\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility is designed to query the status of the SCSI Extended +Copy (XCOPY) facility (see SPC\-3 revision 23 sections 6.3 and 6.17), present +in some modern storage arrays. This utility sends a SCSI RECEIVE COPY +RESULTS command to the given \fIDEVICE\fR and displays the response. +.PP +During the draft stages of SPC\-4 the T10 committee has expanded the XCOPY +command so that it now has two variants: "LID1" (for a List Identifier +length of 1 byte) and "LID4" (for a List Identifier length of 4 bytes). +This utility supports the older, LID1 variant which is also found in SPC\-3 +and earlier. While the LID1 variant in SPC\-4 is command level (binary) +compatible with XCOPY as defined in SPC\-3, some of the command naming has +changed. This utility uses the older, SPC\-3 XCOPY names. +.PP +The command has four distinct modes of operation, distinguished by +the service action field: +.TP +\fBCOPY STATUS [SPC\-4: RECEIVE COPY STATUS(LID1)]\fR +Displays the current status of the EXTENDED COPY command identified by +the list id field. +.TP +\fBRECEIVE DATA [SPC\-4: RECEIVE COPY DATA(LID1)]\fR +Return the held data read by the EXTENDED COPY command identified by +the list id field. This option is only meaningful if the respective +segment descriptor are supported. +.TP +\fBOPERATING PARAMETERS [SPC\-4: RECEIVE COPY OPERATING PARAMETERS]\fR +Return copy manager operating parameters. This option is also useful +to determine if the SCSI Extended Copy facility is supported. +.TP +\fBFAILED SEGMENT DETAILS [SPC\-4: RECEIVE COPY FAILURE DETAILS(LID1)]\fR +Return copy target device sense data and other information about any +failed segments. + +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-f\fR, \fB\-\-failed\fR +sets the service action field to FAILED SEGMENT DETAILS [4]. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +prints out the response buffer in hex. +.TP +\fB\-l\fR, \fB\-\-list_id\fR=\fIID\fR +sets the list identifier field to \fIID\fR (default: 0). +.TP +\fB\-p\fR, \fB\-\-params\fR +sets the service action field to OPERATING PARAMETERS [3]. +This is the default. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-r\fR, \fB\-\-receive\fR +sets the service action field to RECEIVE DATA [1]. +.TP +\fB\-s\fR, \fB\-\-status\fR +sets the service action field to COPY STATUS [0]. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-x\fR, \fB\-\-xfer_len\fR=\fIBTL\fR +sets the allocation length field to \fIBTL\fR. It is the byte transfer +length and is the maximum (byte) size of the response. \fIBTL\fR must be +less than 10000 and defaults to 520. +.SH NOTES +Decoding of \fIRECEIVE DATA\fR service action is not implemented. +.PP +In a similar way the functionality of sg_xcopy has been ported to the +more general ddpt utility (and package), the functionality of this utility +has been ported to the ddptctl utility. +.SH EXAMPLES +Query the operating parameters for a device: +.PP +# sg_copy_results \-p /dev/sdo +.br +Receive copy results (report operating parameters): + Supports no list identifier: no + Maximum target descriptor count: 2 + Maximum segment descriptor count: 1 + Maximum descriptor list length: 92 bytes + Maximum segment length: 33553920 bytes + Inline data not supported + Held data limit: 0 bytes + Maximum stream device transfer size: 0 bytes + Total concurrent copies: 0 + Maximum concurrent copies: 255 + Data segment granularity: 512 bytes + Inline data granularity: 1 bytes + Held data granularity: 1 bytes + Implemented descriptor list: + Segment descriptor 0x02: Copy from block device to block device + Target descriptor 0xe4: Identification descriptor + +.SH EXIT STATUS +The exit status of sg_copy_results is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2012\-2014 Hannes Reinecke and Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_xcopy(sg3_utils), ddpt,ddptctl(ddpt) diff --git a/doc/sg_dd.8 b/doc/sg_dd.8 new file mode 100644 index 0000000..881d11e --- /dev/null +++ b/doc/sg_dd.8 @@ -0,0 +1,522 @@ +.TH SG_DD "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_dd \- copy data to and from files and devices, especially SCSI +devices +.SH SYNOPSIS +.B sg_dd +[\fIbs=BS\fR] [\fIconv=CONV\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR] +[\fIif=IFILE\fR] [\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR] +[\fIoflag=FLAGS\fR] [\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] +.PP +[\fIblk_sgio=\fR{0|1}] [\fIbpt=BPT\fR] [\fIcdbsz=\fR{6|10|12|16}] +[\fIcoe=\fR{0|1|2|3}] [\fIcoe_limit=CL\fR] [\fIdio=\fR{0|1}] +[\fIodir=\fR{0|1}] [\fIof2=OFILE2\fR] [\fIretries=RETR\fR] [\fIsync=\fR{0|1}] +[\fItime=\fR{0|1}] [\fIverbose=VERB\fR] [\fI\-\-dry\-run\fR] [\fI\-V\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Copy data to and from any files. Specialized for "files" that are Linux SCSI +generic (sg) devices, raw devices or other devices that support the SG_IO +ioctl (which are only found in the lk 2.6 series). Similar syntax and +semantics to +.B dd(1) +command. +.PP +The first group in the synopsis above are "standard" Unix +.B dd(1) +operands. The second group are extra options added by this utility. +Both groups are defined below. +.PP +This utility is only supported on Linux whereas most other utilities in the +sg3_utils package have been ported to other operating systems. A utility +called "ddpt" has similar syntax and functionality to sg_dd. ddpt drops some +Linux specific features while adding some other generic features. This allows +ddpt to be ported to other operating systems. +.SH OPTIONS +.TP +\fBblk_sgio\fR={0|1} +when set to 0, block devices (e.g. /dev/sda) are treated like normal +files (i.e. +.B read(2) +and +.B write(2) +are used for IO). When set to 1, block devices are assumed to accept the +SG_IO ioctl and SCSI commands are issued for IO. This is only supported +for 2.6 series kernels. Note that ATAPI devices (e.g. cd/dvd players) use +the SCSI command set but ATA disks do not (unless there is a protocol +conversion as often occurs in the USB mass storage class). If the input +or output device is a block device partition (e.g. /dev/sda3) then setting +this option causes the partition information to be ignored (since access +is directly to the underlying device). Default is 0. See the 'sgio' flag. +.TP +\fBbpt\fR=\fIBPT\fR +each IO transaction will be made using \fIBPT\fR blocks (or less if near +the end of the copy). Default is 128 for logical block sizes less that 2048 +bytes, otherwise the default is 32. So for bs=512 the reads and writes +will each convey 64 KiB of data by default (less if near the end of the +transfer or memory restrictions). When cd/dvd drives are accessed, the +logical block size is typically 2048 bytes and bpt defaults to 32 which +again implies 64 KiB transfers. The block layer when the blk_sgio=1 option +is used has relatively low upper limits for transfer sizes (compared +to sg device nodes, see /sys/block//queue/max_sectors_kb ). +.TP +\fBbs\fR=\fIBS\fR +where \fIBS\fR +.B must +be the logical block size of the physical device (if either the input or +output files are accessed via SCSI commands). Note that this differs from +.B dd(1) +which permits \fIBS\fR to be an integral multiple. Default is 512 which +is usually correct for disks but incorrect for cdroms (which normally +have 2048 byte blocks). For this utility the maximum size of each individual +IO operation is \fIBS\fR * \fIBPT\fR bytes. +.TP +\fBcdbsz\fR={6|10|12|16} +size of SCSI READ and/or WRITE commands issued on sg device +names (or block devices when 'iflag=sgio' and/or 'oflag=sgio' is given). +Default is 10 byte SCSI command blocks (unless calculations indicate +that a 4 byte block number may be exceeded or \fIBPT\fR is greater than +16 bits (65535), in which case it defaults to 16 byte SCSI commands). +.TP +\fBcoe\fR={0|1|2|3} +set to 1 or more for continue on error. Only applies to errors on sg +devices or block devices with the 'sgio' flag set. Thus errors on other +files will stop sg_dd. Default is 0 which implies stop on any error. See +the 'coe' flag for more information. +.TP +\fBcoe_limit\fR=\fICL\fR +where \fICL\fR is the maximum number of consecutive bad blocks stepped +over (due to "coe>0") on reads before the copy terminates. This only +applies when \fIIFILE\fR is accessed via the SG_IO ioctl. The default +is 0 which is interpreted as no limit. This option is meant to stop +the copy soon after unrecorded media is detected while still +offering "continue on error" capability. +.TP +\fBconv\fR=\fBsparse\fR +see the CONVERSIONS section below. +.TP +\fBcount\fR=\fICOUNT\fR +copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the +minimum (of \fIIFILE\fR and \fIOFILE\fR) number of blocks that sg devices +report from SCSI READ CAPACITY commands or that block devices (or their +partitions) report. Normal files are not probed for their size. If +\fIskip=SKIP\fR or \fIskip=SEEK\fR are given and the count is derived (i.e. +not explicitly given) then the derived count is scaled back so that the +copy will not overrun the device. If the file name is a block device +partition and \fICOUNT\fR is not given then the size of the partition +rather than the size of the whole device is used. If \fICOUNT\fR is not +given (or \fIcount=\-1\fR) and cannot be derived then an error message is +issued and no copy takes place. +.TP +\fBdio\fR={0|1} +default is 0 which selects indirect (buffered) IO on sg devices. Value of 1 +attempts direct IO which, if not available, falls back to indirect IO and +notes this at completion. If direct IO is selected and /proc/scsi/sg/allow_dio +has the value of 0 then a warning is issued (and indirect IO is performed). +For finer grain control use 'iflag=dio' or 'oflag=dio'. +.TP +\fBibs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBif\fR=\fIIFILE\fR +read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin +is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR +is given. +.TP +\fBiflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIIFILE\fR and are ignored when +\fIIFILE\fR is stdin. +.TP +\fBobs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBodir\fR={0|1} +when set to one opens block devices (e.g. /dev/sda) with the O_DIRECT +flag. User memory buffers are aligned to the page size when set. The +default is 0 (i.e. the O_DIRECT flag is not used). Has no effect on sg, +normal or raw files. If blk_sgio is also set then both are honoured: +block devices are opened with the O_DIRECT flag and SCSI commands are +issued via the SG_IO ioctl. +.TP +\fBof\fR=\fIOFILE\fR +write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes +to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed. +If \fIOFILE\fR is '.' (period) then it is treated the same way as +/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it +is _not_ truncated; it is overwritten from the start of \fIOFILE\fR +unless 'oflag=append' or \fISEEK\fR is given. +.TP +\fBof2\fR=\fIOFILE2\fR +write output to \fIOFILE2\fR. The default action is not to do this additional +write (i.e. when this option is not given). \fIOFILE2\fR is assumed to be +a normal file or a fifo (i.e. a named pipe). \fIOFILE2\fR is opened for +writing, created if necessary, and closed at the end of the transfer. If +\fIOFILE2\fR is a fifo (named pipe) then some other command should be +consuming that data (e.g. 'md5sum OFILE2'), otherwise this utility will block. +.TP +\fBoflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIOFILE\fR and are ignored when +\fIOFILE\fR is /dev/null, '.' (period), or stdout. +.TP +\fBretries\fR=\fIRETR\fR +sometimes retries at the host are useful, for example when there is a +transport error. When \fIRETR\fR is greater than zero then SCSI READs and +WRITEs are retried on error, \fIRETR\fR times. Default value is zero. +.TP +\fBseek\fR=\fISEEK\fR +start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBskip\fR=\fISKIP\fR +start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBsync\fR={0|1} +when 1, does SYNCHRONIZE CACHE command on \fIOFILE\fR at the end of the +transfer. Only active when \fIOFILE\fR is a sg device file name or a block +device and 'blk_sgio=1' is given. +.TP +\fBtime\fR={0|1} +when 1, times transfer and does throughput calculation, outputting the +results (to stderr) at completion. When 0 (default) doesn't perform timing. +.TP +\fBverbose\fR=\fIVERB\fR +as \fIVERB\fR increases so does the amount of debug output sent to stderr. +Default value is zero which yields the minimum amount of debug output. +A value of 1 reports extra information that is not repetitive. A value +2 reports cdbs and responses for SCSI commands that are not repetitive +(i.e. other that READ and WRITE). Error processing is not considered +repetitive. Values of 3 and 4 yield output for all SCSI commands (and +Unix read() and write() calls) so there can be a lot of output. +This only occurs for scsi generic (sg) devices and block devices when +the 'blk_sgio=1' option is set. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +does all the command line parsing and preparation but bypasses the actual +copy or read. That preparation may include opening \fIIFILE\fR or +\fIOFILE\fR to determine their lengths. This option may be useful for +testing the syntax of complex command line invocations in advance of +executing them. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs usage message and exits. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +when used once, this is equivalent to \fIverbose=1\fR. When used +twice (e.g. "\-vv") this is equivalent to \fIverbose=2\fR, etc. +.TP +\fB\-V\fR, \fB\-\-version\fR +outputs version number information and exits. +.SH CONVERSIONS +One or more conversions can be given to the "conv=" option. If more than +one is given, they should be comma separated. sg_dd does not perform the +traditional dd conversions (e.g. ASCII to EBCDIC). Recently added +conversions overlap somewhat with the flags so some conversions are +now supported by sg_dd. +.TP +noerror +this conversion is very close to "iflag=coe" and is treated as such. See +the "coe" flag. Note that an error on \fIOFILE\fR will stop the copy. +.TP +notrunc +this conversion is accepted for compatibility with dd and ignored since +the default action of this utility is not to truncate \fIOFILE\fR. +.TP +null +has no affect, just a placeholder. +.TP +sparse +FreeBSD supports "conv=sparse" so the same syntax is supported in sg_dd. +See "sparse" in the FLAGS sections for more information. +.TP +sync +is ignored by sg_dd. With dd it means supply zero fill (rather than skip) +and is typically used like this "conv=noerror,sync" to have the same +functionality as sg_dd's "iflag=coe". +.SH FLAGS +Here is a list of flags and their meanings: +.TP +append +causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For regular +files this will lead to data appended to the end of any existing data. +Cannot be used together with the \fIseek=SEEK\fR option as they conflict. +The default action of this utility is to overwrite any existing data +from the beginning of the file or, if \fISEEK\fR is given, starting at +block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g. +a disk) will usually be ignored or may cause an error to be reported. +.TP +coe +continue on error. Only active for sg devices and block devices that +have the 'sgio' flag set. 'iflag=coe oflag=coe' and 'coe=1' are +equivalent. Use this flag twice (e.g. 'iflag=coe,coe') to have the +same action as the 'coe=2'. A medium, hardware or blank check error +while reading will re\-read blocks prior to the bad block, then try to +recover the bad block, supplying zeros if that fails, and finally reread +the blocks after the bad block. A medium, hardware or blank check error +while writing is noted and ignored. The recovery of the bad block when +reading uses the SCSI READ LONG command if 'coe' given twice or +more (also with the command line option 'coe=2'). Further, the READ LONG +will set its CORRCT bit if 'coe' given thrice. SCSI disks may automatically +try and remap faulty sectors (see the AWRE and ARRE in the read write +error recovery mode page (the sdparm utility can access and possibly change +these attributes)). Errors occurring on other files types will stop sg_dd. +Error messages are sent to stderr. This flag is similar + o 'conv=noerror,sync' in the +.B dd(1) +utility. See note about READ LONG below. +.TP +dio +request the sg device node associated with this flag does direct IO. +If direct IO is not available, falls back to indirect IO and notes +this at completion. If direct IO is selected and /proc/scsi/sg/allow_dio +has the value of 0 then a warning is issued (and indirect IO is performed). +.TP +direct +causes the O_DIRECT flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. This flag requires some memory alignment on IO. Hence user +memory buffers are aligned to the page size. Has no effect on sg, normal +or raw files. If 'iflag=sgio' and/or 'oflag=sgio' is also set then both +are honoured: block devices are opened with the O_DIRECT flag and SCSI +commands are issued via the SG_IO ioctl. +.TP +dpo +set the DPO bit (disable page out) in SCSI READ and WRITE commands. Not +supported for 6 byte cdb variants of READ and WRITE. Indicates that +data is unlikely to be required to stay in device (e.g. disk) cache. +May speed media copy and/or cause a media copy to have less impact +on other device users. +.TP +dsync +causes the O_SYNC flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. The 'd' is prepended to lower confusion with the 'sync=0|1' +option which has another action (i.e. a synchronisation to media at the +end of the transfer). +.TP +excl +causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. +.TP +flock +after opening the associated file (i.e. \fIIFILE\fR and/or \fIOFILE\fR) +an attempt is made to get an advisory exclusive lock with the flock() +system call. The flock arguments are "FLOCK_EX | FLOCK_NB" which will +cause the lock to be taken if available else a "temporarily unavailable" +error is generated. An exit status of 90 is produced in the latter case +and no copy is done. +.TP +fua +causes the FUA (force unit access) bit to be set in SCSI READ and/or WRITE +commands. This only has an effect with sg devices or block devices +that have the 'sgio' flag set. The 6 byte variants of the SCSI READ and +WRITE commands do not support the FUA bit. +.TP +nocache +use posix_fadvise() to advise corresponding file there is no need to fill +the file buffer with recently read or written blocks. +.TP +null +has no affect, just a placeholder. +.TP +sgio +causes block devices to be accessed via the SG_IO ioctl rather than +standard UNIX read() and write() commands. When the SG_IO ioctl is +used the SCSI READ and WRITE commands are used directly to move +data. sg devices always use the SG_IO ioctl. This flag offers finer +grain control compared to the otherwise identical 'blk_sgio=1' option. +.TP +sparse +after each \fIBS\fR * \fIBPT\fR byte segment is read from the input, +it is checked for being all zeros. If so, nothing is written to the output +file unless this is the last segment of the transfer. This flag is only +active with the oflag option. It cannot be used when the output is not +seekable (e.g. stdout). It is ignored if the output file is /dev/null . +Note that this utility does not remove the \fIOFILE\fR prior to starting +to write to it. Hence it may be advantageous to manually remove the +\fIOFILE\fR if it is large prior to using oflag=sparse. The last segment +is always written so regular files will show the same length and so +programs like md5sum and sha1sum will generate the same value regardless +of whether oflag=sparse is given or not. This option may be used when the +\fIOFILE\fR is a raw device but is probably only useful if the device is +known to contain zeros (e.g. a SCSI disk after a FORMAT command). +.SH RETIRED OPTIONS +Here are some retired options that are still present: +.TP +append=0 | 1 +when set, equivalent to 'oflag=append'. When clear the action is +to overwrite the existing file (if it exists); this is the default. +See the 'append' flag. +.TP +fua=0 | 1 | 2 | 3 +force unit access bit. When 3, fua is set on both \fIIFILE\fR and +\fIOFILE\fR; when 2, fua is set on \fIIFILE\fR;, when 1, fua is set on +\fIOFILE\fR; when 0 (default), fua is cleared on both. See the 'fua' flag. +.SH NOTES +Block devices (e.g. /dev/sda and /dev/hda) can be given for \fIIFILE\fR. +If neither '\-iflag=direct', 'iflag=sgio' nor 'blk_sgio=1' is given then +normal block IO involving buffering and caching is performed. If +only '\-iflag=direct' is given then the buffering and caching is +bypassed (this is applicable to both SCSI devices and ATA disks). +If 'iflag=sgio' or 'blk_sgio=1' is given then the SG_IO ioctl is used on +the given file causing SCSI commands to be sent to the device and that also +bypasses most of the actions performed by the block layer (this is only +applicable to SCSI devices, not ATA disks). The same applies for block +devices given for \fIOFILE\fR. +.PP +Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +The \fICOUNT\fR, \fISKIP\fR and \fISEEK\fR arguments can take 64 bit +values (i.e. very big numbers). Other values are limited to what can fit in +a signed 32 bit number. +.PP +Data usually gets to the user space in a 2 stage process: first the +SCSI adapter DMAs into kernel buffers and then the sg driver copies +this data into user memory (write operations reverse this sequence). +This is called "indirect IO" and there is a 'dio' option to +select "direct IO" which will DMA directly into user memory. Due to some +issues "direct IO" is disabled in the sg driver and needs a +configuration change to activate it. This is typically done +with 'echo 1 > /proc/scsi/sg/allow_dio'. +.PP +All informative, warning and error output is sent to stderr so that +dd's output file can be stdout and remain unpolluted. If no options +are given, then the usage message is output and nothing else happens. +.PP +Even if READ LONG succeeds on a "bad" block when 'coe=2' (or 'coe=3') +is given, the recovered data may not be useful. There are no guarantees +that the user data will appear "as is" in the first 512 bytes. +.PP +A raw device must be bound to a block device prior to using sg_dd. +See +.B raw(8) +for more information about binding raw devices. To be safe, the sg device +mapping to SCSI block devices should be checked with 'cat /proc/scsi/scsi', +or sg_map before use. +.PP +Disk partition information can often be found with +.B fdisk(8) +[the "\-ul" argument is useful in this respect]. +.PP +For sg devices (and block devices when blk_sgio=1 is given) this utility +issues SCSI READ and WRITE (SBC) commands which are appropriate for disks and +reading from CD/DVD/HD\-DVD/BD drives. Those commands +are not formatted correctly for tape devices so sg_dd should not be used on +tape devices. If the largest block address of the requested transfer +exceeds a 32 bit block number (i.e 0xffff) then a warning is issued and +the sg device is accessed via SCSI READ(16) and WRITE(16) commands. +.PP +The attributes of a block device (partition) are ignored when 'blk_sgio=1' +is used. Hence the whole device is read (rather than just the second +partition) by this invocation: +.PP + sg_dd if=/dev/sdb2 blk_sgio=1 of=t bs=512 +.SH EXAMPLES +.PP +Looks quite similar in usage to dd: +.PP + sg_dd if=/dev/sg0 of=t bs=512 count=1MB +.PP +This will copy 1 million 512 byte blocks from the device associated with +/dev/sg0 (which should have 512 byte blocks) to a file called t. +Assuming /dev/sda and /dev/sg0 are the same device then the above is +equivalent to: +.PP + dd if=/dev/sda iflag=direct of=t bs=512 count=1000000 +.PP +although dd's speed may improve if bs was larger and count was suitably +reduced. The use of the 'iflag=direct' option bypasses the buffering and +caching that is usually done on a block device. +.PP +Using a raw device to do something similar on a ATA disk: +.PP + raw /dev/raw/raw1 /dev/hda +.br + sg_dd if=/dev/raw/raw1 of=t bs=512 count=1MB +.PP +To copy a SCSI disk partition to an ATA disk partition: +.PP + raw /dev/raw/raw2 /dev/hda3 +.br + sg_dd if=/dev/sg0 skip=10123456 of=/dev/raw/raw2 bs=512 +.PP +This assumes a valid partition is found on the SCSI disk at the given +skip block address (past the 5 GB point of that disk) and that +the partition goes to the end of the SCSI disk. An explicit count +is probably a safer option. The partition is copied to /dev/hda3 which +is an offset into the ATA disk /dev/hda . The exact number of blocks +read from /dev/sg0 are written to /dev/hda (i.e. no padding). +.PP +To time a streaming read of the first 1 GB (2 ** 30 bytes) on a disk +this utility could be used: +.PP + sg_dd if=/dev/sg0 of=/dev/null bs=512 count=2m time=1 +.PP +On completion this will output a line like: +"time to transfer data was 18.779506 secs, 57.18 MB/sec". The "MB/sec" +in this case is 1,000,000 bytes per second. +.PP +The 'of2=' option can be used to copy data and take a md5sum of it +without needing to re\-read the data: +.PP + mkfifo fif +.br + md5sum fif & +.br + sg_dd if=/dev/sg3 iflag=coe of=sg3.img oflag=sparse of2=fif bs=512 +.PP +This will image /dev/sg3 (e.g. an unmounted disk) and place the contents +in the (sparse) file sg3.img . Without re\-reading the data it will also +perform a md5sum calculation on the image. +.SH SIGNALS +The signal handling has been borrowed from dd: SIGINT, SIGQUIT and +SIGPIPE output the number of remaining blocks to be transferred and +the records in + out counts; then they have their default action. +SIGUSR1 causes the same information to be output yet the copy continues. +All output caused by signals is sent to stderr. +.SH EXIT STATUS +The exit status of sg_dd is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. Since this utility works at a higher level +than individual commands, and there are 'coe' and 'retries' flags, +individual SCSI command failures do not necessary cause the process +to exit. +.PP +An additional exit status of 90 is generated if the flock flag is given +and some other process holds the advisory exclusive lock. +.SH AUTHORS +Written by Douglas Gilbert and Peter Allworth. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +There is a web page discussing sg_dd at http://sg.danny.cz/sg/sg_dd.html +.PP +A POSIX threads version of this utility called +.B sgp_dd +is in the sg3_utils package. Another version from that package is called +.B sgm_dd +and it uses memory mapped IO to speed transfers from sg devices. +.PP +The lmbench package contains +.B lmdd +which is also interesting. For moving data to and from tapes see +.B dt +which is found at http://www.scsifaq.org/RMiller_Tools/index.html +.PP +To change mode parameters that effect a SCSI device's caching and error +recovery see +.B sdparm(sdparm) +.PP +To verify the data on the media or to verify it against some other +copy of the data see +.B sg_verify(sg3_utils) +.PP +See also +.B raw(8), dd(1), ddrescue(GNU), ddpt diff --git a/doc/sg_decode_sense.8 b/doc/sg_decode_sense.8 new file mode 100644 index 0000000..2396ae3 --- /dev/null +++ b/doc/sg_decode_sense.8 @@ -0,0 +1,156 @@ +.TH SG_DECODE_SENSE "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_decode_sense \- decode SCSI sense and related data +.SH SYNOPSIS +.B sg_decode_sense +[\fI\-\-binary=FN\fR] [\fI\-\-cdb\fR] [\fI\-\-err=ES\fR] [\fI\-\-file=FN\fR] +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-nospace\fR] [\fI\-\-status=SS\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-write=WFN\fR] +[H1 H2 H3 ...] +.SH DESCRIPTION +.\" Add any additional description here +This utility takes SCSI sense data in binary or as a sequence of +ASCII hexadecimal bytes and decodes it. The primary reference for the +decoding is SPC\-4 ANSI INCITS 513\-2015 and the most recent draft +SPC\-5 revision 19 which can be found at http://www.t10.org and other +locations on the internet. +.PP +SCSI sense data is often found in kernel log files as a result of +something going wrong or may be an informative warning. It is often shown +as a sequence of hexadecimal bytes, starting with 70, 71, 72, 73, f0 or f1. +Sense data could be up to 252 bytes long but typically is much shorter +than that, 18 bytes long is often seen and is usually associated with +the older "fixed" format sense data. +.PP +The sense data can be provided on the command line or in a file. If given +on the command line the sense data should be a sequence of hexadecimal bytes +separated by space. Alternatively a file can be given with the contents in +binary or ASCII hexadecimal bytes. The latter form can contain several lines +each with none, one or more ASCII hexadecimal bytes separated by +space (comma or tab). The hash symbol may appear and it and the rest of the +line is ignored making it useful for comments. +.PP +If the \fI\-\-cdb\fR option is given then rather than viewing the given hex +arguments as sense data, it is viewed as a SCSI command descriptor +block (CDB). In this case the command name is printed out. That name is +based on the first hex byte given (know as the opcode) and optionally on +another field called the "service action". +.PP +Another alternate action is when the \fI\-\-err=ES\fR is given. \fIES\fR +is assumed to be an "exit status" value between 0 and 255 from one of the +utilities in this package. A descriptive string is printed. Other options +are ignored apart from \fI\-\-verbose\fR. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-binary\fR=\fIFN\fR +the sense data is read in binary from a file called \fIFN\fR. +.TP +\fB\-c\fR, \fB\-\-cdb\fR +treat the given string of hex arguments as bytes in a SCSI CDB and +decode the command name. +.TP +\fB\-e\fR, \fB\-\-err\fR=\fIES\fR +\fIES\fR should be an "exit status" value between 0 and 255 that is +available from the shell (i.e. the utility's execution context) after the +utility is finished. By default an indicative error message is printed to +stdout; and if the \fI\-\-verbose\fR option is given once (or an odd number +of times) then the message is instead printed to stderr. If \fI\-\-verbose\fR +is given two or more times a longer form of the message is output. In all +cases the message is less than 128 characters long with one trailing line +feed. All other command line options and arguments are ignored. +.TP +\fB\-f\fR, \fB\-\-file\fR=\fIFN\fR +the sense data is read in ASCII hexadecimal from a file called \fIFN\fR. +The sense data should appear as a sequence of bytes separated by space, +comma, tab or newline. Everything from and including a hash symbol to the +end of that line is ignored. If \fI\-\-nospace\fR is set then no separator +is required between the ASCII hexadecimal digits in \fIFN\fR with bytes +decoded from pairs of ASCII hexadecimal digits. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +this option is used in conjunction with \fI\-\-write=WFN\fR in order to +change the output written to \fIWFN\fR to lines of ASCII hex bytes suitable +for a C language compiler. Each line contains up to 16 bytes (e.g. a line +starting with "0x3b,0x07,0x00,0xff"). +.TP +\fB\-n\fR, \fB\-\-nospace\fR +expect ASCII hexadecimal to be a string of hexadecimal digits with no +spaces between them. Bytes are decoded by taking two hexadecimal digits +at a time, so an even number of digits is expected. The string of +hexadecimal digits may be on the command line (replacing "H1 H2 H3") +or spread across multiple lines the \fIFN\fR given to \fI\-\-file=\fR. +On the command line, spaces (or other whitespace characters) between +sequences of hexadecimal digits are ignored; the maximum command line +hex string is 1023 characters long. +.TP +\fB\-s\fR, \fB\-\-status\fR=\fISS\fR +where \fISS\fR is a SCSI status byte value, given in hexadecimal. The +SCSI status byte is related to, but distinct from, sense data. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). +.TP +\fB\-V\fR, \fB\-\-version\fR +output version string then exit. +.TP +\fB\-w\fR, \fB\-\-write\fR=\fIWFN\fR +writes the sense data out to a file called \fIWFN\fR. If necessary \fIWFN\fR +is created. If \fIWFN\fR exists then it is truncated prior to writing the +sense data to it. If the \fI\-\-hex\fR option is also given then ASCII hex +is written to \fIWFN\fR (see the \fI\-\-hex\fR option description); +otherwise binary is written to \fIWFN\fR. This option is a convenience and +may be helpful in converting the ASCII hexadecimal representation of sense +data (or anything else) into the equivalent binary or a compilable ASCII +hex form. +.SH NOTES +Unlike most utilities in this package, this utility does not access a +SCSI device (logical unit). This utility accesses a library associated +with this package. Amongst other things the library decodes SCSI sense +data. +.PP +The sg_raw utility takes a ASCII hexadecimal sequence representing a SCSI +CDB. When sg_raw is given the '\-vvv' option, it will attempt to decode the +CDB name. +.SH EXAMPLES +Sense data is often printed out in kernel logs and sometimes on the +command line when verbose or debug flags are given. It will be at least +8 bytes long, often 18 bytes long but may be longer. A sense data string +might look like this: +.PP +f0 00 03 00 00 12 34 0a 00 00 00 00 11 00 00 00 +.br +00 00 +.PP +Cut and paste it after the sg_decode_sense command: +.PP + sg_decode_sense f0 00 03 00 00 12 34 0a 00 00 00 00 11 00 00 00 00 00 +.PP +and for this sense data the output should look like this: +.PP + Fixed format, current; Sense key: Medium Error +.br + Additional sense: Unrecovered read error +.br + Info fld=0x1234 [4660] +.PP +For a medium error the Info field is the logical block address (LBA) +of the lowest numbered block that the associated SCSI command was not +able to read (verify or write). +.SH EXIT STATUS +The exit status of sg_decode_sense is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2010\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_requests,sg_raw(sg3_utils) diff --git a/doc/sg_emc_trespass.8 b/doc/sg_emc_trespass.8 new file mode 100644 index 0000000..94b592b --- /dev/null +++ b/doc/sg_emc_trespass.8 @@ -0,0 +1,52 @@ +.TH SG_EMC_TRESPASS "8" "December 2012" "sg3_utils\-1.35" SG3_UTILS +.SH NAME +sg_emc_trespass \- change ownership of SCSI LUN from another +Service\-Processor to this one +.SH SYNOPSIS +.B sg_emc_trespass +[\fI\-d\fR] [\fI\-hr\fR] [\fI\-s\fR] +[\fI\-V\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +sg_emc_trespass sends an EMC\-specific Trespass Command to the \fIDEVICE\fR +with the selected options. This Mode Select changes the ownership of the LUN +of the device from another Service\-Processor to the one the command was +received on. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-d\fR +outputs some extra debug information associated with executing this command +.TP +\fB\-hr\fR +Sets the 'Honor Reservation' bit in the command. If set, the trespass +will only succeed to change the ownership from the Peer SP if the Peer +SP does not have an outstanding SCSI reservation for the LUN. By +default, the reservation state will be ignored. +.TP +\fB\-s\fR +Send the short version of the trespass command instead of the long +version. The short version is supported on the EMC FC5300, FC4500 and +FC4700. The long version (default) is supported on the CLARiiON CX and +AX family arrays. +.TP +\fB\-V\fR +print out version string then exit. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI +generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks +and DVD drives) can also be specified. For example "sg_start 0 /dev/sda" +will work in the 2.6 series kernels. +.SH EXIT STATUS +The exit status of sg_emc_trespass is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHOR +Written by Lars Marowsky\-Bree, based on sg_start. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2012 Lars Marowsky\-Bree, Douglas Gilbert. +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/doc/sg_format.8 b/doc/sg_format.8 new file mode 100644 index 0000000..d1d3fde --- /dev/null +++ b/doc/sg_format.8 @@ -0,0 +1,649 @@ +.TH SG_FORMAT "8" "September 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_format \- format, resize a SCSI disk or format a tape +.SH SYNOPSIS +.B sg_format +[\fI\-\-cmplst=\fR{0|1}] [\fI\-\-count=COUNT\fR] [\fI\-\-dcrt\fR] +[\fI\-\-dry\-run\fR] [\fI\-\-early\fR] [\fI\-\-ffmt=FFMT\fR] +[\fI\-\-fmtpinfo=FPI\fR] [\fI\-\-format\fR] [\fI\-\-help\fR] +[\fI\-\-ip\-def\fR] [\fI\-\-long\fR] [\fI\-\-mode=MP\fR] [\fI\-\-pfu=PFU\fR] +[\fI\-\-pie=PIE\fR] [\fI\-\-pinfo\fR] [\fI\-\-poll=PT\fR] [\fI\-\-quick\fR] +[\fI\-\-resize\fR] [\fI\-\-rto_req\fR] [\fI\-\-security\fR] [\fI\-\-six\fR] +[\fI\-\-size=LB_SZ\fR] [\fI\-\-tape=FM\fR] [\fI\-\-timeout=SECS\fR] +[\fI\-\-verbose\fR] [\fI\-\-verify\fR] [\fI\-\-version\fR] [\fI\-\-wait\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Not all SCSI direct access devices need to be formatted and some have vendor +specific formatting procedures. SCSI disks with rotating media are probably +the largest group that do support a 'standard' format operation. They are +typically factory formatted to a block size of 512 bytes with the largest +number of blocks that the manufacturer recommends. The manufacturer's +recommendation typically leaves aside a certain number of tracks, spread +across the media, for reassignment of blocks to logical block addresses +during the life of the disk. +.PP +This utility can format modern SCSI disks and potentially change their block +size (if permitted) and the block count (i.e. number of accessible blocks on +the media also known as "resizing"). Resizing a disk to less than the +manufacturer's recommended block count is sometimes called "short +stroking" (see NOTES section). Resizing the block count while not changing +the block size may not require a format operation. The SBC\-2 standard (see +www.t10.org) has obsoleted the "format device" mode page. Many of the low +level details found in that mode page are now left up to the discretion of +the manufacturer. There is a Format Status log page which reports on the +previous successful format operation(s). +.PP +When this utility is used without options (i.e. it is only given a +\fIDEVICE\fR argument) it prints out the existing block size and block count +derived from two sources. These two sources are a block descriptor in the +response to a MODE SENSE command and the response to a READ CAPACITY command. +The reason for this double check is to detect a "format corrupt" state (see +the NOTES section). This usage will not modify the disk. +.PP +When this utility is used with the "\-\-format" (or "\-F") option it will +attempt to format the given DEVICE. In the absence of the \fI\-\-quick\fR +option there is a 15 second pause during which time the user is invited +thrice (5 seconds apart) to abort sg_format. This occurs just prior the SCSI +FORMAT UNIT command being issued. See the NOTES section for more information. +.PP +Protection information (PI) is optional and is made up of one or more +protection intervals, each made up of 8 bytes associated with a logical +block. When PI is active each logical block will have 1, 2, 4, 8, etc +protection intervals (i.e. a power of two), interleaved with (and following) +the user data to which they refer. Four protection types are defined with +protection type 0 being no protection intervals. See the PROTECTION +INFORMATION section below for more information. +.PP +When the \fI\-\-tape=FM\fR option is given then the SCSI FORMAT MEDIUM +command is sent to the \fIDEVICE\fR. FORMAT MEDIUM is defined in the SSC +documents at T10 and prepares a volume for use. That may include +partitioning the medium. See the section below on TAPE for more information. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-C\fR, \fB\-\-cmplst\fR={0|1} +sets the CMPLST ("complete list") bit in the FORMAT UNIT cdb to 0 or 1. +If the value is 0 then the existing GLIST (grown list) is taken into account. +If the value is 1 then the existing GLIST is ignored. CMPLST defaults to 1 +apart from when the \fI\-\-ffmt=FFMT\fR option's value is non\-zero in which +case CMPLST defaults to 0. See the LISTS section below. In most cases this +bit should be left at its default value. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fICOUNT\fR +where \fICOUNT\fR is the number of blocks to be formatted or media to be +resized to. Can be used with either \fI\-\-format\fR or \fI\-\-resize\fR. +With \fI\-\-format\fR this option need not be given in which case it is +assumed to be zero. +.br +With \fI\-\-format\fR the interpretation of \fICOUNT\fR is: +.br + (\fICOUNT\fR > 0) : only format the first \fICOUNT\fR blocks and READ +CAPACITY will report \fICOUNT\fR blocks after format +.br + (\fICOUNT\fR = 0) and block size unchanged : use existing block count +.br + (\fICOUNT\fR = 0) and block size changed : recommended maximum block +count for new block size +.br + (\fICOUNT\fR = \-1) : use recommended maximum block count +.br + (\fICOUNT\fR < \-1) : illegal +.br +With \fI\-\-resize\fR this option must be given and \fICOUNT\fR has this +interpretation: +.br + (\fICOUNT\fR > 0) : after resize READ CAPACITY will report \fICOUNT\fR +blocks +.br + (\fICOUNT\fR = 0) : after resize READ CAPACITY will report 0 blocks +.br + (\fICOUNT\fR = \-1) : after resize READ CAPACITY will report its +maximum number of blocks +.br + (\fICOUNT\fR < \-1) : illegal +.br +In both cases if the given \fICOUNT\fR exceeds the maximum number of +blocks (for the block size) then the disk reports an error. +See NOTES section below. +.TP +\fB\-D\fR, \fB\-\-dcrt\fR +this option sets the DCRT bit in the FORMAT UNIT command's parameter list +header. It will "disable certification". Certification verifies that blocks +are usable during the format process. Using this option may speed the format. +The default action of this utility (i.e. when this option is not given) is +to clear the DCRT bit thereby requesting "media certification". When the DCRT +bit is set, the FOV bit must also be set hence sg_format does that. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +this option will parse the command line, do all the preparation but bypass +the actual FORMAT UNIT or FORMAT MEDIUM commands. Also if the options would +cause the logical block size to change, then the MODE SELECT command that +would do that is also bypassed when the dry run option is given. +.TP +\fB\-e\fR, \fB\-\-early\fR +during a format operation, The default action of this utility is to poll the +disk every 60 seconds (or every 10 seconds if \fIFFMT\fR is non\-zero) to +determine the progress of the format operation until it is finished. When this +option is given this utility will exit "early", that is as soon as the format +operation has commenced. Then the user can monitor the progress of the ongoing +format operation with other utilities (e.g. sg_turs(8) or sg_requests(8)). +This option and \fI\-\-wait\fR are mutually exclusive. +.TP +\fB\-t\fR, \fB\-\-ffmt\fR=\fIFFMT\fR +\fIFFMT\fR (fast format) is placed in a field of the same name in the FORMAT +UNIT cdb. The field was introduced in SBC\-4 revision 10. The default value +is 0 which implies the former action which is typically to overwrite all +blocks on the \fIDEVICE\fR. That can take a long time (e.g. with hard disks +over 10 TB in size that can be days). With \fIFFMT\fR set that time may be +reduced to minutes. So it is worth trying if it is available. +.br +\fIFFMT\fR has values 1 and 2 for fast format with 3 being reserved +currently. These two values include this description: "The device server +initializes the medium ... without overwriting the medium (i.e. resources +for managing medium access are initialized and the medium is not written)". +The difference between 1 and 2 concerns read operations on LBAs to which no +data has been written to, after the fast format. When \fIFFMT\fR is 1 the +read operation should return "unspecified logical block data" and complete +without error. When \fIFFMT\fR is 2 the read operation may yield check +condition status with a sense key set to hardware error, medium error or +command aborted. See SBC\-4 revision 15 section 4.35 for more details. +.TP +\fB\-f\fR, \fB\-\-fmtpinfo\fR=\fIFPI\fR +sets the FMTPINFO field in the FORMAT UNIT cdb to a value between 0 and 3. +The default value is 0. The FMTPINFO field from SBC\-3 revision 16 is a 2 +bit field (bits 7 and 6 of byte 1 in the cdb). Prior to that revision it was +a single bit field (bit 7 of byte 1 in the cdb) and there was an accompanying +bit called RTO_REQ (bit 6 of byte 1 in the cdb). The deprecated +options "\-\-pinfo" and "\-\-rto\-req" represent the older usage. This +option should be used in their place. See the PROTECTION INFORMATION section +below for more information. +.TP +\fB\-F\fR, \fB\-\-format\fR +issue a SCSI FORMAT UNIT command. +.B This will destroy all the data held on the media. +This option is required to change the block size of a disk. In the absence +of the \fI\-\-quick\fR option, the user is given a 15 second count down to +ponder the wisdom of doing this, during which time control\-C (amongst other +Unix commands) can be used to kill this process before it does any damage. +.br +When used three times (or more) the preliminary MODE SENSE and SELECT +commands are bypassed, leaving only the initial INQUIRY and FORMAT UNIT +commands. This is for emergency use (e.g. when the MODE SENSE/SELECT +commands are not working) and cannot change the logical block size. +.br +See NOTES section for implementation details and EXAMPLES section for typical +use. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage information then exit. +.TP +\fB\-I\fR, \fB\-\-ip\-def\fR +sets the default Initialization Pattern. Some disks (SSDs) use this to flag +that a format should fully provision (i.e. associate a physical block with +every logical block). The same disks (SSDs) might thin provision if this +option is not given. If this option is given then the \fI\-\-security\fR +option cannot be given. Also accepts \fI\-\-ip_def\fR for this option. +.TP +\fB\-l\fR, \fB\-\-long\fR +the default action of this utility is to assume 32 bit logical block +addresses. With 512 byte block size this permits more than 2 +terabytes (almost 2 ** 41 bytes) on a single disk. This option selects +commands and parameters that allow for 64 bit logical block addresses. +Specifically this option sets the "longlba" flag in the MODE SENSE (10) +command and uses READ CAPACITY (16) rather than READ CAPACITY (10). If this +option is not given and READ CAPACITY (10) or MODE SELECT detects a disk +the needs more than 32 bits to represent its logical blocks then it is +set internally. This option does not set the LONGLIST bit in the FORMAT UNIT +command. The LONGLIST bit is set as required depending other +parameters (e.g. when '\-\-pie=PIE' is greater than zero). +.TP +\fB\-M\fR, \fB\-\-mode\fR=\fIMP\fR +\fIMP\fR is a mode page number (0 to 62 inclusive) that will be used for +reading and perhaps changing the device logical block size. The default +is 1 which is the Read\-Write Error Recovery mode page. +.br +Preferably the chosen (or default) mode page should be saveable (i.e. +accept the SP bit set in the MODE SELECT command used when the logical +block size is being changed). Recent version of this utility will retry a +MODE SELECT if the SP=1 variant fails with a sense key of ILLEGAL REQUEST. +That retry will use the same MODE SELECT command but with SP=0 . +.TP +\fB\-P\fR, \fB\-\-pfu\fR=\fIPFU\fR +sets the "Protection Field Usage" field in the parameter block associated +with a FORMAT UNIT command to \fIPFU\fR. The default value is 0, the only +other defined value currently is 1. See the PROTECTION INFORMATION section +below for more information. +.TP +\fB\-q\fR, \fB\-\-pie\fR=\fIPIE\fR +sets the "Protection Interval Exponent" field in the parameter block +associated with a FORMAT UNIT command to \fIPIE\fR. The default value is 0. +\fIPIE\fR can only be non\-zero with protection types 2 and 3. +The value of 0 is typical for 512 byte blocks; with 4096 byte blocks a value +of 3 may be appropriate (i.e. 8 protection intervals interleaved with 4096 +bytes of user data). A device may not support any non\-zero values. This +field first appeared in SBC\-3 revision 18. +.TP +\fB\-p\fR, \fB\-\-pinfo\fR +this option is deprecated, use the \fI\-\-fmtpinfo=FPI\fR option instead. +If used, then it sets bit 7 of byte 1 in the FORMAT UNIT cdb and that +is equivalent to setting \fI\-\-fmtpinfo=2\fR. [So if \fI\-\-pinfo\fR is +used (plus \fI\-\-fmtpinfo=FPI\fR and \fI\-\-pfu=PFU\fR are not given or +their arguments are 0) then protection type 1 is selected.] +.TP +\fB\-x\fR, \fB\-\-poll\fR=\fIPT\fR +where \fIPT\fR is the type of poll used. If \fIPT\fR is 0 then a TEST UNIT +READY command is used, otherwise a REQUEST SENSE command is used. The +default is currently 0 but this will change to 1 in the near future. See +the NOTES sections below. +.TP +\fB\-Q\fR, \fB\-\-quick\fR +the default action (i.e. when the option is not given) is to give the user +15 seconds to reconsider doing a format operation on the \fIDEVICE\fR. +When this option is given that step (i.e. the 15 second warning period) +is skipped. +.TP +\fB\-r\fR, \fB\-\-resize\fR +rather than format the disk, it can be resized. This means changing the +number of blocks on the device reported by the READ CAPACITY command. +This option should be used with the \fI\-\-count=COUNT\fR option. +The contents of all logical blocks on the media remain unchanged when +this option is used. This means that any resize operation can be +reversed. This option cannot be used together with either \fI\-\-format\fR +or a \fI\-\-size=LB_SZ\fR whose argument is different to the existing block +size. +.TP +\fB\-R\fR, \fB\-\-rto_req\fR +The option is deprecated, use the \fI\-\-fmtpinfo=FPI\fR option instead. +If used, then it sets bit 6 of byte 1 in the FORMAT UNIT cdb. +.TP +\fB\-S\fR, \fB\-\-security\fR +sets the "Security Initialization" (SI) bit in the FORMAT UNIT command's +initialization pattern descriptor within the parameter list. According +to SBC\-3 the default initialization pattern "shall be written using a +security erasure write technique". See the NOTES section on the SCSI +SANITIZE command. If this option is given then the \fI\-\-ip_def\fR option +cannot be given. +.TP +\fB\-6\fR, \fB\-\-six\fR +Use 6 byte variants of MODE SENSE and MODE SELECT. The default action +is to use the 10 byte variants. Some MO drives need this option set +when doing a format. +.TP +\fB\-s\fR, \fB\-\-size\fR=\fILB_SZ\fR +where \fILB_SZ\fR is the logical block size (i.e. number of user bytes in each +block) to format the device to. The default value is whatever is currently +reported by the block descriptor in a MODE SENSE command. If the block size +given by this option is different from the current value then a MODE SELECT +command is used to change it prior to the FORMAT UNIT command being +started (as recommended in the SBC standards). Some SCSI disks have 512 byte +logical blocks by default and allow an alternate logical block size of 4096 +bytes. If the given size in unacceptable to the disk, most likely an "Invalid +field in parameter list" message will appear in sense data (requires the +use of '\-v' to decode sense data). +.br +Note that formatting a disk to add or remove protection information is not +regarded as a change to its logical block size so this option should not +be used. +.TP +\fB\-T\fR, \fB\-\-tape\fR=\fIFM\fR +will send a FORMAT MEDIUM command to the \fIDEVICE\fR with its FORMAT field +set to \fIFM\fR. This option is used to prepare a tape (i.e. the "medium") +in a tape drive for use. Values for \fIFM\fR include 0 to do the "default" +format; 1 to partition a volume and 2 to do a default format then partition. +.TP +\fB\-m\fR, \fB\-\-timeout\fR=\fISECS\fR +where \fISECS\fR is the FORMAT UNIT or FORMAT MEDIUM command timeout in +seconds. \fISECS\fR will only be used if it exceeds the internal timeout +which is 20 seconds if the IMMED bit is set and 72000 seconds (20 hours) +or higher if the IMMED bit is not set. If the disk size exceeds 4 TB then +the timeout value is increased to 144000 seconds (40 hours). And if it is +greater than 8 TB then the timeout value is increased to 288000 seconds (80 +hours). If the timeout is exceeded then the operating system will typically +abort the command. Aborting a command may escalate to a LUN reset (or +worse). A timeout may also leave the disk or tape format operation +incomplete. And that may result in the disk or tape being in a "format +corrupt" state requiring another format to remedy the situation. So for +various reasons timeouts are best avoided. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). "\-vvv" gives +a lot more debug output. +.TP +\fB\-y\fR, \fB\-\-verify\fR +set the VERIFY bit in the FORMAT MEDIUM cdb. The default is that the VERIFY +bit is clear. This option is only appropriate for tapes. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-w\fR, \fB\-\-wait\fR +the default format action is to set the "IMMED" bit in the FORMAT UNIT +command's (short) parameter header. If this option (i.e. \fI\-\-wait\fR) is +given then the "IMMED" bit is not set. If \fI\-\-wait\fR is given then the +FORMAT UNIT or FORMAT MEDIUM command waits until the format operation +completes before returning its response. This can be many hours on large +disks. See the \fI\-\-timeout=SECS\fR option. +.SH LISTS +The SBC\-3 draft (revision 20) defines PLIST, CLIST, DLIST and GLIST in +section 4.10 on "Medium defects". Briefly, the PLIST is the "primary" +list of manufacturer detected defects, the CLIST ("certification" list) +contains those detected during the format operation, the DLIST is a list of +defects that can be given to the format operation. The GLIST is the grown +list which starts in the format process as CLIST+DLIST and can "grow" later +due to automatic reallocation (see the ARRE and AWRE bits in the +Read\-Write Error Recovery mode page (see sdparm(8))) and use of the +SCSI REASSIGN BLOCKS command (see sg_reassign(8)). +.PP +By the SBC\-3 standard (following draft revision 36) the CLIST and DLIST +had been removed, leaving PLIST and GLIST. Only PLIST and GLIST are found +in the SBC\-4 drafts. +.PP +The CMPLST bit (controlled by the \fI\-\-cmplst=\fR0|1 option) determines +whether the existing GLIST, when the format operation is invoked, +is taken into account. The sg_format utility sets the FOV bit to zero +which causes DPRY=0, so the PLIST is taken into account, and DCRT=0, so +the CLIST is generated and used during the format process. +.PP +The sg_format utility does not permit a user to provide a defect +list (i.e. DLIST). +.SH PROTECTION INFORMATION +Protection Information (PI) is additional information held with logical +blocks so that an application and/or host bus adapter can check the +correctness of those logical blocks. PI is placed in one or more +protection intervals interleaved in each logical block. Each protection +interval follows the user data to which it refers. A protection interval +contains 8 bytes made up of a 2 byte "logical block guard" (CRC), a 2 +byte "logical block application guard", and a 4 byte "logical block +reference tag". Devices with 512 byte logical block size typically have +one protection interval appended, making its logical block data 520 bytes +long. Devices with 4096 byte logical block size often have 8 protection +intervals spread across its logical block data for a total size of 4160 +bytes. Note that for all other purposes the logical block size is considered +to be 512 and 4096 bytes respectively. +.PP +The SBC\-3 standard have added several "protection types" to the PI +introduced in the SBC\-2 standard. SBC\-3 defines 4 protection types (types +0 to 3) with protection type 0 meaning no PI is maintained. While a device +may support one or more protection types, it can only be formatted with 1 +of the 4. To change a device's protection type, it must be re\-formatted. +For more information see the Protection Information in section 4.22 of +draft SBC\-4 revision 15. +.PP +A device that supports PI information (i.e. supports one or more protection +types 1, 2 and 3) sets the "PROTECT" bit in its standard INQUIRY response. It +also sets the SPT field in the EXTENDED INQUIRY VPD page response to indicate +which protection types it supports. Given PROTECT=1 then SPT=0 implies the +device supports PI type 1 only, SPT=1 implies the device supports PI types 1 +and 2, and various other non\-obvious mappings up to SPT=7 which implies +protection types 1, 2 and 3 are supported. The +.B current +protection type of a disk can be found in the "P_TYPE" and "PROT_EN" +fields in the response of a READ CAPACITY (16) command (e.g. with +the 'sg_readcap \-\-long' utility). +.PP +Given that a device supports a particular protection type, a user can +then choose to format that disk with that protection type by setting +the "FMTPINFO" and "Protection Field Usage" fields in the FORMAT UNIT +command. Those fields correspond to the \fI\-\-fmtpinfo=FPI\fR and the +\fI\-\-pfu=PFU\fR options in this utility. The list below shows the four +protection types followed by the options of this utility needed to select +them: +.br + \fB0\fR : \-\-fmtpinfo=0 \-\-pfu=0 +.br + \fB1\fR : \-\-fmtpinfo=2 \-\-pfu=0 +.br + \fB2\fR : \-\-fmtpinfo=3 \-\-pfu=0 +.br + \fB3\fR : \-\-fmtpinfo=3 \-\-pfu=1 +.br +The default value of \fIFPI\fR (in \fI\-\-fmtpinfo=FPI\fR) is 0 and the +default value of \fIPFU\fR (in \fI\-\-pfu=PFU\fR) is 0. So if neither +\fI\-\-fmtpinfo=FPI\fR nor \fI\-\-pfu=PFU\fR are given then protection +type 0 (i.e. no protection information) is chosen. +.SH NOTES +After a format that changes the logical block size or the number of logical +blocks on a disk, the operating system may need to be told to re\-initialize +its setting for that disk. In Linux that can be done with: +.br + echo 1 > /sys/block/sd{letter(s)}/device/rescan +.br +where "letter(s)" will be between 'a' and 'zzz'. The lsscsi utility in Linux +can be used to check the various namings of a disk. +.PP +The SBC\-2 standard states that the REQUEST SENSE command should be used +for obtaining progress indication when the format command is underway. +However, tests on a selection of disks shows that TEST UNIT READY +commands yield progress indications (but not REQUEST SENSE commands). So +the current version of this utility defaults to using TEST UNIT READY +commands to poll the disk to find out the progress of the format. The +\fI\-\-poll=PT\fR option has been added to control this. +.PP +When the \fI\-\-format\fR option is given without the \fI\-\-wait\fR option +then the SCSI FORMAT UNIT command is issued with the IMMED bit set which +causes the SCSI command to return after it has started the format operation. +The \fI\-\-early\fR option will cause sg_format to exit at that point. +Otherwise the \fIDEVICE\fR is polled every 60 seconds or every 10 seconds +if \fIFFMT\fR is non\-zero. The poll is with TEST UNIT READY or REQUEST SENSE +commands until one reports an "all clear" (i.e. the format operation has +completed). Normally these polling commands will result in a progress +indicator (expressed as a percentage) being output to the screen. If the user +gets bored watching the progress report then sg_format process can be +terminated (e.g. with control\-C) without affecting the format operation +which continues. However a target or device reset (or a power cycle) will +probably cause the device to become "format corrupt". +.PP +When the \fI\-\-format\fR (or \fI\-\-tape\fR) and \fI\-\-wait\fR options are +both given then this utility may take a long time to return. In this case +care should be taken not to send any other SCSI commands to the disk as it +may not respond leaving those commands queued behind the active format +command. This may cause a timeout in the OS driver (in a lot shorter period +than 20 hours applicable to some format operations). This may result in the +OS resetting the disk leaving the format operation incomplete. This may leave +the disk in a "format corrupt" state requiring another format to remedy +the situation. Modern SCSI devices should yield a "not ready" sense key +with an additional sense indicating a format is in progress. With older +devices the user should take precautions that nothing attempts to access +a device while it is being formatted. +.PP +When the block size (i.e. the number of bytes in each block) is changed +on a disk two SCSI commands must be sent: a MODE SELECT to change the block +size followed by a FORMAT command. If the MODE SELECT command succeeds and +the FORMAT fails then the disk may be in a state that the standard +calls "format corrupt". A block descriptor in a subsequent MODE SENSE +will report the requested new block size while a READ CAPACITY command +will report the existing (i.e. previous) block size. Alternatively +the READ CAPACITY command may fail, reporting the device is not ready, +potentially requiring a format. The solution to this situation is to +do a format again (and this time the new block size does not have to +be given) or change the block size back to the original size. +.PP +The SBC\-2 standard states that the block count can be set back to the +manufacturer's maximum recommended value in a format or resize operation. +This can be done by placing an address of 0xffffffff (or the 64 bit +equivalent) in the appropriate block descriptor field to a MODE SELECT +command. In signed (two's complement) arithmetic that value corresponds +to '\-1'. So a \-\-count=\-1 causes the block count to be set back to +the manufacturer's maximum recommended value. To see exactly which SCSI +commands are being executed and parameters passed add the "\-vvv" option to +the sg_format command line. +.PP +The FMTDATA field shown in the FORMAT UNIT cdb does not have a corresponding +option in this utility. When set in the cdb it indicates an additional +parameter list will be sent to the \fIDEVICE\fR along with the cdb. It is set +as required, basically when any field in the parameter list header is set. +.PP +Short stroking is a technique to trade off capacity for performance on +hard disks. "Hard" disk is often used to mean a storage device with +spinning platters which contain the user data. Solid State Disk (SSD) is +the newer form of storage device that contains no moving parts. Hard disk +performance is usually highest on the outer tracks (usually the lower logical +block addresses) so by resizing or reformatting a disk to a smaller capacity, +average performance will usually be increased. +.PP +Other utilities may be useful in finding information associated with +formatting. These include sg_inq(8) to fetch standard INQUIRY +information (e.g. the PROTECT bit) and to fetch the EXTENDED INQUIRY +VPD page (e.g. RTO and GRD_CHK bits). The sdparm(8) utility can be +used to access and potentially change the now obsolete format mode page. +.PP +scsiformat is another utility available for formatting SCSI disks +with Linux. It dates from 1997 (most recent update) and may be useful for +disks whose firmware is of that vintage. +.PP +The \fICOUNT\fR numeric argument may include a multiplicative suffix or be +given in hexadecimal. See the "NUMERIC ARGUMENTS" section in the +sg3_utils(8) man page. +.PP +The SCSI SANITIZE command was introduced in SBC\-3 revision 27. It is closely +related to the ATA sanitize disk feature set and can be used to remove all +existing data from a disk. Sanitize is more likely to be implemented on +modern disks (including SSDs) than FORMAT UNIT's security initialization +feature (see the \fI\-\-security\fR option) and in some cases much faster. +.PP +SSDs that support thin provisioning will typically unmap all logical blocks +during a format. The reason is to improve the SSD's endurance. Also thin +provisioned formats typically complete faster than fully provisioned ones +on the same disk (see the \fI\-\-ip_def\fR option). In either case format +operations on SSDs tend to be a lot faster than they are on hard disks with +spinning media. +.SH TAPE +Tape system use a variant of the FORMAT UNIT command used on disks. Tape +systems use the FORMAT MEDIUM command which is simpler with only three +fields in the cdb typically used. Apart from sharing the same opcode the +cdbs of FORMAT UNIT and FORMAT MEDIUM are quite different. FORMAT MEDIUM's +fields are VERIFY, IMMED and FORMAT (with TRANSFER LENGTH always set to 0). +The VERIFY bit field is set with the \fI\-\-verify\fR option. The IMMED bit +is manipulated by the \fI\-\-wait\fR option in the same way it is for disks; +one difference is that if the \fI\-\-poll=PT\fR option is not given then it +defaults to \fIPT\fR of 1 which means the poll is done with REQUEST SENSE +commands. +.PP +The argument given to the \fI\-\-tape=FM\fR option is used to set the FORMAT +field. \fIFM\fR can take values from "\-1" to "15" where "\-1" (the default) +means don't do a tape format; value "8" to "15" are for vendor specific +formats. The \fI\-\-early\fR option may also be used to set the IMMED +bit and then exit this utility (rather than poll periodically until it is +finished). In this case the tape drive will still be busy doing the format +for some time but, according to T10, should still respond in full to the +INQUIRY and REPORT LUNS commands. Other commands (including REQUEST SENSE) +should yield a "not ready" sense key with an additional sense code +of "Logical unit not ready, format in progress". Additionally REQUEST SENSE +should contain a progress indication in its sense data. +.PP +When \fIFM\fR is 1 or 2 then the settings in the Medium partition mode page +control the partitioning. That mode page can be viewed and modified with the +sdparm utility. +.PP +Prior to invoking this utility the tape may need to be positioned to the +beginning of partition 0. In Linux that can typically be done with the mt +utility (e.g. 'mt \-f /dev/st0 rewind'). +.SH EXAMPLES +These examples use Linux device names. For suitable device names in +other supported Operating Systems see the sg3_utils(8) man page. +.PP +In the first example below simply find out the existing block count and +size derived from two sources: a block descriptor in a MODE SELECT command +response and from the response of a READ CAPACITY commands. No changes +are made: +.PP + # sg_format /dev/sdm +.PP +Now a simple format, leaving the block count and size as they were previously. +The FORMAT UNIT command is executed in IMMED mode and the device is polled +every 60 seconds to print out a progress indication: +.PP + # sg_format \-\-format /dev/sdm +.PP +Now the same format, but waiting (passively) until the format operation is +complete: +.PP + # sg_format \-\-format \-\-wait /dev/sdm +.PP +Next is a format in which the block size is changed to 520 bytes and the block +count is set to the manufacturer's maximum value (for that block size). Note, +not all disks support changing the block size: +.PP + # sg_format \-\-format \-\-size=520 /dev/sdm +.PP +Now a resize operation so that only the first 0x10000 (65536) blocks on a disk +are accessible. The remaining blocks remain unaltered. +.PP + # sg_format \-\-resize \-\-count=0x10000 /dev/sdm +.PP +Now resize the disk back to its normal (maximum) block count: +.PP + # sg_format \-\-resize \-\-count=\-1 /dev/sdm +.PP +One reason to format a SCSI disk is to add protection information. First +check which protection types are supported by a disk (by checking the SPT +field in the Extended inquiry VPD page together with the Protect bit in the +standard inquiry response): +.PP + # sg_vpd \-p ei \-l /dev/sdb +.br + extended INQUIRY data VPD page: +.br + ACTIVATE_MICROCODE=0 +.br + SPT=1 [protection types 1 and 2 supported] +.br + .... +.PP +Format with type 1 protection: +.PP + # sg_format \-\-format \-\-fmtpinfo=2 /dev/sdm +.PP +After a successful format with type 1 protection, READ CAPACITY(16) +should show something like this: +.PP + # sg_readcap \-l /dev/sdm +.br + Read Capacity results: +.br + Protection: prot_en=1, p_type=0, p_i_exponent=0 [type 1 protection] +.br + Logical block provisioning: lbpme=0, lbprz=0 +.br + .... +.PP +To format with type 3 protection: +.PP + # sg_format \-\-format \-\-fmtpinfo=3 \-\-pfu=1 /dev/sdm +.PP +For the disk shown above this will probably fail because the Extended inquiry +VPD page showed only types 1 and 2 protection are supported. +.PP +Here are examples of using fast format (FFMT field in FORMAT UNIT cdb) to +quickly switch between 512 and 4096 byte logical block size. Assume disk +starts with 4096 byte logical block size and all important data has been +backed up. +.PP + # sg_format \-\-format \-\-ffmt=1 \-\-size=512 /dev/sdd +.PP +Now /dev/sdd should have 512 byte logical block size. And to switch it back: +.PP + # sg_format \-\-format \-\-ffmt=1 \-\-size=4096 /dev/sdd +.SH EXIT STATUS +The exit status of sg_format is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. Unless the \fI\-\-wait\fR option is given, the +exit status may not reflect the success of otherwise of the format. +Using sg_turs(8) and sg_readcap(8) after the format operation may be wise. +.SH AUTHORS +Written by Grant Grundler, James Bottomley and Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2005\-2018 Grant Grundler, James Bottomley and Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_turs(8), sg_requests(8), sg_inq(8), sg_modes(8), sg_vpd(8), +.B sg_reassign(8), sg_readcap(8), sg3_utils(8), +.B sg_sanitize(8) [all in sg3_utils], +.B lsscsi(8), mt(mt\-st), sdparm(8), scsiformat (old), hdparm(8) diff --git a/doc/sg_get_config.8 b/doc/sg_get_config.8 new file mode 100644 index 0000000..ba0c392 --- /dev/null +++ b/doc/sg_get_config.8 @@ -0,0 +1,143 @@ +.TH SG_GET_CONFIG "8" "December 2012" "sg3_utils\-1.35" SG3_UTILS +.SH NAME +sg_get_config \- send SCSI GET CONFIGURATION command (MMC\-4 +) +.SH SYNOPSIS +.B sg_get_config +[\fI\-\-brief\fR] [\fI\-\-current\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-inner\-hex\fR] [\fI\-\-list\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] +[\fI\-\-rt=RT\fR] [\fI\-\-starting=FC\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI GET CONFIGURATION command to \fIDEVICE\fR and decodes the +response. The response includes the features and profiles of the device. +Typically these devices are CD, DVD, HD\-DVD and BD players that may (but +not necessarily) have media in them. These devices may well be connected via +ATAPI, USB or IEEE 1394 transports. In such cases they are "SCSI" devices +only in the sense that they use the "Multi\-Media command" set (MMC). +MMC is a specialized SCSI command set whose definition can be found +at http://www.t10.org . +.PP +This utility is based on the MMC\-4 and later draft standards. See section +5 on "Features and Profile for Multi_Media devices" for more information on +specific feature parameters and profiles. The manufacturer's product manual +may also be useful. +.PP +Since modern DVD and BD writers support many features and profiles, the +decoded output from this utility can be large. There are various ways to cut +down the output. If the \fI\-\-brief\fR option is used only the feature names +are shown and the feature parameters are not decoded. Alternatively if only +one feature is of interest then this combination of options is +appropriate: "\-\-rt=2 \-\-starting=\fIFC\fR". Another possibility is to show +only the features that are relevant to the media in the drive (i.e. "current") +with the "\-\-rt=1" option. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-brief\fR +show the feature names but don't decode the parameters of those features. +When used with \fI\-\-list\fR outputs known feature names but not known +profile names. +.TP +\fB\-c\fR, \fB\-\-current\fR +output features marked as current. This option is equivalent to '\-\-rt=1'. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response in hex (don't decode response). +.TP +\fB\-i\fR, \fB\-\-inner\-hex\fR +decode to the feature name level then output each feature's data in hex. +.TP +\fB\-l\fR, \fB\-\-list\fR +list all known feature and profile names. Ignore the device name (if given). +Simply lists the feature names and profiles (followed by their hex values) +that this utility knows about. If \fI\-\-brief\fR is also given then only +feature names are listed. +.TP +\fB\-q\fR, \fB\-\-readonly\fR +opens the DEVICE read\-only rather than read\-write which is the +default. The Linux sg driver needs read\-write access for the SCSI +GET CONFIGURATION command but other access methods may require +read\-only access. +.TP +\fB\-r\fR, \fB\-\-rt\fR=\fIRT\fR +where \fIRT\fR is the field of that name in the GET CONFIGURATION cdb. +Allowable values are 0, 1, 2, or 3 . The command's action also depends on +the value given to the \fI\-\-starting=FC\fR option. The default value is 0. +When \fIRT\fR is 0 then all features, regardless of currency, are +returned (whose feature code is greater than or equal to \fIFC\fR given +to \fI\-\-starting=\fR). When \fIRT\fR is 1 then all current features are +returned (whose feature code is greater than or equal to \fIFC\fR). When +\fIRT\fR is 2 then the feature whose feature code is equal to \fIFC\fR, +if any, is returned. When \fIRT\fR is 3 the response is reserved (probably +yields an "illegal field in cdb" error). To simplify the meanings of the +\fIRT\fR values are: +.br + \fB0\fR : all features, current on not +.br + \fB1\fR : only current features +.br + \fB2\fR : only feature whose code is \fIFC\fR +.br + \fB3\fR : reserved +.br +.TP +\fB\-R\fR, \fB\-\-raw\fR +output response in binary (to stdout). Note that the short form is \fI\-R\fR +unlike most other utilities in this package that use \fI\-r\fR for this +action. +.TP +\fB\-s\fR, \fB\-\-starting\fR=\fIFC\fR +where \fIFC\fR is the feature code value. This option works closely with +the \fI\-\-rt=RT\fR option. The \fIFC\fR value is in the range 0 to +65535 (0xffff) inclusive. Its default value is 0. A value prefixed +with "0x" (or a trailing 'h') is interpreted as hexadecimal. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +There are multiple versions of the MMC (draft) standards: MMC [1997], +MMC\-2 [2000], MMC\-3 [2002], MMC\-4 and MMC\-5. The first three are now +ANSI INCITS standards with the year they became standards shown in +brackets. The draft immediately prior to standardization can +be found at http://www.t10.org . In the initial MMC standard there +was no GET CONFIGURATION command and the relevant information was +obtained from the "CD capabilities and mechanical status mode +page" (mode page 0x2a). It was later renamed the "MM capabilities and +mechanical status mode page" and has been made obsolete in MMC\-4 and +MMC\-5. The GET CONFIGURATION command was introduced in MMC\-2 and has +become a replacement for that mode page. New features such as support +for "BD" (blue ray) media type can only be found by using the +GET CONFIGURATION command. Hence older CD players may not support +the GET CONFIGURATION command in which case the "MM capabilities ..." +mode page can be checked with sdparm(8), sginfo(8) or sg_modes(8). +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be +a SCSI generic (sg) device. In the 2.6 series block devices +can also be specified. For example "sg_get_config /dev/hdc" +will work in the 2.6 series kernels as long as /dev/hdc is +an ATAPI device. In the 2.6 series external DVD writers attached +via USB could be queried with "sg_get_config /dev/scd1" for example. +.SH EXIT STATUS +The exit status of sg_get_config is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2012 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sginfo(8), sg_modes(8), sg_inq(8), sg_prevent(8), +.B sg_start(8) [all in sg3_utils], +.B sdparm(8) diff --git a/doc/sg_get_lba_status.8 b/doc/sg_get_lba_status.8 new file mode 100644 index 0000000..bc35208 --- /dev/null +++ b/doc/sg_get_lba_status.8 @@ -0,0 +1,134 @@ +.TH SG_GET_LBA_STATUS "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_get_lba_status \- send SCSI GET LBA STATUS(16 or 32) command +.SH SYNOPSIS +.B sg_get_lba_status +[\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-brief\fR] [\fI\-\-element-id=EI\fR] +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-maxlen=LEN\fR] +[\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-report\-type=RT\fR] +[\fI\-\-scan-len=SL\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send the SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command to the +\fIDEVICE\fR and output the response. The 16 byte command variant was +introduced in (draft) SBC\-3 revision 20 and devices that support logical +block provisioning should support this command. The GET LBA STATUS(32) +command was added in (draft) SBC\-4 revision 14. +.PP +The default action is to decode the response into one LBA status descriptor +per line output to stdout. The descriptor LBA is output in hex (prefixed +by '0x') and the number of blocks is output in decimal followed by the +provisioning status and additional status in decimal. The provisioning status +can be in the range 0 to 15 of which only 0 (mapped or unknown), 1 (unmapped), +2 (anchored), 3 (mapped) and 4 (unknown) are used currently. The amount of +output can be reduced by the \fI\-\-brief\fR option. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-S\fR, \fB\-\-16\fR +send SCSI GET LBA STATUS(16) command which is the 16 byte variant. In the +absence of the \fI\-\-16\fR or the \fI\-\-32\fR options the SCSI GET LBA +STATUS(16) command is sent. If both \fI\-\-16\fR and the \fI\-\-32\fR options +are given then the GET LBA STATUS(16) command is sent. +.TP +\fB\-T\fR, \fB\-\-32\fR +send SCSI GET LBA STATUS(32) command which is the 32 byte variant. When +given together with the \fI\-\-16\fR option then this option is ignored (so +the GET LBA STATUS(16) command is sent). +.TP +\fB\-b\fR, \fB\-\-brief\fR +when use once then one LBA status descriptor per line is output to stdout. +Each line has this +format: "0x 0x +". So the descriptor's starting LBA and number of blocks +are output in hex while the provisioning status and additional status are +in decimal. When used twice (e.g. '\-bb' or '\-\-brief \-\-brief') then the +provisioning status of the given \fILBA\fR (or LBA 0 if the \fI\-\-lba\fR +option is not given) is output to stdout. A check is made that the given +\fILBA\fR lies in the range of the first returned LBA status descriptor (as +it should according to SBC\-3 revision 20) and warnings are sent to stderr +if it doesn't. +.TP +\fB\-e\fR, \fB\-\-element\-id\fR=\fIEI\fR +where \fIEI\fR is the element identifier of the physical element for which +the LBAs shall be reported based on the value in the report type field (i.e. +\fIRT\fR). This option is only active with the SCSI GET LBA STATUS(32) +command (i.e. it is ignored if the GET LBA STATUS(16) command is sent). +.br +Valid element identifiers are non\-zero. The default value of \fIEI\fR is 0 +which means in the context that no element identifier is specified. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output response to this command in ASCII hex. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the starting Logical Block Address (LBA) to check the +provisioning status for. Note that the \fIDEVICE\fR chooses how many +following blocks that it will return provisioning status for. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in +the cdb's "allocation length" field. If not given then 24 is used. 24 is +enough space for the response header and one LBA status descriptor. +\fILEN\fR should be 8 plus a multiple of 16 (e.g. 24, 40, and 56 are suitable). +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response in binary (to stdout). +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-t\fR, \fB\-\-report\-type\fR=\fIRT\fR +where \fIRT\fR is 0 for report all LBAs; 1 for report LBAs using non\-zero +provisioning status; 2 for report LBAs that are mapped; 3 for report LBAs +that are de\-allocated; 4 for report LBAs that are anchored; 16 for report +LBAs that may return an unrecovered error. The REPORT TYPE field was added +to the GET LBA STATUS cdb in sbc4r12. +.br +Since the REPORT TYPE field is newer than the command, the response contains +the RTP bit to indicate whether or not the \fIDEVICE\fR acts on the REPORT +TYE field (set when it does act on it, clear otherwise). +.TP +\fB\-s\fR, \fB\-\-scan\-len\fR=\fISL\fR +where \fISL\fR is the scan length which is the maximum number of contiguous +logical blocks to be scanned for logical blocks that meet the given report +type (i.e. \fIRT\fR). This option is only active with the SCSI GET LBA +STATUS(32) command (i.e. it is ignored if the GET LBA STATUS(16) command is +sent). +.br +The default value of \fISL\fR is 0 which should be interpreted by the +\fIDEVICE\fR as there is no limits to the number of LBAs that shall be +scanned. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). Additional output +caused by this option is sent to stderr. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +In SBC\-3 revision 25 the calculation associated with the Parameter Data +Length field in the response was modified. Prior to that the byte offset +was 8 and in revision 25 it was changed to 4. +.PP +For a discussion of logical block provisioning see section 4.7 of sbc4r14.pdf +at http://www.t10.org (or the corresponding section of a later draft). +.SH EXIT STATUS +The exit status of sg_get_lba_status is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2009\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_write_same(8), sg_unmap(8) diff --git a/doc/sg_ident.8 b/doc/sg_ident.8 new file mode 100644 index 0000000..76e0b31 --- /dev/null +++ b/doc/sg_ident.8 @@ -0,0 +1,119 @@ +.TH SG_IDENT "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_ident \- send SCSI REPORT/SET IDENTIFYING INFORMATION command +.SH SYNOPSIS +.B sg_ident +[\fI\-\-ascii\fR] [\fI\-\-clear\fR] [\fI\-\-help\fR] [\fI\-\-itype=IT\fR] +[\fI\-\-raw\fR] [\fI\-\-set\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI REPORT IDENTIFYING INFORMATION or SET IDENTIFYING INFORMATION +command to \fIDEVICE\fR. Prior to SPC\-4 (revision 7) these +commands were called REPORT DEVICE IDENTIFIER and SET DEVICE IDENTIFIER +respectively. SCSI devices that support these two commands allow users +to write (set) identifying information and report it back at some +later time. The information is persistent (i.e. stored on some +non\-volatile medium within the SCSI device that will survive a power +outage). +.PP +Typically the space allocated for the information is limited: +SPC\-4 (revision 7) states that for information type 0, the minimum +length is 64 bytes and the maximum is 512 bytes. For other information +types (1 to 126 inclusive) the maximum length is 256 bytes. Also +information types 1 to 126 (inclusive) should contain a null +terminated UTF\-8 string. The author has seen older disks that only +support 16 bytes. +.PP +The default action when no options are given is to invoke the +Report Identifying Information command with the information type defaulting +to zero. Error reports are sent to stderr. By default the information is +shown in ASCII\-HEX (up to 16 bytes per line) with an ASCII representation +to the right with dots replacing non printable characters. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-A\fR, \fB\-\-ascii\fR +invokes the Report Identifying Information command and if anything is +found interprets it as ASCII (or UTF\-8 which is locale dependent) and +prints the information to stdout. +.TP +\fB\-C\fR, \fB\-\-clear\fR +invokes the Set Identifying Information command with an information length +of zero. This has the effect of clearing the existing information. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-itype\fR=\fIIT\fR +where \fIIT\fR is the information type. Defaults to zero. The maximum value +is 127 which is special and cannot be used with \fI\-\-set\fR or +\fI\-\-clear\fR. The information type of 127 (if supported) causes the REPORT +IDENTIFYING INFORMATION command to respond with a list of available +information types and their maximum lengths in bytes. The odd numbered +information types between 3 and 125 (inclusive) are not to be used (as they +clash with the SCC\-2 standard). +.TP +\fB\-r\fR, \fB\-\-raw\fR +invokes the Report Identifying information command and if anything +is found sends the information (which may be binary) to stdout. Nothing else +is sent to stdout however error reports, if any, are sent to stderr. +.TP +\fB\-S\fR, \fB\-\-set\fR +first reads stdin until an EOF is detected then invokes the Set Identifying +Information command to set what has been fetched from stdin as the +information. The amount of data read must be between 1 and 512 bytes +length (inclusive). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.PP +This utility permits users to write their own identifying information to +their SCSI devices. There are several other types of descriptors (or +designators) that the user cannot change. These include the SCSI INQUIRY +command with its standard vendor and product identification strings and the +product revision level; plus the large amount of information provided by +the "Device Identification" VPD page (see sg_vpd). There is also the READ +MEDIA SERIAL NUMBER command (see sg_rmsn). The MMC\-4 command set for CD +and DVDs has a "media serial number" feature (0x109) [and a "logical unit +serial number" feature]. These can be viewed with the sg_get_config utility. +.SH EXAMPLES +First, to see if there is an existing information whose format +is unknown (for information type 0), use no options: +.PP + # sg_ident /dev/sdb +.br + 00 31 32 33 34 35 36 37 38 39 30 1234567890 +.PP +If it is ASCII then it can printed as such: +.PP + # sg_ident \-\-ascii /dev/sdb +.br + 1234567890 +.PP +The information can be copied to a file, cleared and then +re\-asserted with this sequence: +.PP + # sg_ident \-\-raw /dev/sdb > t +.br + # sg_ident \-\-clear /dev/sdb +.br + # cat t | sg_ident \-\-set /dev/sdb +.SH EXIT STATUS +The exit status of sg_ident is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2005\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd(sg3_utils), sg_rmsn(sg3_utils), sg_get_config(sg3_utils) diff --git a/doc/sg_inq.8 b/doc/sg_inq.8 new file mode 100644 index 0000000..13c4191 --- /dev/null +++ b/doc/sg_inq.8 @@ -0,0 +1,517 @@ +.TH SG_INQ "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_inq \- issue SCSI INQUIRY command and/or decode its response +.SH SYNOPSIS +.B sg_inq +[\fI\-\-ata\fR] [\fI\-\-block=0|1\fR] [\fI\-\-cmddt\fR] +[\fI\-\-descriptors\fR] [\fI\-\-export\fR] [\fI\-\-extended\fR] +[\fI\-\-force\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-id\fR] +[\fI\-\-inhex=FN\fR] [\fI\-\-len=LEN\fR] [\fI\-\-long\fR] +[\fI\-\-maxlen=LEN\fR] [\fI\-\-only\fR] [\fI\-\-page=PG\fR] [\fI\-\-raw\fR] +[\fI\-\-vendor\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-vpd\fR] +\fIDEVICE\fR +.PP +.B sg_inq +[\fI\-36\fR] [\fI\-a\fR] [\fI\-A\fR] [\fI\-b\fR] [\fI\-\-B=0|1\fR] +[\fI\-c\fR] [\fI\-cl\fR] [\fI\-d\fR] [\fI\-e\fR] [\fI\-f\fR] +[\fI\-h\fR] [\fI\-H\fR] [\fI\-i\fR] [\fI\-I=FN\fR] [\fI\-l=LEN\fR] +[\fI\-L\fR] [\fI\-m\fR] [\fI\-M\fR] [\fI\-o\fR] [\fI\-p=VPD_PG\fR] +[\fI\-P\fR] [\fI\-r\fR] [\fI\-s\fR] [\fI\-u\fR] [\fI\-v\fR] +[\fI\-V\fR] [\fI\-x\fR] [\fI\-36\fR] [\fI\-?\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility, when \fIDEVICE\fR is given, sends a SCSI INQUIRY command to it +then outputs the response. All SCSI devices are meant to respond to +a "standard" INQUIRY command with at least a 36 byte response (in SCSI 2 and +higher). An INQUIRY is termed as "standard" when both the EVPD and CmdDt (now +obsolete) bits are clear. +.PP +Alternatively the \fI\-\-inhex=FN\fR option can be given. In this case +\fIFN\fR is assumed to be a file name ('\-' for stdin) containing ASCII +hexadecimal representing an INQUIRY response. +.PP +This utility supports two command line syntaxes. The preferred one is shown +first in the synopsis and is described in the main OPTIONS section. A later +section titled OLDER COMMAND LINE OPTIONS describes the second group of +options. +.PP +An important "non\-standard" INQUIRY page is the Device Identification +Vital Product Data (VPD) page [0x83]. Since SPC\-3, support for this page +is mandatory. The \fI\-\-id\fR option decodes this page. New VPD page +information is no longer being added to this utility. To get information +on new VPD pages see the sg_vpd(8) or sdparm(8) utilities. +.PP +In Linux, if the \fIDEVICE\fR exists and the SCSI INQUIRY fails (e.g. because +the SG_IO ioctl is not supported) then an ATA IDENTIFY (PACKET) DEVICE is +tried. If it succeeds then device identification strings are output. The +\fI\-\-raw\fR and \fI\-\-hex\fR options can be used to manipulate the output. +If the \fI\-\-ata\fR option is given then the SCSI INQUIRY is not performed +and the \fIDEVICE\fR is assumed to be ATA (or ATAPI). For more information +see the ATA DEVICES section below. +.PP +In some operating systems a NVMe device (e.g. SSD) may be given as the +\fIDEVICE\fR. For more information see the NVME DEVICES section below. +.PP +The reference document used for interpreting an INQUIRY is T10/BSR INCITS +502 Revision 19 which is draft SPC\-5 revision 19, 14 February 2018). It can +be found at http://www.t10.org . Obsolete and reserved items in the standard +INQUIRY response output are displayed in square brackets. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-a\fR, \fB\-\-ata\fR +Assume given \fIDEVICE\fR is an ATA or ATAPI device which can receive ATA +commands from the host operating system. Skip the SCSI INQUIRY command and +use either the ATA IDENTIFY DEVICE command (for non\-packet devices) or the +ATA IDENTIFY PACKET DEVICE command. To show the response in hex, add +a '\-\-verbose' option. This option is only available in Linux. +.TP +\fB\-B\fR, \fB\-\-block\fR=\fI0|1\fR +this option controls how the file handle to the \fIDEVICE\fR is opened. If +this argument is 0 then the open is non\-blocking. If the argument is 1 then +the open is blocking. In Unix a non\-blocking open is indicated by a +O_NONBLOCK flag while a blocking open is indicated by the absence of that +flag. The default value depends on the operating system and the type of +\fIDEVICE\fR node. For Linux pass\-throughs (i.e. the sg and bsg drivers) +the default is 0. +.TP +\fB\-c\fR, \fB\-\-cmddt\fR +set the Command Support Data (CmdDt) bit (defaults to clear(0)). Used in +conjunction with the \fI\-\-page=PG\fR option where \fIPG\fR specifies the +SCSI command opcode to query. When used twice (e.g. '\-cc') this utility +forms a list by looping over all 256 opcodes (0 to 255 inclusive) only +outputting a line for commands that are found. The CmdDt bit is now +obsolete; it has been replaced by the REPORT SUPPORTED OPERATION CODES +command, see the sg_opcodes(8) utility. +.TP +\fB\-d\fR, \fB\-\-descriptors\fR +decodes and prints the version descriptors found in a standard INQUIRY +response. There are up to 8 of them. Version descriptors indicate which +versions of standards and/or drafts the \fIDEVICE\fR complies with. The +normal components of a standard INQUIRY are output (typically from +the first 36 bytes of the response) followed by the version descriptors +if any. +.TP +\fB\-e\fR +see entry below for \fI\-\-vpd\fR. +.TP +\fB\-f\fR, \fB\-\-force\fR +As a sanity check, the normal action when fetching VPD pages other than +page 0x0 (the "Supported VPD pages" VPD page), is to first fetch page 0x0 +and only if the requested page is one of the supported pages, to go ahead +and fetch the requested page. +.br +When this option is given, skip checking of VPD page 0x0 before accessing +the requested VPD page. The prior check of VPD page 0x0 is known to +crash certain USB devices, so use with care. +.TP +\fB\-u\fR, \fB\-\-export\fR +prints out information obtained from the device. The output can be +modified by selecting a VPD page with \fIPG\fR (from +\fI\-\-page=PG\fR). If the device identification VPD page 0x83 is +given it prints out information in the form: +"SCSI_IDENT__=" to stdout. If the device serial +number VPD page 0x80 is given it prints out information in the form: +"SCSI_SERIAL=". Other VPD pages are not supported. If no VPD +page is given it prints out information in the form: +"SCSI_VENDOR=", "SCSI_MODEL=", and +"SCSI_REVISION=", taken from the standard inquiry. This may be +useful for tools like udev(7) in Linux. +.TP +\fB\-E\fR, \fB\-x\fR, \fB\-\-extended\fR +prints the extended INQUIRY VPD page [0x86]. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. When used twice, after the +usage message, there is a list of available abbreviations than can be +given to the \fI\-\-page=PG\fR option. +.TP +\fB\-H\fR, \fB\-\-hex\fR +rather than decode a standard INQUIRY response, a VPD page or command +support data; print out the response in hex and send the output to stdout. +Error messages and warnings are typically output to stderr. When used twice +with the ATA Information VPD page [0x89] decodes the start of the response +then outputs the ATA IDENTIFY (PACKET) DEVICE response in hexadecimal +bytes (not 16 bit words). When used three times with the ATA Information VPD +page [0x89] or the \fI\-\-ata\fR option, this utility outputs the ATA +IDENTIFY (PACKET) DEVICE response in hexadecimal words suitable for input +to 'hdparm \-\-Istdin'. See note below. +.br +To generate output suitable for placing in a file that can be used by a +later invocation with the \fI\-\-inhex=FN\fR option, use the '\-HHHH' +option (e.g. 'sg_inq \-p di \-HHHH /dev/sg3 > dev_id.hex'). +.TP +\fB\-i\fR, \fB\-\-id\fR +prints the device identification VPD page [0x83]. +.TP +\fB\-I\fR, \fB\-\-inhex\fR=\fIFN\fR +\fIFN\fR is expected to be a file name (or '\-' for stdin) which contains +ASCII hexadecimal or binary representing an INQUIRY (including VPD page) +response. This utility will then decode that response. It is preferable to +also supply the \fI\-\-page=PG\fR option, if not this utility will attempt +to guess which VPD page (or standard INQUIRY) that the response is associated +with. The hexadecimal should be arranged as 1 or 2 digits representing a +byte each of which is whitespace or comma separated. Anything from and +including a hash mark to the end of a line is ignored. If the \fI\-\-raw\fR +option is also given then \fIFN\fR is treated as binary. +.TP +\fB\-l\fR, \fB\-\-len\fR=\fILEN\fR +the number \fILEN\fR is the "allocation length" field in the INQUIRY cdb. +This is the (maximum) length of the response returned by the device. The +default value of \fILEN\fR is 0 which is interpreted as: first request is +for 36 bytes and if necessary execute another INQUIRY if the "additional +length" field in the response indicates that more than 36 bytes is available. +.br +If \fILEN\fR is greater than 0 then only one INQUIRY command is performed. +This means that the Serial Number (obtained from the Serial Number VPD +pgae (0x80)) is not fetched and therefore not printed. +See the NOTES section below about "36 byte INQUIRYs". +.TP +\fB\-L\fR, \fB\-\-long\fR +this option causes more information to be decoded from the Identify command +sent to a NVMe \fIDEVICE\fR. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +this option has the same action as the \fI\-\-len=LEN\fR option above. It has +been added for compatibility with the sg_vpd, sg_modes and sg_logs utilities. +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option on the command line. +.TP +\fB\-o\fR, \fB\-\-only\fR +Do not attempt to additionally retrieve the serial number VPD page (0x80) to +enhance the output of a standard INQUIRY. So with this option given and no +others, this utility will send a standard INQUIRY SCSI command and decode +its response. No other SCSI commands will be sent to the \fIDEVICE\fR. +Without this option an additional SCSI command is sent: a (non\-standard) +SCSI INQUIRY to fetch the Serial Number VPD page. However the Serial +Number VPD page is not mandatory (while the Device Identification page is +mandatory but a billion USB keys ignore that) and may cause nuisance error +reports. +.br +For NVMe devices only the Identify controller is performed, even if the +\fIDEVICE\fR includes a namespace identifier. For example in FreeBSD +given a \fIDEVICE\fR named /dev/nvme0ns1 then an Identify controller is +sent to /dev/nvme0 and nothing is sent to its "ns1" (first namespace). +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR +the \fIPG\fR argument can be either a number of an abbreviation for a VPD +page. To enumerate the available abbreviations for VPD pages use '\-hh' or +a bad abbreviation (e.g, '\-\-page=xxx'). When the \fI\-\-cmddt\fR option is +given (once) then \fIPG\fR is interpreted as an opcode number (so VPD page +abbreviations make little sense). +.br +If \fIPG\fR is a negative number, then a standard INQUIRY is performed. This +can be used to override some guessing logic associated with the +\fI\-\-inhex=FN\fR option. +.br +If \fIPG\fR is not found in the 'Supported VPD pages' VPD page (0x0) then +EDOM is returned. To bypass this check use the \fI\-\-force\fR option. +.TP +\fB\-r\fR, \fB\-\-raw\fR +in the absence of \fI\-\-inhex=FN\fR then the output response is in binary. +The output should be piped to a file or another utility when this option is +used. The binary is sent to stdout, and errors are sent to stderr. +.br +If used with \fI\-\-inhex=FN\fR then the contents of \fIFN\fR is treated as +binary. +.TP +\fB\-s\fR, \fB\-\-vendor\fR +output a standard INQUIRY response's vendor specific fields from offset 36 +to 55 in ASCII. When used twice (i.e. '\-ss') also output the vendor +specific field from offset 96 in ASCII. This is only done if the data +passes some simple sanity checks. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.TP +\fB\-e\fR, \fB\-\-vpd\fR +set the Enable Vital Product Data (EVPD) bit (defaults to clear(0)). Used in +conjunction with the \fI\-\-page=PG\fR option where \fIPG\fR specifies the +VPD page number to query. If the \fI\-\-page=PG\fR is not given then \fIPG\fR +defaults to zero which is the "Supported VPD pages" VPD page. A more up to +date decoding of VPD pages can be found in the sg_vpd(8) utility. +.SH NOTES +Some devices with weak SCSI command set implementations lock up when they +receive commands they don't understand (and some lock up if they receive +response lengths that they don't expect). Such devices need to be treated +carefully, use the '\-\-len=36' option. Without this option this utility will +issue an initial standard INQUIRY requesting 36 bytes of response data. If +the device indicates it could have supplied more data then a second INQUIRY +is issued to fetch the longer response. That second command may lock up +faulty devices. +.PP +ATA or ATAPI devices that use a SCSI to ATA Translation layer (see +SAT at www.t10.org) may support the SCSI ATA INFORMATION VPD page. This +returns the IDENTIFY (PACKET) DEVICE response amongst other things. +The ATA Information VPD page can be fetched with '\-\-page=ai'. +.PP +In the INQUIRY standard response there is a 'MultiP' flag which is set +when the device has 2 or more ports. Some vendors use the preceding +vendor specific ('VS') bit to indicate which port is being accessed by +the INQUIRY command (0 \-> relative port 1 (port "a"), 1 \-> relative +port 2 (port "b")). When the 'MultiP' flag is set, the preceding vendor +specific bit is shown in parentheses. SPC\-3 compliant devices should +use the device identification VPD page (0x83) to show which port is +being used for access and the SCSI ports VPD page (0x88) to show all +available ports on the device. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be +a SCSI generic (sg) device. In the 2.6 series and later block devices (e.g. +disks and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda" +will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char" +device names may be used as well (e.g. "/dev/st0m"). +.PP +The number of bytes output by \fI\-\-hex\fR and \fI\-\-raw\fR is 36 bytes +or the number given to \fI\-\-len=LEN\fR (or \fI\-\-maxlen=LEN\fR). That +number is reduced if the "resid" returned by the HBA indicates less bytes +were sent back from \fIDEVICE\fR. +.PP +The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the +O_RDONLY flag). +.SH ATA DEVICES +There are two major types of ATA devices: non\-packet devices (e.g. ATA +disks) and packet devices (ATAPI). The majority of ATAPI devices are +CD/DVD/BD drives in which the ATAPI transport carries the MMC set (i.e. +a SCSI command set). Further, both types of ATA devices can be connected +to a host computer via a "SCSI" (or some other) transport. When an +ATA disk is controlled via a SCSI (or non\-ATA) transport then two +approaches are commonly used: tunnelling (e.g. STP in Serial Attached +SCSI (SAS)) or by emulating a SCSI device (e.g. with a SCSI to +ATA translation layer, see SAT at www.t10.org ). Even when the +physical transport to the host computer is ATA (especially in the +case of SATA) the operating system may choose to put a SAT +layer in the driver "stack" (e.g. libata in Linux). +.PP +The main identifying command for any SCSI device is an INQUIRY. The +corresponding command for an ATA non\-packet device is IDENTIFY DEVICE +while for an ATA packet device it is IDENTIFY PACKET DEVICE. +.PP +When this utility is invoked for an ATAPI device (e.g. a CD/DVD/BD +drive with "sg_inq /dev/hdc") then a SCSI INQUIRY is sent to the +device and if it responds then the response to decoded and output and +this utility exits. To see the response for an ATA IDENTIFY PACKET +DEVICE command add the \fI\-\-ata\fR option (e.g. "sg_inq \-\-ata /dev/hdc). +.PP +This utility doesn't decode the response to an ATA IDENTIFY (PACKET) +DEVICE command, hdparm does a good job at that. The '\-HHH' option has +been added for use with either the '\-\-ata' or '\-\-page=ai' +option to produce a format acceptable to "hdparm \-\-Istdin". +An example: 'sg_inq \-\-ata \-HHH /dev/hdc | hdparm \-\-Istdin'. See hdparm. +.SH NVME DEVICES +Currently these device are typically SSDs (Solid State Disks) directly +connected to a PCIe connector or via a specialized connector such as a M2 +connector. Linux and FreeBSD treat NVMe storage devices as separate from +SCSI storage with device names like /dev/nvme0n1 (in Linux) and +/dev/nvme0ns1 (in FreeBSD). The NVM Express group has a document titled "NVM +Express: SCSI Translation Reference" which defines a partial "SCSI to NVMe +Translation Layer" often known by its acronym: SNTL. +.PP +On operating systems where it is supported by this package, this utility +will detect NVMe storage devices directly connected and send an Identify +controller NVMe Admin command and decode its response. A NVMe controller +is architecturally similar to a SCSI target device. If the NVMe \fIDEVICE\fR +indicates a namespace then an Identify namespace NVMe Admin command is sent +to that namespace and its response is decoded. Namespaces are numbered +sequentially starting from 1. Namespaces are similar to SCSI Logical Units +and their identifiers (nsid_s) can be thought of as SCSI LUNs. In the +Linux and FreeBSD example device names above the "n1" and the "ns1" parts +indicate nsid 1 . If no namespace is given in the \fIDEVICE\fR then all +namespaces found in the controller are sent Identify namespace commands and +the responses are decoded. +.PP +To get more details in the response use the \fI\-\-long\fR option. To only +get the controller's Identify decoded use the \fI\-\-only\fR option. +.PP +It is possible that even though the \fIDEVICE\fR presents as a NVMe device, +it has a SNTL and accepts SCSI commands. In this case to send a SCSI INQUIRY +command (and fetch its VPD pages) use 'sg_vpd \-p sinq ' (or to get VPD +pages: 'sg_vpd \-p '). +.SH EXIT STATUS +The exit status of sg_inq is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-36\fR +only requests 36 bytes of response data for an INQUIRY. Furthermore even +if the device indicates in its response it can supply more data, a +second (longer) INQUIRY is not performed. This is a paranoid setting. +Equivalent to '\-\-len=36' in the OPTIONS section. +.TP +\fB\-a\fR +fetch the ATA Information VPD page [0x89]. Equivalent to '\-\-page=ai' in +the OPTIONS section. This page is defined in SAT (see at www.t10.org). +.TP +\fB\-A\fR +Assume given \fIDEVICE\fR is an ATA or ATAPI device. +Equivalent to \fI\-\-ata\fR in the OPTIONS section. +.TP +\fB\-b\fR +decodes the Block Limits VPD page [0xb0]. Equivalent to '\-\-page=bl' in +the OPTIONS section. This page is defined in SBC\-2 (see www.t10.org) and +later. +.TP +\fB\-B\fR=\fI0|1\fR +equivalent to \fI\-\-block=0|1\fR in OPTIONS section. +.TP +\fB\-c\fR +set the Command Support Data (CmdDt) bit (defaults to clear(0)). Used in +conjunction with the \fI\-p=VPD_PG\fR option to specify the SCSI command +opcode to query. Equivalent to \fI\-\-cmddt\fR in the OPTIONS section. +.TP +\fB\-cl\fR +lists the command data for all supported commands (followed by the command +name) by looping through all 256 opcodes. This option uses the CmdDt bit +which is now obsolete. See the sg_opcodes(8) utility. +Equivalent to '\-\-cmddt \-\-cmddt' in the OPTIONS section. +.TP +\fB\-d\fR +decodes depending on context. If \fI\-e\fR option is given, or any option +that implies \fI\-e\fR (e.g. '\-i' or '\-p=80'), then this utility attempts +to decode the indicated VPD page. Otherwise the version descriptors (if any) +are listed following a standard INQUIRY response. In the version descriptors +sense, equivalent to \fI\-\-descriptors\fR in the OPTIONS section. +.TP +\fB\-e\fR +enable (i.e. sets) the Vital Product Data (EVPD) bit (defaults to clear(0)). +Used in conjunction with the \fI\-p=VPD_PG\fR option to specify the VPD page +to fetch. If \fI\-p=VPD_PG\fR is not given then VPD page 0 (list supported +VPD pages) is assumed. +.TP +\fB\-f\fR +Equivalent to \fI\-\-force\fR in the OPTIONS section. +.TP +\fB\-h\fR +outputs INQUIRY response in hex rather than trying to decode it. +Equivalent to \fI\-\-hex\fR in the OPTIONS section. +.TP +\fB\-H\fR +same action as \fI\-h\fR. +Equivalent to \fI\-\-hex\fR in the OPTIONS section. +.TP +\fB\-i\fR +decodes the Device Identification VPD page [0x83]. Equivalent to \fI\-\-id\fR +in the OPTIONS section. This page is made up of several "designation +descriptors". If \fI\-h\fR is given then each descriptor header is decoded +and the identifier itself is output in hex. To see the whole VPD 0x83 page +response in hex use '\-p=83 \-h'. +.TP +\fB\-I\fR=\fIFN\fR +equivalent to \fI\-\-inhex=FN\fR in the OPTIONS section. +.TP +\fB\-l\fR=\fILEN\fR +equivalent to \fI\-\-len=LEN\fR in the OPTIONS section. +.TP +\fB\-L\fR +equivalent to \fI\-\-long\fR in the OPTIONS section. +.TP +\fB\-m\fR +decodes the Management network addresses VPD page [0x85]. Equivalent +to '\-\-page=mna' in the OPTIONS section. +.TP +\fB\-M\fR +decodes the Mode page policy VPD page [0x87]. Equivalent to '\-\-page=mpp' +in the OPTIONS section. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-o\fR +equivalent to \fI\-\-only\fR in the OPTIONS section. +.TP +\fB\-p\fR=\fIVPD_PG\fR +used in conjunction with the \fI\-e\fR or \fI\-c\fR option. If neither given +then the \fI\-e\fR option assumed. When the \fI\-e\fR option is also +given (or assumed) then the argument to this option is the VPD page number. +The argument is interpreted as hexadecimal and is expected to be in the range +0 to ff inclusive. Only VPD page 0 is decoded and it lists supported VPD pages +and their names (if known). To decode the mandatory device identification +page (0x83) use the \fI\-i\fR option. A now obsolete usage is when the +\fI\-c\fR option is given in which case the argument to this option is assumed +to be a command opcode number. Recent SCSI draft standards have moved this +facility to a separate command (see sg_opcodes(8)). Defaults to 0 so if +\fI\-e\fR is given without this option then VPD page 0 is output. +.TP +\fB\-P\fR +decodes the Unit Path Report VPD page [0xc0] which is EMC specific. +Equivalent to '\-\-page=upr' in the OPTIONS section. +.TP +\fB\-r\fR +outputs the response in binary to stdout. Equivalent to \fI\-\-raw\fR in +the OPTIONS section. Can be used twice (i.e. '\-rr' (and '\-HHH' has +same effect)) and if used with the \fI\-A\fR or \fI\-a\fR option yields +output with the same format as "cat /proc/ide/hd/identify" so that it +can then be piped to "hdparm \-\-Istdin". +.TP +\fB\-s\fR +decodes the SCSI Ports VPD page [0x88]. +Equivalent to '\-\-page=sp' in the OPTIONS section. +.TP +\fB\-u\fR +equivalent to '\-\-export' in the OPTIONS section. +.TP +\fB\-v\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR +print out version string then exit. +.TP +\fB\-x\fR +decodes the Extended INQUIRY data VPD [0x86] page. +Equivalent to '\-\-page=ei' in the OPTIONS section. +.TP +\fB\-?\fR +output usage message and exit. Ignore all other parameters. +.SH EXAMPLES +The examples in this page use Linux device names. For suitable device +names in other supported Operating Systems see the sg3_utils(8) man page. +.PP +To view the standard inquiry response use without options: +.PP + sg_inq /dev/sda +.PP +Some SCSI devices include version descriptors indicating the various +SCSI standards and drafts they support. They can be viewed with: +.PP + sg_inq \-d /dev/sda +.PP +Modern SCSI devices include Vital Product Data (VPD)pages which can be +viewed with the SCSI INQUIRY command. To list the supported VPD +pages (but not their contents) try: +.PP + sg_inq \-e /dev/sda +.PP +Some VPD pages can be read with the sg_inq utility but a newer utility +called sg_vpd specializes in showing their contents. The sdparm utility +can also be used to show the contents of VPD pages. +.PP +Further examples of sg_inq together with some typical output can be found +on http://sg.danny.cz/sg/sg3_utils.html web page. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2001\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_opcodes(8), sg_vpd(8), sg_logs(8), sg_modes(8), sdparm(8), hdparm(8), +.B sgdiag(scsirastools) diff --git a/doc/sg_logs.8 b/doc/sg_logs.8 new file mode 100644 index 0000000..af07a2a --- /dev/null +++ b/doc/sg_logs.8 @@ -0,0 +1,486 @@ +.TH SG_LOGS "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_logs \- access log pages with SCSI LOG SENSE command +.SH SYNOPSIS +.B sg_logs +[\fI\-\-All\fR] [\fI\-\-all\fR] [\fI\-\-brief\fR] [\fI\-\-filter=FL\fR] +[\fI\-\-hex\fR] [\fI\-\-list\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-name\fR] +[\fI\-\-no_inq\fR] [\fI\-\-page=PG\fR] [\fI\-\-paramp=PP\fR] [\fI\-\-pcb\fR] +[\fI\-\-ppc\fR] [\fI\-\-pdt=DT\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] +[\fI\-\-sp\fR] [\fI\-\-temperature\fR] [\fI\-\-transport\fR] +[\fI\-\-vendor=VP\fR] [\fI\-\-verbose\fR] \fIDEVICE\fR +.PP +.B sg_logs +[\fI\-\-brief\fR] [\fI\-\-filter=FL\fR] [\fI\-\-hex\fR] \fI\-\-in=FN\fR +[\fI\-\-name\fR] [\fI\-\-pdt=DT\fR] [\fI\-\-raw\fR] [\fI\-\-vendor=VP\fR] +.PP +.B sg_logs +[\fI\-\-control=PC\fR] [\fI\-\-in=FN\fR] [\fI\-\-page=PG\fR] [\fI\-\-raw\fR] +[\fI\-\-reset\fR] \fI\-\-select\fR [\fI\-\-sp\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR +.PP +.B sg_logs +[\fI\-\-enumerate\fR] [\fI\-\-filter=FL\fR] [\fI\-\-help\fR] +[\fI\-\-vendor=VP\fR] [\fI\-\-version\fR] +.PP +.B sg_logs +[\fI\-a\fR] [\fI\-A\fR] [\fI\-b\fR] [\fI\-D=DT\fR] [\fI\-c=PC\fR] [\fI\-e\fR] +[\fI\-f=FL\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-i=FN\fR] [\fI\-l\fR] [\fI\-L\fR] +[\fI\-m=LEN\fR] [\fI\-M=VP\fR] [\fI\-n\fR] [\fI\-p=PG\fR] [\fI\-paramp=PP\fR] +[\fI\-pcb\fR] [\fI\-ppc\fR] [\fI\-r\fR] [\fI\-R\fR] [\fI\-select\fR] +[\fI\-sp\fR] [\fI\-t\fR] [\fI\-T\fR] [\fI\-v\fR] [\fI\-V\fR] [\fI\-?\fR] +[\fI\-x\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends a SCSI LOG SENSE command to the \fIDEVICE\fR and then +outputs the response. The LOG SENSE command is used to fetch log pages which, +if known, are decoded by default. When the \fI\-\-reset\fR and/or +\fI\-\-select\fR option is given then a SCSI LOG SELECT command is issued +to the \fIDEVICE\fR. Alternatively one or more log page responses can be in +a file read using the \fI\-\-in=FN\fR option; in this case those responses +are decoded and the \fIDEVICE\fR argument, if given, is ignored. +.PP +In SPC\-4 revision 5 a subpage code was introduced to both the LOG SENSE and +LOG SELECT command. At the same time a page code field was introduced to the +to the LOG SELECT command. The log subpage code can range from 0 to 255 (0xff) +inclusive. The subpage code value 255 can be thought of as a wildcard. +.PP +The SYNOPSIS section above is divided into five forms. The first form +shows the options that can be used to send a LOG SENSE command to the +\fIDEVICE\fR and decode its response. The second form fetches data from a +file (named \fIFN\fR) and decodes it as if it were a response from a LOG +SENSE command. The third form shows the options that can be used to send a +LOG SELECT command. The fourth form groups various management options. +The last form shows the older, deprecated command line interface which is +maintained for backward compatibility. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-A\fR, \fB\-\-All\fR +fetch and decode all the log pages and subpages supported by the \fIDEVICE\fR. +This requires a two stage process: first the "supported log pages and +subpages" log page is fetched, then for each entry in its response, the +corresponding log page (or subpage) is fetched and displayed. Note that there +are many SCSI devices that do not support LOG SENSE subpages and respond +to this option with an illegal request sense key (or ignored the subpage +field). +.TP +\fB\-a\fR, \fB\-\-all\fR +outputs all the log pages supported by the \fIDEVICE\fR. This requires a two +stage process: first the "supported log pages" log page is fetched, then for +each entry in its response, the corresponding log page is fetched and +displayed. When used twice (e.g. '\-aa') all log pages and subpages are +fetched. +.TP +\fB\-b\fR, \fB\-\-brief\fR +shorten the amount of output for some log pages. For example the Tape +Alert log page only outputs parameters whose flags are set when +\fI\-\-brief\fR is given. +.TP +\fB\-c\fR, \fB\-\-control\fR=\fIPC\fR +accepts 0, 1, 2 or 3 for the \fIPC\fR argument: +.br + \fB0\fR : current threshold values +.br + \fB1\fR : current cumulative values +.br + \fB2\fR : default threshold values +.br + \fB3\fR : default cumulative values +.br +The default value is 1 (i.e. current cumulative values). +.TP +\fB\-e\fR, \fB\-\-enumerate\fR +this option is used to output information held in internal tables about +known log pages including their name, acronym and fields. If given, the +\fIDEVICE\fR argument is ignored. When given once (e.g. '\-e') all known +pages are listed, sorted in ascending acronym order (alphabetic). When +given twice, vendor pages are excluded. When given three times, all known +pages are listed, sorted in ascending numeric order listed; when given four +times, vendor pages are excluded from the numeric order. +.br +The \fI\-\-filter=FL\fR and \fI\-\-verbose\fR options reduce the output +of the enumeration. +.TP +\fB\-f\fR, \fB\-\-filter\fR=\fIFL\fR +\fIFL\fR is either a parameter code when \fIDEVICE\fR is given, or a +peripheral device type (pdt) (or other) if \fI\-\-enumerate\fR is given. +.br +In the parameter code case \fIFL\fR is a value between 0 and 65535 (0xffff) +and only the parameter section matching that code is output. If the +\fB\-\-hex\fR option is given the log parameter is output in hexadecimal +rather than decoding it. If the \fB\-\-hex\fR option is used twice then the +leading address on each line of hex is removed. If the \fB\-\-raw\fR option +is given then the log parameter is output in binary. Most log pages contain +one or more log parameters. Examples of those that don't follow that +convention are those pages that list supported log pages (and subpages). +.br +In the \fI\-\-enumerate\fR case, when \fIFL\fR >= zero it is taken as a +pdt value and only log pages associated with that pdt plus generic pages +listed in SPC are enumerated. If \fIFL\fR is \-1 then the filter does +nothing which is the same as not giving this option; when \fIFL\fR is \-2 +then only generic pages listed in SPC are enumerated. If \fIFL\fR is \-10 +then only generic direct access like (e.g. disk) pages are enumerated. If +\fIFL\fR is \-11 then only generic tape like pages (e.g. includes ADC) +are enumerated. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +The default action is to decode known log page numbers (and subpage numbers) +into text. When this option is used once, the response is output in +hexadecimal. When used twice, each line of hex has the ASCII equivalent shown +to the right. When used three times, the hex has no leading address nor +trailing ASCII making it suitable to be placed in a file (or piped). That +file might later be used by another invocation using the \fI\-\-in=FN\fR +option. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIFN\fR +This option may be used in two different contexts. One is with the +\fI\-\-select\fR to send a LOG SELECT command to the given \fIDEVICE\fR; +see the LOG SELECT section below. +.br +The other context is with no \fIDEVICE\fR argument given in which case +the contents of \fIFN\fR are decoded as if it were the response of a LOG +SENSE command (i.e. a log page). For decoding the page and subpage numbers +are taken from \fIFN\fR while the peripheral device type is either +generic (i.e. from SPC) or the value given by \fI\-\-pdt=DT\fR. +.br +\fIFN\fR is treated as a file name (or '\-' for stdin) which contains ASCII +hexadecimal or binary representing a log page. The hexadecimal should be +arranged as 1 or 2 digits representing a byte each of which is whitespace or +comma separated. Anything from and including a hash mark to the end of line +is ignored. If the \fI\-\-raw\fR option is also given then \fIFN\fR is +treated as binary. +.TP +\fB\-l\fR, \fB\-\-list\fR +lists the names of all logs sense pages supported by this device. This is +done by reading the "supported log pages" log page. When used +twice (e.g. '\-ll') lists the names of all logs sense pages and subpages +supported by this device. There is a list of common log page codes below. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +sets the "allocation length" field in the LOG SENSE cdb. The is the maximum +length in bytes that the response will be. Without this option (or \fILEN\fR +equal to 0) this utility first fetches the 4 byte response then does a second +access with the length indicated in the first (4 byte) response. Negative +values and 1 for \fILEN\fR are not accepted. \fILEN\fR cannot exceed +65535 (0xffff). Responses can be quite large (e.g. the background scan +results log page) and this option can be used to limit the amount of +information returned. +.TP +\fB\-n\fR, \fB\-\-name\fR +decode some log pages into 'name=value' entries, one per line. The name +contains no space and may be abbreviated and the value is decimal unless +prefixed by '0x'. Nesting is indicated by leading spaces. This form +is meant to be relatively easy to parse. +.TP +\fB\-x\fR, \fB\-\-no_inq\fR +suppresses the output of information obtained from an initial call to the +INQUIRY command for the standard response. The default (assuming some other +options that suppress this output are also not given) is to output several +device identification strings. +.br +If this option is given twice (or more) then no INQUIRY command is sent +hence there will be no device identification string output either. Also the +peripheral device type (PDT) field will not be obtained so this utility will +not be able to differentiate between some log pages that are device +dependent. It will assume a PDT of 0 (i.e. a disk). +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR +log page name/number to access. \fIPG\fR is either an acronym, a page number, +or a page, subpage number pair. Available acronyms can be listed with the +\fI\-\-enumerate\fR option. Page (0 to 63) and subpage (0 to 255) numbers +are comma separated. They are decimal unless a hexadecimal indication is +given. A hexadecimal number can be specified by a leading "0x" or a +trailing "h". +.br +A few acronyms specify a range of subpage values in which case the acronym +may be followed by a comma then a subpage number. This method can also be +used to fetch the Supported subpages log page (e.g. \-\-page=temp,0xff). +.TP +\fB\-P\fR, \fB\-\-paramp\fR=\fIPP\fR +\fIPP\fR is the parameter pointer value to place in a field of that name in +the LOG SENSE cdb. A decimal number in the range 0 to 65535 (0xffff) is +expected. When a value greater than 0 is given the \fI\-\-ppc\fR option +should be selected. The default value is 0. +.TP +\fB\-q\fR, \fB\-\-pcb\fR +show Parameter Control Byte settings (only relevant when log parameters +being output in ASCII). +.TP +\fB\-Q\fR, \fB\-\-ppc\fR +sets the Parameter Pointer Control (PPC) bit in the LOG SENSE cdb. Default +is 0 (i.e. cleared). This bit was made obsolete in SPC\-4 revision 18. +.TP +\fB\-D\fR, \fB\-\-pdt\fR=\fIDT\fR +\fIDT\fR is the peripheral device type that is used when it is not available +from the \fIDEVICE\fR. There are two main cases of this: with the +\fI\-\-pdt=DT\fR without a \fIDEVICE\fR and when \fI\-\-no_inq\fR is used +with a \fIDEVICE\fR. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the response in binary to stdout. Error messages and warnings are +output to stderr. +.br +This option may also be given together with \fI\-\-in=FN\fR in which case +the contents of \fIFN\fR are interpreted as binary data (and the response is +decoded as normal, not dumped as binary). +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). The +default action is to try and open \fIDEVICE\fR read\-write then if that +fails try to open again with read\-only. However when a read\-write open +succeeds there may still be unwanted actions on the close (e.g. some OSes +try to do a SYNCHRONIZE CACHE command). So this option forces a read\-only +open on \fIDEVICE\fR and if it fails, this utility will exit. Note that +options like \fI\-\-select\fR most likely need a read\-write open. +.TP +\fB\-R\fR, \fB\-\-reset\fR +use SCSI LOG SELECT command (with the PCR bit set) to reset the all log +pages (or the given page). Exactly what is reset depends on the accompanying +SP bit (i.e. \fI\-\-sp\fR option which defaults to 0) and the +\fIPC\fR ("page control") value (which defaults to 1). Supplying this option +implies the \fI\-\-select\fR option as well. This option seems to clear error +counter log pages but leaves pages like self\-test results, start\-stop cycle +counter and temperature log pages unaffected. This option may be required to +clear log pages if a counter reaches its maximum value since the log page in +which the counter is found will remain "stuck" at its maximum value until +some user interaction (e.g. calling sg_logs with this option). +.TP +\fB\-S\fR, \fB\-\-select\fR +use a LOG SELECT command. The default action (i.e. when neither this option +nor \fI\-\-reset\fR is given) is to do a LOG SENSE command. See the LOG +SELECT section. +.TP +\fB\-s\fR, \fB\-\-sp\fR +sets the Saving Parameters (SP) bit. Default is 0 (i.e. cleared). When set +this instructs the device to store the current log page parameters (as +indicated by the DS and TSD parameter codes) in some non\-volatile location. +Hence the log parameters will be preserved across power cycles. This option +is typically not needed, especially if the GLTSD flag is clear in the +control mode page as this instructs the device to periodically save all +saveable log parameters to non\-volatile locations. +.TP +\fB\-t\fR, \fB\-\-temperature\fR +outputs the temperature. First looks in the temperature log page and if +that is not available tries the Informational Exceptions log page which +may also have the current temperature (especially on older disks). +.TP +\fB\-T\fR, \fB\-\-transport\fR +outputs the transport ('Protocol specific port') log page. Equivalent to +setting '\-\-page=18h'. +.TP +\fB\-M\fR, \fB\-\-vendor\fR=\fIVP\fR +where \fIVP\fR is a vendor (e.g. "sea" for Seagate) or product (group) +acronym (e.g. "lto5" for the 5th generation LTO (tape) consortium). Either +the whole log page is vendor specific (e.g. page numbers 0x30 to 0x3f) or +part of a T10 defined log page is vendor specific. For example SPC\-5 +defines parameter code 0x0 of page 0x2f (the Informational Exceptions log +page) and states that the remaining parameter codes (i.e. 0x1 to 0xffff) +are vendor specific. Using a \fIVP\fR of "xxx" will list the available +acronyms. +.br +If this option is used with \fI\-\-page=PG\fR and \fIPG\fR is an acronym +then this option is ignored. If \fIPG\fR is a number (e.g. 0xc0) then +\fIVP\fR is used to choose the which vendor specific page (e.g. sharing +page number 0xc0) to decode. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. When used with \fI\-\-enumerate\fR, in the +list of known log page names, those that have no associated decode logic +are followed by "[hex only]". +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH LOG SELECT +The SCSI LOG SELECT command can be used to reset certain parameters to vendor +specific defaults, save them to non\-volatile storage (i.e. the media), or +supply new page contents. This command has changed between SPC\-3 and SPC\-4 +with the addition of the Page and Subpage Code fields which can only be +non zero when the Parameter list length is zero. +.PP +The \fI\-\-select\fR (or \fI\-\-reset\fR) option is required to issue a LOG +SELECT command. If the \fI\-\-in=FN\fR option is not given (or \fIFN\fR is +effectively empty) then the Parameter list length field is set to zero. If +the \fI\-\-in=FN\fR option is is given then its decoded data is placed in +the data\-out buffer and its length in bytes is placed in the Parameter list +length field. +.PP +Other options that are active with the LOG SELECT command are +\fI\-\-control=PC\fR, \fI\-\-reset\fR (which sets the PCR bit) and +\fI\-\-sp\fR. +.SH +APPLICATION CLIENT +This is the name of a log page that acts as a container for data provided +by the user. An application client is a SCSI term for the program that issues +commands to a SCSI initiator (often known as a Host Bus Adapter (HBA)). So, +for example, this utility is a SCSI application client. +.PP +The Application Client log page has 64 log parameters with parameters codes +0 to 63. Each can hold 252 bytes of user binary data. That 252 bytes (or +less) of user data, with a 4 byte prefix (for a total of 256 bytes) can be +provided with the \fI\-\-in=FN\fR option. A typical prefix would +be '0,n,83,fc'. The "n" is the parameter code in hex so the last log +parameter would be '0,3f,83,fc'. That log parameter could be read back at +some later time with '\-\-page=0xf \-\-filter=0x'. +.SH NOTES +This utility will usually do a double fetch of log pages with the SCSI LOG +SENSE command. The first fetch requests a 4 byte response (i.e. place 4 in +the "allocation length" field in the cdb). From that response it can +calculate the actual length of the response which is what it asks for +on the second fetch. This is typical practice in SCSI and guaranteed to +work in the standards. However some older devices don't comply. For +those devices using the \fI\-\-maxlen=LEN\fR option will do a single fetch. +A value of 252 should be a safe starting point. +.PP +Various log pages hold information error rates, device temperature, start +stop cycles since the device was produced and the results of the last +20 self tests. Self tests can be initiated by the sg_senddiag(8) utility. +The smartmontools package provides much of the information found with +sg_logs in a form suitable for monitoring the health of SCSI disks and +tape drives. +.PP +The simplest way to find which log pages can be decoded by this utility is +to use the \fI\-\-enumerate\fR option. Some page names are known but there +is no decode logic; such cases have "[hex only]" after the log page name +when the \fI\-\-verbose\fR option is given with \fI\-\-enumerate\fR. +.SH EXIT STATUS +The exit status of sg_logs is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.PP +Options with arguments or with two or more letters can have an extra '\-' +prepended. For example: both '\-pcb' and '\-\-pcb' are acceptable. +.TP +\fB\-a\fR +outputs all the log pages supported by the device. +Equivalent to \fI\-\-all\fR in the main description. +.TP +\fB\-A\fR +outputs all the log pages and subpages supported by the device. +Equivalent to '\-\-all \-\-all' in the main description. +.TP +\fB\-c\fR=\fIPC\fR +Equivalent to \fI\-\-control=PC\fR in the main description. +.TP +\fB\-e\fR +enumerate internal tables to show information about known log pages. +Equivalent to \fI\-\-enumerate\fR in the main description. +.TP +\fB\-h\fR +suppresses decoding of known log sense pages and prints out the +response in hex instead. +.TP +\fB\-i\fR=\fIFN\fR +\fIFN\fR is treated as a file name (or '\-' for stdin) which contains ASCII +hexadecimal representing a log page that will be sent as parameter data of a +LOG SELECT command. See the LOG SELECT section. +.TP +\fB\-H\fR +same action as '\-h' in this section and equivalent to \fI\-\-hex\fR in +the main description. +.TP +\fB\-l\fR +lists the names of all logs sense pages supported by this device. +Equivalent to \fI\-\-list\fR in the main description. +.TP +\fB\-L\fR +lists the names of all logs sense pages and subpages supported by this +device. Equivalent to '\-\-list \-\-list' in the main description. +.TP +\fB\-m\fR=\fILEN\fR +request only \fILEN\fR bytes of response data. Default is 0 which is +interpreted as all that is available. \fILEN\fR is decimal unless it has +a leading '0x' or trailing 'h'. Equivalent to \fI\-\-maxlen=LEN\fR in +the main description. +.TP +\fB\-M\fR=\fIVP\fR +Equivalent to \fI\-\-vendor=VP\fR in the main description. +.TP +\fB\-n\fR +Equivalent to \fI\-\-name\fR in the main description. +.TP +\fB\-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-p\fR=\fIPG\fR +log page code to access. \fIPG\fR is either an acronym, a page number, or +a page, subpage pair. Available acronyms can be listed with the +\fI\-\-enumerate\fR option. Page (0 to 3f) and subpage (0 to ff) numbers +are comma separated. The numbers are assumed to be hexadecimal. +.TP +\fB\-paramp\fR=\fIPP\fR +\fIPP\fR is the parameter pointer value (in hex) to place in command. +Should be a number between 0 and ffff inclusive. +.TP +\fB\-pcb\fR +show Parameter Control Byte settings (only relevant when log parameters +being output in ASCII). +.TP +\fB\-ppc\fR +sets the Parameter Pointer Control (PPC) bit. Default is 0 (i.e. cleared). +.TP +\fB\-r\fR +use SCSI LOG SELECT command (PCR bit set) to reset the all log pages (or +the given page). Equivalent to \fI\-\-reset\fR in the main description. +.TP +\fB\-R\fR +Equivalent to \fI\-\-readonly\fR in the main description. +.TP +\fB\-select\fR +use a LOG SELECT command. Equivalent to \fI\-\-select\fR in the main +description. +.TP +\fB\-sp\fR +sets the Saving Parameters (SP) bit. Default is 0 (i.e. cleared). +Equivalent to \fI\-\-sp\fR in the main description. +.TP +\fB\-t\fR +outputs the temperature. Equivalent to \fI\-\-temperature\fR in the main +description. +.TP +\fB\-T\fR +outputs the transport ('Protocol specific port') log page. Equivalent +to \fI\-\-transport\fR in the main description. +.TP +\fB\-v\fR +increase level of verbosity. +.TP +\fB\-V\fR +print out version string then exit. +.TP +\fB\-x\fR +suppress the INQUIRY command. Equivalent to \fI\-\-no_inq\fR in the main +description. +.TP +\fB\-?\fR +output usage message then exit. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2002\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B smartctl(smartmontools), sg_senddiag(8) diff --git a/doc/sg_luns.8 b/doc/sg_luns.8 new file mode 100644 index 0000000..f27ed6f --- /dev/null +++ b/doc/sg_luns.8 @@ -0,0 +1,319 @@ +.TH SG_LUNS "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_luns \- send SCSI REPORT LUNS command or decode given LUN +.SH SYNOPSIS +.B sg_luns +[\fI\-\-decode\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-linux\fR] +[\fI\-\-lu_cong\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-quiet\fR] [\fI\-\-raw\fR] +[\fI\-\-readonly\fR] [\fI\-\-select=SR\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.PP +.B sg_luns +\fI\-\-test=ALUN\fR [\fI\-\-decode\fR] [\fI\-\-hex\fR] [\fI\-\-lu_cong\fR] +[\fI\-\-verbose\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +In the first form shown in the SYNOPSIS this utility sends the SCSI REPORT +LUNS command to the \fIDEVICE\fR and outputs the response. The response +should be a list of LUNs ("a LUN inventory") for the I_T nexus associated +with the \fIDEVICE\fR. Roughly speaking that is all LUNs that share the +target device that the REPORT LUNS command is sent through. This command +is defined in the SPC\-3 and SPC\-4 SCSI standards and its support is +mandatory. The most recent draft if SPC\5 revision 9. +.PP +When the \fI\-\-test=ALUN\fR option is given (the second form in the +SYNOPSIS), then the \fIALUN\fR value is decoded as outlined in various +SCSI Architecture Model (SAM) standards and recent drafts (e.g. SAM\-6 +revision 2, section 4.7) . +.PP +Where required below the first form shown in the SYNOPSIS is called "device +mode" and the second form is called "test mode". +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-d\fR, \fB\-\-decode\fR +decode LUNs into their component parts, as described in the LUN section +of SAM\-3, SAM\-4 and SAM\-5. +.br +[test mode] \fIALUN\fR is decoded irrespective of whether this option is +given or not. If this option is given once then the given \fIALUN\fR is +output in T10 preferred format (which is 8 pairs of hex digits, each +separated by a space). If given twice then the given \fIALUN\fR is output +in an alternate T10 format made up of four quads of hex digits with each +quad separated by a "-" (e.g. C101\-0000\-0000\-0000). +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +[device mode] when given once this utility will output the SCSI +response (i.e. the data\-out buffer) to the REPORT LUNS command in ASCII +hex then exit. When given twice it causes \fI\-\-decode\fR to output +component fields in hex rather than decimal. +.br +[test mode] when this option is given, then decoded component fields of +\fIALUN\fR are output in hex. +.TP +\fB\-l\fR, \fB\-\-linux\fR +this option is only available in Linux. After the T10 representation of +each 64 bit LUN (in 16 hexadecimal digits), if this option is given then +to the right, in square brackets, is the Linux LUN integer in decimal. +If the \fI\-\-hex\fR option is given twice (e.g. \-HH) as well then the +Linux LUN integer is output in hexadecimal. +.TP +\fB\-L\fR, \fB\-\-lu_cong\fR +this option is only considered with \fI\-\-decode\fR. When given once +then the list of LUNs is decoded as if the LU_CONG bit was set in +each LU's corresponding INQUIRY response. When given twice the list of +LUNs is decoded as if the LU_CONG bit was clear in each LU's corresponding +INQUIRY response. When this option is not given and \fI\-\-decode\fR is +given then an INQUIRY is sent to the \fIDEVICE\fR and the setting of +its LU_CONG bit is used to decode the list of LUNs. +.br +[test mode] decode \fIALUN\fR as if the LU_CONG bit is set in its +corresponding standard INQUIRY response. In other words treat \fIALUN\fR +as if it is a conglomerate LUN. If not given (or given twice) then decode +\fIALUN\fR as if the LU_CONG bit is clear. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in +the cdb's "allocation length" field. If not given (or \fILEN\fR is zero) +then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +output only the ASCII hex rendering of each report LUN, one per line. +Without the \fI\-\-quiet\fR option, there is header information printed +before the LUN listing. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the SCSI response (i.e. the data\-out buffer) in binary (to stdout). +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-s\fR, \fB\-\-select\fR=\fISR\fR +\fISR\fR is placed in the SELECT REPORT field of the SCSI REPORT LUNS +command. The default value is 0. Hexadecimal values may be given with +a leading "0x" or a trailing "h". For detailed information see the +REPORT LUNS command in SPC (most recent is SPC\-4 revision 37 in section +6.33). To simplify, for the I_T nexus associated with the \fIDEVICE\fR, the +meanings of the \fISR\fR values defined to date for SPC\-4 are: +.br + \fB0\fR : most luns excluding well known logical unit numbers +.br + \fB1\fR : well known logical unit numbers +.br + \fB2\fR : all luns accessible to this I_T nexus +.br + \fB0x10\fR : only accessible administrative luns +.br + \fB0x11\fR : administrative luns plus non-conglomerate luns (see SPC\-4) +.br + \fB0x12\fR : if \fIDEVICE\fR is an administrative LU, then report its +.br + lun plus its subsidiary luns +.PP +For \fISR\fR values 0x10 and 0x11, the \fIDEVICE\fR must be either LUN 0 or +the REPORT LUNS well known logical unit. Values between 0xf8 and +0xff (inclusive) are vendor specific, other values are reserved. This +utility will accept any value between 0 and 255 (0xff) for \fISR\fR . +.TP +\fB\-t\fR, \fB\-\-test\fR=\fIALUN\fR +\fIALUN\fR is assumed to be a hexadecimal number in ASCII hex or the +letter 'L' followed by a decimal number (see below). The hexadecimal number +can be up to 64 bits in size (i.e. 16 hexadecimal digits) and is padded to +the right if less than 16 hexadecimal digits are given (e.g. +\fI\-\-test=0122003a\fR represents T10 LUN: 01 22 00 3a 00 00 00 00). +\fIALUN\fR may be prefixed by '0x' or '0X' (e.g. the previous example could +have been \fI\-\-test=0x0122003a\fR). \fIALUN\fR may also be given with +spaces, tabs, or a '\-' between each byte (or other grouping (e.g. +c101\-0000\-0000\-0000)). However in the case of space or tab separators +the \fIALUN\fR would need to be surrounded by single or double quotes. +.br +In the leading 'L' case the, following decimal number (hex if preceded +by '0x') is assumed to be a Linux "word flipped" LUN which is converted +into a T10 LUN representation and printed. In both cases the number is +interpreted as a LUN and decoded as if the \fI\-\-decode\fR option had been +given. Also when \fIALUN\fR is a hexadecimal number it can have a +trailing 'L' in which case the corresponding Linux "word flipped" LUN value +is output. The LUN is decoded in all cases. +.br +The action when used with \fI\-\-decode\fR is explained under that option. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +The SCSI REPORT LUNS command is important for Logical Unit (LU) discovery. +After a target device is discovered (usually via some transport specific +mechanism) and after sending an INQUIRY command (to determine the LU_CONG +setting), a REPORT LUNS command should either be sent to LUN 0 (which +is Peripheral device addressing method with bus_id=0 and target/lun=0) +or to the REPORT LUNS well known LUN (i.e. 0xc101000000000000). SAM\-5 +requires that one of these responds with an inventory of LUNS that are +contained in this target device. +.PP +In test mode, if the \fI\-\-hex\fR option is given once then in the decoded +output, some of the component fields are printed in hex with leading zeros. +The leading zeros are to indicate the size of the component field. For +example: in the Peripheral device addressing method (16 bits overall), the +bus ID is 6 bits wide and the target/LUN field is 8 bits wide; so both are +shown with two hex digits (e.g. bus_id=0x02, target=0x3a). +.SH EXAMPLES +Typically by the time user space programs get to run, SCSI LUs have been +discovered. In Linux the lsscsi utility lists the LUs that are currently +present. The LUN of a device (LU) is the fourth element in the tuple at the +beginning of each line. Below we see a target (or "I_T Nexus": "6:0:0") has +two LUNS: 1 and 49409. If 49409 is converted into T10 LUN format it is +0xc101000000000000 which is the REPORT LUNS well known LUN. +.PP + # lsscsi \-g +.br + [6:0:0:1] disk Linux scsi_debug 0004 /dev/sdb /dev/sg1 +.br + [6:0:0:2] disk Linux scsi_debug 0004 /dev/sdc /dev/sg2 +.br + [6:0:0:49409]wlun Linux scsi_debug 0004 \- /dev/sg3 +.PP +We could send a REPORT LUNS command (with \fISR\fR 0x0, 0x1 or 0x2) to any +of those file device nodes and get the same result. Below we use /dev/sg1 : +.PP + # sg_luns /dev/sg1 +.br + Lun list length = 16 which imples 2 lun entry +.br + Report luns [select_report=0x0]: +.br + 0001000000000000 +.br + 0002000000000000 +.PP +That is a bit noisy so cut down the clutter with \fI\-\-quiet\fR: +.PP + # sg_luns \-q /dev/sg1 +.br + 0001000000000000 +.br + 0002000000000000 +.PP +Now decode that LUN into its component parts: +.PP + # sg_luns \-d \-q /dev/sg1 +.br + 0001000000000000 +.br + Peripheral device addressing: lun=1 +.br + 0002000000000000 +.br + Peripheral device addressing: lun=2 +.PP +Now use \fI\-\-select=1\fR to find out if there are any well known +LUNs: +.PP + # sg_luns \-q \-s 1 /dev/sg1 +.br + c101000000000000 +.PP +So how many LUNs do we have all together (associated with the current +I_T Nexus): +.PP + # sg_luns \-q \-s 2 /dev/sg1 +.br + 0001000000000000 +.br + 0002000000000000 +.br + c101000000000000 +.PP + # sg_luns \-q \-s 2 \-d /dev/sg1 +.br + 0001000000000000 +.br + Peripheral device addressing: lun=1 +.br + 0002000000000000 +.br + Peripheral device addressing: lun=1 +.br + c101000000000000 +.br + REPORT LUNS well known logical unit +.PP +The following example uses the \fI\-\-linux\fR option and is not available +in other operating systems. The extra number in square brackets is the +Linux version of T10 LUN shown at the start of the line. +.PP + # sg_luns \-q \-s 2 \-l /dev/sg1 +.br + 0001000000000000 [1] +.br + 0002000000000000 [2] +.br + c101000000000000 [49409] +.PP +Now we use the \fI\-\-test=\fR option to decode LUNS input on the command +line (rather than send a REPORT LUNS command and act on the response): +.PP + # sg_luns \-\-test=0002000000000000 +.br + Decoded LUN: +.br + Peripheral device addressing: lun=2 +.PP + # sg_luns \-\-test="c1 01" +.br + Decoded LUN: +.br + REPORT LUNS well known logical unit +.PP + # sg_luns \-t 0x023a004b \-H +.br + Decoded LUN: +.br + Peripheral device addressing: bus_id=0x02, target=0x3a +.br + >>Second level addressing: +.br + Peripheral device addressing: lun=0x4b +.PP +The next example is Linux specific as we try to find out what the +Linux LUN 49409 translates to in the T10 world: +.PP + # sg_luns \-\-test=L49409 +.br + 64 bit LUN in T10 preferred (hex) format: c1 01 00 00 00 00 00 00 +.br + Decoded LUN: +.br + REPORT LUNS well known logical unit +.PP +And the mapping between T10 and Linux LUN representations can be done the +other way: +.PP + # sg_luns \-t c101L +.br + Linux 'word flipped' integer LUN representation: 49409 +.br + Decoded LUN: +.br + REPORT LUNS well known logical unit +.br +.SH EXIT STATUS +The exit status of sg_luns is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq(8) diff --git a/doc/sg_map.8 b/doc/sg_map.8 new file mode 100644 index 0000000..5cbb4c2 --- /dev/null +++ b/doc/sg_map.8 @@ -0,0 +1,182 @@ +.TH SG_MAP "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +sg_map \- displays mapping between Linux sg and other SCSI devices +.SH SYNOPSIS +.B sg_map +[\fI\-a\fR] [\fI-h\fR] [\fI\-i\fR] [\fI\-n\fR] [\fI\-scd\fR] [\fI\-sd\fR] +[\fI\-sr\fR] [\fI\-st\fR] [\fI\-V\fR] [\fI\-x\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sometimes it is difficult to determine which SCSI device a sg device +name (e.g. /dev/sg0) refers to. This command loops through the +sg devices and finds the corresponding SCSI disk, cdrom or tape +device name (if any). Scanners are an example of SCSI devices +that have no alternate SCSI device name apart from their sg device +name. +.PP +This utility is deprecated and has not been updated for years, only very +obvious bugs will be fixed. Unless a very old version of Linux is being +used (e.g. 2.4 series or earlier), then please use a utility like lsscsi(8) +or the facilities offered by udev(8). +.SH OPTIONS +.TP +\fB\-a\fR +assume the sg devices have alphabetical device names and loop +through /dev/sga, /dev/sgb, etc. Default is numeric scan. +Note that sg device nodes with an alphabetical index have been +deprecated since the Linux kernel 2.2 series. +.TP +\fB\-h\fR +print usage message then exit. +.TP +\fB\-i\fR +in addition do a standard INQUIRY and output vendor, product and revision +strings for devices that are found. +.TP +\fB\-n\fR +assume the sg devices have numeric device names and loop +through /dev/sg0, /dev/sg1, etc. Default is numeric scan +.TP +\fB\-scd\fR +display mappings to SCSI cdrom device names of the form +/dev/scd0, /dev/scd1 etc +.TP +\fB\-sd\fR +display mappings to SCSI disk device names +.TP +\fB\-sr\fR +display mappings to SCSI cdrom device names of the form +/dev/sr0, /dev/sr1 etc +.TP +\fB\-st\fR +display mappings to SCSI tape device names +.TP +\fB\-V\fR +print out version string then exit (without further ado). +.TP +\fB\-x\fR +after each active sg device name is displayed there are +five digits: +.SH NOTES +If no options starting with "\-s" are given then the mapping to +all SCSI disk, cdrom and tape device names is shown. +.PP +If the device file system (devfs) is present a line noting +this is output. The "native" devfs scsi hierarchy makes the +relationship between a sg device name and any corresponding +disk, cdrom or tape device name easy to establish. This +replaces the need for this command. However many applications +will continue to look for Linux SCSI device names in their +traditional places. [Devfs supplies a compatibility daemon +called devfsd whose default configuration adds back the +Linux device names in their traditional positions. +.PP +Quite often the mapping information can be derived by +observing the output of the command: "cat /proc/scsi/scsi". +However if devices have been added since boot this can +be deceptive. +.PP +In the Linux kernel 2.6 series something close to the mapping +shown by this utility can be found by analysing sysfs. The +main difference is that sysfs analysis will show the mapping +between sg nodes and other SCSI device nodes in terms of +major and minor numbers. While major 8, minor 16 will usually +be /dev/sdb this is not necessarily so. Facilities associated +with udev may assign major 8, minor 16 some other device node +name. This version of sg_map has been extended to cope with +sparse disk device node names of the form "/dev/sd" +where can be one of [a\-z,aa\-zz,aaa\-zzz]. See the sg_map26 +utility for a more precise way (i.e. less directory scanning) +for mapping between sg device names and higher level names; +including finding user defined names. +.PP +This utility was written at a time when hotplugging of SCSI devices +was not supported in Linux. It used a simple algorithm to scan sg +device nodes in ascending numeric or alphabetical order, stopping +after there were 5 consecutive errors. +.PP +In the Linux kernel 2.6 series, this utility uses sysfs to find which +sg device nodes are active and only checks those. Hence there can be +large "holes" in the numbering of sg device nodes (e.g. after an +adapter has been removed) and still all active sg device nodes will +be listed. This utility assumes that sg device nodes are named using +the normal conventions and searches from /dev/sg0 to /dev/sg4095 +inclusive. +.SH EXAMPLES +.PP +My system has a SCSI disk, a cd writer and a dvd player: +.br + $ sg_map +.br + # Note: the devfs pseudo file system is present +.br + /dev/sg0 /dev/sda +.br + /dev/sg1 /dev/sr0 +.br + /dev/sg2 /dev/sr1 +.PP +In order to find which sg device name corresponds to the disk: +.br + $ sg_map \-sd +.br + # Note: the devfs pseudo file system is present +.br + /dev/sg0 /dev/sda +.br + /dev/sg1 +.br + /dev/sg2 +.PP +The "\-x" option gives the following output: +.br + sg_map \-x +.br + # Note: the devfs pseudo file system is present +.br + /dev/sg0 1 0 1 0 0 /dev/sda +.br + /dev/sg1 2 0 4 0 5 /dev/sr0 +.br + /dev/sg2 2 0 6 0 5 /dev/sr1 +.PP +When a SCSI scanner is added the output becomes: +.br + $ sg_map +.br + # Note: the devfs pseudo file system is present +.br + /dev/sg0 /dev/sda +.br + /dev/sg1 /dev/sr0 +.br + /dev/sg2 /dev/sr1 +.br + /dev/sg3 +.PP +By process of elimination /dev/sg3 must be the scanner. +.SH EXIT STATUS +The exit status of sg_map is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2013 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_map26(8) +, +.B scsi_info(8) +, +.B scsidev(8) +, +.B devfsd(8) +, +.B lsscsi(8) +, +.B udev(7) diff --git a/doc/sg_map26.8 b/doc/sg_map26.8 new file mode 100644 index 0000000..cb826e2 --- /dev/null +++ b/doc/sg_map26.8 @@ -0,0 +1,161 @@ +.TH SG_MAP26 "8" "November 2012" "sg3_utils\-1.35" SG3_UTILS +.SH NAME +sg_map26 \- map SCSI generic (sg) device to corresponding device names +.SH SYNOPSIS +.B sg_map26 +[\fI\-\-dev_dir=DIR\fR] [\fI\-\-given_is=\fR0|1] [\fI\-\-help\fR] +[\fI\-\-result=\fR0|1|2|3] [\fI\-\-symlink\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Maps a special file (block or char) associated with a SCSI device +to the corresponding SCSI generic (sg) device, or vice versa. +Can also be given a sysfs file, for example '/sys/block/sda' +or '/sys/block/sda/dev'. +.PP +Rather than map to or from a sg device, the sysfs file name +matching a given device special file (or vice versa) can be +requested. This is done with '\-\-result=2' and '\-\-result=3'. +This feature works on ATA devices (e.g. 'dev/hdc') as well +as SCSI devices. +.PP +In this utility, "mapped" refers to finding the relationship between +a SCSI generic (sg) node and the higher level SCSI device name; or +vice versa. For example '/dev/sg0' may "map" to '/dev/sda'. +Mappings may not exist, if a relevant module is not loaded, for +example. Also there are SCSI devices that can only be accessed via a sg +node (e.g. SAF\-TE and some SES devices). +.PP +In this utility, "matching" refers to different representations of +the same device accessed via the same driver. For example, '/dev/hdc' +and '/sys/block/hdc' usually refer to the same device and thus would +be considered matching. A related example is that '/dev/cdrom' +and '/dev/hdc' are also considered matching if '/dev/cdrom' is a +symlink to '/dev/hdc'. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-d\fR, \fB\-\-dev_dir\fR=\fIDIR\fR +where \fIDIR\fR is the directory to search for resultant device special +files in (or symlinks to same). Only active when '\-\-result=0' (the +default) or '\-\-result=2'. If this option is not given and \fIDEVICE\fR is +a device special file then the directory part of \fIDEVICE\fR is assumed. +If this option is not given and \fIDEVICE\fR is a sysfs name, then if +necessary '/dev' is assumed as the directory. +.TP +\fB\-g\fR, \fB\-\-given_is\fR=0 | 1 +specifies the \fIDEVICE\fR is either a device special file (when the +argument is 0), or a sysfs 'dev' file (when the argument is 1). The parent +directory of a sysfs 'dev' file is also accepted (e.g. +either '/sys/block/sda/dev' or '/sys/block/sda' are accepted). Usually +there is no need to give this option since this utility first checks for +special files (or symlinks to special files) and if not, assumes it +has been given a sysfs 'dev' file (or its parent). Generates an error +if given and disagrees with variety of \fIDEVICE\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-r\fR, \fB\-\-result\fR=0 | 1 | 2 | 3 +specifies what variety of file (or files) that this utility tries to find. +The default is a "mapped" device special file, when the argument is 0. +When the argument is 1, this utility tries to find the "mapped" sysfs node +name. When the argument is 2, this utility tries to find the "matching" +device special file. When the argument is 3, this utility tries to find +the "matching" sysfs node name. +.TP +\fB\-s\fR, \fB\-\-symlink\fR +when a device special file is being sought (i.e. when '\-\-result=0' (the +default) or '\-\-result=2') then also look for symlinks to that device +special file in the same directory. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +This utility is designed for the Linux 2.6 (and later) kernel series. +It uses special file major and minor numbers (and whether the special +is block or character) together with sysfs to do its mapping or +matching. In the absence of any other information, device special +files are assumed to be in the '/dev' directory while sysfs is +assumed to be mounted at '/sys'. Device names in sysfs are +predictable, given the corresponding major and minor number of +the device. However, due to udev rules, the name of device +special files can be anything the user desires (e.g. '/dev/sda' +could be named '/dev/my_boot_disk'). When trying to +find a resultant device special file, this utility uses the major +and minor numbers (and whether a block or char device is sought) +to search the device directory. +.PP +This utility only shows one relationship at a time. To get an +overview of all SCSI devices, with special file names and optionally +the "mapped" sg device name, see the lsscsi utility. +.SH EXAMPLES +Assume sg2 maps to sdb while dvd, cdrom and hdc are all matching. +.PP + # sg_map26 /dev/sg2 +.br + /dev/sdb +.PP + # sg_map26 /dev/sdb +.br + /dev/sg2 +.PP + # sg_map26 \-\-result=0 /dev/sdb +.br + /dev/sg2 +.PP + # sg_map26 \-\-result=3 /dev/sdb +.br + /sys/block/sda +.PP + # sg_map26 \-\-result=1 /dev/sdb +.br + /sys/class/scsi_generic/sg0 +.PP +Now look at '/dev/hdc' and friends +.PP + # sg_map26 /dev/hdc +.br + +.PP + # sg_map26 \-\-result=3 /dev/hdc +.br + /sys/block/hdc +.PP + # sg_map26 \-\-result=2 /dev/hdc +.br + /dev/hdc +.PP + # sg_map26 \-\-result=2 \-\-symlink /dev/hdc +.br + /dev/cdrom +.br + /dev/dvd +.br + /dev/hdc +.PP + # sg_map26 \-\-result=2 \-\-symlink /sys/block/hdc +.br + /dev/cdrom +.br + /dev/dvd +.br + /dev/hdc +.SH EXIT STATUS +The exit status of sg_map26 is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2005\-2012 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B udev(7), lsscsi(lsscsi) diff --git a/doc/sg_modes.8 b/doc/sg_modes.8 new file mode 100644 index 0000000..ed2d2e3 --- /dev/null +++ b/doc/sg_modes.8 @@ -0,0 +1,311 @@ +.TH SG_MODES "8" "September 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_modes \- reads mode pages with SCSI MODE SENSE command +.SH SYNOPSIS +.B sg_modes +[\fI\-\-all\fR] [\fI\-\-control=PC\fR] [\fI\-\-dbd\fR] [\fI\-\-dbout\fR] +[\fI\-\-examine\fR] [\fI\-\-flexible\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-list\fR] [\fI\-\-llbaa\fR] [\fI\-\-maxlen=LEN\fR] +[\fI\-\-page=PG[,SPG]\fR] [\fI\-\-raw\fR] [\fI\-R\fR] [\fI\-\-readwrite\fR] +[\fI\-\-six\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fIDEVICE\fR] +.PP +.B sg_modes +[\fI\-6\fR] [\fI\-a\fR] [\fI\-A\fR] [\fI\-c=PC\fR] [\fI\-d\fR] [\fI\-D\fR] +[\fI\-e\fR] [\fI\-f\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-l\fR] [\fI\-L\fR] +[\fI\-m=LEN\fR] [\fI\-p=PG[,SPG]\fR] [\fI\-r\fR] [\fI\-subp=SPG\fR] +[\fI\-v\fR] [\fI\-V\fR] [\fI\-w\fR] [\fI\-?\fR] [\fIDEVICE\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends a MODE SENSE SCSI command to the \fIDEVICE\fR and +outputs the response. There is a 6 byte and 10 byte (cdb) variant of the +MODE SENSE command, this utility defaults to the 10 byte variant. The SPC\-4 +standard (and SPC\-5 drafts) include a note stating that implementers should +migrate away from the SCSI MODE SELECT(6) and MODE SENSE(6) commands in +favour of the 10 byte variants (e.g. MODE SENSE(10)). +.PP +This utility decodes mode page headers and block descriptors but outputs +the contents of each mode page in hex. It also has no facility to change +the mode page contents or block descriptor data. Mode page contents are +decoded and can be changed by the +.B sdparm +utility. +.PP +This utility supports two command line syntaxes, the preferred one is +shown first in the synopsis and explained in this section. A later +section on the old command line syntax outlines the second group of +options. +.PP +If no page is given (and \fI\-\-list\fR is not selected) then \fI\-\-all\fR +is assumed. The \fI\-\-all\fR option requests all mode pages (but not +subpages) in a single response. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-a\fR, \fB\-\-all\fR +output all the mode pages reported by the \fIDEVICE\fR. This is what the +page code 63 (0x3f) is defined to do. When used once, mode subpages are +not fetched. When used twice (e.g. '\-aa'), all mode pages and subpages +are requested which is equivalent to '\-\-page=63,255'. +.TP +\fB\-c\fR, \fB\-\-control\fR=\fIPC\fR +\fIPC\fR is the page control value. Up to four different versions of each +page are held by the device: +.br + \fB0\fR : current values (i.e. those active at present) +.br + \fB1\fR : changeable values +.br + \fB2\fR : default values (i.e. the manufacturer's settings) +.br + \fB3\fR : saved values +.br +The changeable values are bit masks showing which fields could be changed +with a MODE SELECT. The saved values will be re\-instated the next time +the device is power cycled or reset. If this option is not given then +current values [0] are assumed. +.TP +\fB\-d\fR, \fB\-\-dbd\fR +disable block descriptors. By default, block descriptors (usually +one (for disks) or none) are returned in a MODE SENSE response. This option +sets the "disable block descriptors" (DBD) bit in the cdb which instructs +the device not to return any block descriptors in its response. Older +devices may not support this setting and may return an "illegal request" +sense key; alternatively they may ignore it. Oddly the Reduced Block Command +set (RBC) requires this bit set. +.TP +\fB\-D\fR, \fB\-\-dbout\fR +disable outputting block descriptors. Irrespective of whether block +descriptors are present in the response or not, they are not output. +.TP +\fB\-e\fR, \fB\-\-examine\fR +examine each mode page in the range 0 through to 62 (inclusive). +If some response is given then print out the mode page name or +number (in hex) if the name is not known. +.TP +\fB\-f\fR, \fB\-\-flexible\fR +Some devices, bridges and/or drivers attempt crude translations between +MODE SENSE 6 and 10 byte commands without correcting the response. This +will cause the response to be mis\-interpreted (usually with an error saying +the response is malformed). With this option, the length of the response +is checked, and if it looks wrong, the response is then decoded as if the +other mode sense (cdb length) was sent. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +The default action is to decode known mode page numbers (and subpage +numbers) into text. When this option is used once, the response is output +in hexadecimal to stdout. When this option is used twice, mode page numbers +and page control values are output in hex. +.br +When this option is used three times, the full response to the MODE SENSE +command is output in hex to stdout without any decoding. This form can +be redirected to a file (or piped) and then used 'sdparm \-\-inhex=' to +decode. +.TP +\fB\-l\fR, \fB\-\-list\fR +lists all common page and subpage codes and their names that are found in +the command set that matches the peripheral type of the given \fIDEVICE\fR. +If no \fIDEVICE\fR and no \fI\-\-page=PG\fR is given then the common page and +subpage codes and their names are listed for SBC (e.g. a disk). If no +\fIDEVICE\fR is given and a \fI\-\-page=PG\fR is given then the +common page and subpage codes and their names are listed for the command set +whose peripheral device type matches the value given to \fIPG\fR. For +example 'sg_mode \-\-list \-\-page=1' lists the command mode pages and +subpages for tape devices. Additionally if a sub_page_code is given then it +is interpreted as a transport identifier and command transport specific mode +page codes and their names are listed following the main mode page list. +Other options are ignored. +.TP +\fB\-L\fR, \fB\-\-llbaa\fR +set the Long LBA Accepted (LLBAA) bit in the MODE SENSE (10) cdb. This +bit is not defined in the MODE SENSE (6) cdb so setting the '\-L' +and '\-\-six' options is reported as an error. When set the \fIDEVICE\fR +may respond with 16 byte block descriptors as indicated by +the 'LongLBA' field in the response. In most cases setting this option +is not needed. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +The \fILEN\fR argument is the maximum response length in bytes. It is +the 'allocation length' field in the cdb. When not given (or \fILEN\fR is +zero) then the allocation length field is set to 4096 for MODE SENSE (10) +or 252 for MODE SENSE (6). The \fILEN\fR argument must be non\-negative +and no greater than 65535 for MODE SENSE (10) and not greater than 255 +for MODE SENSE (6). +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR +page code to fetch. The \fIPG\fR is assumed to be a decimal value unless +prefixed by '0x' or has a trailing 'h'. It should be a value between 0 +and 63 (inclusive). When not given and a default is required then +a value of 63 (0x3f), which fetches all mode pages, is used. +.br +Alternatively an acronym for the mode page can be given. The available +acronyms can be listed out with the \fI\-\-page=xxx\fR option. They are +almost the same as the acronyms used for mode pages in the sdparm utility. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG,SPG\fR +page code and subpage code values to fetch. Both arguments are assumed +to be decimal unless flagged as hexadecimal. The page code should be +between 0 and 63 inclusive. The subpage code should be between 0 and 255 +inclusive. The default value for the subpage code is 0. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the response in binary to stdout. Error messages and warnings, if +any, are sent to stderr. When this option is used twice (e.g. '\-rr') +then has the same action as '\-R' +.TP +\fB\-R\fR +output the selected mode page to stdout a byte per line. Each line contains +two hexadecimal digits (e.g. "3e"). Useful as input (after editing) to +the sg_wr_mode(8) utility. +.TP +\fB\-w\fR, \fB\-\-readwrite\fR +open \fIDEVICE\fR in "read\-write" mode. Default is to open it in read\-only +mode. +.TP +\fB\-6\fR, \fB\-\-six\fR +by default this utility sends a 10 byte MODE SENSE command to +the \fIDEVICE\fR. However some SCSI devices only support 6 byte MODE SENSE +commands (e.g. SCSI\-2 tape drives). This parameter forces the use +of 6 byte MODE SENSE commands. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH NOTES +If the normal sg_modes utility fails with "illegal command +operation code" then try the '\-\-six' (or '\-6') option. +.PP +This utility performs a SCSI INQUIRY command to determine the peripheral +type of the device (e.g. 0 \-> Direct Access Device (disk)) prior to +sending a MODE SENSE command. This helps in decoding the block +descriptor and mode pages. +.PP +This utility opens \fIDEVICE\fR in read\-only mode (e.g. in Unix, with +the O_RDONLY flag) by default. It will open \fIDEVICE\fR in read\-write +mode if the \fI\-\-readwrite\fR option is given. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI +generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks +and DVD drives) can also be specified. For example "sg_modes \-a /dev/sda" +will work in the 2.6 series kernels. +.SH EXIT STATUS +The exit status of sg_modes is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-6\fR +by default this utility sends a 10 byte MODE SENSE command to +the \fIDEVICE\fR. This parameter forces the use of 6 byte MODE SENSE commands. +See \fI\-\-six\fR in the main description. +.TP +\fB\-a\fR +see \fI\-\-all\fR in the main description. +.TP +\fB\-A\fR +output all the mode pages and subpages supported by the \fIDEVICE\fR. Same +as '\-\-all \-\-all' in the new syntax. +.TP +\fB\-c\fR=\fIPC\fR +\fIPC\fR is the page control value. See \fB\-\-control\fR=\fIPC\fR in +the main description. +.TP +\fB\-d\fR +see \fB\-\-dbd\fR in the main description. +.TP +\fB\-D\fR +see \fB\-\-dbout\fR in the main description. +.TP +\fB\-e\fR +see \fB\-\-examine\fR in the main description. +.TP +\fB\-f\fR +see \fB\-\-flexible\fR in the main description. +.TP +\fB\-h\fR +The default action is to decode known mode page numbers (and subpage +numbers) into text. With this option mode page numbers (and subpage +numbers) are output in hexadecimal. +.TP +\fB\-H\fR +same action as the '\-h' option. +.TP +\fB\-l\fR +see \fB\-\-list\fR in the main description. +.TP +\fB\-L\fR +see \fB\-\-llbaa\fR in the main description. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-m\fR=\fILEN\fR +see \fB\-\-maxlen\fR=\fILEN\fR in the main description. +.TP +\fB\-p\fR=\fIPG\fR +\fIPG\fR is page code to fetch. Should be a hexadecimal number between 0 +and 3f inclusive (0 to 63 decimal). The default value when required is +3f (fetch all mode pages). Note that an acronym for the page and/or +subpage values is not accepted in this older format (because any acronym +starting with the letters 'a' to 'f' is ambiguous; it could either be a hex +number or an acronym). +.TP +\fB\-p\fR=\fIPG,SPG\fR +page code and subpage code values to fetch. The page code should be a +hexadecimal number between 0 and 3f inclusive. The subpage code should +be a hexadecimal number between 0 and ff inclusive. The default value +for the subpage code is 0. +.TP +\fB\-r\fR +output the selected mode page to stdout a byte per line. Each line contains +two hexadecimal digits (e.g. "3e"). Useful as input (after editing) to +the sg_wr_mode(8) utility. +.TP +\fB\-subp\fR=\fISPG\fR +sub page code to fetch. Should be a hexadecimal number between 0 and +0xff inclusive. The default value is 0. +.TP +\fB\-v\fR +increase verbosity of output. +.TP +\fB\-V\fR +print out version string then exit. +.TP +\fB\-w\fR +see \fB\-\-readwrite\fR in the main description. +.TP +\fB\-?\fR +output usage message then exit. Ignore all other parameters. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sdparm(8), sg_wr_mode(8), sginfo(8), +.B sgmode(scsirastools), scsiinfo(net), scu(net), +.B seatools(seagate) +.PP +All these utilities offer some facility to change mode page (or block +descriptor) parameters. diff --git a/doc/sg_opcodes.8 b/doc/sg_opcodes.8 new file mode 100644 index 0000000..93a9508 --- /dev/null +++ b/doc/sg_opcodes.8 @@ -0,0 +1,312 @@ +.TH SG_OPCODES "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_opcodes \- report supported SCSI commands or task management functions +.SH SYNOPSIS +.B sg_opcodes +[\fI\-\-alpha\fR] [\fI\-\-compact\fR] [\fI\-\-enumerate\fR] [\fI\-\-help\fR] +[\fI\-\-hex\fR] [\fI\-\-mask\fR] [\fI\-\-mlu\fR] [\fI\-\-no-inquiry\fR] +[\fI\-\-opcode=OP\fR] [\fI\-\-pdt=DT\fR] [\fI\-\-raw\fR] [\fI\-\-rctd\fR] +[\fI\-\-repd\fR] [\fI\-\-sa=SA\fR] [\fI\-\-tmf\fR] [\fI\-\-unsorted\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.PP +.B sg_opcodes +[\fI\-a\fR] [\fI\-c\fR] [\fI\-e\fR] [\fI\-H\fR] [\fI\-m\fR] [\fI\-M\fR] +[\fI\-n\fR] [\fI\-o=OP\fR] [\fI\-p=DT\fR] [\fI\-q\fR] [\fI\-R\fR] +[\fI\-s=SA\fR] [\fI\-t\fR] [\fI\-u\fR] [\fI\-v\fR] [\fI\-V\fR] [\fI\-?\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends a SCSI REPORT SUPPORTED OPERATION CODES or a REPORT +SUPPORTED TASK MANAGEMENT FUNCTIONS command to the \fIDEVICE\fR and then +outputs the response. The default action is to report supported operation +codes. In this mode it will either list all supported commands or give +detailed information on a specific command identified by the +\fI\-\-opcode=OP\fR option (perhaps with additional information from the +\fI\-\-sa=SA\fR option). +.PP +The name of a SCSI command depends on its peripheral device type (e.g. a +disk). The REPORT SUPPORTED OPERATION CODES and REPORT SUPPORTED TASK +MANAGEMENT FUNCTIONS commands are not supported in the MMC command set for +CD and DVD devices. This utility does an INQUIRY to obtain the peripheral +device type and prints out the vendor, product and revision strings. +.PP +A similar facility to query supported operation codes previously was available +via the CmdDt bit in the SCSI INQUIRY command (see sg_inq(8)). However that +facility was made obsolete and replaced by the REPORT SUPPORTED OPERATION +CODES command in SPC\-3 (revision 4) during February 2002. +.PP +This utility supports two command line syntaxes, the preferred one is +shown first in the synopsis and explained in this section. A later section +on the old command line syntax outlines the second group of options. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-a\fR, \fB\-\-alpha\fR +when all supported commands are being listed there is no requirement for +the device server (i.e. the \fIDEVICE\fR) to sort the list of commands. When +this option is given the list of supported commands is sorted by +name (alphabetically). When this option and the \fB\-\-unsorted\fR option are +both _not_ given then the list of supported commands is sorted +numerically (first by operation code and then by service action). +.TP +\fB\-c\fR, \fB\-\-compact\fR +some command names, especially those associated with some service actions, +are getting longer. This may cause line wrap in the one line per command +mode on some terminals. When this option is given the opcode and service +action fields are combined into a single field with the service action, +prefixed by a comma shown directly after the opcode. If there is no service +action associated with the command, then the comma and the service action +are not shown after the opcode. The CDB size field is not shown when this +option is given. +.TP +\fB\-e\fR, \fB\-\-enumerate\fR +this option prints the name of the SCSI command based on the given opcode, +peripheral device type and optionally the service action. If given, +\fIDEVICE\fR is ignored. The opcode, peripheral device type and service +action default to zero if not given. Thus if this option is the only option +given then "Test Unit ready" is output since its opcode is 0, it has no +service action and it is common to all peripheral device types since it is +defined in the SCSI Primary Commands (SPC) standard(s). +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options +then exits. Ignores \fIDEVICE\fR if given. +.TP +\fB\-H\fR, \fB\-\-hex\fR +outputs the response in ASCII hexadecimal to stdout. +.TP +\fB\-m\fR, \fB\-\-mask\fR +additionally prints out the cdb mask in hex. So a 12 byte cdb will have +a 12 byte hexadecimal mask. If the hexadecimal is expanded (mentally) +to binary then a "1" means the corresponding position in the cdb may +be set. And "0" means the corresponding position in the cdb must not +be set. For "0" mask positions that a user tries to set in a cdb, the +device may either ignore it or report an error, typically with a +sense key of "illegal request". +.TP +\fB\-M\fR, \fB\-\-mlu\fR +additionally prints out an indication (0 or 1) whether the command +effects all logical units in the containing target. MLU (Multiple Logical +Units) is a bit in the REPORT SUPPORTED OPERATION CODES response +introduced by proposal 18-045r1 (and possibly in spc5r20). Without +the option, the default output format which lists all opcodes, does +not include a MLU indication. +.TP +\fB\-n\fR, \fB\-\-no-inquiry\fR +Prior to calling a SCSI REPORT SUPPORTED OPERATION CODES or a REPORT +SUPPORTED TASK MANAGEMENT FUNCTIONS command, a SCSI INQUIRY command +is performed. The reason is to determine the peripheral device type (pdt) +of the \fIDEVICE\fR as this is helpful in translating operation codes +to the command names. By default this utility prints a summary of INQUIRY +command response on stdout. If this option (or the \fI\-\-raw\fR option) +is given then that summary is not printed on stdout. +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-o\fR, \fB\-\-opcode\fR=\fIOP\fR +the \fIDEVICE\fR will be queried for the given operation code (i.e. the +\fIOP\fR value) which is the first byte of a SCSI command. \fIOP\fR is +decimal unless prefixed by "0x" or it has a trailing "h". \fIOP\fR should +be in the range 0 to 255 (0xff) inclusive. When this option is not given +then all available SCSI commands supported by the \fIDEVICE\fR are listed. +.TP +\fB\-p\fR, \fB\-\-pdt\fR=\fIDT\fR +where \fIDT\fR is the peripheral device type. This is used together with +the \fI\-\-enumerate\fR to differentiate when a command opcode (and perhaps +service action) is shared by multiple device types. +.br +This option may also be used with the \fI\-\-no-inquiry\fR option to +suppress this utility doing an INQUIRY command since the main reason +for doing that is to find the peripheral device type of the \fIDEVICE\fR. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the response in binary to stdout. Error messages and warnings, if +any, are sent to stderr. +.TP +\fB\-R\fR, \fB\-\-rctd\fR +set report command timeout descriptor (RCTD) bit in the cdb. The response +may or may not contain command timeout descriptors. If available they are +output. If supported there are two values: a nominal command timeout +and a recommended command timeout. Both have units of seconds. A value +of zero means that no timeout is indicated and this is shown in +the corresponding decoded output as "\-". +.TP +\fB\-q\fR, \fB\-\-repd\fR +set read extended parameter data (REPD) bit in the report task management +functions cdb. 16 bytes rather than the default 4 bytes expected in the +response. This was added in SPC\-4 (revision 26). +.TP +\fB\-s\fR, \fB\-\-sa\fR=\fISA\fR +the \fIDEVICE\fR will be queried for a command with the given service +action (i.e. the \fISA\fR value). Used in conjunction with the +\fI\-\-opcode=OP\fR option. If this option is not given, \fI\-\-opcode=OP\fR +is given and the command in question does have a service action then a value +of 0 will be assumed. \fISA\fR is decimal and expected to be in the range 0 +to 65535 (0xffff) inclusive. +.TP +\fB\-t\fR, \fB\-\-tmf\fR +list supported task management functions. This is done with the SCSI REPORT +SUPPORTED TASK MANAGEMENT FUNCTIONS command. When this option is chosen +the \fI\-\-alpha\fR, \fI\-\-opcode=OP\fR, \fI\-\-rctd\fR, \fI\-\-sa=SA\fR +and \fI\-\-unsorted\fR options are ignored. +.TP +\fB\-u\fR, \fB\-\-unsorted\fR +when all supported commands are being listed there is no requirement for +the device server (i.e. the \fIDEVICE\fR) to sort the list of commands. When +this option is given the list of supported commands is in the order given by +the \fIDEVICE\fR. When this option is not given the supported commands +are sorted numerically (first by operation code and then by service action). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH NOTES +As of SPC\-5 revision 8 the recognized task management functions are: +abort set, abort task set, clear ACA, clear task set, logical unit reset, +query task, query asynchronous event, query task set, and I_T nexus reset. +In SPC\-4 revision 26 target reset and wakeup task management functions +were made obsolete. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI +generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks +and DVD drives) can also be specified. For example "sg_opcodes /dev/sda" +will work in the 2.6 series kernels. +.SH EXIT STATUS +The exit status of sg_opcodes is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-a\fR +sort command alphabetically. Equivalent to \fI\-\-alpha\fR in main +description. +.TP +\fB\-c\fR +see the \fI\-\-compact\fR option above. +.TP +\fB\-e\fR +see the \fI\-\-enumerate\fR option above. +.TP +\fB\-H\fR +see the \fI\-\-hex\fR option above. +.TP +\fB\-m\fR +see the \fI\-\-mask\fR option above. +.TP +\fB\-n\fR +don't print a summary of the SCSI INQUIRY response on stdout. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-o\fR=\fIOP\fR +the \fIDEVICE\fR will be queried for the given operation code (i.e. +\fIOP\fR) which is the first byte of a SCSI command. \fIOP\fR is +hexadecimal and expected to be in the range 0 to ff inclusive. +When this option is not given then all available SCSI commands supported +by the \fIDEVICE\fR are listed. +.TP +\fB\-p\fR=\fIDT\fR +see the \fI\-\-pdt=DT\fR option above. +.TP +\fB\-q\fR +set the read extended parameter data (REPD) bit in report TMF cdb. +Equivalent to \fI\-\-repd\fR in main description. +.TP +\fB\-R\fR +set the report command timeout descriptor (RCTD) bit in cdb. Equivalent +to \fI\-\-rctd\fR in main description. +.TP +\fB\-s\fR=\fISA\fR +the \fIDEVICE\fR will be queried for a command with the given service +action (i.e. \fISA\fR). Used in conjunction with the \fI\-o=OP\fR +option. If this option is not given, \fI\-o=OP\fR is given and the command +in question does have a service action then a value of 0 will be assumed. +\fISA\fR is hexadecimal and expected to be in the range 0 to ffff inclusive. +.TP +\fB\-t\fR +list supported task management functions. Equivalent to \fI\-\-tmf\fR in +the main description. +.TP +\fB\-u\fR +output all supported commands in the order given by \fIDEVICE\fR. +Equivalent to \fI\-\-unsorted\fR in main description. +.TP +\fB\-v\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR +print out version string then exit. +.TP +\fB\-?\fR +output usage message. Ignore all other parameters. +.SH EXAMPLES +The examples in this page use Linux device names. For suitable device +names in other supported Operating Systems see the sg3_utils(8) man page. +.PP +To see the information about a specific command give its operation +code to the '\-\-op=' option. A command line invocation is shown first +followed by a typical response: +.PP + # sg_opcodes \-\-op=93h /dev/sdb +.PP + Opcode=0x93 +.br + Command_name: Write same(16) +.br + Command supported [conforming to SCSI standard] +.br + Usage data: 93 e2 00 00 00 00 ff ff ff ff 00 00 ff ff 00 00 +.PP +The next example shows the supported task management functions: +.PP + # sg_opcodes \-\-tmf \-n /dev/sdb +.PP +Task Management Functions supported by device: +.br + Abort task +.br + Abort task set +.br + Clear ACA +.br + Clear task set +.br + Logical unit reset +.br + Query task +.PP +Enumerate can be used to look up a SCSI command name in the absence of a +device that supports that command. The opcode and service action (if +required) should be supplied: +.PP + # sg_opcodes \-\-enumerate \-\-op=0x9b,0xa +.PP + SCSI command: +.br + Read buffer(16), read data from echo buffer +.br +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq(sg3_utils) diff --git a/doc/sg_persist.8 b/doc/sg_persist.8 new file mode 100644 index 0000000..36d784b --- /dev/null +++ b/doc/sg_persist.8 @@ -0,0 +1,436 @@ +.TH SG_PERSIST "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_persist \- use SCSI PERSISTENT RESERVE command to access registrations +and reservations +.SH SYNOPSIS +.B sg_persist +[\fIOPTIONS\fR] \fIDEVICE\fR +.PP +.B sg_persist +[\fIOPTIONS\fR] \fI\-\-device=DEVICE\fR +.PP +.B sg_persist +\fI\-\-help\fR | \fI\-\-version\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility allows Persistent reservations and registrations to be +queried and changed. Persistent reservations and registrations are +queried by sub\-commands (called "service actions" in SPC\-4) of the +SCSI PERSISTENT RESERVE IN (PRIN) command. Persistent reservations and +registrations are changed by sub\-commands of the SCSI PERSISTENT RESERVE +OUT (PROUT) command. +.PP +There is a two stage process to obtain a persistent reservation. First an +application (an I_T nexus in standard's jargon) must register a reservation +key. If that is accepted (and it should be unless some other I_T nexus has +registered that key) then the application can try and reserve the device. +The reserve operation must specify the reservation key and a "type" (see +the \fI\-\-prout\-type=TYPE\fR option). +.PP +It is relatively safe to query the state of Persistent reservations and +registrations. With no options this utility defaults to the READ KEYS +sub\-command of the PRIN command. Other PRIN sub\-commands are +READ RESERVATION, REPORT CAPABILITIES and READ FULL STATUS. +.PP +Before trying to change Persistent reservations and registrations users +should be aware of what they are doing. The relevant sections of the SCSI +Primary Commands document (i.e. SPC\-5 whose most recent draft is revision +18 dated 4 January 2018) are sections 5.14 (titled "Reservations"), +6.16 (for the PRIN command) and 6.17 (for the PROUT command). To safeguard +against accidental use, the \fI\-\-out\fR option must be given when a +PROUT sub\-command (e.g. \fI\-\-register\fR) is used. +.PP +The older SCSI RESERVE and RELEASE commands (both 6 and 10 byte variants) +are not supported by this utility. In SPC\-3, RESERVE and RELEASE are +deprecated, replaced by Persistent Reservations. RESERVE and RELEASE +have been removed from SPC\-4 and Annex B is provided showing how to +convert to persistent reservation commands. See a utility +called 'scsires' for support of the SCSI RESERVE and RELEASE commands. +.PP +The \fIDEVICE\fR is required by all variants of this utility apart +from \fI\-\-help\fR. The \fIDEVICE\fR can be given either as an +argument (typically but not necessarily the last one) or via +the \fI\-\-device=DEVICE\fR option. +.PP +SPC\-4 does not use the term "sub\-command". It uses the term "service action" +for this and for part of a field's name in the parameter block associated +with the PROUT command (i.e. "service action reservation key"). To lessen +the potential confusion the term "sub\-command" has been introduced. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The following options are sorted in alphabetical order, based on their +long option name. +.TP +\fB\-l\fR, \fB\-\-alloc-length\fR=\fILEN\fR +specify the allocation length of the PRIN command. \fILEN\fR is a hex value. +By default this value is set to the size of the data\-in buffer (8192). +This parameter is of use for verification that response to PRIN commands +with various allocation lengths is per section 4.3.5.6 of SPC\-4 revision 18. +Valid \fILEN\fR values are 0\-8192. +.TP +\fB\-C\fR, \fB\-\-clear\fR +Clear is a sub\-command of the PROUT command. It releases the +persistent reservation (if any) and clears all registrations from the +device. It is required to supply a reservation key that is registered +for this I_T_L nexus (identified by \fI\-\-param\-rk=RK\fR). +.TP +\fB\-d\fR, \fB\-\-device\fR=\fIDEVICE\fR +\fIDEVICE\fR to send SCSI commands to. The \fIDEVICE\fR can either be +provided via this option or via a freestanding argument. For example, +these two: 'sg_persist \-\-device=/dev/sg2' and 'sg_persist /dev/sg2' +are equivalent. +.TP +\fB\-h\fR, \fB\-\-help\fR +output a usage message showing main options. Use twice (e.g. '\-hh') for +the other option and more help. +.TP +\fB\-H\fR, \fB\-\-hex\fR +the response to a valid PRIN sub\-command will be output in hexadecimal. +By default (i.e. without this option) if the PRIN sub\-command is recognised +then the response will be decoded as per SPC\-4. May be used more than +once for more hex and less text. +.TP +\fB\-i\fR, \fB\-\-in\fR +specify that a SCSI PERSISTENT RESERVE IN command is required. This +is the default. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +\fILEN\fR is used as the ALLOCATION LENGTH field of the PRIN command. +\fILEN\fR is by default a decimal value. To give a hex value use a '0x' +or '0X' prefix, or use a 'h' (or 'H') suffix. Can also take multipliers, +see \fI\-\-maxlen=LEN\fR option in the sg3_utils manual page. +.br +This option is the same as \fI\-\-alloc\-length=LEN\fR option apart from +the representation of \fILEN\fR. The option defaults to decimal while +\fI\-\-alloc\-length=LEN\fR only takes hex. +.TP +\fB\-n\fR, \fB\-\-no\-inquiry\fR +the default action is to do a standard SCSI INQUIRY command and output +make, product and revision strings plus the peripheral device type +prior to executing a PRIN or PROUT command. With this option the +INQUIRY command is skipped. +.TP +\fB\-o\fR, \fB\-\-out\fR +specify that a SCSI PERSISTENT RESERVE OUT command is required. +.TP +\fB\-Y\fR, \fB\-\-param\-alltgpt\fR +set the 'all target ports' (ALL_TG_PT) flag in the parameter block of the +PROUT command. Only relevant for 'register' and 'register and ignore existing +key' sub\-commands. +.TP +\fB\-Z\fR, \fB\-\-param\-aptpl\fR +set the 'activate persist through power loss' (APTPL) flag in the parameter +block of the PROUT command. Relevant for 'register', 'register and ignore +existing key' and 'register and move' sub\-commands. +.TP +\fB\-K\fR, \fB\-\-param\-rk\fR=\fIRK\fR +specify the reservation key found in the parameter block of the PROUT +command. \fIRK\fR is assumed to be hex (up to 8 bytes long). Default value +is 0. This option is needed by most PROUT sub\-commands. +.TP +\fB\-S\fR, \fB\-\-param\-sark\fR=\fISARK\fR +specify the service action reservation key found in the parameter block +of the PROUT command. \fISARK\fR is assumed to be hex (up to 8 bytes long). +Default value is 0. This option is needed by some PROUT sub\-commands. +.TP +\fB\-P\fR, \fB\-\-preempt\fR +Preempt is a sub\-command of the PROUT command. Preempts the existing +persistent reservation (identified by \fI\-\-param\-sark=SARK\fR) with +the registration key that is registered for this I_T_L nexus (identified +by \fI\-\-param\-rk=RK\fR). If a new reservation is established as +a result of the preemption then the supplied \fI\-\-prout\-type=TYPE\fR +is used as the type for this new reservation. +.TP +\fB\-A\fR, \fB\-\-preempt\-abort\fR +Preempt and Abort is a sub\-command of the PROUT command. Preempts +the existing persistent reservation (identified by \fI\-\-param\-sark=SARK\fR) +with the registration key that is registered for this I_T_L nexus (identified +by \fI\-\-param\-rk=RK\fR). If a new reservation is established as +a result of the preemption then the supplied \fI\-\-prout\-type=TYPE\fR +is used as the type for this new reservation. ACA and other pending +tasks are aborted. +.TP +\fB\-T\fR, \fB\-\-prout\-type\fR=\fITYPE\fR +specify the PROUT command's 'type' argument. Required by +the 'register\-move', 'reserve', 'release' and 'preempt (and abort)' +sub\-commands. Valid \fITYPE\fR values: 1\-> write exclusive, 3\-> +exclusive access, 5\-> write exclusive \- registrants only, 6\-> +exclusive access \- registrants only, 7\-> write exclusive \- all registrants, +8\-> exclusive access \- all registrants. Default value is 0 (which is +an invalid type). Each "persistent reservation type" is explained in more +detail in a subsection of that name in the read reservation section of +the PRIN command (section 6.15.3.3 of SPC\-4 revision 37). +.TP +\fB\-s\fR, \fB\-\-read\-full\-status\fR +Read Full Status is a sub\-command of the PRIN command. For each registration +with the given SCSI device, it lists the reservation key and associated +information. TransportIDs, if supplied in the response, are decoded. +.TP +\fB\-k\fR, \fB\-\-read\-keys\fR +Read Keys is a sub\-command of the PRIN command. Lists all the reservation +keys registered (i.e. registrations) with the given SCSI device. This is +the default sub\-command for the SCSI PRIN command. +.TP +\fB\-y\fR, \fB\-\-readonly\fR +Open \fIDEVICE\fR read\-only. May be useful with PRIN commands if there are +unwanted side effects with the default read\-write open. When given twice +is interpreted as forcing a read\-write open thus overriding the +SG_PERSIST_IN_RDONLY environment variable if present. See the ENVIRONMENT +VARIABLES section for more. +.TP +\fB\-r\fR, \fB\-\-read\-reservation\fR +Read Reservation is a sub\-command of the PRIN command. List information +about the current holder of the reservation on the \fIDEVICE\fR. If there +is no current reservation this will be noted. Information about the current +holder of the reservation includes its reservation key, scope and type. +.TP +\fB\-s\fR, \fB\-\-read\-status\fR +same as \fI\-\-read\-full\-status\fR. +.TP +\fB\-G\fR, \fB\-\-register\fR +Register is a sub\-command of the PROUT command. It has 3 different +actions depending on associated parameters. a) add a new registration +with '\-\-param\-rk=0' and '\-\-param\-sark='; b) Change an existing +registration with '\-\-param\-rk=' +and '\-\-param\-sark='; or c) Delete an existing registration +with '\-\-param\-rk=' and '\-\-param\-sark=0'. +.TP +\fB\-I\fR, \fB\-\-register\-ignore\fR +Register and Ignore Existing Key is a sub\-command of the PROUT command. +Similar to \fI\-\-register\fR except that when changing a reservation key +the old key is not specified. The '\-\-param\-sark=' option should +also be given. +.TP +\fB\-M\fR, \fB\-\-register\-move\fR +register (another initiator) and move (the reservation held by the current +initiator to that other initiator) is a sub\-command of the PROUT command. +It requires the transportID of the other initiator. [The standard uses the +term I_T nexus but the point to stress is that there are two initiators +(the one sending this command and another one) but only one logical unit.] +The \fI\-\-prout\-type=TYPE\fR and \fI\-\-param\-rk=RK\fR options need to +match that of the existing reservation while \fI\-\-param\-sark=SARK\fR +option specifies the reservation key of the new (i.e. destination) +registration. +.TP +\fB\-Q\fR, \fB\-\-relative\-target\-port\fR=\fIRTPI\fR +relative target port identifier that reservation is to be moved to by +PROUT 'register and move' sub\-command. \fIRTPI\fR is assumed to be hex +in the range 0 to ffff inclusive. Defaults to 0 . +.TP +\fB\-L\fR, \fB\-\-release\fR +Release is a sub\-command of the PROUT command. It releases the +current persistent reservation. The \fI\-\-prout\-type=TYPE\fR +and \fI\-\-param\-rk=RK\fR options, matching the reservation, must also be +specified. +.TP +\fB\-z\fR, \fB\-\-replace\-lost\fR +Replace Lost Reservation is a sub\-command of the PROUT command. It "begins +a recovery process for the lost persistent reservation that is managed by +application clients". It also stops the device server terminating commands +due to a lost persistent reservation. Options should be +be '\-\-param\-rk=0' (or not given), '\-\-param\-sark=' +and \fI\-\-prout\-type=TYPE\fR. +.TP +\fB\-c\fR, \fB\-\-report\-capabilities\fR +Report Capabilities is a sub\-command of the PRIN command. It lists +information about the aspects of persistent reservations that the +\fIDEVICE\fR supports. +.TP +\fB\-R\fR, \fB\-\-reserve\fR +Reserve is a sub\-command of the PROUT command. It creates a new +persistent reservation (if permitted). The \fI\-\-prout\-type=TYPE\fR +and \fI\-\-param\-rk=RK\fR options must also be specified. +.TP +\fB\-X\fR, \fB\-\-transport\-id\fR=\fITIDS\fR +The \fITIDS\fR argument can take one of several forms. It can be a +comma (or single space) separated list of ASCII hex bytes representing +a single TransportID as defined in SPC\-4. They are usually 24 bytes +long apart from in iSCSI. The \fITIDS\fR argument may be a transport +specific form (e.g. "sas,5000c50005b32001" is clearer than an equivalent +to the hex byte form: "6,0,0,0,5,0,c5,0,5,b3,20,1"). The \fITIDS\fR argument +may be "\-" in which case one or more TransportIDs can be read from stdin. +The \fITIDS\fR argument may be of the form "file=" in which case +one or more TransportIDs can be read from a file called . See +the "TRANSPORT IDs" section below for more information. +.TP +\fB\-U\fR, \fB\-\-unreg\fR +optional when the PROUT register and move sub\-command is invoked. If given +it will unregister the current initiator (I_T nexus) after the other initiator +has been registered and the reservation moved to it. When not given the +initiator (I_T nexus) that sent the PROUT command remains registered. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +print out cdb of issued commands prior to execution. If used twice +prints out the parameter block associated with the PROUT command prior +to its execution as well. If used thrice decodes given transportID(s) +as well. To see the response to a PRIN command in low level form use +the \fI\-\-hex\fR option. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string. Ignore all other parameters. +.TP +\fB\-?\fR +output usage message. Ignore all other parameters. +.SH TRANSPORT IDs +TransportIDs are used in persistent reservations to identify initiators. +The format of a TransportID differs depending on the type of transport +being used. Their format is described in SPC\-4 (in draft revision 37 see +section 7.6.4). +.PP +A TransportID is required for the PROUT 'register and move' sub\-command and +the PROUT 'register' sub\-command can have zero, one or more TransportIDs. +.PP +When the \fI\-\-transport\-id=TIDS\fR option is given then the \fITIDS\fR +argument may be a comma (or single space) separated list of ASCII hex bytes +that represent a single TransportID as defined in SPC\-4. Alternatively the +\fITIDS\fR argument may be a transport specific string starting with +either "fcp,", "spi,", "sbp,", "srp,", "iqn", "sas," or "sop,". The "iqn" +form is an iSCSI qualified name. Apart from "iqn" the other transport +specific leadin string may be given in upper case (e.g. "FCP,"). +.PP +The "fcp," form should be followed by 16 ASCII hex digits that represent an +initiator's N_PORT_NAME (e.g. "fcp,10000000C9F3A571"). The "spi," form should +be followed by "," (both +decimal numbers). The "sbp," form should be followed by 16 ASCII hex digits +that represent an initiator's EUI\-64 name. The "srp," form should be +followed by 32 ASCII hex digits that represent an initiator port identifier. +The "sas," form should be followed by 16 ASCII hex digits that represent an +initiator's port SAS address (e.g. "sas,5000c50005b32001"). The "sop," form +takes a hex number that represents a routing id. +.PP +There are two iSCSI qualified name forms. The shorter form contains the +iSCSI name of the initiator +port (e.g. "iqn.5886.com.acme.diskarrays\-sn\-a8675309"). The longer form +adds the initiator session id (ISID in hex) separated by ",i,0x". +For example "iqn.5886.com.acme.diskarrays\-sn\-a8675309,i,0x1234567890ab". +On the command line to stop punctuation in an iSCSI name +being (mis)\-interpreted by the shell, putting the option argument +containing the iSCSI name in double quotes is advised. iSCSI names are +encoded in UTF\-8 so if non (7 bit) ASCII characters appear in the +iSCSI name on the command line, there will be difficulties if they are not +encoded in UTF\-8. The locale can be changed temporarily by prefixing the +command line invocation of sg_persist with "LANG=en_US.utf\-8" for example. +.PP +Alternatively the \fITIDS\fR argument may specify a file (or pipe) from which +one or more TransportIDs may be read. If the \fITIDS\fR argument is "\-" +then stdin (standard input) is read. If the \fITIDS\fR argument is of the +form "file=" then a file called is read. +A valid SPC\-4 TransportID is built from the transport specific string +outlined in the previous paragraphs. The parsing of the data read is +relatively simple. Empty lines are ignored. Everything from and including +a "#" on a line is ignored. Leading spaces and tabs are ignored. There +can be one transportID per line. The transportID can either be a comma, +space or tab separated list of ASCII hex bytes that represent a +TransportID as defined in SPC\-4. Padding with zero bytes to a minimum +length of 24 bytes is performed if necessary. The transportID may also +be transport specific string type discussed above. +.PP +In SPC\-3 the SPEC_I_PT bit set to one and TransportIDs were allowed for +the PROUT register and ignore existing key sub\-command. In SPC\-4 that +is disallowed yielding a CHECK CONDITION status with and ILLEGAL REQUEST +sense key and an additional sense code set to INVALID FIELD IN PARAMETER +LIST. +.SH NOTES +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be +a SCSI generic (sg) device. In the 2.6 series any SCSI device +name (e.g. /dev/sdc, /dev/st1m or /dev/sg3) can be specified. +For example "sg_persist \-\-read\-keys /dev/sdb" +will work in the 2.6 series kernels. +.PP +The only scope for PROUT commands supported in the current draft of +SPC\-4 is "LU_SCOPE". Hence there seems to be no point in offering an +option to set scope to another value. +.PP +Most errors with the PROUT sub\-commands (e.g. missing or +mismatched \fI\-\-prout\-type=TYPE\fR) will result in a RESERVATION +CONFLICT status. This can be a bit confusing when you know there is +only one (active) initiator: the "conflict" is with the SPC standard, not +another initiator. +.PP +Some recent disks accept some PRIN and PROUT sub\-commands when the +media is stopped. One exception was setting the APTPL flag (with +the \fI\-\-param\-aptpl\fR option) during a key register operation, +it complained if the disk one stopped. The error indicated it wanted +the disk spun up and when that happened, the registration was +successful. +.SH EXAMPLES +These examples use Linux device names. For suitable device names in +other supported Operating Systems see the sg3_utils(8) man page. +.PP +Due to the various option defaults the simplest example executes +the 'read keys' sub\-command of the PRIN command: +.PP + sg_persist /dev/sdb +.PP +This is the same as the following (long\-winded) command: +.PP + sg_persist \-\-in \-\-read\-keys \-\-device=/dev/sdb +.PP +To read the current reservation either the '\-\-read\-reservation' form or +the shorter '\-r' can be used: +.PP + sg_persist \-r /dev/sdb +.PP +To +.B register +the new reservation key 0x123abc the following could be used: +.PP + sg_persist \-\-out \-\-register \-\-param\-sark=123abc /dev/sdb +.PP +Given the above registration succeeds, to +.B reserve +the \fIDEVICE\fR (with type 'write exclusive') the following +could be used: +.PP + sg_persist \-\-out \-\-reserve \-\-param\-rk=123abc +.br + \-\-prout\-type=1 /dev/sdb +.PP +To +.B release +the reservation the following can be given (note that +the \-\-param\-rk and \-\-prout\-type arguments must match those of the +reservation): +.PP + sg_persist \-\-out \-\-release \-\-param\-rk=123abc +.br + \-\-prout\-type=1 /dev/sdb +.PP +Finally to +.B unregister +a reservation key (and not effect other +registrations which is what '\-\-clear' would do) the command +is a little surprising: +.PP + sg_persist \-\-out \-\-register \-\-param\-rk=123abc /dev/sdb +.PP +Now have a close look at the difference between the register and +unregister examples above. +.PP +An example file that is suitably formatted to pass transportIDs via +a '\-\-transport\-id=file=transport_ids.txt' option can be found in the +examples sub\-directory of the sg3_utils package. There is also a +simple test script called sg_persist_tst.sh in the same directory. +.PP +The above sequence of commands was tested successfully on a Seagate Savvio +10K.3 disk and a 1200 SSD both of which have SAS interfaces. +.SH EXIT STATUS +The exit status of sg_persist is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH ENVIRONMENT VARIABLES +Currently there is one recognised environment variable: SG_PERSIST_IN_RDONLY. +If present and only if a PRIN command has been selected then the +given \fIDEVICE\fR is opened read\-only (e.g. in Unix that is with the +O_RDONLY flag). See the \fI\-\-readonly\fR option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg3_utils(sg3_utils), scsires(internet) diff --git a/doc/sg_prevent.8 b/doc/sg_prevent.8 new file mode 100644 index 0000000..3548ffb --- /dev/null +++ b/doc/sg_prevent.8 @@ -0,0 +1,59 @@ +.TH SG_PREVENT "8" "November 2012" "sg3_utils\-1.35" SG3_UTILS +.SH NAME +sg_prevent \- send SCSI PREVENT ALLOW MEDIUM REMOVAL command +.SH SYNOPSIS +.B sg_prevent +[\fI\-\-allow\fR] [\fI\-\-help\fR] [\fI\-\-prevent=PC\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI PREVENT ALLOW MEDIUM REMOVAL command to \fIDEVICE\fR. +The default action of this utility is to prevent the removing or +ejecting of the medium from a drive. This is done by ignoring the +SCSI START STOP UNIT command (see sg_start) and ignoring the eject +button on the drive when the user presses it. Drives that hold removable +disks, tape cartridges or cd/dvd media typically implement this command. +The definition of the "prevent" codes for this command differ between +disks and tapes (covered by SBC\-3 and SSC\-3) and cd/dvd drives (covered +by MMC\-5). The "prevent codes" described here are from MMC\-5. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-a\fR, \fB\-\-allow\fR +allow medium removal. This is equivalent to setting to '\-\-prevent=2'. +Cannot be used with \fI\-\-prevent=PC\fR option (i.e. either use +no options (hence prevent removal), this option or \fI\-\-prevent=PC\fR). +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-p\fR, \fB\-\-prevent\fR=\fIPC\fR +where \fIPC\fR is a prevent code value. Defined values are: 0 allows removal, +1 prevents removal (default), 2 allows persistent removal while 3 prevents +persistent removal. "Persistent" in this context means that the +initiator (port) that successfully uses code 3 blocks other initiators (ports) +from allowing removal. A "persistent prevent" state can be cleared by the +owner allowing persistent removal (code 2) or a power cycle (or anything that +resets the device (LU)) or some special commands (e.g. various service +actions of Persistent Reserve Out, see SPC\-3). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH EXIT STATUS +The exit status of sg_prevent is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2012 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_start(sg3_utils), sg_persist(sg3_utils) diff --git a/doc/sg_raw.8 b/doc/sg_raw.8 new file mode 100644 index 0000000..b6bdbcc --- /dev/null +++ b/doc/sg_raw.8 @@ -0,0 +1,218 @@ +.TH SG_RAW "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_raw \- send arbitrary SCSI command to a device +.SH SYNOPSIS +.B sg_raw +[\fI\-\-binary\fR] [\fI\-\-cmdfile=CF\fR] [\fI\-\-enumerate\fR] +[\fI\-\-help\fR] [\fI\-\-infile=IFILE\fR] [\fI\-\-nosense\fR] +[\fI\-\-outfile=OFILE\fR] [\fI\-\-readonly\fR] [\fI\-\-request=RLEN\fR] +[\fI\-\-send=SLEN\fR] [\fI\-\-skip=KLEN\fR] [\fI\-\-timeout=SECS\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR [CDB0 CDB1 ...] +.SH DESCRIPTION +This utility sends an arbitrary SCSI command (between 6 and 256 bytes) to +the \fIDEVICE\fR. There may be no associated data transfer; or data may be +read from a file and sent to the \fIDEVICE\fR; or data may be received from +the \fIDEVICE\fR and then displayed or written to a file. If supported +by the pass through, bidirectional commands may be sent (i.e. containing +both data to be sent to the \fIDEVICE\fR and received from the +\fIDEVICE\fR). +.PP +The SCSI command may be between 6 and 256 bytes long. Each command byte is +specified in plain hex format (00..FF) without a prefix or suffix. The +command can be given either on the command line or via the +\fI\-\-cmdfile=CF\fR option. See EXAMPLES section below. +.PP +The commands pass through a generic SCSI interface which is implemented +for several operating systems including Linux, FreeBSD and Windows. +.PP +Experimental support has been added to send NVMe Admin commands to the +\fIDEVICE\fR. Since all NVMe commands are 64 bytes long it is more +convenient to use the \fI\-\-cmdfile=CF\fR option rather than type the +64 bytes of the NVMe command on the command line. See the section on +NVME below. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-b\fR, \fB\-\-binary\fR +Dump data in binary form, even when writing to stdout. +.TP +\fB\-c\fR, \fB\-\-cmdfile\fR=\fICF\fR +\fICF\fR is the name of a file which contains the command to be executed. +Without this option the command must be given on the command line, after +the options and the \fIDEVICE\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display usage information and exit. +.TP +\fB\-i\fR, \fB\-\-infile\fR=\fIIFILE\fR +Read data from \fIIFILE\fR instead of stdin. This option is ignored if +\fB\-\-send\fR is not specified. +.TP +\fB\-n\fR, \fB\-\-nosense\fR +Don't display SCSI Sense information. +.TP +\fB\-o\fR, \fB\-\-outfile\fR=\fIOFILE\fR +Write data received from the \fIDEVICE\fR to \fIOFILE\fR. The data is +written in binary. By default, data is dumped in hex format to stdout. +If \fIOFILE\fR is '\-' then data is dumped in binary to stdout. +This option is ignored if \fI\-\-request\fR is not specified. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +Open \fIDEVICE\fR read\-only. The default (without this option) is to open +it read\-write. +.TP +\fB\-r\fR, \fB\-\-request\fR=\fIRLEN\fR +Expect to receive up to \fIRLEN\fR bytes of data from the \fIDEVICE\fR. +\fIRLEN\fR may be suffixed with 'k' to use kilobytes (1024 bytes) instead +of bytes. \fIRLEN\fR is decimal unless it has a leading '0x' or a +trailing 'h'. +.br +If \fIRLEN\fR is too small (i.e. either smaller than indicated by the +cdb (typically the "allocation length" field) and/or smaller than the +\fIDEVICE\fR tries to send back) then the HBA driver may complain. Making +\fIRLEN\fR larger than required should cause no problems. Most +SCSI "data\-in" commands return a data block that contains (in its early +bytes) a length that the \fIDEVICE\fR would "like" to send back if +the "allocation length" field in the cdb is large enough. In practice, the +\fIDEVICE\fR will return no more bytes than indicated in the "allocation +length" field of the cdb. +.TP +\fB\-s\fR, \fB\-\-send\fR=\fISLEN\fR +Read \fISLEN\fR bytes of data, either from stdin or from a file, and send +them to the \fIDEVICE\fR. In the SCSI transport, \fISLEN\fR becomes the +length (in bytes) of the "data\-out" buffer. \fISLEN\fR is decimal unless +it has a leading '0x' or a trailing 'h'. +.br +It is the responsibility of the user to make sure that the "data\-out" +length implied or stated in the cdb matches \fISLEN\fR. Note that some +common SCSI commands such as WRITE(10) have a "transfer length" field whose +units are logical blocks (which are often 512 bytes long). +.TP +\fB\-k\fR, \fB\-\-skip\fR=\fIKLEN\fR +Skip the first \fIKLEN\fR bytes of the input file or stream. This option +is ignored if \fI\-\-send\fR is not specified. If \fI\-\-send\fR is given +and this option is not given, then zero bytes are skipped. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR +Wait up to \fISECS\fR seconds for command completion (default: 20). +Note that if a command times out the operating system may start by +aborting the command and if that is unsuccessful it may attempt +to reset the device. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version and license information and exit. +.SH NOTES +The sg_inq utility can be used to send an INQUIRY command to a device +to determine its peripheral device type (e.g. '1' for a streaming +device (tape drive)) which determines which SCSI command sets a device +should support (e.g. SPC and SSC). The sg_vpd utility reads and decodes +a device's Vital Product Pages which may contain useful information. +.PP +The ability to send more than a 16 byte CDB (in some cases 12 byte CDB) +may be restricted by the pass\-through interface, the low level driver +or the transport. In the Linux series 3 kernels, the bsg driver can +handle longer CDBs, block devices (e.g. /dev/sdc) accessed via the +SG_IO ioctl cannot handle CDBs longer than 16 bytes, and the sg driver +can handle longer CDBs from lk 3.17 . +.PP +The CDB command name defined by T10 for the given CDB is shown if +the '\-vv' option is given. The command line syntax still needs to be +correct, so /dev/null may be used for the \fIDEVICE\fR since the CDB +command name decoding is done before the \fIDEVICE\fR is checked. +.SH NVME SUPPORT +Support for NVMe (a.k.a. NVM Express) is currently experimental. NVMe +concepts map reasonably well to the SCSI architecture. A SCSI logical +unit (LU) is similar to a NVMe namespace (although LUN 0 is very common +in SCSI while namespace IDs start at 1). A SCSI target device is similar +to a NVMe controller. SCSI commands vary from 6 to 260 bytes long (although +SCSI command descriptor blocks (cdb_s) longer than 32 bytes are uncommon) +while all NVMe commands are currently 64 bytes long. The SCSI architecture +makes a clear distinction between an initiator (often called a HBA) and +a target (device) while (at least on the PCIe transport) the NVMe +controller plays both roles. At this time this utility only +supports "Admin" commands (i.e. it does not support the I/O (or "NVM") +command set). Admin commands are sent to submission queue 0 while non\-admin +commands are sent to submissions greater than 0. +.PP +One significant difference is that SCSI uses a big endian representation +for integers that are longer than 8 bits (i.e. longer than 1 byte) while +NVMe uses a little endian representation (like most things that have +originated from the Intel organisation). NVMe specifications talk about +Words (16 bits), Double Words (32 bits) and sometimes Quad Words (64 +bits) and has tighter alignment requirements than SCSI. +.PP +One difference that impacts this utility is that NVMe places pointers to +host memory in its commands while SCSI leaves this detail to whichever +transport it is using (e.g. SAS, iSCSI, SRP). Since this utility takes +the command from the user (either on the command line or in a file named +\fICF\fR) but this utility allocates a data\-in or data\-out buffer as +required, the user does not know in advance what the address of that +buffer will be. Some special addresses have been introduced to help with +this problem: the address 0xfffffffffffffffe is interpreted as "use the +data\-in buffer's address" while 0xfffffffffffffffd is interpreted as "use +the data\-out buffer's address". Since NVMe uses little endian notation +then that first address appears in the NVMe command byte stream as "fe" +followed by seven "ff"s. A similar arrangement is made for the length +of that buffer, but since that is a 32 byte quantity, the first 4 +bytes (all "ff"s) are removed. +.PP +Two command file examples can be found in the examples directory of this +package's source tarball: nvme_identify_ctl.hex and nvme_dev_self_test.hex . +.SH EXAMPLES +These examples, apart from the last one, use Linux device names. For +suitable device names in other supported Operating Systems see the +sg3_utils(8) man page. +.TP +sg_raw /dev/scd0 1b 00 00 00 02 00 +Eject the medium in CD drive /dev/scd0. +.TP +sg_raw \-r 1k /dev/sg0 12 00 00 00 60 00 +Perform an INQUIRY on /dev/sg0 and dump the response data (up to +1024 bytes) to stdout. +.TP +sg_raw \-s 512 \-i i512.bin /dev/sda 3b 02 00 00 00 00 00 02 00 00 +Showing an example of writing 512 bytes to a sector on a disk +is a little dangerous. Instead this example will read i512.bin (assumed +to be 512 bytes long) and use the SCSI WRITE BUFFER command to send +it to the "data" buffer (that is mode 2). This is a safe operation. +.TP +sg_raw \-r 512 \-o o512.bin /dev/sda 3c 02 00 00 00 00 00 02 00 00 +This will use the SCSI READ BUFFER command to read 512 bytes from +the "data" buffer (i.e. mode 2) then write it to the o512.bin file. +When used in conjunction with the previous example, if both commands +work then 'cmp i512.bin o512.bin' should show a match. +.TP +sg_raw \-\-infile=urandom.bin \-\-send=512 \-\-request=512 \-\-outfile=out.bin "/dev/bsg/7:0:0:0" 53 00 00 00 00 00 00 00 01 00 +This is a bidirectional XDWRITEREAD(10) command being sent via a Linux +bsg device. Note that data is being read from "urandom.bin" and sent +to the device (data\-out) while resulting data (data\-in) is placed +in the "out.bin" file. Also note the length of both is 512 bytes +which corresponds to the transfer length of 1 (block) in the cdb (i.e. +the second last byte). +.TP +sg_raw.exe PhysicalDrive1 a1 0c 0e 00 00 00 00 00 00 e0 00 00 +This example is from Windows and shows a ATA STANDBY IMMEDIATE command +being sent to PhysicalDrive1. That ATA command is contained within +the SCSI ATA PASS\-THROUGH(12) command (see the SAT or SAT\-2 standard at +http://www.t10.org). Notice that the STANDBY IMMEDIATE command does not +send or receive any additional data, however if it fails sense data +should be returned and displayed. +.SH EXIT STATUS +The exit status of sg_raw is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHOR +Written by Ingo van Lil +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2001\-2018 Ingo van Lil +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq, sg_vpd, sg3_utils (sg3_utils), plscsi diff --git a/doc/sg_rbuf.8 b/doc/sg_rbuf.8 new file mode 100644 index 0000000..078d8b7 --- /dev/null +++ b/doc/sg_rbuf.8 @@ -0,0 +1,189 @@ +.TH SG_RBUF "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_rbuf \- reads data using SCSI READ BUFFER command +.SH SYNOPSIS +.B sg_rbuf +[\fI\-\-buffer=EACH\fR] [\fI\-\-dio\fR] [\fI\-\-help\fR] [\fI\-\-mmap\fR] +[\fI\-\-quick\fR] [\fI\-\-size=OVERALL\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.PP +.B sg_rbuf +[\fI\-b=EACH_KIB\fR] [\fI\-d\fR] [\fI\-m\fR] [\fI\-q\fR] +[\fI\-s=OVERALL_MIB\fR] [\fI\-t\fR] [\fI\-v\fR] [\fI\-V\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This command reads data with the SCSI READ BUFFER command and then discards +it. Typically the data being read is from a disk's memory cache. It is +assumed that the data is sourced quickly (although this is not guaranteed +by the SCSI standards) so that it is faster than reading data from the media. +This command is designed for timing transfer speeds across a SCSI transport. +.PP +To fetch the data with a SCSI READ BUFFER command and optionally decode it +see the sg_read_buffer utility. There is also a sg_write_buffer utility +useful for downloading firmware amongst other things. +.PP +This utility supports two command line syntaxes, the preferred one is +shown first in the synopsis and explained in this section. A later section +on the old command line syntax outlines the second group of options. +.PP +This is a Linux only utility and only works when \fIDEVICE\fR is an sg +device (e.g. "/dev/sg1"). The sg_read_buffer utility has similar +functionality and is ported to other OSes and within Linux can use +bsg and normal block device names (e.g. "/dev/sdc"). +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-buffer\fR=\fIEACH\fR +where \fIEACH\fR is the number of bytes to be transferred by each READ +BUFFER command. The default is the actual available buffer size returned +by the READ BUFFER (descriptor) command. The maximum is +the same as the default, hence this argument can only be used to reduce the +size of each transfer to less than the device's actual available buffer size. +.TP +\fB\-d\fR, \fB\-\-dio\fR +use direct IO if available. This option is only available if the \fIDEVICE\fR +is a sg driver device node (e.g. /dev/sg1). In this case the sg driver will +attempt to configure the DMA from the SCSI adapter to transfer directly into +user memory. This will eliminate the copy via kernel buffers. If not +available then this will be reported and indirect IO will be done instead. +.TP +\fB\-h\fR, \fB\-\-help\fR +print usage message then exit. +.TP +\fB\-m\fR, \fB\-\-mmap\fR +use memory mapped IO if available. This option is only available if the +\fIDEVICE\fR is a sg driver device node (e.g. /dev/sg1). In this case the +sg driver will attempt to configure the DMA from the SCSI adapter to transfer +directly into user memory. This will eliminate the copy via kernel buffers. +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-q\fR, \fB\-\-quick\fR +only transfer the data into kernel buffers (typically by DMA from the SCSI +adapter card) and do not move it into the user space. This option is only +available if the \fIDEVICE\fR is a sg driver device node (e.g. /dev/sg1). +.TP +\fB\-s\fR, \fB\-\-size\fR=\fIOVERALL\fR +where \fIOVERALL\fR is the size of total transfer in bytes. The default is +200 MiB (200*1024*1024 bytes). The actual number of bytes transferred may +be slightly less than requested since all transfers are the same size (and +an integer division is involved rounding towards zero). +.TP +\fB\-t\fR, \fB\-\-time\fR +times the bulk data transfer component of this command. The elapsed time +is printed out plus a MB/sec calculation. In this case "MB" is 1,000,000 +bytes. The gettimeofday() system call is used internally for the time +calculation. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH NOTES +This command is typically used on modern SCSI disks which have a RAM cache +in their drive electronics. If no IO to the magnetic media, or slower devices +like flash RAM, is involved then the disk may be able to source data fast +enough to saturate the bandwidth of the SCSI transport. The bottleneck may +then be the DMA element in the HBA, the Linux drivers or the host machine's +hardware (e.g. speed of RAM). +.PP +Various numeric arguments (e.g. \fIOVERALL\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.SH EXAMPLES +.PP +On the test system /dev/sg0 corresponds to a fast disk on a U2W SCSI +bus (max 80 MB/sec). The disk specifications state that its cache is 4 MB. +.br + $ time ./sg_rbuf /dev/sg0 +.br +READ BUFFER reports: buffer capacity=3434944, +.br + offset boundary=6 +.br +Read 200 MiB (actual 199 MiB, 209531584 bytes), +.br + buffer size=3354 KiB +.br +real 0m5.072s, user 0m0.000s, sys 0m2.280s +.PP +So that is approximately 40 MB/sec at 40 % utilization. Now with +the addition of the "\-q" option this throughput improves and the +utilization drops to 0%. +.br + $ time ./sg_rbuf \-q /dev/sg0 +.br +READ BUFFER reports: buffer capacity=3434944, +.br + offset boundary=6 +.br +Read 200 MiB (actual 199 MiB, 209531584 bytes), +.br + buffer size=3354 KiB +.br +real 0m2.784s, user 0m0.000s, sys 0m0.000s +.SH EXIT STATUS +The exit status of sg_rbuf is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-b\fR=\fIEACH_KIB\fR +where \fIEACH_KIB\fR is the number of Kilobytes (i.e. 1024 byte units) to be +transferred by each READ BUFFER command. Similar to the +\fI\-\-buffer=EACH\fR option in the main description but the units are +different. +.TP +\fB\-d\fR +use direct IO if available. Equivalent to the \fI\-\-dio\fR option in the +main description. +.TP +\fB\-m\fR +use memory mapped IO if available. Equivalent to the \fI\-\-mmap\fR option +in the main description. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-q\fR +only transfer the data into kernel buffers (typically by DMA from +the SCSI adapter card) and do not move it into the user space. +Equivalent to the \fI\-\-quick\fR option in the main description. +.TP +\fB\-s\fR=\fIOVERALL_MIB\fR +where \fIOVERALL_MIB\fR is the size of total transfer in Megabytes (1048576 +bytes). Similar to the \fI\-\-size=OVERALL\fR option in the main description +but the units are different. +.TP +\fB\-t\fR +times the bulk data transfer component of this command. Equivalent to +the \fI\-\-time\fR option in the main description. +.TP +\fB\-v\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR +print out version string then exit. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2017 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_read_buffer, sg_write_buffer, sg_test_rwbuf(all in sg3_utils) diff --git a/doc/sg_rdac.8 b/doc/sg_rdac.8 new file mode 100644 index 0000000..ddbda94 --- /dev/null +++ b/doc/sg_rdac.8 @@ -0,0 +1,46 @@ +.TH SG_RDAC "8" "November 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_rdac \- display or modify SCSI RDAC Redundant Controller mode page +.SH SYNOPSIS +.B sg_rdac +[\fI\-6\fR] [\fI\-a\fR] [\fI\-f=LUN\fR] [\fI\-v\fR] [\fI\-V\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +sg_rdac displays or modifies the RDAC controller settings via the +Redundant Controller mode page (0x2C). When modifying the settings it +allows one to transfer the ownership of individual drives to the +controller the command was received on. +.SH OPTIONS +.TP +\fB\-6\fR +Use the 6 byte cdb variants of the SCSI MODE SENSE and MODE SELECT commands. +The default action (in the absence of this option) is to use the 10 byte +cdb variants. +.TP +\fB\-a\fR +Transfer all (visible) devices +.TP +\fB\-f\fR=\fILUN\fR +Transfer the device identified by \fILUN\fR. This command will only work +if the controller supports 'Dual Active Mode' (aka active/active mode). +\fILUN\fR is a decimal number which cannot exceed 31 when the \fI\-6\fR +option is given, otherwise is cannot exceed 255. +.TP +\fB\-v\fR +be verbose +.TP +\fB\-V\fR +print version string then exit +.SH EXIT STATUS +The exit status of sg_rdac is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHOR +Written by Hannes Reinecke , based on sg_emc_trespass. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2006\-2017 Hannes Reinecke, Douglas Gilbert. +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/doc/sg_read.8 b/doc/sg_read.8 new file mode 100644 index 0000000..3f01134 --- /dev/null +++ b/doc/sg_read.8 @@ -0,0 +1,184 @@ +.TH SG_READ "8" "November 2012" "sg3_utils\-1.35" SG3_UTILS +.SH NAME +sg_read \- read multiple blocks of data, optionally with SCSI READ commands +.SH SYNOPSIS +.B sg_read +[\fIblk_sgio=\fR0|1] [\fIbpt=BPT\fR] [\fIbs=BS\fR] [\fIcdbsz=\fR6|10|12|16] +\fIcount=COUNT\fR [\fIdio=\fR0|1] [\fIdpo=\fR0|1] [\fIfua=\fR0|1] +\fIif=IFILE\fR [\fImmap=\fR0|1] [\fIno_dxfer=\fR0|1] [\fIodir=\fR0|1] +[\fIskip=SKIP\fR] [\fItime=TI\fR] [\fIverbose=VERB\fR] [\fI\-\-help\fR] +[\fI\-\-version\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Read data from a Linux SCSI generic (sg) device, a block device or +a normal file with each read command issued to the same offset or +logical block address (lba). This can be used to test (or time) disk +caching, SCSI (or some other) transport throughput, and/or SCSI +command overhead. +.PP +When the \fICOUNT\fR value is positive, then up to \fIBPT\fR blocks are +read at a time, until the \fICOUNT\fR is exhausted. Each read operation +starts at the same lba which, if \fISKIP\fR is not given, is the +beginning of the file or device. +.PP +The \fICOUNT\fR value may be negative when \fIIFILE\fR is a sg device +or is a block device with 'blk_sgio=1' set. Alternatively 'bpt=0' may +be given. In these cases |\fICOUNT\fR| "zero block" SCSI READ commands +are issued. "Zero block" means "do nothing" for SCSI READ 10, 12 and +16 byte commands (but not for the 6 byte variant). In practice "zero +block" SCSI READ commands have low latency and so are one way to measure +SCSI command overhead. +.SH OPTIONS +.TP +\fBblk_sgio\fR=0 | 1 +The default action of this utility is to use the Unix read() command when +the \fIIFILE\fR is a block device. In lk 2.6 many block devices can handle +SCSI commands issued via the SG_IO ioctl. So when this option is set +the SG_IO ioctl sends SCSI READ commands to \fIIFILE\fR if it is a block +device. +.TP +\fBbpt\fR=\fIBPT\fR +where \fIBPT\fR is the maximum number of blocks each read operation fetches. +Fewer blocks will be fetched when the remaining \fICOUNT\fR is less than +\fIBPT\fR. The default value for \fIBPT\fR is 128. Note that each read +operation starts at the same lba (as given by \fIskip=SKIP\fR or 0). +If 'bpt=0' then the \fICOUNT\fR is interpreted as the number of zero +block SCSI READ commands to issue. +.TP +\fBbs\fR=\fIBS\fR +where \fIBS\fR is the size (in bytes) of each block read. This +.B must +be the block size of the physical device (defaults to 512) if SCSI commands +are being issued to \fIIFILE\fR. +.TP +\fBcdbsz\fR=6 | 10 | 12 | 16 +size of SCSI READ commands issued on sg device names, or block devices +if 'blk_sgio=1' is given. Default is 10 byte SCSI READ cdbs. +.TP +\fBcount\fR=\fICOUNT\fR +when \fICOUNT\fR is a positive number, read that number of blocks, +typically with multiple read operations. When \fICOUNT\fR is negative then +|\fICOUNT\fR| SCSI READ commands are performed requesting zero blocks +to be transferred. This option is mandatory. +.TP +\fBdio\fR=0 | 1 +default is 0 which selects indirect IO. Value of 1 attempts direct +IO which, if not available, falls back to indirect IO and notes this +at completion. This option is only active if \fIIFILE\fR is an sg device. +If direct IO is selected and /proc/scsi/sg/allow_dio +has the value of 0 then a warning is issued (and indirect IO is performed) +.TP +\fBdpo\fR=0 | 1 +when set the disable page out (DPO) bit in SCSI READ commands is set. +Otherwise the DPO bit is cleared (default). +.TP +\fBfua\fR=0 | 1 +when set the force unit access (FUA) bit in SCSI READ commands is set. +Otherwise the FUA bit is cleared (default). +.TP +\fBif\fR=\fIIFILE\fR +read from this \fIIFILE\fR. This argument must be given. If the \fIIFILE\fR +is a normal file then it must be seekable (if (\fICOUNT\fR > \fIBPT\fR) or +\fIskip=SKIP\fR is given). Hence stdin is not acceptable (and giving "\-" +as the \fIIFILE\fR argument is reported as an error). +.TP +\fBmmap\fR=0 | 1 +default is 0 which selects indirect IO. Value of 1 causes memory mapped +IO to be performed. Selecting both dio and mmap is an error. This option +is only active if \fIIFILE\fR is an sg device. +.TP +\fBno_dxfer\fR=0 | 1 +when set then DMA transfers from the device are made into kernel buffers +but no further (i.e. there is no second copy into the user space). The +default value is 0 in which case transfers are made into the user space. +When neither mmap nor dio is set then data transfer are copied via +kernel buffers (i.e. a double copy). Mainly for testing. +.TP +\fBodir\fR=0 | 1 +when set opens an \fIIFILE\fR which is a block device with an additional +O_DIRECT flag. The default value is 0 (i.e. don't open block devices +O_DIRECT). +.TP +\fBskip\fR=\fISKIP\fR +all read operations will start offset by \fISKIP\fR bs\-sized blocks +from the start of the input file (or device). +.TP +\fBtime\fR=\fITI\fR +When \fITI\fR is 0 (default) doesn't perform timing. +When 1, times transfer and does throughput calculation, starting at the +first issued command until completion. When 2, times transfer and does +throughput calculation, starting at the second issued command until +completion. When 3 times from third command, etc. An average number of +commands (SCSI READs or Unix read()s) executed per second is also +output. +.TP +\fBverbose\fR=\fIVERB\fR +as \fIVERB\fR increases so does the amount of debug output sent to stderr. +Default value is zero which yields the minimum amount of debug output. +A value of 1 reports extra information that is not repetitive. +.TP +\fB\-\-help\fR +Output the usage message then exit. +.TP +\fB\-\-version\fR +Output the version string then exit. +.SH NOTES +Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +Data usually gets to the user space in a 2 stage process: first the +SCSI adapter DMAs into kernel buffers and then the sg driver copies +this data into user memory. +This is called "indirect IO" and there is a "dio" option to select +"direct IO" which will DMA directly into user memory. Due to some +issues "direct IO" is disabled in the sg driver and needs a +configuration change to activate it. This is typically done with +"echo 1 > /proc/scsi/sg/allow_dio". An alternate way to avoid the +2 stage copy is to select memory mapped IO with 'mmap=1'. +.SH SIGNALS +The signal handling has been borrowed from dd: SIGINT, SIGQUIT and +SIGPIPE output the number of remaining blocks to be transferred; +then they have their default action. +SIGUSR1 causes the same information to be output yet the copy continues. +All output caused by signals is sent to stderr. +.SH EXAMPLES +.PP +Let us assume that /dev/sg0 is a disk and we wish to time the disk's +cache performance. +.PP + sg_read if=/dev/sg0 bs=512 count=1MB mmap=1 time=2 +.PP +This command will continually read 128 512 byte blocks from block 0. +The "128" is the default value for 'bpt' while "block 0" is chosen +because the 'skip' argument was not given. This will continue until +1,000,000 blocks are read. The idea behind using 'time=2' is that the +first 64 KiB read operation will involve reading the magnetic media +while the remaining read operations will "hit" the disk's cache. The +output of third command will look like this: +.PP + time from second command to end was 4.50 secs, 113.70 MB/sec +.br + Average number of READ commands per second was 1735.27 +.br + 1000000+0 records in, SCSI commands issued: 7813 +.SH EXIT STATUS +The exit status of sg_read is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2012 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +To time streaming media read or write time see +.B sg_dd +is in the sg3_utils package. The lmbench package contains +.B lmdd +which is also interesting. +.B raw(8), dd(1) diff --git a/doc/sg_read_attr.8 b/doc/sg_read_attr.8 new file mode 100644 index 0000000..b7c4d09 --- /dev/null +++ b/doc/sg_read_attr.8 @@ -0,0 +1,214 @@ +.TH SG_READ_ATTR "8" "November 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_read_attr \- send SCSI READ ATTRIBUTE command +.SH SYNOPSIS +.B sg_read_attr +[\fI\-\-cache\fR] [\fI\-\-enumerate\fR] [\fI\-\-ea=EA\fR] +[\fI\-\-filter=FL\fR] [\fI\-\-first=FAI\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-in=FN\fR] [\fI\-\-lvn=LVN\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-pn=PN\fR] +[\fI\-\-quiet\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-sa=SA\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI READ ATTRIBUTE command to \fIDEVICE\fR and outputs the data +returned. This command was introduced in SPC\-3 revision 1 and thus is +applicable to all SCSI devices. In practice it is used mainly for tape +systems. This utility is based on the SPC\-5 draft standard, revision +17 (spc5r17.pdf). +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-c\fR, \fB\-\-cache\fR +sets the CACHE bit in the READ ATTRIBUTE cdb. This instructs the device +server to return cached attributes. By default that bit is cleared +which instructs the device server not to return cached attributes. +.TP +\fB\-e\fR, \fB\-\-enumerate\fR +enumerates all known attributes and service actions. Attributes include +an identifier, length, format and a name as defined by T10. If \fIDEVICE\fR +is given then it is ignored. +.TP +\fB\-E\fR, \fB\-\-ea\fR=\fIEA\fR +where \fIEA\fR is an element address which is placed in the READ ATTRIBUTE +cdb. This field is only found in SMC\-2 and SMC\-3 drafts for medium +changers usually associated with tape libraries. By default this field +is set to zero. +.TP +\fB\-f\fR, \fB\-\-filter\fR=\fIFL\fR +where \fIFL\fR is an attribute identifier in the range 0 to 65535 or \-1. +Attribute identifiers are typically given in hexadecimal in which case the +hex number should be prefixed by "0x" or has a trailing "h". "\-1" is +the default value and means 'match all'; for all other values of \fIFL\fR +on the matching attribute is output. +.TP +\fB\-F\fR, \fB\-\-first\fR=\fIFAI\fR +where \fIFAI\fR is the "first attribute identifier" field in the cdb. It +seems as though the intent of this field is that only attributes whose +identifiers are equal to or greater than \fIFAI\fR are returned. The default +value of \fIFAI\fR is zero. Attributes are returned in ascending identifier +order. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response in hexadecimal to stdout. When used once the whole +response is output in ASCII hexadecimal with a leading address (starting at +0) on each line. When used twice each attribute descriptor in the response +is output separately in hexadecimal. When used thrice the whole response is +output in hexadecimal with no leading address (on each line). +.br +Output generated by '\-HHH' (or \fI\-\-hex\fR used three times) can be +redirected to a file. That file will be in suitable format for \fI\-\-in=FN\fR +to use in a later invocation. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIFN\fR +\fIFN\fR is treated as a file name (or '\-' for stdin) which contains ASCII +hexadecimal or binary representing the response to a READ ATTRIBUTE command +with service action 0x0 (i.e (fetch) attribute values). When this option is +given then \fIDEVICE\fR (if also given) is ignored. +.br +By default \fIFN\fR is assumed to contain ASCII hexadecimal arranged as +bytes which a space, tab or comma delimited. All characters from (and +including) "#" to the end of line are ignored. If the \fI\-\-raw\fR option +is also given then \fIFN\fR is assumed to contain binary data. When the +\fI\-\-raw\fR option is given then after processing the input the +internal raw variable is reset to 0 so it has no effect on the output. +.br +Since the READ ATTRIBUTE response does not contain the service action number +that it is a response to, then the \fI\-\-sa=SA\fR should be given (if not +service action 0 (attribute values) is assumed. +.TP +\fB\-l\fR, \fB\-\-lvn\fR=\fILVN\fR +where \fILVN\fR is placed in the "logical volume number" field of the cdb. +The default value is zero which is required to be the logical volume number +if the device only has one volume. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in +the cdb's "allocation length" field. If not given (or \fILEN\fR is zero) +then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576. +.TP +\fB\-p\fR, \fB\-\-pn\fR=\fIPN\fR +where \fIPN\fR is placed in the "partition number" field of the cdb. If +the \fIDEVICE\fR only has one partition then its partition number must be +zero. The default value of \fIPN\fR is zero. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +this option reduces the amount of information output. For example when +used once (\fISA\fR=0), it suppresses the header line announcing the +output of attributes; when used twice it suppresses the name of each +attribute, leaving only the associated attribute values (or strings). +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the SCSI response (i.e. the data\-out buffer) in binary (to stdout). +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-s\fR, \fB\-\-sa\fR=\fISA\fR +where \fISA\fR is placed on the "service action" field of the cdb. Values +of 0 to 63 are accepted with a default of 0. spc5r08.pdf defines five +service actions: 0 for attributes values ; 1 for an attribute list (names, +not values), 2 for the logical volume list; 3 for the partition list; 4 +is restricted for SMC\-3; and 5 for the supported attribute list. +.br +Alternatively an acronym can be given for \fISA\fR. The acronym should be +one of "av", "al", "lvl", "pn", "smc" or "sa" for service actions 0 to 5 +respectively. The acronyms can also be given in upper case. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +Only tape systems seem to implement the SCSI READ ATTRIBUTE command. The vast +majority of its definition is in the SPC standard so other device types could +use it. +.PP +Much of the information provided by READ ATTRIBUTE can also be found in +pages returned by LOG SENSE (see the sg_logs utility) and in the VPD +pages returned by the INQUIRY command. +.SH EXAMPLES +To list the attributes of a tape drive whose xxxx is /dev/sg1 the following +could be used: +.PP +# sg_read_attr \-s al /dev/sg1 +.br +Attribute list: +.br + Remaining capacity in partition [MiB] +.br + Maximum capacity in partition [MiB] +.br + TapeAlert flags +.br + Load count +.br + MAM space remaining [B] +.br + Assigning organization +.br + Format density code +.br + ... +.PP +To check the number of partitions: +.PP +# sg_read_attr \-s pl /dev/sg1 +.br +Partition number list: +.br + First partition number: 0 +.br + Number of partitions available: 2 +.PP +And to see the attribute values (which is the default service action): +.PP +# sg_read_attr /dev/sg1 +.br +Attribute values: +.br + Remaining capacity in partition [MiB]: 1386103 +.br + Maximum capacity in partition [MiB]: 1386103 +.br + TapeAlert flags: 0 +.br + .... +.PP +To redirect the attribute values response to a file for later decoding: +.PP +# sg_read_attr \-HHH /dev/sg1 > av.hex +.PP +And later the response held in the av.hex file could be decoded with: +.PP +# sg_read_attr \-s av \-\-in=av.hex +.br +Attribute values: +.br + Remaining capacity in partition [MiB]: 1386103 +.br + Maximum capacity in partition [MiB]: 1386103 +.br + TapeAlert flags: 0 +.br + .... +.PP +.SH EXIT STATUS +The exit status of sg_read_attr is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2016\-2017 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd,sg_logs(sg3_utils) diff --git a/doc/sg_read_block_limits.8 b/doc/sg_read_block_limits.8 new file mode 100644 index 0000000..1a32171 --- /dev/null +++ b/doc/sg_read_block_limits.8 @@ -0,0 +1,60 @@ +.TH SG_READ_BLOCK_LIMITS "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_read_block_limits \- send SCSI READ BLOCK LIMITS command +.SH SYNOPSIS +.B sg_read_block_limits +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI READ BLOCK LIMITS command to \fIDEVICE\fR and outputs the +response. This command is defined for tape (drives) and its description +is found in the SSC documents at http://www.t10.org . +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output response in hex (rather than decode it). +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response in binary to stdout. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open \fIDEVICE\fR in read\-only mode. The default is to open it in +read\-write mode. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH EXIT STATUS +The exit status of sg_read_block_limits is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH EXAMPLES +It is usually okay to use no options. Here is an invocation (on the first +line following the "#" command prompt) followed by some typical output: +.PP + # sg_read_block_limits /dev/st0 +.br +Read Block Limits results: +.br + Minimum block size: 1 byte(s) +.br + Maximum block size: 16777215 byte(s), 16383 KB, 15 MB +.br +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2009\-2017 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg3_utils(sg3_utils) diff --git a/doc/sg_read_buffer.8 b/doc/sg_read_buffer.8 new file mode 100644 index 0000000..fd9296c --- /dev/null +++ b/doc/sg_read_buffer.8 @@ -0,0 +1,113 @@ +.TH SG_READ_BUFFER "8" "May 2014" "sg3_utils\-1.39" SG3_UTILS +.SH NAME +sg_read_buffer \- send SCSI READ BUFFER command +.SH SYNOPSIS +.B sg_read_buffer +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-id=ID\fR] [\fI\-\-length=LEN\fR] +[\fI\-\-mode=MO\fR] [\fI\-\-offset=OFF\fR] [\fI\-\-raw\fR] +[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI READ BUFFER command to the \fIDEVICE\fR, and if there +is a response either decodes it, prints it in hexadecimal or sends +it in binary to stdout. If a response is received for a "descriptor" +mode then, in the absence of \fI\-\-hex\fR and \fI\-\-raw\fR, it is +decoded. Response for non\-descriptor modes are output in hexadecimal +unless the \fI\-\-raw\fR option is given. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. If used multiple times also prints +the mode names and their acronyms. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response in hexadecimal. When given twice the response is +output in hex with the corresponding representation in ASCII to the +right of each line. +.TP +\fB\-i\fR, \fB\-\-id\fR=\fIID\fR +this option sets the buffer id field in the cdb. \fIID\fR is a value between +0 (default) and 255 inclusive. +.TP +\fB\-l\fR, \fB\-\-length\fR=\fILEN\fR +where \fILEN\fR is the length, in bytes, that is placed in the "allocation +length" field in the cdb. The default value is 4 (bytes). The device may +respond with less bytes. +.TP +\fB\-m\fR, \fB\-\-mode\fR=\fIMO\fR +this option sets the mode field in the cdb. \fIMO\fR is a value between +0 (default) and 31 inclusive. Alternatively an abbreviation can be given. +See the MODES section below. To list the available mode abbreviations use +an invalid one (e.g. '\-\-mode=xxx'). As an example, to fetch the read +buffer descriptor give '\-\-mode=desc' . +.TP +\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR +this option sets the buffer offset field in the cdb. \fIOFF\fR is a value +between 0 (default) and 2**24\-1 . It is a byte offset. +.TP +\fB\-r\fR, \fB\-\-raw\fR +if a response is received then it is sent in binary to stdout. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH MODES +Following is a list of READ BUFFER command settings for the MODE field. +First is an acronym accepted by the \fIMO\fR argument of this utility. +Following the acronym in square brackets are the corresponding decimal and +hex values that may also be given for \fIMO\fR. The following are listed +in numerical order. +.TP +hd [0, 0x0] +Combined header and data (obsolete in SPC\-4). +.TP +vendor [1, 0x1] +Vendor specific. +.TP +data [2, 0x2] +Data. +.TP +desc [3, 0x3] +Descriptor: yields 4 bytes that contain an offset boundary field (1 byte) +and buffer capacity (3 bytes). +.TP +echo [10, 0xa] +Read data from echo buffer (was called "Echo buffer" in SPC\-3). +.TP +echo_desc [11, 0xb] +Echo buffer descriptor: yields 4 bytes of which the last (lowest) 13 bits +represent the echo buffer capacity. The maximum echo buffer size is 4096 +bytes. +.TP +en_ex [26, 0x1a] +Enable expander communications protocol and Echo buffer. Made obsolete in +SPC\-4. +.TP +err_hist [28, 0x1c] +Error history. Introduced in SPC\-4. +.SH NOTES +All numbers given with options are assumed to be decimal. +Alternatively numerical values can be given in hexadecimal preceded by +either "0x" or "0X" (or has a trailing "h" or "H"). +.SH EXIT STATUS +The exit status of sg_read_buffer is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHORS +Written by Luben Tuikov and Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2006\-2014 Luben Tuikov and Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_write_buffer(sg3_utils) diff --git a/doc/sg_read_long.8 b/doc/sg_read_long.8 new file mode 100644 index 0000000..e1b1591 --- /dev/null +++ b/doc/sg_read_long.8 @@ -0,0 +1,102 @@ +.TH SG_READ_LONG "8" "November 2015" "sg3_utils\-1.42" SG3_UTILS +.SH NAME +sg_read_long \- send a SCSI READ LONG command +.SH SYNOPSIS +.B sg_read_long +[\fI\-\-16\fR] [\fI\-\-correct\fR] [\fI\-\-help\fR] [\fI\-\-lba=LBA\fR] +[\fI\-\-out=OF\fR] [\fI\-\-pblock\fR] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] [\fI\-\-xfer_len=BTL\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send SCSI READ LONG command to \fIDEVICE\fR. The read buffer is output in hex +and ASCII to stdout or placed in a file. Note that the data returned includes +the logical block data (typically 512 bytes for a disk) plus ECC +information (whose format is proprietary) plus optionally other proprietary +data. Note that the logical block data may be encoded or encrypted. +.PP +In SBC\-4 revision 7 the SCSI READ LONG (10 and 16 byte) commands were made +obsolete. In the same revision all uses of SCSI WRITE LONG (10 and 16 byte) +commands were made obsolete apart from the case in which the WR_UNCOR bit is +set. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-S\fR, \fB\-\-16\fR +uses a SCSI READ LONG(16) command. The default action is to use a SCSI +READ LONG(10) command. The READ LONG(10) command has a 32 bit field for +the lba while READ LONG(16) has a 64 bit field. +.TP +\fB\-c\fR, \fB\-\-correct\fR +sets the 'CORRCT' bit in the SCSI READ LONG command. When set the data is +corrected by the ECC before being transferred back to this utility. The +default is to leave the 'CORRCT' bit clear in which case the data is +not corrected. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the logical block address of the sector to read. Assumed +to be in decimal unless prefixed with '0x' (or has a trailing 'h'). Defaults +to lba 0. If the lba is larger than can fit in 32 bits then the \fI\-\-16\fR +option should be used. +.TP +\fB\-o\fR, \fB\-\-out\fR=\fIOF\fR +instead of outputting ASCII hex to stdout, send it in binary to the +file called \fIOF\fR. If '\-' is given for \fIOF\fR then the (binary) +output is sent to stdout. Note that all informative and error output is +sent to stderr. +.TP +\fB\-p\fR, \fB\-\-pblock\fR +sets the 'PBLOCK' bit in the SCSI READ LONG command. When set the +physical block (plus ECC data) containing the requested logical block +address is read. The default is to leave the 'PBLOCK' bit clear in +which case the logical block (plus any ECC data) is read. +.TP +\fB\-r\fR, \fB\-\-readonly\fR +opens the DEVICE read\-only rather than read\-write which is the +default. The Linux sg driver needs read\-write access for the SCSI +READ LONG command but other access methods may require read\-only +access. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-x\fR, \fB\-\-xfer_len\fR=\fIBTL\fR +where \fIBTL\fR is the byte transfer length (default to 520). If the +given value (or the default) does not match the "long" block size of the +device, the appropriate \fIBTL\fR is deduced from the error response and +printed (to stderr). The idea is that the user will retry this utility +with the correct transfer length. +.SH NOTES +If a defective block is found and its contents, if any, has been +retrieved then "sg_reassign" could be used to map out the defective +block. Associated with such an action the number of elements in +the "grown" defect list could be monitored (with "sg_reassign \-\-grown") +as the disk could be nearing the end of its useful lifetime. +.PP +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +As a data point, Fujitsu uses a 54 byte ECC (per block) which is capable +of correcting up to a single burst error or 216 bits "on the +fly". [Information obtained from MAV20xxrc product manual.] +.SH EXIT STATUS +The exit status of sg_read_long is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2016 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_reassign, sg_write_long, sg_dd diff --git a/doc/sg_readcap.8 b/doc/sg_readcap.8 new file mode 100644 index 0000000..ffc8724 --- /dev/null +++ b/doc/sg_readcap.8 @@ -0,0 +1,187 @@ +.TH SG_READCAP "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_readcap \- send SCSI READ CAPACITY command +.SH SYNOPSIS +.B sg_readcap +[\fI\-\-16\fR] [\fI\-\-brief\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-lba=LBA\fR] [\fI\-\-long\fR] [\fI\-\-pmi\fR] [\fI\-\-raw\fR] +[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.PP +.B sg_readcap +[\fI\-16\fR] [\fI\-b\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-lba=LBA\fR] +[\fI\-pmi\fR] [\fI\-r\fR] [\fI\-R\fR] [\fI\-v\fR] [\fI\-V\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +The normal action of the SCSI READ CAPACITY command is to fetch the number +of blocks (and block size) from the \fIDEVICE\fR. +.PP +The SCSI READ CAPACITY command (both 10 and 16 byte cdbs) actually yield +the block address of the last block and the block size. The number of +blocks is thus one plus the block address of the last block (as blocks +are counted origin zero (i.e. starting at block zero)). This is the source +of many "off by one" errors. +.PP +The READ CAPACITY(16) response provides additional information not found in +the READ CAPACITY(10) response. This includes protection and logical block +provisioning information, plus the number of logical blocks per physical +block. So even though the media size may not exceed what READ CAPACITY(10) +can show, it may still be useful to examine the response to READ +CAPACITY(16). Sadly there are horrible SCSI command set implementations in +the wild that crash when the READ CAPACITY(16) command is sent to them. +.PP +Device capacity is the product of the number of blocks by the block size. +This utility outputs this figure in bytes, MiB (1048576 bytes per MiB), +GB (1000000000 bytes per GB) and, if large enough, TB (1000 GB). +.PP +If sg_readcap is called without the \fI\-\-long\fR option then the 10 byte +cdb version (i.e. READ CAPACITY (10)) is sent to the \fIDEVICE\fR. If the +number of blocks in the response is reported as +0xffffffff (i.e. (2**32 \- 1) ) and the \fI\-\-hex\fR option has not been +given, then READ CAPACITY (16) is called and its response is output. +.PP +This utility supports two command line syntaxes, the preferred one is +shown first in the synopsis and explained in this section. A later section +on the old command line syntax outlines the second group of options. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-\-16\fR +Use the 16 byte cdb variant of the READ CAPACITY command. See the '\-\-long' +option. +\fB\-b\fR, \fB\-\-brief\fR +outputs two hex numbers (prefixed with '0x' and space separated) +to stdout. The first number is the maximum number of blocks on the +device (which is one plus the lba of the last accessible block). The +second number is the size in bytes of each block. If the operation fails +then "0x0 0x0" is written to stdout. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response to the READ CAPACITY command (either the 10 or 16 +byte cdb variant) in ASCII hexadecimal on stdout. +.TP +\fB\-L\fR, \fB\-\-lba\fR=\fILBA\fR +used in conjunction with \fI\-\-pmi\fR option. This variant of READ CAPACITY +will yield the last block address after \fILBA\fR prior to a delay. For a +disk, given a \fILBA\fR it yields the highest numbered block on the same +cylinder (i.e. before the heads need to move). \fILBA\fR is assumed to be +decimal unless prefixed by "0x" or it has a trailing "h". Defaults to 0. +This option was made obsolete in SBC\-3 revision 26. +.TP +\fB\-l\fR, \fB\-\-long\fR +Use the 16 byte cdb variant of the READ CAPACITY command. The default +action is to use the 10 byte cdb variant which limits the maximum +block address to (2**32 \- 2). When a 10 byte cdb READ CAPACITY command +is used on a device whose size is too large then a last block address +of 0xffffffff is returned (if the device complies with SBC\-2 or later). +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-p\fR, \fB\-\-pmi\fR +partial medium indicator: for finding the next block address prior to +some delay (e.g. head movement). In the absence of this option, the +total number of blocks and the block size of the device are output. +Used in conjunction with the \fI\-\-lba=LBA\fR option. This option was +made obsolete in SBC\-3 revision 26. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response in binary to stdout. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default for READ CAPACITY(16) is to open it read\-write. The default +for READ CAPACITY(10) is to open it read\-only so this option does not +change anything for this case. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +outputs version string then exits. +.SH NOTES +The response to READ CAPACITY(16) contains a LBPRZ bit in the SBC\-3 +standard (ANSI INCITS 514\-2014). There was also a LBPRZ bit with the same +meaning in the Logical block provisioning VPD page (0xb2). Then somewhat +confusingly T10 expanded the LBPRZ bit to a 3 bit field in SBC\-4 draft +revision 7, but only in the LB provisioning VPD page. The reason for the +expansion was to report a new "provisioning initialization pattern" +state (when an unmapped logical block is read). The new state has been +assigned LBPRZ=2 in the VPD page and it re\-uses LBPRZ=0 in the READ +CAPACITY(16) response. LBPRZ=1 retains the same meaning for both variants, +namely that a block of zeroes will be returned when an unmapped logical block +is read. +.SH EXIT STATUS +The exit status of sg_readcap is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-16\fR +Use the 16 byte cdb variant of the READ CAPACITY command. +Equivalent to \fI\-\-long\fR in the main description. +.TP +\fB\-b\fR +utility outputs two hex numbers (prefixed with '0x' and space separated) to +stdout. The first number is the maximum number of blocks on the device (which +is one plus the lba of the last accessible block). The second number is the +size of each block. If the operation fails then "0x0 0x0" is written to +stdout. Equivalent to \fI\-\-brief\fR in the main description. +.TP +\fB\-h\fR +output the usage message then exit. Giving the \fI\-?\fR option also outputs +the usage message then exits. +.TP +\fB\-H\fR +output the response to the READ CAPACITY command (either the 10 or 16 +byte cdb variant) in ASCII hexadecimal on stdout. +.TP +\fB\-lba\fR=\fILBA\fR +used in conjunction with \fI\-pmi\fR option. This variant of READ CAPACITY +will yield the last block address after \fILBA\fR prior to a delay. +Equivalent to \fI\-\-lba=LBA\fR in the main description. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-pmi\fR +partial medium indicator: for finding the next block address prior to +some delay (e.g. head movement). In the absence of this switch, the +total number of blocks and the block size of the device are output. +Equivalent to \fI\-\-pmi\fR in the main description. +.TP +\fB\-r\fR +output response in binary (to stdout). +.TP +\fB\-R\fR +Equivalent to \fI\-\-readonly\fR in the main description. +.TP +\fB\-v\fR +verbose: print out cdb of issued commands prior to execution. '\-vv' +and '\-vvv' are also accepted yielding greater verbosity. +.TP +\fB\-V\fR +outputs version string then exits. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHORS +Written by Douglas Gilbert +.SH COPYRIGHT +Copyright \(co 1999\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq(sg3_utils) diff --git a/doc/sg_reassign.8 b/doc/sg_reassign.8 new file mode 100644 index 0000000..ecacc7f --- /dev/null +++ b/doc/sg_reassign.8 @@ -0,0 +1,151 @@ +.TH SG_REASSIGN "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_reassign \- send SCSI REASSIGN BLOCKS command +.SH SYNOPSIS +.B sg_reassign +[\fI\-\-address=A,A...\fR] [\fI\-\-dummy\fR] [\fI\-\-eight=0|1\fR] +[\fI\-\-grown\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-longlist=0|1\fR] +[\fI\-\-primary\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI REASSIGN BLOCKS command to \fIDEVICE\fR. Alternatively +this utility can find the number of element in a "grown" or "primary" +defect list with a SCSI READ DEFECT DATA (10) command. These SCSI commands +are defined in SBC\-2 for direct access devices (e.g. a disk). Reassign +blocks is designed to change the physical location of a logical block +that is known or suspected to be defective to another area on the +media. Disks are typically formatted with blocks held in reserve +for this situation. +.PP +If neither the \fI\-\-grown\fR nor \fI\-\-primary\fR option is supplied +then one or more addresses need to be given. If the address (or all of +the addresses) fit into 4 bytes and '\-\-eight=1' is not given then the +parameter block passed to \fIDEVICE\fR is made up of 4 byte logical block +addresses. If any of the addresses need more than 4 bytes to +represent (i.e. >= 2**32) or '\-\-eight=1' is given then the parameter block +passed to \fIDEVICE\fR is made up of 8 byte logical block addresses. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-a\fR, \fB\-\-address\fR=\fIA,A...\fR +where \fIA,A...\fR is a string of comma separated numbers. Each number +is interpreted as decimal unless prefixed by '0x' or '0X' (or it has a +trailing 'h' or 'H'). If multiple logical block addresses are given they +must be separated by a comma or a (single) space. A string that contains +any space separators needs to be quoted. At least one address must be given. +.TP +\fB\-a\fR, \fB\-\-address\fR=\- +reads one or more logical block addresses from stdin. These may be comma, +space, tab or linefeed (newline) separated. If a line contains "#" then +the remaining characters on that line are ignored. Otherwise each non +separator sequence of characters should resolve to a decimal number +unless prefixed by '0x' or '0X' (or has a trailing 'h'). At least one +address must be given. Lines should not be longer than 1023 bytes. +.TP +\fB\-d\fR, \fB\-\-dummy\fR +prepare for but do not execute the SCSI REASSIGN BLOCKS command. Since +the REASSIGN BLOCKS command is essentially irreversible, paranoid +users may wish to check the invocation of this utility before reassigning +defective blocks on a disk. Useful with '\-vv' for those who wish to +view the parameter block that will accompany the command. +.TP +\fB\-e\fR, \fB\-\-eight\fR=0 | 1 +when value is 1 then it sets the 'LONGLBA' flag in the command indicating +that the addresses in the associated parameter block are 8 byte quantities. +When value is 0 then it clears the 'LONGLBA' flag in the command indicating +that the addresses in the associated parameter block are 4 byte quantities. +If this option is not given then 4 byte quantities are assumed unless one +of the address is too large. +.TP +\fB\-g\fR, \fB\-\-grown\fR +use the SCSI READ DEFECT DATA (10) command to determine the number of +elements in the "grown defect list". When this option is given there +is no reassignment of blocks (i.e. this utility is passive). When this +option is given then the \fI\-\-address=\fR option is not permitted. See +the discussion below concerning the relationship between reassigned blocks +and the grown defect list. This list is sometimes referred to as the GLIST. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +print response in hex (for \fB\-g\fR, \fB\-\-grown\fR, \fB\-p\fR +or \fB\-\-primary\fR). +.TP +\fB\-l\fR, \fB\-\-longlist\fR=0 | 1 +sets the REASSIGN BLOCKS cdb field of the same name to the given value. +Only 1000 addresses are permitted so there should be no need to specify +a value of 1. The short list variant restricts the parameter block +length to 2 ** 16 bytes (i.e. about 16000 4 byte addresses or 8000 +8 byte addresses). Added for completeness. +.TP +\fB\-p\fR, \fB\-\-primary\fR +use the SCSI READ DEFECT DATA (10) command to determine the number of +elements in the "primary defect list" which is established during the +manufacturing process. When this option is given there is no reassignment +of blocks (i.e. this utility is passive). When this option is given then +the \fI\-\-address=\fR option is not permitted. This list is sometimes +referred to as the PLIST. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +Note that if the ARRE field (for reads) and/or the AWRE field (for writes) +are set in the "Read Write Error Recovery" mode page then recoverable read +and/or write errors cause automatic reassignment of the defective block. The +PER bit in the same mode page controls whether a RECOVERED ERROR sense key +is reported on not (PER=1 implies do report). Irrespective of the ARRE, AWRE +or PER field settings, the error counter log pages reflect any +errors (recovered or otherwise). Whenever a block is reassigned, a new entry +is added in the "grown" defect list. Apart from doing selftests (see +sg_senddiag or smartmontools) regularly, monitoring the grown defect list of +a disk is a reasonable metric of its health. If the grown list starts growing +quickly that is an ominous sign. The best grown defect lists are empty +ones. The number of elements in the grown defect list can be viewed with +the \fI\-\-grown\fR option. The contents of the grown defect list can be +viewed with the 'sginfo \-G' utility. +.PP +If an unrecoverable error is detected at a logical block address then +REASSIGN BLOCKS is needed to reassign the block. Also if the ARRE and/or +AWRE fields are clear and a recoverable error is detected then the +logical block in question may be reassigned with this utility (otherwise +the error counter log pages will continually be incremented for each +recovered access). +.PP +The number of blocks held in reserve for the purposes of REASSIGN +BLOCKS is vendor specific and may well be limited to the zone within +the media where the original (defective) block lay. When this number +is exhausted subsequent invocations of this utility may result in +a sense key of hardware error and an additional sense of 'No defect +spare location available'. The next step would be to reformat the +disk (or get a replacement). +.PP +The SBC\-2 draft standard (revision 16) notes that when multiple addresses +are given to the SCSI REASSIGN BLOCKS command and there is some failure +at one of the later addresses then all addresses prior to that have already +be reassigned. Care should be taken in such a case. Re\-executing the command +with the same addresses will cause the earlier addresses to be reassigned +again. At some stage the disk will run out of reserved locations. +So unless a large number of addresses are involved it may be safer to +reassign them one address at a time. +.SH EXIT STATUS +The exit status of sg_reassign is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2005\-2017 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_format,sginfo,sg_senddiag(all in sg3_utils), sdparm(sdparm), +.B smartmontools(internet, sourceforge) diff --git a/doc/sg_referrals.8 b/doc/sg_referrals.8 new file mode 100644 index 0000000..b19e400 --- /dev/null +++ b/doc/sg_referrals.8 @@ -0,0 +1,71 @@ +.TH SG_REFERRALS "8" "May 2014" "sg3_utils\-1.39" SG3_UTILS +.SH NAME +sg_referrals \- send SCSI REPORT REFERRALS command +.SH SYNOPSIS +.B sg_referrals +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-maxlen=LEN\fR] +[\fI\-\-one-segment\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send the SCSI REPORT REFERRALS command to the \fIDEVICE\fR and outputs the +response. This command was introduced in (draft) SBC\-3 revision 24 and +devices that support referrals should support this command. +.PP +The default action is to decode the response for all user data segment +referral descriptors. The amount of output can be reduced by the +\fI\-\-lba\fR and \fI\-\-one-segment\fR options. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output response to this command in ASCII hex. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the Logical Block Address (LBA) in the first user +data segment the \fIDEVICE\fR should report the referrals parameter +data for. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in +the cdb's "allocation length" field. If not given then 256 is used. 256 is +enough space for the response header and user data segment descriptors. +.TP +\fB\-s\fR, \fB\-\-one-segment\fR +report the user data segment of the segment spefified by the \fILBA\fR +parameter only. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response in binary (to stdout). +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). Additional output +caused by this option is sent to stderr. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +For a discussion of referrals see section 4.25 of sbc3r25.pdf +at http://www.t10.org (or the corresponding section of a later draft). +.SH EXIT STATUS +The exit status of sg_referrals is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert and Hannes Reinecke. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2009\-2014 Douglas Gilbert and Hannes Reinecke +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd(8) diff --git a/doc/sg_rep_zones.8 b/doc/sg_rep_zones.8 new file mode 100644 index 0000000..df734d0 --- /dev/null +++ b/doc/sg_rep_zones.8 @@ -0,0 +1,79 @@ +.TH SG_REP_ZONES "8" "February 2016" "sg3_utils\-1.42" SG3_UTILS +.SH NAME +sg_rep_zones \- send SCSI REPORT ZONES command +.SH SYNOPSIS +.B sg_rep_zones +[\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-raw\fR] +[\fI\-\-readonly\fR] [\fI\-\-report=OPT\fR] [\fI\-\-start=LBA\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI REPORT ZONES command to \fIDEVICE\fR and outputs the data +returned. This command is found in the ZBC draft standard, revision +4c (zbc\-r04c.pdf). +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response in hexadecimal to stdout. When used once the whole +response is output in ASCII hexadecimal with a leading address (starting at +0) on each line. When used twice each zone descriptor in the response is +output separately in hexadecimal. When used thrice the whole response is +output in hexadecimal with no leading address (on each line). +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in +the cdb's "allocation length" field. If not given (or \fILEN\fR is zero) +then 8192 is used. The maximum allowed value of \fILEN\fR is 1048576. +.TP +\fB\-p\fR, \fB\-\-partial\fR +set the PARTIAL bit in the cdb. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the SCSI response (i.e. the data\-out buffer) in binary (to stdout). +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-o\fR, \fB\-\-report\fR=\fIOPT\fR +where \fIOPT\fR will become the contents of the REPORTING OPTION field +in the cdb. The default value is 0 which means report a list of all zones. +Some other values are 1 for list zones with a zone condition of empty; 2 for +list zones with a zone condition of implicitly opened; 3 for list zones with +a zone condition of explicitly opened; 4 for list zones with a zone condition +of closed; 5 for list zones with a zone condition of full; 6 for list zones +with a zone condition of read only; 7 for list zones with a zone condition of +offline. Other values are 0x10 for list zones with RWP recommended set to +true; 0x11 for list zones with non\-sequential write resource active set to +true and 0x3f for list zones with a zone condition of not write pointer. +.TP +\fB\-s\fR, \fB\-\-start\fR=\fILBA\fR +where \fILBA\fR is at the start or within the first zone to be reported. The +default value is 0. If \fILBA\fR is not a zone start LBA then the preceding +zone start LBA is used for reporting. Assumed to be in decimal unless +prefixed with '0x' or has a trailing 'h' which indicate hexadecimal. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH EXIT STATUS +The exit status of sg_rep_zones is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2014\-2016 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_reset_wp,sg_zone(sg3_utils) diff --git a/doc/sg_requests.8 b/doc/sg_requests.8 new file mode 100644 index 0000000..5372deb --- /dev/null +++ b/doc/sg_requests.8 @@ -0,0 +1,128 @@ +.TH SG_REQUESTS "8" "February 2016" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_requests \- send one or more SCSI REQUEST SENSE commands +.SH SYNOPSIS +.B sg_requests +[\fI\-\-desc\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-maxlen=LEN\fR] +[\fI\-\-num=NUM\fR] [\fI\-\-number=NUM\fR] [\fI\-\-progress\fR] +[\fI\-\-raw\fR] [\fI\-\-status\fR] [\fI\-\-time\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send SCSI REQUEST SENSE command to \fIDEVICE\fR and output the parameter +data response which is expected to be in sense data format. Both fixed +and descriptor sense data formats are supported. +.PP +Multiple REQUEST SENSE commands can be sent with the \fI\-\-num=NUM\fR +option. This can be used for timing purposes or monitoring the progress +indication. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-d\fR, \fB\-\-desc\fR +sets the DESC bit in the REQUEST SENSE SCSI cdb. The \fIDEVICE\fR +should return sense data in descriptor (rather than fixed) format. This +will only occur if the \fIDEVICE\fR recognizes descriptor format (SPC\-3 +and later). If the device is pre SPC\-3 then setting a bit in a reserved +field may cause a check condition status with an illegal request sense key, +but will most likely be ignored. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output response in ASCII hexadecimal. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in the +cdb's "allocation length" field. If not given (or \fILEN\fR is zero) then +252 is used. The maximum value of \fILEN\fR is 255 (but SPC\-4 recommends 252). +.TP +\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR +perform \fINUM\fR SCSI REQUEST SENSE commands, stopping when either \fINUM\fR +is reached or an error occurs. The default value for \fINUM\fR is 1 . +.TP +\fB\-\-number\fR=\fINUM\fR +same action as \fI\-\-num=NUM\fR. Added for compatibility with sg_turs. +.TP +\fB\-p\fR, \fB\-\-progress\fR +show progress indication (a percentage) if available. If \fI\-\-number=NUM\fR +is given, \fINUM\fR is greater than 1 and an initial progress indication +was detected then this utility waits 30 seconds before subsequent checks. +Exits when \fINUM\fR is reached or there are no more progress indications. +Ignores \fI\-\-hex\fR, \fI\-\-raw\fR and \fI\-\-time\fR options. See +NOTES section below. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response in binary (to stdout). +.TP +\fB\-s\fR, \fB\-\-status\fR +if the REQUEST SENSE command finished without error (as indicated by its +SCSI status) then the contents of the parameter data are analysed as +sense data and the exit status is set accordingly. The default +action (i.e. when this option is not given) is to ignore the contents +of the parameter data for the purposes of setting the exit status. +Some types of error set a sense key of "NO SENSE" with non\-zero +information in the additional sense code (e.g. the FAILURE PREDICTION +THRESHOLD EXCEEDED group of codes); this results in an exit status +value of 10. If the sense key is "NO SENSE" and both asc and ascq are +zero then the exit status is set to 0 . See the sg3_utils(8) man page +for exit status values. +.TP +\fB\-t\fR, \fB\-\-time\fR +time the SCSI REQUEST SENSE command(s) and calculate the average number +of operations per second. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +Additionally the response (if received) is output in ASCII\-HEX. Use +this option multiple times for greater verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +In SCSI 1 and 2 the REQUEST SENSE command was very important for error +and warning processing in SCSI. The autosense capability rendered this +command almost superfluous. +.PP +However recent SCSI drafts (e.g. SPC\-4 rev 14 and SBC\-3 rev 14) increase +the utility of the REQUEST SENSE command. Idle and standby (low) power +conditions can be detected with this command. +.PP +The REQUEST SENSE command is not marked as mandatory in SPC\-3 (i.e. for +all SCSI devices) but is marked as mandatory in SBC\-2 (i.e. for disks), +SSC\-3 (i.e. for tapes) and MMC\-4 (i.e. for CD/DVD/HD\-DVD/BD drives). +.PP +The progress indication is optionally part of the sense data. When a prior +command that takes a long time to complete (and typically precludes other +media access commands) is still underway, the progress indication can be used +to determine how long before the device returns to its normal state. +.PP +The SCSI FORMAT command for disks used with the IMMED bit set is an example +of an operation that takes a significant amount of time and precludes other +media access during that time. The IMMED bit set instructs the FORMAT command +to return control to the application client once the format has commenced (see +SBC\-3). Several long duration SCSI commands associated with tape drives also +use the progress indication (see SSC\-3). +.PP +Early standards suggested that the SCSI TEST UNIT READY command be used for +polling the progress indication (see the sg_turs utility). Since SPC\-3 the +standards suggest that the SCSI REQUEST SENSE command should be used instead. +.PP +The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the +O_RDONLY flag). +.SH EXIT STATUS +The exit status of sg_requests is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2016 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_turs (sg3_utils) diff --git a/doc/sg_reset.8 b/doc/sg_reset.8 new file mode 100644 index 0000000..5d4f8f3 --- /dev/null +++ b/doc/sg_reset.8 @@ -0,0 +1,135 @@ +.TH SG_RESET "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_reset \- sends SCSI device, target, bus or host reset; or checks reset +state +.SH SYNOPSIS +.B sg_reset +[\fI\-\-bus\fR] [\fI\-\-device\fR] [\fI\-\-help\fR] [\fI\-\-host\fR] +[\fI\-\-no-esc\fR] [\fI\-\-target\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +The sg_reset utility with no options (just a \fIDEVICE\fR) reports on the +reset state (e.g. if a reset is underway) of the \fIDEVICE\fR. When given +a \fI\-\-device\fR, \fI\-\-target\fR, \fI\-\-bus\fR or \fI\-\-host\fR +option it requests a device, target, bus or host reset respectively. +.PP +A device reset is applied to the Logical Unit (LU) corresponding to +\fIDEVICE\fR. It is most likely implemented by a Low level Driver (LLD) +in Linux as a LOGICAL UNIT RESET task management function. +.PP +The ability to reset a SCSI target was added in Linux kernel 2.6.27 . A LLD +may send Low level Drivers (LLDs) the I_T NEXUS RESET task management +function. Alternatively it may use a transport mechanism to do the same +thing (e.g. a hard reset on the link containing a SAS target). +.PP +In the Linux kernel 2.6 and 3 series this utility can be called on sd, +sr (cd/dvd), st or sg device nodes; if the user has appropriate permissions. +.PP +Users of this utility can check whether a reset recovery is already underway +before trying to send a new reset with this utility. Calling this utility +with no options, just the \fIDEVICE\fR, will do such a check. +.SH OPTIONS +.TP +\fB\-b\fR, \fB\-\-bus\fR +attempt a SCSI bus reset. A bus reset is a SCSI Parallel Interface (SPI) +concept not found in modern transports. A recent LLD may implement it as +a series of resets on targets that might be considered as siblings to the +target on the \fIDEVICE\fR path. +.TP +\fB\-d\fR, \fB\-\-device\fR +attempt a SCSI device reset. This would typically involve sending a LOGICAL +UNIT RESET task management function to \fIDEVICE\fR. +.TP +\fB\-z\fR, \fB\-\-help\fR +print the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-host\fR +attempt a host reset. The "host" in this context is often called +a Host Bus Adapter (HBA) and contains one or more SCSI initiators. +.TP +\fB\-N\fR, \fB\-\-no\-esc\fR +without this option, if a device reset (\fI\-\-device\fR) fails then it +will escalate to a target reset. And if a target reset (\fI\-\-target\fR) +fails then it will escalate to a bus reset. And if a bus +reset (\fI\-\-bus\fR) fails then it will escalate to a host reset. With this +option only the requested reset is attempted. An alternate option name of +\fI\-\-no-escalate\fR is also accepted. +.TP +\fB\-\-no\-escalate\fR +The same as \fB\-N\fR, \fB\-\-no\-esc\fR. +.TP +\fB\-t\fR, \fB\-\-target\fR +attempt a SCSI target reset. A SCSI target contains one or more LUs. This +would typically involve sending a I_T NEXUS RESET task management function +to \fIDEVICE\fR There may be a transport action that is equivalent (e.g. +in SAS a hard reset on the link that contains the target). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). +.TP +\fB\-V\fR, \fB\-\-version\fR +prints the version string then exits. +.SH NOTES +The error recovery code within the Linux kernel (SCSI mid\-level) when faced +with a SCSI command timing out and no response from the device (LU) does the +following. First it tries a device reset and if that is not successful tries +a target reset. If that is not successful it tries a bus reset. If that is +not successful it tries a host reset. The "device,target,bus,host" order is +the reset escalation that the \fI\-\-no-esc\fR option attempts to stop. In +large storage configurations the escalation may be (very) undesirable. +.PP +This utility calls the SG_SCSI_RESET ioctl and as of lk 3.10.7 the +\fI\-\-no-esc\fR option is not supported. Patches to implement this +functionality may be accepted in lk 3.18 or 3.19 . +.PP +SAM\-4 and 5 define a hard reset, a LOGICAL UNIT RESET and a I_T NEXUS +RESET. A hard reset is defined to be a power on condition, a microcode +change or a transport reset event. LOGICAL UNIT RESET and I_T NEXUS +RESET can be requested via task management functions (and support for +LOGICAL UNIT RESET is mandatory). In Linux the SCSI subsystem leaves it up +to the LLDs as to exactly what type (if any) of reset is performed. +The "bus reset" is SCSI Parallel Interface (SPI) concept that may not map +well to recent SCSI transports so it may be a dummy operation. A "host reset" +attempts to re\-initialize the HBA that the request passes through en route +to the \fIDEVICE\fR. Note that a "host reset" and a "bus reset" may cause +collateral damage. +.PP +This utility does not allow individual SCSI commands to be aborted. SAM\-4 +defines ABORT TASK and ABORT TASK SET task management functions for that. +.PP +Prior to SAM\-3 there was a TARGET RESET task management function. And in +SAM\-4 I_T NEXUS RESET appeared which seems closely related: the "I_T" +stands for Initiator\-Target. +.PP +Transports may have their own types of resets not supported by this utility. +For example SAS has a link reset in which both ends of a physical link (e.g. +between a SAS expander and a SAS tape drive) renegotiate their connection. +.PP +Prior to version 0.57 of this utility the command line had short options +only (e.g. \fI\-d\fR but not \fI\-\-device\fR). Also \fI\-h\fR invoked a host +reset while in the current version \fI\-h\fR is equivalent to \fI\-\-help\fR +and both \fI\-H\fR and \fI\-\-host\fR invoke a host reset. For backward +compatibility define the environment variable SG3_UTILS_OLD_OPTS or +SG_RESET_OLD_OPTS . In this case \fI\-h\fR will invoke a host reset and the +output will be verbose as it was previously (equivalent to using the +\fI\-\-verbose\fR option now). +For example: +.PP + SG_RESET_OLD_OPTS=1 sg_reset \-h /dev/sg1 +.br +sg_reset: starting host reset +.br +sg_reset: completed host reset +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variables SG3_UTILS_OLD_OPTS +or SG_RESET_OLD_OPTS can be given. When either is present this utility will +expect the older command line options as outlined in the NOTES section. +.SH AUTHORS +Written by Douglas Gilbert. +.SH COPYRIGHT +Copyright \(co 1999\-2017 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/doc/sg_reset_wp.8 b/doc/sg_reset_wp.8 new file mode 100644 index 0000000..1627e04 --- /dev/null +++ b/doc/sg_reset_wp.8 @@ -0,0 +1,57 @@ +.TH SG_RESET_WP "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_reset_wp \- send SCSI RESET WRITE POINTER command +.SH SYNOPSIS +.B sg_reset_wp +[\fI\-\-all\fR] [\fI\-\-count=ZC\fR] [\fI\-\-help\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] [\fI\-\-zone=ID\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI RESET WRITE POINTER command to the \fIDEVICE\fR. This command +is found in the soon to be released ZBC standard (draft prior to standard: +zbc\-r05.pdf). +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-a\fR, \fB\-\-all\fR +sets the ALL field in the cdb. This causes a reset write pointer operation of +all open zones and full zones. When this option is given then the +\fI\-\-zone=ID\fR option is ignored. Either this option or the +\fI\-\-zone=ID\fR option is required. +.TP +\fB\-C\fR, \fB\-\-count\fR=\fIZC\fR +ZC is placed in the Zone Count field in the cdb of the RESET WRITE POINTER +command supported by this utility. ZC should be a value from 0 to +65535 (0xffff) inclusive. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-z\fR, \fB\-\-zone\fR=\fIID\fR +where \fIID\fR is placed in the cdb's ZONE ID field. A zone id is a zone +start logical block address (LBA). This causes a reset write pointer +operation on the zone identified by the ZONE ID field. The default value is +0. Either this option or the \fI\-\-all\fR option is required. +\fIID\fR is assumed to be in decimal unless prefixed with '0x' or has a +trailing 'h' which indicate hexadecimal. +.SH EXIT STATUS +The exit status of sg_reset_wp is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2014\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_rep_zones,sg_zone(sg3_utils) diff --git a/doc/sg_rmsn.8 b/doc/sg_rmsn.8 new file mode 100644 index 0000000..294c85a --- /dev/null +++ b/doc/sg_rmsn.8 @@ -0,0 +1,64 @@ +.TH SG_RMSN "8" "November 2012" "sg3_utils\-1.31" SG3_UTILS +.SH NAME +sg_rmsn \- send SCSI READ MEDIA SERIAL NUMBER command +.SH SYNOPSIS +.B sg_rmsn +[\fI\-\-help\fR] [\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI READ MEDIA SERIAL NUMBER command to \fIDEVICE\fR and outputs +the response. +.PP +This command is described in SPC\-3 found at www.t10.org . It was originally +added to SPC\-3 in revision 11 (2003/2/12). It is not an mandatory command +and the author has not seen any SCSI devices that support it. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-r\fR, \fB\-\-raw\fR +sends the serial number (if found) to stdout. This output may contain +non\-printable characters (e.g. the serial number is padded with NULLs +at the end so its length is a multiple of 4). The default action is +to print the serial number out in ASCII\-HEX with ASCII characters to +the right. All error messages are sent to stderr. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +opens the DEVICE read\-only rather than read\-write which is the +default. The Linux sg driver needs read\-write access for the SCSI +READ MEDIA SERIAL NUMBER command but other access methods may require +read\-only access. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +Device identification information is also found in a standard INQUIRY +response and its VPD pages (see sg_vpd). The relevant VPD pages are +the "device identification page" (VPD page 0x83) and the "unit serial +number" page (VPD page 0x80). +.PP +The MMC\-4 command set for CD/DVD/HD-DVD/BD drives has a "media serial number" +feature (0x109) [and a "logical unit serial number" feature]. These +can be viewed with sg_get_config. +.SH EXIT STATUS +The exit status of sg_rmsn is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2005\-2012 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd(sg3_utils), sg_get_config(sg3_utils) diff --git a/doc/sg_rtpg.8 b/doc/sg_rtpg.8 new file mode 100644 index 0000000..c4b8d5b --- /dev/null +++ b/doc/sg_rtpg.8 @@ -0,0 +1,64 @@ +.TH SG_RTPG "8" "May 2014" "sg3_utils\-1.39" SG3_UTILS +.SH NAME +sg_rtpg \- send SCSI REPORT TARGET PORT GROUPS command +.SH SYNOPSIS +.B sg_rtpg +[\fI\-\-decode\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-raw\fR] +[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI REPORT TARGET PORT GROUPS command to \fIDEVICE\fR and +outputs the response. +.PP +Target port group access is described in SPC\-3 and SPC\-4 found at +www.t10.org . The most recent draft of SPC\-4 is revision 37 in which +target port groups are described in section 5.15 . +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-d\fR, \fB\-\-decode\fR +decodes the status code and asymmetric access state from each +target port group descriptor returned. The default action is not +to decode these values. +.TP +\fB\-e\fR, \fB\-\-extended\fR +use extended header format for parameter data. This sets the PARAMETER DATA +FORMAT field in the cdb to 1. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output response in hex (rather than partially or fully decode it). +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response in binary to stdout. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +The Report Target Port Groups command should be supported whenever the TPGS +bits in a standard INQUIRY response are greater than zero. [View with +sg_inq utility.] +.SH EXIT STATUS +The exit status of sg_rtpg is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2014 Christophe Varoqui and Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq(sg3_utils) diff --git a/doc/sg_safte.8 b/doc/sg_safte.8 new file mode 100644 index 0000000..0b9c43e --- /dev/null +++ b/doc/sg_safte.8 @@ -0,0 +1,132 @@ +.TH SG_SAFTE "8" "April 2016" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_safte \- access SCSI Accessed Fault\-Tolerant Enclosure (SAF\-TE) device +.SH SYNOPSIS +.B sg_safte +[\fI\-\-config\fR] [\fI\-\-devstatus\fR] [\fI\-\-encstatus\fR] +[\fI\-\-flags\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-insertions\fR] +[\fI\-\-raw\fR] [\fI\-\-usage\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Fetches enclosure status (via a SCSI READ BUFFER command). +The \fIDEVICE\fR should be a SAF\-TE device which may be a storage +array controller (INQUIRY peripheral device type 0xc) or a generic +processor device (INQUIRY peripheral device type 0x3). +.PP +If no options are given (only the \fIDEVICE\fR argument) then the +overall enclosure status as reported by the option +.I +\-\-config +.R +is reported. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-c\fR, \fB\-\-config\fR +will issues a +.I +Read Enclosure Configuration +.R +(READ BUFFER ID 0) cdb to the device, which returns a list of the +enclosure hardware resources. +.TP +\fB\-d\fR, \fB\-\-devstatus\fR +will issue a +.I +Read Device Slot Status +.R +(READ BUFFER ID 4) cdb to the device, which returns information about +the current state of each drive or slot. +.TP +\fB\-s\fR, \fB\-\-encstatus\fR +will issue a +.I +Read Enclosure Status +.R +(READ BUFFER ID 1) cdb to the device, which returns the operational +state of the components. +.TP +\fB\-f\fR, \fB\-\-flags\fR +will issue a +.I +Read Global Flags +.R +(READ BUFFER ID 5) cdb to the device, which read the most recent state +of the global flags of the RAID processor device. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response to a READ BUFFER command in ASCII hex to stdout. If used +once, output the response to the first READ BUFFER command (i.e. with +buffer_id=0). This should be the enclosure configuration. If used twice (or +more often), the response to subsequent READ BUFFER commands is output. +.TP +\fB\-i\fR, \fB\-\-insertions\fR +will issue a +.I +Read Device Insertions +.R +(READ BUFFER ID 3) cdb to the device, which returns information about +the number of times devices have been inserted whilst the RAID system +was powered on. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the response to a READ BUFFER command in binary to stdout. If used +once, output the response to the first READ BUFFER command (i.e. with +buffer_id=0). This should be the enclosure configuration. If used twice (or +more often), the response to subsequent READ BUFFER commands is output. +.TP +\fB\-u\fR, \fB\-\-usage\fR +will issue a +.I +Read Usage Statistics +.R +(READ BUFFER ID 2) cdb to the device, which returns the information on +total usage time and number of power\-on cycles of the RAID device. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +This implementation is based on the intermediate review document dated +19970414 and named "SR041497.pdf". So it is quite old. Intel and nStor +are the authors. Intel have a zip archive containing this and related +documents in the "SAF\-TE: SCSI Accessed Fault Tolerant Enclosures +Interface Specification" section of this page: +.PP +http://www.intel.com/content/www/us/en/servers/ipmi/ipmi\-technical\-resources.html +.PP +Similar functionality is provided by SPC\-4 SCSI Enclosure Services (SES) +devices (Peripheral device type 0xd), which can be queried with the +sg_ses utility. +.SH EXAMPLES +To view the configuration: +.PP + sg_safte /dev/sg1 +.PP +To view the device slot status: +.PP + sg_safte \-\-devstatus /dev/sg1 +.PP +.SH EXIT STATUS +The exit status of sg_safte is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Hannes Reinecke and Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2016 Hannes Reinecke and Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq, sg_ses (in sg3_utils package); safte\-monitor (internet) diff --git a/doc/sg_sanitize.8 b/doc/sg_sanitize.8 new file mode 100644 index 0000000..5bc7cb6 --- /dev/null +++ b/doc/sg_sanitize.8 @@ -0,0 +1,246 @@ +.TH SG_SANITIZE "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_sanitize \- remove all user data from disk with SCSI SANITIZE command +.SH SYNOPSIS +.B sg_sanitize +[\fI\-\-ause\fR] [\fI\-\-block\fR] [\fI\-\-count=OC\fR] [\fI\-\-crypto\fR] +[\fI\-\-dry\-run\fR] [\fI\-\-desc\fR] [\fI\-\-early\fR] [\fI\-\-fail\fR] +[\fI\-\-help\fR] [\fI\-\-invert\fR] [\fI\-\-ipl=LEN\fR] [\fI\-\-overwrite\fR] +[\fI\-\-pattern=PF\fR] [\fI\-\-quick\fR] [\fI\-\-test=TE\fR] +[\fI\-\-timeout=SECS\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +[\fI\-\-wait\fR] [\fI\-\-zero\fR] [\fI\-\-znr\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility invokes the SCSI SANITIZE command. This command was first +introduced in the SBC\-3 revision 27 draft. The purpose of the sanitize +operation is to alter the information in the cache and on the medium of a +logical unit (e.g. a disk) so that the recovery of user data is not +possible. If that user data cannot be erased, or is in the process of +being erased, then the sanitize operation prevents access to that user +data. +.PP +Once a SCSI SANITIZE command has successfully started, then user data from +that disk is no longer available. Even if the disk is power cycled, the +sanitize operation will continue after power is re\-instated until it is +complete. +.PP +This utility requires either the \fI\-\-block\fR, \fI\-\-crypto\fR, +\fI\-\-fail\fR or \fI\-\-overwrite\fR option. With the \fI\-\-block\fR, +\fI\-\-crypto\fR or \fI\-\-overwrite\fR option the user is given 15 seconds +to reconsider whether they wish to erase all the data on a disk, unless +the \fI\-\-quick\fR option is given in which case the sanitize operation +starts immediately. The disk's INQUIRY response strings are printed out just +in case the wrong \fIDEVICE\fR has been given. +.PP +If the \fI\-\-early\fR option is given then this utility will exit soon +after starting the SANITIZE command with the IMMED bit set. The user can +monitor the progress of the sanitize operation with +the "sg_requests \-\-num=9999 \-\-progress" which sends a REQUEST SENSE +command every 30 seconds. Otherwise if the \fI\-\-wait\fR option is given +then this utility will wait until the SANITIZE command completes (or fails) +and that can be many hours. +.PP +If neither the \fI\-\-early\fR nor \fI\-\-wait\fR option is given then +the SANITIZE command is started with the IMMED bit set. After that this +utility sends a REQUEST SENSE command every 60 seconds until there are +no more progress indications. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-A\fR, \fB\-\-ause\fR +sets the AUSE bit in the cdb. AUSE is an acronym for "allow unrestricted +sanitize exit". The default action is to leave the AUSE bit cleared. +.TP +\fB\-B\fR, \fB\-\-block\fR +perform a "block erase" sanitize operation. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fIOC\fR +where \fIOC\fR is the "overwrite count" associated with the "overwrite" +sanitize operation. \fIOC\fR can be a value between 1 and 31 and 1 is +the default. +.TP +\fB\-C\fR, \fB\-\-crypto\fR +perform a "cryptographic erase" sanitize operation. +.TP +\fB\-d\fR, \fB\-\-desc\fR +sets the DESC field in the REQUEST SENSE command used for polling. By +default this field is set to zero. A REQUEST SENSE polling loop is +used after the SANITIZE command is issued (assuming that neither the +\fI\-\-early\fR nor the \fI\-\-wait\fR option have been given) to check +on the progress of this command as it can take some time. +.TP +\fB\-D\fR, \fB\-\-dry\-run\fR +this option will parse the command line, do all the preparation but bypass +the actual SANITIZE command. +.TP +\fB\-e\fR, \fB\-\-early\fR +the default action of this utility is to poll the disk every 60 seconds to +fetch the progress indication until the sanitize is finished. When this +option is given this utility will exit "early" as soon as the SANITIZE +command with the IMMED bit set to 1 has been acknowledged. This option and +\fI\-\-wait\fR cannot both be given. +.TP +\fB\-F\fR, \fB\-\-fail\fR +perform an "exit failure mode" sanitize operation. Typically requires the +preceding SANITIZE command to have set the AUSE bit. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage information then exit. +.TP +\fB\-i\fR, \fB\-\-ipl\fR=\fILEN\fR +set the initialization pattern length to \fILEN\fR bytes. By default it is +set to the length of the pattern file (\fIPF\fR) or 4 if the \fI\-\-zero\fR +option is given. Only active when the \fI\-\-overwrite\fR option is also +given. It is the number of bytes from the \fIPF\fR file that will be used +as the initialization pattern (if the \fI\-\-zero\fR option is not given). +The minimum size is 1 byte and the maximum is the logical block size of the +\fIDEVICE\fR (and not to exceed 65535). If \fILEN\fR exceeds the \fIPF\fR +file size then the initialization pattern is padded with zeros. +.TP +\fB\-I\fR, \fB\-\-invert\fR +set the INVERT bit in the overwrite service action parameter list. This +only affects the "overwrite" sanitize operation. The default is a clear +INVERT bit. When the INVERT bit is set then the initialization pattern +is inverted between consecutive overwrite passes. +.TP +\fB\-O\fR, \fB\-\-overwrite\fR +perform an "overwrite" sanitize operation. When this option is given then +the \fI\-\-pattern=PF\fR or the \fI\-\-zero\fR option is required. +.TP +\fB\-p\fR, \fB\-\-pattern\fR=\fIPF\fR +where \fIPF\fR is the filename of a file containing the initialization +pattern required by an "overwrite" sanitize operation. The length of +this file will be used as the length of the initialization pattern unless +the \fI\-\-ipl=LEN\fR option is given. The length of the initialization +pattern must be from 1 to the logical block size of the \fIDEVICE\fR. +.TP +\fB\-Q\fR, \fB\-\-quick\fR +the default action (i.e. when the option is not given) is to give the user +15 seconds to reconsider doing a sanitize operation on the \fIDEVICE\fR. +When this option is given that step (i.e. the 15 second warning period) +is skipped. +.TP +\fB\-T\fR, \fB\-\-test\fR=\fITE\fR +set the TEST field in the overwrite service action parameter list. This +only affects the "overwrite" sanitize operation. The default is to place +0 in that field. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR +where \fISECS\fR is the number of seconds used for the timeout on the +SANITIZE command. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-w\fR, \fB\-\-wait\fR +the default action (i.e. without this option and the \fI\-\-early\fR option) +is to start the SANITIZE command with the IMMED bit set then poll for the +progress indication with the REQUEST SENSE command until the sanitize +operation is complete (or fails). When this option is given (and the +\fI\-\-early\fR option is not given) then the SANITIZE command is started +with the IMMED bit clear. For a large disk this might take hours. [A +cryptographic erase operation could potentially be very quick.] +.TP +\fB\-z\fR, \fB\-\-zero\fR +with an "overwrite" sanitize operation this option causes the initialization +pattern to be zero (4 zeros are used as the initialization pattern). Cannot +be used with the \fI\-\-pattern=PF\fR option. If this option is given +twice (e.g. '\-zz') then 0xff is used as the initialization byte. +.TP +\fB\-Z\fR, \fB\-\-znr\fR +sets ZNR bit (zoned no reset) in cdb. Introduced in the SBC\-4 revision 7 +draft. +.SH NOTES +The SCSI SANITIZE command is closely related to the ATA SANITIZE command, +both are relatively new with the ATA command being the first one defined. +The SCSI to ATA Translation (SAT) definition for the SCSI SANITIZE command +appeared in the SAT\-3 revision 4 draft. +.PP +When a SAT layer is used to a (S)ATA disk then for OVERWRITE the +initialization pattern must be 4 bytes long. So this means either the +\fI\-\-zero\fR option may be given, or a pattern file (with the +\fI\-\-pattern=PF\fR option) that is 4 bytes long or set to that +length with the \fI\-\-ipl=LEN\fR option. +.PP +The SCSI SANITIZE command is related to the SCSI FORMAT UNIT command. It +is likely that a block erase sanitize operation would take a similar +amount of time as a format on the same disk (e.g. 9 hours for a 2 Terabyte +disk). The primary goal of a format is the configuration of the disk at +the end of a format (e.g. different logical block size or protection +information added). Removal of user data is only a side effect of a format. +With the SCSI SANITIZE command, removal of user data is the primary goal. +If a sanitize operation is interrupted (e.g. the disk is power cycled) +then after power up any remaining user data will not be available and the +sanitize operation will continue. When a format is interrupted (e.g. the +disk is power cycled) the drafts say very little about the state of the +disk. In practice some of the original user data may remain and the format +may need to be restarted. +.PP +Finding out whether a disk (SCSI or ATA) supports SANITIZE can be a +challenge. If the user really needs to find out and no other information +is available then try 'sg_sanitize \-\-fail \-vvv ' and observe +the sense data returned may be the safest approach. Using the \fI\-\-fail\fR +variant of this utility should have no effect unless it follows an already +failed sanitize operation. If the SCSI REPORT SUPPORTED OPERATION CODES +command (see sg_opcodes) is supported then using it would be a better +approach for finding if sanitize is supported. +.SH EXAMPLES +These examples use Linux device names. For suitable device names in +other supported Operating Systems see the sg3_utils(8) man page. +.PP +As a precaution if this utility is called with no options then apart from +printing a usage message, nothing happens: +.PP + sg_sanitize /dev/sdm +.PP +To do a "block erase" sanitize the \fI\-\-block\fR option is required. +The user will be given a 15 second period to reconsider, the SCSI SANITIZE +command will be started with the IMMED bit set, then this utility will +poll for a progress indication with a REQUEST SENSE command until the +sanitize operation is finished: +.PP + sg_sanitize \-\-block /dev/sdm +.PP +To start a "block erase" sanitize and return from this utility once it is +started (but not yet completed) use the \fI\-\-early\fR option: +.PP + sg_sanitize \-\-block \-\-early /dev/sdm +.PP +If the 15 second reconsideration time is not required add the +\fI\-\-quick\fR option: +.PP + sg_sanitize \-\-block \-\-quick \-\-early /dev/sdm +.PP +To do an "overwrite" sanitize a pattern file may be given: +.PP + sg_sanitize \-\-overwrite \-\-pattern=rand.img /dev/sdm +.PP +If the length of that "rand.img" is 512 bytes (a typically logical block +size) then to use only the first 17 bytes (repeatedly) in the "overwrite" +sanitize operation: +.PP + sg_sanitize \-\-overwrite \-\-pattern=rand.img \-\-ipl=17 /dev/sdm +.PP +To overwrite with zeros use: + sg_sanitize \-\-overwrite \-\-zero /dev/sdm +.SH EXIT STATUS +The exit status of sg_sanitize is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. Unless the \fI\-\-wait\fR option is given, the +exit status may not reflect the success of otherwise of the format. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2011\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_requests(8), sg_format(8) diff --git a/doc/sg_sat_identify.8 b/doc/sg_sat_identify.8 new file mode 100644 index 0000000..aee3467 --- /dev/null +++ b/doc/sg_sat_identify.8 @@ -0,0 +1,167 @@ +.TH SG_SAT_IDENTIFY "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_sat_identify \- send ATA IDENTIFY DEVICE command via SCSI to ATA +Translation (SAT) layer +.SH SYNOPSIS +.B sg_sat_identify +[\fI\-\-ck_cond\fR] [\fI\-\-extend\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-ident\fR] [\fI\-\-len=CLEN\fR] [\fI\-\-packet\fR] [\fI\-\-raw\fR] +[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends either an ATA IDENTIFY DEVICE command or an ATA IDENTIFY +PACKET DEVICE command to \fIDEVICE\fR and outputs the response. The devices +that respond to these commands are ATA disks and ATAPI devices respectively. +Rather than send these commands directly to the device they are sent via a +SCSI transport which is assumed to contain a SCSI to ATA Translation (SAT) +Layer (SATL). The SATL may be in an operating system driver, in host bus +adapter firmware or in some external enclosure. +.PP +The SAT standard (SAT ANSI INCITS 431\-2007, prior draft: sat\-r09.pdf at +www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16 +byte "cdb" and the other with a 12 byte cdb. This utility defaults to using +the 16 byte cdb variant. SAT\-4 revision 5 added a SCSI "ATA +PASS\-THROUGH(32)" command. SAT\-2 and SAT\-3 are now also standards: SAT\-2 +ANSI INCITS 465\-2010 and SAT\3 ANSI INCITS 517-2015 . The SAT\-4 project +is near standardization and the most recent draft is sat4r06.pdf . +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-c\fR, \fB\-\-ck_cond\fR +sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The +default setting is clear (i.e. 0). When set the SATL should yield a +sense buffer containing a ATA Result descriptor irrespective of whether +the command succeeded or failed. When clear the SATL should only yield +a sense buffer containing a ATA Result descriptor if the command failed. +.TP +\fB\-e\fR, \fB\-\-extend\fR +sets the EXTEND bit in the ATA PASS\-THROUGH SCSI cdb. The +default setting is clear (i.e. 0). When set a 48 bit LBA command is sent +to the device. This option has no effect when \fI\-\-len=12\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options +then exits. Ignores \fIDEVICE\fR if given. +.TP +\fB\-H\fR, \fB\-\-hex\fR +outputs the ATA IDENTIFY (PACKET) DEVICE response in hex. The default +action (i.e. without any '\-H' options) is to output the response in +hex, grouped in 16 bit words (i.e. the ATA standard's preference). +When given once, the response is output in ASCII hex bytes (i.e. the +SCSI standard's preference). When given twice (i.e. '\-HH') the output +is in hex, grouped in 16 bit words, the same as the default but without +a header. When given thrice (i.e. '\-HHH') the output is in hex, grouped in +16 bit words, in a format that is acceptable for 'hdparm \-\-Istdin' to +process. '\-HHHH' simply outputs hex data bytes, space separated, 16 per +line. +.TP +\fB\-i\fR, \fB\-\-ident\fR +outputs the World Wide Name (WWN) of the device. This should be a NAA\-5 +64 bit number. It is output in hex prefixed with "0x". If not available +then "0x0000000000000000" is output. The equivalent for a SCSI disk (i.e. its +logical unit name) can be found with "sg_vpd \-ii". +.TP +\fB\-l\fR, \fB\-\-len\fR=CLEN +CLEN this is the length of the SCSI cdb used for the ATA PASS\-THROUGH +command. CLEN can either be 12, 16 or 32. The default is 16. The larger +cdb sizes are needed for 48 bit LBA addressing of ATA devices. The ATA +Auxiliary and ICC registers are only conveyed with the 32 byte cdb variant. +.TP +\fB\-p\fR, \fB\-\-packet\fR +send an ATA IDENTIFY PACKET DEVICE command (via the SATL). The default +action is to send an ATA IDENTIFY DEVICE command. Note that the ATAPI +specification by T13 (i.e. the PACKET interface) is now obsolete. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the ATA IDENTIFY (PACKET) DEVICE response in binary. The output +should be piped to a file or another utility when this option is used. +The binary is sent to stdout, and errors are sent to stderr. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increases the level or verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string +.SH NOTES +Since the response to the IDENTIFY (PACKET) DEVICE command is very +important for the correct use of an ATA(PI) device (and is typically the +first command sent), a SATL should provide an ATA Information VPD page +which contains the similar information. +.PP +The SCSI ATA PASS\-THROUGH (12) command's opcode is 0xa1 and it clashes with +the MMC set's BLANK command used by cd/dvd writers. So a SATL in front +of an ATAPI device that uses MMC (i.e. has peripheral device type 5) +probably should treat opcode 0xa1 as a BLANK command and send it through +to the cd/dvd drive. The ATA PASS\-THROUGH (16) command's opcode (0x85) +does not clash with anything so it is a better choice. +.PP +Prior to Linux kernel 2.6.29 USB mass storage limited sense data to 18 bytes +which made the \fB\-\-ck_cond\fR option yield strange (truncated) results. +.SH EXAMPLES +These examples use Linux device names and a Linux utility called hdparm. For +suitable device names in other supported Operating Systems see the +sg3_utils(8) man page. +.PP +In this example /dev/sdb is a SATA 2.5" disk connected via a USB (type C +connector) dongle that implements the UAS (USB attached SCSI) protocol (also +known as UASP). UAS is a vast improvement over the USB mass storage class. +.PP + # sg_sat_identify /dev/sdb +.br +Response for IDENTIFY DEVICE ATA command: +.br + 00 0c5a 3fff c837 0010 0000 0000 003f 0000 .Z ?. .7 .. .. .. .? .. +.br + .... +.PP +The hexadecimal ASCII (with plain ASCII to the right) output is abridged +to a single line (i.e. the first 16 bytes (or 8 words)). Now to decode +some of that ATA Identify response. First sg_inq can decode a few strings: +.PP + # sg_sat_identify \-HHHH /dev/sdb | sg_inq \-\-ata \-I \- +.br +ATA device: model, serial number and firmware revision: +.br + ST9500420AS 5VJCE6R7 0002SDM1 +.PP +For a lot more details, the hdparm utility is a good choice: +.PP + # sg_sat_identify \-HHH /dev/sdb | hdparm \-\-Istdin +.br +ATA device, with non\-removable media +.br + Model Number: ST9500420AS +.br + Serial Number: 5VJCE6R7 +.br + Firmware Revision: 0002SDM1 +.br + Transport: Serial +.br +Standards: +.br + .... +.PP +There are about 80 more lines of details decoded by hdparm in this case. +Notice the difference in the number of "H" options: three give an unadorned +hex output arranged in (little endian) words (i.e. 16 bits each) while +four "H" options give an unadorned hex output in bytes (i.e. 8 bits each). +.SH EXIT STATUS +The exit status of sg_sat_identify is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2006\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd(sg3_utils), sg_inq(sg3_utils), sdparm(sdparm), hdparm(hdparm) diff --git a/doc/sg_sat_phy_event.8 b/doc/sg_sat_phy_event.8 new file mode 100644 index 0000000..8fcf80d --- /dev/null +++ b/doc/sg_sat_phy_event.8 @@ -0,0 +1,109 @@ +.TH SG_SAT_PHY_EVENT "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_sat_phy_event \- use ATA READ LOG EXT via a SAT pass\-through to fetch +SATA phy event counters +.SH SYNOPSIS +.B sg_sat_phy_event +[\fI\-\-ck_cond\fR] [\fI\-\-extend\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-ignore\fR] [\fI\-\-len=\fR{16|12}] [\fI\-\-raw\fR] [\fI\-\-reset\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends an ATA READ LOG EXT with the log page ("address") set to +11h to \fIDEVICE\fR and outputs the response. Log page 11h is defined in +the SATA 2.5 standard and contains phy event counters. Rather than send this +command directly to the \fIDEVICE\fR, are sent via a SCSI transport which is +assumed to contain a SCSI to ATA Translation (SAT) Layer (SATL). The SATL may +be in an operating system driver, in host bus adapter firmware or in some +external enclosure. +.PP +The SAT standard (SAT ANSI INCITS 431\-2007, prior draft: sat\-r09.pdf at +www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16 +byte "cdb" and the other with a 12 byte cdb. This utility defaults to using +the 16 byte cdb variant. SAT\-2 is also a standard: SAT\-2 ANSI INCITS +465\-2010 and the draft prior to that is sat2r09.pdf . The SAT-3 project has +started and the most recent draft is sat3r01.pdf . +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-c\fR, \fB\-\-ck_cond\fR +sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The +default setting is clear (i.e. 0). When set the SATL should yield a +sense buffer containing a ATA Result descriptor irrespective of whether +the command succeeded or failed. When clear the SATL should only yield +a sense buffer containing a ATA Result descriptor if the command failed. +.TP +\fB\-e\fR, \fB\-\-extend\fR +sets the EXTEND bit in the ATA PASS\-THROUGH SCSI cdb. The +default setting is clear (i.e. 0). When set a 48 bit LBA command is sent +to the device. This option has no effect when \fI\-\-len=12\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options +then exits. Ignores \fIDEVICE\fR if given. +.TP +\fB\-H\fR, \fB\-\-hex\fR +outputs the ATA READ LOG EXT response in hex. The default +action (i.e. without any '\-H' options) is to output the response in +hex, grouped in 16 bit words (i.e. the ATA standard's preference). +When given once, the response is output in ASCII hex bytes (i.e. the +SCSI standard's preference). When given twice (i.e. '\-HH') the output +is in hex, grouped in 16 bit words, the same as the default but without +a header. +.TP +\fB\-i\fR, \fB\-\-ignore\fR +usually the phy counter identifier names are decoded. When this option is +given, the numeric value of the identifier is output, the vendor flag, the +data length (in bytes) and the corresponding value. +.TP +\fB\-l\fR, \fB\-\-len\fR={16|12} +this is the length of the SCSI cdb used for the ATA PASS\-THROUGH commands. +The argument can either be 16 or 12. The default is 16. The larger cdb +size is needed for 48 bit LBA addressing of ATA devices. On the other +hand some SCSI transports cannot convey SCSI commands longer than 12 bytes. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the ATA READ LOG EXT response in binary. The output +should be piped to a file or another utility when this option is used. +The binary is sent to stdout, and errors are sent to stderr. +.TP +\fB\-R\fR, \fB\-\-reset\fR +reset the counters after the current values are returned, decoded and +displayed. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increases the level or verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string +.SH NOTES +The SCSI ATA PASS\-THROUGH (12) command's opcode is 0xa1 and it clashes with +the MMC set's BLANK command used by cd/dvd writers. So a SATL in front +of an ATAPI device that uses MMC (i.e. has peripheral device type 5) +probably should treat opcode 0xa1 as a BLANK command and send it through +to the cd/dvd drive. The ATA PASS\-THROUGH (16) command's opcode (0x85) +does not clash with anything so it is a better choice. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be +a SCSI generic (sg) device. In the 2.6 series block devices (e.g. disks +and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda" +will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char" +device names may be used as well (e.g. "/dev/st0m"). Prior to lk 2.6.29 +USB mass storage limited sense data to 18 bytes which made the +\fB\-\-ck_cond\fR option yield strange (truncated) results. +.SH EXIT STATUS +The exit status of sg_sat_identify is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2006\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_sat_identify,sg_sat_read_gplog(sg3_utils), +.B smp_rep_phy_err_log(smp_utils),sdparm(sdparm),hdparm(hdparm) diff --git a/doc/sg_sat_read_gplog.8 b/doc/sg_sat_read_gplog.8 new file mode 100644 index 0000000..cb8f646 --- /dev/null +++ b/doc/sg_sat_read_gplog.8 @@ -0,0 +1,114 @@ +.TH SG_SAT_READ_GPLOG "8" "April 2015" "sg3_utils\-1.41" SG3_UTILS +.SH NAME +sg_sat_read_gplog \- use ATA READ LOG EXT command via a SCSI to ATA +Translation (SAT) layer +.SH SYNOPSIS +.B sg_sat_read_gplog +[\fI\-\-ck_cond\fR] [\fI\-\-count=CO\fR] [\fI\-\-dma\fR] [\fI\-\-help\fR] +[\fI\-\-hex\fR] [\fI\-\-len=\fR{16|12}] [\fI\-\-log=\fRLA] +[\fI\-\-page=\fRPN] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends an ATA READ LOG EXT or an ATA READ LOG DMA EXT command to +the \fIDEVICE\fR. This command is used to read the general purpose log +of (S)ATA disks (not ATAPI devices such as DVD driver). Rather than send the +READ LOG (DMA) EXT command directly to the device it is sent via a SCSI +transport which is assumed to contain a SCSI to ATA Translation (SAT) +Layer (SATL). The SATL may be in an operating system driver, in host bus +adapter (HBA) firmware or in some external enclosure. +.PP +This utility does not currently attempt to decode the response from the +ATA disk, rather it outputs the response in ASCII hexadecimal grouped in +16 bit words. Following ATA conventions those words are decoded little +endian (note that SCSI commands use a big endian representation). In the +future this utility may attempt to decode some log pages, perhaps using +the \fI\-\-decode\fR option. +.PP +The SAT\-2 standard (SAT ANSI INCITS 465-2010, prior draft: sat2r09.pdf at +www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16 +byte "cdb" and the other with a 12 byte cdb. This utility defaults to using +the 16 byte cdb variant. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-C\fR, \fB\-\-ck_cond\fR +sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The +default setting is clear (i.e. 0). When set the SATL should yield a +sense buffer containing a ATA Result descriptor irrespective of whether +the ATA command succeeded or failed. When clear the SATL should only yield +a sense buffer containing a ATA Result descriptor if the ATA command failed. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fICO\fR +the number \fICO\fR is placed in the "count" field in the ATA READ +LOG EXT command. This specified the number of 512\-byte blocks of +data to be read from the specified log. +.TP +\fB\-d\fR, \fB\-\-dma\fR +use the ATA READ LOG DMA EXT command instead of ATA READ LOG EXT command. +Some devices require this to return valid log data. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options then exits. +Ignores \fIDEVICE\fR if given. +.TP +\fB\-H\fR, \fB\-\-hex\fR +when given once, the response is output in ASCII hexadecimal bytes. When +given twice, then the response is grouped into 16 bit words using ATA +conventions (i.e. little endian); this is the default output (i.e. when +this option is not given). When given thrice (i.e. '\-HHH') the output +is in hex, grouped in 16 bit words (without a leading offset and trailing +ASCII on each line), in a format that is acceptable for 'hdparm \-\-Istdin' +to process. +.TP +\fB\-L\fR, \fB\-\-log\fR=\fILA\fR +the number \fILA\fR is known as the "log address" in the ATA standards and +is placed in bits 7:0 of the "lba" field of the ATA READ LOG (DMA) EXT +command. This specifies the log to be returned (See ATA\-ACS for a detailed +list of available log addresses). The default value placed in the "lba +field is 0, returning the directory of available logs. The maximum value +allowed for \fILOG\fR is 0xff. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPN\fR +the number \fIPN\fR is the page number (within the log address) and is +placed in bits 32:16 of the "lba" field of the ATA READ LOG (DMA) EXT +command. The default value placed in the "lba" field is 0. The maximum value +allowed for \fILOG\fR is 0xffff. +.TP +\fB\-l\fR, \fB\-\-len\fR={16|12} +this is the length of the SCSI cdb used for the ATA PASS\-THROUGH commands. +The argument can either be 16 or 12. The default is 16. Some SCSI +transports cannot convey SCSI commands longer than 12 bytes. +.TP +\fB\-r\fR, \fB\-\-readonly\fR +causes the \fIDEVICE\fR to be opened with the read\-only flag (O_RDONLY in +Unix). The default action is to open \fIDEVICE\fR with the read\-write +flag (O_RDWR in Unix). In some cases sending power management commands to +ATA disks are defeated by OS actions on the close() if the \fIDEVICE\fR was +opened with the read\-write flag (e.g. the OS might think it needs to +flush something to disk). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increases the level or verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string +.SH NOTES +Prior to Linux kernel 2.6.29 USB mass storage limited sense data to 18 bytes +which made the \fB\-\-ck_cond\fR option yield strange (truncated) results. +.SH EXIT STATUS +The exit status of sg_sat_read_gplog is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHOR +Written by Hannes Reinecke and Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2014\-2015 Hannes Reinecke, SUSE Linux GmbH +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_sat_identify(sg3_utils), sg_inq(sg3_utils), sdparm(sdparm), +.B hdparm(hdparm) diff --git a/doc/sg_sat_set_features.8 b/doc/sg_sat_set_features.8 new file mode 100644 index 0000000..d732c3d --- /dev/null +++ b/doc/sg_sat_set_features.8 @@ -0,0 +1,112 @@ +.TH SG_SAT_SET_FEATURES "8" "November 2014" "sg3_utils\-1.40" SG3_UTILS +.SH NAME +sg_sat_set_features \- use ATA SET FEATURES command via a SCSI to ATA +Translation (SAT) layer +.SH SYNOPSIS +.B sg_sat_set_features +[\fI\-\-count=CO\fR] [\fI\-\-ck_cond\fR] [\fI--extended\fR] +[\fI\-\-feature=FEA\fR] [\fI\-\-help\fR] [\fI\-\-lba=LBA\fR] +[\fI\-\-len=\fR{16|12}] [\fI\-\-readonly\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends an ATA SET FEATURES command to the \fIDEVICE\fR. +This command is used to change settings of ATA non\-packet (i.e. disks) and +packet devices (e.g. cd/dvd drives). Rather than send the SET FEATURES +command directly to the device it is sent via a SCSI transport which is +assumed to contain a SCSI to ATA Translation (SAT) Layer (SATL). The SATL +may be in an operating system driver, in host bus adapter firmware or in +some external enclosure. +.PP +The SAT standard (SAT ANSI INCITS 431\-2007, prior draft: sat\-r09.pdf at +www.t10.org) defines two SCSI "ATA PASS\-THROUGH" commands: one using a 16 +byte "cdb" and the other with a 12 byte cdb. This utility defaults to using +the 16 byte cdb variant. SAT\-2 is also a standard: SAT\-2 ANSI INCITS +465\-2010 and the draft prior to that is sat2r09.pdf . The SAT-3 project has +started and the most recent draft is sat3r05b.pdf . +.PP +The features can be read using the sg_sat_identify utility which uses either +the ATA IDENTIFY DEVICE (for non\-packet devices) or the IDENTIFY PACKET +DEVICE (for packet devices) command. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fICO\fR +the number \fICO\fR is placed in the "count" field in the ATA SET +FEATURES command. Only some subcommands (a term used for the value +placed in the "feature" field) require the count field to be set. +The default value placed in the "count" field is 0. +.TP +\fB\-C\fR, \fB\-\-ck_cond\fR +sets the CK_COND bit in the ATA PASS\-THROUGH SCSI cdb. The +default setting is clear (i.e. 0). When set the SATL should yield a +sense buffer containing a ATA Result descriptor irrespective of whether +the ATA command succeeded or failed. When clear the SATL should only yield +a sense buffer containing a ATA Result descriptor if the ATA command failed. +.TP +\fB\-e\fR, \fB\-\-extended\fR +allow for extended LBA numbers (i.e. larger than 32 bits). +This value is enabled automatically for large LBA numbers, but can be +enabled explicitly even for low LBA numbers with this option. +.TP +\fB\-f\fR, \fB\-\-feature\fR=\fIFEA\fR +the value \fIFEA\fR is placed in the "feature" field in the ATA SET +FEATURES command. The term "subcommand" is sometimes used for this +value. The default value placed in the "feature" field is 0 which +is reserved and hence should not change anything. Two common examples +are 2h to enable the write cache and 82h to disable it. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options +then exits. Ignores \fIDEVICE\fR if given. +.TP +\fB\-L\fR, \fB\-\-lba\fR=\fILBA\fR +the number \fILBA\fR is placed in the "lba" field of the ATA SET +FEATURES command. Only some sub\-commands (a term used for the value +placed in the "feature" field) require the lba field to be set. This +value is typically not a "logical block address" as the acronym might +imply. The default value placed in the "lba" field is 0. The maximum value +allowed for \fILBA\fR is 0xfffffffe (or 0xffffff if \fI\-\-len=\fR12). +.TP +\fB\-l\fR, \fB\-\-len\fR={16|12} +this is the length of the SCSI cdb used for the ATA PASS\-THROUGH commands. +The argument can either be 16 or 12. The default is 16. Some SCSI +transports cannot convey SCSI commands longer than 12 bytes. +.TP +\fB\-r\fR, \fB\-\-readonly\fR +causes the \fIDEVICE\fR to be opened with the read\-only flag (O_RDONLY in +Unix). The default action is to open \fIDEVICE\fR with the read\-write +flag (O_RDWR in Unix). In some cases sending power management commands to +ATA disks are defeated by OS actions on the close() if the \fIDEVICE\fR was +opened with the read\-write flag (e.g. the OS might think it needs to +flush something to disk). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increases the level or verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string +.SH NOTES +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be +a SCSI generic (sg) device. In the 2.6 and 3 series block devices (e.g. disks +and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda" +will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char" +device names may be used as well (e.g. "/dev/st0m"). Prior to lk 2.6.29 +USB mass storage limited sense data to 18 bytes which made the +\fB\-\-ck_cond\fR option yield strange (truncated) results. +.SH EXIT STATUS +The exit status of sg_sat_set_features is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2007\-2014 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_sat_identify(sg3_utils), sg_inq(sg3_utils), sdparm(sdparm), +.B hdparm(hdparm) diff --git a/doc/sg_scan.8.linux b/doc/sg_scan.8.linux new file mode 100644 index 0000000..0698000 --- /dev/null +++ b/doc/sg_scan.8.linux @@ -0,0 +1,78 @@ +.TH SG_SCAN "8" "May 2013" "sg3_utils\-1.36" SG3_UTILS +.SH NAME +sg_scan \- scans sg devices (or SCSI/ATAPI/ATA devices) and prints +results +.SH SYNOPSIS +.B sg_scan +[\fI\-a\fR] +[\fI\-i\fR] +[\fI\-n\fR] +[\fI\-w\fR] +[\fI\-x\fR] +[\fIDEVICE\fR]* +.SH DESCRIPTION +.\" Add any additional description here +.PP +If no \fIDEVICE\fR names are given, sg_scan does a scan of the sg +devices and outputs a line of information for each sg device that is +currently bound to a SCSI device. If one or more \fIDEVICE\fRs are given +only those devices are scanned. +Each device is opened with the O_NONBLOCK flag so that the scan will +not "hang" on any device that another process holds an O_EXCL lock on. +.PP +Any given \fIDEVICE\fR name is expected to comply +with (to some extent) the Storage Architecture Model (SAM see www.t10.org). +Any device names associated with the Linux SCSI subsystem (e.g. /dev/sda +and /dev/st0m) are suitable. Devices names associated with ATAPI +devices (e.g. most CD/DVD drives and ATAPI tape drives) are also suitable. +If the device does not fall into the above categories then an ATA +IDENTIFY command is tried. +.PP +In Linux 2.6 and 3 series kernels, the lsscsi utility may be helpful. Apart +from providing more information (by data\-mining in the sysfs pseudo file +system), it does not need root permissions to execute, as this utility +would typically need. +.SH OPTIONS +.TP +\fB\-a\fR +do alphabetical scan (i.e. sga, sgb, sgc). Note that sg device nodes with +an alphabetical index have been deprecated since the Linux kernel 2.2 +series. +.TP +\fB\-i\fR +do a SCSI INQUIRY, output results in a second (indented) line. If the device +is an ATA disk then output information from an ATA IDENTIFY command +.TP +\fB\-n\fR +do numeric scan (i.e. sg0, sg1...) [default] +.TP +\fB\-w\fR +use a read/write flag when opening sg device (default is read\-only) +.TP +\fB\-x\fR +extra information output about queueing +.SH NOTES +This utility was written at a time when hotplugging of SCSI devices +was not supported in Linux. It used a simple algorithm to scan sg +device nodes in ascending numeric or alphabetical order, stopping +after there were 4 consecutive errors. +.PP +In the Linux kernel 2.6 series, this utility uses sysfs to find which +sg device nodes are active and only checks those. Hence there can be +large "holes" in the numbering of sg device nodes (e.g. after an +adapter has been removed) and still all active sg device nodes will +be listed. This utility assumes that sg device nodes are named using +the normal conventions and searches from /dev/sg0 to /dev/sg4095 +inclusive. +.SH EXIT STATUS +The exit status of sg_scan is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert and F. Jansen +.SH COPYRIGHT +Copyright \(co 1999\-2013 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B lsscsi(8) diff --git a/doc/sg_scan.8.win32 b/doc/sg_scan.8.win32 new file mode 100644 index 0000000..b87888a --- /dev/null +++ b/doc/sg_scan.8.win32 @@ -0,0 +1,170 @@ +.TH SG_SCAN "8" "August 2014" "sg3_utils\-1.40" SG3_UTILS +.SH NAME +sg_scan \- scan storage devices and map to volume names +.SH SYNOPSIS +.B sg_scan +[\fI\-\-bus\fR] [\fI\-\-help\fR] [\fI\-\-letter=VL\fR] [\fI\-\-scsi\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility scans for physical drives (a.k.a. "hard drives"), cd/dvd drives +and tape drives and maps them to the corresponding volumes. There may be +many, one or no corresponding volumes. There is one line output per device +with identification strings to the right. Its purpose is to list the +storage device names that can be used by other utilities in this package. +.PP +In later versions of Windows this utility may need to be "run as +Administrator" for disks and other devices to be seen. If not those devices +will simply not appear as calls to query them fail with access permission +problems. +.PP +There is an optional SCSI adapter scan which may find additional storage +devices other than the ones listed above. An example is a SCSI Enclosure +Services (SES) device typically found in disk arrays. +.PP +Storage and related devices can have several device names in Windows. +Probably the most common in the volume name (e.g. "D:"). There is also +a "class" device name, and this utility scans for three of +them: "PhysicalDrive", "CDROM" and "TAPE". is an integer +starting at 0 allocated in ascending order as devices are discovered (and +sometimes rediscovered). +.PP +Some storage devices have a SCSI lower level device name which starts +with a SCSI (pseudo) adapter name of the form "SCSI:". To this is added +sub\-addressing in the form of a "bus" number, a "target" identifier and +a LUN (Logical Unit Number). The "bus" number is also known as a "PathId". +These components are combined by the utility to make a device name of the +form: "SCSI:,,". This utility allows the +trailing "," to be omitted in which case a LUN of zero is assumed. This +lower level device name cannot often be used directly since Windows blocks +attempts to use it if a class driver has "claimed" the device. There are +SCSI device types (e.g. Automation/Drive interface type) for which there is +no class driver. At least two transports ("bus types" in Windows jargin): +USB and IEEE 1394 do not have a "scsi" device names of this form. +.PP +In keeping with DOS file system conventions, the various device names +can be given in upper, lower or mixed case. Since "PhysicalDrive" is +tedious to write, a shortened form of "PD" is permitted by all +utilities in this package. +.PP +A single device (e.g. a disk) can have many device names! For +example: "PDO" can also be "C:", "D:" and "SCSI0:0,1,0". The two volume names +reflect that the disk has two partitions on it. Disk partitions that are +not recognised by Windows are not usually given a volume name. However +Vista does show a volume name for a disk which has no partitions recognised +by it and when selected invites the user to format it (which is rather +unfriendly to other OSes). +.PP +The scanning logic and output of this command changed significantly in +sg3_utils version 1.27 . The SCSI adapter based scanned is now an +optional extra. +.PP +For more information see the NOTES section below. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-bus\fR +show the bus type (or transport) by which the device is attached to the +operating systems. Two or more transports may be involved. For example, +a SATA disk may be in the external enclosure connected to the computer via +USB in which case the bus type is USB. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options +then exits. +.TP +\fB\-l\fR, \fB\-\-letter\fR=\fIVL\fR +normally a device that has multiple volume names has up to four listed. If +there are more than that a "+" is added after the fourth. When this option +is given the \fIVL\fR argument is assumed to be a volume name (i.e. 'C' +to 'Z') and if found in the scan, only that volume name appears in the +output. If there are novolume names in the output then \fIVL\fR was not +found. +.TP +\fB\-s\fR, \fB\-\-scsi\fR +do a SCSI adapter based scan after the normal storage device based scan. +There is a blank line between the normal scan and the SCSI adapter based +scan. If this option is given twice then only the SCSI adapter based scan +is done. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increases the level or verbosity. Can be used multiple times to display +more of the internal data, both in normal and error processing. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string +.SH NOTES +This utility does not support Windows 95, 98 and ME (and earlier Windows +operating systems). The target Windows operating systems are currently +Windows 2000, 2003, XP and Vista (and their variants). +.PP +When the \fI\-\-scsi\fR option is given the SCSI adapter tuple is followed +by a list of two or three fields. First is "claimed=0|1" indicating whether +a class driver has claimed the device. The next field is "pdt=" +where is the "peripheral device type" as defined in the SCSI INQUIRY +command (see SPC\-4 at http://www.t10.org). The has a trailing "h" to +indicate that it is hexadecimal. Sometimes a third field with the +word "dubious" appears. This flags that what is supposed to be a SCSI +INQUIRY command response has a badly formed "additional length" field. +Thus the corresponding device is unlikely to be a native SCSI device. +.PP +The DOS device names given the the CreateFile() call all start with +a "\\\\.\\" string. That can be given but if not will be supplied +automatically. +.PP +Scanning devices that are hot unplugged and replugged often can be +problematic, especially with the class device names. Each time a device is +removed and re\-added it gets a larger class device name (e.g. "PD3" +becomes "PD4" leaving "PD3" unused). This utility stops scanning class +devices after it find 8 consecutive "holes". +.SH EXAMPLES +The following examples are from a laptop with an internal drive (SATA), a +CD/DVD drive and a USB attached SATA disk. The latter disk has two volumes +recognised by Windows. +.PP + # sg_scan +.br +PD0 [C] FUJITSU MHY2160BH 0000 +.br +PD1 [DF] WD 2500BEV External 1.05 WD\-WXE90 +.br +CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03 +.PP +Now request bus types as well. BTW That is a SATA disk holding volume C: +and there is a "Sata" bus type. +.PP + # sg_scan \-b +.br +PD0 [C] FUJITSU MHY2160BH 0000 +.br +PD1 [DF] WD 2500BEV External 1.05 WD\-WXE90 +.br +CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03 +.PP +Now request a SCSI adapter scan as well. +.PP + # sg_scan \-b \-s +.br +PD0 [C] FUJITSU MHY2160BH 0000 +.br +PD1 [DF] WD 2500BEV External 1.05 WD\-WXE90 +.br +CDROM0 [E] MATSHITA DVD/CDRW UJDA775 CB03 +.br + +.br +SCSI0:0,0,0 claimed=1 pdt=0h FUJITSU MHY2160BH 0000 +.br +SCSI1:0,0,0 claimed=1 pdt=5h MATSHITA DVD/CDRW UJDA775 CB03 +.PP +.SH EXIT STATUS +The exit status of sg_scan is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2006\-2012 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/doc/sg_seek.8 b/doc/sg_seek.8 new file mode 100644 index 0000000..1b11f75 --- /dev/null +++ b/doc/sg_seek.8 @@ -0,0 +1,146 @@ +.TH SG_SEEK "8" "September 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_seek \- send SCSI SEEK, PRE-FETCH(10) or PRE-FETCH(16) command +.SH SYNOPSIS +.B sg_seek +[\fI\-\-10\fR] [\fI\-\-count=NC\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR] +[\fI\-\-immed\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num\-blocks=NUM\fR] +[\fI\-\-pre\-fetch\fR] [\fI\-\-readonly\fR] [\fI\-\-skip=SB\fR] +[\fI\-\-time\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +[\fI\-\-wrap\-offset=WO\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI SEEK(10), PRE\-FETCH(10) or PRE\-FETCH(16) command to the +\fIDEVICE\fR. The SEEK command has been obsolete since SBC\-2 (2005) but +still is supported on some hard disks and even some SSDs (solid state +disks). The PRE\-FETCH command can be viewed as SEEK's modern replacement. +Instead of talking about moving the disk heads to the track containing +the sort after LBA, it talks about bringing the sort after LBA (and a +given number of blocks) into the disk's cache. Also the PRE\-FETCH commands +have an IMMED field. +.PP +The PRE\-FETCH commands can report "real" errors but usually they will report +one of two "good" statuses. To do this they return the rarely used CONDITION +MET status. If the number of blocks does actually fit in the cache (when +IMMED=0) or there is enough room in the cache when the command arrives (when +IMMED=1) then a CONDITION MET status is returned. If the requested number of +blocks did not fit (IMMED=0) or would not fit (IMMED=1) then status GOOD +is returned. So if a disk has a large cache and PRE\-FETCH is used sparingly +then the command is more likely to return CONDITION MET than GOOD. This +presents some SCSI sub\-systems with problems as due to its rareness they +mishandle CONDITION MET and treat it as an error (see NOTES section below). +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-T\fR, \fB\-\-10\fR +use a 10 byte cdb command, either SEEK(10) or PRE\-FETCH(10) command. In +the absence of the \fI\-\-pre\-fetch\fR option, the SEEK(10) command is +used. If the \fI\-\-pre\-fetch\fR option is given without this option +then a PRE\-FETCH(16) command is used. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fINC\fR +\fINC\fR is the number of commands (one of SEEK(10), PRE\-FETCH(10) or +PRE\-FETCH(16)) that will be executed. The default value is 1. If an error +occurs it is noted and the program continues until \fINC\fR is exhausted. +If \fINC\fR is 0 then options are checked and the \fIDEVICE\fR is opened +but no commands are sent. +.TP +\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR +\fIGN\fR is the group number, a value between 0 and 63 (in hex: 0x3f). The +default value is 0. This option is ignored if the selected command is +SEEK(10). +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-immed\fR +this option only applies to PRE\-FETCH(10) and PRE\-FETCH(16), setting +the IMMED bit. Without this option, the \fIDEVICE\fR returns after it has +completed transferring all, or part of, the requested blocks into the +cache. If this option is given the \fIDEVICE\fR returns after it has done +sanity checks on the cdb (e.g. making sure the \fILBA\fR is greater than +the number of available blocks) and before it does the transfer into the +cache. +.br +Note that even when this option is given, the return status from the +PRE\-FETCH commands is still either CONDITION MET status (if the cache seems +to have enough free space for the transfer) or a GOOD status (if the cache +does not seem to have enough free space). +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +\fILBA\fR is the starting logical block address that is placed in the +command descriptor block (cdb) of the selected command. Note that the +\fILBA\fR field in SEEK(10) and PRE\-FETCH(10) is a 32 bit quantity, +while with PRE\-FETCH(16) it is a 64 bit quantity. The default value is +0 . +.TP +\fB\-n\fR, \fB\-\-num\-blocks\fR=\fINUM\fR +\fINUM\fR is the number of blocks, starting at and including \fILBA\fR, +to place in the \fIDEVICE\fR's cache. The SEEK(10) command does not use +the \fINUM\fR value. For PRE\-FETCH(10) \fINUM\fR is a 16 bit quantity, +while for PRE\-FETCH(16) it is a 32 bit quantity. The default value is +1 . If \fINUM\fR is 0 then the \fIDEVICE\fR will attempt to transfer all +blocks from the given \fILBA\fR to the end of the medium. +.TP +\fB\-p\fR, \fB\-\-pre\-fetch\fR +this option selects either PRE\-FETCH(10) or PRE\-FETCH(16) commands. With +the \fI\-\-10\fR also given, the PRE\-FETCH(10) command is selected; without +that option PRE\-FETCH(16) is selected. The default (in the absence of this +and other 'selecting' options) the SEEK(10) command is selected. +.TP +\fB\-r\fR, \fB\-\-readonly\fR +this option sets a 'read\-only' flag when the underlying operating system +opens the given \fIDEVICE\fR. This may not work since operating systems can +not easily determine whether a pass\-through is a logical read or write +operation so they take a risk averse stance and require read\-write type +\fIDEVICE\fR opens irrespective of what is performed by the pass\-through. +.TP +\fB\-s\fR, \fB\-\-skip\fR=\fISB\fR +\fISB\fR is the number of logical block addresses to skip, between repeated +commands when \fINC\fR is greater than 1. The default value of \fISB\fR is +1 . \fISB\fR may be set to 0 so that all \fINC\fR PRE\-FETCH commands use +the same \fILBA\fR. +.TP +\fB\-t\fR, \fB\-\-time\fR +if given the elapsed time to execute \fINC\fR commands is recorded. This is +printed out before this utility exits. If \fINC\fR is greater than 1 then +the the "per command" time is also printed. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-w\fR, \fB\-\-wrap\-offset\fR=\fIWO\fR +\fIWO\fR is the number of blocks, relative to \fILBA\fR, that when exceeded, +set the next command's logical block address back to \fILBA\fR. Whether +this "reset\-to\-LBA" action occurs depends on the values \fINC\fR and +\fISB\fR. +.SH NOTES +Prior to Linux kernel 4.17 the CONDITION MET status was logged as an error. +Recent versions of FreeBSD handle the CONDITION MET status properly. +.PP +If either the \fI\-\-count=NC\fR or \fI\-\-verbose\fR option is given then +a summary line like the following is output: +.PP + Command count=5, number of condition_mets=3, number of goods=2 +.PP +before the utility exits. +.SH EXIT STATUS +The exit status of sg_seek is 0 (GOOD) or 25 (CONDITION_MET) when this +utility is successful. If multiple commands are executed (e.g. when \fINC\fR +is greater than 1) then the result of the last executed SEEK or PRE\-FETCH +command sets the exit status. Otherwise see the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd(sg3_utils); sdparm(sdparm) diff --git a/doc/sg_senddiag.8 b/doc/sg_senddiag.8 new file mode 100644 index 0000000..e3fa612 --- /dev/null +++ b/doc/sg_senddiag.8 @@ -0,0 +1,300 @@ +.TH SG_SENDDIAG "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_senddiag \- performs a SCSI SEND DIAGNOSTIC command +.SH SYNOPSIS +.B sg_senddiag +[\fI\-\-doff\fR] [\fI\-\-extdur\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-list\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-page=PG\fR] [\fI\-\-pf\fR] +[\fI\-\-raw=H,H...\fR] [\fI\-\-raw=\-\fR] [\fI\-\-selftest=ST\fR] +[\fI\-\-test\fR] [\fI\-\-timeout=SECS\fR] [\fI\-\-uoff\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.PP +.B sg_senddiag +[\fI\-doff\fR] [\fI\-e\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-l\fR] [\fI\-pf\fR] +[\fI\-raw=H,H...\fR] [\fI\-raw=\-\fR] [\fI\-s=ST\fR] [\fI\-t\fR] +[\fI\-T=SECS\fR] [\fI\-uoff\fR] [\fI\-v\fR] [\fI\-V\fR] [\fI\-?\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends a SCSI SEND DIAGNOSTIC command to the \fIDEVICE\fR. It +can issue self\-tests, find supported diagnostic pages or send arbitrary +diagnostic pages. +.PP +When the \fI\-\-list\fR option and a \fIDEVICE\fR are given then the utility +sends a SCSI RECEIVE DIAGNOSTIC RESULTS command to fetch the response (i.e. +the page numbers of supported diagnostic pages). +.PP +When the \fI\-\-list\fR option is given without a \fIDEVICE\fR then a list of +diagnostic page names and their numbers, known by this utility, are listed. +.PP +This utility supports two command line syntax\-es, the preferred one is +shown first in the synopsis and explained in this section. A later section +on the old command line syntax outlines the second group of options. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-d\fR, \fB\-\-doff\fR +set the Device Offline (DevOffL) bit (default is clear). Only significant +when \fI\-\-test\fR option is set for the default self\-test. When set other +operations on any logical units controlled by the this device server (target) +may be affected (delayed) while a default self\-test is underway. +.TP +\fB\-e\fR, \fB\-\-extdur\fR +outputs the expected extended self\-test duration. The duration is given in +seconds (and minutes in parentheses). This figure is obtained from mode page +0xa (i.e. the control mode page). +.TP +\fB\-h\fR, \fB\-\-help\fR +print usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +outputs response from RECEIVE DIAGNOSTIC RESULTS in hex rather than decode it. +Only the Supported Diagnostic Pages diagnostic page (i.e. page_code=0) is +decoded; other pages (e.g. those used by SES) are output in hex. +.br +If \fI\-\-hex\fR is used once, the hex output has a relative address at the +start of each line. If \fI\-\-hex\fR is used twice, then ASCII is shown to +the right of each line of hex. If \fI\-\-hex\fR is used three time or more, +only the hex is output, in two character pairs (i.e. a byte) space separated +and up to 16 bytes per line. This latter form, if placed in a file or piped +through to another invocation, is suitable for the \fI\-\-raw=\-\fR option. +.TP +\fB\-l\fR, \fB\-\-list\fR +when a \fIDEVICE\fR is also given lists the names of all diagnostic pages +supported by this device. The request is sent via a SEND DIAGNOSTIC +command (with the "pF" bit set) and the response is fetched by a RECEIVE +DIAGNOSTIC RESULTS command. When used in the absence of a \fI\-\-list\fR +argument then a list of diagnostic page names and their numbers, known +by this utility, are listed. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the value placed in the parameter list length field of a +SEND DIAGNOSTIC command or in the allocation length field of a RECEIVE +DIAGNOSTIC RESULTS command. This only occurs when the other options imply +there will be data sent or received by the command. The default value +is 4096 bytes. \fILEN\fR cannot exceed 65535 or 0xffff in hexadecimal. +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-P\fR, \fB\-\-page\fR=\fIPG\fR +where \fIPG\fR is the RECEIVE DIAGNOSTIC RESULTS command page code field. +If this option is given the PCV bit in that command is set. When this option +is given then no SEND DIAGNOSTIC command is sent (unlike \fI\-\-list\fR). +If \fIPG\fR is 0 then the response is decoded as if it is the SPC Supported +Diagnostic pages diagnostic page. Other \fIPG\fR values (i.e. 1 to 255) +have their responses output in hex. +.TP +\fB\-p\fR, \fB\-\-pf\fR +set Page Format (PF) bit. By default it is clear (i.e. 0) unless the +list \fI\-\-list\fR option is given in which case the Page Format +bit is set (as required by SPC\-3). +.TP +\fB\-r\fR, \fB\-\-raw\fR=\fIH,H...\fR +string of comma separated hex numbers each of which should resolve to +a byte value (i.e. 0 to ff inclusive). A (single) space separated string +of hex bytes is also allowed but the list needs to be in quotes. This +sequence forms a diagnostic page to be sent with the SCSI SEND DIAGNOSTIC +command. Mostly likely the \fI\-\-pf\fR option should also be given. +.TP +\fB\-r\fR, \fB\-\-raw=\-\fR +reads sequence of bytes from stdin. The sequence may be comma, space, tab +or linefeed (newline) separated. If a line contains "#" then the remaining +characters on that line are ignored. Otherwise each non separator character +should resolve to a byte value (i.e. 0 to ff inclusive). This sequence forms +a diagnostic page to be sent with the SCSI SEND DIAGNOSTIC command. Mostly +likely the \fI\-\-pf\fR option should also be given. +.TP +\fB\-s\fR, \fB\-\-selftest\fR=\fIST\fR +where \fIST\fR is the self\-test code. The default value is 0 which is +inactive. Some other values: +.br + \fB1\fR : background short self\-test +.br + \fB2\fR : background extended self\-test +.br + \fB4\fR : aborts a (background) self\-test that is in progress +.br + \fB5\fR : foreground short self\-test +.br + \fB6\fR : foreground extended self\-test +.br +This option is mutually exclusive with default self\-test (i.e. +can't have (\fIST\fR > 0) and \fI\-\-test\fR). +.TP +\fB\-t\fR, \fB\-\-test\fR +sets the _default_ Self Test (SelfTest) bit. By default this is clear (0). +The \fI\-\-selftest=ST\fR option should not be active together with this +option. Both the \fI\-\-doff\fR and/or \fI\-\-uoff\fR options can be used +with this option. +.TP +\fB\-T\fR, \fB\-\-timeout\fR=\fISECS\fR +where \fISECS\fR is a timeout value (in seconds) for foreground self\-test +operations. The default value is 7200 seconds (2 hours) and any values +of \fISECS\fR less than the default are ignored. +.TP +\fB\-u\fR, \fB\-\-uoff\fR +set the Unit Offline (UnitOffL) bit (default is clear). Only significant +when \fI\-\-test\fR option is set for the default self\-test. When set other +operations on this logical unit may be affected (delayed) while a default +self\-test is underway. Some devices (e.g. Fujitsu disks) do more tests +when this bit is set. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH NOTES +All devices should support the default self\-test. The 'short' self\-test +codes should complete in 2 minutes or less. The 'extended' self\-test +codes' maximum duration is vendor specific (e.g. a little over 10 minutes +with the author's disks). The foreground self\-test codes wait until they +are completed while the background self\-test codes return immediately. The +results of both foreground and background self\-test codes are placed in +the 'self\-test results' log page (see sg_logs(8)). The SCSI command timeout +for this utility is set to 60 minutes to allow for slow foreground extended +self\-tests. +.PP +If the \fIDEVICE\fR is a disk then no file systems residing on that disk +should be mounted during a foreground self\-test. The reason is that other +SCSI commands may become queued behind the foreground self\-test and timeout. +.PP +When the \fI\-\-raw=H,H...\fR option is given then self\-tests should not +be selected. However the \fB\-\-pf\fR (i.e. "page format") option should be +given. The length of the diagnostic page to be sent is derived from the +number of bytes given to the \fI\-\-raw=H,H...\fR option. The diagnostic +page code (number) should be the first byte of the sequence (i.e. as +dictated by SPC\-3 diagnostic page format). See the EXAMPLES section below. +.PP +Arbitrary diagnostic pages can be read (in hex) with the sg_ses(8) +utility (not only those defined in SES\-2). +.PP +If the utility is used with no options (e.g. "sg_senddiag /dev/sg1") +Then a degenerate SCSI SEND DIAGNOSTIC command is sent with zero +in all its fields apart from the opcode. Some devices report this +as an error while others ignore it. It is not entirely clear from +SPC\-3 if it is invalid to send such a command. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI +generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks and +DVD drives) can also be specified. +.PP +To access SCSI enclosures see the sg_ses(8) utility. sg_ses uses the +SCSI SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS commands as outlined +in the SES\-2 (draft) standard. +.SH EXIT STATUS +The exit status of sg_senddiag is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-doff\fR +set the Device Offline (DevOffL) bit (default is clear). Only significant +when \fI\-t\fR option is set for the default self\-test. Equivalent to +\fI\-\-doff\fR in the main description. +.TP +\fB\-e\fR +outputs the expected extended self\-test duration. Equivalent to +\fI\-\-extdur\fR in the main description. +.TP +\fB\-h\fR +outputs response from RECEIVE DIAGNOSTIC RESULTS in hex rather than decode +it. +.TP +\fB\-H\fR +outputs response from RECEIVE DIAGNOSTIC RESULTS in hex rather than decode it. +.TP +\fB\-l\fR +when a \fIDEVICE\fR is also given lists the names of all diagnostic +pages supported by this device. The request is sent via a SEND DIAGNOSTIC +command (with the "pf" bit set) and the response is fetched by a RECEIVE +DIAGNOSTIC RESULTS command. When used in the absence of a \fIDEVICE\fR +argument then a list of diagnostic page names and their numbers, known +by this utility, are listed. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-pf\fR +set Page Format (PF) bit. By default it is clear (i.e. 0) unless +the \fI\-l\fR option is given in which case the Page Format bit is set +(as required by SPC\-3). +.TP +\fB\-raw\fR=\fIH,H...\fR +string of comma separated hex numbers each of which should resolve to +a byte value (i.e. 0 to ff inclusive). This sequence forms a diagnostic +page to be sent with the SCSI SEND DIAGNOSTIC command. Mostly likely +the \fI\-pf\fR option should also be given. +.TP +\fB\-raw=-\fR +reads sequence of bytes from stdin. The sequence may be comma, space, tab +or linefeed (newline) separated. If a line contains "#" then the remaining +characters on that line are ignored. Otherwise each non separator character +should resolve to a byte value (i.e. 0 to ff inclusive). This sequence forms +a diagnostic page to be sent with the SCSI SEND DIAGNOSTIC command. Mostly +likely the \fI\-pf\fR option should also be given. +.TP +\fB\-s\fR=\fIST\fR +where \fIST\fR is the self\-test code. The default value is 0 which is +inactive. A value of 1 selects a background short self\-test; 2 selects +a background extended self\-test; 5 selects a foreground short self\-test; +6 selects a foreground extended test. A value of 4 will abort +a (background) self\-test that is in progress. This option is mutually +exclusive with default self\-test (i.e. \fI\-t\fR). +.TP +\fB\-t\fR +sets the _default_ Self Test (SelfTest) bit. By default this is clear (0). +The \fI\-s=ST\fR option should not be active together with this option. +Both the \fI\-doff\fR and/or \fI\-uoff\fR options can be used with this +option. +.TP +\fB\-T\fR=\fISECS\fR +where \fISECS\fR is a timeout value (in seconds) for foreground self\-test +operations. See the \fI\-\-timeout=SECS\fR option above. +.TP +\fB\-uoff\fR +set the Unit Offline (UnitOffL) bit (default is clear). Equivalent to +\fI\-\-uoff\fR in the main description. +.TP +\fB\-v\fR +increase level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR +print out version string then exit. +.TP +\fB\-?\fR +output usage message. Ignore all other parameters. +.SH EXAMPLES +The examples sub\-directory in the sg3_utils packages contains two example +scripts that turn on the CJTPAT (jitter pattern) on some SAS disks (one +script for each phy). One possible invocation for phy 1 is: +.PP + sg_senddiag \-\-pf \-\-raw=\- /dev/sg2 < sdiag_sas_p1_cjtpat.txt +.PP +There is also an example script that turns on the IDLE pattern. Once a +test pattern has been started it can be turned off by resetting the phy +or with the STOP phy pattern function: +.PP + sg_senddiag \-\-pf \-\-raw=\- /dev/sg2 < sdiag_sas_p1_stop.txt +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2003\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_ses(8), sg_logs(8), smartmontools(see net) diff --git a/doc/sg_ses.8 b/doc/sg_ses.8 new file mode 100644 index 0000000..573c92b --- /dev/null +++ b/doc/sg_ses.8 @@ -0,0 +1,755 @@ +.TH SG_SES "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_ses \- access a SCSI Enclosure Services (SES) device +.SH SYNOPSIS +.B sg_ses +[\fI\-\-descriptor=DES\fR] [\fI\-\-dev\-slot\-num=SN\fR] [\fI\-\-eiioe=A_F\fR] +[\fI\-\-filter\fR] [\fI\-\-get=STR\fR] [\fI\-\-hex\fR] +[\fI\-\-index=IIA\fR | \fI\-\-index=TIA,II\fR] [\fI\-\-inner\-hex\fR] +[\fI\-\-join\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-page=PG\fR] [\fI\-\-quiet\fR] +[\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-sas\-addr=SA\fR] +[\fI\-\-status\fR] [\fI\-\-verbose\fR] [\fI\-\-warn\fR] \fIDEVICE\fR +.PP +.B sg_ses +[\fI\-\-byte1=B1\fR] [\fI\-\-clear=STR\fR] [\fI\-\-control\fR] +[\fI\-\-data=H,H...\fR] [\fI\-\-data=@FN\fR] [\fI\-\-descriptor=DES\fR] +[\fI\-\-dev\-slot\-num=SN\fR] [\fI\-\-index=IIA\fR | \fI\-\-index=TIA,II\fR] +[\fI\-\-mask\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-nickname=SEN\fR] +[\fI\-\-nickid=SEID\fR] [\fI\-\-page=PG\fR] [\fI\-\-readonly\fR] +[\fI\-\-sas\-addr=SA\fR] [\fI\-\-set=STR\fR] [\fI\-\-verbose\fR] +\fIDEVICE\fR +.PP +.B sg_ses +\fI\-\-data=@FN\fR \fI\-\-status\fR [\fI\-\-raw\fR \fI\-\-raw\fR] +[] +.PP +.B sg_ses +[\fI\-\-enumerate\fR] [\fI\-\-index=IIA\fR] [\fI\-\-list\fR] [\fI\-\-help\fR] +[\fI\-\-version\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Fetches management information from a SCSI Enclosure Service (SES) device. +This utility can also modify the state of a SES device. The \fIDEVICE\fR +should be a SES device which may be a dedicated enclosure services +processor in which case an INQUIRY response's Peripheral Device Type is +13 [0xd]. Alternatively it may be attached to another type of SCSI +device (e.g. a disk) in which case the EncServ bit is set in its INQUIRY +response. +.PP +If the \fIDEVICE\fR argument is given with no options then the names of all +diagnostic pages (dpages) supported are listed. Most, but not necessarily +all, of the named dpages are defined in the SES standards and drafts. The +most recent reference for this utility is the draft SCSI Enclosure Services +4 document T10/BSR INCITS 555 Revision 1 at http://www.t10.org . Existing +standards for SES, SES\-2 and SES\-3 are ANSI INCITS 305\-1998 and ANSI +INCITS 448\-2008 and ANSI INCITS 518\-2017 respectively. +.PP +The first form shown in the synopsis is for fetching and decoding dpages or +fields from the SES \fIDEVICE\fR. A SCSI RECEIVE DIAGNOSTIC RESULTS command +is sent to the \fIDEVICE\fR to obtain each dpage response. Rather than +decoding a fetched dpage, it may be output in hex or binary with the +\fI\-\-hex\fR or \fI\-\-raw \-\-raw\fR options. +.PP +The second form in the synopsis is for modifying dpages or fields held in +the SES \fIDEVICE\fR. A SCSI SEND DIAGNOSTIC command containing a "control" +dpage is sent to the \fIDEVICE\fR to cause changes. Changing the state of an +enclosure (e.g. requesting the "ident" (locate) LED to flash on a disk +carrier in an array) is typically done using a read\-modify\-write cycle. +See the section on CHANGING STATE below. +.PP +The third form in the synopsis shows the options for decoding the contents +of a file that holds a hexadecimal or binary representation of a SES +dpage response. Typically an earlier invocation of the first form of this +utility with the '\-HHHH' option would have generated that file. Since no +SCSI commands are sent, the \fIDEVICE\fR argument if given will be ignored. +.PP +The last form in the synopsis shows the options for providing command line +help (i.e. usage information), listing out dpage and field information tables +held by the utility (\fI\-\-enumerate\fR), or printing the version string +of this utility. +.PP +There is a web page discussing this utility at +http://sg.danny.cz/sg/sg_ses.html . Support for downloading microcode to +a SES device has been placed in a separate utility called sg_ses_microcode. +.PP +In the following sections "dpage" refers to a diagnostic page, either fetched +with a SCSI RECEIVE DIAGNOSTIC RESULTS command, sent to the \fIDEVICE\fR with +a SCSI SEND DIAGNOSTIC command, or fetched from data supplied by the +\fI\-\-data=\fR option. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-b\fR, \fB\-\-byte1\fR=\fIB1\fR +some modifiable dpages may need byte 1 (i.e. the second byte) set. In the +Enclosure Control dpage, byte 1 contains the INFO, NON\-CRIT, CRIT and +UNRECOV bits. In the Subenclosure String Out, Subenclosure Nickname Control +and Download Microcode Control dpages, byte 1 is the Subenclosure identifier. +Active when the \fI\-\-control\fR and \fI\-\-data=H,H...\fR options are used +and the default value is 0. If the \fI\-\-clear=STR\fR or \fI\-\-set=STR\fR +option is used then the value read from byte 1 is written back to byte 1. +\fIB1\fR is in decimal unless it is prefixed by '0x' or '0X' (or has a +trailing 'h' or 'H'). +.TP +\fB\-C\fR, \fB\-\-clear\fR=\fISTR\fR +Used to clear an element field in the Enclosure Control or Threshold Out +dpage. Must be used together with an indexing option to specify which element +is to be changed. The Enclosure Control dpage is assumed if the +\fI\-\-page=PG\fR option is not given. See the STR FORMAT and the CLEAR, GET, +SET sections below. +.TP +\fB\-c\fR, \fB\-\-control\fR +will send control information to the \fIDEVICE\fR via a SCSI SEND +DIAGNOSTIC command. Cannot give both this option and \fI\-\-status\fR. +The Enclosure Control, String Out, Threshold Out, Array Control (obsolete +in SES\-2), Subenclosure String Out, Subenclosure Nickname Control and +Download Microcode dpages can be set currently. This option is assumed if +either the \fI\-\-clear=STR\fR or \fI\-\-set=STR\fR option is given. +.TP +\fB\-d\fR, \fB\-\-data\fR=\fIH,H...\fR +permits a string of comma separated (ASCII) hex bytes to be specified (limit +1024). A (single) space separated string of hex bytes is also allowed but +the list needs to be in quotes. This option allows the parameters to a +control dpage to be specified. The string given should not include the first 4 +bytes (i.e. page code and length). See the DATA SUPPLIED section below. +.TP +\fB\-d\fR, \fB\-\-data\fR=\- +reads one or more data strings from stdin, limit almost 2**16 bytes. stdin +may provide ASCII hex as a comma separated list (i.e. as with the +\fI\-\-data=H,H...\fR option). Additionally spaces, tabs and line feeds are +permitted as separators from stdin . Stops reading stdin when an EOF is +detected. See the DATA SUPPLIED section below. +.TP +\fB\-d\fR, \fB\-\-data\fR=@\fIFN\fR +reads one or more data strings from the file called \fIFN\fR, limit almost +2**16 bytes. The contents of the file is decoded in the same fashion as +stdin described in the previous option. See the DATA SUPPLIED section below. +.TP +\fB\-D\fR, \fB\-\-descriptor\fR=\fIDES\fR +where \fIDES\fR is a descriptor name (string) as found in the Element +Descriptor dpage. This is a medium level indexing alternative to the low +level \fI\-\-index=\fR options. If the descriptor name contains a space then +\fIDES\fR needs to be surrounded by quotes (single or double) or the space +escaped (e.g. preceded by a backslash). See the DESCRIPTOR NAME, DEVICE SLOT +NUMBER AND SAS ADDRESS section below. +.TP +\fB\-x\fR, \fB\-\-dev\-slot\-num\fR=\fISN\fR, \fB\-\-dsn\fR=\fISN\fR +where \fISN\fR is a device slot number found in the Additional Element Status +dpage. Only entries for FCP and SAS devices (with EIP=1) have device slot +numbers. \fISN\fR must be a number in the range 0 to 255 (inclusive). 255 is +used to indicate there is no corresponding device slot. This is a medium level +indexing alternative to the low level \fI\-\-index=\fR options. See the +DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS section below. +.TP +\fB\-E\fR, \fB\-\-eiioe\fR=\fIA_F\fR +\fIA_F\fR is either the string 'auto' or 'force'. There was some fuzziness +in the interpretation of the 'element index' field in the Additional Element +Status (AES) dpage between SES\-2 and SES\-3. The EIIOE bit was introduced to +resolve the problem but not all enclosures have caught up. In the SES\-3 +revision 12 draft the EIIOE bit was expanded to a 2 bit EIIOE field. +Using '\-\-eiioe=force' will decode the AES dpage as if the EIIOE field is set +to 1. Using '\-\-eiioe=auto' will decode the AES dpage as if the EIIOE field +is set to 1 if the first AES descriptor has its EIP bit set and its element +index field is 1 (in other words a heuristic to guess whether the EIIOE field +should be set to 1 or 0). +.br +If the enclosure sets the actual EIIOE field to 1 or more then this option has +no effect. It is recommended that HP JBOD users set \-\-eiioe=auto . +.TP +\fB\-e\fR, \fB\-\-enumerate\fR +enumerate all known dpage names and SES elements when this option is given +once. +.br +If \fI\-\-enumerate\fR is given twice, then the recognised acronyms for the +\fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options are +listed. The utility exits after listing this information, so most other +options and \fIDEVICE\fR are ignored. Since there are many acronyms for +the Enclosure Control/Status dpage then the output can be further restricted +by giving the \fI\-\-index=IIA\fR option (e.g. "sg_ses \-ee \-I ts" to only +show the acronyms associated with the Enclosure Control/Status dpage's +Temperature Sensor Element Type). +.TP +\fB\-f\fR, \fB\-\-filter\fR +cuts down on the amount of output from the Enclosure Status dpage and the +Additional Element Status dpage. When this option is given, any line which +has all its binary flags cleared (i.e. 0) is filtered out (i.e. ignored). +If a line has some other value on it (e.g. a temperature) then it is output. +When this option is used twice only elements associated with the "status=ok" +field (in the Enclosure status dpage) are output. The \fI\-\-filter\fR option +is useful for reducing the amount of output generated by the \fI\-\-join\fR +option. +.TP +\fB\-G\fR, \fB\-\-get\fR=\fISTR\fR +Used to read a field in a status element. Must be used together with a an +indexing option to specify which element is to be read. By default the +Enclosure Status dpage is read, the only other dpages that can be read are the +Threshold In and Additional Element Status dpages. If a value is found it is +output in decimal to stdout (by default) or in hexadecimal preceded by "0x" +if the \fI\-\-hex\fR option is also given. See the STR FORMAT and the CLEAR, +GET, SET sections below. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. Since there is a lot of information, +it is split into two pages. The most important is shown on the first page. +Use this option twice (e.g. '\-hh') to output the second page. Note: the +\fI\-\-enumerate\fR option might also be viewed as a help or usage type +option. And like this option it has a "given twice" form: '\-ee'. +.TP +\fB\-H\fR, \fB\-\-hex\fR +If the \fI\-\-get=STR\fR option is given then output the value found (if +any) in hexadecimal, with a leading "0x". Otherwise output the response +in hexadecimal; with trailing ASCII if given once, without it if given +twice, and simple hex if given three or more times. Ignored when all +elements from several dpages are being accessed (e.g. when the \fI\-\-join\fR +option is used). Also see the \fI\-\-raw\fR option which may be used +with this option. +.br +To dump one of more dpage responses to stdout in ASCII parsable hexadecimal +use \fI\-HHH\fR or \fI\-HHHH\fR. The triple H form only outputs hexadecimals +which is fine for a single dpage response. When all dpages are dumped (e.g. +with \fI\-\-page=all\fR) then the quad H form adds the name of each dpage +following a hash mark ('#'). The \fI\-\-data=\fR option parser ignores +everything from and including a hash mark to the end of the line. Hence the +output of the quad H form is still parsable plus it is easier for users to +view and possibly edit. \fI\-HHHHH\fR (that is 5) adds the page code in +hex after the page's name in the comment. +.TP +\fB\-I\fR, \fB\-\-index\fR=\fIIIA\fR +where \fIIIA\fR is either an individual index (II) or an Element type +abbreviation (A). See the INDEXES section below. If the \fI\-\-page=PG\fR +option is not given then the Enclosure Status (or Control) dpage is assumed. +May be used with the \fI\-\-join\fR option or one of the \fI\-\-clear=STR\fR, +\fI\-\-get=STR\fR or \fI\-\-set=STR\fR options. To enumerate the available +Element type abbreviations use the \fI\-\-enumerate\fR option. +.TP +\fB\-I\fR, \fB\-\-index\fR=\fITIA,II\fR +where \fITIA,II\fR is an type header index (TI) or Element type +abbreviation (A) followed by an individual index (II). See the INDEXES section +below. If the \fI\-\-page=PG\fR option is not given then the Enclosure +Status (or Control) dpage is assumed. May be used with the \fI\-\-join\fR +option or one of the \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR or +\fI\-\-set=STR\fR options. To enumerate the available Element type +abbreviations use the \fI\-\-enumerate\fR option. +.TP +\fB\-i\fR, \fB\-\-inner\-hex\fR +the outer levels of a status dpage are decoded and printed out but the +innermost level (e.g. the Element Status Descriptor) is output in hex. Also +active with the Additional Element Status and Threshold In dpages. Can be +used with an indexing option and/or \fI\-\-join\fR options. +.TP +\fB\-j\fR, \fB\-\-join\fR +group elements from the Element Descriptor, Enclosure Status and Additional +Element Status dpages. If this option is given twice then elements from the +Threshold In dpage are also grouped. The order is dictated by the Configuration +dpage. +.br +There can be a bewildering amount of information in the "join" output. The +default is to output everything. Several additional options are provided to +cut down the amount displayed. If the indexing options is given, only the +matching elements and their associated fields are output. The \fI\-\-filter\fR +option (see its description) can be added to reduce the amount of output. +Also "\-\-page=aes" (or "\-p 0xa") can be added to suppress the output of +rows that don't have a "aes" dpage component. See the INDEXES and DESCRIPTOR +NAME, DEVICE SLOT NUMBER AND SAS ADDRESS sections below. +.TP +\fB\-l\fR, \fB\-\-list\fR +This option is equivalent to \fI\-\-enumerate\fR. See that option. +.TP +\fB\-M\fR, \fB\-\-mask\fR +When modifying elements, the default action is a read (status element), +mask, modify (based on \fI\-\-clear=STR\fR or \fI\-\-set=STR\fR) then write +back as the control element. The mask step is new in sg_ses version 1.98 +and is based on what is allowable (and in the same location) in draft SES\-3 +revision 6. Those masks may evolve, as they have in the past. This option +re\-instates the previous logic which was to ignore the mask step. The +default action (i.e. without this option) is to perform the mask step in +the read\-mask\-modify\-write sequence. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +\fILEN\fR is placed in the ALLOCATION LENGTH field of the SCSI RECEIVE +DIAGNOSTIC RESULTS commands sent by the utility. It represents the maximum +size of data the SES device can return (in bytes). It cannot exceed 65535 +and defaults to 65532 (bytes). Some systems may not permit such large sizes +hence the need for this option. If \fILEN\fR is less than 0 or greater than +65535 then an error is generated. If \fILEN\fR is 0 then the default value +is used, otherwise if it is less than 4 then it is ignored (and a warning is +sent to stderr). +.TP +\fB\-n\fR, \fB\-\-nickname\fR=\fISEN\fR +where \fISEN\fR is the new Subenclosure Nickname. Only the first 32 +characters (bytes) of \fISEN\fR are used, if more are given they are +ignored. See the SETTING SUBENCLOSURE NICKNAME section below. +.TP +\fB\-N\fR, \fB\-\-nickid\fR=\fISEID\fR +where \fISEID\fR is the Subenclosure identifier that the new +Nickname (\fISEN\fR) will be applied to. So \fISEID\fR must be an existing +Subenclosure identifier. The default value is 0 which is the +main enclosure. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR +where \fIPG\fR is a dpage abbreviation or code (a number). If \fIPG\fR +starts with a digit it is assumed to be in decimal unless prefixed by +0x for hex. Valid range is 0 to 255 (0x0 to 0xff) inclusive. Default is +dpage 'sdp' which is page_code 0 (i.e. "Supported Diagnostic Pages") if +no other options are given. +.br +Page code 0xff or abbreviation "all" is not a real dpage (as the highest +real dpage is 0x3f) but instead causes all dpages whose page code is 0x2f +or less to be output. This can be used with either the \fI\-HHHH\fR or +\fI\-rr\fR to send either hexadecimal ASCII or binary respectively to +stdout. +.br +To list the available dpage abbreviations give "xxx" for \fIPG\fR; the same +information can also be found with the \fI\-\-enumerate\fR option. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +this suppresses the number of warnings and messages output. The exit status +of the utility is unaffected by this option. +.TP +\fB\-r\fR, \fB\-\-raw\fR +outputs the chosen status dpage in ASCII hex in a format suitable for a +later invocation using the \fI\-\-data=\fR option. A dpage less its first +4 bytes (page code and length) is output. When used twice (e.g. \fI\-rr\fR) +the full dpage contents is output in binary to stdout. +.br +when \fI\-rr\fR is used together with the \fI\-\-data=\-\fR or +\fI\-\-data=@FN\fR then stdin or file FN is decoded as a binary stream that +continues to be read until an end of file (EOF). Once that data is read then +the internal raw option is cleared to 0 so the output is not effected. So +the \fI\-rr\fR option either changes how the input or output is treated, +but not both. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only (e.g. in Unix with the O_RDONLY flag). +The default is to open it read\-write. +.TP +\fB\-A\fR, \fB\-\-sas\-addr\fR=\fISA\fR +this is an indexing method for SAS end devices (e.g. SAS disks). The utility +will try to find the element or slot in the Additional Element Status dpage +whose SAS address matches \fISA\fR. For a SAS disk or tape that SAS address +is its target port identifier for the port connected to that element or slot. +Most SAS disks and tapes have two such target ports, usually numbered +consecutively. +.br +SATA devices in a SAS enclosure often receive "manufactured" target port +identifiers from a SAS expander; typically will have a SAS address close to, +but different from, the SAS address of the expander itself. Note that this +manufactured target port identifier is different from a SATA disk's WWN. +.br +\fISA\fR is a hex number that is up to 8 digits long. It may have a +leading '0x' or '0X' or a trailing 'h' or 'H'. This option is a medium level + indexing alternative to the low level \fI\-\-index=\fR options. +See the DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS section below. +.TP +\fB\-S\fR, \fB\-\-set\fR=\fISTR\fR +Used to set an element field in the Enclosure Control or Threshold Out dpage. +Must be used together with an indexing option to specify which element is to +be changed. The Enclosure Control dpage is assumed if the \fI\-\-page=PG\fR +option is not given. See the STR FORMAT and CLEAR, GET, SET sections below. +.TP +\fB\-s\fR, \fB\-\-status\fR +will fetch dpage from the \fIDEVICE\fR via a SCSI RECEIVE DIAGNOSTIC RESULTS +command (or from \fI\-\-data=@FN\fR). In the absence of other options that +imply modifying a dpage (e.g. \fI\-\-control\fR or \fI\-\-set=STR\fR) then +\fI\-\-status\fR is assumed, except when the \fI\-\-data=\fR option is given. +When the \fI\-\-data=\fR option is given there is no default action: either +the \fI\-\-control\fR or this option must be given to distinguish between +the two different ways that data will be treated. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity. For example when this option is given four +times (in which case the short form is more convenient: '\-vvvv') then if +the internal join array has been generated then it is output to stderr in +a form suitable for debugging. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-w\fR, \fB\-\-warn\fR +warn about certain irregularities with warnings sent to stderr. The join +is a complex operation that relies on information from several dpages to be +synchronized. The quality of SES devices vary and to be fair, the +descriptions from T10 drafts and standards have been tweaked several +times (see the EIIOE field) in order to clear up confusion. +.SH INDEXES +An enclosure can have information about its disk and tape drives plus other +supporting components like power supplies spread across several dpages. +Addressing a specific element (overall or individual) within a dpage is +complicated. This section describes low level indexing (i.e. choosing a +single element (or a group of related elements) from a large number of +elements). If available, the medium level indexing described in the +following section (DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS) +might be simpler to use. +.PP +The Configuration dpage is key to low level indexing: it contains a list +of "type headers", each of which contains an Element type (e.g. Array +Device Slot), a Subenclosure identifier (0 for the primary enclosure) and +a "Number of possible elements". Corresponding to each type header, the +Enclosure Status dpage has one "overall" element plus "Number of possible +elements" individual elements all of which have the given Element type. For +some Element types the "Number of possible elements" will be 0 so the +Enclosure Status dpage has only one "overall" element corresponding to that +type header. The Element Descriptor dpage and the Threshold (In and Out) +dpages follow the same pattern as the Enclosure Status dpage. +.PP +The numeric index corresponding to the overall element is "\-1". If the +Configuration dpage indicates a particular element type has "n" elements +and n is greater than 0 then its indexes range from 0 to n\-1 . +.PP +The Additional Element Status dpage is a bit more complicated. It has +entries for "Number of possible elements" of certain Element types. It +does not have entries corresponding to the "overall" elements. To make +the correspondence a little clearer each descriptor in this dpage optionally +contains an "Element Index Present" (EIP) indicator. If EIP is set then each +element's "Element Index" field refers to the position of the corresponding +element in the Enclosure Status dpage. +.PP +Addressing a single overall element or a single individual element is done +with two indexes: TI and II. Both are origin 0. TI=0 corresponds to the +first type header entry which must be a Device Slot or Array Device Slot +Element type (according to the SES\-2 standard). To address the corresponding +overall instance, II is set to \-1, otherwise II can be set to the individual +instance index. As an alternative to the type header index (TI), an Element +type abbreviation (A) optionally followed by a number (e.g. "ps" refers to +the first Power Supply Element type; "ps1" refers to the second) can be +given. +.PP +One of two command lines variants can be used to specify indexes: +\fI\-\-index=TIA,II\fR where \fITIA\fR is either an type header index (TI) +or an Element type abbreviation (A) (e.g. "ps" or "ps1"). \fIII\fR is either +an individual index or "\-1" to specify the overall element. The second +variant is \fI\-\-index=IIA\fR where \fIIIA\fR is either an individual +index (II) or an Element type abbreviation (A). When \fIIIA\fR is an +individual index then the option is equivalent to \fI\-\-index=0,II\fR. When +\fIIIA\fR is an Element type abbreviation then the option is equivalent to +\fI\-\-index=A,\-1\fR. +.PP +Wherever an individual index is applicable, it can be replaced by an +individual index range. It has the form: \-. For +example: '3\-5' will select individual indexes 3, 4 and 5 . +.PP +To cope with vendor specific Element types (which should be in the range 128 +to 255) the Element type can be given as a number with a leading underscore. +For example these are equivalent: \fI\-\-index=arr\fR and +\fI\-\-index=_23\fR since the Array Device Slot Element type value is 23. +Also \fI\-\-index=ps1\fR and \fI\-\-index=_2_1\fR are equivalent. +.PP +Another example: if the first type header in the Configuration dpage has +has Array Device Slot Element type then \fI\-\-index=0,\-1\fR is +equivalent to \fI\-\-index=arr\fR. Also \fI\-\-index=arr,3\fR is equivalent +to \fI\-\-index=3\fR. +.PP +The \fI\-\-index=\fR options can be used to reduce the amount of +output (e.g. only showing the element associated with the second 12 volt +power supply). They may also be used together with with the +\fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options which +are described in the STR section below. +.SH DESCRIPTOR NAME, DEVICE SLOT NUMBER AND SAS ADDRESS +The three options: \fI\-\-descriptor=DES\fR, \fI\-\-dev\-slot\-num=SN\fR +and \fI\-\-sas\-addr=SA\fR allow medium level indexing, as an alternative +to the low level \fI\-\-index=\fR options. Only one of the three options +can be used in an invocation. Each of the three options implicitly set the +\fI\-\-join\fR option since they need either the Element Descriptor dpage +or the Additional Element Status dpage as well as the dpages needed by the +\fI\-\-index=\fR option. +.PP +These medium level indexing options need support from the SES device and +that support is optional. For example the \fI\-\-descriptor=DES\fR needs +the Element Descriptor dpage provided by the SES device however that is +optional. Also the provided descriptor names need to be useful, and having +descriptor names which are all "0" is not very useful. Also some +elements (e.g. overall elements) may not have descriptor names. +.PP +These medium level indexing options can be used to reduce the amount of +output (e.g. only showing the elements related to device slot number 3). +They may also be used together with with the \fI\-\-clear=STR\fR, +\fI\-\-get=STR\fR and \fI\-\-set=STR\fR options which are described in the +following section. Note that even if a field can be set (e.g. "do not +remove" (dnr)) and that field can be read back with \fI\-\-get=STR\fR +confirming that change, the disk array may still ignore it (e.g. because it +does not have the mechanism to lock the disk drawer). +.SH STR FORMAT +The \fISTR\fR operands of the \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and +\fI\-\-set=STR\fR options all have the same structure. There are two forms: +.br + [=] +.br + :[:][=] +.PP +The is one of a list of common fields (e.g. "ident" and "fault") +that the utility converts internally into the second form. The +is usually in the range 0 to 3, the must be in the range 0 to +7 and the must be in the range 1 to 64 (default 1). The +number of bits are read in the left to right sense of the element tables +shown in the various SES draft documents. For example the 8 bits of +byte 2 would be represented as 2:7:8 with the most significant bit being +2:7 and the least significant bit being 2:0 . +.PP +The is optional but is ignored if provided to \fI\-\-get=STR\fR. +For \fI\-\-set=STR\fR the default is 1 while for \fI\-\-clear=STR\fR +the default value is 0 . is assumed to be decimal, hexadecimal +values can be given in the normal fashion. +.PP +The supported list of s can be viewed by using the +\fI\-\-enumerate\fR option twice (or "\-ee"). +.SH CLEAR, GET, SET +The \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options can +be used up to 8 times in the same invocation. Any s used in the +\fISTR\fR operands must refer to the same dpage. +.PP +When multiple of these options are used (maximum: 8), they are applied in the +order in which they appear on the command line. So if options contradict each +other, the last one appearing on the command line will be enforced. When +there are multiple \fI\-\-clear=STR\fR and \fI\-\-set=STR\fR options, then +the dpage they refer to is only written after the last one. +.SH DATA SUPPLIED +This section describes the two scenarios that can occur when the +\fI\-\-data=\fR option is given. These scenarios are the same irrespective +of whether the argument to the \fI\-\-data=\fR option is a string of +hex bytes on the command line, stdin (indicated by \fI\-\-data=\-\fR) or +names a file (e.g. \fI\-\-data=@thresh_in_dpage.hex\fR). +.PP +The first scenario is flagged by the \fI\-\-control\fR option. It uses the +supplied data to build a 'control' dpage that will be sent to the +\fIDEVICE\fR using the SCSI SCSI SEND DIAGNOSTIC command. The supplied dpage +data should not include its first 4 bytes. Those 4 bytes are added by this +utility using the \fI\-\-page=PG\fR option with \fIPG\fR placed at byte +offset 0). If needed, the \fI\-\-byte1=B1\fR option sets byte offset 1, +else 0 is placed in that position. The number of bytes decoded from the data +provided (i.e. its length) goes into byte offsets 2 and 3. +.PP +The second scenario is flagged by the \fI\-\-status\fR option. It decodes +the supplied data assuming that it represents the response to one or more +SCSI RECEIVE DIAGNOSTIC RESULTS commands. Those responses have typically +been captured from some earlier invocation(s) of this utility. Those earlier +invocations could use the '\-HHH' or '\-HHHH' option and file redirection to +capture that response (or responses) in hexadecimal. The supplied dpage +response data is decoded according to the other command line options. For +example the \fI\-\-join\fR option could be given and that would require the +data from multiple dpages typically: Configuration, Enclosure status, +Element descriptor and Additional element status dpages. If in doubt use +\fI\-\-page=all\fR in the capture phase; having more dpages than needed +is not a problem. +.PP +By default the user supplied data is assumed to be ASCII hexadecimal in +lines that don't exceed 512 characters. Anything on a line from and +including a hash mark ('#') to the end of line is ignored. An end of +line can be a LF or CR,LF and blank lines are ignored. Each separated +pair (or single) hexadecimal digits represent a byte (and neither a +leading '0x' nor a trailing 'h' should be given). Separators are either +space, tab, comma or end of line. +.PP +Alternatively binary can be used and this is flagged by the '\-rr' option. +The \fI\-\-data=H,H...\fR form cannot use binary values for the 'H's, only +ASCII hexadecimal. The other two forms (\fI\-\-data=\-\fR and +\fI\-\-data=@FN\fR) may contain binary data. Note that when the '\-rr' +option is used with \fI\-\-data=@FN\fR that it only changes the +interpretation of the input data, it does not change the decoding and output +representation. +.SH CHANGING STATE +This utility has various techniques for changing the state of a SES device. +As noted above this is typically a read\-modify\-write type operation. +Most modifiable dpages have a "status" (or "in") page that can be read, and +a corresponding "control" (or "out") dpage that can be written back to change +the state of the enclosure. +.PP +The lower level technique provided by this utility involves outputting +a "status" dpage in hex with \fI\-\-raw\fR. Then a text editor can be used +to edit the hex (note: to change an Enclosure Control descriptor the SELECT +bit needs to be set). Next the control dpage data can fed back with the +\fI\-\-data=H,H...\fR option together with the \fI\-\-control\fR option; +the \fI\-\-byte1=B1\fR option may need to be given as well. +.PP +Changes to the Enclosure Control dpage (and the Threshold Out dpage) can be +done at a higher level. This involves choosing a dpage (the default in this +case is the Enclosure Control dpage). Next choose an individual or overall +element index (or name it with its Element Descriptor string). Then give +the element's name (e.g. "ident" for RQST IDENT) or its position within that +element (e.g. in an Array Device Slot Control element RQST IDENT is byte 2, +bit 1 and 1 bit long ("2:1:1")). Finally a value can be given, if not the +value for \fI\-\-set=STR\fR defaults to 1 and for \fI\-\-clear=STR\fR +defaults to 0. +.SH SETTING SUBENCLOSURE NICKNAME +The format of the Subenclosure Nickname control dpage is different from its +corresponding status dpage. The status dpage reports all Subenclosure +Nicknames (and Subenclosure identifier 0 is the main enclosure) while the +control dpage allows only one of them to be changed. Therefore using the +\fB\-\-data\fR option technique to change a Subenclosure nickname is +difficult (but still possible). +.PP +To simplify changing a Subenclosure nickname the \fI\-\-nickname=SEN\fR and +\fI\-\-nickid=SEID\fR options have been added. If the \fISEN\fR string +contains spaces or other punctuation, it should be quoted: surrounded by +single or double quotes (or the offending characters escaped). If the +\fI\-\-nickid=SEID\fR is not given then a Subenclosure identifier of 0 is +assumed. As a guard the \fI\-\-control\fR option must also be given. If +the \fI\-\-page=PG\fR option is not given then \fI\-\-page=snic\fR is +assumed. +.PP +When \fI\-\-nickname=SEN\fR is given then the Subenclosure Nickname Status +dpage is read to obtain the Generation Code field. That Generation Code +together with no more than 32 bytes from the Nickname (\fISEN\fR) and the +Subenclosure Identifier (\fISEID\fR) are written to the Subenclosure Nickname +Control dpage. +.PP +There is an example of changing a nickname in the EXAMPLES section below. +.SH NVME ENCLOSURES +Support has been added to sg_ses (actually, its underlying library) for +NVMe (also known as NVM Express) Enclosures. It can be considered +experimental in sg3_utils package version 1.43 and sg_ses version 2.34 . +.PP +This support is based on a decision by NVME\-MI (Management Interface) +developers to support the SES\-3 standard. This was facilitated by adding +NVME\-MI SES Send and SES Receive commands that tunnel dpage contents as +used by SES. +.SH NOTES +This utility can be used to fetch arbitrary (i.e. non SES) dpages (using +the SCSI READ DIAGNOSTIC command). To this end the \fI\-\-page=PG\fR and +\fI\-\-hex\fR options would be appropriate. Non\-SES dpages can be sent to +a device with the sg_senddiag utility. +.PP +The most troublesome part of the join operation is associating Additional +Element Status descriptors correctly. At least one SES device vendor has +misinterpreted the SES\-2 standard, specifically with its "element index" +field interpretation. The code in this utility interprets the "element +index" field as per the SES\-2 standard and if that yields an inappropriate +Element type, adjusts its indexing to follow that vendor's +misinterpretation. The SES\-3 drafts have introduced the EIIOE (Element +Index Includes Overall Elements) bit which later became a 2 bit field to +resolve this ambiguity. See the \fI\-\-eiioe=A_F\fR option. +.PP +In draft SES\-3 revision 5 the "Door Lock" element name was changed to +the "Door" (and an OPEN field was added to the status element). As a +consequence the former 'dl' element type abbreviation has been changed +to 'do'. +.PP +There is a related command set called SAF\-TE (SCSI attached fault\-tolerant +enclosure) for enclosure (including RAID) status and control. SCSI devices +that support SAF\-TE report "Processor" peripheral device type (0x3) in their +INQUIRY response. See the sg_safte utility in this package or the +safte\-monitor utility on the Internet. +.PP +The internal join array is statically allocated and its size is controlled +by the MX_JOIN_ROWS define. Its current value is 520. +.SH EXAMPLES +Examples can also be found at http://sg.danny.cz/sg/sg_ses.html +.PP +The following examples use Linux device names. For suitable device names +in other supported Operating Systems see the sg3_utils(8) man page. +.PP +To view the supported dpages: +.PP + sg_ses /dev/bsg/6:0:2:0 +.PP +To view the Configuration Diagnostic dpage: +.PP + sg_ses \-\-page=cf /dev/bsg/6:0:2:0 +.PP +To view the Enclosure Status dpage: +.PP + sg_ses \-\-page=es /dev/bsg/6:0:2:0 +.PP +To get the (attached) SAS address of that device (which is held in the +Additional Element Sense dpage (dpage 10)) printed on hex: +.PP + sg_ses \-p aes \-D ArrayDevice07 \-G at_sas_addr \-H /dev/sg3 +.PP +To collate the information in the Enclosure Status, Element Descriptor +and Additional Element Status dpages the \fI\-\-join\fR option can be used: +.PP + sg_ses \-\-join /dev/sg3 +.PP +This will produce a lot of output. To filter out lines that don't contain +much information add the \fI\-\-filter\fR option: +.PP + sg_ses \-\-join \-\-filter /dev/sg3 +.PP +Fields in the various elements of the Enclosure Control and Threshold dpages +can be changed with the \fI\-\-clear=STR\fR and \fI\-\-set=STR\fR +options. [All modifiable dpages can be changed with the \fI\-\-raw\fR and +\fI\-\-data=H,H...\fR options.] The following example looks at making +the "ident" LED (also called "locate") flash on "ArrayDevice07" which is a +disk (or more precisely the carrier drawer the disk is in): +.PP + sg_ses \-\-index=7 \-\-set=2:1:1 /dev/sg3 +.PP +If the Element Descriptor diagnostic dpage shows that "ArrayDevice07" is +the descriptor name associated with element index 7 then this invocation +is equivalent to the previous one: +.PP + sg_ses \-\-descriptor=ArrayDevice07 \-\-set=2:1:1 /dev/sg3 +.PP +Further the byte 2, bit 1 (for 1 bit) field in the Array Device Slot Control +element is RQST IDENT for asking a disk carrier to flash a LED so it can +be located. In this case "ident" (or "locate") is accepted as an acronym +for that field: +.PP + sg_ses \-\-descriptor=ArrayDevice07 \-\-set=ident /dev/sg3 +.PP +To stop that LED flashing: +.PP + sg_ses \-\-dev\-slot\-num=7 \-\-clear=ident /dev/sg3 +.PP +The above assumes the descriptor name 'ArrayDevice07' corresponds to device +slot number 7. +.PP +Now for an example of a more general but lower level technique for changing +a modifiable diagnostic dpage. The String (In and Out) diagnostics dpage is +relatively simple (compared with the Enclosure Status/Control dpage). However +the use of this lower level technique is awkward involving three steps: read, +modify then write. First check the current String (In) dpage contents: +.PP + sg_ses \-\-page=str /dev/bsg/6:0:2:0 +.PP +Now the "read" step. The following command will send the contents of the +String dpage (from byte 4 onwards) to stdout. The output will be in ASCII +hex with pairs of hex digits representing a byte, 16 pairs per line, +space separated. The redirection puts stdout in a file called "t": +.PP + sg_ses \-\-page=str \-\-raw /dev/bsg/6:0:2:0 > t +.PP +Then with the aid of the SES\-3 document (in revision 3: section 6.1.6) +use your favourite editor to change t. The changes can be sent to the +device with: +.PP + sg_ses \-\-page=str \-\-control \-\-data=\- /dev/bsg/6:0:2:0 < t +.PP +If the above is successful, the String dpage should have been changed. To +check try: +.PP + sg_ses \-\-page=str /dev/bsg/6:0:2:0 +.PP +To change the nickname on the main enclosure: +.PP + sg_ses \-\-nickname='1st enclosure' \-\-control /dev/bsg/6:0:2:0 +.PP +To capture the whole state of an enclosure (from a SES perspective) for +later analysis, this can be done: +.PP + sg_ses \-\-page=all \-HHHH /dev/sg5 > enc_sg5_all.hex +.PP +Note that if there are errors or warnings they will be sent to stderr so +they will appear on the command line (since only stdout is redirected). +A text editor could be used to inspect enc_sg5_all.hex . If all looks in +order at some later time, potentially on a different machine where +enc_sg5_all.hex has been copied, a "join" could be done. Note that join +reflects the state of the enclosure when the capture was done. +.PP + sg_ses \-\-data=@enc_sg5_all.hex \-\-status \-\-join +.SH EXIT STATUS +The exit status of sg_ses is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq, sg_safte, sg_senddiag, sg_ses_microcode, sg3_utils (sg3_utils); +.B safte\-monitor (Internet) diff --git a/doc/sg_ses_microcode.8 b/doc/sg_ses_microcode.8 new file mode 100644 index 0000000..43e73ac --- /dev/null +++ b/doc/sg_ses_microcode.8 @@ -0,0 +1,279 @@ +.TH SG_SES_MICROCODE "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_ses_microcode \- send microcode to a SCSI enclosure +.SH SYNOPSIS +.B sg_ses_microcode +[\fI\-\-bpw=CS\fR] [\fI\-\-dry\-run\fR] [\fI\-\-ealsd\fR] [\fI\-\-help\fR] +[\fI\-\-id=ID\fR] [\fI\-\-in=FILE\fR] [\fI\-\-length=LEN\fR] +[\fI\-\-mode=MO\fR] [\fI\-\-non\fR] [\fI\-\-offset=OFF\fR] +[\fI\-\-skip=SKIP\fR] [\fI\-\-subenc=MS\fR] [\fI\-\-tlength=TLEN\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility attempts to download microcode to an enclosure (or one of its +sub\-enclosures) associated with the \fIDEVICE\fR. The process for doing +this is defined in the SCSI Enclosure Services (SES) standards and drafts +maintained by the T10 committee. +.PP +The process is to send one or more sequences containing a SCSI SEND +DIAGNOSTIC command followed optionally by a RECEIVE DIAGNOSTIC RESULTS +command. The former sends a Download microcode Control diagnostic +page (dpage) and the latter fetches a Download microcode status dpage which +can be viewed as a report on the former command. +.PP +The default action (i.e. when the \fI\-\-mode=MO\fR option is not given) +is to fetch the Download microcode status dpage and decode it. This does +not require the microcode (firmware) itself so the \fI\-\-in=FILE\fR option +is not required. +.PP +The most recent reference for this utility is the draft SCSI Enclosure +Services 3 (SES\-3) document T10/2149\-D Revision 7 at http://www.t10.org . +Existing standards for SES and SES\-2 are ANSI INCITS 305\-1998 and ANSI +INCITS 448\-2008 respectively. +.PP +Most other support for SES in this package (apart from downloading +microcode) can be found in the sg_ses utility. Another way of downloading +firmware to a SCSI device is with the WRITE BUFFER command defined in +SPC\-4, see the sg_write_buffer utility. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-bpw\fR=\fICS\fR +where \fICS\fR is the chunk size in bytes and should be a multiple of 4. +This will be the maximum number of bytes sent per SEND DIAGNOSTIC command. +So if \fICS\fR is less than the effective length of the microcode then +multiple SEND DIAGNOSTIC commands are sent, each taking the next chunk +from the read data and increasing the buffer offset field in the Download +microcode control dpage by the appropriate amount. The default is +a chunk size of 0 which is interpreted as a very large number hence only +one SEND DIAGNOSTIC command will be sent. +.br +The number in \fICS\fR can optionally be followed by ",act" or ",activate". +In this case after the microcode has been successfully sent to the +\fIDEVICE\fR, an additional Download microcode control dpage with its mode +set to "Activate deferred microcode" [0xf] is sent. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +the actual calls to perform SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS +commands are skipped when this option is given. No SCSI commands are sent +to the \fIDEVICE\fR but it is still opened and is required to be given. +A dummy device such as /dev/null (in Unix) can be used. +.br +This utility expects a "sensible" response to the RECEIVE DIAGNOSTIC RESULTS +command it sends (and will abort if it doesn't receive one). So this option +supplies dummy responses with one primary enclosure and three +sub\-enclosures. The dummy responses include good status values. +.TP +\fB\-e\fR, \fB\-\-ealsd\fR +exit after last SEND DIAGNOSTIC command. A SES device should not start its +firmware update immediately after the last received "chunk" of its firmware. +Rather it should wait till at least one RECEIVE DIAGNOSTIC RESULTS command +is sent to give the device a chance to report any error. However some +devices do start the firmware update immediately which causes the trailing +RECEIVE DIAGNOSTIC RESULTS command to be held up and often be aborted with +a "target reset" error. +.br +This option causes the trailing RECEIVE DIAGNOSTIC RESULTS command to be +skipped. This option would be typically used with the \fI\-\-bpw=CS\fR +option. +.br +Prior to version 1.10 of this utility [20180112] this (i.e. skipping +the last RECEIVE DIAGNOSTIC RESULTS command) was the default action. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. If used multiple times also prints +the mode names and their acronyms. +.TP +\fB\-i\fR, \fB\-\-id\fR=\fIID\fR +this option sets the BUFFER ID field in the Download microcode control +dpage. \fIID\fR is a value between 0 (default) and 255 inclusive. +.TP +\fB\-I\fR, \fB\-\-in\fR=\fIFILE\fR +read data from file \fIFILE\fR that will be sent with the SEND DIAGNOSTIC +command. If \fIFILE\fR is '\-' then stdin is read until an EOF is +detected (this is the same action as \fI\-\-raw\fR). Data is read from +the beginning of \fIFILE\fR except in the case when it is a regular file +and the \fI\-\-skip=SKIP\fR option is given. +.TP +\fB\-l\fR, \fB\-\-length\fR=\fILEN\fR +where \fILEN\fR is the length, in bytes, of data to be written to the device. +If not given (and the length cannot be deduced from \fI\-\-in=FILE\fR or +\fI\-\-raw\fR) then defaults to zero. If the option is given and the length +deduced from \fI\-\-in=FILE\fR or \fI\-\-raw\fR is less (or no data is +provided), then bytes of 0xff are used as fill bytes. +.TP +\fB\-m\fR, \fB\-\-mode\fR=\fIMO\fR +this option sets the MODE. \fIMO\fR is a value between +0 (which is dmc_status and the default) and 255 inclusive. Alternatively +an abbreviation can be given. See the MODES section below. To list the +available mode abbreviations at run time give an invalid +one (e.g. '\-\-mode=xxx') or use the '\-h' option. +.TP +\fB\-N\fR, \fB\-\-non\fR +allow for non\-standard implementations that reset their Download microcode +engine after a RECEIVE DIAGNOSTIC RESULTS command with the Download microcode +status dpage is sent. When this option is given sending that command and +dpage combination is avoided unless an error has already occurred. +.TP +\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR +this option sets the BUFFER OFFSET field in the Download microcode control +dpage. \fIOFF\fR is a value between 0 (default) and 2**32\-1 . It is a +byte offset. This option is ignored (and a warning sent to stderr) if the +\fI\-\-bpw=CS\fR option is also given. +.TP +\fB\-s\fR, \fB\-\-skip\fR=\fISKIP\fR +this option is only active when \fI\-\-in=FILE\fR is given and \fIFILE\fR is +a regular file, rather than stdin. Data is read starting at byte offset +\fISKIP\fR to the end of file (or the amount given by \fI\-\-length=LEN\fR). +If not given the byte offset defaults to 0 (i.e. the start of the file). +.TP +\fB\-S\fR, \fB\-\-subenc\fR=\fISEID\fR +\fISEID\fR is the sub\-enclosure identify. It defaults to 0 which is the +primary enclosure identifier. +.TP +\fB\-t\fR, \fB\-\-tlength\fR=\fITLEN\fR +\fITLEN\fR is the total length in bytes of the microcode to be (or being) +downloaded. It defaults to 0 which is okay in most cases. This option only +comes into play when \fITLEN\fR is greater than \fILEN\fR. In this case +\fITLEN\fR is sent to the SES \fIDEVICE\fR so that it knows when it only +receives \fILEN\fR bytes from this invocation, that it should expect more +to be sent in the near future (e.g. by another invocation). This option +is only needed when sections of microcode are being sent in separate +invocations of this utility (e.g. the microcode is spread across two files). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH MODES +Following is a list accepted by the \fIMO\fR argument of this utility. +First shown is an acronym followed in square brackets by the corresponding +decimal and hex values that may also be given for \fIMO\fR. +.TP +dmc_status [0, 0x0] +Use RECEIVE DIAGNOSTIC RESULTS to fetch the Download microcode status dpage +and print it out. +.TP +dmc_offs [6, 0x6] +Download microcode with offsets and activate. +.TP +dmc_offs_save [7, 0x7] +Download microcode with offsets, save, and activate. +.TP +dmc_offs_defer [14, 0xe] +Download microcode with offsets, save, and defer activate. +.TP +activate_mc [15, 0xf] +Activate deferred microcode. There is no follow\-up RECEIVE DIAGNOSTIC +RESULTS to fetch the Download microcode status dpage since the \fIDEVICE\fR +might be resetting. +.PP +Apart from dmc_status, these are placed in the Download microcode mode +field in the Download microcode control dpage. In the case of dmc_status +the Download microcode status dpage is fetched with the RECEIVE DIAGNOSTIC +RESULTS command and decoded. +.SH WHEN THE DOWNLOAD FAILS +Firstly, if it succeeds, this utility should stay silent and return. +Typically vendors will change the "revision" string (which is 4 characters +long) whenever they release new firmware. That can be seen in the response +to a SCSI INQUIRY command, for example by using the sg_inq utility. +It is possible that the device needs to be power cycled before the new +microcode becomes active. Also if mode dmc_offs_defer [0xe] is used to +download the microcode, then another invocation with activate_mc may +be needed. +.PP +If something goes wrong, there will typically be messages printed out +by this utility. The first thing to check is the microcode (firmware) +file itself. Is it designed for the device model; has it been corrupted, +and if downgrading (i.e. trying to reinstate older firmware), does +the vendor allow that? +.PP +Getting new firmware on a device is a delicate operation that is not +always well defined by T10's standards and drafts. One might speculate +that they are deliberately vague. In testing this utility one vendor's +interpretation of the standard was somewhat surprising. The \fI\-\-non\fR +option was added to cope with their interpretation. So if the above +suggestions don't help, try adding the \fI\-\-non\fR option. +.SH NOTES +This utility can handle a maximum size of 128 MB of microcode which +should be sufficient for most purposes. In a system that is memory +constrained, such large allocations of memory may fail. +.PP +The user should be aware that most operating systems have limits on the +amount of data that can be sent with one SCSI command. In Linux this +depends on the pass through mechanism used (e.g. block SG_IO or the sg +driver) and various setting in sysfs in the Linux lk 2.6/3 +series (e.g. /sys/block/sda/queue/max_sectors_kb). Devices (i.e. logical +units) also typically have limits on the maximum amount of data they can +handle in one command. These two limitations suggest that modes +containing the word "offset" together with the \fI\-\-bpw=CS\fR option +are required as firmware files get larger and larger. And \fICS\fR +can be quite small, for example 4096 bytes, resulting in many SEND +DIAGNOSTIC commands being sent. +.PP +The exact error from the non\-standard implementation was a sense key of +ILLEGAL REQUEST and an asc/ascq code of 0x26,0x0 which is "Invalid field in +parameter list". If that is seen try again with the \fI\-\-non\fR option. +.PP +Downloading incorrect microcode into a device has the ability to render +that device inoperable. One would hope that the device vendor verifies +the data before activating it. +.PP +A long (operating system) timeout of 7200 seconds is set on each SEND +DIAGNOSTIC command. +.PP +All numbers given with options are assumed to be decimal. +Alternatively numerical values can be given in hexadecimal preceded by +either "0x" or "0X" (or has a trailing "h" or "H"). +.SH EXAMPLES +If no microcode/firmware file is given then this utility fetches and decodes +the Download microcode status dpage which could possibly show another +initiator in the process of updating the microcode. Even if that is +happening, fetching the status page should not cause any problems: +.PP + sg_ses_microcode /dev/sg3 +.br +Download microcode status diagnostic page: +.br + number of secondary sub\-enclosures: 0 +.br + generation code: 0x0 +.br + sub\-enclosure identifier: 0 [primary] +.br + download microcode status: No download microcode operation in progress [0x0] +.br + download microcode additional status: 0x0 +.br + download microcode maximum size: 1048576 bytes +.br + download microcode expected buffer id: 0x0 +.br + download microcode expected buffer id offset: 0 +.PP +The following sends new microcode/firmware to an enclosure. Sending a 1.5 MB +file in one command caused the enclosure to lock up temporarily and did +not update the firmware. Breaking the firmware file into 4 KB chunks (an +educated guess) was more successful: +.PP + sg_ses_microcode \-b 4k \-m dmc_offs_save \-I firmware.bin /dev/sg4 +.PP +The firmware update occurred in the following enclosure power cycle. With +a modern enclosure the Extended Inquiry VPD page gives indications in which +situations a firmware upgrade will take place. +.SH EXIT STATUS +The exit status of sg_ses_microcode is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2014\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_ses, sg_write_buffer, sg_inq(sg3_utils) diff --git a/doc/sg_start.8 b/doc/sg_start.8 new file mode 100644 index 0000000..d22a017 --- /dev/null +++ b/doc/sg_start.8 @@ -0,0 +1,273 @@ +.TH SG_START "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_start \- send SCSI START STOP UNIT command: start, stop, load or eject +medium +.SH SYNOPSIS +.B sg_start +[\fI0\fR] [\fI1\fR] [\fI\-\-eject\fR] [\fI\-\-help\fR] [\fI\-\-fl=FL\fR] +[\fI\-\-immed\fR] [\fI\-\-load\fR] [\fI\-\-loej\fR] [\fI\-\-mod=PC_MOD\fR] +[\fI\-\-noflush\fR] [\fI\-\-pc=PC\fR] [\fI\-\-readonly\fR] [\fI\-\-start\fR] +[\fI\-\-stop\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.PP +.B sg_start +[\fI\-\-eject\fR] [\fI\-\-fl=FL\fR] [\fI\-i\fR] [\fI\-\-imm=0|1\fR] +[\fI\-\-load\fR] [\fI\-\-loej\fR] [\fI\-\-mod=PC_MOD\fR] [\fI\-\-noflush\fR] +[\fI\-\-pc=PC\fR] [\fI\-r\fR] [\fI\-\-start\fR] [\fI\-\-stop\fR] [\fI\-v\fR] +[\fI\-V\fR] [\fI0|1\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +sg_start sends a SCSI START STOP UNIT command to the \fIDEVICE\fR with +the selected options. The most used options are \fI\-\-stop\fR to spin +down a disk and \fI\-\-start\fR to spin up a disk. Using \fI\-\-start\fR +on a disk that is already spinning is harmless. There is also finer grain +control with "power condition": active, idle or standby. This is set +with the \fI\-\-pc=PC\fR option. In some contexts the "stop" state can +be considered an additional power condition. +.PP +Devices that contain removable media such as cd/dvds can use the +\fI\-\-loej\fR option to load the medium when used in conjunction +with \fI\-\-start\fR (i.e. load medium then spin up). Alternatively +\fI\-\-loej\fR may be used to eject the medium when used in conjunction +with \fI\-\-stop\fR (i.e. spin down then eject medium). More simply, the +loading or ejecting of a removable medium can be requested with the +\fI\-\-load\fR or \fI\-\-eject\fR' option. +.PP +If no option or argument is given then a \fI\-\-start\fR is assumed; as the +utility's name suggests. +.PP +This utility supports two command line syntaxes, the preferred one is +shown first in the synopsis and explained in this section. A later +section on the old command line syntax outlines the second group of +options. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB0\fR +same action as \fI\-\-stop\fR. +.TP +\fB1\fR +same action as \fI\-\-start\fR. +.TP +\fB\-e\fR, \fB\-\-eject\fR +stop the medium and eject it from the drive. Only appropriate for a +device with removable medium. Might be ignored (prevented), see below. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-f\fR, \fB\-\-fl\fR=\fIFL\fR +sets the format layer number for the disc to "jump" to (defined in MMC\-5). +Values of \fIFL\fR can be 0 to 3. When this option is chosen, the FL, LoEj +and Start bits are set in the cdb as required by MMC\-5; thus the user does +not need to set the \fI\-\-start\fR and/or \fI\-\-load\fR options. +.TP +\fB\-i\fR, \fB\-\-immed\fR +sets the IMM bit on the START STOP UNIT command so this utility will +return immediately and not wait for the media to complete the requested +action. The default is to wait until the media to complete the requested +action before returning. +.TP +\fB\-l\fR, \fB\-\-load\fR +load the medium in the drive and start it. Only appropriate for a removable +medium. +.TP +\fB\-L\fR, \fB\-\-loej\fR +sets the LOEJ bit on the START STOP UNIT command. This loads the media when +the unit is started or eject it when the unit is stopped (i.e. works in +conjunction with START bit in cdb). This option is ignored if 'pc > 0'. +Default is off (i.e. don't attempt to load or eject media). If a start/start +indication is not given (i.e. neither \fI\-\-start\fR nor \fI\-\-stop\fR) +and this option is given then a load and start action is assumed. +.TP +\fB\-m\fR, \fB\-\-mod\fR=\fIPC_MOD\fR +where \fIPC_MOD\fR is the 'power condition modifier' value. 0 to 15 (inclusive) +are valid and 0 is the default. This 'power condition modifier' field in the +cdb was added after sbc3r13. +.TP +\fB\-n\fR, \fB\-\-noflush\fR +do not perform a flush to media (e.g. like SYNCHRONIZE CACHE does) before +a variant of this utility that limits access to the media. Using the +\fB\-\-stop\fR option is an example of something that limits access to the +media. This 'noflush' field in the cdb was added after sbc3r13. +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-p\fR, \fB\-\-pc\fR=\fIPC\fR +where \fIPC\fR is the 'power conditions' value. 0 to 15 (inclusive) are valid. +Default value is 0. When '\-\-pc=0' then \fB\-\-eject\fR, \fB\-\-load\fR, +\fB\-\-loej\fR, \fB\-\-start\fR and \fB\-\-stop\fR are active. Some common +values are 1 for the "active" power condition (SBC); 2 for the idle power +condition; 3 for the standby power condition; 5 for sleep power +condition (MMC); 7 for LU_CONTROL (SBC), 0xa (decimal 10) for +FORCE_IDLE_0 (SBC) and 0xb (decimal 11) for FORCE_STANDBY_0 (SBC). See recent +SBC\-3, MMC\-5 and SAS drafts at www.t10.org for more information. +.TP +\fB\-r\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR in read\-only mode. Maybe required in Linux to stop a +nuisance spin\-up if the \fIDEVICE\fR is an ATA disk. The nuisance spin\-up +may occur at the end of this command negating the effect of the +\fI\-\-stop\fR option. +.TP +\fB\-s\fR, \fB\-\-start\fR +start (spin\-up) the \fIDEVICE\fR. This sets the START bit in the cdb. Using +this option on an already started device is harmless. In the absence of +other options, this option defaults (i.e. set the START cdb bit). +.TP +\fB\-S\fR, \fB\-\-stop\fR +stop (spin\-down) the \fIDEVICE\fR. This clears the START bit in the cdb. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity. Can be used multiple times. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH NOTES +To avoid confusion, only one of \fI0\fR, \fI1\fR \fI\-\-eject\fR, +\fI\-\-load\fR, \fI\-\-start\fR and \fI\-\-stop\fR should be given. +.PP +There is an associated "power condition" mode page (0x1a) in which timer +values can be set for transitioning to either idle or standby state after +a period of inactivity. The sdparm utility can be used to view the power +condition mode page and if required change it. If a \fIDEVICE\fR is in either +idle or standby power condition state then a REQUEST SENSE command (see +the sg_requests utility) should yield a sense key of "no sense" and an +additional sense code of "Low power condition on" on recent SCSI devices. +.PP +Ejection of removable media (e.g. 'sg_start \-\-eject /dev/hdd' where +the \fIDEVICE\fR is an ATAPI cd/dvd drive) may be prevented by a prior +SCSI PREVENT ALLOW MEDIUM REMOVAL command (see sg_prevent). In this +case this utility should fail with an error generated by the device: +illegal request / medium removal prevented. This can be overridden +using sg_prevent or, for example, 'sdparm \-\-command=unlock /dev/hdd'. +.PP +The SCSI TEST UNIT READY command can be used to find out whether a +\fIDEVICE\fR is ready to transfer data. If rotating media is stopped or +still coming up to speed, then the TEST UNIT READY command will yield +a "not ready" sense key and an more informative additional sense +code. See the sg_turs utility. +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be a SCSI +generic (sg) device. In the 2.6 series block devices (e.g. SCSI disks +and DVD drives) can also be specified. For example "sg_start 0 /dev/sda" +will work in the 2.6 series kernels. +.PP +In the Linux 2.6 series, especially with ATA disks, using this utility +to stop (spin down) a disk may not be sufficient and other mechanisms +will start the disk again some time later. The user might additionally +mark the disk as "offline" with 'echo offline > /sys/block/sda/device/state' +where sda is the block name of the disk. To restart the disk "offline" +can be replaced with "running". Note that once the 'state' is set to +offline, no SCSI commands can be sent to the device until it is set back +to running. Also stopping a disk via a pass\-through +interface (e.g. /dev/sg1 or /dev/bsg/1:0:0:0) may reduce unwanted side +effects (such as restarting it again when this utility completes). +.SH EXIT STATUS +The exit status of sg_start is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.PP +Note that the action of \fI\-\-loej\fR is slightly different in the older +interface: when neither \fI\-\-start\fR nor \fI\-\-stop\fR (nor proxies +for them) are given, \fI\-\-loej\fR performs an eject operation. In the +same situation the newer interface will perform a load operation. +.PP +Earlier versions of sg_start had a '\-s' option to perform a SYNCHRONIZE +CACHE command before the START STOP UNIT command was issued. According to +recent SBC\-2 drafts this is done implicitly if required. Hence the '\-s' +option has been dropped. +.PP +All options, other than '\-v' and '\-V', can be given with a single "\-". +For example: "sg_start \-stop /dev/sda" and "sg_start \-\-stop /dev/sda" +are equivalent. The single "\-" form is for backward compatibility. +.TP +\fB0\fR +stop (spin\-down) \fIDEVICE\fR. +.TP +\fB1\fR +start (spin\-up) \fIDEVICE\fR. +.TP +\fB\-\-eject\fR +stop the medium and eject it from the drive. +.TP +\fB\-\-fl\fR=\fIFL\fR +sets the format layer number for the disc to "jump" to (defined in MMC\-5). +.TP +\fB\-i\fR +sets the IMM bit on the START STOP UNIT command so this utility will return +immediately and not wait for the media to spin down. Same effect +as '\-\-imm=1'. The default action (without this option or a '\-\-imm=1' +option) is to wait until the media spins down before returning. +.TP +\fB\-\-imm\fR=\fI0|1\fR +when the immediate bit is 1 then this utility returns immediately after the +\fIDEVICE\fR has received the command. When this option is 0 (the default) +then the utility returns once the command has completed its action (i.e. it +waits until the device is started or stopped). +.TP +\fB\-\-load\fR +load the medium in the drive and start it. +.TP +\fB\-\-loej\fR +sets the LOEJ bit in the START STOP UNIT cdb. When a "start" operation is +indicated, then a load and start is performed. When a "stop" operation is +indicated, then a stop and eject is performed. When neither a "start" +or "stop" operation is indicated does a stop and eject. [Note that the last +action differs from the new interface in which the option of this name +defaults to load and start.] +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-\-mod\fR=\fIPC_MOD\fR +where \fIPC_MOD\fR is the 'power condition modifier' value. 0 to 15 (inclusive) +are valid and 0 is the default. This field was added after sbc3r13. +.TP +\fB\-\-noflush\fR +do not perform a flush to media (e.g. like SYNCHRONIZE CACHE does) before +a variant of this utility that limits access to the media. Using the +\fB\-\-stop\fR option is an example of something that limits access to the +media. This field was added after sbc3r13. +.TP +\fB\-\-pc\fR=\fIPC\fR +where \fIPC\fR is the 'power condition' value (in hex). 0 to f (inclusive) +are valid. Default value is 0. +.TP +\fB\-r\fR +see the \fI\-\-readonly\fR option above. May be useful for ATA disks. +.TP +\fB\-\-start\fR +start (spin\-up) \fIDEVICE\fR. +.TP +\fB\-\-stop\fR +stop (spin\-down) \fIDEVICE\fR. Same meaning as "0" argument. +.TP +\fB\-v\fR +verbose: outputs SCSI command in hex to console before with executing +it. '\-vv' and '\-vvv' are also accepted yielding greater verbosity. +.TP +\fB\-V\fR +print out version string then exit. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHOR +Written by K. Garloff and D. Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2002\-2017 Kurt Garloff, Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_prevent(sg3_utils), sg_requests(sg3_utils), sg_turs(sg3_utils) +.B sdparm(sdparm) diff --git a/doc/sg_stpg.8 b/doc/sg_stpg.8 new file mode 100644 index 0000000..187054a --- /dev/null +++ b/doc/sg_stpg.8 @@ -0,0 +1,122 @@ +.TH SG_STPG "8" "January 2014" "sg3_utils\-1.38" SG3_UTILS +.SH NAME +sg_stpg \- send SCSI SET TARGET PORT GROUPS command +.SH SYNOPSIS +.B sg_stpg +[\fI\-\-active\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] [\fI\-\-offline\fR] +[\fI\-\-optimized\fR] [\fI\-\-raw\fR] [\fI\-\-standby\fR] +[\fI\-\-state=S,S...\fR] [\fI\-\-tp=P,P...\fR] [\fI\-\-unavailable\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI SET TARGET PORT GROUPS command to \fIDEVICE\fR. This utility +has different modes depending on whether the \fI\-\-tp=\fR option is given. +.PP +If \fI\-\-tp=\fR is given then the SET TARGET PORT GROUPS command parameter +block is built with a descriptor for each element in the list given to +\fI\-\-tp=\fR. The corresponding asymmetric access state value is either +taken from the \fI\-\-state=\fR list or, if that is not given, from one +of the explicit state options (e.g. \fI\-\-unavailable\fR), used repeatedly +if required. +.PP +If \fI\-\-tp=\fR is not given then a sequence of SCSI commands are sent to +the \fIDEVICE\fR leading up to the SET TARGET PORT GROUPS command. First an +INQUIRY is sent to fetch the device identification VPD page to find +the (primary) target port group associated with \fIDEVICE\fR. Then a REPORT +TARGET PORT GROUPS command is issued to find the current state and +whether a transition to the requested state is supported. If so the +SET TARGET PORT GROUPS command is sent. +.PP +Target port group access is described in SPC\-4 found at www.t10.org +in sections 5.8 and 5.16 (in rev 36e dated 2012/8/24). The SET TARGET PORT +GROUPS command is also described in section 6.45 of that document. +.PP +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-a\fR, \fB\-\-active\fR +set active/non\-optimized state. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output response to the REPORT TARGET PORT GROUPS command in hex then exit. +.TP +\fB\-O\fR, \fB\-l\fR, \fB\-\-offline\fR +set offline state. This is the appropriate state to set a target port +to prior to removing the device. Note that a relative target port identifier +should be given with this state (rather than a target port group identifier +that all other states take). +.TP +\fB\-o\fR, \fB\-\-optimized\fR +set active/optimized state. If no other state options or \fI\-\-tp=\fR +option are given then active/optimized is the default state. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output response to the REPORT TARGET PORT GROUPS command in binary to stdout +then exit. +.TP +\fB\-s\fR, \fB\-\-standby\fR +set standby state. Port group shall accept those commands listed +for "unavailable" state plus LOG SELECT/SENSE, MODE SELECT/SENSE, RECEIVE +DIAGNOSTIC RESULTS, SEND DIAGNOSTIC, PERSISTENT RESERVE IN/OUT commands. +.TP +\fB\-S\fR, \fB\-\-state\fR=\fIS,S...\fR +specifies a comma separated list (one element of more) of states. Either +a number or an abbreviation can be given. A number is assumed to be a +decimal number unless it is prefixed by "0x" or has a trailing "h" in +which case a hexadecimal value is assumed. Only the values 0, 1, 2, 3 +or 14 are accepted. The accepted abbreviations are "an", "ao", "o", "s" +or "u"; which represent active/non\-optimized(1), active/optimized(0), +offline(14), standby(2) or unavailable(3) respectively. +.TP +\fB\-t\fR, \fB\-\-tp\fR=\fIP,P...\fR +specifies a comma separated list (one element of more). Each elements is +either a target port group identifier (when the corresponding state is +other than "offline") or a relative target port identifier (when the +corresponding state is "offline"). Each element is assumed to be a +decimal number unless it is prefixed by "0x" or has a trailing "h" in +which case a hexadecimal value is assumed. +.TP +\fB\-u\fR, \fB\-\-unavailable\fR +set unavailable state. Port group shall only accept INQUIRY, REPORT LUNS, +REPORT/SET TARGET PORT GROUPS, REQUEST SENSE and READ/WRITE BUFFER commands. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +The SET TARGET PORT GROUPS command should be supported whenever the TPGS +value in a standard INQUIRY response is 2 or 3. [View with sg_inq utility.] +.PP +Notice that the offline state is termed as a "secondary target port +asymmetric access state" and takes a relative target port identifier (i.e. +acts on a single target port). All the other states are termed as "primary +target port asymmetric access states" and each takes a target port group +identifier (i.e. acts on one or more target ports). +.PP +When \fI\-\-tp=\fR is given then the same number of elements should be +given to the \fI\-\-state=\fR option. If more than one list element is +given to \fI\-\-tp=\fR and an equal number of elements is _not_ given +to the \fI\-\-state=\fR option, then if only one state is specified +then it is repeated. +.SH EXIT STATUS +The exit status of sg_stpg is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2007\-2014 Hannes Reinecke, Christophe Varoqui and Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq, sg_rtpg (sg3_utils) diff --git a/doc/sg_stream_ctl.8 b/doc/sg_stream_ctl.8 new file mode 100644 index 0000000..8451cf5 --- /dev/null +++ b/doc/sg_stream_ctl.8 @@ -0,0 +1,117 @@ +.TH SG_STREAM_CTL "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_stream_ctl \- send SCSI STREAM CONTROL or GET STREAM STATUS command +.SH SYNOPSIS +.B sg_stream_ctl +[\fI\-\-brief\fR] [\fI\-\-close\fR] [\fI\-\-ctl=CTL\fR] [\fI\-\-get\fR] +[\fI\-\-help\fR] [\fI\-\-id=SID\fR] [\fI\-\-maxlen=LEN\fR] [\fI\-\-open\fR] +[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI STREAM CONTROL or GET STREAM STATUS command to the \fIDEVICE\fR. +These commands, together with WRITE STREAM(16 and 32) and several fields in +the Block Limits Extension VPD page [0xb7] support the streams concept. +The stream commands were added in SBC\-4 draft 8 (September 2015). +.PP +Both STREAM CONTROL and GET STREAM STATUS commands expect data from the +\fIDEVICE\fR (referred to as 'data\-in'). In the case of STREAM CONTROL +only the 'open' (STR_CTL<\-\-0x1) actually needs the data\-in as it contains +the "Assigned stream id" if the open was successful. The assigned stream +id should be used by subsequent WRITE STREAM commands and ultimately +by the STREAM CONTROL close (STR_CTL<\-\-0x2). Valid stream ids are between +1 and 65535 inclusive. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-b\fR, \fB\-\-brief\fR +this option reduces the output of the GET STREAM STATUS command to just +one number (in decimal) per line sent to stdout. Those numbers are the +currently open stream ids. If an error occurs then \-1 is sent to stdout +and error related messages are sent to stderr. The default is to print more +words (and fields) from the GET STREAM STATUS response. +.TP +\fB\-c\fR, \fB\-\-close\fR +selects the STREAM CONTROL command and sets STR_CTL<\-\-0x2 (i.e. 'close'). +The \fI\-\-id=SID\fR option should also be given because it defaults to 0 +which is not a valid stream id. +.TP +\fB\-C\fR, \fB\-\-ctl\fR=\fICTL\fR +\fICTL\fR is the value placed in the STR_CTL field of the STREAM CONTROL +command (cdb). It is a two bit field so has 4 variants: 0 and 3 are reserved; +1 opens are new stream and 2 closes the given stream id. '\-\-ctl=1' is +equivalent to '\-\-open' while '\-\-ctl=2' is equivalent to '\-\-close'. +.TP +\fB\-g\fR, \fB\-\-get\fR +selects the GET STREAM STATUS command. If the \fI\-\-id=SID\fR option is +also given the the response starts lists open stream ids from and including +\fISID\fR. If the \fI\-\-id=SID\fR option is not given (or \fISID\fR is 0) +then all open stream id will be returned in the response (data\-in) as long +as the allocation length (defaults to 248 bytes which can be overridden by +the \fI\-\-maxlen=LEN\fR option) is long enough. This is the default action +of this utility (i.e. GET STREAM STATUS command) if no "selecting" options +are given. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-id\fR=\fISID\fR +\fISID\fR is a stream id, a value between 1 and 65535. It is used by STREAM +CONTROL (close) to identify the stream to close. It is used by the GET +STREAM STATUS command as the starting stream id (from and including); so +stream ids that are less than \fISID\fR will not appear in the response. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +\fILEN\fR is the maximum length the response can be. It becomes the +ALLOCATION LENGTH field in both commands. The default (in the absence of +this option) is 8 bytes for STREAM CONTROL and 248 bytes for GET STREAM +STATUS. +.TP +\fB\-o\fR, \fB\-\-open\fR +selects the STREAM CONTROL command and sets STR_CTL<\-\-0x1 (i.e. 'open'). +If the \fI\-\-id=SID\fR option is given then it is ignored. The user should +observe the response as the "Assigned stream id" is printed on stdout if +the open is successful, if not '\-1' is sent to stdout and error messages are +sent to stderr. If the \fI\-\-brief\fR option is also given then the only +thing sent to stdout is a number of the assigned stream id (1 to +65535 inclusive) or '\-1' if there is an error. +.TP +\fB\-r\fR, \fB\-\-readonly\fR +this option sets a 'read\-only' flag when the underlying operating system +opens the given \fIDEVICE\fR. This may not work since operating systems can +not easily determine whether a pass\-through command is a logical read or +write operation on the media (or its metadata) so they take a risk averse +stance and require read\-write type permissions on the \fIDEVICE\fR open +irrespective of what is performed by the pass\-through. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +There are no special read commands for streams. This implies that "normal" +READs (6, 10, 12, 16 or 32) can be used. Note that when a stream is closed, +all resources associated with that stream id are removed, apart from the +data in the written LBAs. To make sure the reading back data is not delayed +too much by error recovery (in the presence of media errors) the user may +set the RECOVERY TIME LIMIT field (RTL, units for non\-zero values: +milliseconds) in the 'Read\-write error recovery' mode page. This can be done +with the sdparm utility. +.PP +The SCSI WRITE STREAM (16 and 32) commands can be found in the sg_write_x +utility in this package. +.SH EXIT STATUS +The exit status of sg_stream_ctl is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_vpd,sg_write_x(sg3_utils); sdparm(sdparm) diff --git a/doc/sg_sync.8 b/doc/sg_sync.8 new file mode 100644 index 0000000..64d3e47 --- /dev/null +++ b/doc/sg_sync.8 @@ -0,0 +1,97 @@ +.TH SG_SYNC "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_sync \- send SCSI SYNCHRONIZE CACHE command +.SH SYNOPSIS +.B sg_sync +[\fI\-\-16\fR] [\fI\-\-count=COUNT\fR] [\fI\-\-group=GN\fR] +[\fI\-\-help\fR] [\fI\-\-immed\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-sync\-nv\fR] +[\fI\-\-timeout=SECS\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send SYNCHRONIZE CACHE(10) or SYNCHRONIZE CACHE(16) command to \fIDEVICE\fR. +These commands are defined for SCSI block devices (see SBC\-3). If successful +these commands make sure that any blocks whose latest versions are held in +cache are written to (also termed as "synchronized with") the medium. +.PP +If the \fILBA\fR and \fICOUNT\fR arguments are both zero (their defaults) +then all blocks in the cache are synchronized. If \fILBA\fR is greater than +zero while \fICOUNT\fR is zero then blocks in the cache whose addresses are +from and including \fILBA\fR to the highest lba on the device are +synchronized. If both \fILBA\fR and \fICOUNT\fR are non zero then blocks in +the cache whose addresses lie in the range \fILBA\fR to +\fILBA\fR+\fICOUNT\fR\-1 inclusive are synchronized with the medium. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-S\fR, \fB\-\-16\fR +performs a SYNCHRONIZE CACHE(16) command. Default is to perform a +SYNCHRONIZE CACHE(10) command. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fICOUNT\fR +where \fICOUNT\fR is the number of blocks to synchronize from and including +\fILBA\fR. Default value is 0. When 0 then all blocks in the cache from and +including \fILBA\fR argument to the highest block address are synchronized. +.TP +\fB\-g\fR, \fB\-\-group\fR=\fIGN\fR +where \fIGN\fR is the group number which can be between 0 and 63 inclusive. +The default value is 0 . Group numbers are used to segregate data collected +within the device. This is a new feature in SBC\-2 and can probably be +ignored for the time being. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-immed\fR +sets the IMMED bit in the SYNCHRONIZE CACHE command. This instructs the +device, if the format of the command is acceptable, to return a GOOD +status immediately rather than wait for the blocks in the cache to be +synchronized with (i.e. written to) the medium. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the lowest logical block address in the cache to +synchronize to the medium. Default value is 0 . +.TP +\fB\-s\fR, \fB\-\-sync\-nv\fR +synchronize the (volatile) cache with the non\-volatile cache. Without this +option (or if there is no non\-volatile cache in the device) the +synchronization is with the medium. The SYNC_NV bit was made obsolete in +SBC\-3 revision 35d. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fISECS\fR +where \fISECS\fR is the number of seconds the OS allows the SYNCHRONIZE +CACHE(16) to complete before it tries to cancel the command. Cancelling +commands (typically with the task management function "abort task") is +best avoided. Note this option is only active together with the \fI\-\-16\fR +option. The default timeout is 60 seconds for both SYNCHRONIZE CACHE(10) +and SYNCHRONIZE CACHE(16). Note that timeout issues can be avoided with +the \fI\-\-immed\fR option. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +With the SYNCHRONIZE CACHE(16) command \fILBA\fR can be up to 64 bits +in size and \fICOUNT\fR up to 32 bits in size. With the SYNCHRONIZ +CACHE(10) command \fILBA\fR can be up to 32 bits in size and \fICOUNT\fR +up to 16 bits in size. +.PP +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.SH EXIT STATUS +The exit status of sg_sync is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_start(sg3_utils) diff --git a/doc/sg_test_rwbuf.8 b/doc/sg_test_rwbuf.8 new file mode 100644 index 0000000..d610531 --- /dev/null +++ b/doc/sg_test_rwbuf.8 @@ -0,0 +1,86 @@ +.TH SG_TEST_RWBUF "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_test_rwbuf \- test a SCSI host adapter by issuing dummy writes +and reads +.SH SYNOPSIS +.B sg_test_rwbuf +[\fI\-\-addrd=AR\fR] [\fI\-\-addwr=AW\fR] [\fI\-\-help\fR] +[\fI\-\-quick\fR] \fI\-\-size=SZ\fR [\fI\-\-times=NUM\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.PP +or an older deprecated format +.B sg_test_rwbuf +\fIDEVICE\fR \fISZ\fR [\fIAW\fR] [\fIAR\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +sg_test_rwbuf writes and reads back \fISZ\fR bytes to the internal buffer of +\fIDEVICE\fR (e.g. /dev/sda or /dev/sg0). A pseudo random pattern is +written to the data buffer on the device then read back. If the same pattern +is found 'Success' is reported. If they do not match (checksums unequal) then +this is reported and up to 24 bytes from the first point of mismatch are +reported; the first line shows what was written and the second line shows +what was received. For testing purposes, you can ask it to write \fIAW\fR or +read \fIAR\fR additional bytes. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-r\fR, \fB\-\-addrd\fR=\fIAR\fR +Read an additional \fIAR\fR bytes (more than indicated by \fISZ\fR) from the +data buffer. Checksum is performed over the first \fISZ\fR bytes. +.TP +\fB\-w\fR, \fB\-\-addwr\fR=\fIAW\fR +Write an additional \fIAW\fR bytes (more than indicated by \fISZ\fR) of +zeros into the data buffer. Checksum is generated over the first \fISZ\fR +bytes. +.TP +\fB\-h\fR, \fB\-\-help\fR +Print out a usage message the exit. +.TP +\fB\-q\fR, \fB\-\-quick\fR +Perform a READ BUFFER descriptor command to find out the available data +buffer length and offset, print them out then exit (without testing +with write/read sequences). +.TP +\fB\-s\fR, \fB\-\-size\fR=\fISZ\fR +where \fISZ\fR is the size of buffer in bytes to be written then read and +checked. This number needs to be less than or equal to the size of the +device's data buffer which can be seen from the \fI\-\-quick\fR option. +Either this option or the \fI\-\-quick\fR option should be given. +.TP +\fB\-t\fR, \fB\-\-times\fR=\fINUM\fR +where \fINUM\fR is the number of times to repeat the write/read to buffer +test. Default value is 1 . +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase verbosity of output. +.TP +\fB\-V\fR, \fB\-\-version\fR +print version number (and data of last change) then exit. +.SH NOTES +The microcode in a SCSI device is _not_ modified by doing a WRITE BUFFER +command with its mode set to "data" (0x2) as done by this utility. Therefore +this utility is safe in that respect. [Mode values 0x4, 0x5, 0x6 and 0x7 +are the dangerous ones :\-)] +.PP +\fBWARNING\fR: If you access the device at the same time (e.g. because it's +a hard disk with a mounted file system on it) the device's buffer may be +used by the device itself for other data at the same time, and overwriting +it may or may not cause data corruption! \fBHOWEVER\fR the SPC\-3 draft +standard does state in its WRITE BUFFER command: "This command shall not +alter any medium of the logical unit when data mode ... is specified". This +implies that it _is_ safe to use this utility with devices that have mounted +file systems on them. +Following this theme further, a disk with active mounted file systems may +cause the data read back to be different (due to caching activity) to what +was written and hence a checksum error. +.SH EXIT STATUS +The exit status of sg_test_rwbuf is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by D. Gilbert and K. Garloff +.SH COPYRIGHT +Copyright \(co 2000\-2018 Douglas Gilbert, Kurt Garloff +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/doc/sg_timestamp.8 b/doc/sg_timestamp.8 new file mode 100644 index 0000000..3dd076d --- /dev/null +++ b/doc/sg_timestamp.8 @@ -0,0 +1,155 @@ +.TH SG_TIMESTAMP "8" "April 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_timestamp \- report or set timestamp on SCSI device +.SH SYNOPSIS +.B sg_timestamp +[\fI\-\-elapsed\fR] [\fI\-\-help\fR] [\fI\-\-hex\fR] +[\fI\-\-milliseconds=MS\fR] [\fI\-\-no\-timestamp\fR] [\fI\-\-origin\fR] +[\fI\-\-raw\fR] [\fI\-\-readonly\fR] [\fI\-\-seconds=SECS\fR] [\fI\-\-srep\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI REPORT TIMESTAMP or SET TIMESTAMP command to the \fIDEVICE\fR. +These commands are found in the SPC\-5 draft standard revision +7 (spc5r07.pdf). +.PP +If either the \fI\-\-milliseconds=MS\fR or \fI\-\-seconds=SECS\fR option is +given (and both can't be given) then the SET TIMESTAMP command is sent; +otherwise the REPORT TIMESTAMP command is sent. +.PP +The timestamp is sent and received from the \fIDEVICE\fR as the number of +milliseconds since the epoch of 1970\-01\-01 00:00:00 UTC and is held in a 48 +bit unsigned integer. That same epoch is used by Unix machines, but they +usually hold the number of seconds since that epoch. The Unix date command +and especally its "+%s" format is useful in converting to and from +timestamps and more humanly readable forms. See the EXAMPLES section below. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-e\fR, \fB\-\-elapsed\fR +assume the timestamp in the REPORT TIMESTAMP is an elapsed time from an +event such as a power cycle or hard reset and format the output as ' +days hh:mm:ss.xxx' where hh is hours (00 to 23 inclusive); mm is +minutes (00 to 59 inclusive); ss is seconds (00 to 59 inclusive) and xxx +is milliseconds (000 to 999 inclusive). If the number of days is 0 +then '0 days' is not output unless this option is given two or more times. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-H\fR, \fB\-\-hex\fR +output the response to REPORT TIMESTAMP in ASCII hexadecimal on stderr. The +response is not decoded. +.TP +\fB\-m\fR, \fB\-\-milliseconds\fR=\fIMS\fR +where \fIMS\fR is the number of milliseconds since 1970\-01\-01 00:00:00 UTC +to set in the \fIDEVICE\fR with the SCSI SET TIMESTAMP command. +.TP +\fB\-N\fR, \fB\-\-no\-timestamp\fR +when REPORT TIMESTAMP is called this option suppress the output of the +timestamp value (in either seconds or milliseconds). This may be useful +in uncluttering the output when trying to decode the timestamp origin (see +the \fI\-\-origin\fR option). +.TP +\fB\-o\fR, \fB\-\-origin\fR +the REPORT TIMESTAMP returned parameter data contains a "timestamp origin" +field. When this option is given, that field is decoded and printed out +before the timestamp value is output. The default action (i.e. when the +option is not given) is not to print out this decoded field. +.br +T10 defines this field as "the most recent event that initialized the +returned device clock". The value 0 indicates a power up of hard reset +initialized the clock; 2 indicates a SET TIMESTAMP initialized the +clock while 3 indicates some other method initialized the clock. +.br +When used once a descriptive string is output (in a line before the +timestamp value). When used twice the value of the TIMESTAMP ORIGIN +field is output (in decimal, a value between 0 and 7 inclusive). When +used thrice a line of the form 'TIMESTAMP_ORIGIN=' is output. +.TP +\fB\-r\fR, \fB\-\-raw\fR +output the SCSI REPORT TIMESTAMP response (i.e. the data\-out buffer) in +binary (to stdout). Note that the \fI\-\-origin\fR and \fI\-\-srep\fR +options are ignored when this option is given. Also all error and +verbose messages are output to stderr. +.TP +\fB\-R\fR, \fB\-\-readonly\fR +open the \fIDEVICE\fR read\-only. The default action is to open the +\fIDEVICE\fR read\-write. +.TP +\fB\-s\fR, \fB\-\-seconds\fR=\fISECS\fR +where \fISECS\fR is the number of seconds since 1970\-01\-01 00:00:00 UTC +to set in the \fIDEVICE\fR with the SCSI SET TIMESTAMP command. \fISECS\fR +is multiplied by 1000 before being used in the SET TIMESTAMP command. +.TP +\fB\-S\fR, \fB\-\-srep\fR +report the number of seconds since 1970\-01\-01 00:00:00 UTC. This is done +by dividing by 1000 the value returned by the SCSI REPORT TIMESTAMP command. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH EXIT STATUS +The exit status of sg_timestamp is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH NOTES +The TCMOS and the SCSIP bits in the Control extension mode page (see sdparm) +modify the actions of the timestamp held by a \fIDEVICE\fR. +.PP +Currently only the "Utilization usage rate based on date and time" parameters +within the Utilization log page (sbc4r09.pdf) use timestamps. See the sg_logs +utility. Vendor specific commands and pages may also be using timestamps. +.SH EXAMPLES +On Unix machines (e.g. Linux, FreeBSD and Solaris) the date command is useful +when working with timestamps. +.PP +To fetch the timestamp from a \fIDEVICE\fR and display it in a humanly +readable form the following could be used: +.PP + # sg_timestamp \-S /dev/sdb +.br +1448993950 +.br + # date \-\-date=@1448993950 +.br +Tue Dec 1 13:19:10 EST 2015 +.br + # date \-R \-\-date="@1448993950" +.br +Tue, 01 Dec 2015 13:19:10 \-0500 +.PP +The latter two date commands show different forms of the same date (i.e. +1448993950 seconds since 1970\-01\-01 00:00:00 UTC). The sg_timestamp and +date commands can be combined using backquotes: +.PP + # date \-R \-\-date=@`sg_timestamp \-S /dev/sdc` +.br +Wed, 16 Dec 2015 20:12:59 \-0500 +.PP +To set the timestamp on the \fIDEVICE\fR to now (approximately) the +following could be used: +.PP + # date +%s +.br +1448993955 +.br + # sg_timestamp \-\-seconds=1448993955 /dev/sdb +.PP +Those two command lines could be combined into one by using backquotes: +.PP + # sg_timestamp \-\-seconds=`date +%s` /dev/sdb +.PP +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2015\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sdparm(sdparm), sg_logs(sg3_utils) diff --git a/doc/sg_turs.8 b/doc/sg_turs.8 new file mode 100644 index 0000000..66e6ad3 --- /dev/null +++ b/doc/sg_turs.8 @@ -0,0 +1,136 @@ +.TH SG_TURS "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_turs \- send one or more SCSI TEST UNIT READY commands +.SH SYNOPSIS +.B sg_turs +[\fI\-\-help\fR] [\fI\-\-low\fR] [\fI\-\-number=NUM\fR] [\fI\-\-num=NUM\fR] +[\fI\-\-progress\fR] [\fI\-\-time\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +\fIDEVICE\fR +.PP +.B sg_turs +[\fI\-n=NUM\fR] [\fI\-p\fR] [\fI\-t\fR] [\fI\-v\fR] [\fI\-V\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility sends one or more SCSI TEST UNIT READY commands to the +\fIDEVICE\fR. This may be useful for timing the per command overhead. +Note that TEST UNIT READY has no associated data, just a 6 byte +command (with each byte a zero) and a returned SCSI status value. +.PP +This utility supports two command line syntaxes, the preferred one is +shown first in the synopsis and explained in this section. A later section +on the old command line syntax outlines the second group of options. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-h\fR, \fB\-\-help\fR +print out the usage message then exit. +.TP +\fB\-l\fR, \fB\-\-low\fR +when [\fI\-\-progress\fR] is not being used, this utility tries to complete +the SCSI TEST UNIT READY command(s) as quickly as possible. Usually it +calls a library function to do each TUR (sg_ll_test_unit_ready). With this +option it uses the lower level sg_pt interface (see sg_pt.h) to save a +little time on each TUR. +.TP +\fB\-n\fR, \fB\-\-number\fR=\fINUM\fR +performs TEST UNIT READY \fINUM\fR times. If not given defaults to 1. +These suffix multipliers are permitted: c C *1; w W *2; b B *512; +k K KiB *1,024; KB *1,000; m M MiB *1,048,576; MB *1,000,000; +g G GiB *1,073,741,824; and GB *1,000,000,000 . Also a suffix of the +form "x" multiplies the leading number by . Alternatively a hex +number may be given, prefixed by either '0x' or has a trailing 'h'. +.TP +\fB\-\-num\fR=\fINUM\fR +same as \fI\-\-number=NUM\fR. Added for compatibility with sg_requests +which has taken over the role of polling the progress indication. +.TP +\fB\-O\fR, \fB\-\-old\fR +Switch to older style options. Please use as first option. +.TP +\fB\-p\fR, \fB\-\-progress\fR +show progress indication (a percentage) if available. If \fI\-\-number=NUM\fR +is given, \fINUM\fR is greater than 1 and an initial progress indication +was detected then this utility waits 30 seconds before subsequent checks. +Exits when \fINUM\fR is reached or there are no more progress indications. +Ignores \fI\-\-time\fR option. See NOTES section below. +.TP +\fB\-t\fR, \fB\-\-time\fR +after completing the requested number of TEST UNIT READY commands, outputs +the total duration and the average number of commands executed per second. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase level or verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print version string then exit. +.SH NOTES +The progress indication is optionally part of the sense data. When a prior +command that takes a long time to complete (and typically precludes other +media access commands) is still underway, the progress indication can be used +to determine how long before the device returns to its normal state. Around +SPC\-3 T10 changed the preferred command for polling the progress indication +from TEST UNIT READY to REQUEST SENSE (see the sg_requests utilty). +.PP +The SCSI FORMAT command for disks used with the IMMED bit set is an example +of an operation that takes a significant amount of time and precludes other +media access during that time. The IMMED bit set instructs the FORMAT command +to return control to the application client once the format has commenced (see +SBC\-3). Several long duration SCSI commands associated with tape drives also +use the progress indication (see SSC\-3). +.PP +The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the +O_RDONLY flag). +.PP +Early standards suggested that the SCSI TEST UNIT READY command be used for +polling the progress indication. More recent standards seem to suggest +the SCSI REQUEST SENSE command should be used instead. +.SH EXIT STATUS +The exit status of sg_turs is 0 when it is successful (e.g. in the case of +a mechanical disk, it is spun up and ready to accept commands). For this +utility the other exit status of interest is 2 corresponding to +the "not ready" sense key. For other exit status values see the sg3_utils(8) +man page. +.SH OLDER COMMAND LINE OPTIONS +The options in this section were the only ones available prior to sg3_utils +version 1.23 . Since then this utility defaults to the newer command line +options which can be overridden by using \fI\-\-old\fR (or \fI\-O\fR) as the +first option. See the ENVIRONMENT VARIABLES section for another way to +force the use of these older command line options. +.TP +\fB\-n\fR=\fINUM\fR +performs TEST UNIT READY \fINUM\fR times. If not given defaults to 1. +Equivalent to \fI\-\-number=NUM\fR in the main description. +.TP +\fB-N\fR, \fB\-\-new\fR +Switch to the newer style options. +.TP +\fB\-p\fR +show progress indication (a percentage) if available. +Equivalent to \fI\-\-progress\fR in the main description. +.TP +\fB\-t\fR +after completing the requested number of TEST UNIT READY commands, outputs +the total duration and the average number of commands executed per second. +Equivalent to \fI\-\-time\fR in the main description. +.TP +\fB\-v\fR +increase level of verbosity. +.TP +\fB\-V\fR +print out version string then exit. +.SH ENVIRONMENT VARIABLES +Since sg3_utils version 1.23 the environment variable SG3_UTILS_OLD_OPTS +can be given. When it is present this utility will expect the older command +line options. So the presence of this environment variable is equivalent to +using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option. +.SH AUTHORS +Written by D. Gilbert +.SH COPYRIGHT +Copyright \(co 2000\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq, sg_requests (sg3_utils) diff --git a/doc/sg_unmap.8 b/doc/sg_unmap.8 new file mode 100644 index 0000000..bd2e35f --- /dev/null +++ b/doc/sg_unmap.8 @@ -0,0 +1,166 @@ +.TH SG_UNMAP "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_unmap \- send SCSI UNMAP command (known as 'trim' in ATA specs) +.SH SYNOPSIS +.B sg_unmap +[\fI\-\-all=ST,RN[,LA]\fR] [\fI\-\-anchor\fR] [\fI\-\-dry\-run\fR] +[\fI\-\-force\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR] [\fI\-\-in=FILE\fR] +[\fI\-\-lba=LBA,LBA...\fR] [\fI\-\-num=NUM,NUM...\fR] [\fI\-\-timeout=TO\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send a SCSI UNMAP command to \fIDEVICE\fR to unmap one or more logical +blocks. This command was introduced in SBC\-3 revision 18 under the broad +heading of "logical block provisioning". Logical blocks may also be unmapped +by the SCSI WRITE SAME command; see the sg_write_same utility. The unmap +capability is closely related to the ATA DATA SET MANAGEMENT command with +the "Trim" bit set. +.PP +Logical blocks to be unmapped can be specified in one of three ways to this +utility. One way is by supplying the start LBAs to the '\-\-lba=' option +and the corresponding number(s) to unmap to the '\-\-num=' option. Another +way is by putting start LBA and number to unmap pairs in a file whose name +is given to the '\-\-in=' option. Alternatively a large segment or all of +a disk (SSD) can be unmapped with the \fI\-\-all=ST_RN[,LA]\fR option. All +values are assumed to be decimal unless prefixed by "0x" (or "0X") or have +a trailing "h" (or "H") in which case they are interpreted as hexadecimal. +Suffix multipliers are permitted on decimal values (e.g. '\-\-num=1m'). +.PP +When the '\-\-lba=' option is given then the '\-\-num=' option must also be +given. If one has a comma separated list as its argument then the other must +have the same number of elements in its list. The arguments can use a single +space as a separator but need to be in quotes or escaped to not be +misinterpreted by the shell. +.PP +With the '\-\-in=FILE' option an even number of values must be found and are +interpreted as pairs: the first value in each pair is a starting LBA and the +second value is the number to unmap from that LBA. Everything from and +including a "#" on a line is ignored as are blank lines. Values may be +comma, space and tab separated or appear on separate lines. Each line should +not exceed 1023 bytes in length. +.PP +Since a lot of data can be lost with this utility, a 15 second "cooling off" +period is given before any UNMAP commands are sent. During this period the +user is reminded what will happen, and to which device, so they can use +control\-C (or some other technique) to terminate this utility before any +unmapping takes place. This period can be bypassed with the \fI\-\-force\fR +option. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-A\fR, \fB\-\-all\fR=\fIST,RN[,LA]\fR +where \fIST\fR is the starting LBA, \fIRN\fR is the repeat number which is +the maximum number of blocks in each SCSI UNMAP command, and \fILA\fR, if +given, is the last LBA to unmap. If \fILA\fR is not given, then the last +LBA on the \fIDEVICE\fR is used. That is obtained by the SCSI READ CAPACITY +command. +.TP +\fB\-a\fR, \fB\-\-anchor\fR +sets the 'Anchor' bit in the command (introduced in sbc3r22). +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +perform all the preparation, including opening \fIDEVICE\fR plus sending +a 'standard' SCSI INQUIRY command (and optionally a READ CAPACITY), but +exit before performing any SCSI UNMAP commands. +.TP +\fB\-f\fR, \fB\-\-force\fR +bypass the 15 second warning period that occurs before any UNMAP commands +are sent. +.TP +\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR +sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero. +\fIGN\fR should be a value between 0 and 63. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-I\fR, \fB\-\-in\fR=\fIFILE\fR +where \fIFILE\fR is a file name containing pairs of values. The first +member of each pair is a starting LBA and the second member of the +pair is the number of logical blocks to unmap from and including that +starting LBA. Values are interpreted as decimal unless indicated +otherwise. This option cannot be present with the '\-\-lba=' option. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA,LBA...\fR +where \fILBA,LBA...\fR is a string of comma (or space) separated values +that are interpreted as starting logical block addresses. Each number +is interpreted as decimal unless prefixed by '0x' or '0X' (or it has a +trailing 'h' or 'H'). An argument that contains any space separators needs +to be quoted (or otherwise escaped). When this option is given then +the '\-\-num=' option must also be given and they must contain the same +number of elements in their arguments. +.TP +\fB\-n\fR, \fB\-\-num\fR=\fINUM,NUM...\fR +where \fINUM,NUM...\fR is a string of comma (or space) separated values +that are interpreted as a number of logical blocks to unmap. Each number +is interpreted as decimal unless prefixed by '0x' or '0X' (or it has a +trailing 'h' or 'H'). Note that 0 blocks is acceptable. An argument that +contains any space separators needs to be quoted (or otherwise escaped). +When this option is given then the '\-\-lba=' option must also be given +and they must contain the same number of elements in their arguments. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR +where \fITO\fR is a timeout value (in seconds) for the UNMAP command. +The default value is 60 seconds. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +Some limits: an LBA can be up to 64 bits, a NUM up to 32 bits (imposed +by structure of UNMAP SCSI command parameter data). The NUM is +further constrained by the MAXIMUM UNMAP LBA COUNT field in the +BLOCK LIMITS VPD page (0xb0). The maximum number of LBA,NUM pairs is +limited to 128 by this utility and may be further constrained by the +MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT field in the BLOCK LIMITS VPD +page. +.PP +Since it is unclear how long the UNMAP command will take to execute +a '\-\-timeout=" option has been provided. The default timeout +period is 60 seconds. If all the logical blocks on a logical unit (e.g. +a disk drive) are to be unmapped then the FORMAT UNIT SCSI command (see +the sg_format utility) may be considered as an alternative. +.PP +Support for logical block provisioning is indicated by the LBPME bit in the +response to the SCSI READ CAPACITY (16) command (see the sg_readcap utility). +.PP +In SBC\-3 revision 25 the LBPU and ANC_SUP bits where added to the +Logical Block Provisioning VPD page. When LBPU is set it indicates that +the device supports the UNMAP command. When the ANC_SUP bit is set it +indicates the device supports anchored LBAs. +.PP +The SCSI UNMAP command does the "right thing" with respect to command +queueing. However its ATA counterpart: the DATA SET MANAGEMENT command with +the "Trim" bit set does not interact well with SATA queueing known as NCQ. +To address this problem T13 have introduced a new command called SFQ DATA SET +MANAGEMENT which also has a Trim bit. +.SH EXAMPLES +In the examples directory of the sg3_utils package there is a +sg_unmap_example.txt file that shows the format that the '\-\-in=' +option accepts. +.PP +To unmap all blocks from and including LBA 0x2000 to the end of the +device (e.g. disk or SSD) with each SCSI UNMAP command given 1024 +blocks to unmap: +.PP + sg_unmap \-\-all=0x2000,1k /dev/sg2 +.PP +Add '\-\-force' to bypass the 15 seconds of warnings. So '\-\-force' is +appropriate for batch files. +.SH EXIT STATUS +The exit status of sg_unmap is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2009\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_format,sg_get_lba_status,sg_readcap,sg_vpd,sg_write_same(sg3_utils) diff --git a/doc/sg_verify.8 b/doc/sg_verify.8 new file mode 100644 index 0000000..51c8f88 --- /dev/null +++ b/doc/sg_verify.8 @@ -0,0 +1,207 @@ +.TH SG_VERIFY "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_verify \- invoke SCSI VERIFY command(s) on a block device +.SH SYNOPSIS +.B sg_verify +[\fI\-\-16\fR] [\fI\-\-bpc=BPC\fR] [\fI\-\-count=COUNT\fR] [\fI\-\-dpo\fR] +[\fI\-\-ebytchk=BCH\fR] [\fI\-\-group=GN\fR] [\fI\-\-help\fR] +[\fI\-\-in=IF\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-ndo=NDO\fR] [\fI\-\-quiet\fR] +[\fI\-\-readonly\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] +[\fI\-\-vrprotect=VRP\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends one or more SCSI VERIFY (10 or 16) commands to \fIDEVICE\fR. These SCSI +commands are defined in the SBC\-2 (draft) standard at http://www.t10.org and +SBC\-3 drafts. +.PP +When \fI\-\-ndo=NDO\fR is not given then the verify starts at the logical +block address given by the \fI\-\-lba=LBA\fR option and continues for +\fI\-\-count=COUNT\fR blocks. No more than \fI\-\-bpc=BPC\fR blocks are +verified by each VERIFY command so if necessary multiple VERIFY commands are +sent. Medium verification operations are performed by the \fIDEVICE\fR (e.g. +assuming each block has additional EEC data, check this against the logical +block contents). No news is good news (i.e. if there are no verify errors +detected then no messages are sent to stderr and the Unix exit status is 0). +.PP +When \fI\-\-ndo=NDO\fR is given then the \fI\-\-bpc=BPC\fR option is +ignored. A single VERIFY command is issued and a comparison starts at the +logical block address given by the \fI\-\-lba=LBA\fR option and continues for +\fI\-\-count=COUNT\fR blocks. The VERIFY command has an associated data\-out +buffer that is \fINDO\fR bytes long. The contents of the data\-out buffer are +obtained from the \fIFN\fR file (if \fI\-\-in=FN\fR is given) or from stdin. +A comparison takes place between data\-out buffer and the logical blocks +on the \fIDEVICE\fR. If the comparison is good then no messages are sent to +stderr and the Unix exit status is 0. If the comparison fails then a sense +buffer with a sense key of MISCOMPARE is returned; in this case the Unix exit +status will be 14. Messages will be sent to stderr associated with MISCOMPARE +sense buffer unless the \fI\-\-quiet\fR option is given. +.PP +In SBC\-3 revision 34 the BYTCHK field in all SCSI VERIFY commands was +expanded from one to two bits. That required some changes in the options +of this utility, see the section below on OPTION CHANGES. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-S\fR, \fB\-\-16\fR +uses a VERIFY(16) command (default VERIFY(10)). Even without this option, +using an \fI\-\-lba=LBA\fR which is too large, will cause the utility +to issue a VERIFY(16) command. +.TP +\fB\-b\fR, \fB\-\-bpc\fR=\fIBPC\fR +this option is ignored if \fI\-\-ndo=NDO\fR is given. Otherwise \fIBPC\fR +specifies the maximum number of blocks that will be verified by a single SCSI +VERIFY command. The default value is 128 blocks which equates to 64 KB for a +disk with 512 byte blocks. If \fIBPC\fR is less than \fICOUNT\fR then +multiple SCSI VERIFY commands are sent to the \fIDEVICE\fR. For the default +VERIFY(10) \fIBPC\fR cannot exceed 0xffff (65,535) while for VERIFY(16) +\fIBPC\fR cannot exceed 0x7fffffff (2,147,483,647). For recent block +devices (disks) this value may be constrained by the maximum transfer length +field in the block limits VPD page. +.TP +\fB\-c\fR, \fB\-\-count\fR=\fICOUNT\fR +where \fICOUNT\fR specifies the number of blocks to verify. The default value +is 1 . If \fICOUNT\fR is greater than \fIBPC\fR (or its default value of 128) +and \fINDO\fR is not given, 0 or less than multiple SCSI VERIFY commands are +sent to the device. Otherwise \fICOUNT\fR becomes the contents of the +verification length field of the SCSI VERIFY command issued. The +.B sg_readcap +utility can be used to find the maximum number of blocks that a block +device (e.g. a disk) has. +.TP +\fB\-d\fR, \fB\-\-dpo\fR +disable page out changes the cache retention priority of blocks read on +the device's cache to the lowest priority. This means that blocks read by +other commands are more likely to remain in the device's cache. +.TP +\fB\-E\fR, \fB\-\-ebytchk\fR=\fIBCH\fR +sets the BYTCHK field to \fIBCH\fR overriding the value (1) set by the +\fI\-\-ndo=NDO\fR option. Values of 1, 2 or 3 are accepted for \fIBCH\fR +however sbc3r34 reserves the value 2. If this option is given then +\fI\-\-ndo=NDO\fR must also be given. If \fIBCH\fR is 3 then \fICOUNT\fR +must be 1 and \fINDO\fR should be the size of one logical block (plus the +size of some or all of the protection information if \fIVRP\fR is greater +than 0). +.TP +\fB\-g\fR, \fB\-\-group\fR=\fIGN\fR +where \fIGN\fR becomes the contents of the group number field in the SCSI +VERIFY(16) command. It can be from 0 to 63 inclusive. The default value for +\fIGN\fR is 0. Note that this option is ignored for the SCSI VERIFY(10) +command. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR +where \fIIF\fR is the name of a file from which \fINDO\fR bytes will be read +and placed in the data\-out buffer. This is only done when the +\fI\-\-ndo=NDO\fR option is given. If this option is not given then stdin +is read. If \fIIF\fR is "\-" then stdin is also used. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR specifies the logical block address of the first block to +start the verify operation. \fILBA\fR is assumed to be decimal unless prefixed +by '0x' or a trailing 'h' (see below). The default value is 0 (i.e. the start +of the device). +.TP +\fB\-n\fR, \fB\-\-ndo\fR=\fINDO\fR +\fINDO\fR is the number of bytes to obtain from the \fIFN\fR file (if +\fI\-\-in=FN\fR is given) or from stdin. Those bytes are placed in the +data\-out buffer associated with the SCSI VERIFY command and \fINDO\fR +is placed in the verification length field in the cdb. The default value +for \fINDO\fR is 0 and the maximum value is dependent on the OS. If the +\fI\-\-ebytchk=BCH\fR option is not given then the BYTCHK field in the cdb +is set to 1. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +suppress the sense buffer messages associated with a MISCOMPARE sense key +that would otherwise be sent to stderr. Still set the exit status to 14 +which is the sense key value indicating a MISCOMPARE . +.TP +\fB\-r\fR, \fB\-\-readonly\fR +opens the DEVICE read\-only rather than read\-write which is the +default. The Linux sg driver needs read\-write access for the SCSI +VERIFY command but other access methods may require read\-only access. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-P\fR, \fB\-\-vrprotect\fR=\fIVRP\fR +where \fIVRP\fR is the value in the vrprotect field in the VERIFY command +cdb. It must be a value between 0 and 7 inclusive. The default value is +zero. +.SH BYTCHK +BYTCHK is the name of a field (two bits wide) in the VERIFY(10) and +VERIFY(16) commands. When set to 1 or 3 (sbc3r34 reserves the value 2) it +indicates that associated with the SCSI VERIFY command, a data\-out buffer +will be sent for the device (disk) to check. Using the \fI\-\-ndo=NDO\fR +option sets the BYTCHK field to 1 and \fINDO\fR is the number of bytes +placed in the data\-out buffer. Those bytes are obtained from stdin or +\fIIF\fR (from the \fI\-\-in=FN\fR option). The \fI\-\-ebytchk=BCH\fR +option may be used to override the BYTCHK field value of 1 with \fIBCH\fR. +.PP +The calculation of \fINDO\fR is left up to the user. Its value depends +on the logical block size (which can be found with the sg_readcap utility), +the \fICOUNT\fR and the \fIVRP\fR values. If the \fIVRP\fR is greater than +0 then each logical block will contain an extra 8 bytes (at least) of +protection information. +.PP +When the BYTCHK field is 0 then the verification process done by the +device (disk) is vendor specific. It typically involves checking each +block on the disk against its error correction codes (ECC) which is +additional data also held on the disk. +.PP +Many Operating Systems put limits on the maximum size of the +data\-out (and data\-in) buffer. For Linux at one time the limit was +less than 1 MB but has been increased somewhat. +.SH OPTION CHANGES +Earlier versions of this utility had a \fI\-\-bytchk=NDO\fR option which +set the BYTCHK bit and set the cdb verification length field to \fINDO\fR. +The shorter form of that option was \fI\-B NDO\fR. For backward +compatibility that option is still present but not documented. In its place +is the \fI\-\-ndo=NDO\fR whose shorter form of \fI\-n NDO\fR. +\fI\-\-ndo=NDO\fR sets the BYTCHK field to 1 unless that is overridden by +the \fI\-\-ebytchk=BCH\fR. +.SH NOTES +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +The amount of error correction and the number of retries attempted before a +block is considered defective are controlled in part by the Verify Error +Recovery mode page. A note in the SBC\-3 draft (rev 29 section 6.4.9 on the +Verify Error Recovery mode page) advises that to minimize the number of +checks (and hence have the most "sensitive" verify check) do the following +in that mode page: set the EER bit to 0, the PER bit to 1, the DTE bit to 1, +the DCR bit to 1, the verify retry count to 0 and the verify recovery time +limit to 0. Mode pages can be modified with the +.B sdparm +utility. +.PP +The SCSI VERIFY(6) command defined in the SSC\-2 standard and later (i.e. +for tape drive systems) is not supported by this utility. +.SH EXIT STATUS +The exit status of sg_verify is 0 when it is successful. When \fIBCH\fR is +other than 0 then a comparison takes place and if it fails then the exit +status is 14 which happens to be the sense key value of MISCOMPARE. +Otherwise see the EXIT STATUS section in the sg3_utils(8) man page. +.PP +Earlier versions of this utility set an exit status of 98 when there was a +MISCOMPARE. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sdparm(sdparm), sg_modes(sg3_utils), sg_readcap(sg3_utils), +.B sg_inq(sg3_utils) diff --git a/doc/sg_vpd.8 b/doc/sg_vpd.8 new file mode 100644 index 0000000..1bbdc7c --- /dev/null +++ b/doc/sg_vpd.8 @@ -0,0 +1,312 @@ +.TH SG_VPD "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_vpd \- fetch SCSI VPD page and/or decode its response +.SH SYNOPSIS +.B sg_vpd +[\fI\-\-all\fR] [\fI\-\-enumerate\fR] [\fI\-\-force\fR] [\fI\-\-help\fR] +[\fI\-\-hex\fR] [\fI\-\-ident\fR] [\fI\-\-inhex=FN\fR] [\fI\-\-long\fR] +[\fI\-\-maxlen=LEN\fR] [\fI\-\-page=PG\fR] [\fI\-\-quiet\fR] [\fI\-\-raw\fR] +[\fI\-\-vendor=VP\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fIDEVICE\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility, when \fIDEVICE\fR is given, fetches a Vital Product Data (VPD) +page and decodes it or outputs it in ASCII hexadecimal or binary. VPD pages +are fetched with a SCSI INQUIRY command. +.PP +Alternatively the \fI\-\-inhex=FN\fR option can be given. In this case +\fIFN\fR is assumed to be a file name ('\-' for stdin) containing ASCII +hexadecimal representing a VPD page response. If the \fI\-\-raw\fR option +is also given then binary input is assumed (rather than ASCII hexadecimal). +.PP +Probably the most important page is the Device Identification +VPD page (page number: 0x83). Since SPC\-3, support for this page +has been flagged as mandatory. This page can be fetched by +using the \fI\-\-ident\fR option. +.PP +The reference document used for interpreting VPD pages (and the INQUIRY +standard response) is T10/BSR INCITS 502 Revision 19 which is draft SPC\-5 +revision 19, 14 February 2018). It can be found at http://www.t10.org . +.PP +When no options are given, other than a \fIDEVICE\fR, then the "Supported +VPD pages" (0x0) VPD page is fetched and decoded. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-a\fR, \fB\-\-all\fR +decode all VPD pages. When used with \fIDEVICE\fR the pages to be decoded +are found in the "Supported VPD pages" VPD page. Pages that cannot be +decoded are displayed in hex; add the \fI\-\-long\fR option to have ASCII +displayed to the right of each line of hex. +.br +If this option is used with the \fI\-\-inhex=FN\fR option then the file +\fIFN\fR is assumed to contain 1 or more VPD pages (in ASCII hex or binary). +Decoding continues until the file is exhausted (or an error occurs). Sanity +checks are aplied on each VPD page's length and the ascending order of VPD +page numbers (required by SPC\-4) so bad data may be detected. +.br +If the \fI\-\-page=PG\fR option is also given then no VPD page whose page +number is greater than \fIPG\fR (or its numeric equivalent) is decoded. +.TP +\fB\-e\fR, \fB\-\-enumerate\fR +list the names of the known VPD pages, first the standard pages (i.e. +those defined by T10), then the vendor specific pages. Each group is sorted +in abbreviation order. The \fIDEVICE\fR and most other options are ignored +and this utility exits after listing the VPD page names. May be used together +with \fI\-\-page=PG\fR where \fIPG\fR is numeric. If so, it searches for the +summary lines of all VPD pages whose number matches \fIPG\fR. May be used +with \fI\-\-vendor=VP\fR to restrict output to known vendor specific pages +for vendor/product \fIVP\fR. +.TP +\fB\-f\fR, \fB\-\-force\fR +As a sanity check, the normal action when fetching VPD pages other than +page 0x0 (the "Supported VPD pages" VPD page), is to first fetch page 0x0 +and only if the requested page is one of the supported pages, to go ahead +and fetch the requested page. +.br +When this option is given, skip checking of VPD page 0x0 before accessing +the requested VPD page. The prior check of VPD page 0x0 is known to +crash certain USB devices, so use with care. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs the usage message summarizing command line options then exits. +Ignores \fIDEVICE\fR if given. +.TP +\fB\-H\fR, \fB\-\-hex\fR +outputs the requested VPD page in ASCII hexadecimal. Can be used multiple +times, see section on the ATA information vpd page. +.br +To generate output suitable for placing in a file that can be used by a +later invocation with the \fI\-\-inhex=FN\fR option, use the '\-HHHH' +option (e.g. 'sg_vpd \-p di \-HHHH /dev/sg3 > dev_id.hex'). The +reason '\-HHHH' is used is to flag that unadorned hexadecimal (without other +text or address offsets) is sent to stdout. +.TP +\fB\-i\fR, \fB\-\-ident\fR +decode the device identification (0x83) VPD page. When used once this option +has the same effect as '\-\-page=di'. When use twice then the short form of +the device identification VPD page's logical unit designator is decoded. In +the latter case this option has the same effect as '\-\-quiet \-\-page=di_lu'. +.TP +\fB\-I\fR, \fB\-\-inhex\fR=\fIFN\fR +\fIFN\fR is expected to be a file name (or '\-' for stdin) which contains +ASCII hexadecimal or binary representing a VPD page (or a standard INQUIRY) +response. This utility will then decode that response. It is preferable to +also supply the \fI\-\-page=PG\fR option, if not this utility will attempt +to guess which VPD page (or standard INQUIRY) the response is associated +with. The hexadecimal should be arranged as 1 or 2 digits representing a +byte each of which is whitespace or comma separated. Anything from and +including a hash mark to the end of line is ignored. If the \fI\-\-raw\fR +option is also given then \fIFN\fR is treated as binary. +.TP +\fB\-l\fR, \fB\-\-long\fR +when decoding some VPD pages, give a little more output. For example the ATA +Information VPD page only shows the signature (in hex) and the IDENTIFY +(PACKET) DEVICE (in hex) when this option is given. +.TP +\fB\-m\fR, \fB\-\-maxlen\fR=\fILEN\fR +where \fILEN\fR is the (maximum) response length in bytes. It is placed in the +cdb's "allocation length" field. If not given (or \fILEN\fR is zero) then +252 is used (apart from the ATA Information VPD page which defaults to 572) +and, if the response indicates this value is insufficient, another INQUIRY +command is sent with a larger value in the cdb's "allocation length" field. +If this option is given and \fILEN\fR is greater than 0 then only one INQUIRY +command is sent. Since many simple devices implement the INQUIRY command +badly (and do not support VPD pages) then the safest value to use for +\fILEN\fR is 36. See the sg_inq man page for the more information. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG\fR +where \fIPG\fR is the VPD page to be decoded or output. The \fIPG\fR argument +can either be an abbreviation, a number or a pair or numbers/abbreviations +separated by a comma. The VPD page abbreviations can be seen by using the +\fI\-\-enumerate\fR option. If a number is given it is assumed to be decimal +unless it has a hexadecimal indicator which is either a leading '0x' or a +trailing 'h'. If one number is given then it is assumed to be a VPD page +number. If two numbers (or abbreviations) are given then the second one is +the same as \fIVP\fR (see the \fI\-\-vendor=VP\fR option). If this option +is not given (nor '\-i', '\-l' nor '\-V') then the "Supported VPD pages" (0x0) +VPD page is fetched and decoded. If \fIPG\fR is '\-1' or 'sinq' then the +standard INQUIRY response is output. This option may also be used with the +\fI\-\-enumerate\fR (see its description). +.br +If \fIPG\fR is not found in the 'Supported VPD pages' VPD page (0x0) then +EDOM is returned. To bypass this check use the \fI\-\-force\fR option. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +suppress the amount of decoding output. +.TP +\fB\-r\fR, \fB\-\-raw\fR +if not used with \fI\-\-inhex=FN\fR then output requested VPD page in binary. +The output should be piped to a file or another utility when this option is +used. The binary is sent to stdout, and errors are sent to stderr. +.br +if used with \fI\-\-inhex=FN\fR then the contents of \fIFN\fR is treated as +binary. +.TP +\fB\-M\fR, \fB\-\-vendor\fR=\fIVP\fR +where \fIVP\fR is a vendor (e.g. "sea" for Seagate) or vendor/product +acronym (e.g. "hp3par" for the 3PAR array from HP). Many vendors have +re\-used the numbers at the beginning of the vendor specific VPD page +range (e.g. page 0xc0) and this option is a way of selecting only those +which are of interest. Using a \fIVP\fR of "xxx" will list the available +acronyms. +.br +If this option is used with \fI\-\-page=PG\fR and \fIPG\fR is an acronym +then this option is ignored. If \fIPG\fR is a number (e.g. 0xc0) then +\fIVP\fR is used to choose the which vendor specific page (e.g. sharing +page number 0xc0) to decode. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increases the level or verbosity. +.TP +\fB\-V\fR, \fB\-\-version\fR +print out version string then exit. +.SH ATA INFORMATION VPD PAGE +This VPD page (0x89 or 'ai') is defined by the SCSI to ATA Translation +standard. It contains information about the SAT layer, the "signature" of +the ATA device and the response to the ATA IDENTIFY (PACKET) DEVICE +command. The latter part has 512 bytes of identity, capability and +settings data which the hdparm utility is capable of decoding (so this +utility doesn't decode it). +.PP +To unclutter the output for this page, the signature and the IDENTIFY (PACKET) +DEVICE response are not output unless the \fI\-\-long\fR option (or +\fI\-\-hex\fR or \fI\-\-raw\fR) are given. When the \fI\-\-long\fR option +is given the IDENTIFY (PACKET) DEVICE response is output as 256 (16 bit) +words as is the fashion for ATA devices. To see that response as a string of +bytes use the '\-HH' option. To format the output suitable for hdparm to +decode use either the '\-HHH' or '\-rr' option. For example if 'dev/sdb' is +a SATA disk behind a SAT layer then this +command: 'sg_vpd \-p ai \-HHH /dev/sdb | hdparm \-\-Istdin' +should decode the ATA IDENTIFY (PACKET) DEVICE response. +.SH NOTES +Since some VPD pages (e.g. the Extended INQUIRY page) depend on settings +in the standard INQUIRY response, then the standard INQUIRY response is +output as a pseudo VPD page when \fIPG\fR is set to '\-1' or 'sinq'. Also +the decoding of some fields (e.g. the Extended INQUIRY page's SPT field) +is expanded when the '\-\-long' option is given using the standard INQUIRY +response information (e.g. the PDT and the PROTECT fields). +.PP +In the 2.4 series of Linux kernels the \fIDEVICE\fR must be +a SCSI generic (sg) device. In the 2.6 series block devices (e.g. disks +and ATAPI DVDs) can also be specified. For example "sg_inq /dev/sda" +will work in the 2.6 series kernels. From lk 2.6.6 other SCSI "char" +device names may be used as well (e.g. "/dev/st0m"). +.PP +The \fIDEVICE\fR is opened with a read\-only flag (e.g. in Unix with the +O_RDONLY flag). +.SH EXIT STATUS +The exit status of sg_vpd is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH EXAMPLES +The examples in this page use Linux device names. For suitable device +names in other supported Operating Systems see the sg3_utils(8) man page. +.PP +To see the VPD pages that a device supports, use with no options. The +command line invocation is shown first followed by a typical response: +.PP + # sg_vpd /dev/sdb +.br +Supported VPD pages VPD page: +.br + Supported VPD pages [sv] +.br + Unit serial number [sn] +.br + Device identification [di] +.br + Extended inquiry data [ei] +.br + Block limits (SBC) [bl] +.PP +To see the VPD page numbers associated with each supported page then +add the '\-\-long' option to the above command line. To view a +VPD page either its number or abbreviation can be given to +the '\-\-page=' option. The page name abbreviations are shown within +square brackets above. In the next example the Extended inquiry data +VPD page is listed: +.PP + # sg_vpd \-\-page=ei /dev/sdb +.br +extended INQUIRY data VPD page: +.br + ACTIVATE_MICROCODE=0 SPT=0 GRD_CHK=0 APP_CHK=0 REF_CHK=0 +.br + UASK_SUP=0 GROUP_SUP=0 PRIOR_SUP=0 HEADSUP=1 ORDSUP=1 SIMPSUP=1 +.br + WU_SUP=0 CRD_SUP=0 NV_SUP=0 V_SUP=0 +.br + P_I_I_SUP=0 LUICLR=0 R_SUP=0 CBCS=0 +.br + Multi I_T nexus microcode download=0 +.br + Extended self\-test completion minutes=0 +.br + POA_SUP=0 HRA_SUP=0 VSA_SUP=0 +.PP +To check if any protection types are supported by a disk use the '\-\-long' +option on the Extended inquiry data VPD page: +.PP + # sg_vpd \-\-page=ei \-\-long /dev/sdb +.br + extended INQUIRY data VPD page: +.br + ACTIVATE_MICROCODE=0 +.br + SPT=1 [protection types 1 and 2 supported] +.br + GRD_CHK=1 +.br + .... +.PP +Search for the name (and acronym) of all pages that share VPD page number +0xb0 . +.PP + # sg_vpd \-\-page=0xb0 \-\-enumerate +.br + Matching standard VPD pages: +.br + bl 0xb0 Block limits (SBC) +.br + oi 0xb0 OSD information +.br + sad 0xb0 Sequential access device capabilities (SSC) +.PP +Some examples follow using the "\-\-all" option. Send an ASCII hexadecimal +representation of all VPD pages to a file: +.PP + # sg_vpd \-\-all \-HHHH /dev/sg3 > all_vpds.hex +.PP +At some later time that file could be decoded with: +.PP + # sg_vpd \-\-all \-\-inhex=all_vpds.hex +.PP +To do the equivalent as the previous example but use a file containing +binary: +.PP + # sg_vpd \-\-all \-\-raw /dev/sg3 > all_vpds.bin +.br + # sg_vpd \-\-all \-\-raw \-\-inhex=all_vpds.bin +.PP +Notice that "\-\-raw" must be given with the second (\-\-inhex) invocation +to alert the utility that all_vpds.bin contains binary as it assumes ASCII +hexadecimal by default. Next we only decode T10 specified VPD pages +excluding vendor specific VPD pages that start at page number 0xc0: +.PP + # sg_vpd \-\-all \-\-page=0xbf \-\-raw \-\-inhex=all_vpds.bin +.PP +Further examples can be found on the http://sg.danny.cz/sg/sg3_utils.html +web page. +.SH AUTHOR +Written by Douglas Gilbert +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2006\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_inq(sg3_utils), sg3_utils(sg3_utils), sdparm(sdparm), hdparm(hdparm) diff --git a/doc/sg_wr_mode.8 b/doc/sg_wr_mode.8 new file mode 100644 index 0000000..c22be58 --- /dev/null +++ b/doc/sg_wr_mode.8 @@ -0,0 +1,225 @@ +.TH SG_WR_MODE "8" "April 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_wr_mode \- write (modify) SCSI mode page +.SH SYNOPSIS +.B sg_wr_mode +[\fI\-\-contents=H,H...\fR] [\fI\-\-dbd\fR] [\fI\-\-force\fR] [\fI\-\-help\fR] +[\fI\-\-len=10|6\fR] [\fI\-\-mask=M,M...\fR] [\fI\-\-page=PG_H[,SPG_H]\fR] +[\fI\-\-rtd\fR] [\fI\-\-save\fR] [\fI\-\-six\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Writes a modified mode page to \fIDEVICE\fR. Uses the SCSI MODE SENSE (6 +or 10 byte variant) command to fetch the existing mode data which includes +a mode page (or subpage). It then combines that with the contents, +potentially masked, and writes the modified mode page with the SCSI MODE +SELECT (6 or 10 byte variant) command. This utility does not modify +the block descriptor(s); if any block descriptors are fetched by the MODE +SENSE command then the same block descriptors are written back with the +following MODE SELECT command. +.PP +If the \fI\-\-rtd\fR option is given then most other options apart from +\fI\-\-save\fR, \fI\-\-len=\fR10|6\fR and \fI\-\-six\fR are ignored. In this +case only a MODE SELECT command is sent to the \fIDEVICE\fR with the RTD +bit (Revert To Defaults) set. This bit was added to this command in SPC\-5 +revision 11, so older devices may not support it. The Extended Inquiry VPD +page has the RTD_SUP bit to indicate whether the \fIDEVICE\fR supports the +RTD bit in the MODE SELECT(6 and 10) commands. When the \fI\-\-rtd\fR option +is given the rest of this section can be ignored. +.PP +If a contents argument is not given then the various components (i.e. +header, block descriptor(s) and mode page) of the "current" values of +the existing mode page are printed out. In this case the mode page is +not altered on the device. +.PP +If the contents are specified, and a mask is not specified, then the contents +must match the existing mode page in various aspects unless the +\fI\-\-force\fR option is given. These include length, mode page code and +subpage code if applicable. If all is well then the contents string is +written to \fIDEVICE\fR as the new mode page. +.PP +If both contents and mask strings are specified then only bit positions +in the contents corresponding to set bits in the mask are taken while the +existing mode page supplies bit positions corresponding to clear bits. +When a mask is given then the mask and/or the contents may be shorter +than the existing mode page. If the mask is shorter than the contents then +the remaining bytes are taken from the contents. If the contents are shorter +than the existing mode page then the remaining bytes are taken from the +existing mod page. +.PP +The force option allows the contents string to be written as the new +mode page without any prior checks on the existing mode page. This should +only be required for vendor specific mode pages. The existing mode data +is ignored apart from the block descriptors which can be suppressed with +the \fI\-\-dbd\fR option if need be. +.PP +Changing individual fields in a mode page is probably more easily done +with the sdparm utility. Fields can be identified by acronym or by a +numerical descriptor. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-c\fR, \fB\-\-contents\fR=\fIH,H...\fR +where \fIH,H...\fR is a string of comma separated hex numbers each of +which should resolve to a byte value (i.e. 0 to ff inclusive). A (single) +space separated string of hex numbers is also allowed but the list needs to +be in quotes. This is the new contents of the mode page to be written to +\fIDEVICE\fR, potentially filtered by the mask string. +.TP +\fB\-c\fR, \fB\-\-contents\fR=- +reads contents string from stdin. The hex numbers in the string may be comma, +space, tab or linefeed (newline) separated. If a line contains "#" then the +remaining characters on that line are ignored. Otherwise each non separator +character should resolve to a byte value (i.e. 0 to ff inclusive). This +forms the new contents of the mode page to be written to \fIDEVICE\fR, +potentially filtered by the mask string. +.TP +\fB\-d\fR, \fB\-\-dbd\fR +disable block descriptors (DBD flag in cdb). Some device types include +block descriptors in the mode data returned by a MODE SENSE command. If +so the same block descriptors are written by the MODE SELECT command. +This option instructs the MODE SENSE command not to return any block +descriptors. This would be a sensible default for this utility apart +from the fact that not all SCSI devices support the DBD bit in the cdb. +.TP +\fB\-f\fR, \fB\-\-force\fR +force the contents string to be taken as the new mode page, or at least +doesn't do checks on the existing mode page. Note that \fIDEVICE\fR may +still reject the new contents for the mode page. Cannot be given with +the \fI\-\-mask=M,M...\fR option. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-l\fR, \fB\-\-len\fR=10 | 6 +length of the SCSI commands (cdb) sent to \fIDEVICE\fR. The default is 10 +so 10 byte MODE SENSE and MODE SELECT commands are issued. Some old devices +don't support the 10 byte variants hence this option. +.TP +\fB\-m\fR, \fB\-\-mask\fR=\fIM,M...\fR +where \fIM,M...\fR is a string of comma separated hex numbers each of which +should resolve to a byte value (i.e. 0 to ff inclusive). A (single) space +separated string of hex numbers is also allowed but the list needs to be in +quotes. The mask chooses (bit by bit) whether the new mode page comes from +the contents (mask bit set) or from the existing mode page (mask bit clear). +If the mask string is shorter than the contents string then the remaining +bytes are taken from the contents string. If the contents string is shorter +than the existing mode page then the remaining bytes are taken from the +existing mode page (i.e. they are left unaltered). +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG_H\fR +where \fIPG_H\fR is the page code value to fetch and modify. The page code +is in hex and should be between 0 and 3e inclusive. Notice that page code +3f to fetch all mode pages is disallowed. +.TP +\fB\-p\fR, \fB\-\-page\fR=\fIPG_H,SPG_H\fR +where \fIPG_H\fR is the page code value and \fISPG_H\fR is the subpage code +value to fetch and modify. Both values are in hex. The subpage code should +be between 0 and fe inclusive. Notice that subpage code ff to fetch all +mode subpages (for a given mode page or all mode pages in the case of 3f,ff) +is disallowed. +.TP +\fB\-R\fR, \fB\-\-rtd\fR +when this option is given most other actions are bypassed and a MODE +SELECT(6 or 10) command is sent to the \fIDEVICE\fR with the RTD bit set. +This will cause all current values (and saved values if the \fI\-\-save\fR +option is also given) of all mode pages to be reverted to their default +values. +.TP +\fB\-s\fR, \fB\-\-save\fR +changes the "saved" mode page when MODE SELECT is successful. By +default (i.e. when \fI\-\-save\fR is not used) only the "current" mode page +values are changed when MODE SELECT is successful. In this case the new mode +page will stay in effect until the device is reset (e.g. power cycled). +When it restarts the "saved" values for the mode page will be re\-instated. +So to make changes permanent use the \fI\-\-save\fR option. +.br +When used with the \fI\-\-rtd\fR option then both the current and saved +values in each mode page are reverted to their default values. In the +absence of \fI\-\-save\fR option only the current values in each mode page +are reverted to their default values. +.TP +\fB\-6\fR, \fB\-\-six\fR +this option will cause the 6 byte variants of MODE SENSE and MODE SELECT +commands to be used. The default is to use the 10 byte options. This option +is equivalent to using the \fI\-\-len=6\fR option. + +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +This utility does not check whether the contents string is trying to +modify parts of the mode page which are changeable. The device should +do that and if some part is not changeable then it should +report: "Invalid field in parameter list". +.PP +Some mode pages are not saveable. If so an attempt to use the \fI\-\-save\fR +option should cause an error to be reported from the device: "Illegal field +in cdb". +.PP +The device is required to do various checks before it accepts a new +mode page. If these checks fail then the mode page is not altered and +either a "parameter list length error" or an "invalid field in +parameter list" error is returned by the device in the sense data. +.PP +The recommended way to modify a mode page is to read it with a +MODE SENSE, modify some part of it then write it back to the +device with a MODE SELECT command. For example, reading an existing mode +page can be accomplished with 'sg_modes \-p=1a \-r /dev/sdb > mp_1a.txt' (the +power condition mode page). The mp_1a.txt file can be edited and then used +as the contents string to this +utility (e.g. 'sg_wr_mode \-p 1a \-s \-c \- /dev/sdb < mp_1a.txt'). +.PP +Two fields differ between what is read from the device with MODE SENSE and +what is written to the device with MODE SELECT: +the mode data length is reserved (i.e. zero(es)) in a MODE +SELECT command while the PS bit ((sub)page byte 0 bit 7) in each +mode (sub)page is reserved (zero) in a MODE SELECT command. +The PS bit given in the contents string is zeroed unless +the \fI\-\-force\fR option is selected. +.SH EXAMPLES +This utility can be used together with the sg_modes utility. To re\-instate +the default mode page values (i.e. the mode page values chosen by the +manufacturer of the device) as both the current and saved mode page +values the following sequence could be used: +.PP + $ sg_modes \-\-control=2 \-\-page=1a \-r /dev/sda > t +.br + $ sg_wr_mode \-\-page=1a \-\-contents=\- \-\-save /dev/sda < t +.PP +Next is an example of using a mask to modify the "idle condition counter" +of the "power condition" mode page (0x1a) from 0x28 to 0x37. Note that the +change is not saved so the "idle condition counter" will revert to 0x28 +after the next power cycle. The output from sg_modes is abridged. +.PP + $ sg_modes \-\-page=1a /dev/hdc +.br + >> Power condition (mmc), page_control: current +.br + 00 1a 0a 00 03 00 00 00 28 00 00 01 2c +.PP + $ sg_wr_mode \-p 1a \-c 0,0,0,0,0,0,0,37 \-m 0,0,0,0,0,0,0,ff /dev/hdc +.PP + $ sg_modes \-p 1a /dev/hdc +.br + >> Power condition (mmc), page_control: current +.br + 00 1a 0a 00 03 00 00 00 37 00 00 01 2c +.SH EXIT STATUS +The exit status of sg_wr_mode is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sdparm(sdparm), sg_modes(sg3_utils), sginfo(sg3_utils) diff --git a/doc/sg_write_buffer.8 b/doc/sg_write_buffer.8 new file mode 100644 index 0000000..d2aab53 --- /dev/null +++ b/doc/sg_write_buffer.8 @@ -0,0 +1,227 @@ +.TH SG_WRITE_BUFFER "8" "January 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_write_buffer \- send SCSI WRITE BUFFER commands +.SH SYNOPSIS +.B sg_write_buffer +[\fI\-\-bpw=CS\fR] [\fI\-\-dry\-run\fR] [\fI\-\-help\fR] [\fI\-\-id=ID\fR] +[\fI\-\-in=FILE\fR] [\fI\-\-length=LEN\fR] [\fI\-\-mode=MO\fR] +[\fI\-\-offset=OFF\fR] [\fI\-\-read\-stdin\fR] [\fI\-\-skip=SKIP\fR] +[\fI\-\-specific=MS\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends one or more SCSI WRITE BUFFER commands to \fIDEVICE\fR, along with data +provided by the user. In some cases no data is required, or data can be read +from the file given in the \fI\-\-in=FILE\fR option, or data is read from +stdin when either \fI\-\-read\-stdin\fR or \fI\-\-in=\-\fR is given. +.PP +Some WRITE BUFFER command variants do not have associated data to send to the +device. For example "activate_mc" activates deferred microcode that was sent +via prior WRITE BUFFER commands. There is a different method used to download +microcode to SES devices, see the sg_ses_microcode utility. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-b\fR, \fB\-\-bpw\fR=\fICS\fR +where \fICS\fR is the chunk size in bytes. This will be the maximum number +of bytes sent per WRITE BUFFER command. So if \fICS\fR is less than the +effective length then multiple WRITE BUFFER commands are sent, each taking +the next chunk from the read data and increasing the buffer offset field +in the WRITE BUFFER command by the appropriate amount. The default is +a chunk size of 0 which is interpreted as a very large number hence only +one WRITE BUFFER command will be sent. This option should only be used with +modes that "download microcode, with offsets ..."; namely either mode 0x6, +0x7, 0xd or 0xe. +.br +The number in \fICS\fR can optionally be followed by ",act" or ",activate". +In this case after WRITE BUFFER commands have been sent until the +effective length is exhausted another WRITE BUFFER command with its mode +set to "Activate deferred microcode mode" [mode 0xf] is sent. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +Do all the command line processing and sanity checks including reading +the input file. However at the point where a WRITE BUFFER SCSI command(s) +would be sent, step over that call and assume it completed without errors +and continue. \fIDEVICE\fR is still opened but can be /dev/null (in Unix). +It is recommended to use \fI\-\-verbose\fR with this option to get an +overview of what would have happened. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. If used multiple times also prints +the mode names and their acronyms. +.TP +\fB\-i\fR, \fB\-\-id\fR=\fIID\fR +this option sets the buffer id field in the cdb. \fIID\fR is a value between +0 (default) and 255 inclusive. +.TP +\fB\-I\fR, \fB\-\-in\fR=\fIFILE\fR +read data from file \fIFILE\fR that will be sent with the WRITE BUFFER +command. If \fIFILE\fR is '\-' then stdin is read until an EOF is +detected (this is the same action as \fI\-\-read\-stdin\fR). Data is read +from the beginning of \fIFILE\fR except in the case when it is a regular file +and the \fI\-\-skip=SKIP\fR option is given. +.TP +\fB\-l\fR, \fB\-\-length\fR=\fILEN\fR +where \fILEN\fR is the length, in bytes, of data to be written to the device. +If not given (and the length cannot be deduced from \fI\-\-in=FILE\fR or +\fI\-\-read\-stdin\fR) then defaults to zero. If the option is given and the +length deduced from \fI\-\-in=FILE\fR or \fI\-\-read\-stdin\fR is less (or no +data is provided), then bytes of 0xff are used as fill bytes. +.TP +\fB\-m\fR, \fB\-\-mode\fR=\fIMO\fR +this option sets the MODE field in the cdb. \fIMO\fR is a value between +0 (default) and 31 inclusive. Alternatively an abbreviation can be given. +See the MODES section below. To list the available mode abbreviations at +run time give an invalid one (e.g. '\-\-mode=xxx') or use the '\-hh' option. +.TP +\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR +this option sets the BUFFER OFFSET field in the cdb. \fIOFF\fR is a value +between 0 (default) and 2**24\-1 . It is a byte offset. +.TP +\fB\-r\fR, \fB\-\-read\-stdin\fR +read data from stdin until an EOF is detected. This data is sent with +the WRITE BUFFER command to \fIDEVICE\fR. The action of this option is the +same as using '\-\-in=\-'. Previously this option's long name was +\fI\-\-raw\fR and it may still be used for backward compatibility. +.TP +\fB\-s\fR, \fB\-\-skip\fR=\fISKIP\fR +this option is only active when \fI\-\-in=FILE\fR is given and \fIFILE\fR is +a regular file, rather than stdin. Data is read starting at byte offset +\fISKIP\fR to the end of file (or the amount given by \fI\-\-length=LEN\fR). +If not given the byte offset defaults to 0 (i.e. the start of the file). +.TP +\fB\-S\fR, \fB\-\-specific\fR=\fIMS\fR +\fIMS\fR is the MODE SPECIFIC field in the cdb. This is a 3\-bit field +so the values 0 to 7 are accepted. This field was introduced in SPC\-4 +revision 32 and can be used to specify additional events that activate +deferred microcode (when \fIMO\fR is 0xD). +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR +\fITO\fR is the command timeout (in seconds) for each WRITE BUFFER command +issued by this utility. Its default value is 300 seconds (5 minutes) and +should only be altered if this is not sufficient. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH MODES +Following is a list of WRITE BUFFER command settings for the MODE field. +First is an acronym accepted by the \fIMO\fR argument of this utility. +Following the acronym in square brackets are the corresponding decimal and +hex values that may also be given for \fIMO\fR. The following are listed +in numerical order. +.TP +hd [0, 0x0] +Combined header and data (obsolete in SPC\-4). +.TP +vendor [1, 0x1] +Vendor specific. +.TP +data [2, 0x2] +Data (was called "Write Data" in SPC\-3). +.TP +dmc [4, 0x4] +Download microcode and activate (was called "Download microcode" in SPC\-3). +.TP +dmc_save [5, 0x5] +Download microcode, save, and activate (was called "Download microcode and +save" in SPC\-3). +.TP +dmc_offs [6, 0x6] +Download microcode with offsets and activate (was called "Download microcode +with offsets" in SPC\-3). +.TP +dmc_offs_save [7, 0x7] +Download microcode with offsets, save, and activate (was called "Download +microcode with offsets and save" in SPC\-3). +.TP +echo [10, 0xa] +Write data to echo buffer (was called "Echo buffer" in SPC\-3). +.TP +dmc_offs_ev_defer [13, 0xd] +Download microcode with offsets, select activation events, save, and defer +activate (introduced in SPC\-4). +.TP +dmc_offs_defer [14, 0xe] +Download microcode with offsets, save, and defer activate (introduced in +SPC\-4). +.TP +activate_mc [15, 0xf] +Activate deferred microcode (introduced in SPC\-4). +.TP +en_ex [26, 0x1A] +Enable expander communications protocol and Echo buffer (obsolete in SPC\-4). +.TP +dis_ex [27, 0x1B] +Disable expander communications protocol (obsolete in SPC\-4). +.TP +deh [28, 0x1C] +Download application client error history (was called "Download application +log" in SPC\-3). +.SH NOTES +If no \fI\-\-length=LEN\fR is given this utility reads up to 8 MiB of data +from the given file \fIFILE\fR (or stdin). If a larger amount of data is +required then the \fI\-\-length=LEN\fR option should be given. +.PP +The user should be aware that most operating systems have limits on the +amount of data that can be sent with one SCSI command. In Linux this +depends on the pass through mechanism used (e.g. block SG_IO or the sg +driver) and various setting in sysfs in the Linux lk 2.6/3 +series (e.g. /sys/block/sda/queue/max_sectors_kb). Devices (i.e. logical +units) also typically have limits on the maximum amount of data they can +handle in one command. These two limitations suggest that modes +containing the word "offset" together with the \fI\-\-bpw=CS\fR option +are required as firmware files get larger and larger. And \fICS\fR +can be quite small, for example 4096 bytes, resulting in many WRITE +BUFFER commands being sent. +.PP +Attempting to download a microcode/firmware file that is too large may +cause an error to occur in the pass\-through layer (i.e. before the +SCSI command is issued). In Linux such error reports can be obscure as +in "pass through os error invalid argument". FreeBSD reports such +errors well to the machine's console but returns a cryptic error message +to this utility. +.PP +Downloading incorrect microcode into a device has the ability to render +that device inoperable. One would hope that the device vendor verifies +the data before activating it. If the SCSI WRITE BUFFER command is given +values in its cdb (e.g. \fILEN\fR) that are inappropriate (e.g. too large) +then the device should respond with a sense key of ILLEGAL REQUEST and +an additional sense code of INVALID FIELD in CDB. If a WRITE BUFFER +command (or a sequence of them) fails due to device vendor verification +checks then it should respond with a sense key of ILLEGAL REQUEST and +an additional sense code of COMMAND SEQUENCE ERROR. +.PP +All numbers given with options are assumed to be decimal. +Alternatively numerical values can be given in hexadecimal preceded by +either "0x" or "0X" (or has a trailing "h" or "H"). +.SH EXAMPLES +The following sends new firmware to an enclosure. Sending a 1.5 MB +file in one WRITE BUFFER command caused the enclosure to lock up +temporarily and did not update the firmware. Breaking the firmware file +into 4 KB chunks (an educated guess) was more successful: +.PP + sg_write_buffer \-b 4k \-m dmc_offs_save \-I firmware.bin /dev/sg4 +.PP +The firmware update occurred in the following enclosure power cycle. With +a modern enclosure the Extended Inquiry VPD page gives indications in which +situations a firmware upgrade will take place. +.SH EXIT STATUS +The exit status of sg_write_buffer is 0 when it is successful. Otherwise +see the sg3_utils(8) man page. +.SH AUTHORS +Written by Luben Tuikov and Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2006\-2018 Luben Tuikov and Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_read_buffer, sg_ses_microcode(sg3_utils) diff --git a/doc/sg_write_long.8 b/doc/sg_write_long.8 new file mode 100644 index 0000000..2d5560d --- /dev/null +++ b/doc/sg_write_long.8 @@ -0,0 +1,176 @@ +.TH SG_WRITE_LONG "8" "January 2016" "sg3_utils\-1.42" SG3_UTILS +.SH NAME +sg_write_long \- send SCSI WRITE LONG command +.SH SYNOPSIS +.B sg_write_long +[\fI\-\-16\fR] [\fI\-\-cor_dis\fR] [\fI\-\-help\fR] [\fI\-\-in=IF\fR] +[\fI\-\-lba=LBA\fR] [\fI\-\-pblock\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] [\fI\-\-wr_uncor\fR] [\fI\-\-xfer_len=BTL\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Send the SCSI WRITE LONG (10 or 16 byte) command to \fIDEVICE\fR. The buffer +to be written to the \fIDEVICE\fR is filled with +.B 0xff +bytes or read from the \fIIF\fR file. This buffer includes the logical +data (e.g. 512 bytes) and the ECC bytes. +.PP +This utility can be used to generate a MEDIUM ERROR at a specific logical +block address. This can be useful for testing error handling. Prior to +such a test, the +.B sg_dd +utility could be used to copy the original contents of the logical +block address to some safe location. After the test the +.B sg_dd +utility could be used to write back the original contents of the +logical block address. An alternate strategy would be to read the "long" +contents of the logical block address with +.B sg_read_long +utility prior to testing and restore it with this utility after testing. +.PP +.B Take care: +If recoverable errors are being injected (e.g. only one or a few bits +changed so that the ECC is able to correct the data) then care should +be taken with the settings in the "read write error recovery" mode page. +Specifically if the ARRE (for reads) and/or AWRE (for writes) are set +then recovered errors will cause the lba to be reassigned (and the old +location to be added to the grown defect list (PLIST)). This is not easily +reversed and uses (one of the finite number of) the spare sectors set +aside for this purpose. If in doubt it is probably safest to clear the +ARRE and AWRE bits. These bits can be checked and modified with the +sdparm utility. For example: "sdparm \-c AWRE,ARRE /dev/sda" will clear +the bits until the disk is power cycled. +.PP +In SBC\-4 revision 7 all uses of SCSI WRITE LONG (10 and 16 byte) commands +were made obsolete apart from the case in which the WR_UNCOR bit is set. +The SCSI READ LONG (10 and 16 byte) commands were made obsolete in the +same revision. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-S\fR, \fB\-\-16\fR +send a SCSI WRITE LONG (16) command to \fIDEVICE\fR. The default action (in +the absence of this option) is to send a SCSI WRITE LONG (10) command. +.TP +\fB\-c\fR, \fB\-\-cor_dis\fR +sets the correction disabled (i.e 'COR_DIS') bit. This inhibits various +other mechanisms such as automatic block reallocation, error recovery +and various informational exception conditions being triggered. +This bit is relatively new in SBC\-3 . +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR +read data (binary) from file named \fIIF\fR and use it for the SCSI WRITE +LONG command. If \fIIF\fR is "\-" then stdin is read. If this option is +not given then 0xff bytes are used as fill. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the logical block address of the sector to overwrite. +Defaults to lba 0 which is a dangerous block to overwrite on a disk that is +in use. Assumed to be in decimal unless prefixed with '0x' or has a +trailing 'h'. If \fILBA\fR is larger than can fit in 32 bits then the +\fI\-\-16\fR option should be used. +.TP +\fB\-p\fR, \fB\-\-pblock\fR +sets the physical block (i.e 'PBLOCK') bit. This instructs \fIDEVICE\fR +to use the given data (unless \fI\-\-wr_uncor\fR is also given) to write +to the physical block specified by \fILBA\fR. The default action +is to write to the logical block corresponding to the given lba. +This bit is relatively new in SBC\-3 . +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). +.TP +\fB\-V\fR, \fB\-\-version\fR +output version string then exit. +.TP +\fB\-w\fR, \fB\-\-wr_uncor\fR +sets the "write uncorrected" (i.e 'WR_UNCOR') bit. This instructs the +\fIDEVICE\fR to flag the given lba (or the physical block that contains it +if \fI\-\-pblock\fR is also given) as having an unrecoverable error +associated with it. Note: no data is transferred to \fIDEVICE\fR, +other than the command (i.e. the cdb). In the absence of this option, the +default action is to use the provided data or 0xff +bytes (\fI\-\-xfer_len=BTL\fR in length) and write it to \fIDEVICE\fR. +This bit is relatively new in SBC\-3 . +.TP +\fB\-x\fR, \fB\-\-xfer_len\fR=\fIBTL\fR +where \fIBTL\fR is the byte transfer length (default to 520). If the +given value (or the default) does not match the "long" block size of the +device, nothing is written to \fIDEVICE\fR and the appropriate xfer_len value +may be deduced from the error response which is printed (to stderr). +.SH NOTES +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +The 10 byte SCSI WRITE LONG command limits the logical block address +to a 32 bit quantity. For larger LBAs use the \fI\-\-16\fR option for the +SCSI WRITE LONG (16) command. +.SH EXAMPLES +This section outlines setting up a block with corrupted data, checking the +error condition, then restoring useful contents to that sector. +.PP +First, if the data in a sector is important, save it with the sg_read_long +utility: +.PP + sg_read_long \-\-lba=0x1234 \-\-out=0x1234_1.img \-x \fIBTL\fR /dev/sda +.PP +This utility may need to be executed several time in order to determine +what the correct value for \fIBTL\fR is. +Next use this utility to "corrupt" that sector. That might be done with: +.PP + sg_write_long \-\-lba=0x1234 \-x \fIBTL\fR /dev/sda +.PP +This will write a sector (and ECC data) of 0xff bytes. Some disks may +reject this (at least one of the author's does). Another approach is +to copy the 0x1234_1.img file (to 0x1234_2.img in this example) and +change some values with a hex editor. Then write the changed image with: +.PP + sg_write_long \-\-lba=0x1234 \-\-in=0x1234_2.img \-x \fIBTL\fR /dev/sda +.PP +Yet another approach is to use the \fI\-\-wr_uncor\fR option, if supported: +.PP + sg_write_long \-\-lba=0x1234 \-\-wr_uncor /dev/sda +.PP +Next we use the sg_dd utility to check that the sector is corrupted. Here is an +example: +.PP + sg_dd if=/dev/sda blk_sgio=1 skip=0x1234 of=. bs=512 count=1 verbose=4 +.PP +Notice that the "blk_sgio=1" option is given. This is to make sure that +the sector is read (and no others) and the error is fully reported. +The "blk_sgio=1" option causes the SG_IO ioctl to be used by sg_dd rather +than the block subsystem. +.PP +Finally we should restore sector 0x1234 to a non\-corrupted state. A sector +full of zeros could be written with: +.PP + sg_dd if=/dev/zero of=/dev/sda blk_sgio=1 seek=0x1234 bs=512 count=1 +.PP +This will result in a sector (block) with 512 bytes of 0x0 without a +MEDIUM ERROR since the ECC and associated data will be regenerated and +thus well formed. The 'blk_sgio=1' option is even more important in this +case as it may stop the block subsystem doing a read before write (since +the read will most likely fail). +Another approach is to write back the original contents: +.PP + sg_write_long \-\-lba=0x1234 \-\-in=0x1234_1.img \-x \fIBTL\fR /dev/sda +.PP +.SH EXIT STATUS +The exit status of sg_write_long is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Saeed Bishara. Further work by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2016 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_read_long, sg_dd (both in sg3_utils), sdparm(sdparm) diff --git a/doc/sg_write_same.8 b/doc/sg_write_same.8 new file mode 100644 index 0000000..55e739d --- /dev/null +++ b/doc/sg_write_same.8 @@ -0,0 +1,324 @@ +.TH SG_WRITE_SAME "8" "November 2017" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_write_same \- send SCSI WRITE SAME command +.SH SYNOPSIS +.B sg_write_same +[\fI\-\-10\fR] [\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-anchor\fR] +[\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR] [\fI\-\-in=IF\fR] [\fI\-\-lba=LBA\fR] +[\fI\-\-lbdata\fR] [\fI\-\-num=NUM\fR] [\fI\-\-ndob\fR] [\fI\-\-pbdata\fR] +[\fI\-\-timeout=TO\fR] [\fI\-\-unmap\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] [\fI\-\-wrprotect=WPR\fR] [\fI\-\-xferlen=LEN\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +Send the SCSI WRITE SAME (10, 16 or 32 byte) command to \fIDEVICE\fR. This +command writes the given block \fINUM\fR times to consecutive blocks on +the \fIDEVICE\fR starting at logical block address \fILBA\fR. +.PP +The length of the block to be written multiple times is obtained from either +the \fILEN\fR argument, or the length of the given input file \fIIF\fR, +or by calling READ CAPACITY(16) on \fIDEVICE\fR. The contents of the +block to be written are obtained from the input file \fIIF\fR or +zeros are used. If READ CAPACITY(16) is called (which implies \fIIF\fR +was not given) and the PROT_EN bit is set then an extra 8 bytes (i.e. +more than the logical block size) of 0xff are sent. If READ CAPACITY(16) +fails then READ CAPACITY(10) is used to determine the block size. +.PP +If neither \fI\-\-10\fR, \fI\-\-16\fR nor \fI\-\-32\fR is given then +WRITE SAME(10) is sent unless one of the following conditions is met. +If \fILBA\fR (plus \fINUM\fR) exceeds 32 bits, \fINUM\fR exceeds 65535, +or the \fI\-\-unmap\fR option is given then WRITE SAME(16) is sent. +The \fI\-\-10\fR, \fI\-\-16\fR and \fI\-\-32\fR options are mutually +exclusive. +.PP +SBC\-3 revision 35d introduced a "No Data\-Out Buffer" (NDOB) bit which, if +set, bypasses the requirement to send a single block of data to the +\fIDEVICE\fR together with the command. Only WRITE SAME (16 and 32 byte) +support the NDOB bit. If given, a user block of zeros is assumed; if +required, protection information of 0xffs is assumed. +.PP +In SBC\-3 revision 26 the UNMAP and ANCHOR bits were added to the +WRITE SAME (10) command. Since the UNMAP bit has been in WRITE SAME (16) +and WRITE SAME (32) since SBC\-3 revision 18, the lower of the two (i.e. +WRITE SAME (16)) is the default when the \fI\-\-unmap\fR option is given. +To send WRITE SAME (10) use the \fI\-\-10\fR option. +.PP +.B Take care: +The WRITE SAME(10, 16 and 32) commands may interpret a \fINUM\fR of zero as +write to the end of \fIDEVICE\fR. This utility defaults \fINUM\fR to 1 . +The WRITE SAME commands have no IMMED bit so if \fINUM\fR is large (or +zero) then an invocation of this utility could take a long time, potentially +as long as a FORMAT UNIT command. In such situations the command timeout +value \fITO\fR may need to be increased from its default value of 60 +seconds. In SBC\-3 revision 26 the WSNZ (write same no zero) bit was added +to the Block Limits VPD page [0xB0]. If set the WRITE SAME commands will not +accept a \fINUM\fR of zero. The same SBC\-3 revision added the "Maximum +Write Same Length" field to the Block Limits VPD page. +.PP +The Logical Block Provisioning VPD page [0xB2] contains the LBPWS and +LBPWS10 bits. If LBPWS is set then WRITE SAME (16) supports the UNMAP bit. +If LBPWS10 is set then WRITE SAME (10) supports the UNMAP bit. If either +LBPWS or LBPWS10 is set and the WRITE SAME (32) is supported then WRITE +SAME (32) supports the UNMAP bit. +.PP +As a precaution against an accidental 'sg_write_same /dev/sda' (for example) +overwriting LBA 0 on /dev/sda with zeros, at least one of the +\fI\-\-in=IF\fR, \fI\-\-lba=LBA\fR or \fI\-\-num=NUM\fR options must be +given. Obviously this utility can destroy a lot of user data so check the +options carefully. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-R\fR, \fB\-\-10\fR +send a SCSI WRITE SAME (10) command to \fIDEVICE\fR. The ability to +set the \fI\-\-unmap\fR (and \fI\-\-anchor\fR) options to this command +was added in SBC\-3 revision 26. +.TP +\fB\-S\fR, \fB\-\-16\fR +send a SCSI WRITE SAME (16) command to \fIDEVICE\fR. +.TP +\fB\-T\fR, \fB\-\-32\fR +send a SCSI WRITE SAME (32) command to \fIDEVICE\fR. +.TP +\fB\-a\fR, \fB\-\-anchor\fR +sets the ANCHOR bit in the cdb. Introduced in SBC\-3 revision 22. +That draft requires the \fI\-\-unmap\fR option to also be specified. +.TP +\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR +sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero. +\fIGN\fR should be a value between 0 and 63. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR +read data (binary) from file named \fIIF\fR and use it as the data\-out +buffer for the SCSI WRITE SAME command. The length of the data\-out buffer +is \fI\-\-xferlen=LEN\fR or, if that is not given, the length of the \fIIF\fR +file. If \fIIF\fR is "\-" then stdin is read. If this option is not given +then 0x00 bytes are used as fill with the length of the data\-out buffer +obtained from \fI\-\-xferlen=LEN\fR or by calling READ CAPACITY(16 or 10). +If the response to READ CAPACITY(16) has the PROT_EN bit set then data\- +out buffer size is modified accordingly with the last 8 bytes set to 0xff. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the logical block address to start the WRITE SAME command. +Defaults to lba 0 which is a dangerous block to overwrite on a disk that is +in use. Assumed to be in decimal unless prefixed with '0x' or has a +trailing 'h'. +.TP +\fB\-L\fR, \fB\-\-lbdata\fR +sets the LBDATA bit in the WRITE SAME cdb. This bit was made obsolete in +sbc3r32 in September 2012. +.TP +\fB\-N\fR, \fB\-\-ndob\fR +sets the NDOB bit in the WRITE SAME (16 and 32 byte) commands. NDOB stands +for No Data\-Out Buffer. Default is to clear this bit. When this option +is given then \fI\-\-in=IF\fR is not allowed and \fI\-\-xferlen=LEN\fR can +only be given if \fILEN\fR is 0 . +.TP +\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR +where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to write the +data\-out buffer to. The default value for \fINUM\fR is 1. The value +corresponds to the 'Number of logical blocks' field in the WRITE SAME cdb. +.br +Note that a value of 0 in \fINUM\fR may be interpreted as write the data\-out +buffer on every block starting at \fILBA\fR to the end of the \fIDEVICE\fR. +If the WSNZ bit (introduced in sbc3r26, January 2011) in the Block Limits VPD +page is set then the value of 0 is disallowed, yielding an Invalid request +sense key. +.TP +\fB\-P\fR, \fB\-\-pbdata\fR +sets the PBDATA bit in the WRITE SAME cdb. This bit was made obsolete in +sbc3r32 in September 2012. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR +where \fITO\fR is the command timeout value in seconds. The default value is +60 seconds. If \fINUM\fR is large (or zero) a WRITE SAME command may require +considerably more time than 60 seconds to complete. +.TP +\fB\-U\fR, \fB\-\-unmap\fR +sets the UNMAP bit in the WRITE SAME(10, 16 and 32) cdb. See UNMAP section +below. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). +.TP +\fB\-V\fR, \fB\-\-version\fR +output version string then exit. +.TP +\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWPR\fR +sets the "Write protect" field in the WRITE SAME cdb to \fIWPR\fR. The +default value is zero. \fIWPR\fR should be a value between 0 and 7. +When \fIWPR\fR is 1 or greater, and the disk's protection type is 1 or +greater, then 8 extra bytes of protection information are expected or +generated (to place in the command's data\-out buffer). +.TP +\fB\-x\fR, \fB\-\-xferlen\fR=\fILEN\fR +where \fILEN\fR is the data\-out buffer length. Defaults to the length of +the \fIIF\fR file or, if that is not given, then the READ CAPACITY(16 or 10) +command is used to find the 'Logical block length in bytes'. That figure +may be increased by 8 bytes if the \fIDEVICE\fR's protection type is 1 or +greater and the WRPROTECT field (see \fI\-\-wrprotect=WPR\fR) is 1 or +greater. If both this option and the \fIIF\fR option are given and +\fILEN\fR exceeds the length of the \fIIF\fR file then \fILEN\fR is the +data\-out buffer length with zeros used as pad bytes. +.SH UNMAP +Logical block provisioning is a new term introduced in SBC\-3 revision 25 +for the ability to mark blocks as unused. For large storage arrays, it is a +way to provision less physical storage than the READ CAPACITY command reports +is available, potentially allocating more physical storage when WRITE +commands require it. For flash memory (e.g. SSD drives) it is a way of +potentially saving power (and perhaps access time) when it is known large +sections (or almost all) of the flash memory is not in use. SSDs need wear +levelling algorithms to have acceptable endurance and typically over +provision to simplify those algorithms; hence they typically contain more +physical flash storage than their logical size would dictate. +.PP +Support for logical block provisioning is indicated by the LBPME bit being +set in the READ CAPACITY(16) command response (see the sg_readcap utility). +That implies at least one of the UNMAP or WRITE SAME(16) commands is +implemented. If the UNMAP command is implemented then +the "Maximum unmap LBA count" and "Maximum unmap block descriptor count" +fields in the Block Limits VPD page should both be greater than zero. The +READ CAPACITY(16) command response also contains a LBPRZ bit which if set +means that if unmapped blocks are read then zeros will be returned for the +data (and if protection information is active, 0xff bytes are returned for +that). In SBC\-3 revision 27 the same LBPRZ bit was added to the Logical +Block Provisioning VPD page. +.PP +In SBC\-3 revision 25 the LBPU and ANC_SUP bits where added to the +Logical Block Provisioning VPD page. When LBPU is set it indicates that +the device supports the UNMAP command (see the sg_unmap utility). When the +ANC_SUP bit is set it indicates the device supports anchored LBAs. +.PP +When the UNMAP bit is set in the cdb then the data\-out buffer is also sent. +Additionally the data section of that data\-out buffer should be full of 0x0 +bytes while the data protection block, 8 bytes at the end if present, should +be set to 0xff bytes. If these conditions are not met and the LBPRZ bit is +set then the UNMAP bit is ignored and the data\-out buffer is written to the +\fIDEVICE\fR as if the UNMAP bit was zero. In the absence of the +\fI\-\-in=IF\fR option, this utility will attempt build a data\-out buffer +that meets the requirements for the UNMAP bit in the cdb to be acted on by +the \fIDEVICE\fR. +.PP +Logical blocks may also be unmapped by the SCSI UNMAP and FORMAT UNIT +commands (see the sg_unmap and sg_format utilities). +.PP +The unmap capability in SCSI is closely related to the ATA DATA SET +MANAGEMENT command with the "Trim" bit set. That ATA trim capability does +not interact well with SATA command queueing known as NCQ. T13 have +introduced a new command called the SFQ DATA SET MANAGEMENT command also +with a the "Trim" bit to address that problem. The SCSI WRITE SAME with +the UNMAP bit set and the UNMAP commands do not have any problems with +SCSI queueing. +.SH NOTES +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +In Linux, prior to lk 3.17, the sg driver did not support cdb sizes greater +than 16 bytes. Hence a device node like /dev/sg1 which is associated with +the sg driver would fail with this utility if the \fI\-\-32\fR option was +given (or implied by other options). The bsg driver with device nodes like +/dev/bsg/6:0:0:1 does support cdb sizes greater than 16 bytes since its +introduction in lk 2.6.28 . +.SH EXIT STATUS +The exit status of sg_write_same is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH EXAMPLES +One simple usage is to write blocks of zero from (and including) a given LBA: +.PP + sg_write_same \-\-lba=0x1234 \-\-num=63 /dev/sdc +.PP +Since \fI\-\-xferlen=LEN\fR has not been given, then this utility will +call the READ CAPACITY command on /dev/sdc to determine the number +of bytes in a logical block. Let us assume that is 512 bytes. Since +\fI\-\-in=IF\fR is not given a block of zeros is assumed. So 63 blocks +of zeros (each block containing 512 bytes) will be written from (and +including) LBA 0x1234 . Note that only one block of zeros is passed +to the SCSI WRITE SAME command in the data\-out buffer (as required by +SBC\-3). +.PP +A similar example follows but in this case the blocks +are "unmapped" ("trimmed" in ATA speak) rather than zeroed: +.PP + sg_write_same \-\-unmap \-L 0x1234 \-n 63 /dev/sdc +.PP +Note that if the LBPRZ bit in the READ CAPACITY(16) response is set (i.e. +LPPRZ is an acronym for logical block provisioning read zeros) then these +two examples do the same thing, at least seen from the point of view of +subsequent reads. +.PP +This utility can also be used to write protection information (PI) on disks +formatted with a protection type greater than zero. PI is 8 bytes of extra +data appended to the user data of a logical block: the first two bytes are a +CRC (the "guard"), the next two bytes are the "application tag" and the last +four bytes are the "reference tag". With protection types 1 and 2 if the +application tag is 0xffff then the guard should not be checked (against the +user data). +.PP +In this example we assume the logical block size (of the user data) is 512 +bytes and the disk has been formatted with protection type 1. Since we are +going to modify LBA 2468 then we take a copy of it first: +.PP + dd if=/dev/sdb skip=2468 bs=512 of=2468.bin count=1 +.PP +The following command line sets the user data to zeros and the PI to 8 +0xFF bytes on LBA 2468: +.PP + sg_write_same \-\-lba=2468 /dev/sdb +.PP +Reading back that block should be successful because the application tag +is 0xffff which suppresses the guard (CRC) check (which would otherwise be +wrong): +.PP + dd if=/dev/sdb skip=2468 bs=512 of=/dev/null count=1 +.PP +Now an attempt is made to create a binary file with zeros in the user data, +0x0000 in the application tag and 0xff bytes in the other two PI fields. It +is awkward to create 0xff bytes in a file (in Unix) as the "tr" command +below shows: +.PP + dd if=/dev/zero bs=1 count=512 of=ud.bin +.br + tr "\\000" "\\377" < /dev/zero | dd bs=1 of=ff_s.bin count=8 +.br + cat ud.bin ff_s.bin > lb.bin +.br + dd if=/dev/zero bs=1 count=2 seek=514 conv=notrunc of=lb.bin +.PP +The resulting file can be viewed with 'hexdump \-C lb.bin' and should +contain 520 bytes. Now that file can be written to LBA 2468 as follows: +.PP + sg_write_same \-\-lba=2468 wrprotect=3 \-\-in=lb.bin /dev/sdb +.PP +Note the \fI\-\-wrprotect=3\fR rather than being set to 1, since we want +the WRITE SAME command to succeed even though the PI data now indicates +the user data is corrupted. When an attempt is made to read the LBA, an +error should occur: +.PP + dd if=/dev/sdb skip=2468 bs=512 of=/dev/null count=1 +.PP +dd errors are not very expressive, if dmesg is checked there should be +a line something like this: "[sdb] Add. Sense: Logical block guard check +failed". The block can be corrected by doing a "sg_write_same \-\-lba=1234 +/dev/sdb" again or restoring the original contents of that LBA: +.PP + dd if=2468.bin bs=512 seek=2468 of=/dev/sdb conv=notrunc count=1 +.PP +Hopefully the dd command would never try to truncate the output file when +it is a block device. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2009\-2017 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_format,sg_get_lba_status,sg_readcap,sg_vpd,sg_unmap(sg3_utils) diff --git a/doc/sg_write_verify.8 b/doc/sg_write_verify.8 new file mode 100644 index 0000000..1ae27cb --- /dev/null +++ b/doc/sg_write_verify.8 @@ -0,0 +1,191 @@ +.TH "WRITE AND VERIFY" "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_write_and_verify \- send the SCSI WRITE AND VERIFY command +.SH SYNOPSIS +.B sg_write_verify +[\fI\-\-16\fR] [\fI\-\-bytchk=BC\fR] [\fI\-\-dpo\fR] [\fI\-\-group=GN\fR] +[\fI\-\-help\fR] [\fI\-\-ilen=ILEN\fR] [\fI\-\-in=IF\fR] \fI\-\-lba=LBA\fR +[\fI\-\-num=NUM\fR] [\fI\-\-repeat\fR] [\fI\-\-timeout=TO\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-wrprotect=WP\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +Send a SCSI WRITE AND VERIFY (10) or (16) command to \fIDEVICE\fR. The +data to be written is read from the \fIIF\fR file or, in its absence, a +buffer full of 0xff bytes is used. The length of the data\-out buffer sent +with the command is \fIILEN\fR bytes or, if that is not given, then it is +the length of the \fIIF\fR file. +.PP +The write operation is to the \fIDEVICE\fR's medium (optionally to its cache) +starting at logical block address \fILBA\fR for \fINUM\fR logical blocks. +After the write to medium is performed a verify operation takes place which +may viewed as a medium read (with appropriate checks) but without the data +being returned. Additionally, if \fIBS\fR is set to one, the data read back +from the medium in the verify operation is compared to the original data\-out +buffer. +.PP +The relationship between the number of logical blocks to be written (i.e. +\fINUM\fR) and the length (in bytes) of the data\-out buffer (i.e. +\fIILEN\fR) may be simply found by multiplying the former by the logical +block size. However if the \fIDEVICE\fR has protection information (PI) +then it becomes a bit more complicated. Hence the calculation is left to +the user with the default \fIILEN\fR, in the absence of the \fIIF\fR file, +being set to \fINUM\fR * 512. +.PP +For sending large amounts of data to contiguous logical blocks, a single +WRITE AND VERIFY command may not be appropriate (e.g. due to operating +system limitations). In such cases see the REPEAT section below. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long option name. +.TP +\fB\-S\fR, \fB\-\-16\fR +Send a WRITE AND VERIFY(16) command. The default is to send a WRITE AND +VERIFY(10) command unless \fILBA\fR or \fINUM\fR are too large for the +10 byte variant. +.TP +\fB\-b\fR, \fB\-\-bytchk\fR=\fIBC\fR +where \fIBC\fR is the value to place in the command's BYTCHK field. Values +between 0 and 3 (inclusive) are accepted. The default is value is 0 which +implies only a write to the medium then a verify operation are performed. The +only other value T10 defines currently is 1 which does performs an additional +comparison between the data\-out buffer that was used by the write operation +and the contents of the logical blocks read back from the medium. +.TP +\fB\-d\fR, \fB\-\-dpo\fR +Set the DPO (disable page out) bit in the command. The default is to leave +it clear. +.TP +\fB\-g\fR, \fB\-\-group\fR=\fIGN\fR +where \fIGN\fR is the value to place in the command's GROUP NUMBER field. +Values between 0 and 31 (inclusive) are accepted. The default is value is 0. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-I\fR, \fB\-\-ilen\fR=\fIILEN\fR +where \fIILEN\fR is the number of bytes that will be placed in the data\-out +buffer. If the \fIIF\fR file is given then no more than \fIILEN\fR bytes +are read from that file. If the \fIIF\fR file does not contain \fIILEN\fR +bytes then an error is reported. If the \fIIF\fR file is not given then +a data\-out buffer with \fIILEN\fR bytes of 0xff is sent. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR +read data (binary) from file named \fIIF\fR. If \fIIF\fR is "\-" then +stdin is used. This data will become the data\-out buffer and will be written +to the \fIDEVICE\fR's medium. If \fIBC\fR is 1 then that data\-out buffer +will be held until after the verify operation and compared to the data read +back from the medium. +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR +where \fILBA\fR is the logical block address to start the write to medium. +Assumed to be in decimal unless prefixed with '0x' or has a trailing 'h'. +Must be provided. +.TP +\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR +where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to write +to the medium. The default value for \fINUM\fR is 1. +.TP +\fB\-R\fR, \fB\-\-repeat\fR +this option will continue to do WRITE AND VERIFY commands until the \fIIF\fR +file is exhausted. This option requires both the \fI\-\-ilen=ILEN\fR and +\fI\-\-in=IF\fR options to be given. Each command starts at the next logical +block address and is for no more than \fINUM\fR blocks. The last command may +be shorter with the number of blocks scaled as required. If there are +residue bytes a warning is sent to stderr. See the REPEAT section. +.TP +\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR +where \fITO\fR is the command timeout value in seconds. The default value is +60 seconds. If \fINUM\fR is large then command may require considerably more +time than 60 seconds to complete. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). +.TP +\fB\-V\fR, \fB\-\-version\fR +output version string then exit. +.TP +\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWP\fR +set the WRPROTECT field in the cdb to \fIWP\fR. The default value is 0 which +implies no protection information is sent (along with the user data) in the +data\-out buffer. +.SH REPEAT +For data sizes around a megabyte and larger, it may be appropriate to send +multiple SCSI WRITE AND VERIFY commands due to operating system +limitations (e.g. pass\-through SCSI interfaces often limit the amount +of data that can be passed with a SCSI command). With this utility the +mechanism for doing that is the \fI\-\-repeat\fR option. +.PP +In this mode the \fI\-\-ilen=ILEN\fR and \fI\-\-in=IF\fR options must be +given. The \fIILEN\fR and \fINUM\fR values are treated as a per SCSI command +parameters. Up to \fIILEN\fR bytes will be read from the \fIIF\fR file +continually until it is exhausted. If the \fIIF\fR file is stdin, reading +continues until an EOF is detected. The data read from each iteration becomes +the data\-out buffer for a new WRITE AND VERIFY command. +.PP +The last read from the file (or stdin) may read less than \fIILEN\fR bytes +in which case the number of logical blocks sent to the last WRITE AND VERIFY +is scaled back accordingly. If there is a residual number of bytes left +after that scaling then that is reported to stderr. +.PP +If an error occurs then that is reported to stderr and via the exit status +and the utility stops at that point. +.SH NOTES +Other SCSI WRITE commands have a Force Unit Access (FUA) bit but that is +set (implicitly) by WRITE AND VERIFY commands hence there is no option to set +it. The data\-out buffer may still additionally be placed in the +\fIDEVICE\fR's cache and setting the DPO bit is a hint not to do that. +.PP +Normal SCSI WRITEs can be done with the ddpt and the sg_dd utilities. The +SCSI WRITE SAME command can be done with the sg_write_same utility while +the SCSI COMPARE AND WRITE command (sg_compare_and_write utility) offers +a "test and set" facility. +.PP +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.SH EXIT STATUS +The exit status of sg_write_verify is 0 when it is successful. If the verify +operation fails that is typically indicated with a medium error which leads +to an exit status of 3. +.PP +If \fIBC\fR is set to 1 and the comparison it causes fails this utility will +indicate the miscompare with an exit status of 14. For other exit status +values see the EXIT STATUS section in the sg3_utils(8) man page. +.SH EXAMPLES +To start with, a simple example: write 1 block of data held in file t.bin +that is 512 bytes long then write that block to LBA 0x1234 on /dev/sg4 . +.PP + # sg_write_verify \-\-lba=0x1234 \-\-in=t.bin /dev/sg4 +.PP +Since '\-\-num=' is not given then it defaults to 1. Further the \fIILEN\fR +value is obtained from the file size of t.bin . To additionally do a +data\-out comparison to the read back data: +.PP + # sg_write_verify \-l 0x1234 \-i t.bin --bytchk=1 /dev/sg4 +.PP +The ddpt command can do copies between SCSI devices using READ and WRITE +commands. However, currently it has no facility to promote those WRITES +to WRITE AND VERIFY commands. Using a pipe, that could be done like this: +.PP + # ddpt if=/dev/sg2 bs=512 bpt=8 count=11 of=\- | +.br +sg_write_verify \-\-in=\- \-l 0x567 \-n 8 \-\-ilen=4096 \-\-repeat /dev/sg4 +.PP +Both ddpt and sg_write_verify are configured for segments of 8 512 byte +logical blocks. Since 11 logical blocks are read then first 8 logical blocks +are copied followed by a copy of the remaining 3 blocks. Since it is assumed +that there is no protection information then the data\-in and data\-out +buffers will be 4096 bytes each. For sg_write_verify this needs to be stated +explicitly with the \-\-ilen=4096 option. +.SH AUTHORS +Bruno Goncalves and Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2014\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B ddpt(in a package of that name), sg_compare_and_write(8), sg_dd(8), +.B sg_write_same(8) diff --git a/doc/sg_write_x.8 b/doc/sg_write_x.8 new file mode 100644 index 0000000..122d950 --- /dev/null +++ b/doc/sg_write_x.8 @@ -0,0 +1,595 @@ +.TH SG_WRITE_X "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_write_x \- SCSI WRITE normal/ATOMIC/SAME/SCATTERED/STREAM, ORWRITE commands +.SH SYNOPSIS +.B sg_write_x +[\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-app\-tag=AT\fR] [\fI\-\-atomic=AB\fR] +[\fI\-\-bmop=OP,PGP\fR] [\fI\-\-bs=BS\fR] [\fI\-\-combined=DOF\fR] +[\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR] [\fI\-\-dry\-run\fR] [\fI\-\-fua\fR] +[\fI\-\-generation=EOG,NOG\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR] +\fI\-\-in=IF\fR [\fI\-\-lba=LBA[,LBA...]\fR] [\fI\-\-normal\fR] +[\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-or\fR] +[\fI\-\-quiet\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-same=NDOB\fR] +[\fI\-\-scat\-file=SF\fR] [\fI\-\-scat\-raw\fR] [\fI\-\-scattered=RD\fR] +[\fI\-\-stream=ID\fR] [\fI\-\-strict\fR] [\fI\-\-tag\-mask=TM\fR] +[\fI\-\-timeout=TO\fR] [\fI\-\-unmap=U_A\fR] [\fI\-\-verbose\fR] +[\fI\-\-version\fR] [\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR +.PP +Synopsis per supported command: +.PP +.B sg_write_x +\fI\-\-normal\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR] +[\fI\-\-app\-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR] +[\fI\-\-fua\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR] +[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR] +[\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR] +\fIDEVICE\fR +.PP +.B sg_write_x +\fI\-\-or\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR] +[\fI\-\-bmop=OP,PGP\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR] +[\fI\-\-generation=EOG,NOG\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] +[\fI\-\-num=NUM\fR] [\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-strict\fR] +[\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=OPR\fR] \fIDEVICE\fR +.PP +.B sg_write_x +\fI\-\-atomic=AB\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR] +[\fI\-\-app-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR] +[\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR] +[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR] +[\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR +.PP +.B sg_write_x +\fI\-\-same=NDOB\fR [\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-app-tag=AT\fR] +[\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR] [\fI\-\-grpnum=GN\fR] +[\fI\-\-in=IF\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR] +[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR] +[\fI\-\-timeout=TO\fR] [\fI\-\-unmap=U_A\fR] +[\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR +.PP +.B sg_write_x +\fI\-\-scattered=RD\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR] +[\fI\-\-app-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR] +[\fI\-\-fua\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA[,LBA...]\fR] +[\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR] +[\fI\-\-ref\-tag=RT\fR] [\fI\-\-scat\-file=SF\fR] [\fI\-\-scat\-raw\fR] +[\fI\-\-strict\fR] [\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR] +[\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR +.PP +.B sg_write_x +\fI\-\-stream=ID\fR \fI\-\-in=IF\fR [\fI\-\-16\fR] [\fI\-\-32\fR] +[\fI\-\-app-tag=AT\fR] [\fI\-\-bs=BS\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR] +[\fI\-\-grpnum=GN\fR] [\fI\-\-lba=LBA\fR] [\fI\-\-num=NUM\fR] +[\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-strict\fR] +[\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR] +\fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +This utility will send one of six SCSI commands, all associated with writing +data to the given \fIDEVICE\fR. They are a "normal" WRITE, ORWRITE, WRITE +ATOMIC, WRITE SAME, WRITE SCATTERED or WRITE STREAM. This utility supports +the 16 and 32 byte variants of all six commands. Hence some closely related +commands are not supported (e.g. WRITE(10)). All 32 byte variants, apart from +ORWRITE(32), require the \fIDEVICE\fR to be formatted with type 1, 2 or 3 +Protection Information (PI), making all logical blocks 8 bytes (or a multiple +of 8 bytes) longer on the media. +.PP +The command line interface is a little crowded with over thirty options. Hence +the SYNOPSIS, after listing all the (long) options, lists those applicable +to each supported command. For each command synopsis, the option that selects +the SCSI command is shown first followed by any required options. If no +command option is given then a "normal" WRITE is assumed. Even though the +\fI\-\-scat\-file=SF\fR option can be given for every command, it is only +shown for WRITE SCATTERED where it is most useful. If the +\fI\-\-scat\-file=SF\fR option is given then neither the +\fI\-\-lba=LBA[,LBA...]\fR nor the \fI\-\-num=NUM[,NUM...]\fR options +should be given. Only the first item of the \fI\-\-lba=LBA[,LBA...]\fR and +the \fI\-\-num=NUM[,NUM...]\fR options (or first pair (or quintet) from the +\fI\-\-scat\-file=SF\fR option) is used for all but the WRITE SCATTERED +command. All commands can take \fI\-\-dry\-run\fR and \fI\-\-verbose\fR in +addition to those shown in the SYNOPSIS. +.PP +The logical block size in bytes can be given explicitly with the +\fI\-\-bs=BS\fR option, as long as \fIBS\fR is greater than zero. It +is typically a power of two, 512 or greater. If the \fI\-\-bs=BS\fR option +is not given or \fIBS\fR is zero then the SCSI READ CAPACITY command is +used to find the logical block size. First the READ CAPACITY(16) command is +tried and if successful the logical block size in the response is typically +used as the actual block size for this utility. The exception is when +PROT_EN is set in the response and the \fI\-\-wrprotect=WPR\fR option is +given and non\-zero; in which case 8 (bytes) is added to the logical block +size to yield the actual block size used by this utility. If READ +CAPACITY(16) fails then READ CAPACITY(10) is tried and if that works then +the logical block size in the response is used as the actual block size. +.PP +The number of bytes this utility will attempt to read from the file named by +\fIIF\fR is the product of the actual block size and the +number_of_blocks (\fINUM\fR or the sum of \fINUM\fR arguments). If less bytes +are read from the file \fIIF\fR and the \fI\-\-strict\fR option is given then +this utility exits with an exit status of SG_LIB_FILE_ERROR. If less bytes +are read from the file \fIIF\fR and the \fI\-\-strict\fR option is not +given then bytes of zero are substituted for the "missing" bytes and this +utility continues. +.PP +Attempts to write multi megabyte data with a single command are likely to fail +for one of several reasons. First the operating system might object to +allocating a buffer that large. Next the SCSI pass\-through usually limits +data blocks to a few megabytes or less. Finally the storage device might +have a limited amount of RAM to support a write operation such as atomic (as +it may need to roll back). The storage device can inform the application +client of its limitations via the block limits VPD page (0xb0), with the +maximum atomic transfer length field amongst others. +.PP +A degenerate LBA (Logical Block Address) range descriptor with no PI has +an LBA and NUM of zero. A degenerate LBA range descriptor with PI +additionally has its RT, AT and TM fields set to zero (note: that is not +the default values for RT, AT and TM). They are degenerate in the sense +that they are indistinguishable from a pad of zeros that follow the scatter +list in the data\-out buffer. SBC\-4 makes clear that a degenerate LBA +range descriptor is valid. This may become an issue if \fIRD\fR given in the +\fI\-\-scattered=RD\fR option has the value 0. In this case the logic may +need to scan the user provided data to calculate the number of LBA +range descriptors which is required by the WRITE SCATTERED cdb. In the +absence of other information the logic will take a degenerate LBA range +descriptor as a terminator of the scatter list. +.PP +The current reference for these commands is draft SBC\-4 (T10/BSR INCITS +506) revision 15 dated 9 November 2017. All six SCSI commands are described +in that document. WRITE ATOMIC was added in SBC\-4 revision 3; WRITE STREAM +was added in SBC\-4 revision 7; WRITE SCATTERED was added in SBC\-4 +revision 11 while the others are in the SBC\-3 standard. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +The options are arranged in alphabetical order based on the long +option name. +.TP +\fB\-6\fR, \fB\-\-16\fR +send the 16 byte cdb variant of the selected SCSI command. If no command +is selected then the (normal) SCSI WRITE(16) command is sent. If neither +this option nor the \fI\-\-32\fR option is given then this option is +assumed. +.TP +\fB\-3\fR, \fB\-\-32\fR +send the 32 byte cdb variant of the selected SCSI command. If no command +is selected then the (normal) SCSI WRITE(32) command is sent. If neither +this option nor the \fI\-\-16\fR option is given then then the +\fI\-\-16\fR option is assumed. If both this option and the \fI\-\-16\fR +option are given then this option takes precedence. Note that apart +from ORWRITE(32) all other 32 byte cdb variants require a \fIDEVICE\fR +formatted with type 1, 2 or 3 protection information. +.TP +\fB\-a\fR, \fB\-\-app\-tag\fR=\fIAT\fR +where \fIAT\fR is the "expected logical block application tag" field found in +most of the 32 byte cdb variants (the exception is ORWRITE(32)). \fIAT\fR is +a 16 bit field which means the maximum value is 0xffff. The default value +is 0xffff . +.TP +\fB\-A\fR, \fB\-\-atomic\fR=\fIAB\fR +selects the WRITE ATOMIC command and \fIAB\fR is placed in the Atomic +Boundary field of its cdb. It is a 16 bit field so the maximum value +is 0xffff. If unsure what value to set, try 0 which will attempt to +write the whole data\-out buffer in a single atomic operation. +.TP +\fB\-B\fR, \fB\-\-bmop\fR=\fIOP,PGP\fR +where \fIOP\fR and \fIPGP\fR are the values to be placed in ORWRITE(32)'s +BMOP and 'Previous Generation Processing' fields respectively. BMOP is +a 3 bit field (ranges from 0 to 7) and PGP is a 4 bit field (ranges from +0 to 15). Both fields default to 0. +.TP +\fB\-b\fR, \fB\-\-bs\fR=\fIBS\fR +where \fIBS\fR is the logical block size or the actual block size which +will be slightly bigger. The default value is zero. If this option +is not given or is given with a \fIBS\fR of zero then the SCSI READ +CAPACITY(16) command is sent to \fIDEVICE\fR. If that fails then the READ +CAPACITY(10) command is sent. The logical and actual block size will be +derived from the response of the READ CAPACITY command. +.br +This section assumes \fIBS\fR is greater than zero. If \fIBS\fR is less than +512 (bytes) or not a multiple of 8, a warning is issued and the utility +continues unless the \fI\-\-strict\fR option is also given. If \fIBS\fR +is a power of two (e.g. 512) then the logical and actual block size is +set to \fIBS\fR (e.g. 512). If \fIBS\fR is not a power of two (e.g. 520) +then the logical block size is set to the closest power of two less than +\fIBS\fR (e.g. 512) and the actual block size is set to \fIBS\fR (e.g. +520). +.br +If the logical and actual block size are different then a later check +will reduce the actual block size back to the logical block size unless +\fI\-\-wrprotect=WPR\fR is greater than zero. +.TP +\fB\-c\fR, \fB\-\-combined\fR=\fIDOF\fR +This option only applies to WRITE SCATTERED and assumes the whole data\-out +buffer can be read from \fIIF\fR given by the \fI\-\-in=IF\fR option. The +whole data\-out buffer is the parameter list header, followed by zero or more +LBA range descriptors, optionally followed by some pad bytes and then the +data to be written to the media. If the \fI\-\-lba=LBA[,LBA...]\fR, +\fI\-\-num=NUM[,NUM...]\fR or \fI\-\-scat\-file=SF\fR options are also given +then an error is generated. The \fIDOF\fR argument should be the value +suitable for the 'Logical Block Data Offset' field in the WRITE SCATTERED +cdb. This is the offset in the data\-out buffer where the data to write +to the media commences. The unit of that field is the actual block size +which is the logical block size plus a multiple of 8, if protection +information (PI) is being sent. When \fIWPR\fR (from \fI\-\-wrprotect=WPR\fR) +is greater than zero then PI is expected. SBC\-4 revision 15 does not state +it but it would appear that a \fIDOF\fR value of 0 is invalid. It is +suggested that this option be used with the \fI\-\-strict\fR option while +experimenting as random or incorrect data fed in via the \fI\-\-in=IF\fR +option could write a lot of "interesting" data all over the \fIDEVICE\fR. +If \fIDOF\fR is given as 0 the utility will scan the data in \fIIF\fR until +\fIRD\fR LBA range descriptors are found; or if \fIRD\fR is also 0 until a +degenerate LBA range descriptor is found. +.TP +\fB\-D\fR, \fB\-\-dld\fR=\fIDLD\fR +where \fIDLD\fR is the duration limits descriptor spread across 3 bits in +the SCSI WRITE(16) and the WRITE SCATTERED(16) cdbs. \fIDLD\fR is between 0 +to 7 inclusive with a default of zero. The DLD0 field in WRITE(16) and WRITE +SCATTERED(16) is set if (0x1 & \fIDLD\fR) is non\-zero. The DLD1 field in +both cdbs is set if (0x2 & \fIDLD\fR) is non\-zero. The DLD2 field in both +cdbs is set if (0x4 & \fIDLD\fR) is non\-zero. +.TP +\fB\-d\fR, \fB\-\-dpo\fR +if this option is given then the DPO (disable page out) bit field in the +cdb is set. The default is to clear this bit field. Applies to all +commands supported by thus utility except WRITE SAME. +.TP +\fB\-x\fR, \fB\-\-dry\-run\fR +this option exits (with a status of 0) just before it would otherwise send +the selected SCSI write command. It may still send a SCSI READ CAPACITY +command (16 byte variant and perhaps 10 byte variant as well) so the +\fIDEVICE\fR is still required. It reads the data in and processes it if the +\fI\-\-in=IF\fR and/or the \fI\-\-scat\-file=SF\fR options are given. All +command line processing and sanity checks (e.g. if the \fI\-\-strict\fR +option is given) will be performed and if there is an error then there will +be a non zero exit status value. +.br +If this option is given twice (e.g. \-xx) then instead of performing the +selected write SCSI command, the data\-out buffer is written to a file +called sg_write_x.bin . If it doesn't exist then that file is created in +the current directory and is truncated if it previously did exist with +longer contents. The data\-out buffer is written in binary with some +information about it written to stdout. For writes other than scattered +the filename and its length in bytes is output to stdout. For write +scattered additionally its number of LBA range descriptors and its +logical block data offset written to stdout. +.TP +\fB\-f\fR, \fB\-\-fua\fR +if this option is given then the FUA (force unit access) bit field in the +cdb is set. The default is to clear this bit field. Applies to all +commands supported by thus utility except WRITE SAME. +.TP +\fB\-G\fR, \fB\-\-generation\fR=\fIEOG,NOG\fR +the arguments for this option are used by the ORWITE(32) command only. +\fIEOG\fR is placed in the "Expected ORWgeneration" field while \fINOG\fR +is placed in the "New ORWgeneration" field. Both are 32 bits long and +default to zero. +.TP +\fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR +sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero. +\fIGN\fR should be a value between 0 and 63. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. Use multiple times for more help. +Currently '\-h' to '\-hhhh' provide different output. +.TP +\fB\-i\fR, \fB\-\-in\fR=\fIIF\fR +read data (in binary) from a file named \fIIF\fR in a single OS system +call (in Unix: read(2)). That data is placed in a continuous buffer and then +used as the data\-out buffer for all SCSI write commands apart from WRITE +SCATTERED(16 or 32) which may include other data in the data\-out buffer. +For WRITE SCATTERED (16 or 32) the data\-out buffer is made up of 3 or 4 +components in this order: a parameter list header (32 zero bytes); zero or +more LBA range descriptors, optionally some pad bytes (zeros) and then data +to write to the media. For WRITE SCATTERED \fIIF\fR only provides the data +to write to the media unless \fI\-\-combined=DOF\fR is given. When the +\fI\-\-combined=DOF\fR option is given \fIIF\fR contains all components of +the WRITE SCATTERED data\-out buffer in binary. The data read from \fIIF\fR +starts from byte offset \fIOFF\fR which defaults to zero and no more than +\fIDLEN\fR bytes are read from that point (i.e. from the file byte offset +\fIOFF\fR). If \fIDLEN\fR is zero or not given the rest of the file \fIIF\fR +is read. This option is mandatory apart from when \-\-same=1 is given (that +sets the NDOB bit which stands for "No Data Out Buffer"). In Unix based +OSes, any number of zeros can be produced by using the /dev/zero device file. +.br +\fIIF\fR may be "\-" which is taken as stdin. In this case the +\fI\-\-offset=OFF,DLEN\fR can be given with \fIOFF\fR set to 0 and +\fILEN\fR set to a non\-zero value, preferably a multiple of the actual block +size. The utility can also deduce how long the \fIIF\fR should be from +\fINUM\fR (or the sum of them in the case of a scatter list). +.TP +\fB\-l\fR, \fB\-\-lba\fR=\fILBA[,LBA...]\fR +where the argument is a single Logical Block Address (LBA) or a comma +separated list of \fILBA\fRs each of which is the address of the first block +written by the selected write command. Only the WRITE SCATTERED command +can usefully take more than one \fILBA\fR. Whatever number of \fILBA\fRs is +given, there needs to be an equal number of \fINUM\fRs given to the +\fI\-\-num=NUM[,NUM...]\fR option. The first given \fILBA\fR joins with the +first given \fINUM\fR to form the first LBA range descriptor (which T10 +number from zero in SBC\-4). The second \fILBA\fR joins with the second +\fILBA\fR to form the second LBA range descriptor, etc. A more convenient +way to define a large number of LBA range descriptors is with the +\fI\-\-scat\-file=SF\fR option. Defaults to logical block 0 (which could be +dangerous) while \fINUM\fR defaults to 0 which makes the combination harmless. +\fILBA\fR is assumed to be in decimal unless prefixed with '0x' or has a +trailing 'h'. +.TP +\fB\-N\fR, \fB\-\-normal\fR +the choice of a "normal" WRITE (16 or 32) command can be made explicitly +with this option. In the absence of selecting any other command (e.g. +\fI\-\-atomic=AB\fR ), the choice of a "normal" WRITE is the default. +.TP +\fB\-n\fR, \fB\-\-num\fR=\fINUM[,NUM...]\fR +where the argument is a single NUMber of blocks (NUM) or a comma separated +list of \fINUM\fRs that pair with the corresponding entries in the +\fI\-\-lba=LBA[,LBA...]\fR option. If a \fINUM\fR is given and is not +provided by another method (e.g. by using the \fI\-\-scat\-file=SF\fR option) +then it defaults to the number of blocks derived from the size of the file +named by \fIIF\fR (starting at byte offset \fIOFF\fR to the end or the file +or \fIDLEN\fR). Apart from the \fI\-\-combined=DOF\fR option, an LBA must +be explicitly given (either with \fII\-\-lba=LBA\fR or via +\fI\-\-scat\-file=SF\fR), if not \fINUM\fR defaults to 0 as a safety measure. +.TP +\fB\-o\fR, \fB\-\-offset\fR=\fIOFF[,DLEN]\fR +where \fIOFF\fR is the byte offset within the file named \fIIF\fR to start +reading from. The default value of \fIOFF\fR is zero which is the beginning +of file named \fIIF\fR. \fIDLEN\fR is the maximum number of bytes to read, +starting at byte offset \fIOFF\fR, from the file named \fIIF\fR. Less bytes +will be read if an end of file occurs before \fIDLEN\fR is exhausted. If +\fIDLEN\fR is zero or not given then reading from byte offset \fIOFF\fR to +the end of the file named \fIIF\fR is assumed. +.TP +\fB\-O\fR, \fB\-\-or\fR +selects the ORWRITE command. ORWRITE(16) has similar fields to WRITE(16) +apart from the WRPROTECT field being named ORPROTECT with slightly different +semantics and the absence of the 3 DLD bit fields. ORWRITE(32) has four +extra fields that are set with the \fI\-\-bmop=OP,PGP\fR and +\fI\-\-generation=EOG,NOG\fR options. ORWRITE(32) is the only 32 byte cdb +command in this utility that does not require a \fIDEVICE\fR formatted with +type 1, 2 or 3 PI (although it will still work if it is formatted with PI). +.TP +\fB\-Q\fR, \fB\-\-quiet\fR +suppress some informational messages such as the ones associated with +detected errors when this utility is about to exit. The exit status value +is still returned to the operating system when this utility exits. +.TP +\fB\-r\fR, \fB\-\-ref\-tag\fR=\fIRT\fR +where \fIRT\fR is the "expected initial logical block reference tag" field +found in the 32 byte cdb variants of WRITE, WRITE ATOMIC, WRITE SAME and +WRITE STREAM. The field is also found in the WRITE SCATTERED(32) LBA range +descriptors. It is a 32 bit field which means the maximum value is +0xffffffff. The default value is 0xffffffff. +.TP +\fB\-S\fR, \fB\-\-same\fR=\fINDOB\fR +selects the WRITE SAME command with the NDOB field set to \fINDOB\fR which +stands for No Data\-Out Buffer. \fINDOB\fR can take values 0 or 1 (i.e. it +is a single bit field). When \-\-same=1 all options associated with the +data\-out buffer are ignored. +.TP +\fB\-q\fR, \fB\-\-scat\-file\fR=\fISF\fR +where \fISF\fR is the name of an auxiliary file containing the scatter list +for the WRITE SCATTERED command. If the \fI\-\-scat\-raw\fR option is also +given then \fISF\fR is assumed to contain both the parameter list header (32 +bytes of zeros) followed by zero or more LBA range descriptors which are +also 32 bytes long each. These components are as defined by SBC\-4 (i.e. +in binary with integers in big endian format). If the \fI\-\-scat\-raw\fR +option is not given then a file of ACSII hexadecimal is expected as described +in the SCATTERED FILE ASCII FORMAT section below. +.br +If this option is given with the \fI\-\-combined=DOF\fR option then this +utility will exit with a syntax error. \fISF\fR must not be "\-", a way +of stopping the user trying to redirect stdin. +.TP +\fB\-R\fR, \fB\-\-scat\-raw\fR +this option only effects the way that the file named \fISF\fR from the +\fI\-\-scat\-file=SF\fR option for WRITE SCATTERED is interpreted. By +default (i.e. without this option), \fISF\fR is parsed as ASCII hexadecimal +with blank lines and line contents from and including '#' to the end of +line ignored. Hence it can contain comments and other indications. When +this option is given, the file named \fISF\fR is interpreted as binary. +As binary it is assumed to contain 32 bytes of zeros (the WRITE SCATTERED +parameter list header) followed by zero or more LBA range descriptors (which +are 32 bytes each). If the \fI\-\-strict\fR option is given the reserved +field in those two items are checked with any non zero bytes causing an +error. +.TP +\fB\-S\fR, \fB\-\-scattered\fR=\fIRD\fR +selects the WRITE SCATTERED command with \fIRD\fR being the number of LBA +range descriptors that will be placed in the data\-out buffer. If \fIRD\fR +is zero then the logic will try and determine the number of range descriptors +by other means (e.g. by parsing the file named by \fISF\fR, if there is one). +The LBA range descriptors differ between the 16 and 32 byte cdb variants of +WRITE SCATTERED. In the 16 byte cdb variant the 32 byte LBA range descriptor +is made up of an 8 byte LBA, followed by a 4 byte number_of_blocks followed +by 20 bytes of zeros. In the 32 byte variant the LBA and number_of_blocks +are followed by a RT (4 bytes), an AT (2 bytes) and a TM (2 bytes) then +12 bytes of zeros. +.br +This paragraph applies when \fIRD\fR is greater than zero. +If \fIRD\fR is less than the number of LBA range descriptors built from +command line options, from the \fI\-\-scat\-file=SF\fR option or +decoded from \fIIF\fR (when the \fI\-\-combined=DOF\fR option is given) +then \fIRD\fR takes precedence; so \fIRD\fR is placed in the "Number of +LBA Range Descriptors" field in the cdb. If \fIRD\fR is greater than +the number of LBA range descriptors found from the provided data and +options, then an error is generated. +.TP +\fB\-T\fR, \fB\-\-stream\fR=\fIID\fR +selects the WRITE STREAM command with the STR_ID field set to \fIID\fR. +\fIID\fR can take values from 0 to 0xffff (i.e. it is a 16 bit field). +.TP +\fB\-s\fR, \fB\-\-strict\fR +when this option is present, more things (e.g. that reserved fields contain +zeros) and any irregularities will terminate the utility with a message to +stderr and an indicative exit status. While experimenting with these commands, +especially WRITE SCATTERED, it is recommended to use this option. +.TP +\fB\-t\fR, \fB\-\-tag\-mask\fR=\fITM\fR +where \fITM\fR is the "logical block application tag mask" field found in the +32 byte cdb variants of WRITE, WRITE ATOMIC, WRITE SAME and WRITE STREAM. The +field is also found in the WRITE SCATTERED(32) LBA range descriptors. It is a +16 bit field which means the maximum value is 0xffff. The default value is +0xffff. +.TP +\fB\-I\fR, \fB\-\-timeout\fR=\fITO\fR +where \fITO\fR is the command timeout value in seconds. The default value is +120 seconds. If \fINUM\fR is large on slow media then these WRITE commands +may require considerably more time than 120 seconds to complete. +.TP +\fB\-u\fR, \fB\-\-unmap\fR=\fIU_A\fR +where \fIU_A\fR is OR\-ed bit values used to set the UNMAP and ANCHOR bit +fields in the WRITE SAME (16 or 32) cdb. If \fIU_A\fR is 1 then the UNMAP +bit field is set; if \fIU_A\fR is 2 then the ANCHOR bit field is set; if +\fIU_A\fR is 3 then both the UNMAP and ANCHOR bit fields are set. The +default value for both bit fields is clear (0); setting \fIU_A\fR to 0 will +also clear both bit fields. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the degree of verbosity (debug messages). These messages are usually +written to stderr. +.TP +\fB\-V\fR, \fB\-\-version\fR +output version string then exit. +.TP +\fB\-w\fR, \fB\-\-wrprotect\fR=\fIWPR\fR +sets the WRPROTECT field (3 bits) in all sg_write_x commands apart from +ORWRITE which has a 3 bit ORPROTECT field (and the synopsis shows \fiOPR\fR +to highlight the difference). In all cases \fIWPR\fR is placed +in that 3 bit field. The default value is zero which does not send any PI +in the data\-out buffer. \fIWPR\fR should be a value between 0 and 7. +.SH SCATTERED FILE ASCII FORMAT +All commands in this utility can take a \fI\-\-scat\-file=SF\fR and that +option can be seen as a replacement for the \fI\-\-lba=LBA[,LBA...]\fR and +\fI\-\-num=NUM[,NUM...]\fR options. if both the \fI\-\-scat\-file=SF\fR and +\fI\-\-scat\-raw\fR options are given then the file named \fISF\fR is +expected to be binary and contain the parameter list header (32 bytes of +zeros for both the 16 and 32 byte variants) followed by zero or more LBA +range descriptors, each of 32 bytes each. This section describes what is +expected in \fISF\fR when the \fI\-\-scat\-raw\fR option is not given. +.PP +The ASCII hexadecimal "scatter file" (named by \fISF\fR) can contain +comments, empty lines and numbers. If multiple numbers appear on one line +they can be separated by spaces, tabs or a single comma. Numbers are parsed +as decimal unless prefixed by "0x" (or "0X") or have a suffix of "h". Ox is +the prefix of hexadecimal number is the C language while T10 uses the "h" +suffix for the same purpose. Anything from and including a "#" character +to the end\-of\-line is ignored, so comments can be placed there. +.PP +For the WRITE SCATTERED (16) command, its LBA range descriptors contain two +items per descriptor: an 8 byte LBA followed by a 4 byte number_of_blocks. +The remaining 20 bytes of the descriptor are zeros. The format accepted +is relatively loose with each decoded value being placed in an LBA and +then a number_of_blocks until the end\-of\-file is reached. The pattern +starts with a LBA and if it doesn't finish with a number_of_blocks (i.e. +an odd number of values are parsed) an error occurs. So the number of +LBA range descriptors generated will be half the number of values parsed +in \fISF\fR. +.PP +For the WRITE SCATTERED (32) command, its LBA range descriptors contain five +items per descriptor: an 8 byte LBA followed by a 4 byte number_of_blocks, +then a 4 byte RT, a 2 byte AT, and a 2 byte TM. The last three items are +associated with protection information (PI). The accepted format in the +\fISF\fR file is more constrained than the 16 byte cdb variant. The items +for each LBA range descriptor must be found on one line with adjacent items +being comma separated. The first two items (LBA and number_of_blocks) must be +given, and if no more items are on the line then RT, AT and TM are given +their default values (all "ff" bytes). Spaces and tabs may appear between +items but commas are the separators. Two commas with no value between them +will cause the "missing" item to receive its default value. +.SH NOTES +Various numeric arguments (e.g. \fILBA\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +In Linux, prior to lk 3.17, the sg driver did not support cdb sizes greater +than 16 bytes. Hence a device node like /dev/sg1 which is associated with +the sg driver would fail with this utility if the \fI\-\-32\fR option was +given (or implied by other options). The bsg driver with device nodes like +/dev/bsg/6:0:0:1 does support cdb sizes greater than 16 bytes since its +introduction in lk 2.6.28 . +.SH EXIT STATUS +The exit status of sg_write_x is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH EXAMPLES +One simple usage is to write 4 blocks of zeros from (and including) a given +LBA according to the rules of WRITE ATOMIC with an atomic boundary of 0. +Since no cdb size option is given, the 16 byte cdb will be assumed (i.e. +WRITE ATOMIC(16)): +.PP + sg_write_x \-\-atomic=0 \-\-in=/dev/zero \-\-lba=0x1234 \-\-num=4 /dev/sdc +.PP +Since \fI\-\-bs=BS\fR has not been given, then this utility will call the +READ CAPACITY(16) command on /dev/sdc to determine the number of bytes in a +logical block. If the READ CAPACITY(16) command fails then the READ +CAPACITY(10) command is tried. Let us assume one of them works and that +the number of bytes in each logical block is 512 bytes. So 4 blocks of +zeros (each block containing 512 bytes) will be written from (and including) +LBA 0x1234 . Now to bypass the need for the READ CAPACITY command(s) the +\fI\-\-bs=BS\fR option can be used: +.PP + sg_write_x \-\-atomic=0 \-\-bs=512 \-\-in=/dev/zero \-\-lba=0x1234 \-\-num=4 +/dev/sdc +.PP +Since \-\-bs= is given and its value (512) is a power of 2, then the actual +block size is also 512. If instead 520 was given then the logical block size +would be 512 (the highest power of 2 less than 520) and the actual block size +would be 520 bytes. To send the 32 byte variant add \-\-32 as in: +.PP + sg_write_x \-\-atomic=0 \-\-32 \-\-bs=512 \-\-in=/dev/zero \-\-lba=0x1234 +\-\-num=4 /dev/sdc +.PP +To send a WRITE STREAM(32) with a STR_ID of 1 use the following: +.PP + sg_write_x \-\-stream=1 \-\-32 \-\-bs=512 \-\-in=/dev/zero \-\-lba=0x1234 +\-\-num=4 /dev/sdc +.PP +Next is a WRITE SCATTERED(16) command with the scatter list, split between +the \-\-lba= and \-\-num= options, on the command line: +.PP + sg_write_x \-\-scattered=2 \-\-lba=2,0x33 \-\-num=4,1 -i /dev/zero /dev/sg1 +.PP +Example of a WRITE SCATTERED(16) command with a degenerate LBA range +descriptor (first element to \-\-lba= and \-\-num=): +.PP + sg_write_x \-\-scattered=2 \-\-lba=0,0x33 \-\-num=0,1 -i /dev/zero /dev/sg1 +.PP +Example of a WRITE SCATTERED(16) command with the scatter list in +scat_file.txt +.PP + sg_write_x \-\-scattered=3 \-q scat_file.txt \-i /dev/zero /dev/sg1 +.PP +Next a WRITE SCATTERED(16) command with its scatter list and data in a +single file. Note that the argument to \-\-scattered= is 0 so the number of +LBA range descriptors is calculated by analyzing the first two blocks of +scat_data.bin (because the argument to \-\-combined= is 2) : +.PP + sg_write_x \-\-scattered=0 \-\-combined=2 \-i scat_data.bin /dev/sg1 +.PP +When the \-xx option is used, a WRITE SCATTERED command is not executed +but instead the contents of the data\-out buffer are written to a file +called sg_write_x.bin . In the case of WRITE SCATTERED that binary file +is suitable for supplying to a later invocation to do the actual write +to media. For example: +.PP + sg_write_x \-\-scattered=3 \-q scat_file.txt \-xx \-i /dev/zero /dev/sg1 +.br +Wrote 8192 bytes to sg_write_x.bin, LB data offset: 1 +.br +Number of LBA range descriptors: 3 +.br + sg_write_x \-\-scattered=0 \-\-combined=1 \-i sg_write_x.bin /dev/sg1 +.PP +Notice when the sg_write_x.bin is written (and nothing is written to the +media), a summary of what has happened is sent to stdout. The value shown +for "LB data offset:" (1) should be given to the \-\-combined= option +when the write to media actually occurs (i.e. the second invocation shown +directly above). +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2017 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_readcap,sg_vpd,sg_write_same,sg_stream_ctl(sg3_utils) diff --git a/doc/sg_xcopy.8 b/doc/sg_xcopy.8 new file mode 100644 index 0000000..437bad0 --- /dev/null +++ b/doc/sg_xcopy.8 @@ -0,0 +1,370 @@ +.TH SG_XCOPY "8" "August 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_xcopy \- copy data to and from files and devices using SCSI EXTENDED +COPY (XCOPY) +.SH SYNOPSIS +.B sg_xcopy +[\fIbs=BS\fR] [\fIconv=CONV\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR] +[\fIif=IFILE\fR] [\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR] +[\fIoflag=FLAGS\fR] [\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR] +[\fI\-\-version\fR] +.PP +[\fIbpt=BPT\fR] [\fIcat=\fR0|1] [\fIdc=\fR0|1] +[\fIid_usage=\fR{hold|discard|disable}] [\fIlist_id=ID\fR] [\fIprio=PRIO\fR] +[\fItime=\fR0|1] [\fIverbose=VERB\fR] [\fI\-\-on_dst|\-\-on_src\fR] +[\fI\-\-verbose\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Copy data to and from any files. Specialized for "files" that are Linux SCSI +devices that support the SCSI EXTENDED COPY (XCOPY) command. +.PP +During the draft stages of SPC\-4 the T10 committee has expanded the XCOPY +command so that it now has two variants: "LID1" (for a List Identifier +length of 1 byte) and "LID4" (for a List Identifier length of 4 bytes). +This utility supports the older, LID1 variant which is also found in SPC\-3 +and earlier. While the LID1 variant in SPC\-4 is command level (binary) +compatible with XCOPY as defined in SPC\-3, some of the command naming has +changed. This utility uses the older, SPC\-3 XCOPY names. +.PP +This utility +has similar syntax and semantics to +.B dd(1) +but with no "conversions" is supported. +.PP +The first group in the synopsis above are "standard" Unix +.B dd(1) +operands. The second group are extra options added by this utility. +Both groups are defined below in combined, alphabetical order. +.PP +By default the XCOPY command is sent to \fIOFILE\fR. This can be changed +with the \fI\-\-on_src\fR or \fIiflag=xflag\fR options which cause the XCOPY +command to be sent to \fIIFILE\fR instead. Also see the section on +ENVIRONMENT VARIABLES. +.PP +The ddpt utility supports the same xcopy(LID1) functionality as this utility +with the same options and flags. Additionally ddpt supports a subset of +xcopy(LID4) functionality variously called "xcopy version 2, lite" or ODX. +ODX is a market name and stands for Offloaded Data Xfer (i.e. transfer). +.SH OPTIONS +.TP +\fBbpt\fR=\fIBPT\fR +each IO transaction will be made using \fIBPT\fR blocks (or less if near +the end of the copy). Default is 128 for logical block sizes less that 2048 +bytes, otherwise the default is 32. So for bs=512 the reads and writes +will each convey 64 KiB of data by default (less if near the end of the +transfer or memory restrictions). When cd/dvd drives are accessed, the +logical block size is typically 2048 bytes and bpt defaults to 32 which again +implies 64 KiB transfers. +.TP +\fBbs\fR=\fIBS\fR +where \fIBS\fR +.B must +be the logical block size of the physical device (if either the input or +output files are accessed via SCSI commands). Note that this differs from +.B dd(1) +which permits \fIBS\fR to be an integral multiple. Defaults to the +device logical block size. +.TP +\fBcat\fR={0|1} +sets the SCSI EXTENDED COPY command segment descriptor CAT bit to 0 or +1 (default: 0). The CAT bit (in conjunction with the PAD bit) controls +the handling of residual data. See section +.B HANDLING OF RESIDUAL DATA +for details. +.TP +\fBdc\fR={0|1} +sets the SCSI EXTENDED COPY command segment descriptor DC bit to 0 or +1 (default: 0). The DC bit controls whether \fICOUNT\fR +refers to the source (\fIdc=0\fR) or the target (\fIdc=1\fR) descriptor. +.TP +\fBconv\fR=\fBCONV\fR +all \fBCONV\fR arguments are ignored. +.TP +\fBapp\fR=\fBAPPEND\fR +all \fBAPPEND\fR arguments are ignored. +.TP +\fBcount\fR=\fICOUNT\fR +copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the +minimum (\fIIFILE\fR if \fIdc=0\fR or \fIOFILE\fR if \fIdc=1\fR) +number of blocks that SCSI devices report from SCSI READ CAPACITY +commands or that block devices (or their partitions) report. Normal +files are not probed for their size. If \fIskip=SKIP\fR or +\fIskip=SEEK\fR are given and the count is derived (i.e. not +explicitly given) then the derived count is scaled back so that the +copy will not overrun the device. If the file name is a block device +partition and \fICOUNT\fR is not given then the size of the partition +rather than the size of the whole device is used. If \fICOUNT\fR is +not given (or \fIcount=\-1\fR) and cannot be derived then an error +message is issued and no copy takes place. +.TP +\fBibs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBid_usage\fR={hold|discard|disable} +sets the SCSI EXTENDED COPY command parameter list field called LIST ID +USAGE to 0 if the argument is 'hold', to 2 if the argument is 'discard', +or to '3' if the argument is 'disable'. +.br +If the device has the ability to hold data (as indicated by "held data +limit" being greater than zero) then \fIid_usage\fR defaults to 'hold' +otherwise it defaults to 'discard'. +.TP +\fBif\fR=\fIIFILE\fR +read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin +is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR +is given. +.TP +\fBiflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIIFILE\fR and are ignored when +\fIIFILE\fR is stdin. +.TP +\fBlist_id\fR=\fIID\fR +sets the SCSI EXTENDED COPY command parameter list field called LIST +IDENTIFIER to \fIID\fR. \fIID\fR should be a value between 0 and +255 (inclusive). \fIID\fR usually defaults to 1 unless +\fIid_usage=disable\fR in which case it defaults to 0. +.TP +\fBobs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBof\fR=\fIOFILE\fR +write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes +to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed. +If \fIOFILE\fR is '.' (period) then it is treated the same way as +/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it +is _not_ truncated; it is overwritten from the start of \fIOFILE\fR +unless 'oflag=append' or \fISEEK\fR is given. +.TP +\fBoflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIOFILE\fR and are ignored when +\fIOFILE\fR is /dev/null, '.' (period), or stdout. +.TP +\fBprio\fR=\fIPRIO\fR +sets the SCSI EXTENDED COPY command parameter list field called PRIORITY +to \fIPRIO\fR. The default value is 1. +.TP +\fBseek\fR=\fISEEK\fR +start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBskip\fR=\fISKIP\fR +start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBtime\fR={0|1} +when 1, times transfer and does throughput calculation, outputting the +results (to stderr) at completion. When 0 (default) doesn't perform timing. +.TP +\fBverbose\fR=\fIVERB\fR +as \fIVERB\fR increases so does the amount of debug output sent to stderr. +Default value is zero which yields the minimum amount of debug output. +A value of 1 reports extra information that is not repetitive. A value +2 reports cdbs and responses for SCSI commands that are not repetitive +(i.e. other that READ and WRITE). Error processing is not considered +repetitive. Values of 3 and 4 yield output for all SCSI commands (and +Unix read() and write() calls) so there can be a lot of output. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs usage message and exits. +.TP +\fB\-\-on_dst\fR +send the XCOPY command to the output file/device (i.e. \fIOFILE\fR). This is +the default unless overridden by the \fI\-\-on_src\fR or \fIiflag=xflag\fR +options. Also see the section below on ENVIRONMENT VARIABLES. +.TP +\fB\-\-on_src\fR +send the XCOPY command to the input file/device (i.e. \fIIFILE\fR). +.TP +\fB\-v\fR, \fB\-\-verbose\fR +equivalent to \fIverbose=1\fR. When used twice, equivalent to +\fIverbose=2\fR, etc. +.TP +\fB\-V\fR, \fB\-\-version\fR +outputs version number information and exits. +.SH FLAGS +Here is a list of flags and their meanings: +.TP +append +causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For regular +files this will lead to data appended to the end of any existing data. +Cannot be used together with the \fIseek=SEEK\fR option as they conflict. +The default action of this utility is to overwrite any existing data +from the beginning of the file or, if \fISEEK\fR is given, starting at +block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g. +a disk) will usually be ignored or may cause an error to be reported. +.TP +excl +causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. +.TP +flock +after opening the associated file (i.e. \fIIFILE\fR and/or \fIOFILE\fR) +an attempt is made to get an advisory exclusive lock with the flock() +system call. The flock arguments are "FLOCK_EX | FLOCK_NB" which will +cause the lock to be taken if available else a "temporarily unavailable" +error is generated. An exit status of 90 is produced in the latter case +and no copy is done. +.TP +null +has no affect, just a placeholder. +.TP +pad +sets the SCSI EXTENDED COPY command segment descriptor PAD bit. The +PAD bit (in conjunction with the CAT bit) controls the handling of +residual data.(See section +.B HANDLING OF RESIDUAL DATA +for details. +.TP +xcopy +has no affect; for compatibility with ddpt. +.SH HANDLING OF RESIDUAL DATA +The \fIpad\fR and \fIcat\fR bits control the handling of residual +data. As the data can be specified either in terms of source or target +logical block size and both might have different block sizes residual data +is likely to happen in these cases. +If both logical block sizes are identical these bits have no effect as +residual data will not occur. +.PP +If none of these bits are set, the EXTENDED COPY command will be +aborted with additional sense 'UNEXPECTED INEXACT SEGMENT'. +.PP +If only the \fIcat\fR bit is set the residual data will be retained +and made available for subsequent segment descriptors. Residual data +will be discarded for the last segment descriptor. +.PP +If the \fIpad\fR bit is set for the source descriptor only, any +residual data for both source or destination will be discarded. +.PP +If the \fIpad\fR bit is set for the target descriptor only any +residual source data will be handled as if the \fIcat\fR bit is set, +but any residual destination data will be padded to make a whole block +transfer. +.PP +If the \fIpad\fR bit is set for both source and target any residual +source data will be discarded, and any residual destination data will +be padded. +.SH ENVIRONMENT VARIABLES +If the command line invocation does not explicitly (and unambiguously) +indicate whether the XCOPY SCSI command should be sent to \fIIFILE\fR (i.e. +the source) or \fIOFILE\fR (i.e. the destination) then a check is +made for the presence of the XCOPY_TO_SRC and XCOPY_TO_DST environment +variables. If either one exists (but not both) then it indicates where +the SCSI XCOPY command will be sent. By default the XCOPY command is +sent to \fIOFILE\fR. +.SH RETIRED OPTIONS +Here are some retired options that are still present: +.TP +append=0 | 1 +when set, equivalent to 'oflag=append'. When clear the action is +to overwrite the existing file (if it exists); this is the default. +See the 'append' flag. +.SH NOTES +Copying data behind an Operating System's back can cause problems. In the +case of Linux, users should look at this link: +http://linux\-mm.org/Drop_Caches +.br +This command sequence may be useful: +.br + sync; echo 3 > /proc/sys/vm/drop_caches +.PP +Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +The \fICOUNT\fR, \fISKIP\fR and \fISEEK\fR arguments can take 64 bit +values (i.e. very big numbers). Other values are limited to what can fit in +a signed 32 bit number. +.PP +All informative, warning and error output is sent to stderr so that +dd's output file can be stdout and remain unpolluted. If no options +are given, then the usage message is output and nothing else happens. +.PP +If a device supports xcopy operations then it should set the 3PC +field (3PC stands for Third Party Copy) in its standard INQUIRY response. +This utility will attempt a xcopy operation irrespective of the value +in the 3PC field but if it is zero (cleared) one would expect the +xcopy operation to fail. +.PP +The status of the SCSI EXTENDED COPY command can be queried with +.B sg_copy_results(sg3_utils) +.PP +Currently only block\-to\-block transfers are implemented; \fIIFILE\fR +and \fIOFILE\fR must refer to a SCSI block device. +.PP +No account is taken of partitions so, for example, /dev/sbc2, /dev/sdc, +/dev/sg2, and /dev/bsg/3:0:0:1 would all refer to the same thing: the +whole logical unit (i.e. the whole disk) starting at LBA 0. So any +partition indication (e.g. /dev/sdc2) is ignored. The user should set +\fISKIP\fR, \fISEEK\fR and \fICOUNT\fR with information obtained +from a command like 'fdisk \-l \-u /dev/sdc' to account for partitions. +.PP +XCOPY (LID1) capability has been added to the ddpt utility which is in +a package of the same name. The ddpt utility will run on other +OSes (e.g. FreeBSD and Windows) while sg_xcopy only runs on Linux. Also +ddpt permits the arguments to \fIibs=\fR and \fIibs=\fR to be different. +.SH EXAMPLES +Copy 2M of data from the start of one device to another: +.PP +# sg_xcopy if=/dev/sdo of=/dev/sdp count=2048 list_id=2 dc=1 +.br +sg_xcopy: if=/dev/sdo skip=0 of=/dev/sdp seek=0 count=1024 +.br +Start of loop, count=1024, bpt=65535, lba_in=0, lba_out=0 +.br +sg_xcopy: 1024 blocks, 1 command +.PP +Check the status of the EXTENDED COPY command: +.PP +# sg_copy_results \-\-status \-\-list_id=2 /dev/sdp +.br +Receive copy results (copy status): + Held data discarded: Yes + Copy manager status: Operation completed without errors + Segments processed: 1 + Transfer count units: 0 + Transfer count: 0 +.SH SIGNALS +The signal handling has been borrowed from dd: SIGINT, SIGQUIT and +SIGPIPE output the number of remaining blocks to be transferred and +the records in + out counts; then they have their default action. +SIGUSR1 causes the same information to be output yet the copy continues. +All output caused by signals is sent to stderr. +.SH EXIT STATUS +The exit status of sg_xcopy is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.PP +An additional exit status of 90 is generated if the flock flag is given +and some other process holds the advisory exclusive lock. +.SH AUTHORS +Written by Hannes Reinecke and Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2018 Hannes Reinecke and Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +There is a web page discussing sg_dd at http://sg.danny.cz/sg/sg_dd.html +.PP +A POSIX threads version of this utility called +.B sgp_dd +is in the sg3_utils package. Another version from that package is called +.B sgm_dd +and it uses memory mapped IO to speed transfers from sg devices. +.PP +The lmbench package contains +.B lmdd +which is also interesting. For moving data to and from tapes see +.B dt +which is found at http://www.scsifaq.org/RMiller_Tools/index.html +.PP +To change mode parameters that effect a SCSI device's caching and error +recovery see +.B sdparm(sdparm) +.PP +See also +.B dd(1), sg_copy_results(sg3_utils), ddrescue(GNU), ddpt,ddptctl(ddpt) diff --git a/doc/sg_zone.8 b/doc/sg_zone.8 new file mode 100644 index 0000000..e3dfd3c --- /dev/null +++ b/doc/sg_zone.8 @@ -0,0 +1,69 @@ +.TH SG_ZONE "8" "May 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sg_zone \- send SCSI OPEN, CLOSE, FINISH or SEQUENTIALIZE ZONE command +.SH SYNOPSIS +.B sg_zone +[\fI\-\-all\fR] [\fI\-\-close\fR] [\fI\-\-count=ZC\fR] [\fI\-\-finish\fR] +[\fI\-\-help\fR] [\fI\-\-open\fR] [\fI\-\-sequentialize\fR] +[\fI\-\-verbose\fR] [\fI\-\-version\fR] [\fI\-\-zone=ID\fR] \fIDEVICE\fR +.SH DESCRIPTION +.\" Add any additional description here +.PP +Sends a SCSI OPEN ZONE, CLOSE ZONE, FINISH ZONE or SEQUENTIALIZE ZONE +command to the \fIDEVICE\fR. All but the last are found in the soon to +be released ZBC standard (close to draft ZBC BSR INCITS 536 revision 05). +The SEQUENTIALIZE ZONE command was added in zbc2r01b. +.PP +One and only one of the \fI\-\-open\fR, \fI\-\-close\fR, \fI\-\-finish\fR +and \fI\-\-sequentialize\fR options can be chosen. +.SH OPTIONS +Arguments to long options are mandatory for short options as well. +.TP +\fB\-a\fR, \fB\-\-all\fR +sets the ALL field in the cdb. +.TP +\fB\-c\fR, \fB\-\-close\fR +causes the CLOSE ZONE command to be sent to the \fIDEVICE\fR. +.TP +\fB\-C\fR, \fB\-\-count\fR=\fIZC\fR +ZC is placed in the Zone Count field in the cdb of all four commands +supported by this utility. ZC should be a value from 0 to 65535 (0xffff) +inclusive. +.TP +\fB\-f\fR, \fB\-\-finish\fR +causes the FINISH ZONE command to be sent to the \fIDEVICE\fR. +.TP +\fB\-h\fR, \fB\-\-help\fR +output the usage message then exit. +.TP +\fB\-o\fR, \fB\-\-open\fR +causes the OPEN ZONE command to be sent to the \fIDEVICE\fR. +.TP +\fB\-S\fR, \fB\-\-sequentialize\fR +causes the SEQUENTIALIZE ZONE command to be sent to the \fIDEVICE\fR. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +increase the level of verbosity, (i.e. debug output). +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.TP +\fB\-z\fR, \fB\-\-zone\fR=\fIID\fR +where \fIID\fR is placed in the cdb's ZONE ID field. A zone id is a zone +start logical block address (LBA). The default value is 0. \fIID\fR is +assumed to be in decimal unless prefixed with '0x' or has a trailing 'h' +which indicate hexadecimal. +.SH EXIT STATUS +The exit status of sg_zone is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2014\-2018 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B sg_rep_zones,sg_reset_wp(sg3_utils) diff --git a/doc/sginfo.8 b/doc/sginfo.8 new file mode 100644 index 0000000..bb913b9 --- /dev/null +++ b/doc/sginfo.8 @@ -0,0 +1,325 @@ +.TH SGINFO "8" "January 2014" "sg3_utils\-1.38" SG3_UTILS +.SH NAME +sginfo \- access mode page information for a SCSI (or ATAPI) device +.SH SYNOPSIS +.B sginfo +[\fIOPTIONS\fR] +[\fIDEVICE\fR] +[\fIREPLACEMENT_PARAMETERS\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +sginfo is a port of the Linux +.B scsiinfo +program by Eric Youngdale. It uses SCSI generic (sg) devices; however in +some cases the high level device name (i.e. sd, sr, st, osst, or hd) can +also be used. The primary role of this program is to access mode page +information. If permitted, mode page information can be altered. In +addition information from the INQUIRY and READ DEFECTS commands are also +available. +.PP +This utility is in legacy mode, only obvious bugs will be fixed. Options +like \fI\-l\fR (to list devices) are broken in recent versions of +Linux (e.g. 2.6 series and later); the lsscsi(8) utility can be used +instead. Also mode pages are not being updated as http://www.t10.org +adds and modifies mode page fields. Those interested in SCSI mode pages +may find the +.B sdparm +utility more up to date and easier use, especially for changing parameters. +.PP +Four sets of values are maintained by a SCSI device for each mode +page: current (active), default (manufacturer's supplied values), +saved (values that are retained if the SCSI device is powered down), +and changeable (mask indicating those values that can be changed). +By default when a mode page is displayed the current values are +shown. This can be overridden by "\-M" (defaults), "\-S" (saved) +or "\-m" (modifiable (i.e. changeable)). +.PP +Many mode pages are decoded: for disks (see SBC\-2), for CD/DVDs (see +MMC\-2/3/4/5), for tapes (see SSC\-2) and for enclosures (see SES\-2). +Some mode pages common to all SCSI peripheral device types are defined +in SPC\-4 (primary commands). A decoded mode page has its field names +in the first column and the corresponding value in the second column. +A "hex" mode page (and subpage) has its byte position in the first +column (in hex and starting at 0x2) and the corresponding hex value +in the second column. Decoded pages can be viewed with the '\-t' option +or with a specific option (e.g. 'c' for the caching mode page). +Naturally decoded pages must be supplied by the \fIDEVICE\fR and +recognised by this program. If supported by the device, decoded pages +may be modified. All mode pages (and subpages) that the device supports +can be viewed in hex (and potentially modified) via the "\-u" option +.PP +If no options are given that will cause mode page(s) or INQUIRY data +to be printed out, then a brief INQUIRY response is output. This +includes the vendor, product and revision level of the device. +.SH OPTIONS +.TP +\fB\-6\fR +Perform 6 byte MODE SENSE and MODE SELECT commands; by default the +10 byte variants are used. +.TP +\fB\-a\fR +Display some INQUIRY data and the unit serial number followed by +all mode pages reported by the device. It is similar to +the '\-t 0x3f' option. If the mode page is known then it is output +in decoded form otherwise it is output in hexadecimal. +.TP +\fB\-A\fR +Display some INQUIRY data and the unit serial number followed by +all mode pages and all mode subpages reported by the device. +It is similar to the '\-t 0x3f,0xff' option. If a mode (sub)page +is known then it is output in decoded form otherwise it is output in +hexadecimal. +.TP +\fB\-c\fR +Access information in the Caching mode page. +.TP +\fB\-C\fR +Access information in the Control mode Page. +.TP +\fB\-d\fR +Display defect lists (default format: index). +.TP +\fB\-D\fR +Access information in the Disconnect\-Reconnect mode page. +.TP +\fB\-e\fR +Access information in the Error Recovery mode page. +.TP +\fB\-E\fR +Access information in the Control Extension mode page. +.TP +\fB\-f\fR +Access information in the Format Device mode page. +.TP +\fB\-F\fR\fIarg\fR +Format of the defect lists: + \-Flogical \- logical block addresses (32 bit) + \-Flba64 \- logical block addresses (64 bit) + \-Fphysical \- physical blocks + \-Findex \- defect bytes from index + \-Fhead \- sort by head +.br +Used in conjunction with "\-d" or "\-G". If a format is not given "index" is +assumed. +.TP +\fB\-g\fR +Access information in the Rigid Disk Drive Geometry mode page. +.TP +\fB\-G\fR +Display grown defect list (default format: index). +.TP +\fB\-i\fR +Display the response to a standard INQUIRY command. +.TP +\fB\-I\fR +Access the Informational Exceptions mode page. +.TP +\fB\-l\fR +Deprecated. Only use in old versions of Linux (e.g. 2.4 and +earlier). Please use lsscsi(8) in the Linux 2.6 series and +later. List known SCSI devices on the system. +.TP +\fB\-n\fR +Access information in the Notch and Partition mode page. +.TP +\fB\-N\fR +Negate (i.e. stop) mode page changes being placed in the "saved" +page (by default changes go to the current and the saved page). +Only active when used together with '\-R'. +.TP +\fB\-P\fR +Access information in the Power Condition mode page. +.TP +\fB\-r\fR +Display all raw (or primary) SCSI device names visible in the /dev +directory. Examples are /dev/sda, /dev/st1 and /dev/scd2. Does not +list sg device names so devices such as a SCSI enclosure which only +have an sg device name are not listed. +.TP +\fB\-s\fR +Display information in the unit serial number page which is a +INQUIRY command variant. +.TP +\fB\-t\fR \fIPN\fR[,\fISPN\fR] +Display information from mode page number \fIPN\fR (and optionally sub +page number \fISPN\fR) in decoded format (if known, otherwise in hex form). +\fIPN\fR is a mode page number in a decimal number from 0 to 63 inclusive. +\fISPN\fR is the mode subpage number and is assumed to be 0 if not given. +\fISPN\fR is a decimal number from 1 to 255 inclusive. A page number of 63 +returns all pages supported by the device in ascending order except for +page 0 which, if present, is last. Page 0 is vendor specific and not +necessarily in mode page format. Alternatively hex values can be given for +both \fIPN\fR and \fISPN\fR (both prefixed by '0x'). +.TP +\fB\-T\fR +Trace commands to obtain more verbose output (for debugging). When used once +SCSI commands are shown (in hex) and any errors from these SCSI commands are +spelt out (i.e. with a decoded and raw sense buffer). When used twice, the +additional data sent with mode select and the response from mode sense are +shown (in hex). +.TP +\fB\-u\fR \fIPN\fR[,\fISPN\fR] +Display information from mode page number \fIPN\fR (and optionally \fISPN\fR) +in hex form. \fIPN\fR is a mode page number in a decimal number from 0 to 63 +inclusive. \fISPN\fR is the mode subpage number and is assumed to be 0 if +not given. \fISPN\fR is a decimal number from 1 to 255 inclusive. A page +number of 63 returns all pages supported by the device in ascending order +except for page 0 which, if present, is last. Page 0 is vendor specific and +not necessarily in mode page format. Alternatively hex values can be given +for both \fIPN\fR and \fISPN\fR (both prefixed by '0x'). For example 63 and +0x3f are equivalent. +.TP +\fB\-v\fR +Display version string then exit. [N.B. This option increases verbosity for +most other utilities in this package as outlined in 'man 8 sg3_utils'. +This odd usage is for backward compatibility with the scsiinfo utility.] +.TP +\fB\-V\fR +Access information in the Verify Error Recovery mode page. [N.B. This +option prints the version string then exits in most other utilities in +this package as outlined in 'man 8 sg3_utils'. This odd usage is for +backward compatibility with the scsiinfo utility.] +.TP +\fB\-z\fR +do a single fetch for mode pages (over\-estimating the expected length +of the returned response). The default action is to do a double +fetch, the first fetch is to find the response length that could be +returned. Devices that closely adhere to SCSI standards should not +require this option, some real world devices do require it. +.SH ADVANCED OPTIONS +Only one of the following three options can be specified. +None of these three implies the current values are returned. +.TP +\fB\-m\fR +Display modifiable fields instead of current values +.TP +\fB\-M\fR +Display manufacturer's defaults instead of current values +.TP +\fB\-S\fR +Display saved defaults instead of current values +.PP +The following are advanced options, not generally suited for most users: +.TP +\fB\-X\fR +Display output values in a list. Make them suitable for editing and +being given back to the '\-R' (replace command). +.TP +\fB\-R\fR +Replace parameters \- best used with \-X (expert use only) +.SH CHANGING MODE PAGE PARAMETERS +Firstly you should know what you are doing before changing existing +parameters. Taking the control page as an example, first list it out +normally (e.g. "sginfo \-C /dev/sda") and +decide which parameter is to be changed (note its position relative +to the other lines output). Then execute the same sginfo command with +the "\-X" option added; this will output the parameter values in a +single row in the same relative positions as the previous command. Now +execute "sginfo \-CXR /dev/sda ..." with the "..." replaced by the +single row of values output by the previous command, with the relevant +parameter changed. Here is a simplified example: +.PP + $ sginfo \-C /dev/sda +.br + Control mode page (0xa) +.br + \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- +.br + TST 0 +.br + D_SENSE 0 +.br + GLTSD 1 +.br + RLEC 0 +.PP +[Actually the Control page has more parameters that shown above.] Next +output those parameters in single line form: +.PP + $ sginfo \-CX /dev/sda +.br + 0 0 1 0 +.PP +Let us assume that the GLTSD bit is to be cleared. The command that +will clear it is: +.PP + $ sginfo \-CXR /dev/sda 0 0 0 0 +.PP +The same number of parameters output by the "\-CX" command needs to be +placed at the end of the "\-CXR" command line (after the device name). +Now check that the change took effect: +.PP + $ sginfo \-C /dev/sda +.br + Control mode page (0xa) +.br + \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- +.br + TST 0 +.br + D_SENSE 0 +.br + GLTSD 0 +.br + RLEC 0 +.PP +When a mode page is "replaced" the default action is to change both the +current page and the saved page. [For some reason versions of sginfo and +scsiinfo prior to 2.0 did not change the "saved" page.] To change only +the current mode page but not the corresponding saved page use the "\-N" +option. +.PP +.SH GENERATING SCRIPT FILES AND HEX PAGES +The "\-aX" or "\-AX" option generates output suitable for a script file. +Mode pages are output in list format (after the INQUIRY and serial +number) one page per line. To facilitate running the output as (part +of) a script file to assert chosen mode page values, each line is +prefixed by "sginfo \-t \fIPN\fR[,\fISPN\fR] \-XR ". When such a script +file is run, it will have the effect of re\-asserting the mode +page values to what they were when the "\-aX" generated the output. +.PP +All mode pages (and subpages) supported by the device can be accessed via +the \-t and \-u options. To see all +mode pages supported by the device use "\-u 63". [To see all mode pages +and all subpages use "\-u 63,255".] To list the control mode page in +hex (mode page index in the first column and the corresponding byte +value in the second column) use "\-u 0xa". Mode pages (subpage code == 0) +start at index position 2 while subpages start at index position 4. +If the "\-Xu ..." option is used then a list a hex values each value +prefixed by "@" is output. Mode (sub)page values can then be modified +with the "\-RXu ..." option. +.PP +.SH RESTRICTIONS +The SCSI MODE SENSE command yields block descriptors as well as a mode +page(s). This utility ignores block descriptors and does not display +them. The "disable block descriptor" switch (DBD) in the MODE SENSE command +is not set since some devices yield errors when it is set. When mode page +values are being changed (the "\-R" option), the same block descriptor +obtained by reading the mode page (i.e. via a MODE SENSE command) is sent +back when the mode page is written (i.e. via a MODE SELECT command). +.PP +.SH REFERENCES +SCSI (draft) standards can be found at http://www.t10.org . The relevant +documents are SPC\-4 (mode pages common to all device types), +SBC\-2 (direct access devices [e.g. disks]), MMC\-4 (CDs and DVDs) and +SSC\-2 (tapes). +.PP +.SH AUTHORS +Written by Eric Youngdale, Michael Weller, Douglas Gilbert, Kurt Garloff, +Thomas Steudten +.PP +.SH HISTORY +scsiinfo version 1.0 was released by Eric Youngdale on 1st November 1993. +The most recent version of scsiinfo is version 1.7 with the last patches +by Michael Weller. sginfo is derived from scsiinfo and uses the sg +interface to get around the 4 KB buffer limit in scsiinfo that cramped +the display of defect lists especially. sginfo was written by Douglas +Gilbert with patches from Kurt Garloff. This manpage corresponds with +version 2.25 of sginfo. +.PP +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B lsscsi(lsscsi), scsiinfo(internet); sg_modes, sg_inq, sg_vpd (sg3_utils), +.B sdparm(sdparm) diff --git a/doc/sgm_dd.8 b/doc/sgm_dd.8 new file mode 100644 index 0000000..346bedb --- /dev/null +++ b/doc/sgm_dd.8 @@ -0,0 +1,284 @@ +.TH SGM_DD "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sgm_dd \- copy data to and from files and devices, especially SCSI +devices +.SH SYNOPSIS +.B sgm_dd +[\fIbs=BS\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR] [\fIif=IFILE\fR] +[\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR] [\fIoflag=FLAGS\fR] +[\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR] [\fI\-\-version\fR] +.PP +[\fIbpt=BPT\fR] [\fIcdbsz=\fR6|10|12|16] [\fIdio=\fR0|1] [\fIsync=\fR0|1] +[\fItime=\fR0|1] [\fIverbose=VERB\fR] [\fI\-\-dry\-run\fR] +[\fI\-\-verbose\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Copy data to and from any files. Specialized for "files" that are +Linux SCSI generic (sg) devices and raw devices. Uses memory mapped +transfers on sg devices. Similar syntax and semantics to +.B dd(1) +but does not perform any conversions. +.PP +Will only perform memory mapped transfers when \fIIFILE\fR or \fIOFILE\fR +are SCSI generic (sg) devices. +.PP +If both \fIIFILE\fR and \fIOFILE\fR are sg devices then memory mapped +transfers are performed on \fIIFILE\fR. If no other flags are specified +then indirect IO is performed on \fIOFILE\fR. If 'oflag=dio' is given then +direct IO is attempted on \fIOFILE\fR. If direct IO is not available, then +this utility falls back to indirect IO and reports this at the end of the +copy. +.PP +The first group in the synopsis above are "standard" Unix +.B dd(1) +operands. The second group are extra options added by this utility. +Both groups are defined below. +.SH OPTIONS +.TP +\fBbpt\fR=\fIBPT\fR +each IO transaction will be made using \fIBPT\fR blocks (or less if +near the end of the copy). Default is 128 for block sizes less that 2048 +bytes, otherwise the default is 32. So for bs=512 the reads and writes +will each convey 64 KiB of data by default (less if near the end of the +transfer or memory restrictions). When cd/dvd drives are accessed, the +block size is typically 2048 bytes and bpt defaults to 32 which again +implies 64 KiB transfers. +.TP +\fBbs\fR=\fIBS\fR +where \fIBS\fR +.B must +be the block size of the physical device. Note that this differs from +.B dd(1) +which permits \fIBS\fR to be an integral multiple. Default is 512 which +is usually correct for disks but incorrect for cdroms (which normally +have 2048 byte blocks). For this utility the maximum size of each individual +IO operation is \fIBS\fR * \fIBPT\fR bytes. +.TP +\fBcdbsz\fR=6 | 10 | 12 | 16 +size of SCSI READ and/or WRITE commands issued on sg device names. +Default is 10 byte SCSI command blocks (unless calculations indicate +that a 4 byte block number may be exceeded, in which case it defaults +to 16 byte SCSI commands). +.TP +\fBcount\fR=\fICOUNT\fR +copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the +minimum (of \fIIFILE\fR and \fIOFILE\fR) number of blocks that sg devices +report from SCSI READ CAPACITY commands or that block devices (or their +partitions) report. Normal files are not probed for their size. If +\fIskip=SKIP\fR or \fIskip=SEEK\fR are given and the count is derived (i.e. +not explicitly given) then the derived count is scaled back so that the +copy will not overrun the device. If the file name is a block device +partition and \fICOUNT\fR is not given then the size of the partition rather +than the size of the whole device is used. If \fICOUNT\fR is not given and +cannot be derived then an error message is issued and no copy takes place. +.TP +\fBdio\fR=0 | 1 +permits direct IO to be selected on the write\-side (i.e. on \fIOFILE\fR). +Only allowed when the read\-side (i.e. \fIIFILE\fR) is a sg device. When +1 there may be a "zero copy" copy (i.e. mmap\-ed transfer on the read into +the user space and direct IO from there on the write, potentially two DMAs +and no data copying from the CPU). Default is 0. +The same action as 'dio=1' is also available with 'oflag=dio'. +.TP +\fBibs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBif\fR=\fIIFILE\fR +read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin +is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR +is given. +.TP +\fBiflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIIFILE\fR and are ignored when +\fIIFILE\fR is stdin. +.TP +\fBobs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBof\fR=\fIOFILE\fR +write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes +to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed. +If \fIOFILE\fR is '.' (period) then it is treated the same way as +/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it +is _not_ truncated; it is overwritten from the start of \fIOFILE\fR +unless 'oflag=append' or \fISEEK\fR is given. +.TP +\fBoflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIOFILE\fR and are ignored when +\fIOFILE\fR is /dev/null, '.' (period), or stdout. +.TP +\fBseek\fR=\fISEEK\fR +start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBskip\fR=\fISKIP\fR +start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBsync\fR=0 | 1 +when 1, does SYNCHRONIZE CACHE command on \fIOFILE\fR at the end of the +transfer. Only active when \fIOFILE\fR is a sg device file name. +.TP +\fBtime\fR=0 | 1 +when 1, times transfer and does throughput calculation, outputting the +results (to stderr) at completion. When 0 (default) doesn't perform timing. +.TP +\fBverbose\fR=\fIVERB\fR +as \fIVERB\fR increases so does the amount of debug output sent to stderr. +Default value is zero which yields the minimum amount of debug output. +A value of 1 reports extra information that is not repetitive. A value +2 reports cdbs and responses for SCSI commands that are not repetitive +(i.e. other that READ and WRITE). Error processing is not considered +repetitive. Values of 3 and 4 yield output for all SCSI commands (and +Unix read() and write() calls) so there can be a lot of output. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +does all the command line parsing and preparation but bypasses the actual +copy or read. That preparation may include opening \fIIFILE\fR or +\fIOFILE\fR to determine their lengths. This option may be useful for +testing the syntax of complex command line invocations in advance of +executing them. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs usage message and exits. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +when used once, this is equivalent to \fIverbose=1\fR. When used +twice (e.g. "\-vv") this is equivalent to \fIverbose=2\fR, etc. +.TP +\fB\-V\fR, \fB\-\-version\fR +outputs version number information and exits. +.SH FLAGS +Here is a list of flags and their meanings: +.TP +append +causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For normal +files this will lead to data appended to the end of any existing data. +Cannot be used together with the \fIseek=SEEK\fR option as they conflict. +The default action of this utility is to overwrite any existing data +from the beginning of the file or, if \fISEEK\fR is given, starting at +block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g. +a disk) will usually be ignored or may cause an error to be reported. +.TP +dio +is only active with oflag (i.e. 'oflag=dio'). Its action is described in +the 'dio=1' option description above. +.TP +direct +causes the O_DIRECT flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. This flag requires some memory alignment on IO. Hence user +memory buffers are aligned to the page size. Has no effect on sg, normal +or raw files. +.TP +dpo +set the DPO bit (disable page out) in SCSI READ and WRITE commands. Not +supported for 6 byte cdb variants of READ and WRITE. Indicates that +data is unlikely to be required to stay in device (e.g. disk) cache. +May speed media copy and/or cause a media copy to have less impact +on other device users. +.TP +dsync +causes the O_SYNC flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. The "d" is prepended to lower confusion with the 'sync=0|1' +option which has another action (i.e. a synchronisation to media at the +end of the transfer). +.TP +excl +causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. +.TP +fua +causes the FUA (force unit access) bit to be set in SCSI READ and/or WRITE +commands. This only has effect with sg devices. The 6 byte variants +of the SCSI READ and WRITE commands do not support the FUA bit. +Only active for sg device file names. +.TP +null +has no affect, just a placeholder. +.SH RETIRED OPTIONS +Here are some retired options that are still present: +.TP +fua=0 | 1 | 2 | 3 +force unit access bit. When 3, fua is set on both \fIIFILE\fR and +\fIOFILE\fR; when 2, fua is set on \fIIFILE\fR; when 1, fua is set on +\fIOFILE\fR; when 0 (default), fua is cleared on both. See the 'fua' flag. +.SH NOTES +A raw device must be bound to a block device prior to using sgm_dd. +See +.B raw(8) +for more information about binding raw devices. To be safe, the sg device +mapping to SCSI block devices should be checked with the lsscsi utility +before use. +.PP +Raw device partition information can often be found with +.B fdisk(8) +[the "\-ul" argument is useful in this respect]. +.PP +Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +The count, skip and seek parameters can take 64 bit values (i.e. very +big numbers). Other values are limited to what can fit in a signed +32 bit number. +.PP +Data usually gets to the user space in a 2 stage process: first the +SCSI adapter DMAs into kernel buffers and then the sg driver copies +this data into user memory (write operations reverse this sequence). +With memory mapped transfers a kernel buffer reserved by sg is memory +mapped (see the +.B mmap(2) +system call) into the user space. When this is done +the second (redundant) copy from kernel buffers to user space is +not needed. Hence the transfer is faster and requires less "grunt" +from the CPU. +.PP +All informative, warning and error output is sent to stderr so that +dd's output file can be stdout and remain unpolluted. If no options +are given, then the usage message is output and nothing else happens. +.PP +For sg devices this utility issues SCSI READ and WRITE (SBC) commands which +are appropriate for disks and reading from CD/DVD/BD drives. Those commands +are not formatted correctly for tape devices so sgm_dd should not be used +on tape devices. +.PP +This utility stops the copy if any error is encountered. For more +advanced "copy on error" logic see the +.B sg_dd +utility (and its 'coe' flag). +.SH EXAMPLES +See the examples given in the man page for +.B sg_dd(8). +.SH SIGNALS +The signal handling has been borrowed from dd: SIGINT, SIGQUIT and +SIGPIPE output the number of remaining blocks to be transferred and +the records in + out counts; then they have their default action. +SIGUSR1 causes the same information to be output yet the copy continues. +All output caused by signals is sent to stderr. +.SH EXIT STATUS +The exit status of sgm_dd is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. Since this utility works at a higher level +than individual commands, and there are 'coe' and 'retries' flags, +individual SCSI command failures do not necessary cause the process +to exit. +.SH AUTHORS +Written by Douglas Gilbert and Peter Allworth. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2018 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +The simplest variant of this utility is called +.B sg_dd. +A POSIX threads version of this utility called +.B sgp_dd +is in the sg3_utils package. The lmbench package contains +.B lmdd +which is also interesting. +.B dd(1), ddpt(ddpt), raw(8) diff --git a/doc/sgp_dd.8 b/doc/sgp_dd.8 new file mode 100644 index 0000000..0c99712 --- /dev/null +++ b/doc/sgp_dd.8 @@ -0,0 +1,328 @@ +.TH SGP_DD "8" "June 2018" "sg3_utils\-1.43" SG3_UTILS +.SH NAME +sgp_dd \- copy data to and from files and devices, especially SCSI +devices +.SH SYNOPSIS +.B sgp_dd +[\fIbs=BS\fR] [\fIcount=COUNT\fR] [\fIibs=BS\fR] [\fIif=IFILE\fR] +[\fIiflag=FLAGS\fR] [\fIobs=BS\fR] [\fIof=OFILE\fR] [\fIoflag=FLAGS\fR] +[\fIseek=SEEK\fR] [\fIskip=SKIP\fR] [\fI\-\-help\fR] [\fI\-\-version\fR] +.PP +[\fIbpt=BPT\fR] [\fIcoe=\fR0|1] [\fIcdbsz=\fR6|10|12|16] [\fIdeb=VERB\fR] +[\fIdio=\fR0|1] [\fIsync=\fR0|1] [\fIthr=THR\fR] [\fItime=\fR0|1] +[\fIverbose=VERB\fR] [\fI\-\-dry\-run\fR] [\fI\-\-verbose\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +Copy data to and from any files. Specialised for "files" that are +Linux SCSI generic (sg) and raw devices. Similar syntax and semantics to +.B dd(1) +but does not perform any conversions. Uses POSIX threads (often +called "pthreads") to increase the amount of parallelism. This improves +speed in some cases. +.PP +The first group in the synopsis above are "standard" Unix +.B dd(1) +operands. The second group are extra options added by this utility. +Both groups are defined below. +.SH OPTIONS +.TP +\fBbpt\fR=\fIBPT\fR +each IO transaction will be made using \fIBPT\fR blocks (or less if +near the end of the copy). Default is 128 for block sizes less that 2048 +bytes, otherwise the default is 32. So for bs=512 the reads and writes +will each convey 64 KiB of data by default (less if near the end of the +transfer or memory restrictions). When cd/dvd drives are accessed, the +block size is typically 2048 bytes and bpt defaults to 32 which again +implies 64 KiB transfers. +.TP +\fBbs\fR=\fIBS\fR +where \fIBS\fR +.B must +be the block size of the physical device. Note that this differs from +.B dd(1) +which permits 'bs' to be an integral multiple of the actual device block +size. Default is 512 which is usually correct for disks but incorrect for +cdroms (which normally have 2048 byte blocks). +.TP +\fBcdbsz\fR=6 | 10 | 12 | 16 +size of SCSI READ and/or WRITE commands issued on sg device names. +Default is 10 byte SCSI command blocks (unless calculations indicate +that a 4 byte block number may be exceeded, in which case it defaults +to 16 byte SCSI commands). +.TP +\fBcoe\fR=0 | 1 +set to 1 for continue on error. Only applies to errors on sg devices. +Thus errors on other files will stop sgp_dd. Default is 0 which +implies stop on any error. See the 'coe' flag for more information. +.TP +\fBcount\fR=\fICOUNT\fR +copy \fICOUNT\fR blocks from \fIIFILE\fR to \fIOFILE\fR. Default is the +minimum (of \fIIFILE\fR and \fIOFILE\fR) number of blocks that sg devices +report from SCSI READ CAPACITY commands or that block devices (or their +partitions) report. Normal files are not probed for their size. If +\fIskip=SKIP\fR or \fIskip=SEEK\fR are given and the count is deduced (i.e. +not explicitly given) then that count is scaled back so that the copy will +not overrun the device. If the file name is a block device partition and +\fICOUNT\fR is not given then the size of the partition rather than the +size of the whole device is used. If \fICOUNT\fR is not given and cannot be +deduced then an error message is issued and no copy takes place. +.TP +\fBdeb\fR=\fIVERB\fR +outputs debug information. If \fIVERB\fR is 0 (default) then there is +minimal debug information and as \fIVERB\fR increases so does the amount +of debug (max debug output when \fIVERB\fR is 9). +.TP +\fBdio\fR=0 | 1 +default is 0 which selects indirect IO. Value of 1 attempts direct +IO which, if not available, falls back to indirect IO and notes this +at completion. If direct IO is selected and /proc/scsi/sg/allow_dio +has the value of 0 then a warning is issued (and indirect IO is performed) +For finer grain control use 'iflag=dio' or 'oflag=dio'. +.TP +\fBibs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBif\fR=\fIIFILE\fR +read from \fIIFILE\fR instead of stdin. If \fIIFILE\fR is '\-' then stdin +is read. Starts reading at the beginning of \fIIFILE\fR unless \fISKIP\fR +is given. +.TP +\fBiflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIIFILE\fR and are ignored when +\fIIFILE\fR is stdin. +.TP +\fBobs\fR=\fIBS\fR +if given must be the same as \fIBS\fR given to 'bs=' option. +.TP +\fBof\fR=\fIOFILE\fR +write to \fIOFILE\fR instead of stdout. If \fIOFILE\fR is '\-' then writes +to stdout. If \fIOFILE\fR is /dev/null then no actual writes are performed. +If \fIOFILE\fR is '.' (period) then it is treated the same way as +/dev/null (this is a shorthand notation). If \fIOFILE\fR exists then it +is _not_ truncated; it is overwritten from the start of \fIOFILE\fR +unless 'oflag=append' or \fISEEK\fR is given. +.TP +\fBoflag\fR=\fIFLAGS\fR +where \fIFLAGS\fR is a comma separated list of one or more flags outlined +below. These flags are associated with \fIOFILE\fR and are ignored when +\fIOFILE\fR is /dev/null, '.' (period), or stdout. +.TP +\fBseek\fR=\fISEEK\fR +start writing \fISEEK\fR bs\-sized blocks from the start of \fIOFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBskip\fR=\fISKIP\fR +start reading \fISKIP\fR bs\-sized blocks from the start of \fIIFILE\fR. +Default is block 0 (i.e. start of file). +.TP +\fBsync\fR=0 | 1 +when 1, does SYNCHRONIZE CACHE command on \fIOFILE\fR at the end of the +transfer. Only active when \fIOFILE\fR is a sg device file name. +.TP +\fBthr\fR=\fITHR\fR +where \fITHR\fR is the number or worker threads (default 4) that attempt to +copy in parallel. Minimum is 1 and maximum is 16. +.TP +\fBtime\fR=0 | 1 +when 1, the transfer is timed and throughput calculation is +performed, outputting the results (to stderr) at completion. When +0 (default) no timing is performed. +.TP +\fBverbose\fR=\fIVERB\fR +increase verbosity. Same as \fIdeb=VERB\fR. Added for compatibility with +sg_dd and sgm_dd. +.TP +\fB\-d\fR, \fB\-\-dry\-run\fR +does all the command line parsing and preparation but bypasses the actual +copy or read. That preparation may include opening \fIIFILE\fR or +\fIOFILE\fR to determine their lengths. This option may be useful for +testing the syntax of complex command line invocations in advance of +executing them. +.TP +\fB\-h\fR, \fB\-\-help\fR +outputs usage message and exits. +.TP +\fB\-v\fR, \fB\-\-verbose\fR +when used once, this is equivalent to \fIverbose=1\fR. When used +twice (e.g. "\-vv") this is equivalent to \fIverbose=2\fR, etc. +.TP +\fB\-V\fR, \fB\-\-version\fR +outputs version number information and exits. +.SH FLAGS +Here is a list of flags and their meanings: +.TP +append +causes the O_APPEND flag to be added to the open of \fIOFILE\fR. For normal +files this will lead to data appended to the end of any existing data. +Cannot be used together with the \fIseek=SEEK\fR option as they conflict. +The default action of this utility is to overwrite any existing data +from the beginning of the file or, if \fISEEK\fR is given, starting at +block \fISEEK\fR. Note that attempting to 'append' to a device file (e.g. +a disk) will usually be ignored or may cause an error to be reported. +.TP +coe +continue on error. When given with 'iflag=', an error that is detected +in a single SCSI command (typically 'bpt' blocks) is noted (by an error +message sent to stderr), then zeros are substituted into the buffer +for the corresponding write operation and the copy continues. Note that the +.B sg_dd +utility is more sophisticated in such error situations when 'iflag=coe'. +When given with 'oflag=', any error reported by a SCSI WRITE command is +reported to stderr and the copy continues (as if nothing went wrong). +.TP +dio +request the sg device node associated with this flag does direct IO. +If direct IO is not available, falls back to indirect IO and notes +this at completion. If direct IO is selected and /proc/scsi/sg/allow_dio +has the value of 0 then a warning is issued (and indirect IO is performed). +.TP +direct +causes the O_DIRECT flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. This flag requires some memory alignment on IO. Hence user +memory buffers are aligned to the page size. Has no effect on sg, normal +or raw files. +.TP +dpo +set the DPO bit (disable page out) in SCSI READ and WRITE commands. Not +supported for 6 byte cdb variants of READ and WRITE. Indicates that +data is unlikely to be required to stay in device (e.g. disk) cache. +May speed media copy and/or cause a media copy to have less impact +on other device users. +.TP +dsync +causes the O_SYNC flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. The 'd' is prepended to lower confusion with the 'sync=0|1' +option which has another action (i.e. a synchronisation to media at the +end of the transfer). +.TP +excl +causes the O_EXCL flag to be added to the open of \fIIFILE\fR and/or +\fIOFILE\fR. +.TP +fua +causes the FUA (force unit access) bit to be set in SCSI READ and/or WRITE +commands. This only has effect with sg devices. The 6 byte variants +of the SCSI READ and WRITE commands do not support the FUA bit. +Only active for sg device file names. +.TP +null +has no affect, just a placeholder. +.SH RETIRED OPTIONS +Here are some retired options that are still present: +.TP +coe=0 | 1 +continue on error is 0 (off) by default. When it is 1, it is +equivalent to 'iflag=coe oflag=coe' described in the FLAGS section +above. Similar to 'conv=noerror,sync' in +.B dd(1) +utility. Default is 0 which implies stop on error. More advanced +coe=1 processing on reads is performed by the sg_dd utility. +.TP +.TP +fua=0 | 1 | 2 | 3 +force unit access bit. When 3, fua is set on both \fIIFILE\fR and +\fIOFILE\fR; when 2, fua is set on \fIIFILE\fR;, when 1, fua is set on +\fIOFILE\fR; when 0 (default), fua is cleared on both. See the 'fua' flag. +.SH NOTES +A raw device must be bound to a block device prior to using sgp_dd. +See +.B raw(8) +for more information about binding raw devices. To be safe, the sg device +mapping to SCSI block devices should be checked with 'cat /proc/scsi/scsi' +before use. +.PP +Raw device partition information can often be found with +.B fdisk(8) +[the "\-ul" argument is useful in this respect]. +.PP +Various numeric arguments (e.g. \fISKIP\fR) may include multiplicative +suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section +in the sg3_utils(8) man page. +.PP +The \fICOUNT\fR, \fISKIP\fR and \fISEEK\fR arguments can take 64 bit +values (i.e. very big numbers). Other values are limited to what can fit in +a signed 32 bit number. +.PP +Data usually gets to the user space in a 2 stage process: first the +SCSI adapter DMAs into kernel buffers and then the sg driver copies +this data into user memory (write operations reverse this sequence). +This is called "indirect IO" and there is a 'dio' option to select +"direct IO" which will DMA directly into user memory. Due to some +issues "direct IO" is disabled in the sg driver and needs a +configuration change to activate it. +.PP +All informative, warning and error output is sent to stderr so that +dd's output file can be stdout and remain unpolluted. If no options +are given, then the usage message is output and nothing else happens. +.PP +Why use sgp_dd? Because in some cases it is twice as fast as dd +(mainly with sg devices, raw devices give some improvement). +Another reason is that big copies fill the block device caches +which has a negative impact on other machine activity. +.SH SIGNALS +The signal handling has been borrowed from dd: SIGINT, SIGQUIT and +SIGPIPE output the number of remaining blocks to be transferred and +the records in + out counts; then they have their default action. +SIGUSR1 causes the same information to be output yet the copy continues. +All output caused by signals is sent to stderr. +.SH EXAMPLES +.PP +Looks quite similar in usage to dd: +.PP + sgp_dd if=/dev/sg0 of=t bs=512 count=1MB +.PP +This will copy 1 million 512 byte blocks from the device associated with +/dev/sg0 (which should have 512 byte blocks) to a file called t. +Assuming /dev/sda and /dev/sg0 are the same device then the above is +equivalent to: +.PP + dd if=/dev/sda of=t bs=512 count=1000000 +.PP +although dd's speed may improve if bs was larger and count was +correspondingly scaled. Using a raw device to do something similar on a +ATA disk: +.PP + raw /dev/raw/raw1 /dev/hda +.br + sgp_dd if=/dev/raw/raw1 of=t bs=512 count=1MB +.PP +To copy a SCSI disk partition to an ATA disk partition: +.PP + raw /dev/raw/raw2 /dev/hda3 +.br + sgp_dd if=/dev/sg0 skip=10123456 of=/dev/raw/raw2 bs=512 +.PP +This assumes a valid partition is found on the SCSI disk at the given +skip block address (past the 5 GB point of that disk) and that +the partition goes to the end of the SCSI disk. An explicit count +is probably a safer option. +.PP +To do a fast copy from one SCSI disk to another one with similar +geometry (stepping over errors on the source disk): +.PP + sgp_dd if=/dev/sg0 of=/dev/sg1 bs=512 coe=1 +.SH EXIT STATUS +The exit status of sgp_dd is 0 when it is successful. Otherwise see +the sg3_utils(8) man page. Since this utility works at a higher level +than individual commands, and there are 'coe' and 'retries' flags, +individual SCSI command failures do not necessary cause the process +to exit. +.SH AUTHORS +Written by Douglas Gilbert and Peter Allworth. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2000\-2017 Douglas Gilbert +.br +This software is distributed under the GPL version 2. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +A simpler, non\-threaded version of this utility but with more +advanced "continue on error" logic is called +.B sg_dd +and is also found in the sg3_utils package. The lmbench package contains +.B lmdd +which is also interesting. +.B raw(8), dd(1) diff --git a/examples/Makefile.freebsd b/examples/Makefile.freebsd new file mode 100644 index 0000000..fda94c3 --- /dev/null +++ b/examples/Makefile.freebsd @@ -0,0 +1,82 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?) +# the default C compiler is clang. Swap the comment marks (lines starting +# with '#') on the next 4 (non-blank) lines. +# CC = gcc +# CC = clang + +# LD = gcc +# LD = clang + + +EXECS = sg_simple5 + +# EXTRAS = sgq_dd + +MAN_PGS = +MAN_PREF = man8 + +OS_FLAGS = -DSG_LIB_FREEBSD +EXTRA_FLAGS = $(OS_FLAGS) + +# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) -I ../include +CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) -I ../include +# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) -I ../include + +CFLAGS_PTHREADS = -D_REENTRANT + +# there is no rule to make the following in the parent directory, +# it is assumed they are already built. +D_FILES = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_cmds_basic.o ../lib/sg_pt_common.o ../lib/sg_pt_freebsd.o + +LDFLAGS = -lcam + +all: $(EXECS) + +extras: $(EXTRAS) + + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) core .depend + +sg_simple5: sg_simple5.o $(D_FILES) + $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES) + + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +# Linux uses GNU make and FreeBSD uses Berkely make. The following lines +# only work in Linux. Possible solutions in FreeBSD: +# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq' +# c) build with 'make -f Makefile.freebsd' +# In Linux one can install bmake (but that won't help here). +# ifeq (.depend,$(wildcard .depend)) +# include .depend +# endif diff --git a/examples/README b/examples/README new file mode 100644 index 0000000..7a5ae28 --- /dev/null +++ b/examples/README @@ -0,0 +1,17 @@ +Building files in this directory depends on several files being already +built in the ../lib directory. So to build files here, the ./configure +needs to be executed in the parent directory followed by changing +directory to the lib directory and calling 'make' there. +Another way is to do a top level 'make' after the ./configure which +will make the libraries followed by all the utilities in the src/ +directory. To make them in FreeBSD use 'make -f Makefile.freebsd' . + +There is an brief explanation of each example in the README file in +the main (i.e. this directory's parent) directory. There are also +some notes at the top of each source file. + +Some files that were previously in this directory have been moved to +the 'testing' directory. + +Douglas Gilbert +19th January 2018 diff --git a/examples/forwarded_sense.txt b/examples/forwarded_sense.txt new file mode 100644 index 0000000..98d8ab4 --- /dev/null +++ b/examples/forwarded_sense.txt @@ -0,0 +1,16 @@ +# Test forwarded sense data. Values are in hex. +# Invocation: 'sg_decode_sense -f forwarded_sense.txt' [dpg 20110209] + +# descriptor header +72 6 18 7 0 0 0 1c + +# Forwarded sense [len=12] +c a 1 2 +72 6 18 7 0 0 0 0 + +# Information [len=12] +0 a 80 0 11 22 33 44 55 66 77 88 + +# FRU [len=4] +3 2 0 + 99 diff --git a/examples/nvme_dev_self_test.hex b/examples/nvme_dev_self_test.hex new file mode 100644 index 0000000..1ef87c6 --- /dev/null +++ b/examples/nvme_dev_self_test.hex @@ -0,0 +1,20 @@ +# 64 byte NVMe Device Self Test command (an Admin command) that is suitable +# for: +# sg_raw --cmdfile= +# +# There is no data-in or data-out associated with this command. This command +# is optional so check the Identify controller command response to see if +# it is supported. +# +# The following invocation will self test the controller and all its +# namespaces (since nsid=0xffffffff) and does a "short" self test on each +# one (since CDW10 is 0x1). + +14 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +# A typical invocation in Linux and FreeBSD would look like this: +# sg_raw --cmdfile=nvme_dev_self_test.hex /dev/nvme0 +# diff --git a/examples/nvme_identify_ctl.hex b/examples/nvme_identify_ctl.hex new file mode 100644 index 0000000..f22141e --- /dev/null +++ b/examples/nvme_identify_ctl.hex @@ -0,0 +1,27 @@ +# 64 byte NVMe Identify controller command (an Admin command) that is +# suitable for: +# sg_raw --cmdfile= --request=4096 +# +# The address field (at byte offset 24, 8 bytes and little endian) gives +# special meaning to the highest address pointers: +# ffffffff fffffffe use address of data-in buffer +# ffffffff fffffffd use address of data-out buffer +# +# The data length field (at byte offset 36, 4 bytes and little endian) +# gives special meaning to the highest block counts: +# fffffffe use byte length of data-in buffer +# fffffffd use byte length of data-out buffer +# +# Since The Identify command reads data "in" from the device, then the +# data-in buffer is appropriate. + +06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 fe ff ff ff ff ff ff ff +00 00 00 00 fe ff ff ff 01 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +# A typical invocation in Linux and FreeBSD would look like this: +# sg_raw --cmdfile=nvme_identify_ctl.hex -r 4k /dev/nvme0 +# +# NVMe likes "4k" (4096 bytes) buffer size, preferably aligned to +# a 4096 byte (or "page") boundary. diff --git a/examples/reassign_addr.txt b/examples/reassign_addr.txt new file mode 100644 index 0000000..9d48d00 --- /dev/null +++ b/examples/reassign_addr.txt @@ -0,0 +1,11 @@ +# This file is an example for the sg_reassign utility. +# That utility can take one or more logical block addresses from stdin when +# either the '--address=-" or "-a -" option is given on the command line. + +# To see logical block addresses placed in the command parameter block +# without executing them command try something like: +# 'sg_reassign --address=- --dummy -vv /dev/sda < reassign_addr.txt + +1,34,0x33,0X444 0x89abcde 0xdeadbeef # 6 lba's + +# dpg 20070130 diff --git a/examples/ref_sense.txt b/examples/ref_sense.txt new file mode 100644 index 0000000..89bca5b --- /dev/null +++ b/examples/ref_sense.txt @@ -0,0 +1,7 @@ +# Test User data segment referral sense data. Values are in hex. +# Invocation: 'sg_decode_sense -f ref_sense.txt' [dpg 20110208] + +72,0,0,0,0,0,0 38 +b,36,1,0 +0,0,0,2,11,11,11,11,22,22,22,22,55,55,55,55,66,66,66,66 1,0,0,7, 2,0,0,8 +0,0,0,1,77,77,77,77,77,77,77,77,88,88,88,88,88,88,88,88, 3,0,0,5 diff --git a/examples/scsi_inquiry.c b/examples/scsi_inquiry.c new file mode 100644 index 0000000..32fe74d --- /dev/null +++ b/examples/scsi_inquiry.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ /* glibc hides this file sometimes */ + +/* Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg") + device driver. +* Copyright (C) 1999 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + This program does a SCSI inquiry command on the given device and + outputs some of the result. This program highlights the use of the + SCSI_IOCTL_SEND_COMMAND ioctl. This should be able to be applied to + any SCSI device file descriptor (not just one related to sg). [Whether + this is a good idea on a disk while it is mounted is debatable. + No detrimental effects when this was tested ...] + +Version 0.15 20160528 +*/ + + +typedef struct my_scsi_ioctl_command { + unsigned int inlen; /* _excluding_ scsi command length */ + unsigned int outlen; + unsigned char data[1]; /* was 0 but that's not ISO C!! */ + /* on input, scsi command starts here then opt. data */ +} My_Scsi_Ioctl_Command; + +#define OFF (2 * sizeof(unsigned int)) + +#ifndef SCSI_IOCTL_SEND_COMMAND +#define SCSI_IOCTL_SEND_COMMAND 1 +#endif + +#define INQUIRY_CMD 0x12 +#define INQUIRY_CMDLEN 6 +#define INQUIRY_REPLY_LEN 96 + + +int main(int argc, char * argv[]) +{ + int s_fd, res, k, to; + unsigned char inq_cdb [INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, + INQUIRY_REPLY_LEN, 0}; + unsigned char * inqBuff = (unsigned char *) + malloc(OFF + sizeof(inq_cdb) + 512); + unsigned char * buffp = inqBuff + OFF; + My_Scsi_Ioctl_Command * ishp = (My_Scsi_Ioctl_Command *)inqBuff; + char * file_name = 0; + int do_nonblock = 0; + int oflags = 0; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-n")) + do_nonblock = 1; + else if (*argv[k] != '-') + file_name = argv[k]; + else { + printf("Unrecognized argument '%s'\n", argv[k]); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'scsi_inquiry [-n] '\n"); + printf(" where: -n open device in non-blocking mode\n"); + printf(" Examples: scsi_inquiry /dev/sda\n"); + printf(" scsi_inquiry /dev/sg0\n"); + printf(" scsi_inquiry -n /dev/scd0\n"); + return 1; + } + + if (do_nonblock) + oflags = O_NONBLOCK; + s_fd = open(file_name, oflags | O_RDWR); + if (s_fd < 0) { + if ((EROFS == errno) || (EACCES == errno)) { + s_fd = open(file_name, oflags | O_RDONLY); + if (s_fd < 0) { + perror("scsi_inquiry: open error"); + return 1; + } + } + else { + perror("scsi_inquiry: open error"); + return 1; + } + } + /* Don't worry, being very careful not to write to a none-scsi file ... */ + res = ioctl(s_fd, SCSI_IOCTL_GET_BUS_NUMBER, &to); + if (res < 0) { + /* perror("ioctl on scsi device, error"); */ + printf("scsi_inquiry: not a scsi device\n"); + return 1; + } + + ishp->inlen = 0; + ishp->outlen = INQUIRY_REPLY_LEN; + memcpy(buffp, inq_cdb, INQUIRY_CMDLEN); + res = ioctl(s_fd, SCSI_IOCTL_SEND_COMMAND, inqBuff); + if (0 == res) { + to = (int)*(buffp + 7); + printf(" %.8s %.16s %.4s, byte_7=0x%x\n", buffp + 8, + buffp + 16, buffp + 32, to); + } + else if (res < 0) + perror("scsi_inquiry: SCSI_IOCTL_SEND_COMMAND err"); + else + printf("scsi_inquiry: SCSI_IOCTL_SEND_COMMAND status=0x%x\n", res); + + res = close(s_fd); + if (res < 0) { + perror("scsi_inquiry: close error"); + return 1; + } + return 0; +} diff --git a/examples/sdiag_sas_p0_cjtpat.txt b/examples/sdiag_sas_p0_cjtpat.txt new file mode 100644 index 0000000..7281092 --- /dev/null +++ b/examples/sdiag_sas_p0_cjtpat.txt @@ -0,0 +1,12 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 0 of the +# given device into CJTPAT (jitter pattern) generation mode. +# Physical transmission speed is 3 Gbps +# N.B. This will turn the receiver off on phy id 0. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,0,1,2,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_cjtpat.txt b/examples/sdiag_sas_p1_cjtpat.txt new file mode 100644 index 0000000..c82a558 --- /dev/null +++ b/examples/sdiag_sas_p1_cjtpat.txt @@ -0,0 +1,13 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 1 of the +# given device into CJTPAT (jitter pattern) generation mode. +# Physical transmission speed is 3 Gbps +# See sdiag_sas_p1_stop.txt to turn off this test pattern. +# N.B. This will turn the receiver off on phy id 1. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,1,2,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_idle.txt b/examples/sdiag_sas_p1_idle.txt new file mode 100644 index 0000000..39091ce --- /dev/null +++ b/examples/sdiag_sas_p1_idle.txt @@ -0,0 +1,14 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 1 of the +# given device into IDLE (continuously transmit idle dwords) mode. +# Physical transmission speed is 3 Gbps (last number on first +# active line can be 8 for 1.5Gbps, 9 for 3Gbps and 10 for 6Gbps). +# See sdiag_sas_p1_stop.txt to turn off this test pattern. +# N.B. This will turn the receiver off on phy id 1. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,1,12,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_stop.txt b/examples/sdiag_sas_p1_stop.txt new file mode 100644 index 0000000..55b95c3 --- /dev/null +++ b/examples/sdiag_sas_p1_stop.txt @@ -0,0 +1,11 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to stop phy identifier 1 of the +# given device producing a test pattern. +# N.B. This should make phy id 1 usable for SAS protocols again. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,0,2,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sg__sat_identify.c b/examples/sg__sat_identify.c new file mode 100644 index 0000000..c70eec0 --- /dev/null +++ b/examples/sg__sat_identify.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program uses a ATA PASS-THROUGH (16) SCSI command to package + an ATA IDENTIFY DEVICE (A1h) command. If the '-p' option is given, + it will package an ATA IDENTIFY PACKET DEVICE (Ech) instead (for + ATAPI device like cd/dvd drives) See http://www.t10.org + SAT draft at time of writing: sat-r08a.pdf + + Invocation: sg__sat_identify [-p] [-v] [-V] + + With SAT, the user can find out whether a device is an ATA disk or + an ATAPI device. The ATA Information VPD page contains a "command + code" field in byte 56. Its values are either ECh for a (s/p)ATA + disk, A1h for a (s/p)ATAPI device, or 0 for unknown. + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ + +#define ATA_IDENTIFY_DEVICE 0xec +#define ATA_IDENTIFY_PACKET_DEVICE 0xa1 +#define ID_RESPONSE_LEN 512 + +#define EBUFF_SZ 256 + +static char * version_str = "1.04 20180220"; + +static void usage() +{ + fprintf(stderr, "Usage: " + "sg__sat_identify [-p] [-v] [-V] \n" + " where: -p do IDENTIFY PACKET DEVICE (def: IDENTIFY " + "DEVICE) command\n" + " -v increase verbosity\n" + " -V print version string and exit\n\n" + "Performs a IDENTIFY (PACKET) DEVICE ATA command via a SAT " + "pass through\n"); +} + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[ID_RESPONSE_LEN]; + uint8_t sense_buffer[32]; + int do_packet = 0; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 4; /* PIO data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * cucp; + + memset(inBuff, 0, sizeof(inBuff)); + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-p")) + ++do_packet; + else if (0 == strcmp(argv[k], "-v")) + ++verbose; + else if (0 == strcmp(argv[k], "-vv")) + verbose += 2; + else if (0 == strcmp(argv[k], "-vvv")) + verbose += 3; + else if (0 == strcmp(argv[k], "-V")) { + fprintf(stderr, "version: %s\n", version_str); + exit(0); + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + usage(); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg__sat_identify: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[6] = 1; /* sector count */ + apt_cdb[14] = (do_packet ? ATA_IDENTIFY_PACKET_DEVICE : + ATA_IDENTIFY_DEVICE); + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | + (byte_block << 2) | t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = ID_RESPONSE_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg__sat_identify: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + if (verbose) + sg_chk_n_print3(">>> ATA_16 command", &io_hdr, 1); + /* check for ATA Return Descriptor */ + cucp = sg_scsi_sense_desc_find(io_hdr.sbp, io_hdr.sb_len_wr, + SAT_ATA_RETURN_DESC); + if (cucp && (cucp[3])) { + if (cucp[3] & 0x4) { + printf("error in returned FIS: aborted command\n"); + printf(" try again with%s '-p' option\n", + (do_packet ? "out" : "")); + break; + } + } + ok = 1; /* not sure what is happening so output response */ + if (0 == verbose) { + printf(">>> Recovered error on ATA_16, may have failed\n"); + printf(" Add '-v' for more information\n"); + } + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + printf("Response for IDENTIFY %sDEVICE ATA command:\n", + (do_packet ? "PACKET " : "")); + dWordHex((const unsigned short *)inBuff, 256, 0, + sg_is_big_endian()); + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg__sat_phy_event.c b/examples/sg__sat_phy_event.c new file mode 100644 index 0000000..40f38e1 --- /dev/null +++ b/examples/sg__sat_phy_event.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include + +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program uses a ATA PASS-THROUGH (16) SCSI command defined + by SAT to package an ATA READ LOG EXT (2Fh) command to fetch + log page 11h. That page contains SATA phy event counters. + For SAT see http://www.t10.org [draft prior to standard: sat-r09.pdf] + For ATA READ LOG EXT command see ATA-8/ACS at www.t13.org . + For SATA phy counter definitions see SATA 2.5 . + + Invocation: sg_sat_phy_event [-v] [-V] + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return Descriptor */ + +#define ATA_READ_LOG_EXT 0x2f +#define SATA_PHY_EVENT_LPAGE 0x11 +#define READ_LOG_EXT_RESPONSE_LEN 512 + +#define EBUFF_SZ 256 + +static const char * version_str = "1.03 20180220"; + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"ignore", no_argument, 0, 'i'}, + {"raw", no_argument, 0, 'r'}, + {"reset", no_argument, 0, 'R'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +static void usage() +{ + fprintf(stderr, "Usage: " + "sg_sat_phy_event [--help] [--hex] [--raw] [--reset] [--verbose]\n" + " [--version] DEVICE\n" + " where:\n" + " --help|-h print this usage message then exit\n" + " --hex|-H output response in hex bytes, use twice for\n" + " hex words\n" + " --ignore|-i ignore identifier names, output id value " + "instead\n" + " --raw|-r output response in binary to stdout\n" + " --reset|-R reset counters (after read)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string then exit\n\n" + "Sends an ATA READ LOG EXT command via a SAT pass through to " + "fetch\nlog page 11h which contains SATA phy event counters\n"); +} + +struct phy_event_t { + int id; + const char * desc; +}; + +static struct phy_event_t phy_event_arr[] = { + {0x1, "Command failed and ICRC error bit set in Error register"}, + {0x2, "R_ERR(p) response for data FIS"}, + {0x3, "R_ERR(p) response for device-to-host data FIS"}, + {0x4, "R_ERR(p) response for host-to-device data FIS"}, + {0x5, "R_ERR(p) response for non-data FIS"}, + {0x6, "R_ERR(p) response for device-to-host non-data FIS"}, + {0x7, "R_ERR(p) response for host-to-device non-data FIS"}, + {0x8, "Device-to-host non-data FIS retries"}, + {0x9, "Transition from drive PHYRDY to drive PHYRDYn"}, + {0xa, "Signature device-to-host register FISes due to COMRESET"}, + {0xb, "CRC errors within host-to-device FIS"}, + {0xd, "non CRC errors within host-to-device FIS"}, + {0xf, "R_ERR(p) response for host-to-device data FIS, CRC"}, + {0x10, "R_ERR(p) response for host-to-device data FIS, non-CRC"}, + {0x12, "R_ERR(p) response for host-to-device non-data FIS, CRC"}, + {0x13, "R_ERR(p) response for host-to-device non-data FIS, non-CRC"}, + {0xc00, "PM: host-to-device non-data FIS, R_ERR(p) due to collision"}, + {0xc01, "PM: signature register - device-to-host FISes"}, + {0xc02, "PM: corrupts CRC propagation of device-to-host FISes"}, + {0x0, NULL}, +}; + +static const char * find_phy_desc(int id) +{ + const struct phy_event_t * pep; + + for (pep = phy_event_arr; pep->desc; ++pep) { + if ((id & 0xfff) == pep->id) + return pep->desc; + } + return NULL; +} + +static void dStrRaw(const uint8_t * str, int len) +{ + int k; + + for (k = 0 ; k < len; ++k) + printf("%c", str[k]); +} + +int main(int argc, char * argv[]) +{ + int sg_fd, c, k, j, ok, res, id, len, vendor; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * device_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[READ_LOG_EXT_RESPONSE_LEN]; + uint8_t sense_buffer[64]; + int hex = 0; + int ignore = 0; + int raw = 0; + int reset = 0; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 4; /* PIO data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * cucp; + int ret = 0; + uint64_t ull; + const char * cp; + + memset(inBuff, 0, sizeof(inBuff)); + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "hHirRvV", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + usage(); + exit(0); + case 'H': + ++hex; + break; + case 'i': + ++ignore; + break; + case 'r': + ++raw; + break; + case 'R': + ++reset; + break; + case 'v': + ++verbose; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + exit(0); + default: + fprintf(stderr, "unrecognised option code %c [0x%x]\n", c, c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if (NULL == device_name) { + device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (0 == device_name) { + fprintf(stderr, "no DEVICE name detected\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + + if ((sg_fd = open(device_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_phy_event: error opening file: %s", device_name); + perror(ebuff); + return SG_LIB_FILE_ERROR; + } + + /* Prepare SCSI ATA PASS-THROUGH COMMAND (16) command */ + if (reset > 0) + apt_cdb[4] = 1; /* features (7:0) */ + apt_cdb[6] = 1; /* sector count */ + apt_cdb[8] = SATA_PHY_EVENT_LPAGE; /* lba_low (7:0) */ + apt_cdb[14] = ATA_READ_LOG_EXT; /* command */ + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) | + t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = READ_LOG_EXT_RESPONSE_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_phy_event: SG_IO ioctl error"); + close(sg_fd); + return SG_LIB_CAT_OTHER; + } + + /* now for the error processing */ + ok = 0; + ret = sg_err_category3(&io_hdr); + switch (ret) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + if (verbose) + sg_chk_n_print3(">>> ATA_16 command", &io_hdr, 1); + /* check for ATA Return Descriptor */ + cucp = sg_scsi_sense_desc_find(io_hdr.sbp, io_hdr.sb_len_wr, + SAT_ATA_RETURN_DESC); + if (cucp && (cucp[3])) { + if (cucp[3] & 0x4) { + fprintf(stderr, "error in returned FIS: aborted command\n"); + break; + } + } + ret = 0; + ok = 1; /* not sure what is happening so output response */ + if (0 == verbose) { + fprintf(stderr, ">>> Recovered error on ATA_16, may have " + "failed\n"); + fprintf(stderr, " Add '-v' for more information\n"); + } + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + if (raw > 0) + dStrRaw(inBuff, 512); + else { + if (verbose && hex) + fprintf(stderr, "Response to READ LOG EXT (page=11h):\n"); + if (1 == hex) + hex2stdout(inBuff, 512, 0); + else if (hex > 1) + dWordHex((const unsigned short *)inBuff, 256, 0, + sg_is_big_endian()); + else { + printf("SATA phy event counters:\n"); + for (k = 4; k < 512; k += (len + 2)) { + id = (inBuff[k + 1] << 8) + inBuff[k]; + if (0 == id) + break; + len = ((id >> 12) & 0x7) * 2; + vendor = !!(id & 0x8000); + id = id & 0xfff; + ull = 0; + for (j = len - 1; j >= 0; --j) { + if (j < (len - 1)) + ull <<= 8; + ull |= inBuff[k + 2 + j]; + } + cp = NULL; + if ((0 == vendor) && (0 == ignore)) + cp = find_phy_desc(id); + if (cp) + printf(" %s: %" PRIu64 "\n", cp, ull); + else + printf(" id=0x%x, vendor=%d, data_len=%d, " + "val=%" PRIu64 "\n", id, vendor, len, ull); + } + } + } + } + res = close(sg_fd); + if (res < 0) { + fprintf(stderr, "close error: %s\n", safe_strerror(-res)); + if (0 == ret) + return SG_LIB_FILE_ERROR; + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} diff --git a/examples/sg__sat_set_features.c b/examples/sg__sat_set_features.c new file mode 100644 index 0000000..fc7a8cc --- /dev/null +++ b/examples/sg__sat_set_features.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program performs a ATA PASS-THROUGH (16) SCSI command in order + to perform an ATA SET FEATURES command. See http://www.t10.org + SAT draft at time of writing: sat-r09.pdf + + Invocation: + sg_sat_set_features [-c ] [-f ] [-h] [-L ] [-v] [-V] + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ + +#define ATA_SET_FEATURES 0xef + +#define EBUFF_SZ 256 + +static char * version_str = "1.05 20180220"; + +static struct option long_options[] = { + {"count", required_argument, 0, 'c'}, + {"chk_cond", no_argument, 0, 'C'}, + {"feature", required_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"lba", required_argument, 0, 'L'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +void usage() +{ + fprintf(stderr, "Usage: " + "sg_sat_set_features [--count=C] [--chk_cond] [--feature=F] " + "[--help]\n" + " [-lba=LBA] [--verbose] [--version] " + "DEVICE\n" + " where:\n" + " --count=C|-c C count field contents (def: 0)\n" + " --chk_cond|-C set chk_cond field in pass-through " + "(def: 0)\n" + " --feature=F|-f F feature field contents (def: 0)\n" + " --help|-h output this usage message\n" + " --lba=LBA| -L LBA LBA field contents (def: 0)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Sends an ATA SET FEATURES command via a SAT pass through.\n" + "Primary feature code is placed in '--feature=F' with '--count=C' " + "and\n" + "'--lba=LBA' being auxiliaries for some features. The arguments C, " + "F and LBA\n" + "are decimal unless prefixed by '0x' or have a trailing 'h'.\n" + "Example enabling write cache: 'sg_sat_set_feature --feature=2 " + "/dev/sdc'\n"); +} + +int main(int argc, char * argv[]) +{ + int sg_fd, c, k; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char device_name[256]; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[64]; + int count = 0; + int feature = 0; + int lba = 0; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 3; /* non-data data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * bp = NULL; + + memset(device_name, 0, sizeof(device_name)); + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "c:Cf:hL:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + count = sg_get_num(optarg); + if ((count < 0) || (count > 255)) { + fprintf(stderr, "bad argument for '--count'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'C': + chk_cond = 1; + break; + case 'f': + feature = sg_get_num(optarg); + if ((feature < 0) || (feature > 255)) { + fprintf(stderr, "bad argument for '--feature'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'h': + case '?': + usage(); + return 0; + case 'L': + lba = sg_get_num(optarg); + if ((lba < 0) || (lba > 255)) { + fprintf(stderr, "bad argument for '--lba'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'v': + ++verbose; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + return 0; + default: + fprintf(stderr, "unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if ('\0' == device_name[0]) { + strncpy(device_name, argv[optind], sizeof(device_name) - 1); + device_name[sizeof(device_name) - 1] = '\0'; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + + if ('\0' == device_name[0]) { + fprintf(stderr, "missing device name!\n"); + usage(); + return 1; + } + + if ((sg_fd = open(device_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_set_features: error opening file: %s", device_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[14] = ATA_SET_FEATURES; + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) | + t_length; + apt_cdb[4] = feature; + apt_cdb[6] = count; + apt_cdb[8] = lba & 0xff; + apt_cdb[10] = (lba >> 8) & 0xff; + apt_cdb[12] = (lba >> 16) & 0xff; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.dxfer_len = 0; + io_hdr.dxferp = NULL; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_set_features: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* error processing: N.B. expect check condition, no sense ... !! */ + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: /* sat-r09 uses this sk */ + case SG_LIB_CAT_NO_SENSE: /* earlier SAT drafts used this */ + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) { + if (verbose > 1) + printf("ATA Return Descriptor expected in sense but not " + "found\n"); + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + } else if (verbose) + sg_chk_n_print3("ATA Return Descriptor", &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + default: + fprintf(stderr, "unexpected SCSI sense category\n"); + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + else if (verbose) + sg_chk_n_print3("ATA Return Descriptor, as expected", + &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg_compare_and_write.txt b/examples/sg_compare_and_write.txt new file mode 100644 index 0000000..05d72b9 --- /dev/null +++ b/examples/sg_compare_and_write.txt @@ -0,0 +1,67 @@ +# sg_compare_and_write.txt +# This file provides a usage example of sg_compare_and_write. +# sg_compare_and_write accepts a buffer containing 2 logical instances: +# - the verify instance: used to match the current content of the LBA range +# - the write instance: used to write to the LBA if the verify succeeds +# +# In case of failure to verify the data, the command will return with check +# condition with the sense code set to MISCOMPARE DURING VERIFY OPERATION. +# +# The following example shows initialization, successful and unsuccessful +# compare and write using sg3_utils. I am using caw_buf_zero2one and +# caw_buf_one2zero as shown bellow. + +$ hexdump /tmp/caw_buf_zero2one +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000200 1111 1111 1111 1111 1111 1111 1111 1111 +* +0000400 + +$ hexdump /tmp/caw_buf_one2zero +0000000 1111 1111 1111 1111 1111 1111 1111 1111 +* +0000200 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000400 + +$ sg_map -i -x +/dev/sg0 0 0 0 0 0 /dev/sda ATA ST3320613AS CC2H +/dev/sg1 3 0 0 0 5 /dev/scd0 HL-DT-ST DVD-RAM GH22NS30 1.01 +/dev/sg2 5 0 0 0 0 /dev/sdb KMNRIO K2 0000 +/dev/sg3 5 0 0 1 0 /dev/sdc KMNRIO K2 0000 + +# First I zero out the volume to make sure that the first compare and write +# will succeed +$ sg_write_same --16 -i /dev/zero -n 0x200000 -x 512 /dev/sdc + +$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000200 + +$ ./sg_compare_and_write --in=/tmp/caw_buf_zero2one --lba=100 --xferlen=1024 /dev/sdc + +# contents of LBA 100 are a block of ones +$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump +0000000 1111 1111 1111 1111 1111 1111 1111 1111 +* +0000200 + +# We repeat the same compare and write command (zero2one input buffer). +# compare and write fails since the verify failed (compared the zero block to +# the actual 1 block in LBA 100 +$ ./sg_compare_and_write --in=/tmp/caw_buf_zero2one --lba=100 --xferlen=1024 /dev/sdc +COMPARE AND WRITE: Fixed format, current; Sense key: Miscompare + Additional sense: Miscompare during verify operation +sg_compare_and_write: SCSI COMPARE AND WRITE failed + +# Now we use the second buffer (one2zero) +$ ./sg_compare_and_write --in=/tmp/caw_buf_one2zero --lba=100 --xferlen=1024 /dev/sdc + +# operation succeeded, contents of LBA 100 are back to zero +$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000200 + diff --git a/examples/sg_excl.c b/examples/sg_excl.c new file mode 100644 index 0000000..8cbab75 --- /dev/null +++ b/examples/sg_excl.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This is a simple program that tests the O_EXCL flag in sg while + executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver + +* Copyright (C) 2003-2018 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_excl [-x] + + Version 3.60 (20180220) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +#define ME "sg_excl: " + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok /*, sg_fd2 */; + uint8_t inq_cdb [INQ_CMD_LEN] = {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t tur_cdb [TUR_CMD_LEN] = {0x00, 0, 0, 0, 0, 0}; + uint8_t inqBuff[INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_excl [-x] '\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDWR | O_EXCL | O_NONBLOCK)) < 0) { + snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf(ME "%s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } +#if 0 + if ((sg_fd2 = open(file_name, O_RDWR | O_EXCL)) < 0) { + snprintf(ebuff, EBUFF_SZ, + ME "error opening file: %s a second time", file_name); + perror(ebuff); + return 1; + } else { + printf(ME "second open of %s in violation of O_EXCL\n", file_name); + close(sg_fd2); + } +#endif + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + io_hdr.dxferp = inqBuff; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror(ME "Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror(ME "Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + sleep(60); + close(sg_fd); + return 0; +} diff --git a/examples/sg_persist_tst.sh b/examples/sg_persist_tst.sh new file mode 100755 index 0000000..a75f997 --- /dev/null +++ b/examples/sg_persist_tst.sh @@ -0,0 +1,130 @@ +#!/bin/sh +# This script is meant as an example of using the sg_persist utility +# in the sg3_utils package. This script works as expected on the +# author's Fujitsu MAM3184, Seagate ST373455 and ST9146803SS disks. +# +# Version 2.0 20171104 + +# N.B. make sure the device name is correct for your environment. + +key="123abc" +key2="333aaa" +kk=${key} +rtype="1" +verbose="" + +usage() +{ + echo "Usage: sg_persist_tst.sh [-e] [-h] [-s] [-v] " + echo " where:" + echo " -e, --exclusive exclusive access (def: write " \ + "exclusive)" + echo " -h, --help print usage message" + echo " -s, --second use second key" + echo " -v, --verbose more verbose output" + echo " -vv even more verbose output" + echo " -vvv even more verbose output" + echo "" + echo "Test SCSI Persistent Reservations with sg_persist utility." + echo "Default key is ${key} and alternate, second key is ${key2} ." + echo "Should be harmless (unless one of those keys is already in use)." + echo "The APTPL bit is not set in the PR register so a power cycle" + echo "on the device will clear the reservation if this script stops" + echo "(or is stopped) before clearing it. Tape drives only seem to " + echo "support 'exclusive access' type (so use '-e')." +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + e|-exclusive) rtype="3" ;; + h|-help) usage ; exit 0 ;; + s|-second) kk=${key2} ;; + vvv) verbose="-vvv" ;; + vv) verbose="-vv" ;; + v|-verbose) verbose="-v" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +echo ">>> try to report capabilities:" +sg_persist -c ${verbose} "$1" +res=$? +case "$res" in + 0) ;; + 1) echo " syntax error" ;; + 2) echo " not ready" ;; + 3) echo " medium error" ;; + 5) echo " illegal request, report capabilities not supported?" ;; + 6) echo " unit attention" ;; + 9) echo " illegal request, Persistent Reserve (In) not supported" ;; + 11) echo " aborted command" ;; + 15) echo " file error with $1 " ;; + 20) echo " no sense" ;; + 21) echo " recovered error" ;; + 33) echo " timeout" ;; + 97) echo " response fails sanity" ;; + 98) echo " other SCSI error" ;; + 99) echo " other error" ;; + *) echo " unknown exit status for sg_persist: $res" ;; +esac +echo "" +sleep 1 + +echo ">>> check if any keys are registered:" +sg_persist --no-inquiry --read-keys ${verbose} "$1" +sleep 1 + +echo +echo ">>> register a key:" +sg_persist -n --out --register --param-sark=${kk} ${verbose} "$1" +sleep 1 + +echo +echo ">>> now key ${kk} should be registered:" +sg_persist -n --read-keys ${verbose} "$1" +sleep 1 + +echo +echo ">>> reserve the device (based on key ${kk}):" +sg_persist -n --out --reserve --param-rk=${kk} --prout-type=${rtype} ${verbose} "$1" +sleep 1 + +echo +echo ">>> check if the device is reserved (it should be now):" +sg_persist -n --read-reservation ${verbose} "$1" +sleep 1 + +echo +echo ">>> try to 'read full status' (may not be supported):" +sg_persist -n --read-full-status ${verbose} "$1" +sleep 1 + +echo +echo ">>> now release reservation:" +sg_persist -n --out --release --param-rk=${kk} --prout-type=${rtype} ${verbose} "$1" +sleep 1 + +echo +echo ">>> check if the device is reserved (it should _not_ be now):" +sg_persist -n --read-reservation ${verbose} "$1" +sleep 1 + +echo +echo ">>> unregister key ${kk}:" +sg_persist -n --out --register --param-rk=${kk} ${verbose} "$1" +sleep 1 + +echo +echo ">>> now key ${kk} should not be registered:" +sg_persist -n -k ${verbose} "$1" +sleep 1 diff --git a/examples/sg_sat_chk_power.c b/examples/sg_sat_chk_power.c new file mode 100644 index 0000000..b5ea18a --- /dev/null +++ b/examples/sg_sat_chk_power.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sg_lib.h" +#include "sg_pr2serr.h" +#include "sg_io_linux.h" + +/* This program performs a ATA PASS-THROUGH (16) SCSI command in order + to perform an ATA CHECK POWER MODE command. See http://www.t10.org + SAT draft at time of writing: sat-r09.pdf + + Invocation: sg_sat_chk_power [-v] [-V] + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ +#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d + +#define ATA_CHECK_POWER_MODE 0xe5 + +#define EBUFF_SZ 256 + +static const char * version_str = "1.07 20180706"; + + +#if 0 +/* Returns length of decoded fixed format sense for SAT ATA pass-through + * command, else returns 0. If returns 0 (expected sense data not found) + * then '\0' placed in first byte of bp. */ +static int +sg_sat_decode_fixed_sense(const uint8_t * sp, int slen, char * bp, + int max_blen, int verbose) +{ + int n; + + if ((NULL == bp) || (NULL == sp) || (max_blen < 1) || (slen < 14)) + return 0; + bp[0] = '\0'; + if ((0x70 != (0x7f & sp[0])) || + (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) || + (0 != sp[12]) || (ASCQ_ATA_PT_INFO_AVAILABLE != sp[13])) + return 0; + n = sg_scnpr(bp, max_blen, "error=0x%x, status=0x%x, device=0x%x, " + "sector_count(7:0)=0x%x%c\n", sp[3], sp[4], sp[5], sp[6], + ((0x40 & sp[8]) ? '+' : ' ')); + if (n >= max_blen) + return max_blen - 1; + n += sg_scnpr(bp + n, max_blen - n, "extend=%d, log_index=0x%x, " + "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", + (!!(0x80 & sp[8])), (0xf & sp[8]), sp[9], sp[10], sp[11], + ((0x20 & sp[8]) ? '+' : ' ')); + if (n >= max_blen) + return max_blen - 1; + if (verbose) + n += sg_scnpr(bp + n, max_blen - n, " sector_count_upper_nonzero=" + "%d, lba_upper_nonzero=%d\n", !!(0x40 & sp[8]), + !!(0x20 & sp[8])); + return (n >= max_blen) ? max_blen - 1 : n; +} +#endif + +int main(int argc, char * argv[]) +{ + int sg_fd, k; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[64]; + int verbose = 0; + int extend = 0; + int chk_cond = 1; /* set to 1 to read register(s) back */ + int protocol = 3; /* non-dat data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * bp = NULL; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-v")) + ++verbose; + else if (0 == strcmp(argv[k], "-vv")) + verbose += 2; + else if (0 == strcmp(argv[k], "-vvv")) + verbose += 3; + else if (0 == strcmp(argv[k], "-V")) { + fprintf(stderr, "version: %s\n", version_str); + exit(0); + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_sat_chk_power [-v] [-V] '\n"); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_chk_power: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[14] = ATA_CHECK_POWER_MODE; + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | + (byte_block << 2) | t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.dxfer_len = 0; + io_hdr.dxferp = NULL; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_chk_power: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* error processing: N.B. expect check condition, no sense ... !! */ + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: /* sat-r09 (latest) uses this sk */ + case SG_LIB_CAT_NO_SENSE: /* earlier SAT drafts used this */ + /* XXX: Until the spec decides which one to go with. 20060607 */ + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) { + if (verbose > 1) + printf("ATA Return Descriptor expected in sense but not " + "found\n"); + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + } else if (verbose) + sg_chk_n_print3("ATA Return Descriptor, as expected", + &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + default: + fprintf(stderr, "unexpected SCSI sense category\n"); + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + else if (verbose) + sg_chk_n_print3("ATA Return Descriptor, as expected", + &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + } + + if (bp) { + switch (bp[5]) { /* sector_count (7:0) */ + case 0xff: + printf("In active mode or idle mode\n"); + break; + case 0x80: + printf("In idle mode\n"); + break; + case 0x41: + printf("In NV power mode and spindle is spun or spinning up\n"); + break; + case 0x40: + printf("In NV power mode and spindle is spun or spinning down\n"); + break; + case 0x0: + printf("In standby mode\n"); + break; + default: + printf("unknown power mode (sector count) value=0x%x\n", bp[5]); + break; + } + } else + fprintf(stderr, "Expecting a ATA Return Descriptor in sense and " + "didn't receive it\n"); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_sat_smart_rd_data.c b/examples/sg_sat_smart_rd_data.c new file mode 100644 index 0000000..1e7295d --- /dev/null +++ b/examples/sg_sat_smart_rd_data.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program performs a ATA PASS-THROUGH (16) SCSI command in order + to perform an ATA SMART/READ DATA command. See http://www.t10.org + SAT draft at time of writing: sat-r08.pdf + + Invocation: sg_sat_smart_rd_data [-v] [-V] + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ + +#define ATA_SMART 0xb0 +#define ATA_SMART_READ_DATA 0xd0 +#define SMART_READ_DATA_RESPONSE_LEN 512 + +#define EBUFF_SZ 256 + +static char * version_str = "1.04 20180220"; + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[SMART_READ_DATA_RESPONSE_LEN]; + uint8_t sense_buffer[32]; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 4; /* PIO data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * bp = NULL; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-v")) + ++verbose; + else if (0 == strcmp(argv[k], "-vv")) + verbose += 2; + else if (0 == strcmp(argv[k], "-vvv")) + verbose += 3; + else if (0 == strcmp(argv[k], "-V")) { + fprintf(stderr, "version: %s\n", version_str); + exit(0); + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_sat_smart_rd_data [-v] [-V] '\n"); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_smart_rd_data: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[4] = ATA_SMART_READ_DATA; /* feature (7:0) */ + apt_cdb[6] = 1; /* number of block (sector count) */ + apt_cdb[10] = 0x4f; /* lba_mid (7:0) */ + apt_cdb[12] = 0xc2; /* lba_high (7:0) */ + apt_cdb[14] = ATA_SMART; + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) | + t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = SMART_READ_DATA_RESPONSE_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_smart_rd_data: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) { + if (verbose > 1) + printf("ATA Return Descriptor expected in sense but not " + "found\n"); + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + } else if (verbose) + sg_chk_n_print3("ATA Return Descriptor", &io_hdr, 1); + if (bp && bp[3]) + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + else + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + printf("Response:\n"); + dWordHex((const unsigned short *)inBuff, 256, 0, + sg_is_big_endian()); + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple1.c b/examples/sg_simple1.c new file mode 100644 index 0000000..d835267 --- /dev/null +++ b/examples/sg_simple1.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver + There is another variant of this program called "sg_simple2" + which does not include the sg_lib.h header and logic and so has + simpler but more primitive error processing. + In the lk 2.6 series devices nodes such as /dev/sda also support + the SG_IO ioctl. + +* Copyright (C) 1999-2007 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_simple1 [-x] + + Version 03.59 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + unsigned char inq_cdb [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char tur_cdb [TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple1 [-x] '\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple1: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple1: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + io_hdr.dxferp = inqBuff; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple1: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple1: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple16.c b/examples/sg_simple16.c new file mode 100644 index 0000000..e119ca3 --- /dev/null +++ b/examples/sg_simple16.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program performs a READ_16 command as scsi mid-level support + 16 byte commands from lk 2.4.15 + +* Copyright (C) 2001-2018 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_simple16 + + Version 1.04 (20180218) + +*/ + +#define READ16_REPLY_LEN 512 +#define READ16_CMD_LEN 16 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t r16_cdb [READ16_CMD_LEN] = + {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[READ16_REPLY_LEN]; + uint8_t sense_buffer[32]; + + for (k = 1; k < argc; ++k) { + if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple16 '\n"); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple16: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple16: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare READ_16 command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(r16_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = READ16_REPLY_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = r16_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple16: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on READ_16, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("READ_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + printf("READ_16 duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple2.c b/examples/sg_simple2.c new file mode 100644 index 0000000..a0710be --- /dev/null +++ b/examples/sg_simple2.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_linux_inc.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver. + There is another variant of this program called "sg_simple1" + which includes the sg_lib.h header and logic and so has more + advanced error processing. + This version demonstrates the "sg3" interface. + In the lk 2.6 series devices nodes such as /dev/sda also support + the SG_IO ioctl. + +* Copyright (C) 1999-2016 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_simple2 [-x] + + Version 03.59 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#define INQ_REPLY_LEN 96 /* logic assumes >= sizeof(inq_cdb) */ +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + + +int main(int argc, char * argv[]) +{ + int sg_fd, k; + unsigned char inq_cdb[INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char tur_cdb[TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple2 [-x] '\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple2: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple2: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + io_hdr.dxferp = inqBuff; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple2: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) { + if (io_hdr.sb_len_wr > 0) { + printf("INQUIRY sense data: "); + for (k = 0; k < io_hdr.sb_len_wr; ++k) { + if ((k > 0) && (0 == (k % 10))) + printf("\n "); + printf("0x%02x ", sense_buffer[k]); + } + printf("\n"); + } + if (io_hdr.masked_status) + printf("INQUIRY SCSI status=0x%x\n", io_hdr.status); + if (io_hdr.host_status) + printf("INQUIRY host_status=0x%x\n", io_hdr.host_status); + if (io_hdr.driver_status) + printf("INQUIRY driver_status=0x%x\n", io_hdr.driver_status); + } + else { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + } + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple2: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) { + if (io_hdr.sb_len_wr > 0) { + printf("TEST UNIT READY sense data: "); + for (k = 0; k < io_hdr.sb_len_wr; ++k) { + if ((k > 0) && (0 == (k % 10))) + printf("\n "); + printf("0x%02x ", sense_buffer[k]); + } + printf("\n"); + } + else if (io_hdr.masked_status) + printf("TEST UNIT READY SCSI status=0x%x\n", io_hdr.status); + else if (io_hdr.host_status) + printf("TEST UNIT READY host_status=0x%x\n", io_hdr.host_status); + else if (io_hdr.driver_status) + printf("TEST UNIT READY driver_status=0x%x\n", + io_hdr.driver_status); + else + printf("TEST UNIT READY unexpected error\n"); + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + } + else + printf("Test Unit Ready successful so unit is ready!\n"); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple3.c b/examples/sg_simple3.c new file mode 100644 index 0000000..4927a92 --- /dev/null +++ b/examples/sg_simple3.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver. + There is another variant of this program called "sg_simple1". + This variant demonstrates using the scatter gather facility in + the sg_io_hdr interface to break an INQUIRY response into its + component parts. + +* Copyright (C) 1999-2016 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_simple3 [-x] + + Version 03.59 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#define INQ_REPLY_BASE_LEN 8 +#define INQ_REPLY_VID_LEN 8 +#define INQ_REPLY_PID_LEN 16 +#define INQ_REPLY_PREV_LEN 4 +#define INQ_REPLY_IOVEC_COUNT 4 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + unsigned char inq_cdb[INQ_CMD_LEN] = {0x12, 0, 0, 0, + INQ_REPLY_BASE_LEN + INQ_REPLY_VID_LEN + + INQ_REPLY_PID_LEN + INQ_REPLY_PREV_LEN, 0}; + unsigned char tur_cdb[TUR_CMD_LEN] = {0x00, 0, 0, 0, 0, 0}; + sg_iovec_t iovec[INQ_REPLY_IOVEC_COUNT]; + unsigned char inqBaseBuff[INQ_REPLY_BASE_LEN]; + char inqVidBuff[INQ_REPLY_VID_LEN]; + char inqPidBuff[INQ_REPLY_PID_LEN]; + char inqPRevBuff[INQ_REPLY_PREV_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple3 [-x] '\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple3: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple3: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + io_hdr.iovec_count = INQ_REPLY_IOVEC_COUNT; + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_BASE_LEN + INQ_REPLY_VID_LEN + + INQ_REPLY_PID_LEN + INQ_REPLY_PREV_LEN; + iovec[0].iov_base = inqBaseBuff; + iovec[0].iov_len = INQ_REPLY_BASE_LEN; + iovec[1].iov_base = inqVidBuff; + iovec[1].iov_len = INQ_REPLY_VID_LEN; + iovec[2].iov_base = inqPidBuff; + iovec[2].iov_len = INQ_REPLY_PID_LEN; + iovec[3].iov_base = inqPRevBuff; + iovec[3].iov_len = INQ_REPLY_PREV_LEN; + io_hdr.dxferp = iovec; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple3: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBaseBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", inqVidBuff, inqPidBuff, inqPRevBuff); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple3: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple4.c b/examples/sg_simple4.c new file mode 100644 index 0000000..95a5023 --- /dev/null +++ b/examples/sg_simple4.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver + This variant shows mmap-ed IO being used to read the data returned + by the INQUIRY command. + +* Copyright (C) 2001-2016 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_simple4 [-x] + + Version 1.02 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#ifndef SG_FLAG_MMAP_IO +#define SG_FLAG_MMAP_IO 4 +#endif /* since /usr/include/scsi/sg.h doesn't know about this yet */ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + unsigned char inq_cdb[INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char tur_cdb[TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + unsigned char * inqBuff; + unsigned char * inqBuff2; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple4 [-x] '\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple4: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30122)) { + printf("sg_simple4: %s needs sg driver version >= 3.1.22\n", + file_name); + close(sg_fd); + return 1; + } + + /* since I know this program will only read from inqBuff then I use + PROT_READ rather than PROT_READ | PROT_WRITE */ + inqBuff = (unsigned char *)mmap(NULL, 8000, PROT_READ | PROT_WRITE, + MAP_SHARED, sg_fd, 0); + if (MAP_FAILED == inqBuff) { + snprintf(ebuff, EBUFF_SZ, "sg_simple4: error using mmap() on " + "file: %s", file_name); + perror(ebuff); + return 1; + } + if (inqBuff[0]) + printf("non-null char at inqBuff[0]\n"); + if (inqBuff[5000]) + printf("non-null char at inqBuff[5000]\n"); + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + /* io_hdr.dxferp = inqBuff; // ignored in mmap-ed IO */ + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + io_hdr.flags = SG_FLAG_MMAP_IO; + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple4: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple4: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + + /* munmap(inqBuff, 8000); */ + /* could call munmap(inqBuff, INQ_REPLY_LEN) here but following close() + causes this too happen anyway */ +#if 1 + inqBuff2 = (unsigned char *)mmap(NULL, 8000, PROT_READ | PROT_WRITE, + MAP_SHARED, sg_fd, 0); + if (MAP_FAILED == inqBuff2) { + snprintf(ebuff, EBUFF_SZ, "sg_simple4: error using mmap() 2 on " + "file: %s", file_name); + perror(ebuff); + return 1; + } + if (inqBuff2[0]) + printf("non-null char at inqBuff2[0]\n"); + if (inqBuff2[5000]) + printf("non-null char at inqBuff2[5000]\n"); + { + pid_t pid; + pid = fork(); + if (pid) { + inqBuff2[5000] = 33; + munmap(inqBuff, 8000); + sleep(3); + } + else { + inqBuff[5000] = 0xaa; + munmap(inqBuff, 8000); + sleep(1); + } + } +#endif + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple5.c b/examples/sg_simple5.c new file mode 100644 index 0000000..47bf7a0 --- /dev/null +++ b/examples/sg_simple5.c @@ -0,0 +1,236 @@ +#include +#include +#include +#include + +#include "sg_lib.h" +#include "sg_pt.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic pass through + interface. This allows this example program to be ported to + OSes other than linux. + +* Copyright (C) 2006-20018 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_simple5 [-x] + + Version 1.03 (20180220) + +*/ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define CMD_TIMEOUT_SECS 60 + + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok, dsize, res, duration, resid, cat, got, slen; + uint8_t inq_cdb [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t tur_cdb [TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + uint8_t inqBuff[INQ_REPLY_LEN]; + char * file_name = 0; + char b[512]; + uint8_t sense_b[32]; + int verbose = 0; + struct sg_pt_base * ptvp; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp("-v", argv[k])) + verbose = 1; + else if (0 == strcmp("-vv", argv[k])) + verbose = 2; + else if (0 == strcmp("-vvv", argv[k])) + verbose = 3; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple5 [-v|-vv|-vvv] '\n"); + return 1; + } + + sg_fd = scsi_pt_open_device(file_name, 1 /* ro */, 0); + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if (sg_fd < 0) { + fprintf(stderr, "error opening file: %s: %s\n", + file_name, safe_strerror(-sg_fd)); + return 1; + } + + dsize = sizeof(inqBuff); + ok = 0; + + ptvp = construct_scsi_pt_obj(); /* one object per command */ + if (NULL == ptvp) { + fprintf(stderr, "sg_simple5: out of memory\n"); + return -1; + } + set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, inqBuff, dsize); + res = do_scsi_pt(ptvp, sg_fd, CMD_TIMEOUT_SECS, verbose); + if (res < 0) { + fprintf(stderr, " pass through os error: %s\n", + safe_strerror(-res)); + goto finish_inq; + } else if (SCSI_PT_DO_BAD_PARAMS == res) { + fprintf(stderr, " bad pass through setup\n"); + goto finish_inq; + } else if (SCSI_PT_DO_TIMEOUT == res) { + fprintf(stderr, " pass through timeout\n"); + goto finish_inq; + } + if ((verbose > 1) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + fprintf(stderr, " duration=%d ms\n", duration); + resid = get_scsi_pt_resid(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + got = dsize - resid; + if (verbose && (resid > 0)) + fprintf(stderr, " requested %d bytes but " + "got %d bytes)\n", dsize, got); + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + if (verbose) { + sg_get_scsi_status_str(get_scsi_pt_status_response(ptvp), + sizeof(b), b); + fprintf(stderr, " scsi status: %s\n", b); + } + goto finish_inq; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptvp); + if (verbose) { + sg_get_sense_str("", sense_b, slen, (verbose > 1), + sizeof(b), b); + fprintf(stderr, "%s", b); + } + if (verbose && (resid > 0)) { + got = dsize - resid; + if ((verbose) || (got > 0)) + fprintf(stderr, " requested %d bytes but " + "got %d bytes\n", dsize, got); + } + goto finish_inq; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " transport: %s", b); + } + goto finish_inq; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " os: %s", b); + } + goto finish_inq; + default: + fprintf(stderr, " unknown pass through result " + "category (%d)\n", cat); + goto finish_inq; + } + + ok = 1; +finish_inq: + destruct_scsi_pt_obj(ptvp); + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s\n", p + 8, p + 16, p + 32); + } + ok = 0; + + + /* Now prepare TEST UNIT READY command */ + ptvp = construct_scsi_pt_obj(); /* one object per command */ + if (NULL == ptvp) { + fprintf(stderr, "sg_simple5: out of memory\n"); + return -1; + } + set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + /* no data in or out */ + res = do_scsi_pt(ptvp, sg_fd, CMD_TIMEOUT_SECS, verbose); + if (res < 0) { + fprintf(stderr, " pass through os error: %s\n", + safe_strerror(-res)); + goto finish_inq; + } else if (SCSI_PT_DO_BAD_PARAMS == res) { + fprintf(stderr, " bad pass through setup\n"); + goto finish_inq; + } else if (SCSI_PT_DO_TIMEOUT == res) { + fprintf(stderr, " pass through timeout\n"); + goto finish_inq; + } + if ((verbose > 1) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + fprintf(stderr, " duration=%d ms\n", duration); + resid = get_scsi_pt_resid(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + if (verbose) { + sg_get_scsi_status_str(get_scsi_pt_status_response(ptvp), + sizeof(b), b); + fprintf(stderr, " scsi status: %s\n", b); + } + goto finish_tur; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptvp); + if (verbose) { + sg_get_sense_str("", sense_b, slen, (verbose > 1), + sizeof(b), b); + fprintf(stderr, "%s", b); + } + goto finish_tur; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " transport: %s", b); + } + goto finish_tur; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " os: %s", b); + } + goto finish_tur; + default: + fprintf(stderr, " unknown pass through result " + "category (%d)\n", cat); + goto finish_tur; + } + + ok = 1; +finish_tur: + destruct_scsi_pt_obj(ptvp); + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + scsi_pt_close_device(sg_fd); + return 0; +} diff --git a/examples/sg_unmap_example.txt b/examples/sg_unmap_example.txt new file mode 100644 index 0000000..1a5f44d --- /dev/null +++ b/examples/sg_unmap_example.txt @@ -0,0 +1,40 @@ +# sg_unmap_example.txt +# This is an example of the contents of a file that can be given to sg_unmap +# For example, assume the /dev/sdc is a scratch disk (e.g. one made by the +# scsi_debug kernel module) then: +# sg_unmap --in=sg_unmap_example.txt /dev/sdc + +0x12345677,1 # unmap LBA 0x12345677 +0x12345678 2 # unmap LBA 0x12345678 and 0x12345679 +0x12340000 3333 # unmap 3333 blocks starting at LBA 0x12340000 + +0X5a5a5a5a5a 0 # unmaps 0 blocks (i.e. does nothing) + + a5a5a5h +7 # unmap 7 blocks starting at LBA 0xa5a5a5 + +# Note that there can be leading and trailing whitespace and whitespace +# (plus comma) can be a separator. +# +# Example invocation: +# $ sg_unmap --in=../examples/sg_unmap_example.txt -vv /dev/sdc +# open /dev/sg2 with flags=0x802 +# unmap cdb: 42 00 00 00 00 00 00 00 58 00 +# unmap parameter list: +# 00 56 00 50 00 00 00 00 00 00 00 00 12 34 56 77 +# 00 00 00 01 00 00 00 00 00 00 00 00 12 34 56 78 +# 00 00 00 02 00 00 00 00 00 00 00 00 12 34 00 00 +# 00 00 0d 05 00 00 00 00 00 00 00 5a 5a 5a 5a 5a +# 00 00 00 00 00 00 00 00 00 00 00 00 00 a5 a5 a5 +# 00 00 00 07 00 00 00 00 +# sg_cmds_process_resp: slen=18 +# unmap: Fixed format, current; Sense key: Illegal Request +# Additional sense: Invalid command operation code +# Raw sense data (in hex): +# 70 00 05 00 00 00 00 0a 00 00 00 00 20 00 00 00 +# 00 00 +# UNMAP not supported +# +# -------------------------------------------------------- +# Notice the 8 byte header then 5 descriptors in the parameter +# list diff --git a/examples/sgq_dd.c b/examples/sgq_dd.c new file mode 100644 index 0000000..debbe37 --- /dev/null +++ b/examples/sgq_dd.c @@ -0,0 +1,1227 @@ +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef uint8_t u_char; /* horrible, for scsi.h */ +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_unaligned.h" + +/* A utility program for the Linux OS SCSI generic ("sg") device driver. +* Copyright (C) 1999-2018 D. Gilbert and P. Allworth +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + This program is a specialization of the Unix "dd" command in which + one or both of the given files is a scsi generic device or a raw + device. A block size ('bs') is assumed to be 512 if not given. This + program complains if 'ibs' or 'obs' are given with some other value + than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If + 'of' is not given or 'of=-' then stdout assumed. Multipliers: + 'c','C' *1 'b','B' *512 'k' *1024 'K' *1000 + 'm' *(1024^2) 'M' *(1000^2) 'g' *(1024^3) 'G' *(1000^3) + + A non-standard argument "bpt" (blocks per transfer) is added to control + the maximum number of blocks in each transfer. The default value is 128. + For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16KB + in this case) are transferred to or from the sg device in a single SCSI + command. + + This version should compile with Linux sg drivers with version numbers + >= 30000 . This version uses queuing within the Linux sg driver. + +*/ + +static char * version_str = "0.61 20180627"; +/* resurrected from "0.55 20020509" */ + +#define DEF_BLOCK_SIZE 512 +#define DEF_BLOCKS_PER_TRANSFER 128 + + +#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */ +#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */ +#define S_RW_LEN 10 /* Use SCSI READ(10) and WRITE(10) */ + +#define SGP_READ10 0x28 +#define SGP_WRITE10 0x2a +#define DEF_NUM_THREADS 4 /* actually degree of concurrency */ +#define MAX_NUM_THREADS 32 + +#ifndef RAW_MAJOR +#define RAW_MAJOR 255 /*unlikey value */ +#endif + +#define FT_OTHER 0 /* filetype other than sg or raw device */ +#define FT_SG 1 /* filetype is sg char device */ +#define FT_RAW 2 /* filetype is raw char device */ + +#define QS_IDLE 0 /* ready to start a copy cycle */ +#define QS_IN_STARTED 1 /* commenced read */ +#define QS_IN_FINISHED 2 /* finished read, ready for write */ +#define QS_OUT_STARTED 3 /* commenced write */ + +#define QS_IN_POLL 11 +#define QS_OUT_POLL 12 + +#define STR_SZ 1024 +#define INOUTF_SZ 512 +#define EBUFF_SZ 512 + + +struct request_element; + +typedef struct request_collection +{ /* one instance visible to all threads */ + int infd; + int skip; + int in_type; + int in_scsi_type; + int in_blk; /* next block address to read */ + int in_count; /* blocks remaining for next read */ + int in_done_count; /* count of completed in blocks */ + int in_partial; + int outfd; + int seek; + int out_type; + int out_scsi_type; + int out_blk; /* next block address to write */ + int out_count; /* blocks remaining for next write */ + int out_done_count; /* count of completed out blocks */ + int out_partial; + int bs; + int bpt; + int dio; + int dio_incomplete; + int sum_of_resids; + int coe; + int debug; + int num_rq_elems; + struct request_element * req_arr; +} Rq_coll; + +typedef struct request_element +{ /* one instance per worker thread */ + int qstate; /* "QS" state */ + int infd; + int outfd; + int wr; + int blk; + int num_blks; + uint8_t * buffp; + uint8_t * alloc_bp; + sg_io_hdr_t io_hdr; + uint8_t cmd[S_RW_LEN]; + uint8_t sb[SENSE_BUFF_LEN]; + int bs; + int dio; + int dio_incomplete; + int resid; + int in_scsi_type; + int out_scsi_type; + int debug; +} Rq_elem; + +static Rq_coll rcoll; +static struct pollfd in_pollfd_arr[MAX_NUM_THREADS]; +static struct pollfd out_pollfd_arr[MAX_NUM_THREADS]; +static int dd_count = -1; + +static const char * proc_allow_dio = "/proc/scsi/sg/allow_dio"; + +static int sg_finish_io(int wr, Rq_elem * rep); + + +/* Returns the number of times 'ch' is found in string 's' given the + * string's length. */ +static int +num_chs_in_str(const char * s, int slen, int ch) +{ + int res = 0; + + while (--slen >= 0) { + if (ch == s[slen]) + ++res; + } + return res; +} + +static void +install_handler (int sig_num, void (*sig_handler) (int sig)) +{ + struct sigaction sigact; + sigaction (sig_num, NULL, &sigact); + if (sigact.sa_handler != SIG_IGN) + { + sigact.sa_handler = sig_handler; + sigemptyset (&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction (sig_num, &sigact, NULL); + } +} + +static void +print_stats() +{ + int infull, outfull; + + if (0 != rcoll.out_count) + fprintf(stderr, " remaining block count=%d\n", rcoll.out_count); + infull = dd_count - rcoll.in_done_count - rcoll.in_partial; + fprintf(stderr, "%d+%d records in\n", infull, rcoll.in_partial); + outfull = dd_count - rcoll.out_done_count - rcoll.out_partial; + fprintf(stderr, "%d+%d records out\n", outfull, rcoll.out_partial); +} + +static void +interrupt_handler(int sig) +{ + struct sigaction sigact; + + sigact.sa_handler = SIG_DFL; + sigemptyset (&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction (sig, &sigact, NULL); + fprintf(stderr, "Interrupted by signal,"); + print_stats (); + kill (getpid (), sig); +} + +static void +siginfo_handler(int sig) +{ + fprintf(stderr, "Progress report, continuing ...\n"); + print_stats (); + if (sig) { } /* suppress unused warning */ +} + +static int +dd_filetype(const char * filename) +{ + struct stat st; + + if (stat(filename, &st) < 0) + return FT_OTHER; + if (S_ISCHR(st.st_mode)) { + if (RAW_MAJOR == major(st.st_rdev)) + return FT_RAW; + else if (SCSI_GENERIC_MAJOR == major(st.st_rdev)) + return FT_SG; + } + return FT_OTHER; +} + +static void +usage() +{ + fprintf(stderr, "Usage: " + "sgq_dd [if=] [skip=] [of=] [seek=] " + "[bs=]\n" + " [bpt=] [count=] [dio=0|1] [thr=] " + "[coe=0|1] [gen=]\n" + " [time=0|1] [deb=] [--version]\n" + " usually either 'if' or 'of' is a sg or raw device\n" + " 'bpt' is blocks_per_transfer (default is 128)\n" + " 'dio' is direct IO, 1->attempt, 0->indirect IO (def)\n" + " 'thr' is number of queues, must be > 0, default 4, max 32\n"); + fprintf(stderr, " 'coe' continue on sg error, 0->exit (def), " + "1->zero + continue\n" + " 'time' 0->no timing(def), 1->time plus calculate throughput\n" + " 'gen' 0-> 1 file is special(def), 1-> any files allowed\n" + " 'deb' is debug, 0->none (def), > 0->varying degrees of debug\n"); +} + +/* Returns -1 for error, 0 for nothing found, QS_IN_POLL or QS_OUT_POLL */ +static int +do_poll(Rq_coll * clp, int timeout, int * req_indexp) +{ + int k, res; + + if (FT_SG == clp->out_type) { + while (((res = poll(out_pollfd_arr, clp->num_rq_elems, timeout)) < 0) + && (EINTR == errno)) + ; + if (res < 0) { + perror("poll error on output fds"); + return -1; + } + else if (res > 0) { + for (k = 0; k < clp->num_rq_elems; ++k) { + if (out_pollfd_arr[k].revents & POLLIN) { + if (req_indexp) + *req_indexp = k; + return QS_OUT_POLL; + } + } + } + } + if (FT_SG == clp->in_type) { + while (((res = poll(in_pollfd_arr, clp->num_rq_elems, timeout)) < 0) + && (EINTR == errno)) + ; + if (res < 0) { + perror("poll error on input fds"); + return -1; + } + else if (res > 0) { + for (k = 0; k < clp->num_rq_elems; ++k) { + if (in_pollfd_arr[k].revents & POLLIN) { + if (req_indexp) + *req_indexp = k; + return QS_IN_POLL; + } + } + } + } + return 0; +} + + +/* Return of 0 -> success, -1 -> failure, 2 -> try again */ +static int +read_capacity(int sg_fd, int * num_sect, int * sect_sz) +{ + int res; + uint8_t rc_cdb [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t rcBuff[64]; + uint8_t sense_b[64]; + sg_io_hdr_t io_hdr; + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(rc_cdb); + io_hdr.mx_sb_len = sizeof(sense_b); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = sizeof(rcBuff); + io_hdr.dxferp = rcBuff; + io_hdr.cmdp = rc_cdb; + io_hdr.sbp = sense_b; + io_hdr.timeout = DEF_TIMEOUT; + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("read_capacity (SG_IO) error"); + return -1; + } + res = sg_err_category3(&io_hdr); + if (SG_LIB_CAT_UNIT_ATTENTION == res) + return 2; /* probably have another go ... */ + else if (SG_LIB_CAT_CLEAN != res) { + sg_chk_n_print3("read capacity", &io_hdr, 1); + return -1; + } + *num_sect = 1 + sg_get_unaligned_be32(rcBuff + 0); + *sect_sz = sg_get_unaligned_be32(rcBuff + 4); +#ifdef DEBUG + fprintf(stderr, "number of sectors=%d, sector size=%d\n", + *num_sect, *sect_sz); +#endif + return 0; +} + +/* 0 -> ok, 1 -> short read, -1 -> error */ +static int +normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks) +{ + int res; + int stop_after_write = 0; + + rep->qstate = QS_IN_STARTED; + if (rep->debug > 8) + fprintf(stderr, "normal_in_operation: start blk=%d num_blks=%d\n", + rep->blk, rep->num_blks); + while (((res = read(rep->infd, rep->buffp, + blocks * rep->bs)) < 0) && (EINTR == errno)) + ; + if (res < 0) { + fprintf(stderr, "sgq_dd: reading, in_blk=%d, errno=%d\n", rep->blk, + errno); + return -1; + } + if (res < blocks * rep->bs) { + int o_blocks = blocks; + stop_after_write = 1; + blocks = res / rep->bs; + if ((res % rep->bs) > 0) { + blocks++; + clp->in_partial++; + } + /* Reverse out + re-apply blocks on clp */ + clp->in_blk -= o_blocks; + clp->in_count += o_blocks; + rep->num_blks = blocks; + clp->in_blk += blocks; + clp->in_count -= blocks; + } + clp->in_done_count -= blocks; + rep->qstate = QS_IN_FINISHED; + return stop_after_write; +} + +/* 0 -> ok, -1 -> error */ +static int +normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks) +{ + int res; + + rep->qstate = QS_OUT_STARTED; + if (rep->debug > 8) + fprintf(stderr, "normal_out_operation: start blk=%d num_blks=%d\n", + rep->blk, rep->num_blks); + while (((res = write(rep->outfd, rep->buffp, + rep->num_blks * rep->bs)) < 0) && (EINTR == errno)) + ; + if (res < 0) { + fprintf(stderr, "sgq_dd: output, out_blk=%d, errno=%d\n", rep->blk, + errno); + return -1; + } + if (res < blocks * rep->bs) { + blocks = res / rep->bs; + if ((res % rep->bs) > 0) { + blocks++; + clp->out_partial++; + } + rep->num_blks = blocks; + } + clp->out_done_count -= blocks; + rep->qstate = QS_IDLE; + return 0; +} + +/* Returns 1 for retryable, 0 for ok, -ve for error */ +static int +sg_fin_in_operation(Rq_coll * clp, Rq_elem * rep) +{ + int res; + + rep->qstate = QS_IN_FINISHED; + res = sg_finish_io(rep->wr, rep); + if (res < 0) { + if (clp->coe) { + memset(rep->buffp, 0, rep->num_blks * rep->bs); + fprintf(stderr, ">> substituted zeros for in blk=%d for " + "%d bytes\n", rep->blk, rep->num_blks * rep->bs); + res = 0; + } + else { + fprintf(stderr, "error finishing sg in command\n"); + return res; + } + } + if (0 == res) { /* looks good, going to return */ + if (rep->dio_incomplete || rep->resid) { + clp->dio_incomplete += rep->dio_incomplete; + clp->sum_of_resids += rep->resid; + } + clp->in_done_count -= rep->num_blks; + } + return res; +} + +/* Returns 1 for retryable, 0 for ok, -ve for error */ +static int +sg_fin_out_operation(Rq_coll * clp, Rq_elem * rep) +{ + int res; + + rep->qstate = QS_IDLE; + res = sg_finish_io(rep->wr, rep); + if (res < 0) { + if (clp->coe) { + fprintf(stderr, ">> ignored error for out blk=%d for " + "%d bytes\n", rep->blk, rep->num_blks * rep->bs); + res = 0; + } + else { + fprintf(stderr, "error finishing sg out command\n"); + return res; + } + } + if (0 == res) { + if (rep->dio_incomplete || rep->resid) { + clp->dio_incomplete += rep->dio_incomplete; + clp->sum_of_resids += rep->resid; + } + clp->out_done_count -= rep->num_blks; + } + return res; +} + +static int +sg_start_io(Rq_elem * rep) +{ + sg_io_hdr_t * hp = &rep->io_hdr; + int res; + + rep->qstate = rep->wr ? QS_OUT_STARTED : QS_IN_STARTED; + memset(rep->cmd, 0, sizeof(rep->cmd)); + rep->cmd[0] = rep->wr ? SGP_WRITE10 : SGP_READ10; + sg_put_unaligned_be32((uint32_t)rep->blk, rep->cmd + 2); + sg_put_unaligned_be16((uint16_t)rep->num_blks, rep->cmd + 7); + memset(hp, 0, sizeof(sg_io_hdr_t)); + hp->interface_id = 'S'; + hp->cmd_len = sizeof(rep->cmd); + hp->cmdp = rep->cmd; + hp->dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; + hp->dxfer_len = rep->bs * rep->num_blks; + hp->dxferp = rep->buffp; + hp->mx_sb_len = sizeof(rep->sb); + hp->sbp = rep->sb; + hp->timeout = DEF_TIMEOUT; + hp->usr_ptr = rep; + hp->pack_id = rep->blk; + if (rep->dio) + hp->flags |= SG_FLAG_DIRECT_IO; + if (rep->debug > 8) { + fprintf(stderr, "sg_start_io: SCSI %s, blk=%d num_blks=%d\n", + rep->wr ? "WRITE" : "READ", rep->blk, rep->num_blks); + sg_print_command(hp->cmdp); + fprintf(stderr, " len=%d, dxfrp=%p, cmd_len=%d\n", + hp->dxfer_len, hp->dxferp, hp->cmd_len); + } + + while (((res = write(rep->wr ? rep->outfd : rep->infd, hp, + sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno)) + ; + if (res < 0) { + if (ENOMEM == errno) + return 1; + return res; + } + return 0; +} + +/* -1 -> unrecoverable error, 0 -> successful, 1 -> try again */ +static int +sg_finish_io(int wr, Rq_elem * rep) +{ + int res; + sg_io_hdr_t io_hdr; + sg_io_hdr_t * hp; +#if 0 + static int testing = 0; /* thread dubious! */ +#endif + + memset(&io_hdr, 0 , sizeof(sg_io_hdr_t)); + /* FORCE_PACK_ID active set only read packet with matching pack_id */ + io_hdr.interface_id = 'S'; + io_hdr.dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; + io_hdr.pack_id = rep->blk; + + while (((res = read(wr ? rep->outfd : rep->infd, &io_hdr, + sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno)) + ; + if (res < 0) { + perror("finishing io on sg device, error"); + return -1; + } + if (rep != (Rq_elem *)io_hdr.usr_ptr) { + fprintf(stderr, + "sg_finish_io: bad usr_ptr, request-response mismatch\n"); + exit(1); + } + memcpy(&rep->io_hdr, &io_hdr, sizeof(sg_io_hdr_t)); + hp = &rep->io_hdr; + + switch (sg_err_category3(hp)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: + fprintf(stderr, "Recovered error on block=%d, num=%d\n", + rep->blk, rep->num_blks); + break; + case SG_LIB_CAT_UNIT_ATTENTION: + return 1; + default: + { + char ebuff[EBUFF_SZ]; + snprintf(ebuff, EBUFF_SZ, "%s blk=%d", + rep->wr ? "writing": "reading", rep->blk); + sg_chk_n_print3(ebuff, hp, 1); + return -1; + } + } +#if 0 + if (0 == (++testing % 100)) return -1; +#endif + if (rep->dio && + ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO)) + rep->dio_incomplete = 1; /* count dios done as indirect IO */ + else + rep->dio_incomplete = 0; + rep->resid = hp->resid; + if (rep->debug > 8) + fprintf(stderr, "sg_finish_io: completed %s, blk=%d\n", + wr ? "WRITE" : "READ", rep->blk); + return 0; +} + +/* Returns scsi_type or -1 for error */ +static int +sg_prepare(int fd, int sz) +{ + int res, t; + struct sg_scsi_id info; + + res = ioctl(fd, SG_GET_VERSION_NUM, &t); + if ((res < 0) || (t < 30000)) { + fprintf(stderr, "sgq_dd: sg driver prior to 3.x.y\n"); + return -1; + } + res = ioctl(fd, SG_SET_RESERVED_SIZE, &sz); + if (res < 0) + perror("sgq_dd: SG_SET_RESERVED_SIZE error"); +#if 0 + t = 1; + res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t); + if (res < 0) + perror("sgq_dd: SG_SET_FORCE_PACK_ID error"); +#endif + res = ioctl(fd, SG_GET_SCSI_ID, &info); + if (res < 0) { + perror("sgq_dd: SG_SET_SCSI_ID error"); + return -1; + } + else + return info.scsi_type; +} + +/* Return 0 for ok, anything else for errors */ +static int +prepare_rq_elems(Rq_coll * clp, const char * inf, const char * outf) +{ + int k; + Rq_elem * rep; + size_t psz; + char ebuff[EBUFF_SZ]; + int sz = clp->bpt * clp->bs; + int scsi_type; + + clp->req_arr = malloc(sizeof(Rq_elem) * clp->num_rq_elems); + if (NULL == clp->req_arr) + return 1; + for (k = 0; k < clp->num_rq_elems; ++k) { + rep = &clp->req_arr[k]; + memset(rep, 0, sizeof(Rq_elem)); + psz = getpagesize(); + if (NULL == (rep->alloc_bp = malloc(sz + psz))) + return 1; + rep->buffp = (uint8_t *) + (((unsigned long)rep->alloc_bp + psz - 1) & (~(psz - 1))); + rep->qstate = QS_IDLE; + rep->bs = clp->bs; + rep->dio = clp->dio; + rep->debug = clp->debug; + rep->out_scsi_type = clp->out_scsi_type; + if (FT_SG == clp->in_type) { + if (0 == k) + rep->infd = clp->infd; + else { + if ((rep->infd = open(inf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg reading", inf); + perror(ebuff); + return 1; + } + } + in_pollfd_arr[k].fd = rep->infd; + in_pollfd_arr[k].events = POLLIN; + if ((scsi_type = sg_prepare(rep->infd, sz)) < 0) + return 1; + if (0 == k) + clp->in_scsi_type = scsi_type; + rep->in_scsi_type = clp->in_scsi_type; + } + else + rep->infd = clp->infd; + + if (FT_SG == clp->out_type) { + if (0 == k) + rep->outfd = clp->outfd; + else { + if ((rep->outfd = open(outf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg writing", outf); + perror(ebuff); + return 1; + } + } + out_pollfd_arr[k].fd = rep->outfd; + out_pollfd_arr[k].events = POLLIN; + if ((scsi_type = sg_prepare(rep->outfd, sz)) < 0) + return 1; + if (0 == k) + clp->out_scsi_type = scsi_type; + rep->out_scsi_type = clp->out_scsi_type; + } + else + rep->outfd = clp->outfd; + } + return 0; +} + +/* Returns a "QS" code and req index, or QS_IDLE and position of first idle + (-1 if no idle position). Returns -1 on poll error. */ +static int +decider(Rq_coll * clp, int first_xfer, int * req_indexp) +{ + int k, res; + Rq_elem * rep; + int first_idle_index = -1; + int lowest_blk_index = -1; + int times; + int try_poll = 0; + int lowest_blk = INT_MAX; + + times = first_xfer ? 1 : clp->num_rq_elems; + for (k = 0; k < times; ++k) { + rep = &clp->req_arr[k]; + if ((QS_IN_STARTED == rep->qstate) || + (QS_OUT_STARTED == rep->qstate)) + try_poll = 1; + else if ((QS_IN_FINISHED == rep->qstate) && (rep->blk < lowest_blk)) { + lowest_blk = rep->blk; + lowest_blk_index = k; + } + else if ((QS_IDLE == rep->qstate) && (first_idle_index < 0)) + first_idle_index = k; + } + if (try_poll) { + res = do_poll(clp, 0, req_indexp); + if (0 != res) + return res; + } + + if (lowest_blk_index >= 0) { + if (req_indexp) + *req_indexp = lowest_blk_index; + return QS_IN_FINISHED; + } +#if 0 + if (try_poll) { + res = do_poll(clp, 2, req_indexp); + if (0 != res) + return res; + } +#endif + if (req_indexp) + *req_indexp = first_idle_index; + return QS_IDLE; +} + + +int +main(int argc, char * argv[]) +{ + bool verbose_given = false; + bool version_given = false; + int skip = 0; + int seek = 0; + int ibs = 0; + int obs = 0; + char str[STR_SZ]; + char * key; + char * buf; + char inf[INOUTF_SZ]; + char outf[INOUTF_SZ]; + int res, k, n, keylen; + int in_num_sect = 0; + int out_num_sect = 0; + int num_threads = DEF_NUM_THREADS; + int gen = 0; + int do_time = 0; + int in_sect_sz, out_sect_sz, first_xfer, qstate, req_index, seek_skip; + int blocks, stop_after_write, terminate; + char ebuff[EBUFF_SZ]; + Rq_elem * rep; + struct timeval start_tm, end_tm; + + memset(&rcoll, 0, sizeof(Rq_coll)); + rcoll.bpt = DEF_BLOCKS_PER_TRANSFER; + rcoll.in_type = FT_OTHER; + rcoll.out_type = FT_OTHER; + inf[0] = '\0'; + outf[0] = '\0'; + + for(k = 1; k < argc; k++) { + if (argv[k]) + strncpy(str, argv[k], STR_SZ); + else + continue; + for(key = str, buf = key; *buf && *buf != '=';) + buf++; + if (*buf) + *buf++ = '\0'; + keylen = strlen(key); + if (strcmp(key,"if") == 0) + strncpy(inf, buf, INOUTF_SZ); + else if (strcmp(key,"of") == 0) + strncpy(outf, buf, INOUTF_SZ); + else if (0 == strcmp(key,"ibs")) + ibs = sg_get_num(buf); + else if (0 == strcmp(key,"obs")) + obs = sg_get_num(buf); + else if (0 == strcmp(key,"bs")) + rcoll.bs = sg_get_num(buf); + else if (0 == strcmp(key,"bpt")) + rcoll.bpt = sg_get_num(buf); + else if (0 == strcmp(key,"skip")) + skip = sg_get_num(buf); + else if (0 == strcmp(key,"seek")) + seek = sg_get_num(buf); + else if (0 == strcmp(key,"count")) + dd_count = sg_get_num(buf); + else if (0 == strcmp(key,"dio")) + rcoll.dio = sg_get_num(buf); + else if (0 == strcmp(key,"thr")) + num_threads = sg_get_num(buf); + else if (0 == strcmp(key,"coe")) + rcoll.coe = sg_get_num(buf); + else if (0 == strcmp(key,"gen")) + gen = sg_get_num(buf); + else if ((0 == strncmp(key,"deb", 3)) || (0 == strncmp(key,"verb", 4))) + rcoll.debug = sg_get_num(buf); + else if (0 == strcmp(key,"time")) + do_time = sg_get_num(buf); + else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) { + res = 0; + n = num_chs_in_str(key + 1, keylen - 1, 'h'); + if (n > 0) { + usage(); + return 0; + } + n = num_chs_in_str(key + 1, keylen - 1, 'v'); + if (n > 0) + verbose_given = true; + rcoll.debug += n; + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'V'); + if (n > 0) + version_given = true; + res += n; + if (res < (keylen - 1)) { + fprintf(stderr, "Unrecognised short option in '%s', try " + "'--help'\n", key); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strncmp(key, "--help", 6)) { + usage(); + return 0; + } else if (0 == strncmp(key, "--verb", 6)) { + verbose_given = true; + ++rcoll.debug; + } else if (0 == strncmp(key, "--vers", 6)) + version_given = true; + else { + fprintf(stderr, "Unrecognized argument '%s'\n", key); + usage(); + return 1; + } + } +#ifdef DEBUG + fprintf(stderr, "In DEBUG mode, "); + if (verbose_given && version_given) { + fprintf(stderr, "but override: '-vV' given, zero verbose and " + "continue\n"); + verbose_given = false; + version_given = false; + rcoll.debug = 0; + } else if (! verbose_given) { + fprintf(stderr, "set '-vv'\n"); + rcoll.debug = 2; + } else + fprintf(stderr, "keep verbose=%d\n", rcoll.debug); +#else + if (verbose_given && version_given) + fprintf(stderr, "Not in DEBUG mode, so '-vV' has no special action\n"); +#endif + if (version_given) { + fprintf(stderr, "sgq_dd for sg version 3 driver: %s\n", + version_str); + return 0; + return 0; + } + + if (argc < 2) { + usage(); + return 1; + } + if (rcoll.bs <= 0) { + rcoll.bs = DEF_BLOCK_SIZE; + fprintf(stderr, "Assume default 'bs' (block size) of %d bytes\n", + rcoll.bs); + } + if ((ibs && (ibs != rcoll.bs)) || (obs && (obs != rcoll.bs))) { + fprintf(stderr, "If 'ibs' or 'obs' given must be same as 'bs'\n"); + usage(); + return 1; + } + if ((skip < 0) || (seek < 0)) { + fprintf(stderr, "skip and seek cannot be negative\n"); + return 1; + } + if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) { + fprintf(stderr, "too few or too many threads requested\n"); + usage(); + return 1; + } + if (rcoll.debug) + fprintf(stderr, "sgq_dd: if=%s skip=%d of=%s seek=%d count=%d\n", + inf, skip, outf, seek, dd_count); + install_handler (SIGINT, interrupt_handler); + install_handler (SIGQUIT, interrupt_handler); + install_handler (SIGPIPE, interrupt_handler); + install_handler (SIGUSR1, siginfo_handler); + + rcoll.infd = STDIN_FILENO; + rcoll.outfd = STDOUT_FILENO; + if (inf[0] && ('-' != inf[0])) { + rcoll.in_type = dd_filetype(inf); + + if (FT_SG == rcoll.in_type) { + if ((rcoll.infd = open(inf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg reading", inf); + perror(ebuff); + return 1; + } + } + if (FT_SG != rcoll.in_type) { + if ((rcoll.infd = open(inf, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for reading", inf); + perror(ebuff); + return 1; + } + else if (skip > 0) { + loff_t offset = skip; + + offset *= rcoll.bs; /* could exceed 32 here! */ + if (lseek(rcoll.infd, offset, SEEK_SET) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: couldn't skip to required position on %s", inf); + perror(ebuff); + return 1; + } + } + } + } + if (outf[0] && ('-' != outf[0])) { + rcoll.out_type = dd_filetype(outf); + + if (FT_SG == rcoll.out_type) { + if ((rcoll.outfd = open(outf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg writing", outf); + perror(ebuff); + return 1; + } + } + else { + if (FT_OTHER == rcoll.out_type) { + if ((rcoll.outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for writing", outf); + perror(ebuff); + return 1; + } + } + else { + if ((rcoll.outfd = open(outf, O_WRONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for raw writing", outf); + perror(ebuff); + return 1; + } + } + if (seek > 0) { + loff_t offset = seek; + + offset *= rcoll.bs; /* could exceed 32 bits here! */ + if (lseek(rcoll.outfd, offset, SEEK_SET) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: couldn't seek to required position on %s", outf); + perror(ebuff); + return 1; + } + } + } + } + if ((STDIN_FILENO == rcoll.infd) && (STDOUT_FILENO == rcoll.outfd)) { + fprintf(stderr, "Disallow both if and of to be stdin and stdout\n"); + return 1; + } + if ((FT_OTHER == rcoll.in_type) && (FT_OTHER == rcoll.out_type) && !gen) { + fprintf(stderr, "Either 'if' or 'of' must be a sg or raw device\n"); + return 1; + } + if (0 == dd_count) + return 0; + else if (dd_count < 0) { + if (FT_SG == rcoll.in_type) { + res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz); + if (2 == res) { + fprintf(stderr, "Unit attention, media changed(in), repeat\n"); + res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz); + } + if (0 != res) { + fprintf(stderr, "Unable to read capacity on %s\n", inf); + in_num_sect = -1; + } + else { + if (in_num_sect > skip) + in_num_sect -= skip; + } + } + if (FT_SG == rcoll.out_type) { + res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz); + if (2 == res) { + fprintf(stderr, "Unit attention, media changed(out), " + "repeat\n"); + res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz); + } + if (0 != res) { + fprintf(stderr, "Unable to read capacity on %s\n", outf); + out_num_sect = -1; + } + else { + if (out_num_sect > seek) + out_num_sect -= seek; + } + } + if (in_num_sect > 0) { + if (out_num_sect > 0) + dd_count = (in_num_sect > out_num_sect) ? out_num_sect : + in_num_sect; + else + dd_count = in_num_sect; + } + else + dd_count = out_num_sect; + } + if (rcoll.debug > 1) + fprintf(stderr, "Start of loop, count=%d, in_num_sect=%d, " + "out_num_sect=%d\n", dd_count, in_num_sect, out_num_sect); + if (dd_count <= 0) { + fprintf(stderr, "Couldn't calculate count, please give one\n"); + return 1; + } + + rcoll.in_count = dd_count; + rcoll.in_done_count = dd_count; + rcoll.skip = skip; + rcoll.in_blk = skip; + rcoll.out_count = dd_count; + rcoll.out_done_count = dd_count; + rcoll.seek = seek; + rcoll.out_blk = seek; + + if ((FT_SG == rcoll.in_type) || (FT_SG == rcoll.out_type)) + rcoll.num_rq_elems = num_threads; + else + rcoll.num_rq_elems = 1; + if (prepare_rq_elems(&rcoll, inf, outf)) { + fprintf(stderr, "Setup failure, perhaps no memory\n"); + return 1; + } + + first_xfer = 1; + stop_after_write = 0; + terminate = 0; + seek_skip = rcoll.seek - rcoll.skip; + if (do_time) { + start_tm.tv_sec = 0; + start_tm.tv_usec = 0; + gettimeofday(&start_tm, NULL); + } + while (rcoll.out_done_count > 0) { /* >>>>>>>>> main loop */ + req_index = -1; + qstate = decider(&rcoll, first_xfer, &req_index); + rep = (req_index < 0) ? NULL : (rcoll.req_arr + req_index); + switch (qstate) { + case QS_IDLE: + if ((NULL == rep) || (rcoll.in_count <= 0)) { + /* usleep(1000); */ + /* do_poll(&rcoll, 10, NULL); */ + /* do_poll(&rcoll, 0, NULL); */ + break; + } + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: non-sleeping QS_IDLE state, " + "req_index=%d\n", req_index); + if (first_xfer >= 2) + first_xfer = 0; + else if (1 == first_xfer) + ++first_xfer; + if (stop_after_write) { + terminate = 1; + break; + } + blocks = (rcoll.in_count > rcoll.bpt) ? rcoll.bpt : rcoll.in_count; + rep->wr = 0; + rep->blk = rcoll.in_blk; + rep->num_blks = blocks; + rcoll.in_blk += blocks; + rcoll.in_count -= blocks; + + if (FT_SG == rcoll.in_type) { + res = sg_start_io(rep); + if (0 != res) { + if (1 == res) + fprintf(stderr, "Out of memory starting sg io\n"); + terminate = 1; + } + } + else { + res = normal_in_operation(&rcoll, rep, blocks); + if (res < 0) + terminate = 1; + else if (res > 0) + stop_after_write = 1; + } + break; + case QS_IN_FINISHED: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is QS_IN_FINISHED, " + "req_index=%d\n", req_index); + if ((rep->blk + seek_skip) != rcoll.out_blk) { + /* if write would be out of sequence then wait */ + if (rcoll.debug > 4) + fprintf(stderr, " sgq_dd: QS_IN_FINISHED, " + "out of sequence\n"); + usleep(200); + break; + } + rep->wr = 1; + rep->blk = rcoll.out_blk; + blocks = rep->num_blks; + rcoll.out_blk += blocks; + rcoll.out_count -= blocks; + + if (FT_SG == rcoll.out_type) { + res = sg_start_io(rep); + if (0 != res) { + if (1 == res) + fprintf(stderr, "Out of memory starting sg io\n"); + terminate = 1; + } + } + else { + if (normal_out_operation(&rcoll, rep, blocks) < 0) + terminate = 1; + } + break; + case QS_IN_POLL: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is QS_IN_POLL, " + "req_index=%d\n", req_index); + res = sg_fin_in_operation(&rcoll, rep); + if (res < 0) + terminate = 1; + else if (res > 1) { + if (first_xfer) { + /* only retry on first xfer */ + if (0 != sg_start_io(rep)) + terminate = 1; + } + else + terminate = 1; + } + break; + case QS_OUT_POLL: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is QS_OUT_POLL, " + "req_index=%d\n", req_index); + res = sg_fin_out_operation(&rcoll, rep); + if (res < 0) + terminate = 1; + else if (res > 1) { + if (first_xfer) { + /* only retry on first xfer */ + if (0 != sg_start_io(rep)) + terminate = 1; + } + else + terminate = 1; + } + break; + default: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is ?????\n"); + terminate = 1; + break; + } + if (terminate) + break; + } /* >>>>>>>>>>>>> end of main loop */ + + if ((do_time) && (start_tm.tv_sec || start_tm.tv_usec)) { + struct timeval res_tm; + double a, b; + + gettimeofday(&end_tm, NULL); + res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec; + res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec; + if (res_tm.tv_usec < 0) { + --res_tm.tv_sec; + res_tm.tv_usec += 1000000; + } + a = res_tm.tv_sec; + a += (0.000001 * res_tm.tv_usec); + b = (double)rcoll.bs * (dd_count - rcoll.out_done_count); + printf("time to transfer data was %d.%06d secs", + (int)res_tm.tv_sec, (int)res_tm.tv_usec); + if ((a > 0.00001) && (b > 511)) + printf(", %.2f MB/sec\n", b / (a * 1000000.0)); + else + printf("\n"); + } + + if (STDIN_FILENO != rcoll.infd) + close(rcoll.infd); + if (STDOUT_FILENO != rcoll.outfd) + close(rcoll.outfd); + res = 0; + if (0 != rcoll.out_count) { + fprintf(stderr, ">>>> Some error occurred,\n"); + res = 2; + } + print_stats(); + if (rcoll.dio_incomplete) { + int fd; + char c; + + fprintf(stderr, ">> Direct IO requested but incomplete %d times\n", + rcoll.dio_incomplete); + if ((fd = open(proc_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + fprintf(stderr, ">>> %s set to '0' but should be set " + "to '1' for direct IO\n", proc_allow_dio); + } + close(fd); + } + } + if (rcoll.sum_of_resids) + fprintf(stderr, ">> Non-zero sum of residual counts=%d\n", + rcoll.sum_of_resids); + return res; +} diff --git a/examples/transport_ids.txt b/examples/transport_ids.txt new file mode 100644 index 0000000..0c60456 --- /dev/null +++ b/examples/transport_ids.txt @@ -0,0 +1,31 @@ +# This file is an example for the sg_persist utility. +# It discusses using "TransportID"s which are defined (most recently) +# in SPC-4 revison 20 section 7.5.4 titled: "TransportID identifiers". +# +# The sg_persist utility can take one or more "transportID"s from stdin when +# either the '--transport-id=-" or "-X -" option is given on the command +# line. + +# To see transport IDs decoded after they have been read in (e.g. to check +# they are well formed) use the verbose flag 3 times (i.e. "... -vvv ..."). + +# Here is a simple example (for SPI) of a comma separted hex list: +1,0,0,7,0,0,0,1 # SPI, initiator address=7, relative_port_num=1 + +# and here is the transport specific format for the same thing: +# spi,1,7 + +# An example for SAS follows, first as a hex string, then in transport +# specific format (incremented by 1) +6,0,0,0,50,6,5,b0,0,6,f2,60 +sas,500605b00006f261 + +# For iSCSI the hex list form is awkward, better to use the transport +# specific format. [The leading spaces are ignored.] + iqn.5886.com.acme.diskarrays-sn-a8675309 + + + # Leading spaces and tabs before a '#' are ok. + + +# dpg 20090824 diff --git a/getopt_long/getopt.h b/getopt_long/getopt.h new file mode 100644 index 0000000..4d6543b --- /dev/null +++ b/getopt_long/getopt.h @@ -0,0 +1,86 @@ +/* $NetBSD: getopt.h,v 1.7 2005/02/03 04:39:32 perry Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * modified May 12, 2005 by Jim Basney + * + * removed #include of non-POSIX and + * removed references to _NETBSD_SOURCE and HAVE_NBTOOL_CONFIG_H + * added #if !HAVE_GETOPT_LONG + * removed __BEGIN_DECLS and __END_DECLS + */ + +#ifndef _MYPROXY_GETOPT_H_ +#define _MYPROXY_GETOPT_H_ + +#if !HAVE_GETOPT_LONG + +#include + +/* + * Gnu like getopt_long() and BSD4.4 getsubopt()/optreset extensions + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +extern char *optarg; +extern int optind; +extern int optopt; +extern int opterr; + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +int getopt_long(int, char * const *, const char *, + const struct option *, int *); + +#endif /* !HAVE_GETOPT_LONG */ + +#endif /* !_MYPROXY_GETOPT_H_ */ diff --git a/getopt_long/getopt_long.c b/getopt_long/getopt_long.c new file mode 100644 index 0000000..f94dcae --- /dev/null +++ b/getopt_long/getopt_long.c @@ -0,0 +1,434 @@ +/* $NetBSD: getopt_long.c,v 1.17 2004/06/20 22:20:15 jmc Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * modified May 12, 2005 by Jim Basney + * + * removed #include of non-POSIX + * removed #include of "namespace.h" + * use local "port_getopt.h" instead of + * removed REPLACE_GETOPT and HAVE_NBTOOL_CONFIG_H sections + * removed __P() from function declarations + * use ANSI C function parameter lists + * removed optreset support + * replace _DIAGASSERT() with assert() + * replace non-POSIX warnx(...) with fprintf(stderr, ...) + * added extern declarations for optarg, optind, opterr, and optopt + */ + +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD: getopt_long.c,v 1.17 2004/06/20 22:20:15 jmc Exp $"); +#endif /* LIBC_SCCS and not lint */ + +#include +#include +#include "getopt.h" +#include +#include +#include + +#ifdef __weak_alias +__weak_alias(getopt_long,_getopt_long) +#endif + +#if !HAVE_GETOPT_LONG +#define IGNORE_FIRST (*options == '-' || *options == '+') +#define PRINT_ERROR ((opterr) && ((*options != ':') \ + || (IGNORE_FIRST && options[1] != ':'))) +#define IS_POSIXLY_CORRECT (getenv("POSIXLY_CORRECT") != NULL) +#define PERMUTE (!IS_POSIXLY_CORRECT && !IGNORE_FIRST) +/* XXX: GNU ignores PC if *options == '-' */ +#define IN_ORDER (!IS_POSIXLY_CORRECT && *options == '-') + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((IGNORE_FIRST && options[1] == ':') \ + || (*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +extern char *optarg; +extern int optind, opterr, optopt; + +static int getopt_internal (int, char * const *, const char *); +static int gcd (int, int); +static void permute_args (int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return b; +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + assert(nargv != NULL); + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + * Returns -2 if -- is found (can be long option or end of options marker). + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options) +{ + char *oli; /* option letter list index */ + int optchar; + + assert(nargv != NULL); + assert(options != NULL); + + optarg = NULL; + + /* + * XXX Some programs (like rsyncd) expect to be able to + * XXX re-initialize optind to 0 and have getopt_long(3) + * XXX properly function again. Work around this braindamage. + */ + if (optind == 0) + optind = 1; + +start: + if (!*place) { /* update scanning pointer */ + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return -1; + } + if ((*(place = nargv[optind]) != '-') + || (place[1] == '\0')) { /* found non-option */ + place = EMSG; + if (IN_ORDER) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return INORDER; + } + if (!PERMUTE) { + /* + * if no permutation wanted, stop parsing + * at first non-option + */ + return -1; + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + if (place[1] && *++place == '-') { /* found "--" */ + place++; + return -2; + } + } + if ((optchar = (int)*place++) == (int)':' || + (oli = strchr(options + (IGNORE_FIRST ? 1 : 0), optchar)) == NULL) { + /* option letter unknown or ':' */ + if (!*place) + ++optind; + if (PRINT_ERROR) + fprintf(stderr, illoptchar, optchar); + optopt = optchar; + return BADCH; + } + if (optchar == 'W' && oli[1] == ';') { /* -W long-option */ + /* XXX: what if no long options provided (called by getopt)? */ + if (*place) + return -2; + + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + fprintf(stderr, recargchar, optchar); + optopt = optchar; + return BADARG; + } else /* white space */ + place = nargv[optind]; + /* + * Handle -W arg the same as --arg (which causes getopt to + * stop parsing). + */ + return -2; + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + /* XXX: disable test for :: if PC? (GNU doesn't) */ + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + fprintf(stderr, recargchar, optchar); + optopt = optchar; + return BADARG; + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return optchar; +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + int retval; + + assert(nargv != NULL); + assert(options != NULL); + assert(long_options != NULL); + /* idx may be NULL */ + + if ((retval = getopt_internal(nargc, nargv, options)) == -2) { + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = place; + match = -1; + + optind++; + place = EMSG; + + if (*current_argv == '\0') { /* found "--" */ + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return -1; + } + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == + (unsigned)current_argv_len) { + /* exact match */ + match = i; + break; + } + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + fprintf(stderr, ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return BADCH; + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + fprintf(stderr, noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of + * flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return BADARG; + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use + * next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' + * indicates no error should be generated + */ + if (PRINT_ERROR) + fprintf(stderr, recargstring, current_argv); + /* + * XXX: GNU sets optopt to val regardless + * of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return BADARG; + } + } else { /* unknown option */ + if (PRINT_ERROR) + fprintf(stderr, illoptstring, current_argv); + optopt = 0; + return BADCH; + } + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + retval = 0; + } else + retval = long_options[match].val; + if (idx) + *idx = match; + } + return retval; +} +#endif /* !GETOPT_LONG */ diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..64c27b4 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,62 @@ + +scsiincludedir = $(includedir)/scsi + +scsiinclude_HEADERS = \ + sg_lib.h \ + sg_lib_data.h \ + sg_cmds.h \ + sg_cmds_basic.h \ + sg_cmds_extra.h \ + sg_cmds_mmc.h \ + sg_pr2serr.h \ + sg_unaligned.h \ + sg_pt.h \ + sg_pt_nvme.h + +if OS_LINUX +scsiinclude_HEADERS += \ + sg_linux_inc.h \ + sg_io_linux.h \ + sg_pt_linux.h + +noinst_HEADERS = \ + sg_pt_win32.h +endif + +if OS_WIN32_MINGW +scsiinclude_HEADERS += sg_pt_win32.h + +noinst_HEADERS = \ + sg_linux_inc.h \ + sg_io_linux.h +endif + +if OS_WIN32_CYGWIN +scsiinclude_HEADERS += sg_pt_win32.h + +noinst_HEADERS = \ + sg_linux_inc.h \ + sg_io_linux.h +endif + +if OS_FREEBSD +noinst_HEADERS = \ + sg_linux_inc.h \ + sg_io_linux.h \ + sg_pt_win32.h +endif + +if OS_SOLARIS +noinst_HEADERS = \ + sg_linux_inc.h \ + sg_io_linux.h \ + sg_pt_win32.h +endif + +if OS_OSF +noinst_HEADERS = \ + sg_linux_inc.h \ + sg_io_linux.h \ + sg_pt_win32.h +endif + diff --git a/include/freebsd_nvme_ioctl.h b/include/freebsd_nvme_ioctl.h new file mode 100644 index 0000000..a7c970b --- /dev/null +++ b/include/freebsd_nvme_ioctl.h @@ -0,0 +1,168 @@ +#ifndef FREEBSD_NVME_IOCTL_H +#define FREEBSD_NVME_IOCTL_H + +/*- + * Copyright (C) 2012-2013 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NVME_PASSTHROUGH_CMD _IOWR('n', 0, struct nvme_pt_command) + +#if __FreeBSD_version < 1100110 +struct nvme_command +{ + /* dword 0 */ + uint16_t opc : 8; /* opcode */ + uint16_t fuse : 2; /* fused operation */ + uint16_t rsvd1 : 6; + uint16_t cid; /* command identifier */ + + /* dword 1 */ + uint32_t nsid; /* namespace identifier */ + + /* dword 2-3 */ + uint32_t rsvd2; + uint32_t rsvd3; + + /* dword 4-5 */ + uint64_t mptr; /* metadata pointer */ + + /* dword 6-7 */ + uint64_t prp1; /* prp entry 1 */ + + /* dword 8-9 */ + uint64_t prp2; /* prp entry 2 */ + + /* dword 10-15 */ + uint32_t cdw10; /* command-specific */ + uint32_t cdw11; /* command-specific */ + uint32_t cdw12; /* command-specific */ + uint32_t cdw13; /* command-specific */ + uint32_t cdw14; /* command-specific */ + uint32_t cdw15; /* command-specific */ +} __packed; + +struct nvme_status { + + uint16_t p : 1; /* phase tag */ + uint16_t sc : 8; /* status code */ + uint16_t sct : 3; /* status code type */ + uint16_t rsvd2 : 2; + uint16_t m : 1; /* more */ + uint16_t dnr : 1; /* do not retry */ +} __packed; + +struct nvme_completion { + + /* dword 0 */ + uint32_t cdw0; /* command-specific */ + + /* dword 1 */ + uint32_t rsvd1; + + /* dword 2 */ + uint16_t sqhd; /* submission queue head pointer */ + uint16_t sqid; /* submission queue identifier */ + + /* dword 3 */ + uint16_t cid; /* command identifier */ + struct nvme_status status; +} __packed; + +struct nvme_pt_command { + + /* + * cmd is used to specify a passthrough command to a controller or + * namespace. + * + * The following fields from cmd may be specified by the caller: + * * opc (opcode) + * * nsid (namespace id) - for admin commands only + * * cdw10-cdw15 + * + * Remaining fields must be set to 0 by the caller. + */ + struct nvme_command cmd; + + /* + * cpl returns completion status for the passthrough command + * specified by cmd. + * + * The following fields will be filled out by the driver, for + * consumption by the caller: + * * cdw0 + * * status (except for phase) + * + * Remaining fields will be set to 0 by the driver. + */ + struct nvme_completion cpl; + + /* buf is the data buffer associated with this passthrough command. */ + void * buf; + + /* + * len is the length of the data buffer associated with this + * passthrough command. + */ + uint32_t len; + + /* + * is_read = 1 if the passthrough command will read data into the + * supplied buffer from the controller. + * + * is_read = 0 if the passthrough command will write data from the + * supplied buffer to the controller. + */ + uint32_t is_read; + + /* + * driver_lock is used by the driver only. It must be set to 0 + * by the caller. + */ + struct mtx * driver_lock; +}; +#else +#include +#endif + +#define nvme_completion_is_error(cpl) \ + ((cpl)->status.sc != 0 || (cpl)->status.sct != 0) + +#define NVME_CTRLR_PREFIX "/dev/nvme" +#define NVME_NS_PREFIX "ns" + +#ifdef __cplusplus +} +#endif + +#endif /* for FREEBSD_NVME_IOCTL_H */ diff --git a/include/sg_cmds.h b/include/sg_cmds.h new file mode 100644 index 0000000..690f53a --- /dev/null +++ b/include/sg_cmds.h @@ -0,0 +1,21 @@ +#ifndef SG_CMDS_H +#define SG_CMDS_H + +/******************************************************************** + * This header did contain wrapper declarations for many SCSI commands + * up until sg3_utils version 1.22 . In that version, the command + * wrappers were broken into two groups, the 'basic' ones found in the + * "sg_cmds_basic.h" header and the 'extra' ones found in the + * "sg_cmds_extra.h" header. This header now simply includes those two + * headers. + * In sg3_utils version 1.26 the sg_cmds_mmc.h header was added and + * contains some MMC specific commands. + * The corresponding function definitions are found in the sg_cmds_basic.c, + * sg_cmds_extra.c and sg_cmds_mmc.c files. + ********************************************************************/ + +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_cmds_mmc.h" + +#endif diff --git a/include/sg_cmds_basic.h b/include/sg_cmds_basic.h new file mode 100644 index 0000000..4fb0ddf --- /dev/null +++ b/include/sg_cmds_basic.h @@ -0,0 +1,356 @@ +#ifndef SG_CMDS_BASIC_H +#define SG_CMDS_BASIC_H + +/* + * Copyright (c) 2004-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +/* + * Error, warning and verbose output is sent to the file pointed to by + * sg_warnings_strm which is declared in sg_lib.h and can be set with + * the sg_set_warnings_strm() function. If not given sg_warnings_strm + * defaults to stderr. + * If 'noisy' is false and 'verbose' is zero then following functions should + * not output anything to sg_warnings_strm. If 'noisy' is true and 'verbose' + * is zero then Unit Attention, Recovered, Medium and Hardware errors (sense + * keys) send output to sg_warnings_strm. Increasing values of 'verbose' + * send increasing amounts of (debug) output to sg_warnings_strm. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Functions with the "_pt" suffix take a pointer to an object (derived from) + * sg_pt_base rather than an open file descriptor as their first argument. + * That object is assumed to be constructed and have a device file descriptor + * associated with it. clear_scsi_pt_obj() is called at the start of each + * "_pt" function. Caller is responsible for lifetime of ptp. */ + +struct sg_pt_base; + + +/* Invokes a SCSI INQUIRY command and yields the response + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ABORTED_COMMAND, a negated errno or -1 -> other errors */ +int sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values, negated error or -1 + * for other errors. The CMDDT field is obsolete in the INQUIRY cdb (since + * spc3r16 in 2003) so * an argument to set it has been removed (use the + * REPORT SUPPORTED OPERATION CODES command instead). Adds the ability to + * set the command abort timeout and the ability to report the residual + * count. If timeout_secs is zero or less the default command abort timeout + * (60 seconds) is used. If residp is non-NULL then the residual value is + * written where residp points. A residual value of 0 implies mx_resp_len + * bytes have be written where resp points. If the residual value equals + * mx_resp_len then no bytes have been written. */ +int sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Similar to sg_ll_inquiry_v2(). See note above about "_pt" suffix. */ +int sg_ll_inquiry_pt(struct sg_pt_base * ptp, bool evpd, int pg_op, + void * resp, int mx_resp_len, int timeout_secs, + int * residp, bool noisy, int verbose); + +/* Invokes a SCSI LOG SELECT command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Log Select not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ABORTED_COMMAND, * SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code, + int subpg_code, uint8_t * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Log Sense not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, uint8_t * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Same as sg_ll_log_sense() apart from timeout_secs and residp. See + * sg_ll_inquiry_v2() for their description */ +int sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, uint8_t * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose); +/* v2 adds RTD (revert to defaults) bit, added in spc5r11 */ +int sg_ll_mode_select6_v2(int sg_fd, bool pf, bool rtd, bool sp, + void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose); +/* v2 adds RTD (revert to defaults) bit, added in spc5r11 */ +int sg_ll_mode_select10_v2(int sg_fd, bool pf, bool rtd, bool sp, + void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_ILLEGAL_REQ -> + * bad field in cdb, * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Same as sg_ll_mode_sense10() apart from timeout_secs and residp. See + * sg_ll_inquiry_v2() for their description */ +int sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, + int pg_code, int sub_pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command (SPC-3) + * prevent==0 allows removal, prevent==1 prevents removal ... + * Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> command not supported + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose); + +/* Invokes a SCSI READ CAPACITY (10) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION + * -> perhaps media changed, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success, + * SG_LIB_CAT_UNIT_ATTENTION -> media changed??, SG_LIB_CAT_INVALID_OP + * -> cdb not supported, SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Luns not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */ +int sg_ll_report_luns(int sg_fd, int select_report, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Similar to sg_ll_report_luns(). See note above about "_pt" suffix. */ +int sg_ll_report_luns_pt(struct sg_pt_base * ptp, int select_report, + void * resp, int mx_resp_len, bool noisy, + int verbose); + +/* Invokes a SCSI REQUEST SENSE command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Request Sense not supported??, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Similar to sg_ll_request_sense(). See note above about "_pt" suffix. */ +int sg_ll_request_sense_pt(struct sg_pt_base * ptp, bool desc, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI START STOP UNIT command (SBC + MMC). + * Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Start stop unit not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure + * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and + * format_layer_number(mmc) fields. They also overlap on the noflush(sbc) + * and fl(mmc) one bit field. This is the cause of the awkardly named + * pc_mod__fl_num and noflush__fl arguments to this function. */ +int sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num, + int power_cond, bool noflush__fl, bool loej, + bool start, bool noisy, int verbose); + +/* Similar to sg_ll_start_stop_unit(). See note above about "_pt" suffix. */ +int sg_ll_start_stop_unit_pt(struct sg_pt_base * ptp, bool immed, + int pc_mod__fl_num, int power_cond, + bool noflush__fl, bool loej, bool start, + bool noisy, int verbose); + +/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_INVALID_OP -> cdb not supported, + * SG_LIB_CAT_IlLEGAL_REQ -> bad field in cdb + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */ +int sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group, + unsigned int lba, unsigned int count, bool noisy, + int verbose); + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_ABORTED_COMMAND, -1 -> other failure */ +int sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose); + +/* Similar to sg_ll_test_unit_ready(). See note above about "_pt" suffix. */ +int sg_ll_test_unit_ready_pt(struct sg_pt_base * ptp, int pack_id, + bool noisy, int verbose); + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Looks for progress indicator if 'progress' non-NULL; + * if found writes value [0..65535] else write -1. + * Return of 0 -> success, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ABORTED_COMMAND, SG_LIB_CAT_NOT_READY -> + * device not ready, -1 -> other failure */ +int sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, + bool noisy, int verbose); + +/* Similar to sg_ll_test_unit_ready_progress(). See note above about "_pt" + * suffix. */ +int sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptp, int pack_id, + int * progress, bool noisy, int verbose); + + +struct sg_simple_inquiry_resp { + uint8_t peripheral_qualifier; + uint8_t peripheral_type; + uint8_t byte_1; /* was 'rmb' prior to version 1.39 */ + /* now rmb == !!(0x80 & byte_1) */ + uint8_t version; /* as per recent drafts: whole of byte 2 */ + uint8_t byte_3; + uint8_t byte_5; + uint8_t byte_6; + uint8_t byte_7; + char vendor[9]; /* T10 field is 8 bytes, NUL char appended */ + char product[17]; + char revision[5]; +}; + +/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP -> not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * a negated errno or -1 -> other errors */ +int sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose); + +/* Similar to sg_simple_inquiry(). See note above about "_pt" suffix. */ +int sg_simple_inquiry_pt(struct sg_pt_base * ptvp, + struct sg_simple_inquiry_resp * inq_data, bool noisy, + int verbose); + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. In most cases users are + * interested in the first mode page. This function returns the (byte) + * offset of the start of the first mode page. Set mode_sense_6 to true for + * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful + * or -1 if failure. If there is a failure a message is written to err_buff + * if it is non-NULL and err_buff_len > 0. */ +int sg_mode_page_offset(const uint8_t * resp, int resp_len, + bool mode_sense_6, char * err_buff, int err_buff_len); + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. This functions returns the + * length (in bytes) of those three components. Note that the return value + * can exceed resp_len in which case the MODE SENSE command should be + * re-issued with a larger response buffer. If bd_lenp is non-NULL and if + * successful the block descriptor length (in bytes) is written to *bd_lenp. + * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10) + * responses. Returns -1 if there is an error (e.g. response too short). */ +int sg_msense_calc_length(const uint8_t * resp, int resp_len, + bool mode_sense_6, int * bd_lenp); + +/* Fetches current, changeable, default and/or saveable modes pages as + * indicated by pcontrol_arr for given pg_code and sub_pg_code. If + * mode6 is true then use MODE SENSE (6) else use MODE SENSE (10). If + * flexible true and mode data length seems wrong then try and + * fix (compensating hack for bad device or driver). pcontrol_arr + * should have 4 elements for output of current, changeable, default + * and saved values respectively. Each element should be NULL or + * at least mx_mpage_len bytes long. + * Return of 0 -> overall success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, + * SG_LIB_CAT_MALFORMED -> bad response, -1 -> other failure. + * If success_mask pointer is not NULL then first zeros it. Then set bits + * 0, 1, 2 and/or 3 if the current, changeable, default and saved values + * respectively have been fetched. If error on current page + * then stops and returns that error; otherwise continues if an error is + * detected but returns the first error encountered. */ +int sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, + int sub_pg_code, bool dbd, bool flexible, + int mx_mpage_len, int * success_mask, + void * pcontrol_arr[], int * reported_lenp, + int verbose); + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. Implementation calls scsi_pt_open_device(). */ +int sg_cmds_open_device(const char * device_name, bool read_only, int verbose); + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. Implementation calls scsi_pt_open_flags(). */ +int sg_cmds_open_flags(const char * device_name, int flags, int verbose); + +/* Returns 0 if successful. If error in Unix returns negated errno. + Implementation calls scsi_pt_close_device(). */ +int sg_cmds_close_device(int device_fd); + +const char * sg_cmds_version(); + +#define SG_NO_DATA_IN 0 + + +/* This is a helper function used by sg_cmds_* implementations after the + * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid + * sense data is found it is decoded and output to sg_warnings_strm (def: + * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for + * sense data (may not be fatal), -1 for failed, 0, or a positive number. If + * 'mx_di_len > 0' then asks pass-through for resid and returns + * (mx_di_len - resid); otherwise returns 0. So for data-in it should return + * the actual number of bytes received. For data-out (to device) or no data + * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category + * output via 'o_sense_cat' pointer (if not NULL). Note that several sense + * categories also have data in bytes received; -2 is still returned. */ +int sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, + int pt_res, int mx_di_len, const uint8_t * sense_b, + bool noisy, int verbose, int * o_sense_cat); + +/* NVMe devices use a different command set. This function will return true + * if the device associated with 'pvtp' is a NVME device, else it will + * return false (e.g. for SCSI devices). */ +bool sg_cmds_is_nvme(const struct sg_pt_base * ptvp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/sg_cmds_extra.h b/include/sg_cmds_extra.h new file mode 100644 index 0000000..974d17c --- /dev/null +++ b/include/sg_cmds_extra.h @@ -0,0 +1,386 @@ +#ifndef SG_CMDS_EXTRA_H +#define SG_CMDS_EXTRA_H + +/* + * Copyright (c) 2004-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Note: all functions that have an 'int timeout_secs' argument will use + * that value if it is > 0. Otherwise they will set an internal default + * which is currently 60 seconds. This timeout is typically applied in the + * SCSI stack above the initiator. If it goes off then the SCSI command is + * aborted and there can be other unwelcome side effects. Note that some + * commands (e.g. FORMAT UNIT and the Third Party copy commands) can take + * a lot longer than the default timeout. */ + +/* Functions with the "_pt" suffix ^^^ take a pointer to an object (derived + * from) sg_pt_base rather than an open file descriptor as their first + * argument. That object is assumed to be constructed and have a device file + * descriptor * associated with it. Caller is responsible for lifetime of + * ptp. + * ^^^ apart from sg_ll_ata_pt() as 'pass-through' is part of its name. */ + +struct sg_pt_base; + + +/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is + * selected by the cdb_len argument that can take values of 12, 16 or 32 + * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9 + * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from + * the control byte, the rest is copied into an internal cdb which is then + * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15 + * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the + * timeout is set to 60 seconds. For data in or out transfers set dinp or + * doutp, and dlen to the number of bytes to transfer. If dlen is zero then + * no data transfer is assumed. If sense buffer obtained then it is written + * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is + * obtained then written to ata_return_dp, else ata_return_dp[0] is set to + * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers. + * Returns SCSI status value (>= 0) or -1 if other error. Users are + * expected to check the sense buffer themselves. If available the data in + * resid is written to residp. Note in SAT-2 and later, fixed format sense + * data may be placed in *sensep in which case sensep[0]==0x70, prior to + * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72). + */ +int sg_ll_ata_pt(int sg_fd, const uint8_t * cdbp, int cdb_len, + int timeout_secs, void * dinp, void * doutp, int dlen, + uint8_t * sensep, int max_sense_len, uint8_t * ata_return_dp, + int max_ata_return_len, int * residp, int verbose); + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Format unit not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Note that sg_ll_format_unit2() and + * sg_ll_format_unit_v2() are the same, both add the ffmt argument. */ +int sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose); +int sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose); +int sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplist, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI GET LBA STATUS(16) or GET LBA STATUS(32) command (SBC). + * Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> GET LBA STATUS(16 or 32) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure. + * sg_ll_get_lba_status() calls the 16 byte variant with rt=0 . */ +int sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp, + int alloc_len, bool noisy, int verbose); +int sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose); +int sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len, + uint32_t element_id, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose); + +/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0 + * when successful, SG_LIB_CAT_INVALID_OP if command not supported, + * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0 + * when successful, SG_LIB_CAT_INVALID_OP if command not supported, + * SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope, + unsigned int rq_type, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ BLOCK LIMITS not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_NOT_READY (shouldn't happen), -1 -> other failure */ +int sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI READ BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * resp, int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, + int dl_format, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ LONG(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose); + +/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> READ LONG(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose); + +/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Read media serial number not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> invalid opcode, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_NOT_READY -> device not ready, -1 -> other failure */ +int sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, + void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Receive diagnostic results not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Same as sg_ll_receive_diag() but with added timeout_secs and residp + * arguments. Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose); + +int sg_ll_receive_diag_pt(struct sg_pt_base * ptp, bool pcv, int pg_code, + void * resp, int mx_resp_len, int timeout_secs, + int * residp, bool noisy, int verbose); + +/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was + * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report identifying information not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len, + bool noisy, int verbose); + +/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose); +int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len, + bool extended, bool noisy, int verbose); + +/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose); + +/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Report Referrals not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg, + void * resp, int mx_resp_len, bool noisy, + int verbose); + +/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can + * take a long time, if so set long_duration flag in which case the timeout + * is set to 7200 seconds; if the value of long_duration is > 7200 then that + * value is taken as the timeout value in seconds. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Send diagnostic not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit, + bool devofl_bit, bool unitofl_bit, int long_duration, + void * paramp, int param_len, bool noisy, int verbose); + +int sg_ll_send_diag_pt(struct sg_pt_base * ptp, int st_code, bool pf_bit, + bool st_bit, bool devofl_bit, bool unitofl_bit, + int long_duration, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was + * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set identifying information not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len, + bool noisy, int verbose); + +/* Invokes a SCSI UNMAP (SBC-3) command. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */ +int sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose); +/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field + * (sbc3r22). Otherwise same as sg_ll_unmap() . */ +int sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI VERIFY (10) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Verify(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info, + * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */ +int sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytechk, + unsigned int lba, int veri_len, void * data_out, + int data_out_len, unsigned int * infop, bool noisy, + int verbose); + +/* Invokes a SCSI VERIFY (16) command (SBC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Verify(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_MEDIUM_HARD -> medium or hardware error, no valid info, + * SG_LIB_CAT_MEDIUM_HARD_WITH_INFO -> as previous, with valid info, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_MISCOMPARE, -1 -> other failure */ +int sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytechk, + uint64_t llba, int veri_len, int group_num, + void * data_out, int data_out_len, uint64_t * infop, + bool noisy, int verbose); + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * paramp, int param_len, bool noisy, int verbose); + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Adds mode specific field (spc4r32) and timeout + * to command abort to override default of 60 seconds. If timeout_secs is + * 0 or less then the default timeout is used instead. */ +int +sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id, + uint32_t buffer_offset, void * paramp, + uint32_t param_len, int timeout_secs, bool noisy, + int verbose); + +/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> WRITE LONG(10) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + unsigned int lba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose); + +/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * SG_LIB_CAT_INVALID_OP -> WRITE LONG(16) not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, + * SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO -> bad field in cdb, with info + * field written to 'offsetp', SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + uint64_t llba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose); + +/* Invokes a SPC-3 SCSI RECEIVE COPY RESULTS command. In SPC-4 this function + * supports all service action variants of the THIRD-PARTY COPY IN opcode. + * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI EXTENDED COPY(LID1) command. For EXTENDED COPY(LID4) + * including POPULATE TOKEN and WRITE USING TOKEN use + * sg_ll_3party_copy_out(). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Extended copy not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose); + +/* Handles various service actions associated with opcode 0x83 which is + * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID4), + * POPULATE TOKEN and WRITE USING TOKEN commands. Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> opcode 0x83 not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, + int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose); + +/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC). + * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_* + * positive values or -1 -> other errors. Note that CONDITION MET status + * is returned when immed=true and num_blocks can fit in device's cache, + * somewaht strangely, GOOD status (return 0) is returned if num_blocks + * cannot fit in device's cache. If do_seek10==true then does a SEEK(10) + * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10) + * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then + * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */ +int sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed, + uint64_t lba, uint32_t num_blocks, int group_num, + int timeout_secs, bool noisy, int verbose); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/sg_cmds_mmc.h b/include/sg_cmds_mmc.h new file mode 100644 index 0000000..3988b1d --- /dev/null +++ b/include/sg_cmds_mmc.h @@ -0,0 +1,52 @@ +#ifndef SG_CMDS_MMC_H +#define SG_CMDS_MMC_H + +/* + * Copyright (c) 2008-2017 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Invokes a SCSI GET CONFIGURATION command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_get_config(int sg_fd, int rt, int starting, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba, + int max_num_desc, int type, void * resp, + int mx_resp_len, bool noisy, int verbose); + +/* Invokes a SCSI SET CD SPEED command (MMC). + * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed, + int drv_write_speed, bool noisy, int verbose); + +/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len, + bool noisy, int verbose); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/sg_io_linux.h b/include/sg_io_linux.h new file mode 100644 index 0000000..b57aa63 --- /dev/null +++ b/include/sg_io_linux.h @@ -0,0 +1,186 @@ +#ifndef SG_IO_LINUX_H +#define SG_IO_LINUX_H + +/* + * Copyright (c) 2004-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +/* + * Version 1.06 [20180119] + */ + +/* + * This header file contains Linux specific information related to the SCSI + * command pass through in the SCSI generic (sg) driver and the Linux + * block layer. + */ + +#include "sg_lib.h" +#include "sg_linux_inc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* host_bytes: DID_* are Linux SCSI result (a 32 bit variable) bits 16:23 */ +#ifndef DID_OK +#define DID_OK 0x00 +#endif +#ifndef DID_NO_CONNECT +#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */ +#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */ +#define DID_TIME_OUT 0x03 /* Timed out for some other reason */ +#define DID_BAD_TARGET 0x04 /* Bad target (id?) */ +#define DID_ABORT 0x05 /* Told to abort for some other reason */ +#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */ +#define DID_ERROR 0x07 /* Internal error */ +#define DID_RESET 0x08 /* Reset by somebody */ +#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */ +#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */ +#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */ +#endif +#ifndef DID_IMM_RETRY +#define DID_IMM_RETRY 0x0c /* Retry without decrementing retry count */ +#endif +#ifndef DID_REQUEUE +#define DID_REQUEUE 0x0d /* Requeue command (no immediate retry) also + * without decrementing the retry count */ +#endif +#ifndef DID_TRANSPORT_DISRUPTED +#define DID_TRANSPORT_DISRUPTED 0xe +#endif +#ifndef DID_TRANSPORT_FAILFAST +#define DID_TRANSPORT_FAILFAST 0xf +#endif +#ifndef DID_TARGET_FAILURE +#define DID_TARGET_FAILURE 0x10 +#endif +#ifndef DID_NEXUS_FAILURE +#define DID_NEXUS_FAILURE 0x11 +#endif + +/* These defines are to isolate applications from kernel define changes */ +#define SG_LIB_DID_OK DID_OK +#define SG_LIB_DID_NO_CONNECT DID_NO_CONNECT +#define SG_LIB_DID_BUS_BUSY DID_BUS_BUSY +#define SG_LIB_DID_TIME_OUT DID_TIME_OUT +#define SG_LIB_DID_BAD_TARGET DID_BAD_TARGET +#define SG_LIB_DID_ABORT DID_ABORT +#define SG_LIB_DID_PARITY DID_PARITY +#define SG_LIB_DID_ERROR DID_ERROR +#define SG_LIB_DID_RESET DID_RESET +#define SG_LIB_DID_BAD_INTR DID_BAD_INTR +#define SG_LIB_DID_PASSTHROUGH DID_PASSTHROUGH +#define SG_LIB_DID_SOFT_ERROR DID_SOFT_ERROR +#define SG_LIB_DID_IMM_RETRY DID_IMM_RETRY +#define SG_LIB_DID_REQUEUE DID_REQUEUE +#define SG_LIB_TRANSPORT_DISRUPTED DID_TRANSPORT_DISRUPTED +#define SG_LIB_DID_TRANSPORT_FAILFAST DID_TRANSPORT_FAILFAST +#define SG_LIB_DID_TARGET_FAILURE DID_TARGET_FAILURE +#define SG_LIB_DID_NEXUS_FAILURE DID_NEXUS_FAILURE + +/* DRIVER_* are Linux SCSI result (a 32 bit variable) bits 24:27 */ +#ifndef DRIVER_OK +#define DRIVER_OK 0x00 +#endif +#ifndef DRIVER_BUSY +#define DRIVER_BUSY 0x01 +#define DRIVER_SOFT 0x02 +#define DRIVER_MEDIA 0x03 +#define DRIVER_ERROR 0x04 +#define DRIVER_INVALID 0x05 +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_HARD 0x07 +#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */ + +/* SUGGEST_* are Linux SCSI result (a 32 bit variable) bits 28:31 */ +/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept + * to stop compilation breakages. + * Following "suggests" are "or-ed" with one of previous 8 entries */ +#define SUGGEST_RETRY 0x10 +#define SUGGEST_ABORT 0x20 +#define SUGGEST_REMAP 0x30 +#define SUGGEST_DIE 0x40 +#define SUGGEST_SENSE 0x80 +#define SUGGEST_IS_OK 0xff +#endif + +#ifndef DRIVER_MASK +#define DRIVER_MASK 0x0f +#endif +#ifndef SUGGEST_MASK +#define SUGGEST_MASK 0xf0 +#endif + +/* These defines are to isolate applications from kernel define changes */ +#define SG_LIB_DRIVER_OK DRIVER_OK +#define SG_LIB_DRIVER_BUSY DRIVER_BUSY +#define SG_LIB_DRIVER_SOFT DRIVER_SOFT +#define SG_LIB_DRIVER_MEDIA DRIVER_MEDIA +#define SG_LIB_DRIVER_ERROR DRIVER_ERROR +#define SG_LIB_DRIVER_INVALID DRIVER_INVALID +#define SG_LIB_DRIVER_TIMEOUT DRIVER_TIMEOUT +#define SG_LIB_DRIVER_HARD DRIVER_HARD +#define SG_LIB_DRIVER_SENSE DRIVER_SENSE + + +/* N.B. the SUGGEST_* codes are no longer used in Linux and are only kept + * to stop compilation breakages. */ +#define SG_LIB_SUGGEST_RETRY SUGGEST_RETRY +#define SG_LIB_SUGGEST_ABORT SUGGEST_ABORT +#define SG_LIB_SUGGEST_REMAP SUGGEST_REMAP +#define SG_LIB_SUGGEST_DIE SUGGEST_DIE +#define SG_LIB_SUGGEST_SENSE SUGGEST_SENSE +#define SG_LIB_SUGGEST_IS_OK SUGGEST_IS_OK +#define SG_LIB_DRIVER_MASK DRIVER_MASK +#define SG_LIB_SUGGEST_MASK SUGGEST_MASK + +void sg_print_masked_status(int masked_status); +void sg_print_host_status(int host_status); +void sg_print_driver_status(int driver_status); + +/* sg_chk_n_print() returns 1 quietly if there are no errors/warnings + else it prints errors/warnings (prefixed by 'leadin') to + 'sg_warnings_fd' and returns 0. raw_sinfo indicates whether the + raw sense buffer (in ASCII hex) should be printed. */ +int sg_chk_n_print(const char * leadin, int masked_status, int host_status, + int driver_status, const uint8_t * sense_buffer, + int sb_len, bool raw_sinfo); + +/* The following function declaration is for the sg version 3 driver. */ +struct sg_io_hdr; +/* sg_chk_n_print3() returns 1 quietly if there are no errors/warnings; + else it prints errors/warnings (prefixed by 'leadin') to + 'sg_warnings_fd' and returns 0. */ +int sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, + bool raw_sinfo); + +/* Calls sg_scsi_normalize_sense() after obtaining the sense buffer and + its length from the struct sg_io_hdr pointer. If these cannot be + obtained, false is returned. */ +bool sg_normalize_sense(const struct sg_io_hdr * hp, + struct sg_scsi_sense_hdr * sshp); + +int sg_err_category(int masked_status, int host_status, int driver_status, + const uint8_t * sense_buffer, int sb_len); + +int sg_err_category_new(int scsi_status, int host_status, int driver_status, + const uint8_t * sense_buffer, int sb_len); + +/* The following function declaration is for the sg version 3 driver. */ +int sg_err_category3(struct sg_io_hdr * hp); + + +/* Note about SCSI status codes found in older versions of Linux. + Linux has traditionally used a 1 bit right shifted and masked + version of SCSI standard status codes. Now CHECK_CONDITION + and friends (in ) are deprecated. */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/sg_lib.h b/include/sg_lib.h new file mode 100644 index 0000000..e860b92 --- /dev/null +++ b/include/sg_lib.h @@ -0,0 +1,676 @@ +#ifndef SG_LIB_H +#define SG_LIB_H + +/* + * Copyright (c) 2004-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +/* + * + * On 5th October 2004 a FreeBSD license was added to this file. + * The intention is to keep this file and the related sg_lib.c file + * as open source and encourage their unencumbered use. + * + * Current version number of this library is in the sg_lib_data.c file and + * can be accessed with the sg_lib_version() function. + */ + + +/* + * This header file contains defines and function declarations that may + * be useful to applications that communicate with devices that use a + * SCSI command set. These command sets have names like SPC-4, SBC-3, + * SSC-3, SES-2 and draft standards defining them can be found at + * http://www.t10.org . Virtually all devices in the Linux SCSI subsystem + * utilize SCSI command sets. Many devices in other Linux device subsystems + * utilize SCSI command sets either natively or via emulation (e.g. a + * SATA disk in a USB enclosure). + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* SCSI Peripheral Device Types (PDT) [5 bit field] */ +#define PDT_DISK 0x0 /* direct access block device (disk) */ +#define PDT_TAPE 0x1 /* sequential access device (magnetic tape) */ +#define PDT_PRINTER 0x2 /* printer device (see SSC-1) */ +#define PDT_PROCESSOR 0x3 /* processor device (e.g. SAFTE device) */ +#define PDT_WO 0x4 /* write once device (some optical disks) */ +#define PDT_MMC 0x5 /* CD/DVD/BD (multi-media) */ +#define PDT_SCANNER 0x6 /* obsolete */ +#define PDT_OPTICAL 0x7 /* optical memory device (some optical disks) */ +#define PDT_MCHANGER 0x8 /* media changer device (e.g. tape robot) */ +#define PDT_COMMS 0x9 /* communications device (obsolete) */ +#define PDT_SAC 0xc /* storage array controller device */ +#define PDT_SES 0xd /* SCSI Enclosure Services (SES) device */ +#define PDT_RBC 0xe /* Reduced Block Commands (simplified PDT_DISK) */ +#define PDT_OCRW 0xf /* optical card read/write device */ +#define PDT_BCC 0x10 /* bridge controller commands */ +#define PDT_OSD 0x11 /* Object Storage Device (OSD) */ +#define PDT_ADC 0x12 /* Automation/drive commands (ADC) */ +#define PDT_SMD 0x13 /* Security Manager Device (SMD) */ +#define PDT_ZBC 0x14 /* Zoned Block Commands (ZBC) */ +#define PDT_WLUN 0x1e /* Well known logical unit (WLUN) */ +#define PDT_UNKNOWN 0x1f /* Unknown or no device type */ + +#ifndef SAM_STAT_GOOD +/* The SCSI status codes as found in SAM-4 at www.t10.org */ +#define SAM_STAT_GOOD 0x0 +#define SAM_STAT_CHECK_CONDITION 0x2 +#define SAM_STAT_CONDITION_MET 0x4 +#define SAM_STAT_BUSY 0x8 +#define SAM_STAT_INTERMEDIATE 0x10 /* obsolete in SAM-4 */ +#define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14 /* obsolete in SAM-4 */ +#define SAM_STAT_RESERVATION_CONFLICT 0x18 +#define SAM_STAT_COMMAND_TERMINATED 0x22 /* obsolete in SAM-3 */ +#define SAM_STAT_TASK_SET_FULL 0x28 +#define SAM_STAT_ACA_ACTIVE 0x30 +#define SAM_STAT_TASK_ABORTED 0x40 +#endif + +/* The SCSI sense key codes as found in SPC-4 at www.t10.org */ +#define SPC_SK_NO_SENSE 0x0 +#define SPC_SK_RECOVERED_ERROR 0x1 +#define SPC_SK_NOT_READY 0x2 +#define SPC_SK_MEDIUM_ERROR 0x3 +#define SPC_SK_HARDWARE_ERROR 0x4 +#define SPC_SK_ILLEGAL_REQUEST 0x5 +#define SPC_SK_UNIT_ATTENTION 0x6 +#define SPC_SK_DATA_PROTECT 0x7 +#define SPC_SK_BLANK_CHECK 0x8 +#define SPC_SK_VENDOR_SPECIFIC 0x9 +#define SPC_SK_COPY_ABORTED 0xa +#define SPC_SK_ABORTED_COMMAND 0xb +#define SPC_SK_RESERVED 0xc +#define SPC_SK_VOLUME_OVERFLOW 0xd +#define SPC_SK_MISCOMPARE 0xe +#define SPC_SK_COMPLETED 0xf + +/* Transport protocol identifiers or just Protocol identifiers */ +#define TPROTO_FCP 0 +#define TPROTO_SPI 1 +#define TPROTO_SSA 2 +#define TPROTO_1394 3 +#define TPROTO_SRP 4 /* SCSI over RDMA */ +#define TPROTO_ISCSI 5 +#define TPROTO_SAS 6 +#define TPROTO_ADT 7 +#define TPROTO_ATA 8 +#define TPROTO_UAS 9 /* USB attached SCSI */ +#define TPROTO_SOP 0xa /* SCSI over PCIe */ +#define TPROTO_PCIE 0xb /* includes NVMe */ +#define TPROTO_NONE 0xf + +/* SCSI Feature Sets (sfs) */ +#define SCSI_FS_SPC_DISCOVERY_2016 0x1 +#define SCSI_FS_SBC_BASE_2010 0x102 +#define SCSI_FS_SBC_BASE_2016 0x101 +#define SCSI_FS_SBC_BASIC_PROV_2016 0x103 +#define SCSI_FS_SBC_DRIVE_MAINT_2016 0x104 + +/* Often SCSI responses use the highest integer that can fit in a field + * to indicate "unbounded" or limit does not apply. Sometimes represented + * in output as "-1" for brevity */ +#define SG_LIB_UNBOUNDED_16BIT 0xffff +#define SG_LIB_UNBOUNDED_32BIT 0xffffffffU +#define SG_LIB_UNBOUNDED_64BIT 0xffffffffffffffffULL + +#if (__STDC_VERSION__ >= 199901L) /* C99 or later */ + typedef uintptr_t sg_uintptr_t; +#else + typedef unsigned long sg_uintptr_t; +#endif + +/* Borrowed from Linux kernel; no check that 'arr' actually is one */ +#define SG_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + + +/* The format of the version string is like this: "2.26 20170906" */ +const char * sg_lib_version(); + +/* Returns length of SCSI command given the opcode (first byte). + * Yields the wrong answer for variable length commands (opcode=0x7f) + * and potentially some vendor specific commands. */ +int sg_get_command_size(uint8_t cdb_byte0); + +/* Command name given pointer to the cdb. Certain command names + * depend on peripheral type (give 0 or -1 if unknown). Places command + * name into buff and will write no more than buff_len bytes. */ +void sg_get_command_name(const uint8_t * cdbp, int peri_type, int buff_len, + char * buff); + +/* Command name given only the first byte (byte 0) of a cdb and + * peripheral type (give 0 or -1 if unknown). */ +void sg_get_opcode_name(uint8_t cdb_byte0, int peri_type, int buff_len, + char * buff); + +/* Command name given opcode (byte 0), service action and peripheral type. + * If no service action give 0, if unknown peripheral type give 0 or -1 . */ +void sg_get_opcode_sa_name(uint8_t cdb_byte0, int service_action, + int peri_type, int buff_len, char * buff); + +/* Fetch NVMe command name given first byte (byte offset 0 in 64 byte + * command) of command. Gets Admin NVMe command name if 'admin' is true + * (e.g. opcode=0x6 -> Identify), otherwise gets NVM command set name + * (e.g. opcode=0 -> Flush). Returns 'buff'. */ +char * sg_get_nvme_opcode_name(uint8_t cmd_byte0, bool admin, int buff_len, + char * buff); + +/* Fetch scsi status string. */ +void sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff); + +/* This is a slightly stretched SCSI sense "descriptor" format header. + * The addition is to allow the 0x70 and 0x71 response codes. The idea + * is to place the salient data of both "fixed" and "descriptor" sense + * format into one structure to ease application processing. + * The original sense buffer should be kept around for those cases + * in which more information is required (e.g. the LBA of a MEDIUM ERROR). */ +struct sg_scsi_sense_hdr { + uint8_t response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */ + uint8_t sense_key; + uint8_t asc; + uint8_t ascq; + uint8_t byte4; + uint8_t byte5; + uint8_t byte6; + uint8_t additional_length; +}; + +/* Maps the salient data from a sense buffer which is in either fixed or + * descriptor format into a structure mimicking a descriptor format + * header (i.e. the first 8 bytes of sense descriptor format). + * If zero response code returns false. Otherwise returns true and if 'sshp' + * is non-NULL then zero all fields and then set the appropriate fields in + * that structure. sshp::additional_length is always 0 for response + * codes 0x70 and 0x71 (fixed format). */ +bool sg_scsi_normalize_sense(const uint8_t * sensep, int sense_len, + struct sg_scsi_sense_hdr * sshp); + +/* Attempt to find the first SCSI sense data descriptor that matches the + * given 'desc_type'. If found return pointer to start of sense data + * descriptor; otherwise (including fixed format sense data) returns NULL. */ +const uint8_t * sg_scsi_sense_desc_find(const uint8_t * sensep, int sense_len, + int desc_type); + +/* Get sense key from sense buffer. If successful returns a sense key value + * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ +int sg_get_sense_key(const uint8_t * sensep, int sense_len); + +/* Yield string associated with sense_key value. Returns 'buff'. */ +char * sg_get_sense_key_str(int sense_key, int buff_len, char * buff); + +/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ +char * sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff); + +/* Returns true if valid bit set, false if valid bit clear. Irrespective the + * information field is written out via 'info_outp' (except when it is + * NULL). Handles both fixed and descriptor sense formats. */ +bool sg_get_sense_info_fld(const uint8_t * sensep, int sb_len, + uint64_t * info_outp); + +/* Returns true if fixed format or command specific information descriptor + * is found in the descriptor sense; else false. If available the command + * specific information field (4 byte integer in fixed format, 8 byte + * integer in descriptor format) is written out via 'cmd_spec_outp'. + * Handles both fixed and descriptor sense formats. */ +bool sg_get_sense_cmd_spec_fld(const uint8_t * sensep, int sb_len, + uint64_t * cmd_spec_outp); + +/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. + * In descriptor format if the stream commands descriptor not found + * then returns false. Writes true or false corresponding to these bits to + * the last three arguments if they are non-NULL. */ +bool sg_get_sense_filemark_eom_ili(const uint8_t * sensep, int sb_len, + bool * filemark_p, bool * eom_p, + bool * ili_p); + +/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also + * returns true if progress indication sense data descriptor found. Places + * progress field from sense data where progress_outp points. If progress + * field is not available returns false. Handles both fixed and descriptor + * sense formats. N.B. App should multiply by 100 and divide by 65536 + * to get percentage completion from given value. */ +bool sg_get_sense_progress_fld(const uint8_t * sensep, int sb_len, + int * progress_outp); + +/* Closely related to sg_print_sense(). Puts decoded sense data in 'buff'. + * Usually multiline with multiple '\n' including one trailing. If + * 'raw_sinfo' set appends sense buffer in hex. 'leadin' is string prepended + * to each line written to 'buff', NULL treated as "". Returns the number of + * bytes written to 'buff' excluding the trailing '\0'. + * N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the first + * line output. Also this function returned type void. */ +int sg_get_sense_str(const char * leadin, const uint8_t * sense_buffer, + int sb_len, bool raw_sinfo, int buff_len, char * buff); + +/* Decode descriptor format sense descriptors (assumes sense buffer is + * in descriptor format). 'leadin' is string prepended to each line written + * to 'b', NULL treated as "". Returns the number of bytes written to 'b' + * excluding the trailing '\0'. If problem, returns 0. */ +int sg_get_sense_descriptors_str(const char * leadin, + const uint8_t * sense_buffer, + int sb_len, int blen, char * b); + +/* Decodes a designation descriptor (e.g. as found in the Device + * Identification VPD page (0x83)) into string 'b' whose maximum length is + * blen. 'leadin' is string prepended to each line written to 'b', NULL + * treated as "". Returns the number of bytes written to 'b' excluding the + * trailing '\0'. */ +int sg_get_designation_descriptor_str(const char * leadin, + const uint8_t * ddp, int dd_len, + bool print_assoc, bool do_long, + int blen, char * b); + +/* Yield string associated with peripheral device type (pdt). Returns + * 'buff'. If 'pdt' out of range yields "bad pdt" string. */ +char * sg_get_pdt_str(int pdt, int buff_len, char * buff); + +/* Some lesser used PDTs share a lot in common with a more used PDT. + * Examples are PDT_ADC decaying to PDT_TAPE and PDT_ZBC to PDT_DISK. + * If such a lesser used 'pdt' is given to this function, then it will + * return the more used PDT (i.e. "decays to"); otherwise 'pdt' is returned. + * Valid for 'pdt' 0 to 31, for other values returns 0. */ +int sg_lib_pdt_decay(int pdt); + +/* Yield string associated with transport protocol identifier (tpi). Returns + * 'buff'. If 'tpi' out of range yields "bad tpi" string. */ +char * sg_get_trans_proto_str(int tpi, int buff_len, char * buff); + +/* Decode TransportID pointed to by 'bp' of length 'bplen'. Place decoded + * string output in 'buff' which is also the return value. Each new line + * is prefixed by 'leadin'. If leadin NULL treat as "". */ +char * sg_decode_transportid_str(const char * leadin, uint8_t * bp, int bplen, + bool only_one, int buff_len, char * buff); + +/* Returns a designator's type string given 'val' (0 to 15 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_type_str(int val); + +/* Returns a designator's code_set string given 'val' (0 to 15 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_code_set_str(int val); + +/* Returns a designator's association string given 'val' (0 to 3 inclusive), + * otherwise returns NULL. */ +const char * sg_get_desig_assoc_str(int val); + +/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) + * returns pointer to string (same as 'buff') associated with 'sfs_code'. + * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match + * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL + * then where it points is set to true if a match is found else it is set to + * false. If 'buff' is not NULL then in the case of a match a descriptive + * string is written to 'buff' while if there is not a not then a string + * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC + * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. + * Example: + * char b[64]; + * ... + * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); + */ +const char * sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, + char * buff, bool * foundp, int verbose); + +/* This is a heuristic that takes into account the command bytes and length + * to decide whether the presented unstructured sequence of bytes could be + * a SCSI command. If so it returns true otherwise false. Vendor specific + * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed + * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The + * only SCSI commands considered above 16 bytes of length are the Variable + * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e). + * Both have an inbuilt length field which can be cross checked with clen. + * No NVMe commands (64 bytes long plus some extra added by some OSes) have + * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS + * structures that are sent across the wire. The 'FIS register' structure is + * used to move a command from a SATA host to device, but the ATA 'command' + * is not the first byte. So it is harder to say what will happen if a + * FIS structure is presented as a SCSI command, hopefully there is a low + * probability this function will yield true in that case. */ +bool sg_is_scsi_cdb(const uint8_t * cdbp, int clen); + +/* Yield string associated with NVMe command status value in sct_sc. It + * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 + * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). + * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found + * a string of the form "Reserved [0x]" is generated. + * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ +char * sg_get_nvme_cmd_status_str(uint16_t sct_sc, int buff_len, char * buff); + +/* Attempts to map NVMe status value ((SCT << 8) | SC) n sct_sc to a SCSI + * status, sense_key, asc and ascq tuple. If successful returns true and + * writes to non-NULL pointer arguments; otherwise returns false. */ +bool sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, + uint8_t * asc_p, uint8_t * ascq_p); + +/* Add vendor (sg3_utils) specific sense descriptor for the NVMe Status + * field. Assumes descriptor (i.e. not fixed) sense. Assume sbp has room. */ +void sg_nvme_desc2sense(uint8_t * sbp, bool dnr, bool more, uint16_t sct_sc); + +/* Build minimum sense buffer, either descriptor type (desc=true) or fixed + * type (desc=false). Assume sbp has enough room (8 or 14 bytes + * respectively). sbp should have room for 32 or 18 bytes respectively */ +void sg_build_sense_buffer(bool desc, uint8_t *sbp, uint8_t skey, + uint8_t asc, uint8_t ascq); + +extern FILE * sg_warnings_strm; + +void sg_set_warnings_strm(FILE * warnings_strm); + +/* The following "print" functions send ASCII to 'sg_warnings_strm' file + * descriptor (default value is stderr). 'leadin' is string prepended to + * each line printed out, NULL treated as "". */ +void sg_print_command(const uint8_t * command); +void sg_print_scsi_status(int scsi_status); + +/* DSENSE is 'descriptor sense' as opposed to the older 'fixed sense'. Reads + * environment variable SG3_UTILS_DSENSE. Only (currently) used in SNTL. */ +bool sg_get_initial_dsense(void); + +/* 'leadin' is string prepended to each line printed out, NULL treated as + * "". N.B. prior to sg3_utils v 1.42 'leadin' was only prepended to the + * first line printed. */ +void sg_print_sense(const char * leadin, const uint8_t * sense_buffer, + int sb_len, bool raw_info); + +/* This examines exit_status and if an error message is known it is output + * to stdout/stderr and true is returned. If no error message is + * available nothing is output and false is returned. If exit_status is + * zero (no error) nothing is output and true is returned. If exit_status + * is negative then nothing is output and false is returned. If leadin is + * non-NULL then it is printed before the error message. All messages are + * a single line with a trailing LF. */ +bool sg_if_can2stdout(const char * leadin, int exit_status); +bool sg_if_can2stderr(const char * leadin, int exit_status); + +/* This examines exit_status and if an error message is known it is output + * as a string to 'b' and true is returned. If 'longer' is true and extra + * information is available then it is added to the output. If no error + * message is available a null character is output and false is returned. + * If exit_status is zero (no error) and 'longer' is true then the string + * 'No errors' is output; if 'longer' is false then a null character is + * output; in both cases true is returned. If exit_status is negative then + * a null character is output and false is returned. All messages are a + * single line (less than 80 characters) with no trailing LF. The output + * string including the trailing null character is no longer than b_len. */ +bool sg_exit2str(int exit_status, bool longer, int b_len, char * b); + +/* Utilities can use these exit status values for syntax errors and + * file (device node) problems (e.g. not found or permissions). */ +#define SG_LIB_SYNTAX_ERROR 1 /* command line syntax problem */ + +/* The sg_err_category_sense() function returns one of the following. + * These may be used as exit status values (from a process). Notice that + * some of the lower values correspond to SCSI sense key values. */ +#define SG_LIB_CAT_CLEAN 0 /* No errors or other information */ +#define SG_LIB_OK_TRUE SG_LIB_CAT_CLEAN /* No error, reporting true */ +/* Value 1 left unused for utilities to use SG_LIB_SYNTAX_ERROR */ +#define SG_LIB_CAT_NOT_READY 2 /* sense key, unit stopped? + * [sk,asc,ascq: 0x2,*,*] */ +#define SG_LIB_CAT_MEDIUM_HARD 3 /* medium or hardware error, blank check + * [sk,asc,ascq: 0x3/0x4/0x8,*,*] */ +#define SG_LIB_CAT_ILLEGAL_REQ 5 /* Illegal request (other than invalid + * opcode): [sk,asc,ascq: 0x5,*,*] */ +#define SG_LIB_CAT_UNIT_ATTENTION 6 /* sense key, device state changed + * [sk,asc,ascq: 0x6,*,*] */ + /* was SG_LIB_CAT_MEDIA_CHANGED earlier [sk,asc,ascq: 0x6,0x28,*] */ +#define SG_LIB_CAT_DATA_PROTECT 7 /* sense key, media write protected? + * [sk,asc,ascq: 0x7,*,*] */ +#define SG_LIB_CAT_INVALID_OP 9 /* (Illegal request,) Invalid opcode: + * [sk,asc,ascq: 0x5,0x20,0x0] */ +#define SG_LIB_CAT_COPY_ABORTED 10 /* sense key, some data transferred + * [sk,asc,ascq: 0xa,*,*] */ +#define SG_LIB_CAT_ABORTED_COMMAND 11 /* interpreted from sense buffer + * [sk,asc,ascq: 0xb,! 0x10,*] */ +#define SG_LIB_CAT_MISCOMPARE 14 /* sense key, probably verify + * [sk,asc,ascq: 0xe,*,*] */ +#define SG_LIB_FILE_ERROR 15 /* device or other file problem */ +#define SG_LIB_CAT_NO_SENSE 20 /* sense data with key of "no sense" + * [sk,asc,ascq: 0x0,*,*] */ +#define SG_LIB_CAT_RECOVERED 21 /* Successful command after recovered err + * [sk,asc,ascq: 0x1,*,*] */ +#define SG_LIB_LBA_OUT_OF_RANGE 22 /* Illegal request, LBA Out Of Range + * [sk,asc,ascq: 0x5,0x21,0x0] */ +#define SG_LIB_CAT_RES_CONFLICT SAM_STAT_RESERVATION_CONFLICT + /* 24: this is a SCSI status, not sense. + * It indicates reservation by another + * machine blocks this command */ +#define SG_LIB_CAT_CONDITION_MET 25 /* SCSI status, not sense key. + * Only from PRE-FETCH (SBC-4) */ +#define SG_LIB_CAT_BUSY 26 /* SCSI status, not sense. Invites retry */ +#define SG_LIB_CAT_TS_FULL 27 /* SCSI status, not sense. Wait then retry */ +#define SG_LIB_CAT_ACA_ACTIVE 28 /* SCSI status; ACA seldom used */ +#define SG_LIB_CAT_TASK_ABORTED 29 /* SCSI status, this command aborted by? */ +#define SG_LIB_CONTRADICT 31 /* error involving two or more cl options */ +#define SG_LIB_LOGIC_ERROR 32 /* unexpected situation in code */ +#define SG_LIB_OK_FALSE 36 /* no error, reporting false (cf. no error, + * reporting true is SG_LIB_OK_TRUE(0) ) */ +#define SG_LIB_CAT_PROTECTION 40 /* subset of aborted command (for PI, DIF) + * [sk,asc,ascq: 0xb,0x10,*] */ +/* 47: flock error used in ddpt utility */ +#define SG_LIB_NVME_STATUS 48 /* NVMe Status Field (SF) other than 0 */ +#define SG_LIB_WILD_RESID 49 /* Residual value for data-in transfer of a + * SCSI command is nonsensical */ +#define SG_LIB_OS_BASE_ERR 50 /* in Linux: values found in: + * include/uapi/asm-generic/errno-base.h + * Example: ENOMEM reported as 62 (=50+12) + * if errno > 46 then use this value */ +/* 51-->96 set aside for Unix errno values shifted by SG_LIB_OS_BASE_ERR */ +#define SG_LIB_CAT_MALFORMED 97 /* Response to SCSI command malformed */ +#define SG_LIB_CAT_SENSE 98 /* Something else is in the sense buffer */ +#define SG_LIB_CAT_OTHER 99 /* Some other error/warning has occurred + * (e.g. a transport or driver error) */ +/* 100 to 120 (inclusive) used by ddpt utility */ +#define SG_LIB_UNUSED_ABOVE 120 /* Put extra errors in holes below this */ + +/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less + * common sense key then return SG_LIB_CAT_SENSE .*/ +int sg_err_category_sense(const uint8_t * sense_buffer, int sb_len); + +/* Here are some additional sense data categories that are not returned + * by sg_err_category_sense() but are returned by some related functions. */ +#define SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO 17 /* Illegal request (other than */ + /* invalid opcode) plus 'info' field: */ + /* [sk,asc,ascq: 0x5,*,*] */ +#define SG_LIB_CAT_MEDIUM_HARD_WITH_INFO 18 /* medium or hardware error */ + /* sense key plus 'info' field: */ + /* [sk,asc,ascq: 0x3/0x4,*,*] */ +#define SG_LIB_CAT_TIMEOUT 33 /* SCSI command timeout */ +#define SG_LIB_CAT_PROTECTION_WITH_INFO 41 /* aborted command sense key, */ + /* protection plus 'info' field: */ + /* [sk,asc,ascq: 0xb,0x10,*] */ + +/* Yield string associated with sense category. Returns 'buff' (or pointer + * to "Bad sense category" if 'buff' is NULL). If sense_cat unknown then + * yield "Sense category: " string. The original 'sense + * category' concept has been expanded to most detected errors and is + * returned by these utilities as their exit status value (an (unsigned) + * 8 bit value where 0 means good (i.e. no errors)). Uses the + * sg_exit2str() function. */ +const char * sg_get_category_sense_str(int sense_cat, int buff_len, + char * buff, int verbose); + + +/* Iterates to next designation descriptor in the device identification + * VPD page. The 'initial_desig_desc' should point to start of first + * descriptor with 'page_len' being the number of valid bytes in that + * and following descriptors. To start, 'off' should point to a negative + * value, thereafter it should point to the value yielded by the previous + * call. If 0 returned then 'initial_desig_desc + *off' should be a valid + * descriptor; returns -1 if normal end condition and -2 for an abnormal + * termination. Matches association, designator_type and/or code_set when + * any of those values are greater than or equal to zero. */ +int sg_vpd_dev_id_iter(const uint8_t * initial_desig_desc, int page_len, + int * off, int m_assoc, int m_desig_type, + int m_code_set); + + +/* <<< General purpose (i.e. not SCSI specific) utility functions >>> */ + +/* Always returns valid string even if errnum is wild (or library problem). + * If errnum is negative, flip its sign. */ +char * safe_strerror(int errnum); + + +/* Print (to stdout) 'str' of bytes in hex, 16 bytes per line optionally + * followed at the right hand side of the line with an ASCII interpretation. + * Each line is prefixed with an address, starting at 0 for str[0]..str[15]. + * All output numbers are in hex. 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 16 ASCII-hex bytes + * = 0 in addition, the bytes are listed in ASCII to the right + * < 0 only the ASCII-hex bytes are listed (i.e. without address) +*/ +void dStrHex(const char * str, int len, int no_ascii); + +/* Print (to sg_warnings_strm (stderr)) 'str' of bytes in hex, 16 bytes per + * line optionally followed at right by its ASCII interpretation. Same + * logic as dStrHex() with different output stream (i.e. stderr). */ +void dStrHexErr(const char * str, int len, int no_ascii); + +/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space + * separated) to 'b' not to exceed 'b_len' characters. Each line + * starts with 'leadin' (NULL for no leadin) and there are 16 bytes + * per line with an extra space between the 8th and 9th bytes. 'format' + * is 0 for repeat in printable ASCII ('.' for non printable chars) to + * right of each line; 1 don't (so just output ASCII hex). Returns + * number of bytes written to 'b' excluding the trailing '\0'. */ +int dStrHexStr(const char * str, int len, const char * leadin, int format, + int cb_len, char * cbp); + +/* The following 3 functions are equivalent to dStrHex(), dStrHexErr() and + * dStrHexStr() respectively. The difference is the type of the first of + * argument: uint8_t instead of char. The name of the argument is changed + * to b_str to stress it is a pointer to the start of a binary string. */ +void hex2stdout(const uint8_t * b_str, int len, int no_ascii); +void hex2stderr(const uint8_t * b_str, int len, int no_ascii); +int hex2str(const uint8_t * b_str, int len, const char * leadin, int format, + int cb_len, char * cbp); + +/* Returns true when executed on big endian machine; else returns false. + * Useful for displaying ATA identify words (which need swapping on a + * big endian machine). */ +bool sg_is_big_endian(); + +/* Returns true if byte sequence starting at bp with a length of b_len is + * all zeros (for sg_all_zeros()) or all 0xff_s (for sg_all_ffs()); + * otherwise returns false. If bp is NULL or b_len <= 0 returns false. */ +bool sg_all_zeros(const uint8_t * bp, int b_len); +bool sg_all_ffs(const uint8_t * bp, int b_len); + +/* Extract character sequence from ATA words as in the model string + * in a IDENTIFY DEVICE response. Returns number of characters + * written to 'ochars' before 0 character is found or 'num' words + * are processed. */ +int sg_ata_get_chars(const uint16_t * word_arr, int start_word, + int num_words, bool is_big_endian, char * ochars); + +/* Print (to stdout) 16 bit 'words' in hex, 8 words per line optionally + * followed at the right hand side of the line with an ASCII interpretation + * (pairs of ASCII characters in big endian order (upper first)). + * Each line is prefixed with an address, starting at 0. + * All output numbers are in hex. 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 8 ASCII-hex words + * = 0 in addition, the words are listed in ASCII pairs to the right + * = -1 only the ASCII-hex words are listed (i.e. without address) + * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" + * < -2 same as -1 + * If 'swapb' is true then bytes in each word swapped. Needs to be set + * for ATA IDENTIFY DEVICE response on big-endian machines. +*/ +void dWordHex(const uint16_t * words, int num, int no_ascii, bool swapb); + +/* If the number in 'buf' can not be decoded or the multiplier is unknown + * then -1 is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H') + * suffix. Otherwise a decimal multiplier suffix may be given. Recognised + * multipliers: c C *1; w W *2; b B *512; k K KiB *1,024; + * KB *1,000; m M MiB *1,048,576; MB *1,000,000; g G GiB *1,073,741,824; + * GB *1,000,000,000 and x which multiplies by . Ignore leading + * spaces and tabs; accept comma, hyphen, space, tab and hash as terminator. + */ +int sg_get_num(const char * buf); + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int sg_get_num_nomult(const char * buf); + +/* If the number in 'buf' can not be decoded or the multiplier is unknown + * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a 'h' (or 'H') + * suffix. Otherwise a decimal multiplier suffix may be given. In addition + * to supporting the multipliers of sg_get_num(), this function supports: + * t T TiB *(2**40); TB *(10**12); p P PiB *(2**50); PB *(10**15) . + * Ignore leading spaces and tabs; accept comma, hyphen, space, tab and hash + * as terminator. */ +int64_t sg_get_llnum(const char * buf); + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int64_t sg_get_llnum_nomult(const char * buf); + +/* Returns pointer to heap (or NULL) that is aligned to a align_to byte + * boundary. Sends back *buff_to_free pointer in third argument that may be + * different from the return value. If it is different then the *buff_to_free + * pointer should be freed (rather than the returned value) when the heap is + * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all + * returned heap to zeros. If num_bytes is 0 then set to page size. */ +uint8_t * sg_memalign(uint32_t num_bytes, uint32_t align_to, + uint8_t ** buff_to_free, bool vb); + +/* Returns OS page size in bytes. If uncertain returns 4096. */ +uint32_t sg_get_page_size(void); + +/* If byte_count is 0 or less then the OS page size is used as denominator. + * Returns true if the remainder of ((unsigned)pointer % byte_count) is 0, + * else returns false. */ +bool sg_is_aligned(const void * pointer, int byte_count); + +/* Does similar job to sg_get_unaligned_be*() but this function starts at + * a given start_bit (i.e. within byte, so 7 is MSbit of byte and 0 is LSbit) + * offset. Maximum number of num_bits is 64. For example, these two + * invocations are equivalent (and should yield the same result); + * sg_get_big_endian(from_bp, 7, 16) + * sg_get_unaligned_be16(from_bp) */ +uint64_t sg_get_big_endian(const uint8_t * from_bp, + int start_bit /* 0 to 7 */, + int num_bits /* 1 to 64 */); + +/* Does similar job to sg_put_unaligned_be*() but this function starts at + * a given start_bit offset. Maximum number of num_bits is 64. Preserves + * residual bits in partially written bytes. start_bit 7 is MSb. */ +void sg_set_big_endian(uint64_t val, uint8_t * to, int start_bit /* 0 to 7 */, + int num_bits /* 1 to 64 */); + +/* If os_err_num is within bounds then the returned value is 'os_err_num + + * SG_LIB_OS_BASE_ERR' otherwise SG_LIB_OS_BASE_ERR is returned. If + * os_err_num is 0 then 0 is returned. */ +int sg_convert_errno(int os_err_num); + + +/* <<< Architectural support functions [is there a better place?] >>> */ + +/* Non Unix OSes distinguish between text and binary files. + * Set text mode on fd. Does nothing in Unix. Returns negative number on + * failure. */ +int sg_set_text_mode(int fd); + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + * failure. */ +int sg_set_binary_mode(int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* SG_LIB_H */ diff --git a/include/sg_lib_data.h b/include/sg_lib_data.h new file mode 100644 index 0000000..a870043 --- /dev/null +++ b/include/sg_lib_data.h @@ -0,0 +1,131 @@ +#ifndef SG_LIB_DATA_H +#define SG_LIB_DATA_H + +/* + * Copyright (c) 2007-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. + */ + +/* + * This header file contains some structure declarations and array name + * declarations which are defined in the sg_lib_data.c . + * Typically this header does not need to be exposed to users of the + * sg_lib interface declared in sg_libs.h . + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Operation codes with associated service actions that change or qualify + * the command name */ +#define SG_EXTENDED_COPY 0x83 /* since spc4r34 became next entry */ +#define SG_3PARTY_COPY_OUT 0x83 /* new in spc4r34: Third party copy out */ +#define SG_RECEIVE_COPY 0x84 /* since spc4r34 became next entry */ +#define SG_3PARTY_COPY_IN 0x84 /* new in spc4r34: Third party copy in */ +#define SG_MAINTENANCE_IN 0xa3 +#define SG_MAINTENANCE_OUT 0xa4 +#define SG_PERSISTENT_RESERVE_IN 0x5e +#define SG_PERSISTENT_RESERVE_OUT 0x5f +#define SG_READ_ATTRIBUTE 0x8c +#define SG_READ_BUFFER 0x3c /* now READ BUFFER(10) */ +#define SG_READ_BUFFER_16 0x9b +#define SG_READ_POSITION 0x34 /* SSC command with service actions */ +#define SG_SANITIZE 0x48 +#define SG_SERVICE_ACTION_BIDI 0x9d +#define SG_SERVICE_ACTION_IN_12 0xab +#define SG_SERVICE_ACTION_IN_16 0x9e +#define SG_SERVICE_ACTION_OUT_12 0xa9 +#define SG_SERVICE_ACTION_OUT_16 0x9f +#define SG_VARIABLE_LENGTH_CMD 0x7f +#define SG_WRITE_BUFFER 0x3b +#define SG_ZONING_OUT 0x94 +#define SG_ZONING_IN 0x95 + + + +struct sg_lib_simple_value_name_t { + int value; + const char * name; +}; + +struct sg_lib_value_name_t { + int value; + int peri_dev_type; /* 0 -> SPC and/or PDT_DISK, >0 -> PDT */ + const char * name; +}; + +struct sg_value_2names_t { + int value; + const char * name; + const char * name2; +}; + +struct sg_lib_asc_ascq_t { + uint8_t asc; /* additional sense code */ + uint8_t ascq; /* additional sense code qualifier */ + const char * text; +}; + +struct sg_lib_asc_ascq_range_t { + uint8_t asc; /* additional sense code (ASC) */ + uint8_t ascq_min; /* ASCQ minimum in range */ + uint8_t ascq_max; /* ASCQ maximum in range */ + const char * text; +}; + +/* First use: SCSI status, sense_key, asc, ascq tuple */ +struct sg_lib_4tuple_u8 { + uint8_t t1; + uint8_t t2; + uint8_t t3; + uint8_t t4; +}; + + +extern const char * sg_lib_version_str; + +extern struct sg_lib_value_name_t sg_lib_normal_opcodes[]; +extern struct sg_lib_value_name_t sg_lib_read_buff_arr[]; +extern struct sg_lib_value_name_t sg_lib_write_buff_arr[]; +extern struct sg_lib_value_name_t sg_lib_maint_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_maint_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_pr_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_pr_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_in12_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_out12_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_in16_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_out16_arr[]; +extern struct sg_lib_value_name_t sg_lib_serv_bidi_arr[]; +extern struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[]; +extern struct sg_lib_value_name_t sg_lib_variable_length_arr[]; +extern struct sg_lib_value_name_t sg_lib_zoning_out_arr[]; +extern struct sg_lib_value_name_t sg_lib_zoning_in_arr[]; +extern struct sg_lib_value_name_t sg_lib_read_attr_arr[]; +extern struct sg_lib_value_name_t sg_lib_read_pos_arr[]; +extern struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[]; +extern struct sg_lib_asc_ascq_t sg_lib_asc_ascq[]; +extern struct sg_lib_value_name_t sg_lib_scsi_feature_sets[]; +extern const char * sg_lib_sense_key_desc[]; +extern const char * sg_lib_pdt_strs[]; +extern const char * sg_lib_transport_proto_strs[]; +extern int sg_lib_pdt_decay_arr[]; + +extern struct sg_lib_simple_value_name_t sg_lib_nvme_admin_cmd_arr[]; +extern struct sg_lib_simple_value_name_t sg_lib_nvme_nvm_cmd_arr[]; +extern struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[]; +extern struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[]; + +extern struct sg_value_2names_t sg_exit_str_arr[]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/sg_linux_inc.h b/include/sg_linux_inc.h new file mode 100644 index 0000000..1f76018 --- /dev/null +++ b/include/sg_linux_inc.h @@ -0,0 +1,57 @@ +#ifndef SG_LINUX_INC_H +#define SG_LINUX_INC_H + +#ifdef SG_KERNEL_INCLUDES + #include /* C99 header for exact integer types */ + #define __user + typedef uint8_t u8; + #include "/usr/src/linux/include/scsi/sg.h" + #include "/usr/src/linux/include/scsi/scsi.h" +#else + #ifdef SG_TRICK_GNU_INCLUDES + #include + #include + #else + #include + #include + #endif +#endif + +#ifdef BLKGETSIZE64 + #ifndef u64 + #include /* C99 header for exact integer types */ + typedef uint64_t u64; /* problems with BLKGETSIZE64 ioctl in lk 2.4 */ + #endif +#endif + +/* + Getting the correct include files for the sg interface can be an ordeal. + In a perfect world, one would just write: + #include + #include + This would include the files found in the /usr/include/scsi directory. + Those files are maintained with the GNU library which may or may not + agree with the kernel and version of sg driver that is running. Any + many cases this will not matter. However in some it might, for example + glibc 2.1's include files match the sg driver found in the lk 2.2 + series. Hence if glibc 2.1 is used with lk 2.4 then the additional + sg v3 interface will not be visible. + If this is a problem then defining SG_KERNEL_INCLUDES will access the + kernel supplied header files (assuming they are in the normal place). + The GNU library maintainers and various kernel people don't like + this approach (but it does work). + The technique selected by defining SG_TRICK_GNU_INCLUDES worked (and + was used) prior to glibc 2.2 . Prior to that version /usr/include/linux + was a symbolic link to /usr/src/linux/include/linux . + + There are other approaches if this include "mixup" causes pain. These + would involve include files being copied or symbolic links being + introduced. + + Sorry about the inconvenience. Typically neither SG_KERNEL_INCLUDES + nor SG_TRICK_GNU_INCLUDES is defined. + + dpg 20010415, 20030522 +*/ + +#endif diff --git a/include/sg_pr2serr.h b/include/sg_pr2serr.h new file mode 100644 index 0000000..c317f5b --- /dev/null +++ b/include/sg_pr2serr.h @@ -0,0 +1,70 @@ +#ifndef SG_PR2SERR_H +#define SG_PR2SERR_H + +/* + * Copyright (c) 2004-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +/* These are convenience functions that replace the somewhat long-winded + * fprintf(stderr, ....). The second form (i.e. pr2ws() ) is for internal + * library use and may place its output somewhere other than stderr; it + * depends on the external variable sg_warnings_strm which can be set + * with sg_set_warnings_strm(). By default it uses stderr. */ + +/* With regard to sg_scnpr(): + * Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of + * functions. Returns number of chars placed in cp excluding the + * trailing null char. So for cp_max_len > 0 the return value is always + * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are + * written to cp. Note this means that when cp_max_len = 1, this function + * assumes that cp[0] is the null character and does nothing (and returns + * 0). Linux kernel has a similar function called scnprintf(). */ + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(__GNUC__) || defined(__clang__) +#ifdef SG_LIB_MINGW +/* MinGW uses Microsoft's printf */ +int pr2serr(const char * fmt, ...); + +int pr2ws(const char * fmt, ...); + +int sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...); + +#else /* GNU/clang other than MinGW */ + +int pr2serr(const char * fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +int pr2ws(const char * fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +int sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...) + __attribute__ ((format (printf, 3, 4))); +#endif + +#else /* not GNU (and not clang) */ + +int pr2serr(const char * fmt, ...); + +int pr2ws(const char * fmt, ...); + +int sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...); + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/sg_pt.h b/include/sg_pt.h new file mode 100644 index 0000000..c882893 --- /dev/null +++ b/include/sg_pt.h @@ -0,0 +1,246 @@ +#ifndef SG_PT_H +#define SG_PT_H + +/* + * 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. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This declaration hides the fact that each implementation has its own + * structure "derived" (using a C++ term) from this one. It compiles + * because 'struct sg_pt_base' is only referenced (by pointer: 'objp') + * in this interface. An instance of this structure represents the + * context of one SCSI command. + * If an instance of sg_pt_base is shared across several threads then + * it is up to the application to take care of multi-threaded issues + * with that instance. */ +struct sg_pt_base; + + +/* The format of the version string is like this: "3.04 20180213". + * The leading digit will be incremented if this interface changes + * in a way that may impact backward compatibility. */ +const char * scsi_pt_version(); +const char * sg_pt_version(); /* both functions give same result */ + + +/* Returns file descriptor or file handle and is >= 0 if successful. + * If error in Unix returns negated errno. */ +int scsi_pt_open_device(const char * device_name, bool read_only, + int verbose); + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. Returns valid file descriptor or handle ( >= 0 ) if successful, + * otherwise returns -1 or a negated errno. + * In Win32 O_EXCL translated to equivalent. */ +int scsi_pt_open_flags(const char * device_name, int flags, int verbose); + +/* Returns 0 if successful. 'device_fd' should be a value that was previously + * returned by scsi_pt_open_device() or scsi_pt_open_flags() that has not + * already been closed. If error in Unix returns negated errno. */ +int scsi_pt_close_device(int device_fd); + +/* Assumes dev_fd is an "open" file handle associated with device_name. If + * the implementation (possibly for one OS) cannot determine from dev_fd if + * a SCSI or NVMe pass-through is referenced, then it might guess based on + * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if + * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is + * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes + * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0. + * If error, returns negated errno (operating system) value. */ +int check_pt_file_handle(int dev_fd, const char * device_name, int verbose); + + +/* Creates an object that can be used to issue one or more SCSI commands + * (or task management functions). Returns NULL if problem. + * Once this object has been created it should be destroyed with + * destruct_scsi_pt_obj() when it is no longer needed. */ +struct sg_pt_base * construct_scsi_pt_obj(void); + +/* An alternate and preferred way to create an object that can be used to + * issue one or more SCSI (or NVMe) commands (or task management functions). + * This variant associates a device file descriptor (handle) with the object + * and a verbose argument that causes messages to be written to stderr if + * errors occur. The reason for this is to optionally allow the detection of + * NVMe devices that will cause pt_device_is_nvme() to return true. Set + * dev_fd to -1 if no open device file descriptor is available. Caller + * should additionally call get_scsi_pt_os_err() after this call to check + * for errors. The dev_fd argument may be -1 to indicate no device file + * descriptor. */ +struct sg_pt_base * + construct_scsi_pt_obj_with_fd(int dev_fd, int verbose); + +/* Forget any previous dev_fd and install the one given. May attempt to + * find file type (e.g. if pass-though) from OS so there could be an error. + * Returns 0 for success or the same value as get_scsi_pt_os_err() + * will return. dev_fd should be >= 0 for a valid file handle or -1 . */ +int set_pt_file_handle(struct sg_pt_base * objp, int dev_fd, int verbose); + +/* Valid file handles (which is the return value) are >= 0 . Returns -1 + * if there is no valid file handle. */ +int get_pt_file_handle(const struct sg_pt_base * objp); + +/* Clear state information held in *objp . This allows this object to be + * used to issue more than one SCSI command. The dev_fd is remembered. + * Use set_pt_file_handle() to change dev_fd. */ +void clear_scsi_pt_obj(struct sg_pt_base * objp); + +/* Set the CDB (command descriptor block). May also be a NVMe Admin command + * which will be 64 bytes long. + * + * Note that the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be + * called after this function to "guess" which command set the given command + * belongs to. */ +void set_scsi_pt_cdb(struct sg_pt_base * objp, const uint8_t * cdb, + int cdb_len); + +/* Set the sense buffer and the maximum length of that buffer. For NVMe + * commands this "sense" buffer will receive the 4 DWORDs of from the + * completion queue. */ +void set_scsi_pt_sense(struct sg_pt_base * objp, uint8_t * sense, + int max_sense_len); + +/* Set a pointer and length to be used for data transferred from device */ +void set_scsi_pt_data_in(struct sg_pt_base * objp, /* from device */ + uint8_t * dxferp, int dxfer_ilen); + +/* Set a pointer and length to be used for data transferred to device */ +void set_scsi_pt_data_out(struct sg_pt_base * objp, /* to device */ + const uint8_t * dxferp, int dxfer_olen); + +/* Set a pointer and length to be used for metadata transferred to + * (out_true=true) or from (out_true=false) device (NVMe only) */ +void set_pt_metadata_xfer(struct sg_pt_base * objp, uint8_t * mdxferp, + uint32_t mdxfer_len, bool out_true); + +/* The following "set_"s implementations may be dummies */ +void set_scsi_pt_packet_id(struct sg_pt_base * objp, int pack_id); +void set_scsi_pt_tag(struct sg_pt_base * objp, uint64_t tag); +void set_scsi_pt_task_management(struct sg_pt_base * objp, int tmf_code); +void set_scsi_pt_task_attr(struct sg_pt_base * objp, int attribute, + int priority); + +/* Following is a guard which is defined when set_scsi_pt_flags() is + * present. Older versions of this library may not have this function. */ +#define SCSI_PT_FLAGS_FUNCTION 1 +/* If neither QUEUE_AT_HEAD nor QUEUE_AT_TAIL are given, or both + * are given, use the pass-through default. */ +#define SCSI_PT_FLAGS_QUEUE_AT_TAIL 0x10 +#define SCSI_PT_FLAGS_QUEUE_AT_HEAD 0x20 +/* Set (potentially OS dependent) flags for pass-through mechanism. + * Apart from contradictions, flags can be OR-ed together. */ +void set_scsi_pt_flags(struct sg_pt_base * objp, int flags); + +#define SCSI_PT_DO_START_OK 0 +#define SCSI_PT_DO_BAD_PARAMS 1 +#define SCSI_PT_DO_TIMEOUT 2 +#define SCSI_PT_DO_NVME_STATUS 48 /* == SG_LIB_NVME_STATUS */ +/* If OS error prior to or during command submission then returns negated + * error value (e.g. Unix '-errno'). This includes interrupted system calls + * (e.g. by a signal) in which case -EINTR would be returned. Note that + * system call errors also can be fetched with get_scsi_pt_os_err(). + * Return 0 if okay (i.e. at the very least: command sent). Positive + * return values are errors (see SCSI_PT_DO_* defines). If a file descriptor + * has already been provided by construct_scsi_pt_obj_with_fd() then the + * given 'fd' can be -1 or the same value as given to the constructor. */ +int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs, + int verbose); + +#define SCSI_PT_RESULT_GOOD 0 +#define SCSI_PT_RESULT_STATUS 1 /* other than GOOD and CHECK CONDITION */ +#define SCSI_PT_RESULT_SENSE 2 +#define SCSI_PT_RESULT_TRANSPORT_ERR 3 +#define SCSI_PT_RESULT_OS_ERR 4 +/* This function, called soon after do_scsi_pt(), returns one of the above + * result categories. The highest numbered applicable category is returned. + * + * Note that the sg_cmds_process_resp() function found in sg_cmds_basic.h + * is useful for processing SCSI command responses. + * And the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be called + * after set_scsi_pt_cdb() to "guess" which command set the given command + * belongs to. */ +int get_scsi_pt_result_category(const struct sg_pt_base * objp); + +/* If not available return 0 which implies there is no residual + * value. If supported the number of bytes actually sent back by + * the device is 'dxfer_ilen - get_scsi_pt_len()' bytes. */ +int get_scsi_pt_resid(const struct sg_pt_base * objp); + +/* Returns SCSI status value (from device that received the command). If an + * NVMe command was issued directly (i.e. through do_scsi_pt() then return + * NVMe status (i.e. ((SCT << 8) | SC)). If problem returns -1. */ +int get_scsi_pt_status_response(const struct sg_pt_base * objp); + +/* Returns SCSI status value or, if NVMe command given to do_scsi_pt(), + * then returns NVMe result (i.e. DWord(0) from completion queue). If + * 'objp' is NULL then returns 0xffffffff. */ +uint32_t get_pt_result(const struct sg_pt_base * objp); + +/* Actual sense length returned. If sense data is present but + actual sense length is not known, return 'max_sense_len' */ +int get_scsi_pt_sense_len(const struct sg_pt_base * objp); + +/* If not available return 0 (for success). */ +int get_scsi_pt_os_err(const struct sg_pt_base * objp); +char * get_scsi_pt_os_err_str(const struct sg_pt_base * objp, int max_b_len, + char * b); + +/* If not available return 0 (for success) */ +int get_scsi_pt_transport_err(const struct sg_pt_base * objp); +void set_scsi_pt_transport_err(struct sg_pt_base * objp, int err); +char * get_scsi_pt_transport_err_str(const struct sg_pt_base * objp, + int max_b_len, char * b); + +/* If not available return -1 otherwise return number of milliseconds + * that the lower layers (and hardware) took to execute the previous + * command. */ +int get_scsi_pt_duration_ms(const struct sg_pt_base * objp); + +/* Return true if device associated with 'objp' uses NVMe command set. To + * be useful (in modifying the type of command sent (SCSI or NVMe) then + * construct_scsi_pt_obj_with_fd() should be used followed by an invocation + * of this function. */ +bool pt_device_is_nvme(const struct sg_pt_base * objp); + +/* If a NVMe block device (which includes the NSID) handle is associated + * with 'objp', then its NSID is returned (values range from 0x1 to + * 0xffffffe). Otherwise 0 is returned. */ +uint32_t get_pt_nvme_nsid(const struct sg_pt_base * objp); + + +/* Should be invoked once per objp after other processing is complete in + * order to clean up resources. For ever successful construct_scsi_pt_obj() + * call there should be one destruct_scsi_pt_obj(). If the + * construct_scsi_pt_obj_with_fd() function was used to create this object + * then the dev_fd provided to that constructor is not altered by this + * destructor. So the user should still close dev_fd (perhaps with + * scsi_pt_close_device() ). */ +void destruct_scsi_pt_obj(struct sg_pt_base * objp); + +#ifdef SG_LIB_WIN32 +#define SG_LIB_WIN32_DIRECT 1 + +/* Request SPT direct interface when state_direct is 1, state_direct set + * to 0 for the SPT indirect interface. Default setting selected by build + * (i.e. library compile time) and is usually indirect. */ +void scsi_pt_win32_direct(int state_direct); + +/* Returns current SPT interface state, 1 for direct, 0 for indirect */ +int scsi_pt_win32_spt_state(void); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* SG_PT_H */ diff --git a/include/sg_pt_linux.h b/include/sg_pt_linux.h new file mode 100644 index 0000000..00010ba --- /dev/null +++ b/include/sg_pt_linux.h @@ -0,0 +1,173 @@ +#ifndef SG_PT_LINUX_H +#define SG_PT_LINUX_H + +/* + * Copyright (c) 2017-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include + +#include + +#include "sg_pt_nvme.h" + +/* This header is for internal use by the sg3_utils library (libsgutils) + * and is Linux specific. Best not to include it directly in code that + * is meant to be OS independent. */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_LINUX_BSG_H + +#define BSG_PROTOCOL_SCSI 0 + +#define BSG_SUB_PROTOCOL_SCSI_CMD 0 +#define BSG_SUB_PROTOCOL_SCSI_TMF 1 +#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2 + +/* + * For flag constants below: + * sg.h sg_io_hdr also has bits defined for it's flags member. These + * two flag values (0x10 and 0x20) have the same meaning in sg.h . For + * bsg the BSG_FLAG_Q_AT_HEAD flag is ignored since it is the default. + */ +#define BSG_FLAG_Q_AT_TAIL 0x10 /* default is Q_AT_HEAD */ +#define BSG_FLAG_Q_AT_HEAD 0x20 + +struct sg_io_v4 { + __s32 guard; /* [i] 'Q' to differentiate from v3 */ + __u32 protocol; /* [i] 0 -> SCSI , .... */ + __u32 subprotocol; /* [i] 0 -> SCSI command, 1 -> SCSI task + management function, .... */ + + __u32 request_len; /* [i] in bytes */ + __u64 request; /* [i], [*i] {SCSI: cdb} */ + __u64 request_tag; /* [i] {SCSI: task tag (only if flagged)} */ + __u32 request_attr; /* [i] {SCSI: task attribute} */ + __u32 request_priority; /* [i] {SCSI: task priority} */ + __u32 request_extra; /* [i] {spare, for padding} */ + __u32 max_response_len; /* [i] in bytes */ + __u64 response; /* [i], [*o] {SCSI: (auto)sense data} */ + + /* "dout_": data out (to device); "din_": data in (from device) */ + __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else + dout_xfer points to array of iovec */ + __u32 dout_xfer_len; /* [i] bytes to be transferred to device */ + __u32 din_iovec_count; /* [i] 0 -> "flat" din transfer */ + __u32 din_xfer_len; /* [i] bytes to be transferred from device */ + __u64 dout_xferp; /* [i], [*i] */ + __u64 din_xferp; /* [i], [*o] */ + + __u32 timeout; /* [i] units: millisecond */ + __u32 flags; /* [i] bit mask */ + __u64 usr_ptr; /* [i->o] unused internally */ + __u32 spare_in; /* [i] */ + + __u32 driver_status; /* [o] 0 -> ok */ + __u32 transport_status; /* [o] 0 -> ok */ + __u32 device_status; /* [o] {SCSI: command completion status} */ + __u32 retry_delay; /* [o] {SCSI: status auxiliary information} */ + __u32 info; /* [o] additional information */ + __u32 duration; /* [o] time to complete, in milliseconds */ + __u32 response_len; /* [o] bytes of response actually written */ + __s32 din_resid; /* [o] din_xfer_len - actual_din_xfer_len */ + __s32 dout_resid; /* [o] dout_xfer_len - actual_dout_xfer_len */ + __u64 generated_tag; /* [o] {SCSI: transport generated task tag} */ + __u32 spare_out; /* [o] */ + + __u32 padding; +}; + +#else + +#include + +#endif + + +struct sg_pt_linux_scsi { + struct sg_io_v4 io_hdr; /* use v4 header as it is more general */ + /* Leave io_hdr in first place of this structure */ + bool is_sg; + bool is_bsg; + bool is_nvme; /* OS device type, if false ignore nvme_direct */ + bool nvme_direct; /* false: our SNTL; true: received NVMe command */ + bool nvme_stat_dnr; /* Do No Retry, part of completion status field */ + bool nvme_stat_more; /* More, part of completion status field */ + bool mdxfer_out; /* direction of metadata xfer, true->data-out */ + int dev_fd; /* -1 if not given (yet) */ + int in_err; + int os_err; + uint32_t nvme_nsid; /* 1 to 0xfffffffe are possibly valid, 0 + * implies dev_fd is not a NVMe device + * (is_nvme=false) or it is a NVMe char + * device (e.g. /dev/nvme0 ) */ + uint32_t nvme_result; /* DW0 from completion queue */ + uint32_t nvme_status; /* SCT|SC: DW3 27:17 from completion queue, + * note: the DNR+More bit are not there. + * The whole 16 byte completion q entry is + * sent back as sense data */ + uint32_t mdxfer_len; + struct sg_sntl_dev_state_t dev_stat; + void * mdxferp; + uint8_t * nvme_id_ctlp; /* cached response to controller IDENTIFY */ + uint8_t * free_nvme_id_ctlp; + uint8_t tmf_request[4]; +}; + +struct sg_pt_base { + struct sg_pt_linux_scsi impl; +}; + + +#ifndef sg_nvme_admin_cmd +#define sg_nvme_admin_cmd sg_nvme_passthru_cmd +#endif + +/* Linux NVMe related ioctls */ +#ifndef NVME_IOCTL_ID +#define NVME_IOCTL_ID _IO('N', 0x40) +#endif +#ifndef NVME_IOCTL_ADMIN_CMD +#define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct sg_nvme_admin_cmd) +#endif +#ifndef NVME_IOCTL_SUBMIT_IO +#define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct sg_nvme_user_io) +#endif +#ifndef NVME_IOCTL_IO_CMD +#define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct sg_nvme_passthru_cmd) +#endif +#ifndef NVME_IOCTL_RESET +#define NVME_IOCTL_RESET _IO('N', 0x44) +#endif +#ifndef NVME_IOCTL_SUBSYS_RESET +#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) +#endif + +extern bool sg_bsg_nvme_char_major_checked; +extern int sg_bsg_major; +extern volatile int sg_nvme_char_major; +extern long sg_lin_page_size; + +void sg_find_bsg_nvme_char_major(int verbose); +int sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb); + +/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5) + * to the name of its associated char device (e.g. /dev/nvme0). If this + * occurs true is returned and the char device name is placed in 'b' (as + * long as b_len is sufficient). Otherwise false is returned. */ +bool sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len, + char * b); + + +#ifdef __cplusplus +} +#endif + +#endif /* end of SG_PT_LINUX_H */ diff --git a/include/sg_pt_nvme.h b/include/sg_pt_nvme.h new file mode 100644 index 0000000..a910b05 --- /dev/null +++ b/include/sg_pt_nvme.h @@ -0,0 +1,218 @@ +#ifndef SG_PT_NVME_H +#define SG_PT_NVME_H + +/* + * Copyright (c) 2017-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* structures copied and slightly modified from which + * is Copyright (c) 2011-2014, Intel Corporation. */ + + +/* Note that the command input structure is in (packed) "cpu" format. That + * means, for example, if the CPU is little endian (most are) then so is the + * structure. However what comes out in the data-in buffer (e.g. for the + * Admin Identify command response) is almost all little endian following ATA + * (but no SCSI and IP which are big endian) and Intel's preference. There + * are exceptions, for example the EUI-64 identifiers in the Admin Identify + * response are big endian. + * + * Code online (e.g. nvme-cli at github.com) seems to like packed strcutures, + * the author prefers byte offset plus a range of unaligned integer builders + * such as those in sg_unaligned.h . + */ + +#ifdef __GNUC__ +#ifndef __clang__ + struct __attribute__((__packed__)) sg_nvme_user_io +#else + struct sg_nvme_user_io +#endif +#else +struct sg_nvme_user_io +#endif +{ + uint8_t opcode; + uint8_t flags; + uint16_t control; + uint16_t nblocks; + uint16_t rsvd; + uint64_t metadata; + uint64_t addr; + uint64_t slba; + uint32_t dsmgmt; + uint32_t reftag; + uint16_t apptag; + uint16_t appmask; +} +#ifdef SG_LIB_FREEBSD +__packed; +#else +; +#endif + +/* Using byte offsets and unaligned be/le copies safer than packed + * structures. These are for sg_nvme_user_io . */ +#define SG_NVME_IO_OPCODE 0 +#define SG_NVME_IO_FLAGS 1 +#define SG_NVME_IO_CONTROL 2 +#define SG_NVME_IO_NBLOCKS 4 +#define SG_NVME_IO_RSVD 6 +#define SG_NVME_IO_METADATA 8 +#define SG_NVME_IO_ADDR 16 +#define SG_NVME_IO_SLBA 24 +#define SG_NVME_IO_DSMGMT 32 +#define SG_NVME_IO_REFTAG 36 +#define SG_NVME_IO_APPTAG 40 +#define SG_NVME_IO_APPMASK 42 + +#ifdef __GNUC__ +#ifndef __clang__ + struct __attribute__((__packed__)) sg_nvme_passthru_cmd +#else + struct sg_nvme_passthru_cmd +#endif +#else +struct sg_nvme_passthru_cmd +#endif +{ + uint8_t opcode; + uint8_t flags; + uint16_t rsvd1; + uint32_t nsid; + uint32_t cdw2; + uint32_t cdw3; + uint64_t metadata; + uint64_t addr; + uint32_t metadata_len; + uint32_t data_len; + uint32_t cdw10; + uint32_t cdw11; + uint32_t cdw12; + uint32_t cdw13; + uint32_t cdw14; + uint32_t cdw15; +#ifdef SG_LIB_LINUX + uint32_t timeout_ms; + uint32_t result; /* out: DWord(0) from completion queue */ +#endif +} +#ifdef SG_LIB_FREEBSD +__packed; +#else +; +#endif + +struct sg_sntl_dev_state_t { + uint8_t scsi_dsense; + uint8_t enclosure_override; /* ENC_OV in sdparm */ + uint8_t pdt; /* 6 bit value in INQUIRY response */ + uint8_t enc_serv; /* single bit in INQUIRY response */ + uint8_t id_ctl253; /* NVMSR field of Identify controller (byte 253) */ +}; + +struct sg_sntl_result_t { + uint8_t sstatus; + uint8_t sk; + uint8_t asc; + uint8_t ascq; + uint8_t in_byte; + uint8_t in_bit; /* use 255 for 'no bit position given' */ +}; + +struct sg_opcode_info_t { + uint8_t opcode; + uint16_t sa; /* service action, 0 for none */ + uint32_t flags; /* OR-ed set of F_* flags */ + uint8_t len_mask[16]; /* len=len_mask[0], then mask for cdb[1]... */ + /* ignore cdb bytes after position 15 */ +}; + +/* Using byte offsets and unaligned be/le copies safer than packed + * structures. These are for sg_nvme_passthru_cmd . */ +#define SG_NVME_PT_OPCODE 0 /* length: 1 byte */ +#define SG_NVME_PT_FLAGS 1 /* length: 1 byte */ +#define SG_NVME_PT_RSVD1 2 /* length: 2 bytes */ +#define SG_NVME_PT_NSID 4 /* length: 4 bytes */ +#define SG_NVME_PT_CDW2 8 /* length: 4 bytes */ +#define SG_NVME_PT_CDW3 12 /* length: 4 bytes */ +#define SG_NVME_PT_METADATA 16 /* length: 8 bytes */ +#define SG_NVME_PT_ADDR 24 /* length: 8 bytes */ +#define SG_NVME_PT_METADATA_LEN 32 /* length: 4 bytes */ +#define SG_NVME_PT_DATA_LEN 36 /* length: 4 bytes */ +#define SG_NVME_PT_CDW10 40 /* length: 4 bytes */ +#define SG_NVME_PT_CDW11 44 /* length: 4 bytes */ +#define SG_NVME_PT_CDW12 48 /* length: 4 bytes */ +#define SG_NVME_PT_CDW13 52 /* length: 4 bytes */ +#define SG_NVME_PT_CDW14 56 /* length: 4 bytes */ +#define SG_NVME_PT_CDW15 60 /* length: 4 bytes */ + +#ifdef SG_LIB_LINUX +/* General references state that "all NVMe commands are 64 bytes long". If + * so then the following are add-ons by Linux, go to the OS and not the + * the NVMe device. */ +#define SG_NVME_PT_TIMEOUT_MS 64 /* length: 4 bytes */ +#define SG_NVME_PT_RESULT 68 /* length: 4 bytes */ +#endif + +/* Byte offset of Result and Status (plus phase bit) in CQ */ +#define SG_NVME_PT_CQ_RESULT 0 /* CDW0, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW0 0 /* CDW0, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW1 4 /* CDW1, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW2 8 /* CDW2, length: 4 bytes */ +#define SG_NVME_PT_CQ_DW3 12 /* CDW3, length: 4 bytes */ +#define SG_NVME_PT_CQ_STATUS_P 14 /* CDW3 31:16, length: 2 bytes */ + + +/* Valid namespace IDs (nsid_s) range from 1 to 0xfffffffe, leaving: */ +#define SG_NVME_BROADCAST_NSID 0xffffffff /* all namespaces */ +#define SG_NVME_CTL_NSID 0x0 /* the "controller's" namespace */ + +/* Vendor specific (sg3_utils) VPD pages */ +#define SG_NVME_VPD_NICR 0xde /* NVME Identify controller response */ + +extern struct sg_opcode_info_t sg_opcode_info_arr[]; + + +/* Given the NVMe Identify Controller response and optionally the NVMe + * Identify Namespace response (NULL otherwise), generate the SCSI VPD + * page 0x83 (device identification) descriptor(s) in dop. Return the + * number of bytes written which will not exceed max_do_len. Probably use + * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport + * protocol (tproto) should be -1 if not known, else SCSI value. + * N.B. Does not write total VPD page length into dop[2:3] . */ +int sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p, + const uint8_t * nvme_id_ns_p, int pdt, + int tproto, uint8_t * dop, int max_do_len); + +/* Initialize dev_stat pointed to by dsp */ +void sntl_init_dev_stat(struct sg_sntl_dev_state_t * dsp); + +/* Internal function (common to all OSes) to support the SNTL SCSI MODE + * SENSE(10) command. Has a vendor specific Unit Attention mpage which + * has only one field currently: ENC_OV (enclosure override) */ +int sntl_resp_mode_sense10(const struct sg_sntl_dev_state_t * dsp, + const uint8_t * cdbp, uint8_t * dip, int mx_di_len, + struct sg_sntl_result_t * resp); + +/* Internal function (common to all OSes) to support the SNTL SCSI MODE + * SELECT(10) command. */ +int sntl_resp_mode_select10(struct sg_sntl_dev_state_t * dsp, + const uint8_t * cdbp, const uint8_t * dop, + int do_len, struct sg_sntl_result_t * resp); + +#ifdef __cplusplus +} +#endif + +#endif /* SG_PT_NVME_H */ diff --git a/include/sg_pt_win32.h b/include/sg_pt_win32.h new file mode 100644 index 0000000..a19485d --- /dev/null +++ b/include/sg_pt_win32.h @@ -0,0 +1,473 @@ +#ifndef SG_PT_WIN32_H +#define SG_PT_WIN32_H +/* + * The information in this file was obtained from scsi-wnt.h by + * Richard Stemmer, rs@epost.de . He in turn gives credit to + * Jay A. Key (for scsipt.c). + * The plscsi program (by Pat LaVarre ) has + * also been used as a reference. + * Much of the information in this header can also be obtained + * from msdn.microsoft.com . + * Updated for cygwin version 1.7.17 changes 20121026 + */ + +/* WIN32_LEAN_AND_MEAN may be required to prevent inclusion of */ +#define WIN32_LEAN_AND_MEAN +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCSI_MAX_SENSE_LEN 64 +#define SCSI_MAX_CDB_LEN 16 +#define SCSI_MAX_INDIRECT_DATA 16384 + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + ULONG_PTR DataBufferOffset; /* was ULONG; problem in 64 bit */ + ULONG SenseInfoOffset; + UCHAR Cdb[SCSI_MAX_CDB_LEN]; +} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH; + + +typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + PVOID DataBuffer; + ULONG SenseInfoOffset; + UCHAR Cdb[SCSI_MAX_CDB_LEN]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + + +typedef struct { + SCSI_PASS_THROUGH spt; + /* plscsi shows a follow on 16 bytes allowing 32 byte cdb */ + ULONG Filler; + UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN]; + UCHAR ucDataBuf[SCSI_MAX_INDIRECT_DATA]; +} SCSI_PASS_THROUGH_WITH_BUFFERS, *PSCSI_PASS_THROUGH_WITH_BUFFERS; + + +typedef struct { + SCSI_PASS_THROUGH_DIRECT spt; + ULONG Filler; + UCHAR ucSenseBuf[SCSI_MAX_SENSE_LEN]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + + + +typedef struct { + UCHAR NumberOfLogicalUnits; + UCHAR InitiatorBusId; + ULONG InquiryDataOffset; +} SCSI_BUS_DATA, *PSCSI_BUS_DATA; + + +typedef struct { + UCHAR NumberOfBusses; + SCSI_BUS_DATA BusData[1]; +} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO; + + +typedef struct { + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + BOOLEAN DeviceClaimed; + ULONG InquiryDataLength; + ULONG NextInquiryDataOffset; + UCHAR InquiryData[1]; +} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA; + + +typedef struct { + ULONG Length; + UCHAR PortNumber; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; +} SCSI_ADDRESS, *PSCSI_ADDRESS; + +/* + * Standard IOCTL define + */ +#ifndef CTL_CODE +#define CTL_CODE(DevType, Function, Method, Access) \ + (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) +#endif + +/* + * file access values + */ +#ifndef FILE_ANY_ACCESS +#define FILE_ANY_ACCESS 0 +#endif +#ifndef FILE_READ_ACCESS +#define FILE_READ_ACCESS 0x0001 +#endif +#ifndef FILE_WRITE_ACCESS +#define FILE_WRITE_ACCESS 0x0002 +#endif + +// IOCTL_STORAGE_QUERY_PROPERTY + +#define FILE_DEVICE_MASS_STORAGE 0x0000002d +#define IOCTL_STORAGE_BASE FILE_DEVICE_MASS_STORAGE +#define FILE_ANY_ACCESS 0 + +// #define METHOD_BUFFERED 0 + +#define IOCTL_STORAGE_QUERY_PROPERTY \ + CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) + + +#ifndef _DEVIOCTL_ +typedef enum _STORAGE_BUS_TYPE { + BusTypeUnknown = 0x00, + BusTypeScsi = 0x01, + BusTypeAtapi = 0x02, + BusTypeAta = 0x03, + BusType1394 = 0x04, + BusTypeSsa = 0x05, + BusTypeFibre = 0x06, + BusTypeUsb = 0x07, + BusTypeRAID = 0x08, + BusTypeiScsi = 0x09, + BusTypeSas = 0x0A, + BusTypeSata = 0x0B, + BusTypeSd = 0x0C, + BusTypeMmc = 0x0D, + BusTypeVirtual = 0xE, + BusTypeFileBackedVirtual = 0xF, + BusTypeSpaces = 0x10, + BusTypeNvme = 0x11, + BusTypeSCM = 0x12, + BusTypeUfs = 0x13, + BusTypeMax = 0x14, + BusTypeMaxReserved = 0x7F +} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE; + +typedef enum _STORAGE_PROTOCOL_TYPE { + ProtocolTypeUnknown = 0, + ProtocolTypeScsi, + ProtocolTypeAta, + ProtocolTypeNvme, + ProtocolTypeSd +} STORAGE_PROTOCOL_TYPE; + +typedef enum _STORAGE_PROTOCOL_NVME_DATA_TYPE { + NVMeDataTypeUnknown = 0, + NVMeDataTypeIdentify, + NVMeDataTypeLogPage, + NVMeDataTypeFeature +} STORAGE_PROTOCOL_NVME_DATA_TYPE; + +typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA { + STORAGE_PROTOCOL_TYPE ProtocolType; + ULONG DataType; + ULONG ProtocolDataRequestValue; + ULONG ProtocolDataRequestSubValue; + ULONG ProtocolDataOffset; + ULONG ProtocolDataLength; + ULONG FixedProtocolReturnData; + ULONG Reserved[3]; +} STORAGE_PROTOCOL_SPECIFIC_DATA; + + +typedef struct _STORAGE_DEVICE_DESCRIPTOR { + ULONG Version; + ULONG Size; + UCHAR DeviceType; + UCHAR DeviceTypeModifier; + BOOLEAN RemovableMedia; + BOOLEAN CommandQueueing; + ULONG VendorIdOffset; /* 0 if not available */ + ULONG ProductIdOffset; /* 0 if not available */ + ULONG ProductRevisionOffset;/* 0 if not available */ + ULONG SerialNumberOffset; /* -1 if not available ?? */ + STORAGE_BUS_TYPE BusType; + ULONG RawPropertiesLength; + UCHAR RawDeviceProperties[1]; +} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR; + +#define STORAGE_PROTOCOL_STRUCTURE_VERSION 0x1 + +#define IOCTL_STORAGE_PROTOCOL_COMMAND \ + CTL_CODE(IOCTL_STORAGE_BASE, 0x04F0, METHOD_BUFFERED, \ + FILE_READ_ACCESS | FILE_WRITE_ACCESS) + +typedef struct _STORAGE_PROTOCOL_COMMAND { + DWORD Version; /* STORAGE_PROTOCOL_STRUCTURE_VERSION */ + DWORD Length; + STORAGE_PROTOCOL_TYPE ProtocolType; + DWORD Flags; + DWORD ReturnStatus; + DWORD ErrorCode; + DWORD CommandLength; + DWORD ErrorInfoLength; + DWORD DataToDeviceTransferLength; + DWORD DataFromDeviceTransferLength; + DWORD TimeOutValue; + DWORD ErrorInfoOffset; + DWORD DataToDeviceBufferOffset; + DWORD DataFromDeviceBufferOffset; + DWORD CommandSpecific; + DWORD Reserved0; + DWORD FixedProtocolReturnData; + DWORD Reserved1[3]; + BYTE Command[1]; /* has CommandLength elements */ +} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND; + +#endif /* _DEVIOCTL_ */ + +typedef struct _STORAGE_DEVICE_UNIQUE_IDENTIFIER { + ULONG Version; + ULONG Size; + ULONG StorageDeviceIdOffset; + ULONG StorageDeviceOffset; + ULONG DriveLayoutSignatureOffset; +} STORAGE_DEVICE_UNIQUE_IDENTIFIER, *PSTORAGE_DEVICE_UNIQUE_IDENTIFIER; + +// Use CompareStorageDuids(PSTORAGE_DEVICE_UNIQUE_IDENTIFIER duid1, duid2) +// to test for equality + +#ifndef _DEVIOCTL_ +typedef enum _STORAGE_QUERY_TYPE { + PropertyStandardQuery = 0, + PropertyExistsQuery, + PropertyMaskQuery, + PropertyQueryMaxDefined +} STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE; + +typedef enum _STORAGE_PROPERTY_ID { + StorageDeviceProperty = 0, + StorageAdapterProperty, + StorageDeviceIdProperty, + StorageDeviceUniqueIdProperty, + StorageDeviceWriteCacheProperty, + StorageMiniportProperty, + StorageAccessAlignmentProperty, + /* Identify controller goes to adapter; Identify namespace to device */ + StorageAdapterProtocolSpecificProperty = 49, + StorageDeviceProtocolSpecificProperty = 50 +} STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID; + +typedef struct _STORAGE_PROPERTY_QUERY { + STORAGE_PROPERTY_ID PropertyId; + STORAGE_QUERY_TYPE QueryType; + UCHAR AdditionalParameters[1]; +} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY; + +typedef struct _STORAGE_PROTOCOL_DATA_DESCRIPTOR { + DWORD Version; + DWORD Size; + STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecificData; +} STORAGE_PROTOCOL_DATA_DESCRIPTOR, *PSTORAGE_PROTOCOL_DATA_DESCRIPTOR; + +// Command completion status +// The "Phase Tag" field and "Status Field" are separated in spec. We define +// them in the same data structure to ease the memory access from software. +// +typedef union { + struct { + USHORT P : 1; // Phase Tag (P) + + USHORT SC : 8; // Status Code (SC) + USHORT SCT : 3; // Status Code Type (SCT) + USHORT Reserved : 2; + USHORT M : 1; // More (M) + USHORT DNR : 1; // Do Not Retry (DNR) + } DUMMYSTRUCTNAME; + USHORT AsUshort; +} NVME_COMMAND_STATUS, *PNVME_COMMAND_STATUS; + +// Information of log: NVME_LOG_PAGE_ERROR_INFO. Size: 64 bytes +// +typedef struct { + ULONGLONG ErrorCount; + USHORT SQID; // Submission Queue ID + USHORT CMDID; // Command ID + NVME_COMMAND_STATUS Status; // Status Field: This field indicates the + // Status Field for the command that + // completed. The Status Field is located in + // bits 15:01, bit 00 corresponds to the Phase + // Tag posted for the command. + struct { + USHORT Byte : 8; // Byte in command that contained error + USHORT Bit : 3; // Bit in command that contained error + USHORT Reserved : 5; + } ParameterErrorLocation; + + ULONGLONG Lba; // LBA: This field indicates the first LBA + // that experienced the error condition, if + // applicable. + ULONG NameSpace; // Namespace: This field indicates the nsid + // that the error is associated with, if + // applicable. + UCHAR VendorInfoAvailable; // Vendor Specific Information Available + UCHAR Reserved0[3]; + ULONGLONG CommandSpecificInfo; // This field contains command specific + // information. If used, the command + // definition specifies the information + // returned. + UCHAR Reserved1[24]; +} NVME_ERROR_INFO_LOG, *PNVME_ERROR_INFO_LOG; + +typedef struct { + + ULONG DW0; + ULONG Reserved; + + union { + struct { + USHORT SQHD; // SQ Head Pointer (SQHD) + USHORT SQID; // SQ Identifier (SQID) + } DUMMYSTRUCTNAME; + + ULONG AsUlong; + } DW2; + + union { + struct { + USHORT CID; // Command Identifier (CID) + NVME_COMMAND_STATUS Status; + } DUMMYSTRUCTNAME; + + ULONG AsUlong; + } DW3; + +} NVME_COMPLETION_ENTRY, *PNVME_COMPLETION_ENTRY; + + +// Bit-mask values for STORAGE_PROTOCOL_COMMAND - "Flags" field. +// +// Flag indicates the request targeting to adapter instead of device. +#define STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST 0x80000000 + +// +// Status values for STORAGE_PROTOCOL_COMMAND - "ReturnStatus" field. +// +#define STORAGE_PROTOCOL_STATUS_PENDING 0x0 +#define STORAGE_PROTOCOL_STATUS_SUCCESS 0x1 +#define STORAGE_PROTOCOL_STATUS_ERROR 0x2 +#define STORAGE_PROTOCOL_STATUS_INVALID_REQUEST 0x3 +#define STORAGE_PROTOCOL_STATUS_NO_DEVICE 0x4 +#define STORAGE_PROTOCOL_STATUS_BUSY 0x5 +#define STORAGE_PROTOCOL_STATUS_DATA_OVERRUN 0x6 +#define STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES 0x7 + +#define STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED 0xFF + +// Command Length for Storage Protocols. +// +// NVMe commands are always 64 bytes. +#define STORAGE_PROTOCOL_COMMAND_LENGTH_NVME 0x40 + +// Command Specific Information for Storage Protocols - CommandSpecific field +// +#define STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND 0x01 +#define STORAGE_PROTOCOL_SPECIFIC_NVME_NVM_COMMAND 0x02 + +#endif /* _DEVIOCTL_ */ + + +// NVME_PASS_THROUGH + +#ifndef STB_IO_CONTROL +typedef struct _SRB_IO_CONTROL { + ULONG HeaderLength; + UCHAR Signature[8]; + ULONG Timeout; + ULONG ControlCode; + ULONG ReturnCode; + ULONG Length; +} SRB_IO_CONTROL, *PSRB_IO_CONTROL; +#endif + +#ifndef NVME_PASS_THROUGH_SRB_IO_CODE + +#define NVME_SIG_STR "NvmeMini" +#define NVME_STORPORT_DRIVER 0xe000 + +#define NVME_PASS_THROUGH_SRB_IO_CODE \ + CTL_CODE(NVME_STORPORT_DRIVER, 0x0800, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#pragma pack(1) + +/* Following is pre-Win10; used with DeviceIoControl(IOCTL_SCSI_MINIPORT), + * in Win10 need DeviceIoControl(IOCTL_STORAGE_PROTOCOL_COMMAND) for pure + * pass-through. Win10 also has "Protocol specific queries" for things like + * Identify and Get feature. */ +typedef struct _NVME_PASS_THROUGH_IOCTL +{ + SRB_IO_CONTROL SrbIoCtrl; + ULONG VendorSpecific[6]; + ULONG NVMeCmd[16]; /* Command DW[0...15] */ + ULONG CplEntry[4]; /* Completion DW[0...3] */ + ULONG Direction; /* 0=None, 1=Out, 2=In, 3=I/O */ + ULONG QueueId; /* 0=AdminQ */ + ULONG DataBufferLen; /* sizeof(DataBuffer) if Data In */ + ULONG MetaDataLen; + ULONG ReturnBufferLen; /* offsetof(DataBuffer), plus + * sizeof(DataBuffer) if Data Out */ + UCHAR DataBuffer[1]; +} NVME_PASS_THROUGH_IOCTL; +#pragma pack() + +#endif // NVME_PASS_THROUGH_SRB_IO_CODE + + +/* + * method codes + */ +#define METHOD_BUFFERED 0 +#define METHOD_IN_DIRECT 1 +#define METHOD_OUT_DIRECT 2 +#define METHOD_NEITHER 3 + + +#define IOCTL_SCSI_BASE 0x00000004 + +/* + * constants for DataIn member of SCSI_PASS_THROUGH* structures + */ +#define SCSI_IOCTL_DATA_OUT 0 +#define SCSI_IOCTL_DATA_IN 1 +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 + +#define IOCTL_SCSI_PASS_THROUGH CTL_CODE(IOCTL_SCSI_BASE, 0x0401, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_MINIPORT CTL_CODE(IOCTL_SCSI_BASE, 0x0402, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE(IOCTL_SCSI_BASE, 0x0403, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE(IOCTL_SCSI_BASE, 0x0404, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE(IOCTL_SCSI_BASE, 0x0405, \ + METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE(IOCTL_SCSI_BASE, 0x0406, \ + METHOD_BUFFERED, FILE_ANY_ACCESS) + +#ifdef __cplusplus +} +#endif + +#endif /* SG_PT_WIN32_H */ diff --git a/include/sg_unaligned.h b/include/sg_unaligned.h new file mode 100644 index 0000000..ca702e8 --- /dev/null +++ b/include/sg_unaligned.h @@ -0,0 +1,489 @@ +#ifndef SG_UNALIGNED_H +#define SG_UNALIGNED_H + +/* + * Copyright (c) 2014-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include /* for uint8_t and friends */ +#include /* for memcpy */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* These inline functions convert integers (always unsigned) to byte streams + * and vice versa. They have two goals: + * - change the byte ordering of integers between host order and big + * endian ("_be") or little endian ("_le") + * - copy the big or little endian byte stream so it complies with any + * alignment that host integers require + * + * Host integer to given endian byte stream is a "_put_" function taking + * two arguments (integer and pointer to byte stream) returning void. + * Given endian byte stream to host integer is a "_get_" function that takes + * one argument and returns an integer of appropriate size (uint32_t for 24 + * bit operations, uint64_t for 48 bit operations). + * + * Big endian byte format "on the wire" is the default used by SCSI + * standards (www.t10.org). Big endian is also the network byte order. + * Little endian is used by ATA, PCI and NVMe. + */ + +/* The generic form of these routines was borrowed from the Linux kernel, + * via mhvtl. There is a specialised version of the main functions for + * little endian or big endian provided that not-quite-standard defines for + * endianness are available from the compiler and the header + * (a GNU extension) has been detected by ./configure . To force the + * generic version, use './configure --disable-fast-lebe ' . */ + +/* Note: Assumes that the source and destination locations do not overlap. + * An example of overlapping source and destination: + * sg_put_unaligned_le64(j, ((uint8_t *)&j) + 1); + * Best not to do things like that. + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" /* need this to see if HAVE_BYTESWAP_H */ +#endif + +#undef GOT_UNALIGNED_SPECIALS /* just in case */ + +#if defined(__BYTE_ORDER__) && defined(HAVE_BYTESWAP_H) && \ + ! defined(IGNORE_FAST_LEBE) + +#if defined(__LITTLE_ENDIAN__) || (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + +#define GOT_UNALIGNED_SPECIALS 1 + +#include /* for bswap_16(), bswap_32() and bswap_64() */ + +// #warning ">>>>>> Doing Little endian special unaligneds" + +static inline uint16_t sg_get_unaligned_be16(const void *p) +{ + uint16_t u; + + memcpy(&u, p, 2); + return bswap_16(u); +} + +static inline uint32_t sg_get_unaligned_be32(const void *p) +{ + uint32_t u; + + memcpy(&u, p, 4); + return bswap_32(u); +} + +static inline uint64_t sg_get_unaligned_be64(const void *p) +{ + uint64_t u; + + memcpy(&u, p, 8); + return bswap_64(u); +} + +static inline void sg_put_unaligned_be16(uint16_t val, void *p) +{ + uint16_t u = bswap_16(val); + + memcpy(p, &u, 2); +} + +static inline void sg_put_unaligned_be32(uint32_t val, void *p) +{ + uint32_t u = bswap_32(val); + + memcpy(p, &u, 4); +} + +static inline void sg_put_unaligned_be64(uint64_t val, void *p) +{ + uint64_t u = bswap_64(val); + + memcpy(p, &u, 8); +} + +static inline uint16_t sg_get_unaligned_le16(const void *p) +{ + uint16_t u; + + memcpy(&u, p, 2); + return u; +} + +static inline uint32_t sg_get_unaligned_le32(const void *p) +{ + uint32_t u; + + memcpy(&u, p, 4); + return u; +} + +static inline uint64_t sg_get_unaligned_le64(const void *p) +{ + uint64_t u; + + memcpy(&u, p, 8); + return u; +} + +static inline void sg_put_unaligned_le16(uint16_t val, void *p) +{ + memcpy(p, &val, 2); +} + +static inline void sg_put_unaligned_le32(uint32_t val, void *p) +{ + memcpy(p, &val, 4); +} + +static inline void sg_put_unaligned_le64(uint64_t val, void *p) +{ + memcpy(p, &val, 8); +} + +#elif defined(__BIG_ENDIAN__) || (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + +#define GOT_UNALIGNED_SPECIALS 1 + +#include + +// #warning ">>>>>> Doing BIG endian special unaligneds" + +static inline uint16_t sg_get_unaligned_le16(const void *p) +{ + uint16_t u; + + memcpy(&u, p, 2); + return bswap_16(u); +} + +static inline uint32_t sg_get_unaligned_le32(const void *p) +{ + uint32_t u; + + memcpy(&u, p, 4); + return bswap_32(u); +} + +static inline uint64_t sg_get_unaligned_le64(const void *p) +{ + uint64_t u; + + memcpy(&u, p, 8); + return bswap_64(u); +} + +static inline void sg_put_unaligned_le16(uint16_t val, void *p) +{ + uint16_t u = bswap_16(val); + + memcpy(p, &u, 2); +} + +static inline void sg_put_unaligned_le32(uint32_t val, void *p) +{ + uint32_t u = bswap_32(val); + + memcpy(p, &u, 4); +} + +static inline void sg_put_unaligned_le64(uint64_t val, void *p) +{ + uint64_t u = bswap_64(val); + + memcpy(p, &u, 8); +} + +static inline uint16_t sg_get_unaligned_be16(const void *p) +{ + uint16_t u; + + memcpy(&u, p, 2); + return u; +} + +static inline uint32_t sg_get_unaligned_be32(const void *p) +{ + uint32_t u; + + memcpy(&u, p, 4); + return u; +} + +static inline uint64_t sg_get_unaligned_be64(const void *p) +{ + uint64_t u; + + memcpy(&u, p, 8); + return u; +} + +static inline void sg_put_unaligned_be16(uint16_t val, void *p) +{ + memcpy(p, &val, 2); +} + +static inline void sg_put_unaligned_be32(uint32_t val, void *p) +{ + memcpy(p, &val, 4); +} + +static inline void sg_put_unaligned_be64(uint64_t val, void *p) +{ + memcpy(p, &val, 8); +} + +#endif /* __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ */ +#endif /* #if defined __BYTE_ORDER__ && defined && + * ! defined IGNORE_FAST_LEBE */ + + +#ifndef GOT_UNALIGNED_SPECIALS + +/* Now we have no tricks left, so use the only way this can be done + * correctly in C safely: lots of shifts. */ + +// #warning ">>>>>> Doing GENERIC unaligneds" + +static inline uint16_t sg_get_unaligned_be16(const void *p) +{ + return ((const uint8_t *)p)[0] << 8 | ((const uint8_t *)p)[1]; +} + +static inline uint32_t sg_get_unaligned_be32(const void *p) +{ + return ((const uint8_t *)p)[0] << 24 | ((const uint8_t *)p)[1] << 16 | + ((const uint8_t *)p)[2] << 8 | ((const uint8_t *)p)[3]; +} + +static inline uint64_t sg_get_unaligned_be64(const void *p) +{ + return (uint64_t)sg_get_unaligned_be32(p) << 32 | + sg_get_unaligned_be32((const uint8_t *)p + 4); +} + +static inline void sg_put_unaligned_be16(uint16_t val, void *p) +{ + ((uint8_t *)p)[0] = (uint8_t)(val >> 8); + ((uint8_t *)p)[1] = (uint8_t)val; +} + +static inline void sg_put_unaligned_be32(uint32_t val, void *p) +{ + sg_put_unaligned_be16(val >> 16, p); + sg_put_unaligned_be16(val, (uint8_t *)p + 2); +} + +static inline void sg_put_unaligned_be64(uint64_t val, void *p) +{ + sg_put_unaligned_be32(val >> 32, p); + sg_put_unaligned_be32(val, (uint8_t *)p + 4); +} + + +static inline uint16_t sg_get_unaligned_le16(const void *p) +{ + return ((const uint8_t *)p)[1] << 8 | ((const uint8_t *)p)[0]; +} + +static inline uint32_t sg_get_unaligned_le32(const void *p) +{ + return ((const uint8_t *)p)[3] << 24 | ((const uint8_t *)p)[2] << 16 | + ((const uint8_t *)p)[1] << 8 | ((const uint8_t *)p)[0]; +} + +static inline uint64_t sg_get_unaligned_le64(const void *p) +{ + return (uint64_t)sg_get_unaligned_le32((const uint8_t *)p + 4) << 32 | + sg_get_unaligned_le32(p); +} + +static inline void sg_put_unaligned_le16(uint16_t val, void *p) +{ + ((uint8_t *)p)[0] = val & 0xff; + ((uint8_t *)p)[1] = val >> 8; +} + +static inline void sg_put_unaligned_le32(uint32_t val, void *p) +{ + sg_put_unaligned_le16(val >> 16, (uint8_t *)p + 2); + sg_put_unaligned_le16(val, p); +} + +static inline void sg_put_unaligned_le64(uint64_t val, void *p) +{ + sg_put_unaligned_le32(val >> 32, (uint8_t *)p + 4); + sg_put_unaligned_le32(val, p); +} + +#endif /* #ifndef GOT_UNALIGNED_SPECIALS */ + +/* Following are lesser used conversions that don't have specializations + * for endianness; big endian first. In summary these are the 24, 48 bit and + * given-length conversions plus the "nz" conditional put conversions. */ + +/* Now big endian, get 24+48 then put 24+48 */ +static inline uint32_t sg_get_unaligned_be24(const void *p) +{ + return ((const uint8_t *)p)[0] << 16 | ((const uint8_t *)p)[1] << 8 | + ((const uint8_t *)p)[2]; +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t sg_get_unaligned_be48(const void *p) +{ + return (uint64_t)sg_get_unaligned_be16(p) << 32 | + sg_get_unaligned_be32((const uint8_t *)p + 2); +} + +/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than + * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is + * an 8 byte unsigned integer. */ +static inline uint64_t sg_get_unaligned_be(int num_bytes, const void *p) +{ + if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t))) + return 0; + else { + const uint8_t * xp = (const uint8_t *)p; + uint64_t res = *xp; + + for (++xp; num_bytes > 1; ++xp, --num_bytes) + res = (res << 8) | *xp; + return res; + } +} + +static inline void sg_put_unaligned_be24(uint32_t val, void *p) +{ + ((uint8_t *)p)[0] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[2] = val & 0xff; +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void sg_put_unaligned_be48(uint64_t val, void *p) +{ + sg_put_unaligned_be16(val >> 32, p); + sg_put_unaligned_be32(val, (uint8_t *)p + 2); +} + +/* Now little endian, get 24+48 then put 24+48 */ +static inline uint32_t sg_get_unaligned_le24(const void *p) +{ + return (uint32_t)sg_get_unaligned_le16(p) | + ((const uint8_t *)p)[2] << 16; +} + +/* Assume 48 bit value placed in uint64_t */ +static inline uint64_t sg_get_unaligned_le48(const void *p) +{ + return (uint64_t)sg_get_unaligned_le16((const uint8_t *)p + 4) << 32 | + sg_get_unaligned_le32(p); +} + +static inline void sg_put_unaligned_le24(uint32_t val, void *p) +{ + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; +} + +/* Assume 48 bit value placed in uint64_t */ +static inline void sg_put_unaligned_le48(uint64_t val, void *p) +{ + ((uint8_t *)p)[5] = (val >> 40) & 0xff; + ((uint8_t *)p)[4] = (val >> 32) & 0xff; + ((uint8_t *)p)[3] = (val >> 24) & 0xff; + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; +} + +/* Returns 0 if 'num_bytes' is less than or equal to 0 or greater than + * 8 (i.e. sizeof(uint64_t)). Else returns result in uint64_t which is + * an 8 byte unsigned integer. */ +static inline uint64_t sg_get_unaligned_le(int num_bytes, const void *p) +{ + if ((num_bytes <= 0) || (num_bytes > (int)sizeof(uint64_t))) + return 0; + else { + const uint8_t * xp = (const uint8_t *)p + (num_bytes - 1); + uint64_t res = *xp; + + for (--xp; num_bytes > 1; --xp, --num_bytes) + res = (res << 8) | *xp; + return res; + } +} + +/* Since cdb and parameter blocks are often memset to zero before these + * unaligned function partially fill them, then check for a val of zero + * and ignore if it is with these variants. First big endian, then little */ +static inline void sg_nz_put_unaligned_be16(uint16_t val, void *p) +{ + if (val) + sg_put_unaligned_be16(val, p); +} + +static inline void sg_nz_put_unaligned_be24(uint32_t val, void *p) +{ + if (val) { + ((uint8_t *)p)[0] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[2] = val & 0xff; + } +} + +static inline void sg_nz_put_unaligned_be32(uint32_t val, void *p) +{ + if (val) + sg_put_unaligned_be32(val, p); +} + +static inline void sg_nz_put_unaligned_be64(uint64_t val, void *p) +{ + if (val) + sg_put_unaligned_be64(val, p); +} + +static inline void sg_nz_put_unaligned_le16(uint16_t val, void *p) +{ + if (val) + sg_put_unaligned_le16(val, p); +} + +static inline void sg_nz_put_unaligned_le24(uint32_t val, void *p) +{ + if (val) { + ((uint8_t *)p)[2] = (val >> 16) & 0xff; + ((uint8_t *)p)[1] = (val >> 8) & 0xff; + ((uint8_t *)p)[0] = val & 0xff; + } +} + +static inline void sg_nz_put_unaligned_le32(uint32_t val, void *p) +{ + if (val) + sg_put_unaligned_le32(val, p); +} + +static inline void sg_nz_put_unaligned_le64(uint64_t val, void *p) +{ + if (val) + sg_put_unaligned_le64(val, p); +} + + +#ifdef __cplusplus +} +#endif + +#endif /* SG_UNALIGNED_H */ diff --git a/lib/BSD_LICENSE b/lib/BSD_LICENSE new file mode 100644 index 0000000..7f1906b --- /dev/null +++ b/lib/BSD_LICENSE @@ -0,0 +1,24 @@ + +Copyright (c) 1999-2018, Douglas Gilbert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..f90fed9 --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,71 @@ +libsgutils2_la_SOURCES = \ + sg_lib.c \ + sg_lib_data.c \ + sg_cmds_basic.c \ + sg_cmds_basic2.c \ + sg_cmds_extra.c \ + sg_cmds_mmc.c \ + sg_pt_common.c + +if OS_LINUX +libsgutils2_la_SOURCES += \ + sg_pt_linux.c \ + sg_io_linux.c \ + sg_pt_linux_nvme.c +endif + +if OS_WIN32_MINGW +libsgutils2_la_SOURCES += sg_pt_win32.c +endif + +if OS_WIN32_CYGWIN +libsgutils2_la_SOURCES += sg_pt_win32.c +endif + +if OS_FREEBSD +libsgutils2_la_SOURCES += sg_pt_freebsd.c +endif + +if OS_SOLARIS +libsgutils2_la_SOURCES += sg_pt_solaris.c +endif + +if OS_OSF +libsgutils2_la_SOURCES += sg_pt_osf1.c +endif + +if DEBUG +# This is active if --enable-debug given to ./configure +# removed -Wduplicated-branches because needs gcc-8 +DBG_CFLAGS = -Wextra -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference -Wshadow -Wjump-misses-init +DBG_CPPFLAGS = -DDEBUG +else +DBG_CFLAGS = +DBG_CPPFLAGS = +endif + +# For C++/clang testing +## CC = gcc-8 +## CC = g++ +## CC = clang +## CC = clang++ +## CC = powerpc64-linux-gnu-gcc + +# -std= can be c99, c11, gnu11, etc. Default is gnu11 for C code +# -Wall is no longer all warnings. Add -W (since renamed to -Wextra) for more +AM_CPPFLAGS = -iquote ${top_srcdir}/include -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $(DBG_CPPFLAGS) +AM_CFLAGS = -Wall -W $(DBG_CFLAGS) +# AM_CFLAGS = -Wall -W -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++14 +# AM_CFLAGS = -Wall -W -pedantic -std=c++1z + +lib_LTLIBRARIES = libsgutils2.la + +libsgutils2_la_LDFLAGS = -version-info 2:0:0 -no-undefined + +libsgutils2_la_LIBADD = @GETOPT_O_FILES@ +libsgutils2_la_DEPENDENCIES = @GETOPT_O_FILES@ + + diff --git a/lib/sg_cmds_basic.c b/lib/sg_cmds_basic.c new file mode 100644 index 0000000..e625465 --- /dev/null +++ b/lib/sg_cmds_basic.c @@ -0,0 +1,784 @@ +/* + * Copyright (c) 1999-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. + */ + +/* + * CONTENTS + * Some SCSI commands are executed in many contexts and hence began + * to appear in several sg3_utils utilities. This files centralizes + * some of the low level command execution code. In most cases the + * interpretation of the command response is left to the each + * utility. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +/* Needs to be after config.h */ +#ifdef SG_LIB_LINUX +#include +#endif + + +static const char * const version_str = "1.90 20180712"; + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define EBUFF_SZ 256 + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define INQUIRY_CMD 0x12 +#define INQUIRY_CMDLEN 6 +#define REQUEST_SENSE_CMD 0x3 +#define REQUEST_SENSE_CMDLEN 6 +#define REPORT_LUNS_CMD 0xa0 +#define REPORT_LUNS_CMDLEN 12 +#define TUR_CMD 0x0 +#define TUR_CMDLEN 6 + +#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */ + + +const char * +sg_cmds_version() +{ + return version_str; +} + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. */ +int +sg_cmds_open_device(const char * device_name, bool read_only, int verbose) +{ + return scsi_pt_open_device(device_name, read_only, verbose); +} + +/* Returns file descriptor >= 0 if successful. If error in Unix returns + negated errno. */ +int +sg_cmds_open_flags(const char * device_name, int flags, int verbose) +{ + return scsi_pt_open_flags(device_name, flags, verbose); +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +sg_cmds_close_device(int device_fd) +{ + return scsi_pt_close_device(device_fd); +} + +static const char * const pass_through_s = "pass-through"; + +static int +sg_cmds_process_helper(const char * leadin, int mx_di_len, int resid, + const uint8_t * sbp, int slen, bool noisy, + int verbose, int * o_sense_cat) +{ + int scat, got; + bool n = false; + bool check_data_in = false; + char b[512]; + + scat = sg_err_category_sense(sbp, slen); + switch (scat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_LBA_OUT_OF_RANGE: + case SG_LIB_CAT_ABORTED_COMMAND: + case SG_LIB_CAT_COPY_ABORTED: + case SG_LIB_CAT_DATA_PROTECT: + case SG_LIB_CAT_PROTECTION: + case SG_LIB_CAT_NO_SENSE: + case SG_LIB_CAT_MISCOMPARE: + n = false; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_MEDIUM_HARD: + check_data_in = true; +#if defined(__GNUC__) +#if (__GNUC__ >= 7) + __attribute__((fallthrough)); + /* FALL THROUGH */ +#endif +#endif + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_SENSE: + default: + n = noisy; + break; + } + if (verbose || n) { + if (leadin && (strlen(leadin) > 0)) + pr2ws("%s:\n", leadin); + sg_get_sense_str(NULL, sbp, slen, (verbose > 1), + sizeof(b), b); + pr2ws("%s", b); + if ((mx_di_len > 0) && (resid > 0)) { + got = mx_di_len - resid; + if ((verbose > 2) || check_data_in || (got > 0)) + pr2ws(" %s requested %d bytes (data-in) but got %d " + "bytes\n", pass_through_s, mx_di_len, got); + } + } + if (o_sense_cat) + *o_sense_cat = scat; + return -2; +} + +/* This is a helper function used by sg_cmds_* implementations after the + * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid + * sense data is found it is decoded and output to sg_warnings_strm (def: + * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for + * "sense" category (may not be fatal), -1 for failed, 0, or a positive + * number. If 'mx_di_len > 0' then asks pass-through for resid and returns + * (mx_di_len - resid); otherwise returns 0. So for data-in it should return + * the actual number of bytes received. For data-out (to device) or no data + * call with 'mx_di_len' set to 0 or less. If -2 returned then sense category + * output via 'o_sense_cat' pointer (if not NULL). Note that several sense + * categories also have data in bytes received; -2 is still returned. */ +int +sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin, + int pt_res, int mx_di_len, const uint8_t * sbp, + bool noisy, int verbose, int * o_sense_cat) +{ + int got, cat, duration, slen, resid, resp_code, sstat; + bool transport_sense; + char b[1024]; + + if (NULL == leadin) + leadin = ""; + if (pt_res < 0) { +#ifdef SG_LIB_LINUX + if (verbose) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); + if ((-ENXIO == pt_res) && o_sense_cat) { + if (verbose > 2) + pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n"); + *o_sense_cat = SG_LIB_CAT_NOT_READY; + return -2; + } else if (noisy && (0 == verbose)) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); +#else + if (noisy || verbose) + pr2ws("%s: %s os error: %s\n", leadin, pass_through_s, + safe_strerror(-pt_res)); +#endif + return -1; + } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) { + pr2ws("%s: bad %s setup\n", leadin, pass_through_s); + return -1; + } else if (SCSI_PT_DO_TIMEOUT == pt_res) { + pr2ws("%s: %s timeout\n", leadin, pass_through_s); + return -1; + } + if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + pr2ws(" duration=%d ms\n", duration); + resid = (mx_di_len > 0) ? get_scsi_pt_resid(ptvp) : 0; + slen = get_scsi_pt_sense_len(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + if (sbp && (slen > 7)) { + resp_code = sbp[0] & 0x7f; + /* SBC referrals can have status=GOOD and sense_key=COMPLETED */ + if (resp_code >= 0x70) { + if (resp_code < 0x72) { + if (SPC_SK_NO_SENSE != (0xf & sbp[2])) + sg_err_category_sense(sbp, slen); + } else if (resp_code < 0x74) { + if (SPC_SK_NO_SENSE != (0xf & sbp[1])) + sg_err_category_sense(sbp, slen); + } + } + } + if (mx_di_len > 0) { + got = mx_di_len - resid; + if ((verbose > 1) && (resid != 0)) + pr2ws(" %s: %s requested %d bytes (data-in) but got %d " + "bytes\n", leadin, pass_through_s, mx_di_len, got); + if (got >= 0) + return got; + else { + if (verbose) + pr2ws(" %s: %s can't get negative bytes, say it got " + "none\n", leadin, pass_through_s); + return 0; + } + } else + return 0; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + sstat = get_scsi_pt_status_response(ptvp); + if (o_sense_cat) { + switch (sstat) { + case SAM_STAT_RESERVATION_CONFLICT: + *o_sense_cat = SG_LIB_CAT_RES_CONFLICT; + return -2; + case SAM_STAT_CONDITION_MET: + *o_sense_cat = SG_LIB_CAT_CONDITION_MET; + return -2; + case SAM_STAT_BUSY: + *o_sense_cat = SG_LIB_CAT_BUSY; + return -2; + case SAM_STAT_TASK_SET_FULL: + *o_sense_cat = SG_LIB_CAT_TS_FULL; + return -2; + case SAM_STAT_ACA_ACTIVE: + *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE; + return -2; + case SAM_STAT_TASK_ABORTED: + *o_sense_cat = SG_LIB_CAT_TASK_ABORTED; + return -2; + default: + break; + } + } + if (verbose || noisy) { + sg_get_scsi_status_str(sstat, sizeof(b), b); + pr2ws("%s: scsi status: %s\n", leadin, b); + } + return -1; + case SCSI_PT_RESULT_SENSE: + return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, slen, + noisy, verbose, o_sense_cat); + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose || noisy) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + pr2ws("%s: transport: %s\n", leadin, b); + } +#ifdef SG_LIB_LINUX + transport_sense = (slen > 0); +#else + transport_sense = ((SAM_STAT_CHECK_CONDITION == + get_scsi_pt_status_response(ptvp)) && (slen > 0)); +#endif + if (transport_sense) + return sg_cmds_process_helper(leadin, mx_di_len, resid, sbp, + slen, noisy, verbose, o_sense_cat); + else + return -1; + case SCSI_PT_RESULT_OS_ERR: + if (verbose || noisy) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + pr2ws("%s: os: %s\n", leadin, b); + } + return -1; + default: + pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s, + cat); + return -1; + } +} + +bool +sg_cmds_is_nvme(const struct sg_pt_base * ptvp) +{ + return pt_device_is_nvme(ptvp); +} + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +static const char * const inquiry_s = "inquiry"; + + + +/* Returns 0 on success, while positive values are SG_LIB_CAT_* errors + * (e.g. SG_LIB_CAT_MALFORMED). If OS error, returns negated errno or -1. */ +static int +sg_ll_inquiry_com(struct sg_pt_base * ptvp, bool cmddt, bool evpd, int pg_op, + void * resp, int mx_resp_len, int timeout_secs, + int * residp, bool noisy, int verbose) +{ + int res, ret, k, sense_cat, resid; + uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + uint8_t * up; + + if (cmddt) + inq_cdb[1] |= 0x2; + if (evpd) + inq_cdb[1] |= 0x1; + inq_cdb[2] = (uint8_t)pg_op; + /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */ + sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3); + if (verbose) { + pr2ws(" %s cdb: ", inquiry_s); + for (k = 0; k < INQUIRY_CMDLEN; ++k) + pr2ws("%02x ", inq_cdb[k]); + pr2ws("\n"); + } + if (resp && (mx_resp_len > 0)) { + up = (uint8_t *)resp; + up[0] = 0x7f; /* defensive prefill */ + if (mx_resp_len > 4) + up[4] = 0; + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, -1, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, inquiry_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else if (ret < 4) { + if (verbose) + pr2ws("%s: got too few bytes (%d)\n", __func__, ret); + ret = SG_LIB_CAT_MALFORMED; + } else + ret = 0; + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s resid (%d) should never exceed requested " + "len=%d\n", inquiry_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer, based on resid */ + memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values, negated errno or + * -1 -> other errors. The CMDDT field is obsolete in the INQUIRY cdb. */ +int +sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_inquiry_com(ptvp, cmddt, evpd, pg_op, resp, mx_resp_len, + 0 /* timeout_sec */, NULL, noisy, verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so + * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION + * CODES command instead). Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_inquiry_com(ptvp, false, evpd, pg_op, resp, mx_resp_len, + timeout_secs, residp, noisy, verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Similar to _v2 but takes a pointer to an object (derived from) sg_pt_base. + * That object is assumed to be constructed and have a device file descriptor + * associated with it. Caller is responsible for lifetime of ptp. */ +int +sg_ll_inquiry_pt(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, bool noisy, + int verbose) +{ + clear_scsi_pt_obj(ptvp); + return sg_ll_inquiry_com(ptvp, false, evpd, pg_op, resp, mx_resp_len, + timeout_secs, residp, noisy, verbose); + +} + +/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response. + * Returns 0 when successful, various SG_LIB_CAT_* positive values, negated + * errno or -1 -> other errors */ +int +sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose) +{ + int ret; + uint8_t * inq_resp = NULL; + uint8_t * free_irp = NULL; + + if (inq_data) { + memset(inq_data, 0, sizeof(* inq_data)); + inq_data->peripheral_qualifier = 0x3; + inq_data->peripheral_type = 0x1f; + } + inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, verbose > 4); + if (NULL == inq_resp) { + pr2ws("%s: out of memory\n", __func__); + return sg_convert_errno(ENOMEM); + } + ret = sg_ll_inquiry_v2(sg_fd, false, 0, inq_resp, SAFE_STD_INQ_RESP_LEN, + 0, NULL, noisy, verbose); + + if (inq_data && (0 == ret)) { + inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; + inq_data->peripheral_type = inq_resp[0] & 0x1f; + inq_data->byte_1 = inq_resp[1]; + inq_data->version = inq_resp[2]; + inq_data->byte_3 = inq_resp[3]; + inq_data->byte_5 = inq_resp[5]; + inq_data->byte_6 = inq_resp[6]; + inq_data->byte_7 = inq_resp[7]; + memcpy(inq_data->vendor, inq_resp + 8, 8); + memcpy(inq_data->product, inq_resp + 16, 16); + memcpy(inq_data->revision, inq_resp + 32, 4); + } + if (free_irp) + free(free_irp); + return ret; +} + +/* Similar to sg_simple_inquiry() but takes pointer to pt object rather + * than device file descriptor. */ +int +sg_simple_inquiry_pt(struct sg_pt_base * ptvp, + struct sg_simple_inquiry_resp * inq_data, + bool noisy, int verbose) +{ + int ret; + uint8_t * inq_resp = NULL; + uint8_t * free_irp = NULL; + + if (inq_data) { + memset(inq_data, 0, sizeof(* inq_data)); + inq_data->peripheral_qualifier = 0x3; + inq_data->peripheral_type = 0x1f; + } + inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, verbose > 4); + if (NULL == inq_resp) { + pr2ws("%s: out of memory\n", __func__); + return sg_convert_errno(ENOMEM); + } + ret = sg_ll_inquiry_pt(ptvp, false, 0, inq_resp, SAFE_STD_INQ_RESP_LEN, + 0, NULL, noisy, verbose); + + if (inq_data && (0 == ret)) { + inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7; + inq_data->peripheral_type = inq_resp[0] & 0x1f; + inq_data->byte_1 = inq_resp[1]; + inq_data->version = inq_resp[2]; + inq_data->byte_3 = inq_resp[3]; + inq_data->byte_5 = inq_resp[5]; + inq_data->byte_6 = inq_resp[6]; + inq_data->byte_7 = inq_resp[7]; + memcpy(inq_data->vendor, inq_resp + 8, 8); + memcpy(inq_data->product, inq_resp + 16, 16); + memcpy(inq_data->revision, inq_resp + 32, 4); + } + if (free_irp) + free(free_irp); + return ret; +} + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Looks for progress indicator if 'progress' non-NULL; + * if found writes value [0..65535] else write -1. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id, + int * progress, bool noisy, int verbose) +{ + static const char * const tur_s = "test unit ready"; + int res, ret, k, sense_cat; + uint8_t tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + if (verbose) { + pr2ws(" %s cdb: ", tur_s); + for (k = 0; k < TUR_CMDLEN; ++k) + pr2ws("%02x ", tur_cdb[k]); + pr2ws("\n"); + } + + clear_scsi_pt_obj(ptvp); + set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_packet_id(ptvp, pack_id); + res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, tur_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + if (progress) { + int slen = get_scsi_pt_sense_len(ptvp); + + if (! sg_get_sense_progress_fld(sense_b, slen, progress)) + *progress = -1; + } + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + return ret; +} + +int +sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress, + bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, progress, noisy, + verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI TEST UNIT READY command. + * 'pack_id' is just for diagnostics, safe to set to 0. + * Returns 0 when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, NULL, noisy, + verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_test_unit_ready_pt(struct sg_pt_base * ptvp, int pack_id, bool noisy, + int verbose) +{ + return sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, NULL, noisy, + verbose); +} + +/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various + * SG_LIB_CAT_* positive values or -1 -> other errors */ +static int +sg_ll_request_sense_com(struct sg_pt_base * ptvp, int sg_fd, bool desc, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + bool ptvp_given = false; + int k, ret, res, sense_cat; + static const char * const rq_s = "request sense"; + uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] = + {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + if (desc) + rs_cdb[1] |= 0x1; + if (mx_resp_len > 0xff) { + pr2ws("mx_resp_len cannot exceed 255\n"); + return -1; + } + rs_cdb[4] = mx_resp_len & 0xff; + if (verbose) { + pr2ws(" %s cmd: ", rq_s); + for (k = 0; k < REQUEST_SENSE_CMDLEN; ++k) + pr2ws("%02x ", rs_cdb[k]); + pr2ws("\n"); + } + + if (ptvp) + ptvp_given = true; + else { + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + } + set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, rq_s, res, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((mx_resp_len >= 8) && (ret < 8)) { + if (verbose) + pr2ws(" %s: got %d bytes in response, too short\n", rq_s, + ret); + ret = -1; + } else + ret = 0; + } + if ((! ptvp_given) && ptvp) + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_request_sense_com(NULL, sg_fd, desc, resp, mx_resp_len, + noisy, verbose); +} + +int +sg_ll_request_sense_pt(struct sg_pt_base * ptvp, bool desc, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + clear_scsi_pt_obj(ptvp); + return sg_ll_request_sense_com(ptvp, -1, desc, resp, mx_resp_len, + noisy, verbose); +} + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +static int +sg_ll_report_luns_com(struct sg_pt_base * ptvp, int sg_fd, int select_report, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const report_luns_s = "report luns"; + bool ptvp_given = false; + int k, ret, res, sense_cat; + uint8_t rl_cdb[REPORT_LUNS_CMDLEN] = + {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + rl_cdb[2] = select_report & 0xff; + sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", report_luns_s); + for (k = 0; k < REPORT_LUNS_CMDLEN; ++k) + pr2ws("%02x ", rl_cdb[k]); + pr2ws("\n"); + } + + if (ptvp) + ptvp_given = true; + else if (NULL == ((ptvp = create_pt_obj(report_luns_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, report_luns_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + if ((! ptvp_given) && ptvp) + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors, + * Expects sg_fd to be >= 0 representing an open device fd. */ +int +sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_report_luns_com(NULL, sg_fd, select_report, resp, + mx_resp_len, noisy, verbose); +} + + +/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Expects a non-NULL ptvp containing an open device fd. */ +int +sg_ll_report_luns_pt(struct sg_pt_base * ptvp, int select_report, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + clear_scsi_pt_obj(ptvp); + return sg_ll_report_luns_com(ptvp, -1, select_report, resp, + mx_resp_len, noisy, verbose); +} diff --git a/lib/sg_cmds_basic2.c b/lib/sg_cmds_basic2.c new file mode 100644 index 0000000..e77e290 --- /dev/null +++ b/lib/sg_cmds_basic2.c @@ -0,0 +1,1053 @@ +/* + * Copyright (c) 1999-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. + */ + +/* + * CONTENTS + * Some SCSI commands are executed in many contexts and hence began + * to appear in several sg3_utils utilities. This files centralizes + * some of the low level command execution code. In most cases the + * interpretation of the command response is left to the each + * utility. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define EBUFF_SZ 256 + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define SYNCHRONIZE_CACHE_CMD 0x35 +#define SYNCHRONIZE_CACHE_CMDLEN 10 +#define SERVICE_ACTION_IN_16_CMD 0x9e +#define SERVICE_ACTION_IN_16_CMDLEN 16 +#define READ_CAPACITY_16_SA 0x10 +#define READ_CAPACITY_10_CMD 0x25 +#define READ_CAPACITY_10_CMDLEN 10 +#define MODE_SENSE6_CMD 0x1a +#define MODE_SENSE6_CMDLEN 6 +#define MODE_SENSE10_CMD 0x5a +#define MODE_SENSE10_CMDLEN 10 +#define MODE_SELECT6_CMD 0x15 +#define MODE_SELECT6_CMDLEN 6 +#define MODE_SELECT10_CMD 0x55 +#define MODE_SELECT10_CMDLEN 10 +#define LOG_SENSE_CMD 0x4d +#define LOG_SENSE_CMDLEN 10 +#define LOG_SELECT_CMD 0x4c +#define LOG_SELECT_CMDLEN 10 +#define START_STOP_CMD 0x1b +#define START_STOP_CMDLEN 6 +#define PREVENT_ALLOW_CMD 0x1e +#define PREVENT_ALLOW_CMDLEN 6 + +#define MODE6_RESP_HDR_LEN 4 +#define MODE10_RESP_HDR_LEN 8 +#define MODE_RESP_ARB_LEN 1024 + +#define INQUIRY_RESP_INITIAL_LEN 36 + + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group, + unsigned int lba, unsigned int count, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "synchronize cache(10)"; + int res, ret, k, sense_cat; + uint8_t sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] = + {SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (sync_nv) + sc_cdb[1] |= 4; + if (immed) + sc_cdb[1] |= 2; + sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2); + sc_cdb[6] = group & 0x1f; + if (count > 0xffff) { + pr2ws("count too big\n"); + return -1; + } + sg_put_unaligned_be16((int16_t)count, sc_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SYNCHRONIZE_CACHE_CMDLEN; ++k) + pr2ws("%02x ", sc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read capacity(16)"; + int k, ret, res, sense_cat; + uint8_t rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] = + {SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (pmi) { /* lbs only valid when pmi set */ + rc_cdb[14] |= 1; + sg_put_unaligned_be64(llba, rc_cdb + 2); + } + /* Allocation length, no guidance in SBC-2 rev 15b */ + sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", rc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read capacity(10)"; + int k, ret, res, sense_cat; + uint8_t rc_cdb[READ_CAPACITY_10_CMDLEN] = + {READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (pmi) { /* lbs only valid when pmi set */ + rc_cdb[8] |= 1; + sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2); + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_CAPACITY_10_CMDLEN; ++k) + pr2ws("%02x ", rc_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode sense(6)"; + int res, ret, k, sense_cat, resid; + uint8_t modes_cdb[MODE_SENSE6_CMDLEN] = + {MODE_SENSE6_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (uint8_t)(dbd ? 0x8 : 0); + modes_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + modes_cdb[3] = (uint8_t)(sub_pg_code & 0xff); + modes_cdb[4] = (uint8_t)(mx_resp_len & 0xff); + if (mx_resp_len > 0xff) { + pr2ws("mx_resp_len too big\n"); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SENSE6_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code, + resp, mx_resp_len, 0, NULL, noisy, verbose); +} + +/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code, + int sub_pg_code, void * resp, int mx_resp_len, + int timeout_secs, int * residp, bool noisy, int verbose) +{ + int res, ret, k, sense_cat, resid; + static const char * const cdb_name_s = "mode sense(10)"; + struct sg_pt_base * ptvp; + uint8_t modes_cdb[MODE_SENSE10_CMDLEN] = + {MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + modes_cdb[1] = (uint8_t)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0)); + modes_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + modes_cdb[3] = (uint8_t)(sub_pg_code & 0xff); + sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7); + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + goto gen_err; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SENSE10_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + goto gen_err; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +gen_err: + if (residp) + *residp = 0; + return -1; +} + +/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_mode_select6_v2(int sg_fd, bool pf, bool rtd, bool sp, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode select(6)"; + int res, ret, k, sense_cat; + uint8_t modes_cdb[MODE_SELECT6_CMDLEN] = + {MODE_SELECT6_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (uint8_t)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0)); + if (rtd) + modes_cdb[1] |= 0x2; + modes_cdb[4] = (uint8_t)(param_len & 0xff); + if (param_len > 0xff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SELECT6_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len, + bool noisy, int verbose) +{ + return sg_ll_mode_select6_v2(sg_fd, pf, false, sp, paramp, param_len, + noisy, verbose); +} + +/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors, + * v2 adds rtd (revert to defaults) bit (spc5r11). */ +int +sg_ll_mode_select10_v2(int sg_fd, bool pf, bool rtd, bool sp, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "mode select(10)"; + int res, ret, k, sense_cat; + uint8_t modes_cdb[MODE_SELECT10_CMDLEN] = + {MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + modes_cdb[1] = (uint8_t)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0)); + if (rtd) + modes_cdb[1] |= 0x2; + sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7); + if (param_len > 0xffff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MODE_SELECT10_CMDLEN; ++k) + pr2ws("%02x ", modes_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, + int param_len, bool noisy, int verbose) +{ + return sg_ll_mode_select10_v2(sg_fd, pf, false, sp, paramp, param_len, + noisy, verbose); +} + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. In most cases users are + * interested in the first mode page. This function returns the (byte) + * offset of the start of the first mode page. Set mode_sense_6 to true for + * MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful + * or -1 if failure. If there is a failure a message is written to err_buff + * if it is non-NULL and err_buff_len > 0. */ +int +sg_mode_page_offset(const uint8_t * resp, int resp_len, + bool mode_sense_6, char * err_buff, int err_buff_len) +{ + int bd_len, calc_len, offset; + bool err_buff_ok = ((err_buff_len > 0) && err_buff); + + if ((NULL == resp) || (resp_len < 4)) + goto too_short; + if (mode_sense_6) { + calc_len = resp[0] + 1; + bd_len = resp[3]; + offset = bd_len + MODE6_RESP_HDR_LEN; + } else { /* Mode sense(10) */ + if (resp_len < 8) + goto too_short; + calc_len = sg_get_unaligned_be16(resp) + 2; + bd_len = sg_get_unaligned_be16(resp + 6); + /* LongLBA doesn't change this calculation */ + offset = bd_len + MODE10_RESP_HDR_LEN; + } + if ((offset + 2) > calc_len) { + if (err_buff_ok) + snprintf(err_buff, err_buff_len, "calculated response " + "length too small, offset=%d calc_len=%d bd_len=%d\n", + offset, calc_len, bd_len); + offset = -1; + } + return offset; +too_short: + if (err_buff_ok) + snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) " + "too short\n", (mode_sense_6 ? 6 : 10), resp_len); + return -1; +} + +/* MODE SENSE commands yield a response that has header then zero or more + * block descriptors followed by mode pages. This functions returns the + * length (in bytes) of those three components. Note that the return value + * can exceed resp_len in which case the MODE SENSE command should be + * re-issued with a larger response buffer. If bd_lenp is non-NULL and if + * successful the block descriptor length (in bytes) is written to *bd_lenp. + * Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10) + * responses. Returns -1 if there is an error (e.g. response too short). */ +int +sg_msense_calc_length(const uint8_t * resp, int resp_len, + bool mode_sense_6, int * bd_lenp) +{ + int calc_len; + + if (NULL == resp) + goto an_err; + if (mode_sense_6) { + if (resp_len < 4) + goto an_err; + calc_len = resp[0] + 1; + } else { + if (resp_len < 8) + goto an_err; + calc_len = sg_get_unaligned_be16(resp + 0) + 2; + } + if (bd_lenp) + *bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6); + return calc_len; +an_err: + if (bd_lenp) + *bd_lenp = 0; + return -1; +} + +/* Fetches current, changeable, default and/or saveable modes pages as + * indicated by pcontrol_arr for given pg_code and sub_pg_code. If + * mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If + * flexible set and mode data length seems wrong then try and + * fix (compensating hack for bad device or driver). pcontrol_arr + * should have 4 elements for output of current, changeable, default + * and saved values respectively. Each element should be NULL or + * at least mx_mpage_len bytes long. + * Return of 0 -> overall success, various SG_LIB_CAT_* positive values or + * -1 -> other errors. + * If success_mask pointer is not NULL then first zeros it. Then set bits + * 0, 1, 2 and/or 3 if the current, changeable, default and saved values + * respectively have been fetched. If error on current page + * then stops and returns that error; otherwise continues if an error is + * detected but returns the first error encountered. */ +int +sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code, + bool dbd, bool flexible, int mx_mpage_len, + int * success_mask, void * pcontrol_arr[], + int * reported_lenp, int verbose) +{ + bool resp_mode6; + int k, n, res, offset, calc_len, xfer_len; + int resid = 0; + const int msense10_hlen = MODE10_RESP_HDR_LEN; + uint8_t buff[MODE_RESP_ARB_LEN]; + char ebuff[EBUFF_SZ]; + int first_err = 0; + + if (success_mask) + *success_mask = 0; + if (reported_lenp) + *reported_lenp = 0; + if (mx_mpage_len < 4) + return 0; + memset(ebuff, 0, sizeof(ebuff)); + /* first try to find length of current page response */ + memset(buff, 0, msense10_hlen); + if (mode6) /* want first 8 bytes just in case */ + res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code, + sub_pg_code, buff, msense10_hlen, true, + verbose); + else /* MODE SENSE(10) obviously */ + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd, + 0 /* pc */, pg_code, sub_pg_code, buff, + msense10_hlen, 0, &resid, true, verbose); + if (0 != res) + return res; + n = buff[0]; + if (reported_lenp) { + int m; + + m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid; + if (m < 0) /* Grrr, this should not happen */ + m = 0; + *reported_lenp = m; + } + resp_mode6 = mode6; + if (flexible) { + if (mode6 && (n < 3)) { + resp_mode6 = false; + if (verbose) + pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) " + "response processing\n", n); + } + if ((! mode6) && (n > 5)) { + if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) && + (0 == buff[5]) && (0 == buff[6])) { + buff[1] = n; + buff[0] = 0; + if (verbose) + pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) " + "response so fix length\n", n); + } else + resp_mode6 = true; + } + } + if (verbose && (resp_mode6 != mode6)) + pr2ws(">>> msense(%d) but resp[0]=%d so switch response " + "processing\n", (mode6 ? 6 : 10), buff[0]); + calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL); + if (calc_len > MODE_RESP_ARB_LEN) + calc_len = MODE_RESP_ARB_LEN; + offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ); + if (offset < 0) { + if (('\0' != ebuff[0]) && (verbose > 0)) + pr2ws("%s: %s\n", __func__, ebuff); + return SG_LIB_CAT_MALFORMED; + } + xfer_len = calc_len - offset; + if (xfer_len > mx_mpage_len) + xfer_len = mx_mpage_len; + + for (k = 0; k < 4; ++k) { + if (NULL == pcontrol_arr[k]) + continue; + memset(pcontrol_arr[k], 0, mx_mpage_len); + resid = 0; + if (mode6) + res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */, + pg_code, sub_pg_code, buff, + calc_len, true, verbose); + else + res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd, + k /* pc */, pg_code, sub_pg_code, + buff, calc_len, 0, &resid, true, + verbose); + if (res || resid) { + if (0 == first_err) { + if (res) + first_err = res; + else { + first_err = -49; /* unexpected resid != 0 */ + if (verbose) + pr2ws("%s: unexpected resid=%d, page=0x%x, " + "pcontrol=%d\n", __func__, resid, pg_code, k); + } + } + if (0 == k) + break; /* if problem on current page, it won't improve */ + else + continue; + } + if (xfer_len > 0) + memcpy(pcontrol_arr[k], buff + offset, xfer_len); + if (success_mask) + *success_mask |= (1 << k); + } + return first_err; +} + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. */ +int +sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, uint8_t * resp, + int mx_resp_len, bool noisy, int verbose) +{ + return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code, + paramp, resp, mx_resp_len, 0, NULL, noisy, + verbose); +} + +/* Invokes a SCSI LOG SENSE command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * or less the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +int +sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code, + int subpg_code, int paramp, uint8_t * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "log sense"; + int res, ret, k, sense_cat, resid; + uint8_t logs_cdb[LOG_SENSE_CMDLEN] = + {LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + goto gen_err; + } + logs_cdb[1] = (uint8_t)((ppc ? 2 : 0) | (sp ? 1 : 0)); + logs_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + logs_cdb[3] = (uint8_t)(subpg_code & 0xff); + sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5); + sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < LOG_SENSE_CMDLEN; ++k) + pr2ws("%02x ", logs_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + goto gen_err; + set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((mx_resp_len > 3) && (ret < 4)) { + /* resid indicates LOG SENSE response length bad, so zero it */ + resp[2] = 0; + resp[3] = 0; + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2ws("%s: resid (%d) should never exceed requested len=%d\n", + cdb_name_s, resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +gen_err: + if (residp) + *residp = 0; + return -1; +} + +/* Invokes a SCSI LOG SELECT command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code, + int subpg_code, uint8_t * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "log select"; + int res, ret, k, sense_cat; + uint8_t logs_cdb[LOG_SELECT_CMDLEN] = + {LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (param_len > 0xffff) { + pr2ws("%s: param_len too big\n", cdb_name_s); + return -1; + } + logs_cdb[1] = (uint8_t)((pcr ? 2 : 0) | (sp ? 1 : 0)); + logs_cdb[2] = (uint8_t)(((pc << 6) & 0xc0) | (pg_code & 0x3f)); + logs_cdb[3] = (uint8_t)(subpg_code & 0xff); + sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < LOG_SELECT_CMDLEN; ++k) + pr2ws("%02x ", logs_cdb[k]); + pr2ws("\n"); + } + if ((verbose > 1) && (param_len > 0)) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr(paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num, + int power_cond, bool noflush__fl, bool loej, bool start, + bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_start_stop_unit_pt(ptvp, immed, pc_mod__fl_num, power_cond, + noflush__fl, loej, start, noisy, verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI START STOP UNIT command (SBC + MMC). + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and + * format_layer_number(mmc) fields. They also overlap on the noflush(sbc) + * and fl(mmc) one bit field. This is the cause of the awkardly named + * pc_mod__fl_num and noflush__fl arguments to this function. + * */ +int +sg_ll_start_stop_unit_pt(struct sg_pt_base * ptvp, bool immed, + int pc_mod__fl_num, int power_cond, bool noflush__fl, + bool loej, bool start, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "start stop unit"; + int k, res, ret, sense_cat; + uint8_t ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + if (immed) + ssuBlk[1] = 0x1; + ssuBlk[3] = pc_mod__fl_num & 0xf; /* bits 2 and 3 are reserved in MMC */ + ssuBlk[4] = ((power_cond & 0xf) << 4); + if (noflush__fl) + ssuBlk[4] |= 0x4; + if (loej) + ssuBlk[4] |= 0x2; + if (start) + ssuBlk[4] |= 0x1; + if (verbose) { + pr2ws(" %s command:", cdb_name_s); + for (k = 0; k < (int)sizeof(ssuBlk); ++k) + pr2ws(" %02x", ssuBlk[k]); + pr2ws("\n"); + } + + clear_scsi_pt_obj(ptvp); + set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, -1, START_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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; + return ret; +} + +/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command + * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3] + * prevent==0 allows removal, prevent==1 prevents removal ... + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "prevent allow medium removal"; + int k, res, ret, sense_cat; + uint8_t p_cdb[PREVENT_ALLOW_CMDLEN] = + {PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((prevent < 0) || (prevent > 3)) { + pr2ws("prevent argument should be 0, 1, 2 or 3\n"); + return -1; + } + p_cdb[4] |= (prevent & 0x3); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PREVENT_ALLOW_CMDLEN; ++k) + pr2ws("%02x ", p_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/lib/sg_cmds_extra.c b/lib/sg_cmds_extra.c new file mode 100644 index 0000000..97f80fe --- /dev/null +++ b/lib/sg_cmds_extra.c @@ -0,0 +1,2455 @@ +/* + * Copyright (c) 1999-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_extra.h" +#include "sg_pt.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ +#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */ + +#define SERVICE_ACTION_IN_16_CMD 0x9e +#define SERVICE_ACTION_IN_16_CMDLEN 16 +#define SERVICE_ACTION_OUT_16_CMD 0x9f +#define SERVICE_ACTION_OUT_16_CMDLEN 16 +#define MAINTENANCE_IN_CMD 0xa3 +#define MAINTENANCE_IN_CMDLEN 12 +#define MAINTENANCE_OUT_CMD 0xa4 +#define MAINTENANCE_OUT_CMDLEN 12 + +#define ATA_PT_12_CMD 0xa1 +#define ATA_PT_12_CMDLEN 12 +#define ATA_PT_16_CMD 0x85 +#define ATA_PT_16_CMDLEN 16 +#define ATA_PT_32_SA 0x1ff0 +#define ATA_PT_32_CMDLEN 32 +#define FORMAT_UNIT_CMD 0x4 +#define FORMAT_UNIT_CMDLEN 6 +#define PERSISTENT_RESERVE_IN_CMD 0x5e +#define PERSISTENT_RESERVE_IN_CMDLEN 10 +#define PERSISTENT_RESERVE_OUT_CMD 0x5f +#define PERSISTENT_RESERVE_OUT_CMDLEN 10 +#define READ_BLOCK_LIMITS_CMD 0x5 +#define READ_BLOCK_LIMITS_CMDLEN 6 +#define READ_BUFFER_CMD 0x3c +#define READ_BUFFER_CMDLEN 10 +#define READ_DEFECT10_CMD 0x37 +#define READ_DEFECT10_CMDLEN 10 +#define REASSIGN_BLKS_CMD 0x7 +#define REASSIGN_BLKS_CMDLEN 6 +#define RECEIVE_DIAGNOSTICS_CMD 0x1c +#define RECEIVE_DIAGNOSTICS_CMDLEN 6 +#define THIRD_PARTY_COPY_OUT_CMD 0x83 /* was EXTENDED_COPY_CMD */ +#define THIRD_PARTY_COPY_OUT_CMDLEN 16 +#define THIRD_PARTY_COPY_IN_CMD 0x84 /* was RECEIVE_COPY_RESULTS_CMD */ +#define THIRD_PARTY_COPY_IN_CMDLEN 16 +#define SEND_DIAGNOSTIC_CMD 0x1d +#define SEND_DIAGNOSTIC_CMDLEN 6 +#define SERVICE_ACTION_IN_12_CMD 0xab +#define SERVICE_ACTION_IN_12_CMDLEN 12 +#define READ_LONG10_CMD 0x3e +#define READ_LONG10_CMDLEN 10 +#define UNMAP_CMD 0x42 +#define UNMAP_CMDLEN 10 +#define VERIFY10_CMD 0x2f +#define VERIFY10_CMDLEN 10 +#define VERIFY16_CMD 0x8f +#define VERIFY16_CMDLEN 16 +#define WRITE_LONG10_CMD 0x3f +#define WRITE_LONG10_CMDLEN 10 +#define WRITE_BUFFER_CMD 0x3b +#define WRITE_BUFFER_CMDLEN 10 +#define PRE_FETCH10_CMD 0x34 +#define PRE_FETCH10_CMDLEN 10 +#define PRE_FETCH16_CMD 0x90 +#define PRE_FETCH16_CMDLEN 16 +#define SEEK10_CMD 0x2b +#define SEEK10_CMDLEN 10 + +#define GET_LBA_STATUS16_SA 0x12 +#define GET_LBA_STATUS32_SA 0x12 +#define READ_LONG_16_SA 0x11 +#define READ_MEDIA_SERIAL_NUM_SA 0x1 +#define REPORT_IDENTIFYING_INFORMATION_SA 0x5 +#define REPORT_TGT_PRT_GRP_SA 0xa +#define SET_IDENTIFYING_INFORMATION_SA 0x6 +#define SET_TGT_PRT_GRP_SA 0xa +#define WRITE_LONG_16_SA 0x11 +#define REPORT_REFERRALS_SA 0x13 +#define EXTENDED_COPY_LID1_SA 0x0 + + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + + +/* Invokes a SCSI GET LBA STATUS(16) command (SBC). Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_get_lba_status16(int sg_fd, uint64_t start_llba, uint8_t rt, + void * resp, int alloc_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Get LBA status(16)"; + int k, res, sense_cat, ret; + uint8_t getLbaStatCmd[SERVICE_ACTION_IN_16_CMDLEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(getLbaStatCmd, 0, sizeof(getLbaStatCmd)); + getLbaStatCmd[0] = SERVICE_ACTION_IN_16_CMD; + getLbaStatCmd[1] = GET_LBA_STATUS16_SA; + + sg_put_unaligned_be64(start_llba, getLbaStatCmd + 2); + sg_put_unaligned_be32((uint32_t)alloc_len, getLbaStatCmd + 10); + getLbaStatCmd[14] = rt; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", getLbaStatCmd[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, getLbaStatCmd, sizeof(getLbaStatCmd)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, alloc_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_get_lba_status(int sg_fd, uint64_t start_llba, void * resp, + int alloc_len, bool noisy, int verbose) +{ + return sg_ll_get_lba_status16(sg_fd, start_llba, /* rt = */ 0x0, resp, + alloc_len, noisy, verbose); +} + +#define GLS32_CMD_LEN 32 + +int +sg_ll_get_lba_status32(int sg_fd, uint64_t start_llba, uint32_t scan_len, + uint32_t element_id, uint8_t rt, + void * resp, int alloc_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Get LBA status(32)"; + int k, res, sense_cat, ret; + uint8_t gls32_cmd[GLS32_CMD_LEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(gls32_cmd, 0, sizeof(gls32_cmd)); + gls32_cmd[0] = SG_VARIABLE_LENGTH_CMD; + gls32_cmd[7] = GLS32_CMD_LEN - 8; + sg_put_unaligned_be16((uint16_t)GET_LBA_STATUS32_SA, gls32_cmd + 8); + gls32_cmd[10] = rt; + sg_put_unaligned_be64(start_llba, gls32_cmd + 12); + sg_put_unaligned_be32(scan_len, gls32_cmd + 20); + sg_put_unaligned_be32(element_id, gls32_cmd + 24); + sg_put_unaligned_be32((uint32_t)alloc_len, gls32_cmd + 28); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GLS32_CMD_LEN; ++k) + pr2ws("%02x ", gls32_cmd[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, gls32_cmd, sizeof(gls32_cmd)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, alloc_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, false, noisy, + verbose); +} + +/* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len, + bool extended, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Report target port groups"; + int k, res, ret, sense_cat; + uint8_t rtpg_cdb[MAINTENANCE_IN_CMDLEN] = + {MAINTENANCE_IN_CMD, REPORT_TGT_PRT_GRP_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (extended) + rtpg_cdb[1] |= 0x20; + sg_put_unaligned_be32((uint32_t)mx_resp_len, rtpg_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k) + pr2ws("%02x ", rtpg_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, rtpg_cdb, sizeof(rtpg_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_set_tgt_prt_grp(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Set target port groups"; + int k, res, ret, sense_cat; + uint8_t stpg_cdb[MAINTENANCE_OUT_CMDLEN] = + {MAINTENANCE_OUT_CMD, SET_TGT_PRT_GRP_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)param_len, stpg_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k) + pr2ws("%02x ", stpg_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, stpg_cdb, sizeof(stpg_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT REFERRALS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_referrals(int sg_fd, uint64_t start_llba, bool one_seg, + void * resp, int mx_resp_len, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "Report referrals"; + int k, res, ret, sense_cat; + uint8_t repRef_cdb[SERVICE_ACTION_IN_16_CMDLEN] = + {SERVICE_ACTION_IN_16_CMD, REPORT_REFERRALS_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be64(start_llba, repRef_cdb + 2); + sg_put_unaligned_be32((uint32_t)mx_resp_len, repRef_cdb + 10); + if (one_seg) + repRef_cdb[14] = 0x1; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", repRef_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, repRef_cdb, sizeof(repRef_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can + * take a long time, if so set long_duration flag in which case the timeout + * is set to 7200 seconds; if the value of long_duration is > 7200 then that + * value is taken as the timeout value in seconds. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_send_diag_pt(struct sg_pt_base * ptvp, int st_code, bool pf_bit, + bool st_bit, bool devofl_bit, bool unitofl_bit, + int long_duration, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Send diagnostic"; + int k, res, ret, sense_cat, tmout; + uint8_t senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] = + {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + senddiag_cdb[1] = (uint8_t)(st_code << 5); + if (pf_bit) + senddiag_cdb[1] |= 0x10; + if (st_bit) + senddiag_cdb[1] |= 0x4; + if (devofl_bit) + senddiag_cdb[1] |= 0x2; + if (unitofl_bit) + senddiag_cdb[1] |= 0x1; + sg_put_unaligned_be16((uint16_t)param_len, senddiag_cdb + 3); + if (long_duration > LONG_PT_TIMEOUT) + tmout = long_duration; + else + tmout = long_duration ? LONG_PT_TIMEOUT : DEF_PT_TIMEOUT; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SEND_DIAGNOSTIC_CMDLEN; ++k) + pr2ws("%02x ", senddiag_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + if (paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + pr2ws(" %s timeout: %d seconds\n", cdb_name_s, tmout); + } + } + + set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, -1, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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; + + return ret; +} + +int +sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit, + bool devofl_bit, bool unitofl_bit, int long_duration, + void * paramp, int param_len, bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_send_diag_pt(ptvp, st_code, pf_bit, st_bit, devofl_bit, + unitofl_bit, long_duration, paramp, param_len, + noisy, verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_diag_pt(struct sg_pt_base * ptvp, bool pcv, int pg_code, + void * resp, int mx_resp_len, int timeout_secs, + int * residp, bool noisy, int verbose) +{ + int resid = 0; + int k, res, ret, sense_cat; + static const char * const cdb_name_s = "Receive diagnostic results"; + uint8_t rcvdiag_cdb[RECEIVE_DIAGNOSTICS_CMDLEN] = + {RECEIVE_DIAGNOSTICS_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + + if (pcv) + rcvdiag_cdb[1] = 0x1; + rcvdiag_cdb[2] = (uint8_t)(pg_code); + sg_put_unaligned_be16((uint16_t)mx_resp_len, rcvdiag_cdb + 3); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < RECEIVE_DIAGNOSTICS_CMDLEN; ++k) + pr2ws("%02x ", rcvdiag_cdb[k]); + pr2ws("\n"); + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, -1, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + return ret; +} + + +/* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_receive_diag_pt(ptvp, pcv, pg_code, resp, mx_resp_len, 0, + NULL, noisy, verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +int +sg_ll_receive_diag_v2(int sg_fd, bool pcv, int pg_code, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int verbose) +{ + int ret; + struct sg_pt_base * ptvp; + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); + if (NULL == ptvp) + return sg_convert_errno(ENOMEM); + ret = sg_ll_receive_diag_pt(ptvp, pcv, pg_code, resp, mx_resp_len, + timeout_secs, residp, noisy, verbose); + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_defect10(int sg_fd, bool req_plist, bool req_glist, int dl_format, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Read defect(10)"; + int res, k, ret, sense_cat; + uint8_t rdef_cdb[READ_DEFECT10_CMDLEN] = + {READ_DEFECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rdef_cdb[2] = (dl_format & 0x7); + if (req_plist) + rdef_cdb[2] |= 0x10; + if (req_glist) + rdef_cdb[2] |= 0x8; + sg_put_unaligned_be16((uint16_t)mx_resp_len, rdef_cdb + 7); + if (mx_resp_len > 0xffff) { + pr2ws("mx_resp_len too big\n"); + return -1; + } + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_DEFECT10_CMDLEN; ++k) + pr2ws("%02x ", rdef_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, rdef_cdb, sizeof(rdef_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ MEDIA SERIAL NUMBER command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_media_serial_num(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Read media serial number"; + int k, res, ret, sense_cat; + uint8_t rmsn_cdb[SERVICE_ACTION_IN_12_CMDLEN] = + {SERVICE_ACTION_IN_12_CMD, READ_MEDIA_SERIAL_NUM_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)mx_resp_len, rmsn_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_12_CMDLEN; ++k) + pr2ws("%02x ", rmsn_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, rmsn_cdb, sizeof(rmsn_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REPORT IDENTIFYING INFORMATION command. This command was + * called REPORT DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_report_id_info(int sg_fd, int itype, void * resp, int max_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Report identifying information"; + int k, res, ret, sense_cat; + uint8_t rii_cdb[MAINTENANCE_IN_CMDLEN] = {MAINTENANCE_IN_CMD, + REPORT_IDENTIFYING_INFORMATION_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)max_resp_len, rii_cdb + 6); + rii_cdb[10] |= (itype << 1) & 0xfe; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_IN_CMDLEN; ++k) + pr2ws("%02x ", rii_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, rii_cdb, sizeof(rii_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, max_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, max_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET IDENTIFYING INFORMATION command. This command was + * called SET DEVICE IDENTIFIER prior to spc4r07. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_set_id_info(int sg_fd, int itype, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Set identifying information"; + int k, res, ret, sense_cat; + uint8_t sii_cdb[MAINTENANCE_OUT_CMDLEN] = {MAINTENANCE_OUT_CMD, + SET_IDENTIFYING_INFORMATION_SA, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)param_len, sii_cdb + 6); + sii_cdb[10] |= (itype << 1) & 0xfe; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < MAINTENANCE_OUT_CMDLEN; ++k) + pr2ws("%02x ", sii_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, sii_cdb, sizeof(sii_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_format_unit(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst, + dlist_format, 0, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a FORMAT UNIT (SBC-3) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_format_unit2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int ffmt, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + return sg_ll_format_unit_v2(sg_fd, fmtpinfo, longlist, fmtdata, cmplst, + dlist_format, ffmt, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a FORMAT UNIT (SBC-4) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors. + * FFMT field added in sbc4r10 [20160121] */ +int +sg_ll_format_unit_v2(int sg_fd, int fmtpinfo, bool longlist, bool fmtdata, + bool cmplst, int dlist_format, int ffmt, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Format unit"; + int k, res, ret, sense_cat, tmout; + uint8_t fu_cdb[FORMAT_UNIT_CMDLEN] = + {FORMAT_UNIT_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (fmtpinfo) + fu_cdb[1] |= (fmtpinfo << 6); + if (longlist) + fu_cdb[1] |= 0x20; + if (fmtdata) + fu_cdb[1] |= 0x10; + if (cmplst) + fu_cdb[1] |= 0x8; + if (dlist_format) + fu_cdb[1] |= (dlist_format & 0x7); + if (ffmt) + fu_cdb[4] |= (ffmt & 0x3); + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < 6; ++k) + pr2ws("%02x ", fu_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + if (param_len > 0) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + pr2ws(" %s timeout: %d seconds\n", cdb_name_s, tmout); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, fu_cdb, sizeof(fu_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI REASSIGN BLOCKS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_reassign_blocks(int sg_fd, bool longlba, bool longlist, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Reassign blocks"; + int res, k, ret, sense_cat; + uint8_t reass_cdb[REASSIGN_BLKS_CMDLEN] = + {REASSIGN_BLKS_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (longlba) + reass_cdb[1] = 0x2; + if (longlist) + reass_cdb[1] |= 0x1; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < REASSIGN_BLKS_CMDLEN; ++k) + pr2ws("%02x ", reass_cdb[k]); + pr2ws("\n"); + } + if (verbose > 1) { + pr2ws(" %s parameter list\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, reass_cdb, sizeof(reass_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PERSISTENT RESERVE IN command (SPC). Returns 0 + * when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_persistent_reserve_in(int sg_fd, int rq_servact, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Persistent reservation in"; + int res, k, ret, sense_cat; + uint8_t prin_cdb[PERSISTENT_RESERVE_IN_CMDLEN] = + {PERSISTENT_RESERVE_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (rq_servact > 0) + prin_cdb[1] = (uint8_t)(rq_servact & 0x1f); + sg_put_unaligned_be16((uint16_t)mx_resp_len, prin_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PERSISTENT_RESERVE_IN_CMDLEN; ++k) + pr2ws("%02x ", prin_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, prin_cdb, sizeof(prin_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PERSISTENT RESERVE OUT command (SPC). Returns 0 + * when successful, various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_persistent_reserve_out(int sg_fd, int rq_servact, int rq_scope, + unsigned int rq_type, void * paramp, + int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "Persistent reservation out"; + int res, k, ret, sense_cat; + uint8_t prout_cdb[PERSISTENT_RESERVE_OUT_CMDLEN] = + {PERSISTENT_RESERVE_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (rq_servact > 0) + prout_cdb[1] = (uint8_t)(rq_servact & 0x1f); + prout_cdb[2] = (((rq_scope & 0xf) << 4) | (rq_type & 0xf)); + sg_put_unaligned_be16((uint16_t)param_len, prout_cdb + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < PERSISTENT_RESERVE_OUT_CMDLEN; ++k) + pr2ws("%02x ", prout_cdb[k]); + pr2ws("\n"); + if (verbose > 1) { + pr2ws(" %s parameters:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, 0); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, prout_cdb, sizeof(prout_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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 bool +has_blk_ili(uint8_t * sensep, int sb_len) +{ + int resp_code; + const uint8_t * cup; + + if (sb_len < 8) + return false; + resp_code = (0x7f & sensep[0]); + if (resp_code >= 0x72) { /* descriptor format */ + /* find block command descriptor */ + if ((cup = sg_scsi_sense_desc_find(sensep, sb_len, 0x5))) + return (cup[3] & 0x20); + } else /* fixed */ + return (sensep[2] & 0x20); + return false; +} + +/* Invokes a SCSI READ LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_long10(int sg_fd, bool pblock, bool correct, unsigned int lba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "read long(10)"; + int k, res, sense_cat, ret; + uint8_t readLong_cdb[READ_LONG10_CMDLEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(readLong_cdb, 0, READ_LONG10_CMDLEN); + readLong_cdb[0] = READ_LONG10_CMD; + if (pblock) + readLong_cdb[1] |= 0x4; + if (correct) + readLong_cdb[1] |= 0x2; + + sg_put_unaligned_be32((uint32_t)lba, readLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_LONG10_CMDLEN; ++k) + pr2ws("%02x ", readLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, valid, ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_long16(int sg_fd, bool pblock, bool correct, uint64_t llba, + void * resp, int xfer_len, int * offsetp, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "read long(16)"; + int k, res, sense_cat, ret; + uint8_t readLong_cdb[SERVICE_ACTION_IN_16_CMDLEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(readLong_cdb, 0, sizeof(readLong_cdb)); + readLong_cdb[0] = SERVICE_ACTION_IN_16_CMD; + readLong_cdb[1] = READ_LONG_16_SA; + if (pblock) + readLong_cdb[14] |= 0x2; + if (correct) + readLong_cdb[14] |= 0x1; + + sg_put_unaligned_be64(llba, readLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, readLong_cdb + 12); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k) + pr2ws("%02x ", readLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, readLong_cdb, sizeof(readLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, xfer_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE LONG (10) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_long10(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + unsigned int lba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write long(10)"; + int k, res, sense_cat, ret; + uint8_t writeLong_cdb[WRITE_LONG10_CMDLEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(writeLong_cdb, 0, WRITE_LONG10_CMDLEN); + writeLong_cdb[0] = WRITE_LONG10_CMD; + if (cor_dis) + writeLong_cdb[1] |= 0x80; + if (wr_uncor) + writeLong_cdb[1] |= 0x40; + if (pblock) + writeLong_cdb[1] |= 0x20; + + sg_put_unaligned_be32((uint32_t)lba, writeLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < (int)sizeof(writeLong_cdb); ++k) + pr2ws("%02x ", writeLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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_ILLEGAL_REQ: + { + int valid, slen, ili; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE LONG (16) command (SBC). Note that 'xfer_len' + * is in bytes. Returns 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_long16(int sg_fd, bool cor_dis, bool wr_uncor, bool pblock, + uint64_t llba, void * data_out, int xfer_len, + int * offsetp, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write long(16)"; + int k, res, sense_cat, ret; + uint8_t writeLong_cdb[SERVICE_ACTION_OUT_16_CMDLEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(writeLong_cdb, 0, sizeof(writeLong_cdb)); + writeLong_cdb[0] = SERVICE_ACTION_OUT_16_CMD; + writeLong_cdb[1] = WRITE_LONG_16_SA; + if (cor_dis) + writeLong_cdb[1] |= 0x80; + if (wr_uncor) + writeLong_cdb[1] |= 0x40; + if (pblock) + writeLong_cdb[1] |= 0x20; + + sg_put_unaligned_be64(llba, writeLong_cdb + 2); + sg_put_unaligned_be16((uint16_t)xfer_len, writeLong_cdb + 12); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SERVICE_ACTION_OUT_16_CMDLEN; ++k) + pr2ws("%02x ", writeLong_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, writeLong_cdb, sizeof(writeLong_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, xfer_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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_ILLEGAL_REQ: + { + bool valid, ili; + int slen; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + ili = has_blk_ili(sense_b, slen); + if (valid && ili) { + if (offsetp) + *offsetp = (int)(int64_t)ull; + ret = SG_LIB_CAT_ILLEGAL_REQ_WITH_INFO; + } else { + if (verbose > 1) + pr2ws(" info field: 0x%" PRIx64 ", valid: %d, " + "ili: %d\n", ull, (int)valid, (int)ili); + ret = SG_LIB_CAT_ILLEGAL_REQ; + } + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI VERIFY (10) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, * various SG_LIB_CAT_* positive values or + * -1 -> other errors */ +int +sg_ll_verify10(int sg_fd, int vrprotect, bool dpo, int bytchk, + unsigned int lba, int veri_len, void * data_out, + int data_out_len, unsigned int * infop, bool noisy, + int verbose) +{ + static const char * const cdb_name_s = "verify(10)"; + int k, res, ret, sense_cat, slen; + uint8_t v_cdb[VERIFY10_CMDLEN] = + {VERIFY10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */ + v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ; + if (dpo) + v_cdb[1] |= 0x10; + sg_put_unaligned_be32((uint32_t)lba, v_cdb + 2); + sg_put_unaligned_be16((uint16_t)veri_len, v_cdb + 7); + if (verbose > 1) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < VERIFY10_CMDLEN; ++k) + pr2ws("%02x ", v_cdb[k]); + pr2ws("\n"); + if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) { + k = data_out_len > 4104 ? 4104 : data_out_len; + pr2ws(" data_out buffer%s\n", + (data_out_len > 4104 ? ", first 4104 bytes" : "")); + hex2stderr((const uint8_t *)data_out, k, verbose < 5); + } + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (data_out_len > 0) + set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, data_out_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + if (valid) { + if (infop) + *infop = (unsigned int)ull; + ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO; + } else + ret = SG_LIB_CAT_MEDIUM_HARD; + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI VERIFY (16) command (SBC and MMC). + * Note that 'veri_len' is in blocks while 'data_out_len' is in bytes. + * Returns of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_verify16(int sg_fd, int vrprotect, bool dpo, int bytchk, uint64_t llba, + int veri_len, int group_num, void * data_out, + int data_out_len, uint64_t * infop, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "verify(16)"; + int k, res, ret, sense_cat, slen; + uint8_t v_cdb[VERIFY16_CMDLEN] = + {VERIFY16_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + /* N.B. BYTCHK field expanded to 2 bits sbc3r34 */ + v_cdb[1] = (((vrprotect & 0x7) << 5) | ((bytchk & 0x3) << 1)) ; + if (dpo) + v_cdb[1] |= 0x10; + sg_put_unaligned_be64(llba, v_cdb + 2); + sg_put_unaligned_be32((uint32_t)veri_len, v_cdb + 10); + v_cdb[14] = group_num & 0x1f; + if (verbose > 1) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < VERIFY16_CMDLEN; ++k) + pr2ws("%02x ", v_cdb[k]); + pr2ws("\n"); + if ((verbose > 3) && bytchk && data_out && (data_out_len > 0)) { + k = data_out_len > 4104 ? 4104 : data_out_len; + pr2ws(" data_out buffer%s\n", + (data_out_len > 4104 ? ", first 4104 bytes" : "")); + hex2stderr((const uint8_t *)data_out, k, verbose < 5); + } + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return sg_convert_errno(ENOMEM); + set_scsi_pt_cdb(ptvp, v_cdb, sizeof(v_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (data_out_len > 0) + set_scsi_pt_data_out(ptvp, (uint8_t *)data_out, data_out_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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; + uint64_t ull = 0; + + slen = get_scsi_pt_sense_len(ptvp); + valid = sg_get_sense_info_fld(sense_b, slen, &ull); + if (valid) { + if (infop) + *infop = ull; + ret = SG_LIB_CAT_MEDIUM_HARD_WITH_INFO; + } else + ret = SG_LIB_CAT_MEDIUM_HARD; + } + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a ATA PASS-THROUGH (12, 16 or 32) SCSI command (SAT). This is + * selected by the cdb_len argument that can take values of 12, 16 or 32 + * only (else -1 is returned). The byte at offset 0 (and bytes 0 to 9 + * inclusive for ATA PT(32)) pointed to be cdbp are ignored and apart from + * the control byte, the rest is copied into an internal cdb which is then + * sent to the device. The control byte is byte 11 for ATA PT(12), byte 15 + * for ATA PT(16) and byte 1 for ATA PT(32). If timeout_secs <= 0 then the + * timeout is set to 60 seconds. For data in or out transfers set dinp or + * doutp, and dlen to the number of bytes to transfer. If dlen is zero then + * no data transfer is assumed. If sense buffer obtained then it is written + * to sensep, else sensep[0] is set to 0x0. If ATA return descriptor is + * obtained then written to ata_return_dp, else ata_return_dp[0] is set to + * 0x0. Either sensep or ata_return_dp (or both) may be NULL pointers. + * Returns SCSI status value (>= 0) or -1 if other error. Users are + * expected to check the sense buffer themselves. If available the data in + * resid is written to residp. Note in SAT-2 and later, fixed format sense + * data may be placed in *sensep in which case sensep[0]==0x70, prior to + * SAT-2 descriptor sense format was required (i.e. sensep[0]==0x72). + */ +int +sg_ll_ata_pt(int sg_fd, const uint8_t * cdbp, int cdb_len, + int timeout_secs, void * dinp, void * doutp, int dlen, + uint8_t * sensep, int max_sense_len, + uint8_t * ata_return_dp, int max_ata_return_len, + int * residp, int verbose) +{ + int k, res, slen, duration; + int ret = -1; + uint8_t apt_cdb[ATA_PT_32_CMDLEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + uint8_t * sp; + const uint8_t * bp; + struct sg_pt_base * ptvp; + const char * cnamep; + char b[256]; + + memset(apt_cdb, 0, sizeof(apt_cdb)); + b[0] = '\0'; + switch (cdb_len) { + case 12: + cnamep = "ATA pass-through(12)"; + apt_cdb[0] = ATA_PT_12_CMD; + memcpy(apt_cdb + 1, cdbp + 1, 10); + /* control byte at cdb[11] left at zero */ + break; + case 16: + cnamep = "ATA pass-through(16)"; + apt_cdb[0] = ATA_PT_16_CMD; + memcpy(apt_cdb + 1, cdbp + 1, 14); + /* control byte at cdb[15] left at zero */ + break; + case 32: + cnamep = "ATA pass-through(32)"; + apt_cdb[0] = SG_VARIABLE_LENGTH_CMD; + /* control byte at cdb[1] left at zero */ + apt_cdb[7] = 0x18; /* length starting at next byte */ + sg_put_unaligned_be16(ATA_PT_32_SA, apt_cdb + 8); + memcpy(apt_cdb + 10, cdbp + 10, 32 - 10); + break; + default: + pr2ws("cdb_len must be 12, 16 or 32\n"); + return -1; + } + if (NULL == cdbp) { + if (verbose) + pr2ws("%s NULL cdb pointer\n", cnamep); + return -1; + } + if (sensep && (max_sense_len >= (int)sizeof(sense_b))) { + sp = sensep; + slen = max_sense_len; + } else { + sp = sense_b; + slen = sizeof(sense_b); + } + if (verbose) { + pr2ws(" %s cdb: ", cnamep); + if (cdb_len < 32) { + for (k = 0; k < cdb_len; ++k) + pr2ws("%02x ", apt_cdb[k]); + pr2ws("\n"); + } else { + pr2ws("\n"); + hex2stderr(apt_cdb, cdb_len, -1); + } + } + if (NULL == ((ptvp = create_pt_obj(cnamep)))) + return -1; + set_scsi_pt_cdb(ptvp, apt_cdb, cdb_len); + set_scsi_pt_sense(ptvp, sp, slen); + if (dlen > 0) { + if (dinp) + set_scsi_pt_data_in(ptvp, (uint8_t *)dinp, dlen); + else if (doutp) + set_scsi_pt_data_out(ptvp, (uint8_t *)doutp, dlen); + } + res = do_scsi_pt(ptvp, sg_fd, + ((timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT), + verbose); + if (SCSI_PT_DO_BAD_PARAMS == res) { + if (verbose) + pr2ws("%s: bad parameters\n", cnamep); + goto out; + } else if (SCSI_PT_DO_TIMEOUT == res) { + if (verbose) + pr2ws("%s: timeout\n", cnamep); + goto out; + } else if (res > 2) { + if (verbose) + pr2ws("%s: do_scsi_pt: errno=%d\n", cnamep, -res); + } + + if ((verbose > 2) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + pr2ws(" duration=%d ms\n", duration); + + switch (get_scsi_pt_result_category(ptvp)) { + case SCSI_PT_RESULT_GOOD: + if ((sensep) && (max_sense_len > 0)) + *sensep = 0; + if ((ata_return_dp) && (max_ata_return_len > 0)) + *ata_return_dp = 0; + if (residp && (dlen > 0)) + *residp = get_scsi_pt_resid(ptvp); + ret = 0; + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD + CHECK CONDITION */ + if ((sensep) && (max_sense_len > 0)) + *sensep = 0; + if ((ata_return_dp) && (max_ata_return_len > 0)) + *ata_return_dp = 0; + ret = get_scsi_pt_status_response(ptvp); + break; + case SCSI_PT_RESULT_SENSE: + if (sensep && (sp != sensep)) { + k = get_scsi_pt_sense_len(ptvp); + k = (k > max_sense_len) ? max_sense_len : k; + memcpy(sensep, sp, k); + } + if (ata_return_dp && (max_ata_return_len > 0)) { + /* search for ATA return descriptor */ + bp = sg_scsi_sense_desc_find(sp, slen, 0x9); + if (bp) { + k = bp[1] + 2; + k = (k > max_ata_return_len) ? max_ata_return_len : k; + memcpy(ata_return_dp, bp, k); + } else + ata_return_dp[0] = 0x0; + } + if (residp && (dlen > 0)) + *residp = get_scsi_pt_resid(ptvp); + ret = get_scsi_pt_status_response(ptvp); + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) + pr2ws("%s: transport error: %s\n", cnamep, + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b)); + break; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) + pr2ws("%s: os error: %s\n", cnamep, + get_scsi_pt_os_err_str(ptvp, sizeof(b) , b)); + break; + default: + if (verbose) + pr2ws("%s: unknown pt_result_category=%d\n", cnamep, + get_scsi_pt_result_category(ptvp)); + break; + } + +out: + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ BUFFER(10) command (SPC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * resp, int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read buffer(10)"; + int res, k, ret, sense_cat; + uint8_t rbuf_cdb[READ_BUFFER_CMDLEN] = + {READ_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + rbuf_cdb[1] = (uint8_t)(mode & 0x1f); + rbuf_cdb[2] = (uint8_t)(buffer_id & 0xff); + sg_put_unaligned_be24((uint32_t)buffer_offset, rbuf_cdb + 3); + sg_put_unaligned_be24((uint32_t)mx_resp_len, rbuf_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", rbuf_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rbuf_cdb, sizeof(rbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> success + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_write_buffer(int sg_fd, int mode, int buffer_id, int buffer_offset, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "write buffer"; + int k, res, ret, sense_cat; + uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] = + {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + wbuf_cdb[1] = (uint8_t)(mode & 0x1f); + wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff); + sg_put_unaligned_be24((uint32_t)buffer_offset, wbuf_cdb + 3); + sg_put_unaligned_be24((uint32_t)param_len, wbuf_cdb + 6); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", wbuf_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list", cdb_name_s); + if (2 == verbose) { + pr2ws("%s:\n", (param_len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)paramp, + (param_len > 256 ? 256 : param_len), -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)paramp, param_len, 0); + } + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI WRITE BUFFER command (SPC). Return of 0 -> + * success, SG_LIB_CAT_INVALID_OP -> invalid opcode, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure. Adds mode specific field (spc4r32) and timeout + * to command abort to override default of 60 seconds. If timeout_secs is + * 0 or less then the default timeout is used instead. */ +int +sg_ll_write_buffer_v2(int sg_fd, int mode, int m_specific, int buffer_id, + uint32_t buffer_offset, void * paramp, + uint32_t param_len, int timeout_secs, bool noisy, + int verbose) +{ + int k, res, ret, sense_cat; + uint8_t wbuf_cdb[WRITE_BUFFER_CMDLEN] = + {WRITE_BUFFER_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (buffer_offset > 0xffffff) { + pr2ws("%s: buffer_offset value too large for 24 bits\n", __func__); + return -1; + } + if (param_len > 0xffffff) { + pr2ws("%s: param_len value too large for 24 bits\n", __func__); + return -1; + } + wbuf_cdb[1] = (uint8_t)(mode & 0x1f); + wbuf_cdb[1] |= (uint8_t)((m_specific & 0x7) << 5); + wbuf_cdb[2] = (uint8_t)(buffer_id & 0xff); + sg_put_unaligned_be24(buffer_offset, wbuf_cdb + 3); + sg_put_unaligned_be24(param_len, wbuf_cdb + 6); + if (verbose) { + pr2ws(" Write buffer cdb: "); + for (k = 0; k < WRITE_BUFFER_CMDLEN; ++k) + pr2ws("%02x ", wbuf_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" Write buffer parameter list%s:\n", + ((param_len > 256) ? " (first 256 bytes)" : "")); + hex2stderr((const uint8_t *)paramp, + ((param_len > 256) ? 256 : param_len), -1); + } + } + if (timeout_secs <= 0) + timeout_secs = DEF_PT_TIMEOUT; + + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2ws("%s: out of memory\n", __func__); + return -1; + } + set_scsi_pt_cdb(ptvp, wbuf_cdb, sizeof(wbuf_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose); + ret = sg_cmds_process_resp(ptvp, "Write buffer", res, SG_NO_DATA_IN, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI UNMAP command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_unmap(int sg_fd, int group_num, int timeout_secs, void * paramp, + int param_len, bool noisy, int verbose) +{ + return sg_ll_unmap_v2(sg_fd, false, group_num, timeout_secs, paramp, + param_len, noisy, verbose); +} + +/* Invokes a SCSI UNMAP (SBC-3) command. Version 2 adds anchor field + * (sbc3r22). Otherwise same as sg_ll_unmap() . */ +int +sg_ll_unmap_v2(int sg_fd, bool anchor, int group_num, int timeout_secs, + void * paramp, int param_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "unmap"; + int k, res, ret, sense_cat, tmout; + uint8_t u_cdb[UNMAP_CMDLEN] = + {UNMAP_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (anchor) + u_cdb[1] |= 0x1; + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + u_cdb[6] = group_num & 0x1f; + sg_put_unaligned_be16((uint16_t)param_len, u_cdb + 7); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < UNMAP_CMDLEN; ++k) + pr2ws("%02x ", u_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, u_cdb, sizeof(u_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI READ BLOCK LIMITS command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_read_block_limits(int sg_fd, void * resp, int mx_resp_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "read block limits"; + int k, ret, res, sense_cat; + uint8_t rl_cdb[READ_BLOCK_LIMITS_CMDLEN] = + {READ_BLOCK_LIMITS_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < READ_BLOCK_LIMITS_CMDLEN; ++k) + pr2ws("%02x ", rl_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, ret, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI RECEIVE COPY RESULTS command. Actually cover all current + * uses of opcode 0x84 (Third-party copy IN). Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + int k, res, ret, sense_cat; + uint8_t rcvcopyres_cdb[THIRD_PARTY_COPY_IN_CMDLEN] = + {THIRD_PARTY_COPY_IN_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + char b[64]; + + sg_get_opcode_sa_name(THIRD_PARTY_COPY_IN_CMD, sa, 0, (int)sizeof(b), b); + rcvcopyres_cdb[1] = (uint8_t)(sa & 0x1f); + if (sa <= 4) /* LID1 variants */ + rcvcopyres_cdb[2] = (uint8_t)(list_id); + else if ((sa >= 5) && (sa <= 7)) /* LID4 variants */ + sg_put_unaligned_be32((uint32_t)list_id, rcvcopyres_cdb + 2); + sg_put_unaligned_be32((uint32_t)mx_resp_len, rcvcopyres_cdb + 10); + + if (verbose) { + pr2ws(" %s cdb: ", b); + for (k = 0; k < THIRD_PARTY_COPY_IN_CMDLEN; ++k) + pr2ws("%02x ", rcvcopyres_cdb[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(b)))) + return -1; + set_scsi_pt_cdb(ptvp, rcvcopyres_cdb, sizeof(rcvcopyres_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, b, res, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) + 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; +} + + +/* SPC-4 rev 35 and later calls this opcode (0x83) "Third-party copy OUT" + * The original EXTENDED COPY command (now called EXTENDED COPY (LID1)) + * is the only one supported by sg_ll_extended_copy(). See function + * sg_ll_3party_copy_out() for the other service actions ( > 0 ). */ + +/* Invokes a SCSI EXTENDED COPY (LID1) command. Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_extended_copy(int sg_fd, void * paramp, int param_len, bool noisy, + int verbose) +{ + int k, res, ret, sense_cat; + uint8_t xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] = + {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + const char * opcode_name = "Extended copy (LID1)"; + + xcopy_cdb[1] = (uint8_t)(EXTENDED_COPY_LID1_SA & 0x1f); + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + + if (verbose) { + pr2ws(" %s cdb: ", opcode_name); + for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k) + pr2ws("%02x ", xcopy_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", opcode_name); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(opcode_name)))) + return -1; + set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, opcode_name, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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; +} + +/* Handles various service actions associated with opcode 0x83 which is + * called THIRD PARTY COPY OUT. These include the EXTENDED COPY(LID1 and + * LID4), POPULATE TOKEN and WRITE USING TOKEN commands. + * Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +int +sg_ll_3party_copy_out(int sg_fd, int sa, unsigned int list_id, int group_num, + int timeout_secs, void * paramp, int param_len, + bool noisy, int verbose) +{ + int k, res, ret, sense_cat, tmout; + uint8_t xcopy_cdb[THIRD_PARTY_COPY_OUT_CMDLEN] = + {THIRD_PARTY_COPY_OUT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + char cname[80]; + + sg_get_opcode_sa_name(THIRD_PARTY_COPY_OUT_CMD, sa, 0, sizeof(cname), + cname); + xcopy_cdb[1] = (uint8_t)(sa & 0x1f); + switch (sa) { + case 0x0: /* XCOPY(LID1) */ + case 0x1: /* XCOPY(LID4) */ + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + break; + case 0x10: /* POPULATE TOKEN (SBC-3) */ + case 0x11: /* WRITE USING TOKEN (SBC-3) */ + sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 6); + sg_put_unaligned_be32((uint32_t)param_len, xcopy_cdb + 10); + xcopy_cdb[14] = (uint8_t)(group_num & 0x1f); + break; + case 0x1c: /* COPY OPERATION ABORT */ + sg_put_unaligned_be32((uint32_t)list_id, xcopy_cdb + 2); + break; + default: + pr2ws("%s: unknown service action 0x%x\n", __func__, sa); + return -1; + } + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + + if (verbose) { + pr2ws(" %s cdb: ", cname); + for (k = 0; k < THIRD_PARTY_COPY_OUT_CMDLEN; ++k) + pr2ws("%02x ", xcopy_cdb[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cname); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cname)))) + return -1; + set_scsi_pt_cdb(ptvp, xcopy_cdb, sizeof(xcopy_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + ret = sg_cmds_process_resp(ptvp, cname, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI PRE-FETCH(10), PRE-FETCH(16) or SEEK(10) command (SBC). + * Returns 0 -> success, 25 (SG_LIB_CAT_CONDITION_MET), various SG_LIB_CAT_* + * positive values or -1 -> other errors. Note that CONDITION MET status + * is returned when immed=true and num_blocks can fit in device's cache, + * somewaht strangely, GOOD status (return 0) is returned if num_blocks + * cannot fit in device's cache. If do_seek10==true then does a SEEK(10) + * command with given lba, if that LBA is < 2**32 . Unclear what SEEK(10) + * does, assume it is like PRE-FETCH. If timeout_secs is 0 (or less) then + * use DEF_PT_TIMEOUT (60 seconds) as command timeout. */ +int +sg_ll_pre_fetch_x(int sg_fd, bool do_seek10, bool cdb16, bool immed, + uint64_t lba, uint32_t num_blocks, int group_num, + int timeout_secs, bool noisy, int verbose) +{ + static const char * const cdb10_name_s = "Pre-fetch(10)"; + static const char * const cdb16_name_s = "Pre-fetch(16)"; + static const char * const cdb_seek_name_s = "Seek(10)"; + int k, res, sense_cat, ret, cdb_len, tmout; + const char *cdb_name_s; + uint8_t preFetchCdb[PRE_FETCH16_CMDLEN]; /* all use longest cdb */ + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + memset(preFetchCdb, 0, sizeof(preFetchCdb)); + if (do_seek10) { + if (lba > UINT32_MAX) { + if (verbose) + pr2ws("%s: LBA exceeds 2**32 in %s\n", __func__, + cdb_seek_name_s); + return -1; + } + preFetchCdb[0] = SEEK10_CMD; + cdb_len = SEEK10_CMDLEN; + cdb_name_s = cdb_seek_name_s; + sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2); + } else { + if ((! cdb16) && + ((lba > UINT32_MAX) || (num_blocks > UINT16_MAX))) { + cdb16 = true; + if (noisy || verbose) + pr2ws("%s: do %s due to %s size\n", __func__, cdb16_name_s, + (lba > UINT32_MAX) ? "LBA" : "NUM_BLOCKS"); + } + if (cdb16) { + preFetchCdb[0] = PRE_FETCH16_CMD; + cdb_len = PRE_FETCH16_CMDLEN; + cdb_name_s = cdb16_name_s; + if (immed) + preFetchCdb[1] = 0x2; + sg_put_unaligned_be64(lba, preFetchCdb + 2); + sg_put_unaligned_be32(num_blocks, preFetchCdb + 10); + preFetchCdb[14] = 0x3f & group_num; + } else { + preFetchCdb[0] = PRE_FETCH10_CMD; + cdb_len = PRE_FETCH10_CMDLEN; + cdb_name_s = cdb10_name_s; + if (immed) + preFetchCdb[1] = 0x2; + sg_put_unaligned_be32((uint32_t)lba, preFetchCdb + 2); + preFetchCdb[6] = 0x3f & group_num; + sg_put_unaligned_be16((uint16_t)num_blocks, preFetchCdb + 7); + } + } + tmout = (timeout_secs > 0) ? timeout_secs : DEF_PT_TIMEOUT; + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < cdb_len; ++k) + pr2ws("%02x ", preFetchCdb[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, preFetchCdb, cdb_len); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, tmout, verbose); + if (0 == res) { + int sstat = get_scsi_pt_status_response(ptvp); + + if (SG_LIB_CAT_CONDITION_MET == sstat) { + ret = SG_LIB_CAT_CONDITION_MET; + if (verbose > 2) + pr2ws("%s: returns SG_LIB_CAT_CONDITION_MET\n", __func__); + goto fini; + } + } + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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; +fini: + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/lib/sg_cmds_mmc.c b/lib/sg_cmds_mmc.c new file mode 100644 index 0000000..e135859 --- /dev/null +++ b/lib/sg_cmds_mmc.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2008-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. + */ + +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_cmds_mmc.h" +#include "sg_pt.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + +#define GET_CONFIG_CMD 0x46 +#define GET_CONFIG_CMD_LEN 10 +#define GET_PERFORMANCE_CMD 0xac +#define GET_PERFORMANCE_CMD_LEN 12 +#define SET_CD_SPEED_CMD 0xbb +#define SET_CD_SPEED_CMDLEN 12 +#define SET_STREAMING_CMD 0xb6 +#define SET_STREAMING_CMDLEN 12 + + +static struct sg_pt_base * +create_pt_obj(const char * cname) +{ + struct sg_pt_base * ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) + pr2ws("%s: out of memory\n", cname); + return ptvp; +} + +/* Invokes a SCSI SET CD SPEED command (MMC). + * Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> command not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION, + * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND, + * -1 -> other failure */ +int +sg_ll_set_cd_speed(int sg_fd, int rot_control, int drv_read_speed, + int drv_write_speed, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "set cd speed"; + int res, ret, k, sense_cat; + uint8_t scsCmdBlk[SET_CD_SPEED_CMDLEN] = {SET_CD_SPEED_CMD, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + scsCmdBlk[1] |= (rot_control & 0x3); + sg_put_unaligned_be16((uint16_t)drv_read_speed, scsCmdBlk + 2); + sg_put_unaligned_be16((uint16_t)drv_write_speed, scsCmdBlk + 4); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SET_CD_SPEED_CMDLEN; ++k) + pr2ws("%02x ", scsCmdBlk[k]); + pr2ws("\n"); + } + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, scsCmdBlk, sizeof(scsCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else + ret = 0; + + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI GET CONFIGURATION command (MMC-3,4,5). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int +sg_ll_get_config(int sg_fd, int rt, int starting, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "get configuration"; + int res, k, ret, sense_cat; + uint8_t gcCmdBlk[GET_CONFIG_CMD_LEN] = {GET_CONFIG_CMD, 0, 0, 0, + 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((rt < 0) || (rt > 3)) { + pr2ws("Bad rt value: %d\n", rt); + return -1; + } + gcCmdBlk[1] = (rt & 0x3); + if ((starting < 0) || (starting > 0xffff)) { + pr2ws("Bad starting field number: 0x%x\n", starting); + return -1; + } + sg_put_unaligned_be16((uint16_t)starting, gcCmdBlk + 2); + if ((mx_resp_len < 0) || (mx_resp_len > 0xffff)) { + pr2ws("Bad mx_resp_len: 0x%x\n", starting); + return -1; + } + sg_put_unaligned_be16((uint16_t)mx_resp_len, gcCmdBlk + 7); + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GET_CONFIG_CMD_LEN; ++k) + pr2ws("%02x ", gcCmdBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gcCmdBlk, sizeof(gcCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else { + if ((verbose > 2) && (ret > 3)) { + uint8_t * bp; + int len; + + bp = (uint8_t *)resp; + len = sg_get_unaligned_be32(bp + 0); + if (len < 0) + len = 0; + len = (ret < len) ? ret : len; + pr2ws(" %s: response:\n", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, len, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI GET PERFORMANCE command (MMC-3...6). + * Returns 0 when successful, SG_LIB_CAT_INVALID_OP if command not + * supported, SG_LIB_CAT_ILLEGAL_REQ if field in cdb not supported, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_ABORTED_COMMAND, else -1 */ +int +sg_ll_get_performance(int sg_fd, int data_type, unsigned int starting_lba, + int max_num_desc, int ttype, void * resp, + int mx_resp_len, bool noisy, int verbose) +{ + static const char * const cdb_name_s = "get performance"; + int res, k, ret, sense_cat; + uint8_t gpCmdBlk[GET_PERFORMANCE_CMD_LEN] = {GET_PERFORMANCE_CMD, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + if ((data_type < 0) || (data_type > 0x1f)) { + pr2ws("Bad data_type value: %d\n", data_type); + return -1; + } + gpCmdBlk[1] = (data_type & 0x1f); + sg_put_unaligned_be32((uint32_t)starting_lba, gpCmdBlk + 2); + if ((max_num_desc < 0) || (max_num_desc > 0xffff)) { + pr2ws("Bad max_num_desc: 0x%x\n", max_num_desc); + return -1; + } + sg_put_unaligned_be16((uint16_t)max_num_desc, gpCmdBlk + 8); + if ((ttype < 0) || (ttype > 0xff)) { + pr2ws("Bad type: 0x%x\n", ttype); + return -1; + } + gpCmdBlk[10] = (uint8_t)ttype; + + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < GET_PERFORMANCE_CMD_LEN; ++k) + pr2ws("%02x ", gpCmdBlk[k]); + pr2ws("\n"); + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, gpCmdBlk, sizeof(gpCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else { + if ((verbose > 2) && (ret > 3)) { + uint8_t * bp; + int len; + + bp = (uint8_t *)resp; + len = sg_get_unaligned_be32(bp + 0); + if (len < 0) + len = 0; + len = (ret < len) ? ret : len; + pr2ws(" %s: response", cdb_name_s); + if (3 == verbose) { + pr2ws("%s:\n", (len > 256 ? ", first 256 bytes" : "")); + hex2stderr((const uint8_t *)resp, (len > 256 ? 256 : len), + -1); + } else { + pr2ws(":\n"); + hex2stderr((const uint8_t *)resp, len, 0); + } + } + ret = 0; + } + destruct_scsi_pt_obj(ptvp); + return ret; +} + +/* Invokes a SCSI SET STREAMING command (MMC). Return of 0 -> success, + * SG_LIB_CAT_INVALID_OP -> Set Streaming not supported, + * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND, + * SG_LIB_CAT_UNIT_ATTENTION, SG_LIB_CAT_NOT_READY -> device not ready, + * -1 -> other failure */ +int +sg_ll_set_streaming(int sg_fd, int type, void * paramp, int param_len, + bool noisy, int verbose) +{ + static const char * const cdb_name_s = "set streaming"; + int k, res, ret, sense_cat; + uint8_t ssCmdBlk[SET_STREAMING_CMDLEN] = + {SET_STREAMING_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + struct sg_pt_base * ptvp; + + ssCmdBlk[8] = type; + sg_put_unaligned_be16((uint16_t)param_len, ssCmdBlk + 9); + if (verbose) { + pr2ws(" %s cdb: ", cdb_name_s); + for (k = 0; k < SET_STREAMING_CMDLEN; ++k) + pr2ws("%02x ", ssCmdBlk[k]); + pr2ws("\n"); + if ((verbose > 1) && paramp && param_len) { + pr2ws(" %s parameter list:\n", cdb_name_s); + hex2stderr((const uint8_t *)paramp, param_len, -1); + } + } + + if (NULL == ((ptvp = create_pt_obj(cdb_name_s)))) + return -1; + set_scsi_pt_cdb(ptvp, ssCmdBlk, sizeof(ssCmdBlk)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_NOT_READY: + case SG_LIB_CAT_INVALID_OP: + case SG_LIB_CAT_ILLEGAL_REQ: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ABORTED_COMMAND: + ret = sense_cat; + break; + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = -1; + break; + } + } else + ret = 0; + destruct_scsi_pt_obj(ptvp); + return ret; +} diff --git a/lib/sg_io_linux.c b/lib/sg_io_linux.c new file mode 100644 index 0000000..cab93fe --- /dev/null +++ b/lib/sg_io_linux.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 1999-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. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SG_LIB_LINUX + +#include "sg_io_linux.h" +#include "sg_pr2serr.h" + + +/* Version 1.10 20180613 */ + + +void +sg_print_masked_status(int masked_status) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + sg_print_scsi_status(scsi_status); +} + +/* host_bytes: DID_* are Linux SCSI result (a 32 bit variable) bits 16:23 */ + +static const char * linux_host_bytes[] = { + "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT", + "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR", + "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR", + "DID_IMM_RETRY", "DID_REQUEUE", "DID_TRANSPORT_DISRUPTED", + "DID_TRANSPORT_FAILFAST", "DID_TARGET_FAILURE", "DID_NEXUS_FAILURE", + "DID_ALLOC_FAILURE", "DID_MEDIUM_ERROR", +}; + +void +sg_print_host_status(int host_status) +{ + pr2ws("Host_status=0x%02x ", host_status); + if ((host_status < 0) || + (host_status >= (int)SG_ARRAY_SIZE(linux_host_bytes))) + pr2ws("is invalid "); + else + pr2ws("[%s] ", linux_host_bytes[host_status]); +} + +/* DRIVER_* are Linux SCSI result (a 32 bit variable) bits 24:27 */ + +static const char * linux_driver_bytes[] = { + "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA", + "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD", + "DRIVER_SENSE" +}; + +#if 0 + +/* SUGGEST_* are Linux SCSI result (a 32 bit variable) bits 28:31 */ + +static const char * linux_driver_suggests[] = { + "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP", + "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN", + "SUGGEST_SENSE" +}; +#endif + + +void +sg_print_driver_status(int driver_status) +{ + int driv; + const char * driv_cp = "invalid"; + + driv = driver_status & SG_LIB_DRIVER_MASK; + if (driv < (int)SG_ARRAY_SIZE(linux_driver_bytes)) + driv_cp = linux_driver_bytes[driv]; +#if 0 + sugg = (driver_status & SG_LIB_SUGGEST_MASK) >> 4; + if (sugg < (int)SG_ARRAY_SIZE(linux_driver_suggests)) + sugg_cp = linux_driver_suggests[sugg]; +#endif + pr2ws("Driver_status=0x%02x", driver_status); + pr2ws(" [%s] ", driv_cp); +} + +/* Returns 1 if no errors found and thus nothing printed; otherwise + prints error/warning (prefix by 'leadin') and returns 0. */ +static int +sg_linux_sense_print(const char * leadin, int scsi_status, int host_status, + int driver_status, const uint8_t * sense_buffer, + int sb_len, bool raw_sinfo) +{ + bool done_leadin = false; + bool done_sense = false; + + scsi_status &= 0x7e; /*sanity */ + if ((0 == scsi_status) && (0 == host_status) && (0 == driver_status)) + return 1; /* No problems */ + if (0 != scsi_status) { + if (leadin) + pr2ws("%s: ", leadin); + done_leadin = true; + pr2ws("SCSI status: "); + sg_print_scsi_status(scsi_status); + pr2ws("\n"); + if (sense_buffer && ((scsi_status == SAM_STAT_CHECK_CONDITION) || + (scsi_status == SAM_STAT_COMMAND_TERMINATED))) { + /* SAM_STAT_COMMAND_TERMINATED is obsolete */ + sg_print_sense(0, sense_buffer, sb_len, raw_sinfo); + done_sense = true; + } + } + if (0 != host_status) { + if (leadin && (! done_leadin)) + pr2ws("%s: ", leadin); + if (done_leadin) + pr2ws("plus...: "); + else + done_leadin = true; + sg_print_host_status(host_status); + pr2ws("\n"); + } + if (0 != driver_status) { + if (done_sense && + (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status))) + return 0; + if (leadin && (! done_leadin)) + pr2ws("%s: ", leadin); + if (done_leadin) + pr2ws("plus...: "); + sg_print_driver_status(driver_status); + pr2ws("\n"); + if (sense_buffer && (! done_sense) && + (SG_LIB_DRIVER_SENSE == (SG_LIB_DRIVER_MASK & driver_status))) + sg_print_sense(0, sense_buffer, sb_len, raw_sinfo); + } + return 0; +} + +#ifdef SG_IO + +bool +sg_normalize_sense(const struct sg_io_hdr * hp, + struct sg_scsi_sense_hdr * sshp) +{ + if ((NULL == hp) || (0 == hp->sb_len_wr)) { + if (sshp) + memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); + return 0; + } + return sg_scsi_normalize_sense(hp->sbp, hp->sb_len_wr, sshp); +} + +/* Returns 1 if no errors found and thus nothing printed; otherwise + returns 0. */ +int +sg_chk_n_print3(const char * leadin, struct sg_io_hdr * hp, + bool raw_sinfo) +{ + return sg_linux_sense_print(leadin, hp->status, hp->host_status, + hp->driver_status, hp->sbp, hp->sb_len_wr, + raw_sinfo); +} +#endif + +/* Returns 1 if no errors found and thus nothing printed; otherwise + returns 0. */ +int +sg_chk_n_print(const char * leadin, int masked_status, int host_status, + int driver_status, const uint8_t * sense_buffer, + int sb_len, bool raw_sinfo) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + return sg_linux_sense_print(leadin, scsi_status, host_status, + driver_status, sense_buffer, sb_len, + raw_sinfo); +} + +#ifdef SG_IO +int +sg_err_category3(struct sg_io_hdr * hp) +{ + return sg_err_category_new(hp->status, hp->host_status, + hp->driver_status, hp->sbp, hp->sb_len_wr); +} +#endif + +int +sg_err_category(int masked_status, int host_status, int driver_status, + const uint8_t * sense_buffer, int sb_len) +{ + int scsi_status = (masked_status << 1) & 0x7e; + + return sg_err_category_new(scsi_status, host_status, driver_status, + sense_buffer, sb_len); +} + +int +sg_err_category_new(int scsi_status, int host_status, int driver_status, + const uint8_t * sense_buffer, int sb_len) +{ + int masked_driver_status = (SG_LIB_DRIVER_MASK & driver_status); + + scsi_status &= 0x7e; + if ((0 == scsi_status) && (0 == host_status) && + (0 == masked_driver_status)) + return SG_LIB_CAT_CLEAN; + if ((SAM_STAT_CHECK_CONDITION == scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == scsi_status) || + (SG_LIB_DRIVER_SENSE == masked_driver_status)) + return sg_err_category_sense(sense_buffer, sb_len); + if (0 != host_status) { + if ((SG_LIB_DID_NO_CONNECT == host_status) || + (SG_LIB_DID_BUS_BUSY == host_status) || + (SG_LIB_DID_TIME_OUT == host_status)) + return SG_LIB_CAT_TIMEOUT; + if (SG_LIB_DID_NEXUS_FAILURE == host_status) + return SG_LIB_CAT_RES_CONFLICT; + } + if (SG_LIB_DRIVER_TIMEOUT == masked_driver_status) + return SG_LIB_CAT_TIMEOUT; + return SG_LIB_CAT_OTHER; +} + +#endif /* if SG_LIB_LINUX defined */ diff --git a/lib/sg_lib.c b/lib/sg_lib.c new file mode 100644 index 0000000..c2c5891 --- /dev/null +++ b/lib/sg_lib.c @@ -0,0 +1,3563 @@ +/* + * Copyright (c) 1999-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. + */ + +/* NOTICE: + * On 5th October 2004 (v1.00) this file name was changed from sg_err.c + * to sg_lib.c and the previous GPL was changed to a FreeBSD license. + * The intention is to maintain this file and the related sg_lib.h file + * as open source and encourage their unencumbered use. + * + * CONTRIBUTIONS: + * This file started out as a copy of SCSI opcodes, sense keys and + * additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem + * in the kernel source file: drivers/scsi/constant.c . That file + * bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale" + * and a GPL notice. + * + * Much of the data in this file is derived from SCSI draft standards + * found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4) + * being the central point of reference. + * + * Contributions: + * sense key specific field decoding [Trent Piepho 20031116] + * + */ + +#define _POSIX_C_SOURCE 200809L /* for posix_memalign() */ +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */ + +#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */ + +typedef unsigned int my_uint; /* convenience to save a few line wraps */ + +FILE * sg_warnings_strm = NULL; /* would like to default to stderr */ + + +int +pr2ws(const char * fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args); + va_end(args); + return n; +} + +/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of + * functions. Returns number of chars placed in cp excluding the + * trailing null char. So for cp_max_len > 0 the return value is always + * < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars are + * written to cp. Note this means that when cp_max_len = 1, this function + * assumes that cp[0] is the null character and does nothing (and returns + * 0). Linux kernel has a similar function called scnprintf(). Public + * declaration in sg_pr2serr.h header */ +int +sg_scnpr(char * cp, int cp_max_len, const char * fmt, ...) +{ + va_list args; + int n; + + if (cp_max_len < 2) + return 0; + va_start(args, fmt); + n = vsnprintf(cp, cp_max_len, fmt, args); + va_end(args); + return (n < cp_max_len) ? n : (cp_max_len - 1); +} + +/* Simple ASCII printable (does not use locale), includes space and excludes + * DEL (0x7f). */ +static inline int +my_isprint(int ch) +{ + return ((ch >= ' ') && (ch < 0x7f)); +} + +/* DSENSE is 'descriptor sense' as opposed to the older 'fixed sense'. + * Only (currently) used in SNTL. */ +bool +sg_get_initial_dsense(void) +{ + int k; + const char * cp; + + cp = getenv("SG3_UTILS_DSENSE"); + if (cp) { + if (1 == sscanf(cp, "%d", &k)) + return k ? true : false; + } + return false; +} + +/* Searches 'arr' for match on 'value' then 'peri_type'. If matches + 'value' but not 'peri_type' then yields first 'value' match entry. + Last element of 'arr' has NULL 'name'. If no match returns NULL. */ +static const struct sg_lib_value_name_t * +get_value_name(const struct sg_lib_value_name_t * arr, int value, + int peri_type) +{ + const struct sg_lib_value_name_t * vp = arr; + const struct sg_lib_value_name_t * holdp; + + if (peri_type < 0) + peri_type = 0; + for (; vp->name; ++vp) { + if (value == vp->value) { + if (peri_type == vp->peri_dev_type) + return vp; + holdp = vp; + while ((vp + 1)->name && (value == (vp + 1)->value)) { + ++vp; + if (peri_type == vp->peri_dev_type) + return vp; + } + return holdp; + } + } + return NULL; +} + +/* If this function is not called, sg_warnings_strm will be NULL and all users + * (mainly fprintf() ) need to check and substitute stderr as required */ +void +sg_set_warnings_strm(FILE * warnings_strm) +{ + sg_warnings_strm = warnings_strm; +} + +#define CMD_NAME_LEN 128 + +void +sg_print_command(const uint8_t * command) +{ + int k, sz; + char buff[CMD_NAME_LEN]; + + sg_get_command_name(command, 0, CMD_NAME_LEN, buff); + buff[CMD_NAME_LEN - 1] = '\0'; + + pr2ws("%s [", buff); + if (SG_VARIABLE_LENGTH_CMD == command[0]) + sz = command[7] + 8; + else + sz = sg_get_command_size(command[0]); + for (k = 0; k < sz; ++k) + pr2ws("%02x ", command[k]); + pr2ws("]\n"); +} + +/* SCSI Status values */ +static const struct sg_lib_simple_value_name_t sstatus_str_arr[] = { + {0x0, "Good"}, + {0x2, "Check Condition"}, + {0x4, "Condition Met"}, + {0x8, "Busy"}, + {0x10, "Intermediate (obsolete)"}, + {0x14, "Intermediate-Condition Met (obsolete)"}, + {0x18, "Reservation Conflict"}, + {0x22, "Command terminated (obsolete)"}, + {0x28, "Task Set Full"}, + {0x30, "ACA Active"}, + {0x40, "Task Aborted"}, + {0xffff, NULL}, +}; + +void +sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff) +{ + const struct sg_lib_simple_value_name_t * sstatus_p; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + scsi_status &= 0x7e; /* sanitize as much as possible */ + for (sstatus_p = sstatus_str_arr; sstatus_p->name; ++sstatus_p) { + if (scsi_status == sstatus_p->value) + break; + } + if (sstatus_p->name) + sg_scnpr(buff, buff_len, "%s", sstatus_p->name); + else + sg_scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status); +} + +void +sg_print_scsi_status(int scsi_status) +{ + char buff[128]; + + sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff); + buff[sizeof(buff) - 1] = '\0'; + pr2ws("%s ", buff); +} + +/* Get sense key from sense buffer. If successful returns a sense key value + * between 0 and 15. If sense buffer cannot be decode, returns -1 . */ +int +sg_get_sense_key(const uint8_t * sbp, int sb_len) +{ + if ((NULL == sbp) || (sb_len < 2)) + return -1; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + return (sb_len < 3) ? -1 : (sbp[2] & 0xf); + case 0x72: + case 0x73: + return sbp[1] & 0xf; + default: + return -1; + } +} + +/* Yield string associated with sense_key value. Returns 'buff'. */ +char * +sg_get_sense_key_str(int sense_key, int buff_len, char * buff) +{ + if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + if ((sense_key >= 0) && (sense_key < 16)) + sg_scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]); + else + sg_scnpr(buff, buff_len, "invalid value: 0x%x", sense_key); + return buff; +} + +/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */ +char * +sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff) +{ + int k, num, rlen; + bool found = false; + struct sg_lib_asc_ascq_t * eip; + struct sg_lib_asc_ascq_range_t * ei2p; + + if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) { + ei2p = &sg_lib_asc_ascq_range[k]; + if ((ei2p->asc == asc) && + (ascq >= ei2p->ascq_min) && + (ascq <= ei2p->ascq_max)) { + found = true; + num = sg_scnpr(buff, buff_len, "Additional sense: "); + rlen = buff_len - num; + sg_scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq); + } + } + if (found) + return buff; + + for (k = 0; sg_lib_asc_ascq[k].text; ++k) { + eip = &sg_lib_asc_ascq[k]; + if (eip->asc == asc && + eip->ascq == ascq) { + found = true; + sg_scnpr(buff, buff_len, "Additional sense: %s", eip->text); + } + } + if (! found) { + if (asc >= 0x80) + sg_scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x " + "(hex)", asc, ascq); + else if (ascq >= 0x80) + sg_scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification " + "ASCQ=%02x (hex)", asc, ascq); + else + sg_scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq); + } + return buff; +} + +/* Attempt to find the first SCSI sense data descriptor that matches the + * given 'desc_type'. If found return pointer to start of sense data + * descriptor; otherwise (including fixed format sense data) returns NULL. */ +const uint8_t * +sg_scsi_sense_desc_find(const uint8_t * sbp, int sb_len, + int desc_type) +{ + int add_sb_len, add_d_len, desc_len, k; + const uint8_t * descp; + + if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) + return NULL; + if ((sbp[0] < 0x72) || (sbp[0] > 0x73)) + return NULL; + add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); + descp = &sbp[8]; + for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) { + descp += desc_len; + add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1; + desc_len = add_d_len + 2; + if (descp[0] == desc_type) + return descp; + if (add_d_len < 0) /* short descriptor ?? */ + break; + } + return NULL; +} + +/* Returns true if valid bit set, false if valid bit clear. Irrespective the + * information field is written out via 'info_outp' (except when it is + * NULL). Handles both fixed and descriptor sense formats. */ +bool +sg_get_sense_info_fld(const uint8_t * sbp, int sb_len, + uint64_t * info_outp) +{ + const uint8_t * bp; + uint64_t ull; + + if (info_outp) + *info_outp = 0; + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (info_outp) + *info_outp = sg_get_unaligned_be32(sbp + 3); + return !!(sbp[0] & 0x80); + case 0x72: + case 0x73: + bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */); + if (bp && (0xa == bp[1])) { + ull = sg_get_unaligned_be64(bp + 4); + if (info_outp) + *info_outp = ull; + return !!(bp[2] & 0x80); /* since spc3r23 should be set */ + } else + return false; + default: + return false; + } +} + +/* Returns true if fixed format or command specific information descriptor + * is found in the descriptor sense; else false. If available the command + * specific information field (4 byte integer in fixed format, 8 byte + * integer in descriptor format) is written out via 'cmd_spec_outp'. + * Handles both fixed and descriptor sense formats. */ +bool +sg_get_sense_cmd_spec_fld(const uint8_t * sbp, int sb_len, + uint64_t * cmd_spec_outp) +{ + const uint8_t * bp; + + if (cmd_spec_outp) + *cmd_spec_outp = 0; + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (cmd_spec_outp) + *cmd_spec_outp = sg_get_unaligned_be32(sbp + 8); + return true; + case 0x72: + case 0x73: + bp = sg_scsi_sense_desc_find(sbp, sb_len, + 1 /* command specific info desc */); + if (bp && (0xa == bp[1])) { + if (cmd_spec_outp) + *cmd_spec_outp = sg_get_unaligned_be64(bp + 4); + return true; + } else + return false; + default: + return false; + } +} + +/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set. + * In descriptor format if the stream commands descriptor not found + * then returns false. Writes true or false corresponding to these bits to + * the last three arguments if they are non-NULL. */ +bool +sg_get_sense_filemark_eom_ili(const uint8_t * sbp, int sb_len, + bool * filemark_p, bool * eom_p, bool * ili_p) +{ + const uint8_t * bp; + + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + if (sbp[2] & 0xe0) { + if (filemark_p) + *filemark_p = !!(sbp[2] & 0x80); + if (eom_p) + *eom_p = !!(sbp[2] & 0x40); + if (ili_p) + *ili_p = !!(sbp[2] & 0x20); + return true; + } else + return false; + case 0x72: + case 0x73: + /* Look for stream commands sense data descriptor */ + bp = sg_scsi_sense_desc_find(sbp, sb_len, 4); + if (bp && (bp[1] >= 2)) { + if (bp[3] & 0xe0) { + if (filemark_p) + *filemark_p = !!(bp[3] & 0x80); + if (eom_p) + *eom_p = !!(bp[3] & 0x40); + if (ili_p) + *ili_p = !!(bp[3] & 0x20); + return true; + } + } + return false; + default: + return false; + } +} + +/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also + * returns true if progress indication sense data descriptor found. Places + * progress field from sense data where progress_outp points. If progress + * field is not available returns false and *progress_outp is unaltered. + * Handles both fixed and descriptor sense formats. + * Hint: if true is returned *progress_outp may be multiplied by 100 then + * divided by 65536 to get the percentage completion. */ +bool +sg_get_sense_progress_fld(const uint8_t * sbp, int sb_len, + int * progress_outp) +{ + const uint8_t * bp; + int sk, sk_pr; + + if (sb_len < 7) + return false; + switch (sbp[0] & 0x7f) { + case 0x70: + case 0x71: + sk = (sbp[2] & 0xf); + if ((sb_len < 18) || + ((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk))) + return false; + if (sbp[15] & 0x80) { /* SKSV bit set */ + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(sbp + 16); + return true; + } else + return false; + case 0x72: + case 0x73: + /* sense key specific progress (0x2) or progress descriptor (0xa) */ + sk = (sbp[1] & 0xf); + sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk); + if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) && + (0x6 == bp[1]) && (0x80 & bp[4])) { + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(bp + 5); + return true; + } else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) && + ((0x6 == bp[1]))) { + if (progress_outp) + *progress_outp = sg_get_unaligned_be16(bp + 6); + return true; + } else + return false; + default: + return false; + } +} + +char * +sg_get_pdt_str(int pdt, int buff_len, char * buff) +{ + if ((pdt < 0) || (pdt > 31)) + sg_scnpr(buff, buff_len, "bad pdt"); + else + sg_scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]); + return buff; +} + +int +sg_lib_pdt_decay(int pdt) +{ + if ((pdt < 0) || (pdt > 31)) + return 0; + return sg_lib_pdt_decay_arr[pdt]; +} + +char * +sg_get_trans_proto_str(int tpi, int buff_len, char * buff) +{ + if ((tpi < 0) || (tpi > 15)) + sg_scnpr(buff, buff_len, "bad tpi"); + else + sg_scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]); + return buff; +} + +#define TRANSPORT_ID_MIN_LEN 24 + +char * +sg_decode_transportid_str(const char * lip, uint8_t * bp, int bplen, + bool only_one, int blen, char * b) +{ + int proto_id, num, k, n, normal_len, tpid_format; + uint64_t ull; + int bump; + + if ((NULL == b) || (blen < 1)) + return b; + else if (1 == blen) { + b[0] = '\0'; + return b; + } + if (NULL == lip) + lip = ""; + bump = TRANSPORT_ID_MIN_LEN; /* should be overwritten in all loop paths */ + for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) { + if ((k > 0) && only_one) + break; + if ((bplen < 24) || (0 != (bplen % 4))) + n += sg_scnpr(b + n, blen - n, "%sTransport Id short or not " + "multiple of 4 [length=%d]:\n", lip, blen); + else + n += sg_scnpr(b + n, blen - n, "%sTransport Id of initiator:\n", + lip); + tpid_format = ((bp[0] >> 6) & 0x3); + proto_id = (bp[0] & 0xf); + normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ? + TRANSPORT_ID_MIN_LEN : bplen; + switch (proto_id) { + case TPROTO_FCP: /* Fibre channel */ + n += sg_scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n", + lip); + if (0 != tpid_format) + n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SPI: /* Scsi Parallel Interface, obsolete */ + n += sg_scnpr(b + n, blen - n, "%s Parallel SCSI initiator SCSI " + "address: 0x%x\n", lip, + sg_get_unaligned_be16(bp + 2)); + if (0 != tpid_format) + n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += sg_scnpr(b + n, blen - n, "%s relative port number (of " + "corresponding target): 0x%x\n", lip, + sg_get_unaligned_be16(bp + 6)); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SSA: + n += sg_scnpr(b + n, blen - n, "%s SSA (transport id not " + "defined):\n", lip); + n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_1394: /* IEEE 1394 */ + n += sg_scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n", + lip); + if (0 != tpid_format) + n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SRP: /* SCSI over RDMA */ + n += sg_scnpr(b + n, blen - n, "%s RDMA initiator port " + "identifier:\n", lip); + if (0 != tpid_format) + n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ISCSI: + n += sg_scnpr(b + n, blen - n, "%s iSCSI ", lip); + num = sg_get_unaligned_be16(bp + 2); + if (0 == tpid_format) + n += sg_scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]); + else if (1 == tpid_format) + n += sg_scnpr(b + n, blen - n, "world wide unique port id: " + "%.*s\n", num, &bp[4]); + else { + n += sg_scnpr(b + n, blen - n, " [Unexpected TPID format: " + "%d]\n", tpid_format); + n += hex2str(bp, num + 4, lip, 0, blen - n, b + n); + } + bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ? + TRANSPORT_ID_MIN_LEN : num + 4); + break; + case TPROTO_SAS: + ull = sg_get_unaligned_be64(bp + 4); + n += sg_scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n", + lip, ull); + if (0 != tpid_format) + n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: " + "%d]\n", lip, tpid_format); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ADT: /* no TransportID defined by T10 yet */ + n += sg_scnpr(b + n, blen - n, "%s ADT:\n", lip); + n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_ATA: /* no TransportID defined by T10 yet */ + n += sg_scnpr(b + n, blen - n, "%s ATAPI:\n", lip); + n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_UAS: /* no TransportID defined by T10 yet */ + n += sg_scnpr(b + n, blen - n, "%s UAS:\n", lip); + n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_SOP: + n += sg_scnpr(b + n, blen - n, "%s SOP ", lip); + num = sg_get_unaligned_be16(bp + 2); + if (0 == tpid_format) + n += sg_scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num); + else { + n += sg_scnpr(b + n, blen - n, " [Unexpected TPID format: " + "%d]\n", tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + } + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_PCIE: /* no TransportID defined by T10 yet */ + n += sg_scnpr(b + n, blen - n, "%s PCIE:\n", lip); + n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip, + tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + case TPROTO_NONE: /* no TransportID defined by T10 */ + n += sg_scnpr(b + n, blen - n, "%s No specified protocol\n", + lip); + /* n += hex2str(bp, ((bplen > 24) ? 24 : bplen), + * lip, 0, blen - n, b + n); */ + bump = TRANSPORT_ID_MIN_LEN; + break; + default: + n += sg_scnpr(b + n, blen - n, "%s unknown protocol id=0x%x " + "TPID format=%d\n", lip, proto_id, tpid_format); + n += hex2str(bp, normal_len, lip, 1, blen - n, b + n); + bump = TRANSPORT_ID_MIN_LEN; + break; + } + } + return b; +} + + +static const char * desig_code_set_str_arr[] = +{ + "Reserved [0x0]", + "Binary", + "ASCII", + "UTF-8", + "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]", + "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]", + "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", +}; + +const char * +sg_get_desig_code_set_str(int val) +{ + if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_code_set_str_arr))) + return desig_code_set_str_arr[val]; + else + return NULL; +} + +static const char * desig_assoc_str_arr[] = +{ + "Addressed logical unit", + "Target port", /* that received request; unless SCSI ports VPD */ + "Target device that contains addressed lu", + "Reserved [0x3]", +}; + +const char * +sg_get_desig_assoc_str(int val) +{ + if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_assoc_str_arr))) + return desig_assoc_str_arr[val]; + else + return NULL; +} + +static const char * desig_type_str_arr[] = +{ + "vendor specific [0x0]", + "T10 vendor identification", + "EUI-64 based", + "NAA", + "Relative target port", + "Target port group", /* spc4r09: _primary_ target port group */ + "Logical unit group", + "MD5 logical unit identifier", + "SCSI name string", + "Protocol specific port identifier", /* spc4r36 */ + "UUID identifier", /* spc5r08 */ + "Reserved [0xb]", + "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]", +}; + +const char * +sg_get_desig_type_str(int val) +{ + if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_type_str_arr))) + return desig_type_str_arr[val]; + else + return NULL; +} + +int +sg_get_designation_descriptor_str(const char * lip, const uint8_t * ddp, + int dd_len, bool print_assoc, bool do_long, + int blen, char * b) +{ + int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa; + int vsi, k, n, dlen; + const uint8_t * ip; + uint64_t vsei; + uint64_t id_ext; + char e[64]; + const char * cp; + + n = 0; + if (NULL == lip) + lip = ""; + if (dd_len < 4) { + n += sg_scnpr(b + n, blen - n, "%sdesignator desc too short: got " + "length of %d want 4 or more\n", lip, dd_len); + return n; + } + dlen = ddp[3]; + if (dlen > (dd_len - 4)) { + n += sg_scnpr(b + n, blen - n, "%sdesignator too long: says it is %d " + "bytes, but given %d bytes\n", lip, dlen, dd_len - 4); + return n; + } + ip = ddp + 4; + p_id = ((ddp[0] >> 4) & 0xf); + c_set = (ddp[0] & 0xf); + piv = ((ddp[1] & 0x80) ? 1 : 0); + assoc = ((ddp[1] >> 4) & 0x3); + desig_type = (ddp[1] & 0xf); + if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc)))) + n += sg_scnpr(b + n, blen - n, "%s %s:\n", lip, cp); + n += sg_scnpr(b + n, blen - n, "%s designator type: ", lip); + cp = sg_get_desig_type_str(desig_type); + if (cp) + n += sg_scnpr(b + n, blen - n, "%s", cp); + n += sg_scnpr(b + n, blen - n, ", code set: "); + cp = sg_get_desig_code_set_str(c_set); + if (cp) + n += sg_scnpr(b + n, blen - n, "%s", cp); + n += sg_scnpr(b + n, blen - n, "\n"); + if (piv && ((1 == assoc) || (2 == assoc))) + n += sg_scnpr(b + n, blen - n, "%s transport: %s\n", lip, + sg_get_trans_proto_str(p_id, sizeof(e), e)); + /* printf(" associated with the %s\n", sdparm_assoc_arr[assoc]); */ + switch (desig_type) { + case 0: /* vendor specific */ + k = 0; + if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */ + for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k) + ; + if (k >= dlen) + k = 1; + } + if (k) + n += sg_scnpr(b + n, blen - n, "%s vendor specific: %.*s\n", + lip, dlen, ip); + else { + n += sg_scnpr(b + n, blen - n, "%s vendor specific:\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + } + break; + case 1: /* T10 vendor identification */ + n += sg_scnpr(b + n, blen - n, "%s vendor id: %.8s\n", lip, ip); + if (dlen > 8) { + if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */ + n += sg_scnpr(b + n, blen - n, "%s vendor specific: " + "%.*s\n", lip, dlen - 8, ip + 8); + } else { + n += sg_scnpr(b + n, blen - n, "%s vendor specific: 0x", + lip); + for (m = 8; m < dlen; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "\n"); + } + } + break; + case 2: /* EUI-64 based */ + if (! do_long) { + if ((8 != dlen) && (12 != dlen) && (16 != dlen)) { + n += sg_scnpr(b + n, blen - n, "%s << expect 8, 12 and " + "16 byte EUI, got %d >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + n += sg_scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < dlen; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "\n"); + break; + } + n += sg_scnpr(b + n, blen - n, "%s EUI-64 based %d byte " + "identifier\n", lip, dlen); + if (1 != c_set) { + n += sg_scnpr(b + n, blen - n, "%s << expected binary " + "code_set (1) >>\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + ci_off = 0; + if (16 == dlen) { + ci_off = 8; + id_ext = sg_get_unaligned_be64(ip); + n += sg_scnpr(b + n, blen - n, "%s Identifier extension: 0x%" + PRIx64 "\n", lip, id_ext); + } else if ((8 != dlen) && (12 != dlen)) { + n += sg_scnpr(b + n, blen - n, "%s << can only decode 8, 12 " + "and 16 byte ids >>\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = sg_get_unaligned_be24(ip + ci_off); + n += sg_scnpr(b + n, blen - n, "%s IEEE Company_id: 0x%x\n", lip, + c_id); + vsei = 0; + for (m = 0; m < 5; ++m) { + if (m > 0) + vsei <<= 8; + vsei |= ip[ci_off + 3 + m]; + } + n += sg_scnpr(b + n, blen - n, "%s Vendor Specific Extension " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + if (12 == dlen) { + d_id = sg_get_unaligned_be32(ip + 8); + n += sg_scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n", + lip, d_id); + } + break; + case 3: /* NAA */ + if (1 != c_set) { + n += sg_scnpr(b + n, blen - n, "%s << unexpected code set " + "%d for NAA >>\n", lip, c_set); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + naa = (ip[0] >> 4) & 0xff; + switch (naa) { + case 2: /* NAA 2: IEEE Extended */ + if (8 != dlen) { + n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 2 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = (((ip[0] & 0xf) << 8) | ip[1]); + c_id = sg_get_unaligned_be24(ip + 2); + vsi = sg_get_unaligned_be24(ip + 5); + if (do_long) { + n += sg_scnpr(b + n, blen - n, "%s NAA 2, vendor " + "specific identifier A: 0x%x\n", lip, d_id); + n += sg_scnpr(b + n, blen - n, "%s IEEE Company_id: " + "0x%x\n", lip, c_id); + n += sg_scnpr(b + n, blen - n, "%s vendor specific " + "identifier B: 0x%x\n", lip, vsi); + n += sg_scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 8; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "]\n"); + } + n += sg_scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "\n"); + break; + case 3: /* NAA 3: Locally assigned */ + if (8 != dlen) { + n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 3 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + if (do_long) + n += sg_scnpr(b + n, blen - n, "%s NAA 3, Locally " + "assigned:\n", lip); + n += sg_scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "\n"); + break; + case 5: /* NAA 5: IEEE Registered */ + if (8 != dlen) { + n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 5 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | + (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); + vsei = ip[3] & 0xf; + for (m = 1; m < 5; ++m) { + vsei <<= 8; + vsei |= ip[3 + m]; + } + if (do_long) { + n += sg_scnpr(b + n, blen - n, "%s NAA 5, IEEE " + "Company_id: 0x%x\n", lip, c_id); + n += sg_scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + n += sg_scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 8; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "]\n"); + } else { + n += sg_scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 8; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "\n"); + } + break; + case 6: /* NAA 6: IEEE Registered extended */ + if (16 != dlen) { + n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 6 " + "identifier length: 0x%x >>\n", lip, dlen); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | + (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); + vsei = ip[3] & 0xf; + for (m = 1; m < 5; ++m) { + vsei <<= 8; + vsei |= ip[3 + m]; + } + if (do_long) { + n += sg_scnpr(b + n, blen - n, "%s NAA 6, IEEE " + "Company_id: 0x%x\n", lip, c_id); + n += sg_scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier: 0x%" PRIx64 "\n", lip, vsei); + vsei = sg_get_unaligned_be64(ip + 8); + n += sg_scnpr(b + n, blen - n, "%s Vendor Specific " + "Identifier Extension: 0x%" PRIx64 "\n", lip, + vsei); + n += sg_scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 16; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "]\n"); + } else { + n += sg_scnpr(b + n, blen - n, "%s 0x", lip); + for (m = 0; m < 16; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]); + n += sg_scnpr(b + n, blen - n, "\n"); + } + break; + default: + n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA [0x%x] " + ">>\n", lip, naa); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + break; + case 4: /* Relative target port */ + if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { + n += sg_scnpr(b + n, blen - n, "%s << expected binary " + "code_set, target port association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, "", 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += sg_scnpr(b + n, blen - n, "%s Relative target port: 0x%x\n", + lip, d_id); + break; + case 5: /* (primary) Target port group */ + if ((1 != c_set) || (1 != assoc) || (4 != dlen)) { + n += sg_scnpr(b + n, blen - n, "%s << expected binary " + "code_set, target port association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += sg_scnpr(b + n, blen - n, "%s Target port group: 0x%x\n", + lip, d_id); + break; + case 6: /* Logical unit group */ + if ((1 != c_set) || (0 != assoc) || (4 != dlen)) { + n += sg_scnpr(b + n, blen - n, "%s << expected binary " + "code_set, logical unit association, length 4 >>\n", + lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + d_id = sg_get_unaligned_be16(ip + 2); + n += sg_scnpr(b + n, blen - n, "%s Logical unit group: 0x%x\n", + lip, d_id); + break; + case 7: /* MD5 logical unit identifier */ + if ((1 != c_set) || (0 != assoc)) { + n += sg_scnpr(b + n, blen - n, "%s << expected binary " + "code_set, logical unit association >>\n", lip); + n += hex2str(ip, dlen, "", 1, blen - n, b + n); + break; + } + n += sg_scnpr(b + n, blen - n, "%s MD5 logical unit " + "identifier:\n", lip); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + case 8: /* SCSI name string */ + if (3 != c_set) { /* accept ASCII as subset of UTF-8 */ + if (2 == c_set) { + if (do_long) + n += sg_scnpr(b + n, blen - n, "%s << expected " + "UTF-8, use ASCII >>\n", lip); + } else { + n += sg_scnpr(b + n, blen - n, "%s << expected UTF-8 " + "code_set >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + } + n += sg_scnpr(b + n, blen - n, "%s SCSI name string:\n", lip); + /* does %s print out UTF-8 ok?? + * Seems to depend on the locale. Looks ok here with my + * locale setting: en_AU.UTF-8 + */ + n += sg_scnpr(b + n, blen - n, "%s %.*s\n", lip, dlen, + (const char *)ip); + break; + case 9: /* Protocol specific port identifier */ + /* added in spc4r36, PIV must be set, proto_id indicates */ + /* whether UAS (USB) or SOP (PCIe) or ... */ + if (! piv) + n += sg_scnpr(b + n, blen - n, " %s >>>> Protocol specific " + "port identifier expects protocol\n%s " + "identifier to be valid and it is not\n", lip, lip); + if (TPROTO_UAS == p_id) { + n += sg_scnpr(b + n, blen - n, "%s USB device address: " + "0x%x\n", lip, 0x7f & ip[0]); + n += sg_scnpr(b + n, blen - n, "%s USB interface number: " + "0x%x\n", lip, ip[2]); + } else if (TPROTO_SOP == p_id) { + n += sg_scnpr(b + n, blen - n, "%s PCIe routing ID, bus " + "number: 0x%x\n", lip, ip[0]); + n += sg_scnpr(b + n, blen - n, "%s function number: " + "0x%x\n", lip, ip[1]); + n += sg_scnpr(b + n, blen - n, "%s [or device number: " + "0x%x, function number: 0x%x]\n", lip, + (0x1f & (ip[1] >> 3)), 0x7 & ip[1]); + } else + n += sg_scnpr(b + n, blen - n, "%s >>>> unexpected protocol " + "identifier: %s\n%s with Protocol " + "specific port identifier\n", lip, + sg_get_trans_proto_str(p_id, sizeof(e), e), lip); + break; + case 0xa: /* UUID identifier */ + if (1 != c_set) { + n += sg_scnpr(b + n, blen - n, "%s << expected binary " + "code_set >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + if ((1 != ((ip[0] >> 4) & 0xf)) || (18 != dlen)) { + n += sg_scnpr(b + n, blen - n, "%s << expected locally " + "assigned UUID, 16 bytes long >>\n", lip); + n += hex2str(ip, dlen, lip, 0, blen - n, b + n); + break; + } + n += sg_scnpr(b + n, blen - n, "%s Locally assigned UUID: ", + lip); + for (m = 0; m < 16; ++m) { + if ((4 == m) || (6 == m) || (8 == m) || (10 == m)) + n += sg_scnpr(b + n, blen - n, "-"); + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[2 + m]); + } + n += sg_scnpr(b + n, blen - n, "\n"); + if (do_long) { + n += sg_scnpr(b + n, blen - n, "%s [0x", lip); + for (m = 0; m < 16; ++m) + n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[2 + m]); + n += sg_scnpr(b + n, blen - n, "]\n"); + } + break; + default: /* reserved */ + n += sg_scnpr(b + n, blen - n, "%s reserved designator=0x%x\n", + lip, desig_type); + n += hex2str(ip, dlen, lip, 1, blen - n, b + n); + break; + } + return n; +} + +static int +decode_sks(const char * lip, const uint8_t * descp, int add_d_len, + int sense_key, bool * processedp, int blen, char * b) +{ + int progress, pr, rem, n; + + n = 0; + if (NULL == lip) + lip = ""; + switch (sense_key) { + case SPC_SK_ILLEGAL_REQUEST: + if (add_d_len < 6) { + n += sg_scnpr(b + n, blen - n, "Field pointer: "); + goto too_short; + } + /* abbreviate to fit on one line */ + n += sg_scnpr(b + n, blen - n, "Field pointer:\n"); + n += sg_scnpr(b + n, blen - n, "%s Error in %s: byte %d", lip, + (descp[4] & 0x40) ? "Command" : "Data parameters", + sg_get_unaligned_be16(descp + 5)); + if (descp[4] & 0x08) { + n += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); + } else + n += sg_scnpr(b + n, blen - n, "\n"); + break; + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_RECOVERED_ERROR: + n += sg_scnpr(b + n, blen - n, "Actual retry count: "); + if (add_d_len < 6) + goto too_short; + n += sg_scnpr(b + n, blen - n,"%u\n", + sg_get_unaligned_be16(descp + 5)); + break; + case SPC_SK_NO_SENSE: + case SPC_SK_NOT_READY: + n += sg_scnpr(b + n, blen - n, "Progress indication: "); + if (add_d_len < 6) + goto too_short; + progress = sg_get_unaligned_be16(descp + 5); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + n += sg_scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem); + break; + case SPC_SK_COPY_ABORTED: + n += sg_scnpr(b + n, blen - n, "Segment pointer:\n"); + if (add_d_len < 6) + goto too_short; + n += sg_scnpr(b + n, blen - n, "%s Relative to start of %s, " + "byte %d", lip, (descp[4] & 0x20) ? + "segment descriptor" : "parameter list", + sg_get_unaligned_be16(descp + 5)); + if (descp[4] & 0x08) + n += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07); + else + n += sg_scnpr(b + n, blen - n, "\n"); + break; + case SPC_SK_UNIT_ATTENTION: + n += sg_scnpr(b + n, blen - n, "Unit attention condition queue:\n"); + n += sg_scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip, + !!(descp[4] & 0x1)); + break; + default: + n += sg_scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n", + sense_key); + *processedp = false; + break; + } + return n; + +too_short: + n += sg_scnpr(b + n, blen - n, "%s\n", " >> descriptor too short"); + *processedp = false; + return n; +} + +#define TPGS_STATE_OPTIMIZED 0x0 +#define TPGS_STATE_NONOPTIMIZED 0x1 +#define TPGS_STATE_STANDBY 0x2 +#define TPGS_STATE_UNAVAILABLE 0x3 +#define TPGS_STATE_OFFLINE 0xe +#define TPGS_STATE_TRANSITIONING 0xf + +static int +decode_tpgs_state(int st, char * b, int blen) +{ + switch (st) { + case TPGS_STATE_OPTIMIZED: + return sg_scnpr(b, blen, "active/optimized"); + case TPGS_STATE_NONOPTIMIZED: + return sg_scnpr(b, blen, "active/non optimized"); + case TPGS_STATE_STANDBY: + return sg_scnpr(b, blen, "standby"); + case TPGS_STATE_UNAVAILABLE: + return sg_scnpr(b, blen, "unavailable"); + case TPGS_STATE_OFFLINE: + return sg_scnpr(b, blen, "offline"); + case TPGS_STATE_TRANSITIONING: + return sg_scnpr(b, blen, "transitioning between states"); + default: + return sg_scnpr(b, blen, "unknown: 0x%x", st); + } +} + +static int +uds_referral_descriptor_str(char * b, int blen, const uint8_t * dp, + int alen, const char * lip) +{ + int n = 0; + int dlen = alen - 2; + int k, j, g, f, tpgd; + const uint8_t * tp; + uint64_t ull; + char c[40]; + + if (NULL == lip) + lip = ""; + n += sg_scnpr(b + n, blen - n, "%s Not all referrals: %d\n", lip, + !!(dp[2] & 0x1)); + dp += 4; + for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) { + tpgd = dp[3]; + g = (tpgd * 4) + 20; + n += sg_scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f); + if ((k + g) > dlen) { + n += sg_scnpr(b + n, blen - n, "%s truncated descriptor, " + "stop\n", lip); + return n; + } + ull = sg_get_unaligned_be64(dp + 4); + n += sg_scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64 + "\n", lip, ull); + ull = sg_get_unaligned_be64(dp + 12); + n += sg_scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64 + "\n", lip, ull); + for (j = 0; j < tpgd; ++j) { + tp = dp + 20 + (j * 4); + decode_tpgs_state(tp[0] & 0xf, c, sizeof(c)); + n += sg_scnpr(b + n, blen - n, "%s tpg: %d state: %s\n", + lip, sg_get_unaligned_be16(tp + 2), c); + } + } + return n; +} + +static const char * dd_usage_reason_str_arr[] = { + "Unknown", + "resend this and further commands to:", + "resend this command to:", + "new subsiduary lu added to this administrative lu:", + "administrative lu associated with a preferred binding:", + }; + + +/* Decode descriptor format sense descriptors (assumes sense buffer is + * in descriptor format). 'leadin' is string prepended to each line written + * to 'b', NULL treated as "". Returns the number of bytes written to 'b' + * excluding the trailing '\0'. If problem, returns 0. */ +int +sg_get_sense_descriptors_str(const char * lip, const uint8_t * sbp, + int sb_len, int blen, char * b) +{ + int add_sb_len, add_d_len, desc_len, k, j, sense_key; + int n, progress, pr, rem; + uint16_t sct_sc; + bool processed; + const uint8_t * descp; + const char * dtsp = " >> descriptor too short"; + const char * eccp = "Extended copy command"; + const char * ddp = "destination device"; + char z[64]; + + if ((NULL == b) || (blen <= 0)) + return 0; + b[0] = '\0'; + if (lip) + sg_scnpr(z, sizeof(z), "%.60s ", lip); + else + sg_scnpr(z, sizeof(z), " "); + if ((sb_len < 8) || (0 == (add_sb_len = sbp[7]))) + return 0; + add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8); + sense_key = (sbp[1] & 0xf); + + for (descp = (sbp + 8), k = 0, n = 0; + (k < add_sb_len) && (n < blen); + k += desc_len, descp += desc_len) { + add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1; + if ((k + add_d_len + 2) > add_sb_len) + add_d_len = add_sb_len - k - 2; + desc_len = add_d_len + 2; + n += sg_scnpr(b + n, blen - n, "%s Descriptor type: ", lip); + processed = true; + switch (descp[0]) { + case 0: + n += sg_scnpr(b + n, blen - n, "Information: "); + if (add_d_len >= 10) { + if (! (0x80 & descp[2])) + n += sg_scnpr(b + n, blen - n, "Valid=0 (-> vendor " + "specific) "); + n += sg_scnpr(b + n, blen - n, "0x"); + for (j = 0; j < 8; ++j) + n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]); + n += sg_scnpr(b + n, blen - n, "\n"); + } else { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 1: + n += sg_scnpr(b + n, blen - n, "Command specific: "); + if (add_d_len >= 10) { + n += sg_scnpr(b + n, blen - n, "0x"); + for (j = 0; j < 8; ++j) + n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]); + n += sg_scnpr(b + n, blen - n, "\n"); + } else { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 2: /* Sense Key Specific */ + n += sg_scnpr(b + n, blen - n, "Sense key specific: "); + n += decode_sks(lip, descp, add_d_len, sense_key, &processed, + blen - n, b + n); + break; + case 3: + n += sg_scnpr(b + n, blen - n, "Field replaceable unit code: "); + if (add_d_len >= 2) + n += sg_scnpr(b + n, blen - n, "0x%x\n", descp[3]); + else { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 4: + n += sg_scnpr(b + n, blen - n, "Stream commands: "); + if (add_d_len >= 2) { + if (descp[3] & 0x80) + n += sg_scnpr(b + n, blen - n, "FILEMARK"); + if (descp[3] & 0x40) + n += sg_scnpr(b + n, blen - n, "End Of Medium (EOM)"); + if (descp[3] & 0x20) + n += sg_scnpr(b + n, blen - n, "Incorrect Length " + "Indicator (ILI)"); + n += sg_scnpr(b + n, blen - n, "\n"); + } else { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 5: + n += sg_scnpr(b + n, blen - n, "Block commands: "); + if (add_d_len >= 2) + n += sg_scnpr(b + n, blen - n, "Incorrect Length Indicator " + "(ILI) %s\n", + (descp[3] & 0x20) ? "set" : "clear"); + else { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 6: + n += sg_scnpr(b + n, blen - n, "OSD object identification\n"); + processed = false; + break; + case 7: + n += sg_scnpr(b + n, blen - n, "OSD response integrity check " + "value\n"); + processed = false; + break; + case 8: + n += sg_scnpr(b + n, blen - n, "OSD attribute identification\n"); + processed = false; + break; + case 9: /* this is defined in SAT (SAT-2) */ + n += sg_scnpr(b + n, blen - n, "ATA Status Return: "); + if (add_d_len >= 12) { + int extend, count; + + extend = descp[2] & 1; + count = descp[5] + (extend ? (descp[4] << 8) : 0); + n += sg_scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s" + " count=0x%x ", extend, descp[3], lip, + count); + if (extend) + n += sg_scnpr(b + n, blen - n, + "lba=0x%02x%02x%02x%02x%02x%02x ", + descp[10], descp[8], descp[6], descp[11], + descp[9], descp[7]); + else + n += sg_scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ", + descp[11], descp[9], descp[7]); + n += sg_scnpr(b + n, blen - n, "device=0x%x status=0x%x\n", + descp[12], descp[13]); + } else { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + } + break; + case 0xa: + /* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */ + n += sg_scnpr(b + n, blen - n, "Another progress indication: "); + if (add_d_len < 6) { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + progress = sg_get_unaligned_be16(descp + 6); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + n += sg_scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem); + n += sg_scnpr(b + n, blen - n, "%s [sense_key=0x%x " + "asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3], + descp[4]); + break; + case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */ + n += sg_scnpr(b + n, blen - n, "User data segment referral: "); + if (add_d_len < 2) { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += sg_scnpr(b + n, blen - n, "\n"); + n += uds_referral_descriptor_str(b + n, blen - n, descp, + add_d_len, lip); + break; + case 0xc: /* Added in SPC-4 rev 28 */ + n += sg_scnpr(b + n, blen - n, "Forwarded sense data\n"); + if (add_d_len < 2) { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += sg_scnpr(b + n, blen - n, "%s FSDT: %s\n", lip, + (descp[2] & 0x80) ? "set" : "clear"); + j = descp[2] & 0xf; + n += sg_scnpr(b + n, blen - n, "%s Sense data source: ", lip); + switch (j) { + case 0: + n += sg_scnpr(b + n, blen - n, "%s source device\n", eccp); + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + n += sg_scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1); + break; + default: + n += sg_scnpr(b + n, blen - n, "unknown [%d]\n", j); + } + { + char c[480]; + + sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c); + c[sizeof(c) - 1] = '\0'; + n += sg_scnpr(b + n, blen - n, "%s Forwarded status: %s\n", + lip, c); + if (add_d_len > 2) { + /* recursing; hope not to get carried away */ + n += sg_scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n", + lip); + sg_get_sense_str(lip, descp + 4, add_d_len - 2, false, + sizeof(c), c); + n += sg_scnpr(b + n, blen - n, "%s", c); + n += sg_scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n", + lip); + } + } + break; + case 0xd: /* Added in SBC-3 rev 36d */ + /* this descriptor combines descriptors 0, 1, 2 and 3 */ + n += sg_scnpr(b + n, blen - n, "Direct-access block device\n"); + if (add_d_len < 28) { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + if (0x20 & descp[2]) + n += sg_scnpr(b + n, blen - n, "%s ILI (incorrect length " + "indication) set\n", lip); + if (0x80 & descp[4]) { + n += sg_scnpr(b + n, blen - n, "%s Sense key specific: ", + lip); + n += decode_sks(lip, descp, add_d_len, sense_key, &processed, + blen - n, b + n); + } + n += sg_scnpr(b + n, blen - n, "%s Field replaceable unit " + "code: 0x%x\n", lip, descp[7]); + if (0x80 & descp[2]) { + n += sg_scnpr(b + n, blen - n, "%s Information: 0x", lip); + for (j = 0; j < 8; ++j) + n += sg_scnpr(b + n, blen - n, "%02x", descp[8 + j]); + n += sg_scnpr(b + n, blen - n, "\n"); + } + n += sg_scnpr(b + n, blen - n, "%s Command specific: 0x", lip); + for (j = 0; j < 8; ++j) + n += sg_scnpr(b + n, blen - n, "%02x", descp[16 + j]); + n += sg_scnpr(b + n, blen - n, "\n"); + break; + case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */ + n += sg_scnpr(b + n, blen - n, "Device designation\n"); + j = (int)SG_ARRAY_SIZE(dd_usage_reason_str_arr); + if (descp[3] < j) + n += sg_scnpr(b + n, blen - n, "%s Usage reason: %s\n", + lip, dd_usage_reason_str_arr[descp[3]]); + else + n += sg_scnpr(b + n, blen - n, "%s Usage reason: " + "reserved[%d]\n", lip, descp[3]); + n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2, + true, false, blen - n, + b + n); + break; + case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */ + n += sg_scnpr(b + n, blen - n, "Microcode activation "); + if (add_d_len < 6) { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + progress = sg_get_unaligned_be16(descp + 6); + n += sg_scnpr(b + n, blen - n, "time: "); + if (0 == progress) + n += sg_scnpr(b + n, blen - n, "unknown\n"); + else + n += sg_scnpr(b + n, blen - n, "%d seconds\n", progress); + break; + case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */ + n += sg_scnpr(b + n, blen - n, "NVMe Status: "); + if (add_d_len < 6) { + n += sg_scnpr(b + n, blen - n, "%s\n", dtsp); + processed = false; + break; + } + n += sg_scnpr(b + n, blen - n, "DNR=%d, M=%d, ", + (int)!!(0x80 & descp[5]), (int)!!(0x40 & descp[5])); + sct_sc = sg_get_unaligned_be16(descp + 6); + n += sg_scnpr(b + n, blen - n, "SCT_SC=0x%x\n", sct_sc); + if (sct_sc > 0) { + char d[80]; + + n += sg_scnpr(b + n, blen - n, " %s\n", + sg_get_nvme_cmd_status_str(sct_sc, sizeof(d), d)); + } + break; + default: + if (descp[0] >= 0x80) + n += sg_scnpr(b + n, blen - n, "Vendor specific [0x%x]\n", + descp[0]); + else + n += sg_scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]); + processed = false; + break; + } + if (! processed) { + if (add_d_len > 0) { + n += sg_scnpr(b + n, blen - n, "%s ", lip); + for (j = 0; j < add_d_len; ++j) { + if ((j > 0) && (0 == (j % 24))) + n += sg_scnpr(b + n, blen - n, "\n%s ", lip); + n += sg_scnpr(b + n, blen - n, "%02x ", descp[j + 2]); + } + n += sg_scnpr(b + n, blen - n, "\n"); + } + } + if (add_d_len < 0) + n += sg_scnpr(b + n, blen - n, "%s short descriptor\n", lip); + } + return n; +} + +/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count' + * and/or 'lba' values to indicate that not all data in those fields is shown. + * That extra field information may be available in the ATA pass-through + * results log page parameter with the corresponding 'log_index'. */ +static int +sg_get_sense_sat_pt_fixed_str(const char * lip, const uint8_t * sp, + int slen, int blen, char * b) +{ + int n = 0; + bool extend, count_upper_nz, lba_upper_nz; + + if ((blen < 1) || (slen < 12)) + return n; + if (NULL == lip) + lip = ""; + if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) + n += sg_scnpr(b + n, blen - n, "%s >> expected Sense key: Recovered " + "Error ??\n", lip); + /* Fixed sense command-specific information field starts at sp + 8 */ + extend = !!(0x80 & sp[8]); + count_upper_nz = !!(0x40 & sp[8]); + lba_upper_nz = !!(0x20 & sp[8]); + /* Fixed sense information field starts at sp + 3 */ + n += sg_scnpr(b + n, blen - n, "%s error=0x%x, status=0x%x, " + "device=0x%x, count(7:0)=0x%x%c\n", lip, sp[3], sp[4], + sp[5], sp[6], (count_upper_nz ? '+' : ' ')); + n += sg_scnpr(b + n, blen - n, "%s extend=%d, log_index=0x%x, " + "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip, + (int)extend, (0xf & sp[8]), sp[9], sp[10], sp[11], + (lba_upper_nz ? '+' : ' ')); + return n; +} + +/* Fetch sense information */ +int +sg_get_sense_str(const char * lip, const uint8_t * sbp, int sb_len, + bool raw_sinfo, int cblen, char * cbp) +{ + bool descriptor_format = false; + bool sdat_ovfl = false; + bool valid; + int len, progress, n, r, pr, rem, blen; + unsigned int info; + uint8_t resp_code; + const char * ebp = NULL; + char ebuff[64]; + char b[256]; + struct sg_scsi_sense_hdr ssh; + + if ((NULL == cbp) || (cblen <= 0)) + return 0; + else if (1 == cblen) { + cbp[0] = '\0'; + return 0; + } + blen = sizeof(b); + n = 0; + if (NULL == lip) + lip = ""; + if ((NULL == sbp) || (sb_len < 1)) { + n += sg_scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip); + return n; + } + resp_code = 0x7f & sbp[0]; + valid = !!(sbp[0] & 0x80); + len = sb_len; + if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) { + switch (ssh.response_code) { + case 0x70: /* fixed, current */ + ebp = "Fixed format, current"; + len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; + len = (len > sb_len) ? sb_len : len; + sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; + break; + case 0x71: /* fixed, deferred */ + /* error related to a previous command */ + ebp = "Fixed format, <<>>"; + len = (sb_len > 7) ? (sbp[7] + 8) : sb_len; + len = (len > sb_len) ? sb_len : len; + sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false; + break; + case 0x72: /* descriptor, current */ + descriptor_format = true; + ebp = "Descriptor format, current"; + sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; + break; + case 0x73: /* descriptor, deferred */ + descriptor_format = true; + ebp = "Descriptor format, <<>>"; + sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false; + break; + case 0x0: + ebp = "Response code: 0x0 (?)"; + break; + default: + sg_scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x", + ssh.response_code); + ebp = ebuff; + break; + } + n += sg_scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp, + sg_lib_sense_key_desc[ssh.sense_key]); + if (sdat_ovfl) + n += sg_scnpr(cbp + n, cblen - n, "%s<<>>\n", lip); + if (descriptor_format) { + n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + n += sg_get_sense_descriptors_str(lip, sbp, len, + cblen - n, cbp + n); + } else if ((len > 12) && (0 == ssh.asc) && + (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) { + /* SAT ATA PASS-THROUGH fixed format */ + n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len, + cblen - n, cbp + n); + } else if (len > 2) { /* fixed format */ + if (len > 12) + n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip, + sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b)); + r = 0; + if (strlen(lip) > 0) + r += sg_scnpr(b + r, blen - r, "%s", lip); + if (len > 6) { + info = sg_get_unaligned_be32(sbp + 3); + if (valid) + r += sg_scnpr(b + r, blen - r, " Info fld=0x%x [%u] ", + info, info); + else if (info > 0) + r += sg_scnpr(b + r, blen - r, " Valid=0, Info fld=0x%x " + "[%u] ", info, info); + } else + info = 0; + if (sbp[2] & 0xe0) { + if (sbp[2] & 0x80) + r += sg_scnpr(b + r, blen - r, " FMK"); + /* current command has read a filemark */ + if (sbp[2] & 0x40) + r += sg_scnpr(b + r, blen - r, " EOM"); + /* end-of-medium condition exists */ + if (sbp[2] & 0x20) + r += sg_scnpr(b + r, blen - r, " ILI"); + /* incorrect block length requested */ + r += sg_scnpr(b + r, blen - r, "\n"); + } else if (valid || (info > 0)) + r += sg_scnpr(b + r, blen - r, "\n"); + if ((len >= 14) && sbp[14]) + r += sg_scnpr(b + r, blen - r, "%s Field replaceable unit " + "code: %d\n", lip, sbp[14]); + if ((len >= 18) && (sbp[15] & 0x80)) { + /* sense key specific decoding */ + switch (ssh.sense_key) { + case SPC_SK_ILLEGAL_REQUEST: + r += sg_scnpr(b + r, blen - r, "%s Sense Key Specific: " + "Error in %s: byte %d", lip, + ((sbp[15] & 0x40) ? + "Command" : "Data parameters"), + sg_get_unaligned_be16(sbp + 16)); + if (sbp[15] & 0x08) + r += sg_scnpr(b + r, blen - r, " bit %d\n", + sbp[15] & 0x07); + else + r += sg_scnpr(b + r, blen - r, "\n"); + break; + case SPC_SK_NO_SENSE: + case SPC_SK_NOT_READY: + progress = sg_get_unaligned_be16(sbp + 16); + pr = (progress * 100) / 65536; + rem = ((progress * 100) % 65536) / 656; + r += sg_scnpr(b + r, blen - r, "%s Progress indication: " + "%d.%02d%%\n", lip, pr, rem); + break; + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_RECOVERED_ERROR: + r += sg_scnpr(b + r, blen - r, "%s Actual retry count: " + "0x%02x%02x\n", lip, sbp[16], sbp[17]); + break; + case SPC_SK_COPY_ABORTED: + r += sg_scnpr(b + r, blen - r, "%s Segment pointer: ", + lip); + r += sg_scnpr(b + r, blen - r, "Relative to start of %s, " + "byte %d", ((sbp[15] & 0x20) ? + "segment descriptor" : "parameter list"), + sg_get_unaligned_be16(sbp + 16)); + if (sbp[15] & 0x08) + r += sg_scnpr(b + r, blen - r, " bit %d\n", + sbp[15] & 0x07); + else + r += sg_scnpr(b + r, blen - r, "\n"); + break; + case SPC_SK_UNIT_ATTENTION: + r += sg_scnpr(b + r, blen - r, "%s Unit attention " + "condition queue: ", lip); + r += sg_scnpr(b + r, blen - r, "overflow flag is %d\n", + !!(sbp[15] & 0x1)); + break; + default: + r += sg_scnpr(b + r, blen - r, "%s Sense_key: 0x%x " + "unexpected\n", lip, ssh.sense_key); + break; + } + } + if (r > 0) + n += sg_scnpr(cbp + n, cblen - n, "%s", b); + } else + n += sg_scnpr(cbp + n, cblen - n, "%s fixed descriptor length " + "too short, len=%d\n", lip, len); + } else { /* unable to normalise sense buffer, something irregular */ + if (sb_len < 4) { /* Too short */ + n += sg_scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 " + "byte minimum)\n", lip); + goto check_raw; + } + if (0x7f == resp_code) { /* Vendor specific */ + n += sg_scnpr(cbp + n, cblen - n, "%sVendor specific sense " + "buffer, in hex:\n", lip); + n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n); + return n; /* no need to check raw, just output in hex */ + } + /* non-extended SCSI-1 sense data ?? */ + r = 0; + if (strlen(lip) > 0) + r += sg_scnpr(b + r, blen - r, "%s", lip); + r += sg_scnpr(b + r, blen - r, "Probably uninitialized data.\n%s " + "Try to view as SCSI-1 non-extended sense:\n", lip); + r += sg_scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error " + "code=%d\n", valid, ((sbp[0] >> 4) & 0x7), + (sbp[0] & 0xf)); + if (valid) + sg_scnpr(b + r, blen - r, "%s lba=0x%x\n", lip, + sg_get_unaligned_be24(sbp + 1) & 0x1fffff); + n += sg_scnpr(cbp + n, cblen - n, "%s\n", b); + len = sb_len; + if (len > 32) + len = 32; /* trim in case there is a lot of rubbish */ + } +check_raw: + if (raw_sinfo) { + int embed_len; + char z[64]; + + n += sg_scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex), " + "sb_len=%d", lip, sb_len); + if (n >= (cblen - 1)) + return n; + if ((sb_len > 7) && (sbp[0] >= 0x70) && (sbp[0] < 0x74)) { + embed_len = sbp[7] + 8; + n += sg_scnpr(cbp + n, cblen - n, ", embedded_len=%d\n", + embed_len); + } else { + embed_len = sb_len; + n += sg_scnpr(cbp + n, cblen - n, "\n"); + } + if (n >= (cblen - 1)) + return n; + + sg_scnpr(z, sizeof(z), "%.50s ", lip); + n += hex2str(sbp, embed_len, z, -1, cblen - n, cbp + n); + } + return n; +} + +/* Print sense information */ +void +sg_print_sense(const char * leadin, const uint8_t * sbp, int sb_len, + bool raw_sinfo) +{ + uint32_t pg_sz = sg_get_page_size(); + char *cp; + uint8_t *free_cp; + + cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, 0); + if (NULL == cp) + return; + sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp); + pr2ws("%s", cp); + free(free_cp); +} + +/* This examines exit_status and if an error message is known it is output + * as a string to 'b' and true is returned. If 'longer' is true and extra + * information is available then it is added to the output. If no error + * message is available a null character is output and false is returned. + * If exit_status is zero (no error) and 'longer' is true then the string + * 'No errors' is output; if 'longer' is false then a null character is + * output; in both cases true is returned. If exit_status is negative then + * a null character is output and false is returned. All messages are a + * single line (less than 80 characters) with no trailing LF. The output + * string including the trailing null character is no longer than b_len. + * exit_status represents the Unix exit status available after a utility + * finishes executing (for whatever reason). */ +bool sg_exit2str(int exit_status, bool longer, int b_len, char *b) +{ + const struct sg_value_2names_t * ess = sg_exit_str_arr; + + if ((b_len < 1) || (NULL == b)) + return false; + /* if there is a valid buffer, initialize it to a valid empty string */ + b[0] = '\0'; + if (exit_status < 0) + return false; + else if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status)) { + if (longer) + goto fini; + return true; + } + + if ((exit_status > SG_LIB_OS_BASE_ERR) && /* 51 to 96 inclusive */ + (exit_status < SG_LIB_CAT_MALFORMED)) { + snprintf(b, b_len, "%s%s", (longer ? "OS error: " : ""), + safe_strerror(exit_status - SG_LIB_OS_BASE_ERR)); + return true; + } else if ((exit_status > 128) && (exit_status < 255)) { + snprintf(b, b_len, "Utility stopped/aborted by signal number: %d", + exit_status - 128); + return true; + } +fini: + for ( ; ess->name; ++ess) { + if (exit_status == ess->value) + break; + } + if (ess->name) { + if (longer && ess->name2) + snprintf(b, b_len, "%s, %s", ess->name, ess->name2); + else + snprintf(b, b_len, "%s", ess->name); + return true; + } + return false; +} + +static bool +sg_if_can2fp(const char * leadin, int exit_status, FILE * fp) +{ + char b[256]; + const char * s = leadin ? leadin : ""; + + if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status)) + return true; /* don't print anything */ + else if (sg_exit2str(exit_status, false, sizeof(b), b)) { + fprintf(fp, "%s%s\n", s, b); + return true; + } else + return false; +} + +/* This examines exit_status and if an error message is known it is output + * to stdout/stderr and true is returned. If no error message is + * available nothing is output and false is returned. If exit_status is + * zero (no error) nothing is output and true is returned. If exit_status + * is negative then nothing is output and false is returned. If leadin is + * non-NULL then it is printed before the error message. All messages are + * a single line with a trailing LF. */ +bool +sg_if_can2stdout(const char * leadin, int exit_status) +{ + return sg_if_can2fp(leadin, exit_status, stdout); +} + +/* See sg_if_can2stdout() comments */ +bool +sg_if_can2stderr(const char * leadin, int exit_status) +{ + return sg_if_can2fp(leadin, exit_status, + sg_warnings_strm ? sg_warnings_strm : stderr); +} + +/* If os_err_num is within bounds then the returned value is 'os_err_num + + * SG_LIB_OS_BASE_ERR' otherwise SG_LIB_OS_BASE_ERR is returned. If + * os_err_num is 0 then 0 is returned. */ +int +sg_convert_errno(int os_err_num) +{ + if (os_err_num <= 0) { + if (os_err_num < 0) + return SG_LIB_OS_BASE_ERR; + return os_err_num; /* os_err_num of 0 maps to 0 */ + } + if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR)) + return SG_LIB_OS_BASE_ERR + os_err_num; + return SG_LIB_OS_BASE_ERR; +} + +static const char * const bad_sense_cat = "Bad sense category"; + +/* Yield string associated with sense category. Returns 'b' (or pointer + * to "Bad sense category" if 'b' is NULL). If sense_cat unknown then + * yield "Sense category: " string. The original 'sense + * category' concept has been expanded to most detected errors and is + * returned by these utilities as their exit status value (an (unsigned) + * 8 bit value where 0 means good (i.e. no errors)). Uses sg_exit2str() + * function. */ +const char * +sg_get_category_sense_str(int sense_cat, int b_len, char * b, int verbose) +{ + int n; + + if (NULL == b) + return bad_sense_cat; + if (b_len <= 0) + return b; + if (! sg_exit2str(sense_cat, (verbose > 0), b_len, b)) { + n = sg_scnpr(b, b_len, "Sense category: %d", sense_cat); + if ((0 == verbose) && (n < (b_len - 1))) + sg_scnpr(b + n, b_len - n, ", try '-v' option for more " + "information"); + } + return b; /* Note that a valid C string is returned in all cases */ +} + +/* See description in sg_lib.h header file */ +bool +sg_scsi_normalize_sense(const uint8_t * sbp, int sb_len, + struct sg_scsi_sense_hdr * sshp) +{ + uint8_t resp_code; + if (sshp) + memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr)); + if ((NULL == sbp) || (sb_len < 1)) + return false; + resp_code = 0x7f & sbp[0]; + if ((resp_code < 0x70) || (resp_code > 0x73)) + return false; + if (sshp) { + sshp->response_code = resp_code; + if (sshp->response_code >= 0x72) { /* descriptor format */ + if (sb_len > 1) + sshp->sense_key = (0xf & sbp[1]); + if (sb_len > 2) + sshp->asc = sbp[2]; + if (sb_len > 3) + sshp->ascq = sbp[3]; + if (sb_len > 7) + sshp->additional_length = sbp[7]; + } else { /* fixed format */ + if (sb_len > 2) + sshp->sense_key = (0xf & sbp[2]); + if (sb_len > 7) { + sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8); + if (sb_len > 12) + sshp->asc = sbp[12]; + if (sb_len > 13) + sshp->ascq = sbp[13]; + } + } + } + return true; +} + +/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a + * less common sense key then return SG_LIB_CAT_SENSE .*/ +int +sg_err_category_sense(const uint8_t * sbp, int sb_len) +{ + struct sg_scsi_sense_hdr ssh; + + if ((sbp && (sb_len > 2)) && + (sg_scsi_normalize_sense(sbp, sb_len, &ssh))) { + switch (ssh.sense_key) { /* 0 to 0x1f */ + case SPC_SK_NO_SENSE: + return SG_LIB_CAT_NO_SENSE; + case SPC_SK_RECOVERED_ERROR: + return SG_LIB_CAT_RECOVERED; + case SPC_SK_NOT_READY: + return SG_LIB_CAT_NOT_READY; + case SPC_SK_MEDIUM_ERROR: + case SPC_SK_HARDWARE_ERROR: + case SPC_SK_BLANK_CHECK: + return SG_LIB_CAT_MEDIUM_HARD; + case SPC_SK_UNIT_ATTENTION: + return SG_LIB_CAT_UNIT_ATTENTION; + /* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */ + case SPC_SK_ILLEGAL_REQUEST: + if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) + return SG_LIB_CAT_INVALID_OP; + else if ((0x21 == ssh.asc) && (0x0 == ssh.ascq)) + return SG_LIB_LBA_OUT_OF_RANGE; + else + return SG_LIB_CAT_ILLEGAL_REQ; + break; + case SPC_SK_ABORTED_COMMAND: + if (0x10 == ssh.asc) + return SG_LIB_CAT_PROTECTION; + else + return SG_LIB_CAT_ABORTED_COMMAND; + case SPC_SK_MISCOMPARE: + return SG_LIB_CAT_MISCOMPARE; + case SPC_SK_DATA_PROTECT: + return SG_LIB_CAT_DATA_PROTECT; + case SPC_SK_COPY_ABORTED: + return SG_LIB_CAT_COPY_ABORTED; + case SPC_SK_COMPLETED: + case SPC_SK_VOLUME_OVERFLOW: + return SG_LIB_CAT_SENSE; + default: + ; /* reserved and vendor specific sense keys fall through */ + } + } + return SG_LIB_CAT_SENSE; +} + +/* Beware: gives wrong answer for variable length command (opcode=0x7f) */ +int +sg_get_command_size(uint8_t opcode) +{ + switch ((opcode >> 5) & 0x7) { + case 0: + return 6; + case 1: case 2: case 6: case 7: + return 10; + case 3: case 5: + return 12; + break; + case 4: + return 16; + default: + return 10; + } +} + +void +sg_get_command_name(const uint8_t * cmdp, int peri_type, int buff_len, + char * buff) +{ + int service_action; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + if (NULL == cmdp) { + sg_scnpr(buff, buff_len, "%s", " command pointer"); + return; + } + service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ? + sg_get_unaligned_be16(cmdp + 8) : (cmdp[1] & 0x1f); + sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff); +} + +struct op_code2sa_t { + int op_code; + int pdt_match; /* -1->all; 0->disk,ZBC,RCB, 1->tape+adc+smc */ + struct sg_lib_value_name_t * arr; + const char * prefix; +}; + +static struct op_code2sa_t op_code2sa_arr[] = { + {SG_VARIABLE_LENGTH_CMD, -1, sg_lib_variable_length_arr, NULL}, + {SG_MAINTENANCE_IN, -1, sg_lib_maint_in_arr, NULL}, + {SG_MAINTENANCE_OUT, -1, sg_lib_maint_out_arr, NULL}, + {SG_SERVICE_ACTION_IN_12, -1, sg_lib_serv_in12_arr, NULL}, + {SG_SERVICE_ACTION_OUT_12, -1, sg_lib_serv_out12_arr, NULL}, + {SG_SERVICE_ACTION_IN_16, -1, sg_lib_serv_in16_arr, NULL}, + {SG_SERVICE_ACTION_OUT_16, -1, sg_lib_serv_out16_arr, NULL}, + {SG_SERVICE_ACTION_BIDI, -1, sg_lib_serv_bidi_arr, NULL}, + {SG_PERSISTENT_RESERVE_IN, -1, sg_lib_pr_in_arr, "Persistent reserve in"}, + {SG_PERSISTENT_RESERVE_OUT, -1, sg_lib_pr_out_arr, + "Persistent reserve out"}, + {SG_3PARTY_COPY_OUT, -1, sg_lib_xcopy_sa_arr, NULL}, + {SG_3PARTY_COPY_IN, -1, sg_lib_rec_copy_sa_arr, NULL}, + {SG_READ_BUFFER, -1, sg_lib_read_buff_arr, "Read buffer(10)"}, + {SG_READ_BUFFER_16, -1, sg_lib_read_buff_arr, "Read buffer(16)"}, + {SG_READ_ATTRIBUTE, -1, sg_lib_read_attr_arr, "Read attribute"}, + {SG_READ_POSITION, 1, sg_lib_read_pos_arr, "Read position"}, + {SG_SANITIZE, 0, sg_lib_sanitize_sa_arr, "Sanitize"}, + {SG_WRITE_BUFFER, -1, sg_lib_write_buff_arr, "Write buffer"}, + {SG_ZONING_IN, 0, sg_lib_zoning_in_arr, NULL}, + {SG_ZONING_OUT, 0, sg_lib_zoning_out_arr, NULL}, + {0xffff, -1, NULL, NULL}, +}; + +void +sg_get_opcode_sa_name(uint8_t cmd_byte0, int service_action, + int peri_type, int buff_len, char * buff) +{ + int d_pdt; + const struct sg_lib_value_name_t * vnp; + const struct op_code2sa_t * osp; + char b[80]; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + + if (peri_type < 0) + peri_type = 0; + d_pdt = sg_lib_pdt_decay(peri_type); + for (osp = op_code2sa_arr; osp->arr; ++osp) { + if ((int)cmd_byte0 == osp->op_code) { + if ((osp->pdt_match < 0) || (d_pdt == osp->pdt_match)) { + vnp = get_value_name(osp->arr, service_action, peri_type); + if (vnp) { + if (osp->prefix) + sg_scnpr(buff, buff_len, "%s, %s", osp->prefix, + vnp->name); + else + sg_scnpr(buff, buff_len, "%s", vnp->name); + } else { + sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b); + sg_scnpr(buff, buff_len, "%s service action=0x%x", b, + service_action); + } + } else + sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); + return; + } + } + sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff); +} + +void +sg_get_opcode_name(uint8_t cmd_byte0, int peri_type, int buff_len, + char * buff) +{ + const struct sg_lib_value_name_t * vnp; + int grp; + + if ((NULL == buff) || (buff_len < 1)) + return; + else if (1 == buff_len) { + buff[0] = '\0'; + return; + } + if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) { + sg_scnpr(buff, buff_len, "%s", "Variable length"); + return; + } + grp = (cmd_byte0 >> 5) & 0x7; + switch (grp) { + case 0: + case 1: + case 2: + case 4: + case 5: + vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type); + if (vnp) + sg_scnpr(buff, buff_len, "%s", vnp->name); + else + sg_scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); + break; + case 3: + sg_scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0); + break; + case 6: + case 7: + sg_scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0); + break; + default: + sg_scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0); + break; + } +} + +/* Fetch NVMe command name given first byte (byte offset 0 in 64 byte + * command) of command. Gets Admin NVMe command name if 'admin' is true + * (e.g. opcode=0x6 -> Identify), otherwise gets NVM command set name + * (e.g. opcode=0 -> Flush). Returns 'buff'. */ +char * +sg_get_nvme_opcode_name(uint8_t cmd_byte0, bool admin, int buff_len, + char * buff) +{ + const struct sg_lib_simple_value_name_t * vnp = admin ? + sg_lib_nvme_admin_cmd_arr : sg_lib_nvme_nvm_cmd_arr; + + if ((NULL == buff) || (buff_len < 1)) + return buff; + else if (1 == buff_len) { + buff[0] = '\0'; + return buff; + } + for ( ; vnp->name; ++vnp) { + if (cmd_byte0 == (uint8_t)vnp->value) { + snprintf(buff, buff_len, "%s", vnp->name); + return buff; + } + } + if (admin) { + if (cmd_byte0 >= 0xc0) + snprintf(buff, buff_len, "Vendor specific opcode: 0x%x", + cmd_byte0); + else if (cmd_byte0 >= 0x80) + snprintf(buff, buff_len, "Command set specific opcode: 0x%x", + cmd_byte0); + else + snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0); + } else { /* NVM (non-Admin) command set */ + if (cmd_byte0 >= 0x80) + snprintf(buff, buff_len, "Vendor specific opcode: 0x%x", + cmd_byte0); + else + snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0); + } + return buff; +} + +/* Iterates to next designation descriptor in the device identification + * VPD page. The 'initial_desig_desc' should point to start of first + * descriptor with 'page_len' being the number of valid bytes in that + * and following descriptors. To start, 'off' should point to a negative + * value, thereafter it should point to the value yielded by the previous + * call. If 0 returned then 'initial_desig_desc + *off' should be a valid + * descriptor; returns -1 if normal end condition and -2 for an abnormal + * termination. Matches association, designator_type and/or code_set when + * any of those values are greater than or equal to zero. */ +int +sg_vpd_dev_id_iter(const uint8_t * initial_desig_desc, int page_len, + int * off, int m_assoc, int m_desig_type, int m_code_set) +{ + bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0)); + int k = *off; + const uint8_t * bp = initial_desig_desc; + + while ((k + 3) < page_len) { + k = (k < 0) ? 0 : (k + bp[k + 3] + 4); + if ((k + 4) > page_len) + break; + if (fltr) { + if (m_code_set >= 0) { + if ((bp[k] & 0xf) != m_code_set) + continue; + } + if (m_assoc >= 0) { + if (((bp[k + 1] >> 4) & 0x3) != m_assoc) + continue; + } + if (m_desig_type >= 0) { + if ((bp[k + 1] & 0xf) != m_desig_type) + continue; + } + } + *off = k; + return 0; + } + return (k == page_len) ? -1 : -2; +} + +static const char * sg_sfs_spc_reserved = "SPC Reserved"; +static const char * sg_sfs_sbc_reserved = "SBC Reserved"; +static const char * sg_sfs_ssc_reserved = "SSC Reserved"; +static const char * sg_sfs_zbc_reserved = "ZBC Reserved"; +static const char * sg_sfs_reserved = "Reserved"; + +/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31) + * returns pointer to string (same as 'buff') associated with 'sfs_code'. + * When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match + * on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL + * then where it points is set to true if a match is found else it is set to + * false. If 'buff' is not NULL then in the case of a match a descriptive + * string is written to 'buff' while if there is not a not then a string + * ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC + * or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL. + * Example: + * char b[64]; + * ... + * printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0)); + */ +const char * +sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff, + bool * foundp, int verbose) +{ + const struct sg_lib_value_name_t * vnp = NULL; + int n = 0; + int my_pdt; + + if ((NULL == buff) || (buff_len < 1)) { + if (foundp) + *foundp = false; + return NULL; + } else if (1 == buff_len) { + buff[0] = '\0'; + if (foundp) + *foundp = false; + return NULL; + } + my_pdt = ((peri_type < -1) || (peri_type > 0x1f)) ? -2 : peri_type; + vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt); + if (vnp && (-2 != my_pdt)) { + if (peri_type != vnp->peri_dev_type) + vnp = NULL; /* shouldn't really happen */ + } + if (foundp) + *foundp = vnp ? true : false; + if (sfs_code < 0x100) { /* SPC Feature Sets */ + if (vnp) { + if (verbose) + n += sg_scnpr(buff, buff_len, "SPC %s", vnp->name); + else + n += sg_scnpr(buff, buff_len, "%s", vnp->name); + } else + n += sg_scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved); + } else if (sfs_code < 0x200) { /* SBC Feature Sets */ + if (vnp) { + if (verbose) + n += sg_scnpr(buff, buff_len, "SBC %s", vnp->name); + else + n += sg_scnpr(buff, buff_len, "%s", vnp->name); + } else + n += sg_scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved); + } else if (sfs_code < 0x300) { /* SSC Feature Sets */ + if (vnp) { + if (verbose) + n += sg_scnpr(buff, buff_len, "SSC %s", vnp->name); + else + n += sg_scnpr(buff, buff_len, "%s", vnp->name); + } else + n += sg_scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved); + } else if (sfs_code < 0x400) { /* ZBC Feature Sets */ + if (vnp) { + if (verbose) + n += sg_scnpr(buff, buff_len, "ZBC %s", vnp->name); + else + n += sg_scnpr(buff, buff_len, "%s", vnp->name); + } else + n += sg_scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved); + } else { /* Other SCSI Feature Sets */ + if (vnp) { + if (verbose) + n += sg_scnpr(buff, buff_len, "[unrecognized PDT] %s", + vnp->name); + else + n += sg_scnpr(buff, buff_len, "%s", vnp->name); + } else + n += sg_scnpr(buff, buff_len, "%s", sg_sfs_reserved); + + } + if (verbose > 4) + pr2serr("%s: length of returned string (n) %d\n", __func__, n); + return buff; +} + +/* This is a heuristic that takes into account the command bytes and length + * to decide whether the presented unstructured sequence of bytes could be + * a SCSI command. If so it returns true otherwise false. Vendor specific + * SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed + * to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The + * only SCSI commands considered above 16 bytes of length are the Variable + * Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e). + * Both have an inbuilt length field which can be cross checked with clen. + * No NVMe commands (64 bytes long plus some extra added by some OSes) have + * opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS + * structures that are sent across the wire. The FIS register structure is + * used to move a command from a SATA host to device, but the ATA 'command' + * is not the first byte. So it is harder to say what will happen if a + * FIS structure is presented as a SCSI command, hopfully there is a low + * probability this function will yield true in that case. */ +bool +sg_is_scsi_cdb(const uint8_t * cdbp, int clen) +{ + int ilen, sa; + uint8_t opcode; + uint8_t top3bits; + + if (clen < 6) + return false; + opcode = cdbp[0]; + top3bits = opcode >> 5; + if (0x3 == top3bits) { + if ((clen < 12) || (clen % 4)) + return false; /* must be modulo 4 and 12 or more bytes */ + switch (opcode) { + case 0x7e: /* Extended cdb (XCDB) */ + ilen = 4 + sg_get_unaligned_be16(cdbp + 2); + return (ilen == clen); + case 0x7f: /* Variable Length cdb */ + ilen = 8 + cdbp[7]; + sa = sg_get_unaligned_be16(cdbp + 8); + /* service action (sa) 0x0 is reserved */ + return ((ilen == clen) && sa); + default: + return false; + } + } else if (clen <= 16) { + switch (clen) { + case 6: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x0 == top3bits); /* 6 byte cdb */ + case 10: + if (top3bits > 0x5) /* vendor */ + return true; + return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */ + case 16: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x4 == top3bits); /* 16 byte cdb */ + case 12: + if (top3bits > 0x5) /* vendor */ + return true; + return (0x5 == top3bits); /* 12 byte cdb */ + default: + return false; + } + } + /* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or + * opcode > 0x7f). */ + return false; +} + +/* Yield string associated with NVMe command status value in sct_sc. It + * expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25 + * are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC). + * Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found + * a string of the form "Reserved [0x]" is generated. + * Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/ +char * +sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b) +{ + int k; + uint16_t s = 0x3ff & sct_sc; + const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; + + if ((b_len <= 0) || (NULL == b)) + return b; + else if (1 == b_len) { + b[0] = '\0'; + return b; + } + for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { + if (s == (uint16_t)vp->value) { + strncpy(b, vp->name, b_len); + b[b_len - 1] = '\0'; + return b; + } + } + if (k >= 1000) + pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", + __func__); + snprintf(b, b_len, "Reserved [0x%x]", sct_sc); + return b; +} + +/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status, + * sense_key, asc and ascq tuple. If successful returns true and writes to + * non-NULL pointer arguments; otherwise returns false. */ +bool +sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p, + uint8_t * asc_p, uint8_t * ascq_p) +{ + int k, ind; + uint16_t s = 0x3ff & sct_sc; + struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr; + struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr; + + for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) { + if (s == (uint16_t)vp->value) + break; + } + if (k >= 1000) { + pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n", + __func__); + return false; + } + if (NULL == vp->name) + return false; + ind = vp->peri_dev_type; + + + for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp) + ; /* count entries for valid index range */ + if (k >= 1000) { + pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n", + __func__); + return false; + } else if (ind >= k) + return false; + mp = sg_lib_scsi_status_sense_arr + ind; + if (status_p) + *status_p = mp->t1; + if (sk_p) + *sk_p = mp->t2; + if (asc_p) + *asc_p = mp->t3; + if (ascq_p) + *ascq_p = mp->t4; + return true; +} + +/* Add vendor (sg3_utils) specific sense descriptor for the NVMe Status + * field. Assumes descriptor (i.e. not fixed) sense. Assumes sbp has room. */ +void +sg_nvme_desc2sense(uint8_t * sbp, bool dnr, bool more, uint16_t sct_sc) +{ + int len = sbp[7] + 8; + + sbp[len] = 0xde; /* vendor specific descriptor type */ + sbp[len + 1] = 6; /* descriptor is 8 bytes long */ + memset(sbp + len + 2, 0, 6); + if (dnr) + sbp[len + 5] = 0x80; + if (more) + sbp[len + 5] |= 0x40; + sg_put_unaligned_be16(sct_sc, sbp + len + 6); + sbp[7] += 8; +} + +/* Build minimum sense buffer, either descriptor type (desc=true) or fixed + * type (desc=false). Assume sbp has enough room (8 or 14 bytes + * respectively). sbp should have room for 32 or 18 bytes respectively */ +void +sg_build_sense_buffer(bool desc, uint8_t *sbp, uint8_t skey, uint8_t asc, + uint8_t ascq) +{ + if (desc) { + sbp[0] = 0x72; /* descriptor, current */ + sbp[1] = skey; + sbp[2] = asc; + sbp[3] = ascq; + sbp[7] = 0; + } else { + sbp[0] = 0x70; /* fixed, current */ + sbp[2] = skey; + sbp[7] = 0xa; /* Assumes length is 18 bytes */ + sbp[12] = asc; + sbp[13] = ascq; + } +} + +/* safe_strerror() contributed by Clayton Weaver + * Allows for situation in which strerror() is given a wild value (or the + * C library is incomplete) and returns NULL. Still not thread safe. + */ + +static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ', + 'e', 'r', 'r', 'n', 'o', ':', ' ', 0}; + +char * +safe_strerror(int errnum) +{ + size_t len; + char * errstr; + + if (errnum < 0) + errnum = -errnum; + errstr = strerror(errnum); + if (NULL == errstr) { + len = strlen(safe_errbuf); + sg_scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum); + return safe_errbuf; + } + return errstr; +} + +static void +trimTrailingSpaces(char * b) +{ + int k; + + for (k = ((int)strlen(b) - 1); k >= 0; --k) { + if (' ' != b[k]) + break; + } + if ('\0' != b[k + 1]) + b[k + 1] = '\0'; +} + +/* Note the ASCII-hex output goes to stdout. [Most other output from functions + * in this file go to sg_warnings_strm (default stderr).] + * 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 16 ASCII-hex bytes + * = 0 in addition, the bytes are listed in ASCII to the right + * < 0 only the ASCII-hex bytes are listed (i.e. without address) */ +static void +dStrHexFp(const char* str, int len, int no_ascii, FILE * fp) +{ + const char * p = str; + const char * formatstr; + uint8_t c; + char buff[82]; + int a = 0; + int bpstart = 5; + const int cpstart = 60; + int cpos = cpstart; + int bpos = bpstart; + int i, k, blen; + + if (len <= 0) + return; + blen = (int)sizeof(buff); + if (0 == no_ascii) /* address at left and ASCII at right */ + formatstr = "%.76s\n"; + else /* previously when > 0 str was "%.58s\n" */ + formatstr = "%s\n"; /* when < 0 str was: "%.48s\n" */ + memset(buff, ' ', 80); + buff[80] = '\0'; + if (no_ascii < 0) { + bpstart = 0; + bpos = bpstart; + for (k = 0; k < len; k++) { + c = *p++; + if (bpos == (bpstart + (8 * 3))) + bpos++; + sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c); + buff[bpos + 2] = ' '; + if ((k > 0) && (0 == ((k + 1) % 16))) { + trimTrailingSpaces(buff); + fprintf(fp, formatstr, buff); + bpos = bpstart; + memset(buff, ' ', 80); + } else + bpos += 3; + } + if (bpos > bpstart) { + buff[bpos + 2] = '\0'; + trimTrailingSpaces(buff); + fprintf(fp, "%s\n", buff); + } + return; + } + /* no_ascii>=0, start each line with address (offset) */ + k = sg_scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + + for (i = 0; i < len; i++) { + c = *p++; + bpos += 3; + if (bpos == (bpstart + (9 * 3))) + bpos++; + sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c); + buff[bpos + 2] = ' '; + if (no_ascii) + buff[cpos++] = ' '; + else { + if (! my_isprint(c)) + c = '.'; + buff[cpos++] = c; + } + if (cpos > (cpstart + 15)) { + if (no_ascii) + trimTrailingSpaces(buff); + fprintf(fp, formatstr, buff); + bpos = bpstart; + cpos = cpstart; + a += 16; + memset(buff, ' ', 80); + k = sg_scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + } + } + if (cpos > cpstart) { + buff[cpos] = '\0'; + if (no_ascii) + trimTrailingSpaces(buff); + fprintf(fp, "%s\n", buff); + } +} + +void +dStrHex(const char* str, int len, int no_ascii) +{ + dStrHexFp(str, len, no_ascii, stdout); +} + +void +dStrHexErr(const char* str, int len, int no_ascii) +{ + dStrHexFp(str, len, no_ascii, + (sg_warnings_strm ? sg_warnings_strm : stderr)); +} + +#define DSHS_LINE_BLEN 160 +#define DSHS_BPL 16 + +/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space + * separated) to 'b' not to exceed 'b_len' characters. Each line + * starts with 'leadin' (NULL for no leadin) and there are 16 bytes + * per line with an extra space between the 8th and 9th bytes. 'format' + * is 0 for repeat in printable ASCII ('.' for non printable) to + * right of each line; 1 don't (so just output ASCII hex). Returns + * number of bytes written to 'b' excluding the trailing '\0'. */ +int +dStrHexStr(const char * str, int len, const char * leadin, int format, + int b_len, char * b) +{ + uint8_t c; + int bpstart, bpos, k, n, prior_ascii_len; + bool want_ascii; + char buff[DSHS_LINE_BLEN + 2]; + char a[DSHS_BPL + 1]; + const char * p = str; + + if (len <= 0) { + if (b_len > 0) + b[0] = '\0'; + return 0; + } + if (b_len <= 0) + return 0; + want_ascii = !format; + if (want_ascii) { + memset(a, ' ', DSHS_BPL); + a[DSHS_BPL] = '\0'; + } + if (leadin) { + bpstart = strlen(leadin); + /* Cap leadin at (DSHS_LINE_BLEN - 70) characters */ + if (bpstart > (DSHS_LINE_BLEN - 70)) + bpstart = DSHS_LINE_BLEN - 70; + } else + bpstart = 0; + bpos = bpstart; + prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1; + n = 0; + memset(buff, ' ', DSHS_LINE_BLEN); + buff[DSHS_LINE_BLEN] = '\0'; + if (bpstart > 0) + memcpy(buff, leadin, bpstart); + for (k = 0; k < len; k++) { + c = *p++; + if (bpos == (bpstart + ((DSHS_BPL / 2) * 3))) + bpos++; /* for extra space in middle of each line's hex */ + sg_scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x", + (int)(uint8_t)c); + buff[bpos + 2] = ' '; + if (want_ascii) + a[k % DSHS_BPL] = my_isprint(c) ? c : '.'; + if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) { + trimTrailingSpaces(buff); + if (want_ascii) { + n += sg_scnpr(b + n, b_len - n, "%-*s %s\n", + prior_ascii_len, buff, a); + memset(a, ' ', DSHS_BPL); + } else + n += sg_scnpr(b + n, b_len - n, "%s\n", buff); + if (n >= (b_len - 1)) + return n; + memset(buff, ' ', DSHS_LINE_BLEN); + bpos = bpstart; + if (bpstart > 0) + memcpy(buff, leadin, bpstart); + } else + bpos += 3; + } + if (bpos > bpstart) { + trimTrailingSpaces(buff); + if (want_ascii) + n += sg_scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len, + buff, a); + else + n += sg_scnpr(b + n, b_len - n, "%s\n", buff); + } + return n; +} + +void +hex2stdout(const uint8_t * b_str, int len, int no_ascii) +{ + dStrHex((const char *)b_str, len, no_ascii); +} + +void +hex2stderr(const uint8_t * b_str, int len, int no_ascii) +{ + dStrHexErr((const char *)b_str, len, no_ascii); +} + +int +hex2str(const uint8_t * b_str, int len, const char * leadin, int format, + int b_len, char * b) +{ + return dStrHexStr((const char *)b_str, len, leadin, format, b_len, b); +} + +/* Returns true when executed on big endian machine; else returns false. + * Useful for displaying ATA identify words (which need swapping on a + * big endian machine). */ +bool +sg_is_big_endian() +{ + union u_t { + uint16_t s; + uint8_t c[sizeof(uint16_t)]; + } u; + + u.s = 0x0102; + return (u.c[0] == 0x01); /* The lowest address contains + the most significant byte */ +} + +bool +sg_all_zeros(const uint8_t * bp, int b_len) +{ + if ((NULL == bp) || (b_len <= 0)) + return false; + for (--b_len; b_len >= 0; --b_len) { + if (0x0 != bp[b_len]) + return false; + } + return true; +} + +bool +sg_all_ffs(const uint8_t * bp, int b_len) +{ + if ((NULL == bp) || (b_len <= 0)) + return false; + for (--b_len; b_len >= 0; --b_len) { + if (0xff != bp[b_len]) + return false; + } + return true; +} + +static uint16_t +swapb_uint16(uint16_t u) +{ + uint16_t r; + + r = (u >> 8) & 0xff; + r |= ((u & 0xff) << 8); + return r; +} + +/* Note the ASCII-hex output goes to stdout. [Most other output from functions + * in this file go to sg_warnings_strm (default stderr).] + * 'no_ascii' allows for 3 output types: + * > 0 each line has address then up to 8 ASCII-hex 16 bit words + * = 0 in addition, the ASCI bytes pairs are listed to the right + * = -1 only the ASCII-hex words are listed (i.e. without address) + * = -2 only the ASCII-hex words, formatted for "hdparm --Istdin" + * < -2 same as -1 + * If 'swapb' is true then bytes in each word swapped. Needs to be set + * for ATA IDENTIFY DEVICE response on big-endian machines. */ +void +dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb) +{ + const uint16_t * p = words; + uint16_t c; + char buff[82]; + uint8_t upp, low; + int a = 0; + const int bpstart = 3; + const int cpstart = 52; + int cpos = cpstart; + int bpos = bpstart; + int i, k, blen; + + if (num <= 0) + return; + blen = (int)sizeof(buff); + memset(buff, ' ', 80); + buff[80] = '\0'; + if (no_ascii < 0) { + for (k = 0; k < num; k++) { + c = *p++; + if (swapb) + c = swapb_uint16(c); + bpos += 5; + sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)c); + buff[bpos + 4] = ' '; + if ((k > 0) && (0 == ((k + 1) % 8))) { + if (-2 == no_ascii) + printf("%.39s\n", buff +8); + else + printf("%.47s\n", buff); + bpos = bpstart; + memset(buff, ' ', 80); + } + } + if (bpos > bpstart) { + if (-2 == no_ascii) + printf("%.39s\n", buff +8); + else + printf("%.47s\n", buff); + } + return; + } + /* no_ascii>=0, start each line with address (offset) */ + k = sg_scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + + for (i = 0; i < num; i++) { + c = *p++; + if (swapb) + c = swapb_uint16(c); + bpos += 5; + sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)c); + buff[bpos + 4] = ' '; + if (no_ascii) { + buff[cpos++] = ' '; + buff[cpos++] = ' '; + buff[cpos++] = ' '; + } else { + upp = (c >> 8) & 0xff; + low = c & 0xff; + if (! my_isprint(upp)) + upp = '.'; + buff[cpos++] = upp; + if (! my_isprint(low)) + low = '.'; + buff[cpos++] = low; + buff[cpos++] = ' '; + } + if (cpos > (cpstart + 23)) { + printf("%.76s\n", buff); + bpos = bpstart; + cpos = cpstart; + a += 8; + memset(buff, ' ', 80); + k = sg_scnpr(buff + 1, blen - 1, "%.2x", a); + buff[k + 1] = ' '; + } + } + if (cpos > cpstart) + printf("%.76s\n", buff); +} + +/* If the number in 'buf' can be decoded or the multiplier is unknown + * then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal + * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). + * Main (SI) multipliers supported: K, M, G. Ignore leading spaces and + * tabs; accept comma, hyphen, space, tab and hash as terminator. */ +int +sg_get_num(const char * buf) +{ + int res, num, n, len; + unsigned int unum; + char * cp; + const char * b; + char c = 'c'; + char c2 = '\0'; /* keep static checker happy */ + char c3 = '\0'; /* keep static checker happy */ + char lb[16]; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + n = strspn(buf, " \t"); + if (n > 0) { + if (n == len) + return -1; + buf += n; + len -= n; + } + /* following hack to keep C++ happy */ + cp = strpbrk((char *)buf, " \t,#-"); + if (cp) { + len = cp - buf; + n = (int)sizeof(lb) - 1; + len = (len < n) ? len : n; + memcpy(lb, buf, len); + lb[len] = '\0'; + b = lb; + } else + b = buf; + if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { + res = sscanf(b + 2, "%x", &unum); + num = unum; + } else if ('H' == toupper((int)b[len - 1])) { + res = sscanf(b, "%x", &unum); + num = unum; + } else + res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3); + if (res < 1) + return -1LL; + else if (1 == res) + return num; + else { + if (res > 2) + c2 = toupper((int)c2); + if (res > 3) + c3 = toupper((int)c3); + switch (toupper((int)c)) { + case 'C': + return num; + case 'W': + return num * 2; + case 'B': + return num * 512; + case 'K': + if (2 == res) + return num * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1024; + return -1; + case 'M': + if (2 == res) + return num * 1048576; + if (('B' == c2) || ('D' == c2)) + return num * 1000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1048576; + return -1; + case 'G': + if (2 == res) + return num * 1073741824; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1073741824; + return -1; + case 'X': + cp = (char *)strchr(b, 'x'); + if (NULL == cp) + cp = (char *)strchr(b, 'X'); + if (cp) { + n = sg_get_num(cp + 1); + if (-1 != n) + return num * n; + } + return -1; + default: + pr2ws("unrecognized multiplier\n"); + return -1; + } + } +} + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. */ +int +sg_get_num_nomult(const char * buf) +{ + int res, len, num; + unsigned int unum; + char * commap; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + commap = (char *)strchr(buf + 1, ','); + if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { + res = sscanf(buf + 2, "%x", &unum); + num = unum; + } else if (commap && ('H' == toupper((int)*(commap - 1)))) { + res = sscanf(buf, "%x", &unum); + num = unum; + } else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) { + res = sscanf(buf, "%x", &unum); + num = unum; + } else + res = sscanf(buf, "%d", &num); + if (1 == res) + return num; + else + return -1; +} + +/* If the number in 'buf' can be decoded or the multiplier is unknown + * then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal + * multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)). + * Main (SI) multipliers supported: K, M, G, T, P. Ignore leading spaces + * and tabs; accept comma, hyphen, space, tab and hash as terminator. */ +int64_t +sg_get_llnum(const char * buf) +{ + int res, len, n; + int64_t num, ll; + uint64_t unum; + char * cp; + const char * b; + char c = 'c'; + char c2 = '\0'; /* keep static checker happy */ + char c3 = '\0'; /* keep static checker happy */ + char lb[32]; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1LL; + len = strlen(buf); + n = strspn(buf, " \t"); + if (n > 0) { + if (n == len) + return -1LL; + buf += n; + len -= n; + } + /* following hack to keep C++ happy */ + cp = strpbrk((char *)buf, " \t,#-"); + if (cp) { + len = cp - buf; + n = (int)sizeof(lb) - 1; + len = (len < n) ? len : n; + memcpy(lb, buf, len); + lb[len] = '\0'; + b = lb; + } else + b = buf; + if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) { + res = sscanf(b + 2, "%" SCNx64 , &unum); + num = unum; + } else if ('H' == toupper((int)b[len - 1])) { + res = sscanf(b, "%" SCNx64 , &unum); + num = unum; + } else + res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3); + if (res < 1) + return -1LL; + else if (1 == res) + return num; + else { + if (res > 2) + c2 = toupper((int)c2); + if (res > 3) + c3 = toupper((int)c3); + switch (toupper((int)c)) { + case 'C': + return num; + case 'W': + return num * 2; + case 'B': + return num * 512; + case 'K': + if (2 == res) + return num * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1024; + return -1LL; + case 'M': + if (2 == res) + return num * 1048576; + if (('B' == c2) || ('D' == c2)) + return num * 1000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1048576; + return -1LL; + case 'G': + if (2 == res) + return num * 1073741824; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1073741824; + return -1LL; + case 'T': + if (2 == res) + return num * 1099511627776LL; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000000LL; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1099511627776LL; + return -1LL; + case 'P': + if (2 == res) + return num * 1099511627776LL * 1024; + if (('B' == c2) || ('D' == c2)) + return num * 1000000000000LL * 1000; + if (('I' == c2) && (4 == res) && ('B' == c3)) + return num * 1099511627776LL * 1024; + return -1LL; + case 'X': + cp = (char *)strchr(b, 'x'); + if (NULL == cp) + cp = (char *)strchr(b, 'X'); + if (cp) { + ll = sg_get_llnum(cp + 1); + if (-1LL != ll) + return num * ll; + } + return -1LL; + default: + pr2ws("unrecognized multiplier\n"); + return -1LL; + } + } +} + +/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a + * hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is + * assumed. Does not accept multipliers. Accept a comma (","), hyphen ("-"), + * a whitespace or newline as terminator. Only decimal numbers can represent + * negative numbers and '-1' must be treated separately. */ +int64_t +sg_get_llnum_nomult(const char * buf) +{ + int res, len; + int64_t num; + uint64_t unum; + + if ((NULL == buf) || ('\0' == buf[0])) + return -1; + len = strlen(buf); + if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) { + res = sscanf(buf + 2, "%" SCNx64 "", &unum); + num = unum; + } else if ('H' == toupper(buf[len - 1])) { + res = sscanf(buf, "%" SCNx64 "", &unum); + num = unum; + } else + res = sscanf(buf, "%" SCNd64 "", &num); + return (1 == res) ? num : -1; +} + +/* Extract character sequence from ATA words as in the model string + * in a IDENTIFY DEVICE response. Returns number of characters + * written to 'ochars' before 0 character is found or 'num' words + * are processed. */ +int +sg_ata_get_chars(const uint16_t * word_arr, int start_word, + int num_words, bool is_big_endian, char * ochars) +{ + int k; + uint16_t s; + char a, b; + char * op = ochars; + + for (k = start_word; k < (start_word + num_words); ++k) { + s = word_arr[k]; + if (is_big_endian) { + a = s & 0xff; + b = (s >> 8) & 0xff; + } else { + a = (s >> 8) & 0xff; + b = s & 0xff; + } + if (a == 0) + break; + *op++ = a; + if (b == 0) + break; + *op++ = b; + } + return op - ochars; +} + +int +pr2serr(const char * fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = vfprintf(stderr, fmt, args); + va_end(args); + return n; +} + +#ifdef SG_LIB_FREEBSD +#include +#elif defined(SG_LIB_WIN32) +#include +#endif + +uint32_t +sg_get_page_size(void) +{ +#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) + return sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */ +#elif defined(SG_LIB_WIN32) + static bool got_page_size = false; + static uint32_t win_page_size; + + if (! got_page_size) { + SYSTEM_INFO si; + + GetSystemInfo(&si); + win_page_size = si.dwPageSize; + got_page_size = true; + } + return win_page_size; +#elif defined(SG_LIB_FREEBSD) + return PAGE_SIZE; +#else + return 4096; /* give up, pick likely figure */ +#endif +} + +/* Returns pointer to heap (or NULL) that is aligned to a align_to byte + * boundary. Sends back *buff_to_free pointer in third argument that may be + * different from the return value. If it is different then the *buff_to_free + * pointer should be freed (rather than the returned value) when the heap is + * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all + * returned heap to zeros. If num_bytes is 0 then set to page size. */ +uint8_t * +sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free, + bool vb) +{ + size_t psz; + uint8_t * res; + + if (buff_to_free) /* make sure buff_to_free is NULL if alloc fails */ + *buff_to_free = NULL; + psz = (align_to > 0) ? align_to : sg_get_page_size(); + if (0 == num_bytes) + num_bytes = psz; /* ugly to handle otherwise */ + +#ifdef HAVE_POSIX_MEMALIGN + { + int err; + void * wp = NULL; + + err = posix_memalign(&wp, psz, num_bytes); + if (err || (NULL == wp)) { + pr2ws("%s: posix_memalign: error [%d], out of memory?\n", + __func__, err); + return NULL; + } + memset(wp, 0, num_bytes); + if (buff_to_free) + *buff_to_free = (uint8_t *)wp; + res = (uint8_t *)wp; + if (vb) { + pr2ws("%s: posix_ma, len=%d, ", __func__, num_bytes); + if (buff_to_free) + pr2ws("wrkBuffp=%p, ", (void *)res); + pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res); + } + return res; + } +#else + { + void * wrkBuff; + sg_uintptr_t align_1 = psz - 1; + + wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1); + if (NULL == wrkBuff) { + if (buff_to_free) + *buff_to_free = NULL; + return NULL; + } else if (buff_to_free) + *buff_to_free = (uint8_t *)wrkBuff; + res = (uint8_t *)(void *) + (((sg_uintptr_t)wrkBuff + align_1) & (~align_1)); + if (vb) { + pr2ws("%s: hack, len=%d, ", __func__, num_bytes); + if (buff_to_free) + pr2ws("buff_to_free=%p, ", wrkBuff); + pr2ws("align_1=%lu, rp=%p\n", align_1, (void *)res); + } + return res; + } +#endif +} + +/* If byte_count is 0 or less then the OS page size is used as denominator. + * Returns true if the remainder of ((unsigned)pointer % byte_count) is 0, + * else returns false. */ +bool +sg_is_aligned(const void * pointer, int byte_count) +{ + return 0 == ((sg_uintptr_t)pointer % + ((byte_count > 0) ? (uint32_t)byte_count : + sg_get_page_size())); +} + +/* Does similar job to sg_get_unaligned_be*() but this function starts at + * a given start_bit (i.e. within byte, so 7 is MSbit of byte and 0 is LSbit) + * offset. Maximum number of num_bits is 64. For example, these two + * invocations are equivalent (and should yield the same result); + * sg_get_big_endian(from_bp, 7, 16) + * sg_get_unaligned_be16(from_bp) */ +uint64_t +sg_get_big_endian(const uint8_t * from_bp, int start_bit /* 0 to 7 */, + int num_bits /* 1 to 64 */) +{ + uint64_t res; + int sbit_o1 = start_bit + 1; + + res = (*from_bp++ & ((1 << sbit_o1) - 1)); + num_bits -= sbit_o1; + while (num_bits > 0) { + res <<= 8; + res |= *from_bp++; + num_bits -= 8; + } + if (num_bits < 0) + res >>= (-num_bits); + return res; +} + +/* Does similar job to sg_put_unaligned_be*() but this function starts at + * a given start_bit offset. Maximum number of num_bits is 64. Preserves + * residual bits in partially written bytes. start_bit 7 is MSb. */ +void +sg_set_big_endian(uint64_t val, uint8_t * to, + int start_bit /* 0 to 7 */, int num_bits /* 1 to 64 */) +{ + int sbit_o1 = start_bit + 1; + int mask, num, k, x; + + if ((NULL == to) || (start_bit > 7) || (num_bits > 64)) { + pr2ws("%s: bad args: start_bit=%d, num_bits=%d\n", __func__, + start_bit, num_bits); + return; + } + mask = (8 != sbit_o1) ? ((1 << sbit_o1) - 1) : 0xff; + k = start_bit - ((num_bits - 1) % 8); + if (0 != k) + val <<= ((k > 0) ? k : (8 + k)); + num = (num_bits + 15 - sbit_o1) / 8; + for (k = 0; k < num; ++k) { + if ((sbit_o1 - num_bits) > 0) + mask &= ~((1 << (sbit_o1 - num_bits)) - 1); + if (k < (num - 1)) + x = (val >> ((num - k - 1) * 8)) & 0xff; + else + x = val & 0xff; + to[k] = (to[k] & ~mask) | (x & mask); + mask = 0xff; + num_bits -= sbit_o1; + sbit_o1 = 8; + } +} + +const char * +sg_lib_version() +{ + return sg_lib_version_str; +} + + +#ifdef SG_LIB_MINGW +/* Non Unix OSes distinguish between text and binary files. + Set text mode on fd. Does nothing in Unix. Returns negative number on + failure. */ + +#include + +int +sg_set_text_mode(int fd) +{ + return setmode(fd, O_TEXT); +} + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + failure. */ +int +sg_set_binary_mode(int fd) +{ + return setmode(fd, O_BINARY); +} + +#else +/* For Unix the following functions are dummies. */ +int +sg_set_text_mode(int fd) +{ + return fd; /* fd should be >= 0 */ +} + +int +sg_set_binary_mode(int fd) +{ + return fd; +} + +#endif diff --git a/lib/sg_lib_data.c b/lib/sg_lib_data.c new file mode 100644 index 0000000..d5ca380 --- /dev/null +++ b/lib/sg_lib_data.c @@ -0,0 +1,1852 @@ +/* + * Copyright (c) 2007-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. + */ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +#define SG_SCSI_STRINGS 1 +#endif + +#include "sg_lib.h" +#include "sg_lib_data.h" + + +const char * sg_lib_version_str = "2.58 20180911";/* spc5r19, sbc4r15 */ + + +/* indexed by pdt; those that map to own index do not decay */ +int sg_lib_pdt_decay_arr[32] = { + PDT_DISK, PDT_TAPE, PDT_TAPE /* printer */, PDT_PROCESSOR, + PDT_DISK /* WO */, PDT_MMC, PDT_SCANNER, PDT_DISK /* optical */, + PDT_MCHANGER, PDT_COMMS, 0xa, 0xb, + PDT_SAC, PDT_SES, PDT_DISK /* rbc */, PDT_OCRW, + PDT_BCC, PDT_OSD, PDT_TAPE /* adc */, PDT_SMD, + PDT_DISK /* zbc */, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, PDT_WLUN, PDT_UNKNOWN +}; + +#ifdef SG_SCSI_STRINGS +struct sg_lib_value_name_t sg_lib_normal_opcodes[] = { + {0, 0, "Test Unit Ready"}, + {0x1, 0, "Rezero Unit"}, + {0x1, PDT_TAPE, "Rewind"}, + {0x3, 0, "Request Sense"}, + {0x4, 0, "Format Unit"}, + {0x4, PDT_TAPE, "Format medium"}, + {0x4, PDT_PRINTER, "Format"}, + {0x5, 0, "Read Block Limits"}, + {0x7, 0, "Reassign Blocks"}, + {0x7, PDT_MCHANGER, "Initialize element status"}, + {0x8, 0, "Read(6)"}, /* obsolete in sbc3r30 */ + {0x8, PDT_PROCESSOR, "Receive"}, + {0xa, 0, "Write(6)"}, /* obsolete in sbc3r30 */ + {0xa, PDT_PRINTER, "Print"}, + {0xa, PDT_PROCESSOR, "Send"}, + {0xb, 0, "Seek(6)"}, + {0xb, PDT_TAPE, "Set capacity"}, + {0xb, PDT_PRINTER, "Slew and print"}, + {0xf, 0, "Read reverse(6)"}, + {0x10, 0, "Write filemarks(6)"}, + {0x10, PDT_PRINTER, "Synchronize buffer"}, + {0x11, 0, "Space(6)"}, + {0x12, 0, "Inquiry"}, + {0x13, 0, "Verify(6)"}, /* SSC */ + {0x14, 0, "Recover buffered data"}, + {0x15, 0, "Mode select(6)"}, /* SBC-3 r31 recommends Mode select(10) */ + {0x16, 0, "Reserve(6)"}, /* obsolete in SPC-4 r11 */ + {0x16, PDT_MCHANGER, "Reserve element(6)"}, + {0x17, 0, "Release(6)"}, /* obsolete in SPC-4 r11 */ + {0x17, PDT_MCHANGER, "Release element(6)"}, + {0x18, 0, "Copy"}, /* obsolete in SPC-4 r11 */ + {0x19, 0, "Erase(6)"}, + {0x1a, 0, "Mode sense(6)"}, /* SBC-3 r31 recommends Mode sense(10) */ + {0x1b, 0, "Start stop unit"}, + {0x1b, PDT_TAPE, "Load unload"}, + {0x1b, PDT_ADC, "Load unload"}, + {0x1b, PDT_PRINTER, "Stop print"}, + {0x1c, 0, "Receive diagnostic results"}, + {0x1d, 0, "Send diagnostic"}, + {0x1e, 0, "Prevent allow medium removal"}, + {0x23, 0, "Read Format capacities"}, + {0x24, 0, "Set window"}, + {0x25, 0, "Read capacity(10)"}, + /* SBC-3 r31 recommends Read capacity(16) */ + {0x25, PDT_OCRW, "Read card capacity"}, + {0x28, 0, "Read(10)"}, /* SBC-3 r31 recommends Read(16) */ + {0x29, 0, "Read generation"}, + {0x2a, 0, "Write(10)"}, /* SBC-3 r31 recommends Write(16) */ + {0x2b, 0, "Seek(10)"}, + {0x2b, PDT_TAPE, "Locate(10)"}, + {0x2b, PDT_MCHANGER, "Position to element"}, + {0x2c, 0, "Erase(10)"}, + {0x2d, 0, "Read updated block"}, + {0x2e, 0, "Write and verify(10)"}, + /* SBC-3 r31 recommends Write and verify(16) */ + {0x2f, 0, "Verify(10)"}, /* SBC-3 r31 recommends Verify(16) */ + {0x30, 0, "Search data high(10)"}, + {0x31, 0, "Search data equal(10)"}, + {0x32, 0, "Search data low(10)"}, + {0x33, 0, "Set limits(10)"}, + {0x34, 0, "Pre-fetch(10)"}, /* SBC-3 r31 recommends Pre-fetch(16) */ + {0x34, PDT_TAPE, "Read position"}, + {0x35, 0, "Synchronize cache(10)"}, + /* SBC-3 r31 recommends Synchronize cache(16) */ + {0x36, 0, "Lock unlock cache(10)"}, + {0x37, 0, "Read defect data(10)"}, + /* SBC-3 r31 recommends Read defect data(12) */ + {0x37, PDT_MCHANGER, "Initialize element status with range"}, + {0x38, 0, "Medium scan"}, + {0x39, 0, "Compare"}, /* obsolete in SPC-4 r11 */ + {0x3a, 0, "Copy and verify"}, /* obsolete in SPC-4 r11 */ + {0x3b, 0, "Write buffer"}, + {0x3c, 0, "Read buffer(10)"}, + {0x3d, 0, "Update block"}, + {0x3e, 0, "Read long(10)"}, /* obsolete in SBC-4 r7 */ + {0x3f, 0, "Write long(10)"}, /* SBC-3 r31 recommends Write long(16) */ + {0x40, 0, "Change definition"}, /* obsolete in SPC-4 r11 */ + {0x41, 0, "Write same(10)"}, /* SBC-3 r31 recommends Write same(16) */ + {0x42, 0, "Unmap"}, /* added SPC-4 rev 18 */ + {0x42, PDT_MMC, "Read sub-channel"}, + {0x43, PDT_MMC, "Read TOC/PMA/ATIP"}, + {0x44, 0, "Report density support"}, + {0x45, PDT_MMC, "Play audio(10)"}, + {0x46, PDT_MMC, "Get configuration"}, + {0x47, PDT_MMC, "Play audio msf"}, + {0x48, 0, "Sanitize"}, + {0x4a, PDT_MMC, "Get event status notification"}, + {0x4b, PDT_MMC, "Pause/resume"}, + {0x4c, 0, "Log select"}, + {0x4d, 0, "Log sense"}, + {0x4e, 0, "Stop play/scan"}, + {0x50, 0, "Xdwrite(10)"}, /* obsolete in SBC-3 r31 */ + {0x51, 0, "Xpwrite(10)"}, /* obsolete in SBC-4 r15 */ + {0x51, PDT_MMC, "Read disk information"}, + {0x52, 0, "Xdread(10)"}, /* obsolete in SBC-3 r31 */ + {0x52, PDT_MMC, "Read track information"}, + {0x53, 0, "Xdwriteread(10)"}, /* obsolete in SBC-4 r15 */ + {0x54, 0, "Send OPC information"}, + {0x55, 0, "Mode select(10)"}, + {0x56, 0, "Reserve(10)"}, /* obsolete in SPC-4 r11 */ + {0x56, PDT_MCHANGER, "Reserve element(10)"}, + {0x57, 0, "Release(10)"}, /* obsolete in SPC-4 r11 */ + {0x57, PDT_MCHANGER, "Release element(10)"}, + {0x58, 0, "Repair track"}, + {0x5a, 0, "Mode sense(10)"}, + {0x5b, 0, "Close track/session"}, + {0x5c, 0, "Read buffer capacity"}, + {0x5d, 0, "Send cue sheet"}, + {0x5e, 0, "Persistent reserve in"}, + {0x5f, 0, "Persistent reserve out"}, + {0x7e, 0, "Extended cdb (XCBD)"}, /* added in SPC-4 r12 */ + {0x80, 0, "Xdwrite extended(16)"}, /* obsolete in SBC-4 r15 */ + {0x80, PDT_TAPE, "Write filemarks(16)"}, + {0x81, 0, "Rebuild(16)"}, + {0x81, PDT_TAPE, "Read reverse(16)"}, + {0x82, 0, "Regenerate(16)"}, + {0x83, 0, "Third party copy out"}, /* Extended copy, before spc4r34 */ + /* Following was "Receive copy results", before spc4r34 */ + {0x84, 0, "Third party copy in"}, + {0x85, 0, "ATA pass-through(16)"}, /* was 0x98 in spc3 rev21c */ + {0x86, 0, "Access control in"}, + {0x87, 0, "Access control out"}, + {0x88, 0, "Read(16)"}, + {0x89, 0, "Compare and write"}, + {0x8a, 0, "Write(16)"}, + {0x8b, 0, "Orwrite(16)"}, + {0x8c, 0, "Read attribute"}, + {0x8d, 0, "Write attribute"}, + {0x8e, 0, "Write and verify(16)"}, + {0x8f, 0, "Verify(16)"}, + {0x90, 0, "Pre-fetch(16)"}, + {0x91, 0, "Synchronize cache(16)"}, + {0x91, PDT_TAPE, "Space(16)"}, + {0x92, 0, "Lock unlock cache(16)"}, + {0x92, PDT_TAPE, "Locate(16)"}, + {0x93, 0, "Write same(16)"}, + {0x93, PDT_TAPE, "Erase(16)"}, + {0x94, PDT_ZBC, "ZBC out"}, /* new sbc4r04, has service actions */ + {0x95, PDT_ZBC, "ZBC in"}, /* new sbc4r04, has service actions */ + {0x9a, 0, "Write stream(16)"}, /* added sbc4r07 */ + {0x9b, 0, "Read buffer(16)"}, /* added spc5r02 */ + {0x9c, 0, "Write atomic(16)"}, + {0x9d, 0, "Service action bidirectional"}, /* added spc4r35 */ + {0x9e, 0, "Service action in(16)"}, + {0x9f, 0, "Service action out(16)"}, + {0xa0, 0, "Report luns"}, + {0xa1, 0, "ATA pass-through(12)"}, + {0xa1, PDT_MMC, "Blank"}, + {0xa2, 0, "Security protocol in"}, + {0xa3, 0, "Maintenance in"}, + {0xa3, PDT_MMC, "Send key"}, + {0xa4, 0, "Maintenance out"}, + {0xa4, PDT_MMC, "Report key"}, + {0xa5, 0, "Move medium"}, + {0xa5, PDT_MMC, "Play audio(12)"}, + {0xa6, 0, "Exchange medium"}, + {0xa6, PDT_MMC, "Load/unload medium"}, + {0xa7, 0, "Move medium attached"}, + {0xa7, PDT_MMC, "Set read ahead"}, + {0xa8, 0, "Read(12)"}, /* SBC-3 r31 recommends Read(16) */ + {0xa9, 0, "Service action out(12)"}, + {0xaa, 0, "Write(12)"}, /* SBC-3 r31 recommends Write(16) */ + {0xab, 0, "Service action in(12)"}, + {0xac, 0, "erase(12)"}, + {0xac, PDT_MMC, "Get performance"}, + {0xad, PDT_MMC, "Read DVD/BD structure"}, + {0xae, 0, "Write and verify(12)"}, + /* SBC-3 r31 recommends Write and verify(16) */ + {0xaf, 0, "Verify(12)"}, /* SBC-3 r31 recommends Verify(16) */ + {0xb0, 0, "Search data high(12)"}, + {0xb1, 0, "Search data equal(12)"}, + {0xb1, PDT_MCHANGER, "Open/close import/export element"}, + {0xb2, 0, "Search data low(12)"}, + {0xb3, 0, "Set limits(12)"}, + {0xb4, 0, "Read element status attached"}, + {0xb5, 0, "Security protocol out"}, + {0xb5, PDT_MCHANGER, "Request volume element address"}, + {0xb6, 0, "Send volume tag"}, + {0xb6, PDT_MMC, "Set streaming"}, + {0xb7, 0, "Read defect data(12)"}, + {0xb8, 0, "Read element status"}, + {0xb9, 0, "Read CD msf"}, + {0xba, 0, "Redundancy group in"}, + {0xba, PDT_MMC, "Scan"}, + {0xbb, 0, "Redundancy group out"}, + {0xbb, PDT_MMC, "Set CD speed"}, + {0xbc, 0, "Spare in"}, + {0xbd, 0, "Spare out"}, + {0xbd, PDT_MMC, "Mechanism status"}, + {0xbe, 0, "Volume set in"}, + {0xbe, PDT_MMC, "Read CD"}, + {0xbf, 0, "Volume set out"}, + {0xbf, PDT_MMC, "Send DVD/BD structure"}, + {0xffff, 0, NULL}, +}; + +/* Read buffer(10) [0x3c] and Read buffer(16) [0x9b] service actions (sa), + * need prefix */ +struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { + {0x0, 0, "combined header and data [or multiple modes]"}, + {0x2, 0, "data"}, + {0x3, 0, "descriptor"}, + {0xa, 0, "read data from echo buffer"}, + {0xb, 0, "echo buffer descriptor"}, + {0x1a, 0, "enable expander comms protocol and echo buffer"}, + {0x1c, 0, "error history"}, + {0xffff, 0, NULL}, +}; + +/* Write buffer [0x3b] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { + {0x0, 0, "combined header and data [or multiple modes]"}, + {0x2, 0, "data"}, + {0x4, 0, "download microcode and activate"}, + {0x5, 0, "download microcode, save, and activate"}, + {0x6, 0, "download microcode with offsets and activate"}, + {0x7, 0, "download microcode with offsets, save, and activate"}, + {0xa, 0, "write data to echo buffer"}, + {0xd, 0, "download microcode with offsets, select activation events, " + "save and defer activate"}, + {0xe, 0, "download microcode with offsets, save and defer activate"}, + {0xf, 0, "activate deferred microcode"}, + {0x1a, 0, "enable expander comms protocol and echo buffer"}, + {0x1b, 0, "disable expander comms protocol"}, + {0x1c, 0, "download application client error history"}, + {0xffff, 0, NULL}, +}; + +/* Read position (SSC) [0x34] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_read_pos_arr[] = { + {0x0, PDT_TAPE, "short form - block id"}, + {0x1, PDT_TAPE, "short form - vendor specific"}, + {0x6, PDT_TAPE, "long form"}, + {0x8, PDT_TAPE, "extended form"}, + {0xffff, 0, NULL}, +}; + +/* Maintenance in [0xa3] service actions */ +struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { + {0x0, PDT_SAC, "Report assigned/unassigned p_extent"}, + {0x1, PDT_SAC, "Report component device"}, + {0x2, PDT_SAC, "Report component device attachments"}, + {0x3, PDT_SAC, "Report peripheral device"}, + {0x4, PDT_SAC, "Report peripheral device associations"}, + {0x5, 0, "Report identifying information"}, + /* was "Report device identifier" prior to spc4r07 */ + {0x6, PDT_SAC, "Report states"}, + {0x7, PDT_SAC, "Report device identification"}, + {0x8, PDT_SAC, "Report unconfigured capacity"}, + {0x9, PDT_SAC, "Report supported configuration method"}, + {0xa, 0, "Report target port groups"}, + {0xb, 0, "Report aliases"}, + {0xc, 0, "Report supported operation codes"}, + {0xd, 0, "Report supported task management functions"}, + {0xe, 0, "Report priority"}, + {0xf, 0, "Report timestamp"}, + {0x10, 0, "Management protocol in"}, + {0x1d, PDT_DISK, "Report provisioning initialization pattern"}, + /* added in sbc4r07, shares sa 0x1d with ssc5r01 (tape) */ + {0x1d, PDT_TAPE, "Receive recommended access order"}, + {0x1e, PDT_TAPE, "Read dynamic runtime attribute"}, + {0x1e, PDT_ADC, "Report automation device attributes"}, + {0x1f, 0, "Maintenance in vendor specific"}, + {0xffff, 0, NULL}, +}; + +/* Maintenance out [0xa4] service actions */ +struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { + {0x0, PDT_SAC, "Add peripheral device / component device"}, + {0x1, PDT_SAC, "Attach to component device"}, + {0x2, PDT_SAC, "Exchange p_extent"}, + {0x3, PDT_SAC, "Exchange peripheral device / component device"}, + {0x4, PDT_SAC, "Instruct component device"}, + {0x5, PDT_SAC, "Remove peripheral device / component device"}, + {0x6, 0, "Set identifying information"}, + /* was "Set device identifier" prior to spc4r07 */ + {0x7, PDT_SAC, "Break peripheral device / component device"}, + {0xa, 0, "Set target port groups"}, + {0xb, 0, "Change aliases"}, + {0xc, 0, "Remove I_T nexus"}, + {0xe, 0, "Set priority"}, + {0xf, 0, "Set timestamp"}, + {0x10, 0, "Management protocol out"}, + {0x1d, PDT_TAPE, "Generate recommended access order"}, + {0x1e, PDT_TAPE, "write dynamic runtime attribute"}, + {0x1e, PDT_ADC, "Set automation device attributes"}, + {0x1f, 0, "Maintenance out vendor specific"}, + {0xffff, 0, NULL}, +}; + +/* Sanitize [0x48] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { + {0x1, 0, "overwrite"}, + {0x2, 0, "block erase"}, + {0x3, 0, "cryptographic erase"}, + {0x1f, 0, "exit failure mode"}, + {0xffff, 0, NULL}, +}; + +/* Service action in(12) [0xab] service actions */ +struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { + {0x1, 0, "Read media serial number"}, + {0xffff, 0, NULL}, +}; + +/* Service action out(12) [0xa9] service actions */ +struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { + {0xff, 0, "Impossible command name"}, + {0xffff, 0, NULL}, +}; + +/* Service action in(16) [0x9e] service actions */ +struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { + {0xf, 0, "Receive binding report"}, /* added spc5r11 */ + {0x10, 0, "Read capacity(16)"}, + {0x11, 0, "Read long(16)"}, /* obsolete in SBC-4 r7 */ + {0x12, 0, "Get LBA status(16)"}, /* 32 byte variant added in sbc4r14 */ + {0x13, 0, "Report referrals"}, + {0x14, 0, "Stream control"}, + {0x15, 0, "Background control"}, + {0x16, 0, "Get stream status"}, + {0x17, 0, "Get physical element status"}, /* added sbc4r13 */ + {0x18, 0, "Remove element and truncate"}, /* added sbc4r13 */ + {0xffff, 0, NULL}, +}; + +/* Service action out(16) [0x9f] service actions */ +struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { + {0x0b, 0, "Test bind"}, /* added spc5r13 */ + {0x0c, 0, "Prepare bind report"}, /* added spc5r11 */ + {0x0d, 0, "Set affiliation"}, + {0x0e, 0, "Bind"}, + {0x0f, 0, "Unbind"}, + {0x11, 0, "Write long(16)"}, + {0x12, 0, "Write scattered(16)"}, /* added sbc4r11 */ + {0x14, PDT_ZBC, "Reset write pointer"}, + {0x1f, PDT_ADC, "Notify data transfer device(16)"}, + {0xffff, 0, NULL}, +}; + +/* Service action bidirectional [0x9d] service actions */ +struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { + {0xffff, 0, NULL}, +}; + +/* Persistent reserve in [0x5e] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { + {0x0, 0, "read keys"}, + {0x1, 0, "read reservation"}, + {0x2, 0, "report capabilities"}, + {0x3, 0, "read full status"}, + {0xffff, 0, NULL}, +}; + +/* Persistent reserve out [0x5f] service actions, need prefix */ +struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { + {0x0, 0, "register"}, + {0x1, 0, "reserve"}, + {0x2, 0, "release"}, + {0x3, 0, "clear"}, + {0x4, 0, "preempt"}, + {0x5, 0, "preempt and abort"}, + {0x6, 0, "register and ignore existing key"}, + {0x7, 0, "register and move"}, + {0x8, 0, "replace lost reservation"}, + {0xffff, 0, NULL}, +}; + +/* Third party copy in [0x83] service actions + * Opcode 'Receive copy results' was renamed 'Third party copy in' in spc4r34 + * LID1 is an abbreviation of List Identifier length of 1 byte. In SPC-5 + * LID1 discontinued (references back to SPC-4) and "(LID4)" suffix removed + * as there is no need to differentiate. */ +struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* originating */ + {0x0, 0, "Extended copy(LID1)"}, + {0x1, 0, "Extended copy"}, /* was 'Extended copy(LID4)' */ + {0x10, 0, "Populate token"}, + {0x11, 0, "Write using token"}, + {0x16, 1, "Set tape stream mirroring"}, /* ADC-4 and SSC-5 */ + {0x1c, 0, "Copy operation abort"}, + {0xffff, 0, NULL}, +}; + +/* Third party copy out [0x84] service actions + * Opcode 'Extended copy' was renamed 'Third party copy out' in spc4r34 + * LID4 is an abbreviation of List Identifier length of 4 bytes */ +struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* retrieve */ + {0x0, 0, "Receive copy status(LID1)"}, + {0x1, 0, "Receive copy data(LID1)"}, + {0x3, 0, "Receive copy operating parameters"}, + {0x4, 0, "Receive copy failure details(LID1)"}, + {0x5, 0, "Receive copy status"}, /* was 'Receive copy status(LID4)' */ + {0x6, 0, "Receive copy data"}, /* was 'Receive copy data(LID4)' */ + {0x7, 0, "Receive ROD token information"}, + {0x8, 0, "Report all ROD tokens"}, + {0x16, 1, "Report tape stream mirroring"}, /* SSC-5 */ + {0xffff, 0, NULL}, +}; + +/* Variable length cdb [0x7f] service actions (more than 16 bytes long) */ +struct sg_lib_value_name_t sg_lib_variable_length_arr[] = { + {0x1, 0, "Rebuild(32)"}, + {0x2, 0, "Regenerate(32)"}, + {0x3, 0, "Xdread(32)"}, /* obsolete in SBC-3 r31 */ + {0x4, 0, "Xdwrite(32)"}, /* obsolete in SBC-3 r31 */ + {0x5, 0, "Xdwrite extended(32)"}, /* obsolete in SBC-4 r15 */ + {0x6, 0, "Xpwrite(32)"}, /* obsolete in SBC-4 r15 */ + {0x7, 0, "Xdwriteread(32)"}, /* obsolete in SBC-4 r15 */ + {0x8, 0, "Xdwrite extended(64)"}, /* obsolete in SBC-4 r15 */ + {0x9, 0, "Read(32)"}, + {0xa, 0, "Verify(32)"}, + {0xb, 0, "Write(32)"}, + {0xc, 0, "Write and verify(32)"}, + {0xd, 0, "Write same(32)"}, + {0xe, 0, "Orwrite(32)"}, /* added sbc3r25 */ + {0xf, 0, "Atomic write(32)"}, /* added sbc4r02 */ + {0x10, 0, "Write stream(32)"}, /* added sbc4r07 */ + {0x11, 0, "Write scattered(32)"}, /* added sbc4r11 */ + {0x12, 0, "Get LBA status(32)"}, /* added sbc4r14 */ + {0x1800, 0, "Receive credential"}, + {0x1ff0, 0, "ATA pass-through(32)"},/* added sat4r05 */ + {0x8801, 0, "Format OSD (osd)"}, + {0x8802, 0, "Create (osd)"}, + {0x8803, 0, "List (osd)"}, + {0x8805, 0, "Read (osd)"}, + {0x8806, 0, "Write (osd)"}, + {0x8807, 0, "Append (osd)"}, + {0x8808, 0, "Flush (osd)"}, + {0x880a, 0, "Remove (osd)"}, + {0x880b, 0, "Create partition (osd)"}, + {0x880c, 0, "Remove partition (osd)"}, + {0x880e, 0, "Get attributes (osd)"}, + {0x880f, 0, "Set attributes (osd)"}, + {0x8812, 0, "Create and write (osd)"}, + {0x8815, 0, "Create collection (osd)"}, + {0x8816, 0, "Remove collection (osd)"}, + {0x8817, 0, "List collection (osd)"}, + {0x8818, 0, "Set key (osd)"}, + {0x8819, 0, "Set master key (osd)"}, + {0x881a, 0, "Flush collection (osd)"}, + {0x881b, 0, "Flush partition (osd)"}, + {0x881c, 0, "Flush OSD (osd)"}, + {0x8880, 0, "Object structure check (osd-2)"}, + {0x8881, 0, "Format OSD (osd-2)"}, + {0x8882, 0, "Create (osd-2)"}, + {0x8883, 0, "List (osd-2)"}, + {0x8884, 0, "Punch (osd-2)"}, + {0x8885, 0, "Read (osd-2)"}, + {0x8886, 0, "Write (osd-2)"}, + {0x8887, 0, "Append (osd-2)"}, + {0x8888, 0, "Flush (osd-2)"}, + {0x8889, 0, "Clear (osd-2)"}, + {0x888a, 0, "Remove (osd-2)"}, + {0x888b, 0, "Create partition (osd-2)"}, + {0x888c, 0, "Remove partition (osd-2)"}, + {0x888e, 0, "Get attributes (osd-2)"}, + {0x888f, 0, "Set attributes (osd-2)"}, + {0x8892, 0, "Create and write (osd-2)"}, + {0x8895, 0, "Create collection (osd-2)"}, + {0x8896, 0, "Remove collection (osd-2)"}, + {0x8897, 0, "List collection (osd-2)"}, + {0x8898, 0, "Set key (osd-2)"}, + {0x8899, 0, "Set master key (osd-2)"}, + {0x889a, 0, "Flush collection (osd-2)"}, + {0x889b, 0, "Flush partition (osd-2)"}, + {0x889c, 0, "Flush OSD (osd-2)"}, + {0x88a0, 0, "Query (osd-2)"}, + {0x88a1, 0, "Remove member objects (osd-2)"}, + {0x88a2, 0, "Get member attributes (osd-2)"}, + {0x88a3, 0, "Set member attributes (osd-2)"}, + {0x88b1, 0, "Read map (osd-2)"}, + {0x8f7c, 0, "Perform SCSI command (osd-2)"}, + {0x8f7d, 0, "Perform task management function (osd-2)"}, + {0x8f7e, 0, "Perform SCSI command (osd)"}, + {0x8f7f, 0, "Perform task management function (osd)"}, + {0xffff, 0, NULL}, +}; + +/* Zoning out [0x94] service actions */ +struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = { + {0x1, PDT_ZBC, "Close zone"}, + {0x2, PDT_ZBC, "Finish zone"}, + {0x3, PDT_ZBC, "Open zone"}, + {0x4, PDT_ZBC, "Reset write pointer"}, + {0x10, PDT_ZBC, "Sequentialize zone"}, /* zbc2r01b */ + {0xffff, 0, NULL}, +}; + +/* Zoning in [0x95] service actions */ +struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = { + {0x0, PDT_ZBC, "Report zones"}, + {0xffff, 0, NULL}, +}; + +/* Read attribute [0x8c] service actions */ +struct sg_lib_value_name_t sg_lib_read_attr_arr[] = { + {0x0, 0, "attribute values"}, + {0x1, 0, "attribute list"}, + {0x2, 0, "logical volume list"}, + {0x3, 0, "partition list"}, + {0x5, 0, "supported attributes"}, + {0xffff, 0, NULL}, +}; + +#else /* SG_SCSI_STRINGS */ + +struct sg_lib_value_name_t sg_lib_normal_opcodes[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_buff_arr[] = { /* opcode 0x3c */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_write_buff_arr[] = { /* opcode 0x3b */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_pos_arr[] = { /* opcode 0x34 (SSC) */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_maint_in_arr[] = { /* opcode 0xa3 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_maint_out_arr[] = { /* opcode 0xa4 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_sanitize_sa_arr[] = { /* opcode 0x94 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_in12_arr[] = { /* opcode 0xab */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_out12_arr[] = { /* opcode 0xa9 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_in16_arr[] = { /* opcode 0x9e */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_out16_arr[] = { /* opcode 0x9f */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_serv_bidi_arr[] = { /* opcode 0x9d */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_pr_in_arr[] = { /* opcode 0x5e */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_pr_out_arr[] = { /* opcode 0x5f */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_xcopy_sa_arr[] = { /* opcode 0x83 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_rec_copy_sa_arr[] = { /* opcode 0x84 */ + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_variable_length_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_zoning_out_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_zoning_in_arr[] = { + {0xffff, 0, NULL}, +}; + +struct sg_lib_value_name_t sg_lib_read_attr_arr[] = { + {0xffff, 0, NULL}, +}; + +#endif /* SG_SCSI_STRINGS */ + +/* A conveniently formatted list of SCSI ASC/ASCQ codes and their + * corresponding text can be found at: www.t10.org/lists/asc-num.txt + * The following should match asc-num.txt dated 20150423 */ + +#ifdef SG_SCSI_STRINGS +struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] = +{ + {0x40,0x01,0x7f,"Ram failure [0x%x]"}, + {0x40,0x80,0xff,"Diagnostic failure on component [0x%x]"}, + {0x41,0x01,0xff,"Data path failure [0x%x]"}, + {0x42,0x01,0xff,"Power-on or self-test failure [0x%x]"}, + {0x4d,0x00,0xff,"Tagged overlapped commands [0x%x]"}, + {0x70,0x00,0xff,"Decompression exception short algorithm id of 0x%x"}, + {0, 0, 0, NULL} +}; + +struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] = +{ + {0x00,0x00,"No additional sense information"}, + {0x00,0x01,"Filemark detected"}, + {0x00,0x02,"End-of-partition/medium detected"}, + {0x00,0x03,"Setmark detected"}, + {0x00,0x04,"Beginning-of-partition/medium detected"}, + {0x00,0x05,"End-of-data detected"}, + {0x00,0x06,"I/O process terminated"}, + {0x00,0x07,"Programmable early warning detected"}, + {0x00,0x11,"Audio play operation in progress"}, + {0x00,0x12,"Audio play operation paused"}, + {0x00,0x13,"Audio play operation successfully completed"}, + {0x00,0x14,"Audio play operation stopped due to error"}, + {0x00,0x15,"No current audio status to return"}, + {0x00,0x16,"operation in progress"}, + {0x00,0x17,"Cleaning requested"}, + {0x00,0x18,"Erase operation in progress"}, + {0x00,0x19,"Locate operation in progress"}, + {0x00,0x1a,"Rewind operation in progress"}, + {0x00,0x1b,"Set capacity operation in progress"}, + {0x00,0x1c,"Verify operation in progress"}, + {0x00,0x1d,"ATA pass through information available"}, + {0x00,0x1e,"Conflicting SA creation request"}, + {0x00,0x1f,"Logical unit transitioning to another power condition"}, + {0x00,0x20,"Extended copy information available"}, + {0x00,0x21,"Atomic command aborted due to ACA"}, + {0x00,0x22,"Deferred microcode is pending"}, + {0x01,0x00,"No index/sector signal"}, + {0x02,0x00,"No seek complete"}, + {0x03,0x00,"Peripheral device write fault"}, + {0x03,0x01,"No write current"}, + {0x03,0x02,"Excessive write errors"}, + {0x04,0x00,"Logical unit not ready, cause not reportable"}, + {0x04,0x01,"Logical unit is in process of becoming ready"}, + {0x04,0x02,"Logical unit not ready, " + "initializing command required"}, + {0x04,0x03,"Logical unit not ready, " + "manual intervention required"}, + {0x04,0x04,"Logical unit not ready, format in progress"}, + {0x04,0x05,"Logical unit not ready, rebuild in progress"}, + {0x04,0x06,"Logical unit not ready, recalculation in progress"}, + {0x04,0x07,"Logical unit not ready, operation in progress"}, + {0x04,0x08,"Logical unit not ready, long write in progress"}, + {0x04,0x09,"Logical unit not ready, self-test in progress"}, + {0x04,0x0a,"Logical unit " + "not accessible, asymmetric access state transition"}, + {0x04,0x0b,"Logical unit " + "not accessible, target port in standby state"}, + {0x04,0x0c,"Logical unit " + "not accessible, target port in unavailable state"}, + {0x04,0x0d,"Logical unit not ready, structure check required"}, + {0x04,0x0e,"Logical unit not ready, security session in progress"}, + {0x04,0x10,"Logical unit not ready, " + "auxiliary memory not accessible"}, + {0x04,0x11,"Logical unit not ready, " + "notify (enable spinup) required"}, + {0x04,0x12,"Logical unit not ready, offline"}, + {0x04,0x13,"Logical unit not ready, SA creation in progress"}, + {0x04,0x14,"Logical unit not ready, space allocation in progress"}, + {0x04,0x15,"Logical unit not ready, robotics disabled"}, + {0x04,0x16,"Logical unit not ready, configuration required"}, + {0x04,0x17,"Logical unit not ready, calibration required"}, + {0x04,0x18,"Logical unit not ready, a door is open"}, + {0x04,0x19,"Logical unit not ready, operating in sequential mode"}, + {0x04,0x1a,"Logical unit not ready, start stop unit command in progress"}, + {0x04,0x1b,"Logical unit not ready, sanitize in progress"}, + {0x04,0x1c,"Logical unit not ready, additional power use not yet " + "granted"}, + {0x04,0x1d,"Logical unit not ready, configuration in progress"}, + {0x04,0x1e,"Logical unit not ready, microcode activation required"}, + {0x04,0x1f,"Logical unit not ready, microcode download required"}, + {0x04,0x20,"Logical unit not ready, logical unit reset required"}, + {0x04,0x21,"Logical unit not ready, hard reset required"}, + {0x04,0x22,"Logical unit not ready, power cycle required"}, + {0x04,0x23,"Logical unit not ready, affiliation required"}, + {0x05,0x00,"Logical unit does not respond to selection"}, + {0x06,0x00,"No reference position found"}, + {0x07,0x00,"Multiple peripheral devices selected"}, + {0x08,0x00,"Logical unit communication failure"}, + {0x08,0x01,"Logical unit communication time-out"}, + {0x08,0x02,"Logical unit communication parity error"}, + {0x08,0x03,"Logical unit communication CRC error (Ultra-DMA/32)"}, + {0x08,0x04,"Unreachable copy target"}, + {0x09,0x00,"Track following error"}, + {0x09,0x01,"Tracking servo failure"}, + {0x09,0x02,"Focus servo failure"}, + {0x09,0x03,"Spindle servo failure"}, + {0x09,0x04,"Head select fault"}, + {0x09,0x05,"Vibration induced tracking error"}, + {0x0A,0x00,"Error log overflow"}, + {0x0B,0x00,"Warning"}, + {0x0B,0x01,"Warning - specified temperature exceeded"}, + {0x0B,0x02,"Warning - enclosure degraded"}, + {0x0B,0x03,"Warning - background self-test failed"}, + {0x0B,0x04,"Warning - background pre-scan detected medium error"}, + {0x0B,0x05,"Warning - background medium scan detected medium error"}, + {0x0B,0x06,"Warning - non-volatile cache now volatile"}, + {0x0B,0x07,"Warning - degraded power to non-volatile cache"}, + {0x0B,0x08,"Warning - power loss expected"}, + {0x0B,0x09,"Warning - device statistics notification active"}, + {0x0B,0x0A,"Warning - high critical temperature limit exceeded"}, + {0x0B,0x0B,"Warning - low critical temperature limit exceeded"}, + {0x0B,0x0C,"Warning - high operating temperature limit exceeded"}, + {0x0B,0x0D,"Warning - low operating temperature limit exceeded"}, + {0x0B,0x0E,"Warning - high critical humidity limit exceeded"}, + {0x0B,0x0F,"Warning - low critical humidity limit exceeded"}, + {0x0B,0x10,"Warning - high operating humidity limit exceeded"}, + {0x0B,0x11,"Warning - low operating humidity limit exceeded"}, + {0x0B,0x12,"Warning - microcode security at risk"}, + {0x0B,0x13,"Warning - microcode digital signature validation failure"}, + {0x0C,0x00,"Write error"}, + {0x0C,0x01,"Write error - recovered with auto reallocation"}, + {0x0C,0x02,"Write error - auto reallocation failed"}, + {0x0C,0x03,"Write error - recommend reassignment"}, + {0x0C,0x04,"Compression check miscompare error"}, + {0x0C,0x05,"Data expansion occurred during compression"}, + {0x0C,0x06,"Block not compressible"}, + {0x0C,0x07,"Write error - recovery needed"}, + {0x0C,0x08,"Write error - recovery failed"}, + {0x0C,0x09,"Write error - loss of streaming"}, + {0x0C,0x0A,"Write error - padding blocks added"}, + {0x0C,0x0B,"Auxiliary memory write error"}, + {0x0C,0x0C,"Write error - unexpected unsolicited data"}, + {0x0C,0x0D,"Write error - not enough unsolicited data"}, + {0x0C,0x0E,"Multiple write errors"}, + {0x0C,0x0F,"Defects in error window"}, + {0x0C,0x10,"Incomplete multiple atomic write operations"}, + {0x0C,0x11,"Write error - recovery scan needed"}, + {0x0C,0x12,"Write error - insufficient zone resources"}, + {0x0D,0x00,"Error detected by third party temporary initiator"}, + {0x0D,0x01,"Third party device failure"}, + {0x0D,0x02,"Copy target device not reachable"}, + {0x0D,0x03,"Incorrect copy target device type"}, + {0x0D,0x04,"Copy target device data underrun"}, + {0x0D,0x05,"Copy target device data overrun"}, + {0x0E,0x00,"Invalid information unit"}, + {0x0E,0x01,"Information unit too short"}, + {0x0E,0x02,"Information unit too long"}, + {0x0E,0x03,"Invalid field in command information unit"}, + {0x10,0x00,"Id CRC or ECC error"}, + {0x10,0x01,"Logical block guard check failed"}, + {0x10,0x02,"Logical block application tag check failed"}, + {0x10,0x03,"Logical block reference tag check failed"}, + {0x10,0x04,"Logical block protection error on recover buffered data"}, + {0x10,0x05,"Logical block protection method error"}, + {0x11,0x00,"Unrecovered read error"}, + {0x11,0x01,"Read retries exhausted"}, + {0x11,0x02,"Error too long to correct"}, + {0x11,0x03,"Multiple read errors"}, + {0x11,0x04,"Unrecovered read error - auto reallocate failed"}, + {0x11,0x05,"L-EC uncorrectable error"}, + {0x11,0x06,"CIRC unrecovered error"}, + {0x11,0x07,"Data re-synchronization error"}, + {0x11,0x08,"Incomplete block read"}, + {0x11,0x09,"No gap found"}, + {0x11,0x0A,"Miscorrected error"}, + {0x11,0x0B,"Unrecovered read error - recommend reassignment"}, + {0x11,0x0C,"Unrecovered read error - recommend rewrite the data"}, + {0x11,0x0D,"De-compression CRC error"}, + {0x11,0x0E,"Cannot decompress using declared algorithm"}, + {0x11,0x0F,"Error reading UPC/EAN number"}, + {0x11,0x10,"Error reading ISRC number"}, + {0x11,0x11,"Read error - loss of streaming"}, + {0x11,0x12,"Auxiliary memory read error"}, + {0x11,0x13,"Read error - failed retransmission request"}, + {0x11,0x14,"Read error - LBA marked bad by application client"}, + {0x11,0x15,"Write after sanitize required"}, + {0x12,0x00,"Address mark not found for id field"}, + {0x13,0x00,"Address mark not found for data field"}, + {0x14,0x00,"Recorded entity not found"}, + {0x14,0x01,"Record not found"}, + {0x14,0x02,"Filemark or setmark not found"}, + {0x14,0x03,"End-of-data not found"}, + {0x14,0x04,"Block sequence error"}, + {0x14,0x05,"Record not found - recommend reassignment"}, + {0x14,0x06,"Record not found - data auto-reallocated"}, + {0x14,0x07,"Locate operation failure"}, + {0x15,0x00,"Random positioning error"}, + {0x15,0x01,"Mechanical positioning error"}, + {0x15,0x02,"Positioning error detected by read of medium"}, + {0x16,0x00,"Data synchronization mark error"}, + {0x16,0x01,"Data sync error - data rewritten"}, + {0x16,0x02,"Data sync error - recommend rewrite"}, + {0x16,0x03,"Data sync error - data auto-reallocated"}, + {0x16,0x04,"Data sync error - recommend reassignment"}, + {0x17,0x00,"Recovered data with no error correction applied"}, + {0x17,0x01,"Recovered data with retries"}, + {0x17,0x02,"Recovered data with positive head offset"}, + {0x17,0x03,"Recovered data with negative head offset"}, + {0x17,0x04,"Recovered data with retries and/or circ applied"}, + {0x17,0x05,"Recovered data using previous sector id"}, + {0x17,0x06,"Recovered data without ECC - data auto-reallocated"}, + {0x17,0x07,"Recovered data without ECC - recommend reassignment"}, + {0x17,0x08,"Recovered data without ECC - recommend rewrite"}, + {0x17,0x09,"Recovered data without ECC - data rewritten"}, + {0x18,0x00,"Recovered data with error correction applied"}, + {0x18,0x01,"Recovered data with error corr. & retries applied"}, + {0x18,0x02,"Recovered data - data auto-reallocated"}, + {0x18,0x03,"Recovered data with CIRC"}, + {0x18,0x04,"Recovered data with L-EC"}, + {0x18,0x05,"Recovered data - recommend reassignment"}, + {0x18,0x06,"Recovered data - recommend rewrite"}, + {0x18,0x07,"Recovered data with ECC - data rewritten"}, + {0x18,0x08,"Recovered data with linking"}, + {0x19,0x00,"Defect list error"}, + {0x19,0x01,"Defect list not available"}, + {0x19,0x02,"Defect list error in primary list"}, + {0x19,0x03,"Defect list error in grown list"}, + {0x1A,0x00,"Parameter list length error"}, + {0x1B,0x00,"Synchronous data transfer error"}, + {0x1C,0x00,"Defect list not found"}, + {0x1C,0x01,"Primary defect list not found"}, + {0x1C,0x02,"Grown defect list not found"}, + {0x1D,0x00,"Miscompare during verify operation"}, + {0x1D,0x01,"Miscompare verify of unmapped lba"}, + {0x1E,0x00,"Recovered id with ECC correction"}, + {0x1F,0x00,"Partial defect list transfer"}, + {0x20,0x00,"Invalid command operation code"}, + {0x20,0x01,"Access denied - initiator pending-enrolled"}, + {0x20,0x02,"Access denied - no access rights"}, + {0x20,0x03,"Access denied - invalid mgmt id key"}, + {0x20,0x04,"Illegal command while in write capable state"}, + {0x20,0x05,"Write type operation while in read capable state (obs)"}, + {0x20,0x06,"Illegal command while in explicit address mode"}, + {0x20,0x07,"Illegal command while in implicit address mode"}, + {0x20,0x08,"Access denied - enrollment conflict"}, + {0x20,0x09,"Access denied - invalid LU identifier"}, + {0x20,0x0A,"Access denied - invalid proxy token"}, + {0x20,0x0B,"Access denied - ACL LUN conflict"}, + {0x20,0x0C,"Illegal command when not in append-only mode"}, + {0x20,0x0D,"Not an administrative logical unit"}, + {0x20,0x0E,"Not a subsidiary logical unit"}, + {0x20,0x0F,"Not a conglomerate logical unit"}, + {0x21,0x00,"Logical block address out of range"}, + {0x21,0x01,"Invalid element address"}, + {0x21,0x02,"Invalid address for write"}, + {0x21,0x03,"Invalid write crossing layer jump"}, + {0x21,0x04,"Unaligned write command"}, + {0x21,0x05,"Write boundary violation"}, + {0x21,0x06,"Attempt to read invalid data"}, + {0x21,0x07,"Read boundary violation"}, + {0x21,0x08,"Misaligned write command"}, + {0x22,0x00,"Illegal function (use 20 00, 24 00, or 26 00)"}, + {0x23,0x00,"Invalid token operation, cause not reportable"}, + {0x23,0x01,"Invalid token operation, unsupported token type"}, + {0x23,0x02,"Invalid token operation, remote token usage not supported"}, + {0x23,0x03,"invalid token operation, remote rod token creation not " + "supported"}, + {0x23,0x04,"Invalid token operation, token unknown"}, + {0x23,0x05,"Invalid token operation, token corrupt"}, + {0x23,0x06,"Invalid token operation, token revoked"}, + {0x23,0x07,"Invalid token operation, token expired"}, + {0x23,0x08,"Invalid token operation, token cancelled"}, + {0x23,0x09,"Invalid token operation, token deleted"}, + {0x23,0x0a,"Invalid token operation, invalid token length"}, + {0x24,0x00,"Invalid field in cdb"}, + {0x24,0x01,"CDB decryption error"}, + {0x24,0x02,"Invalid cdb field while in explicit block model (obs)"}, + {0x24,0x03,"Invalid cdb field while in implicit block model (obs)"}, + {0x24,0x04,"Security audit value frozen"}, + {0x24,0x05,"Security working key frozen"}, + {0x24,0x06,"Nonce not unique"}, + {0x24,0x07,"Nonce timestamp out of range"}, + {0x24,0x08,"Invalid xcdb"}, + {0x24,0x09,"Invalid fast format"}, + {0x25,0x00,"Logical unit not supported"}, + {0x26,0x00,"Invalid field in parameter list"}, + {0x26,0x01,"Parameter not supported"}, + {0x26,0x02,"Parameter value invalid"}, + {0x26,0x03,"Threshold parameters not supported"}, + {0x26,0x04,"Invalid release of persistent reservation"}, + {0x26,0x05,"Data decryption error"}, + {0x26,0x06,"Too many target descriptors"}, + {0x26,0x07,"Unsupported target descriptor type code"}, + {0x26,0x08,"Too many segment descriptors"}, + {0x26,0x09,"Unsupported segment descriptor type code"}, + {0x26,0x0A,"Unexpected inexact segment"}, + {0x26,0x0B,"Inline data length exceeded"}, + {0x26,0x0C,"Invalid operation for copy source or destination"}, + {0x26,0x0D,"Copy segment granularity violation"}, + {0x26,0x0E,"Invalid parameter while port is enabled"}, + {0x26,0x0F,"Invalid data-out buffer integrity check value"}, + {0x26,0x10,"Data decryption key fail limit reached"}, + {0x26,0x11,"Incomplete key-associated data set"}, + {0x26,0x12,"Vendor specific key reference not found"}, + {0x26,0x13,"Application tag mode page is invalid"}, + {0x26,0x14,"Tape stream mirroring prevented"}, + {0x26,0x15,"Copy source or copy destination not authorized"}, + {0x27,0x00,"Write protected"}, + {0x27,0x01,"Hardware write protected"}, + {0x27,0x02,"Logical unit software write protected"}, + {0x27,0x03,"Associated write protect"}, + {0x27,0x04,"Persistent write protect"}, + {0x27,0x05,"Permanent write protect"}, + {0x27,0x06,"Conditional write protect"}, + {0x27,0x07,"Space allocation failed write protect"}, + {0x27,0x08,"Zone is read only"}, + {0x28,0x00,"Not ready to ready change, medium may have changed"}, + {0x28,0x01,"Import or export element accessed"}, + {0x28,0x02,"Format-layer may have changed"}, + {0x28,0x03,"Import/export element accessed, medium changed"}, + {0x29,0x00,"Power on, reset, or bus device reset occurred"}, + {0x29,0x01,"Power on occurred"}, + {0x29,0x02,"SCSI bus reset occurred"}, + {0x29,0x03,"Bus device reset function occurred"}, + {0x29,0x04,"Device internal reset"}, + {0x29,0x05,"Transceiver mode changed to single-ended"}, + {0x29,0x06,"Transceiver mode changed to lvd"}, + {0x29,0x07,"I_T nexus loss occurred"}, + {0x2A,0x00,"Parameters changed"}, + {0x2A,0x01,"Mode parameters changed"}, + {0x2A,0x02,"Log parameters changed"}, + {0x2A,0x03,"Reservations preempted"}, + {0x2A,0x04,"Reservations released"}, + {0x2A,0x05,"Registrations preempted"}, + {0x2A,0x06,"Asymmetric access state changed"}, + {0x2A,0x07,"Implicit asymmetric access state transition failed"}, + {0x2A,0x08,"Priority changed"}, + {0x2A,0x09,"Capacity data has changed"}, + {0x2A,0x0c, "Error recovery attributes have changed"}, + {0x2A,0x0d, "Data encryption capabilities changed"}, + {0x2A,0x10,"Timestamp changed"}, + {0x2A,0x11,"Data encryption parameters changed by another i_t nexus"}, + {0x2A,0x12,"Data encryption parameters changed by vendor specific event"}, + {0x2A,0x13,"Data encryption key instance counter has changed"}, + {0x2A,0x0a,"Error history i_t nexus cleared"}, + {0x2A,0x0b,"Error history snapshot released"}, + {0x2A,0x14,"SA creation capabilities data has changed"}, + {0x2A,0x15,"Medium removal prevention preempted"}, + {0x2A,0x16,"Zone reset write pointer recommended"}, + {0x2B,0x00,"Copy cannot execute since host cannot disconnect"}, + {0x2C,0x00,"Command sequence error"}, + {0x2C,0x01,"Too many windows specified"}, + {0x2C,0x02,"Invalid combination of windows specified"}, + {0x2C,0x03,"Current program area is not empty"}, + {0x2C,0x04,"Current program area is empty"}, + {0x2C,0x05,"Illegal power condition request"}, + {0x2C,0x06,"Persistent prevent conflict"}, + {0x2C,0x07,"Previous busy status"}, + {0x2C,0x08,"Previous task set full status"}, + {0x2C,0x09,"Previous reservation conflict status"}, + {0x2C,0x0A,"Partition or collection contains user objects"}, + {0x2C,0x0B,"Not reserved"}, + {0x2C,0x0C,"ORWRITE generation does not match"}, + {0x2C,0x0D,"Reset write pointer not allowed"}, + {0x2C,0x0E,"Zone is offline"}, + {0x2C,0x0F,"Stream not open"}, + {0x2C,0x10,"Unwritten data in zone"}, + {0x2C,0x11,"Descriptor format sense data required"}, + {0x2D,0x00,"Overwrite error on update in place"}, + {0x2E,0x00,"Insufficient time for operation"}, + {0x2E,0x01,"Command timeout before processing"}, + {0x2E,0x02,"Command timeout during processing"}, + {0x2E,0x03,"Command timeout during processing due to error recovery"}, + {0x2F,0x00,"Commands cleared by another initiator"}, + {0x2F,0x01,"Commands cleared by power loss notification"}, + {0x2F,0x02,"Commands cleared by device server"}, + {0x2F,0x03,"Some commands cleared by queuing layer event"}, + {0x30,0x00,"Incompatible medium installed"}, + {0x30,0x01,"Cannot read medium - unknown format"}, + {0x30,0x02,"Cannot read medium - incompatible format"}, + {0x30,0x03,"Cleaning cartridge installed"}, + {0x30,0x04,"Cannot write medium - unknown format"}, + {0x30,0x05,"Cannot write medium - incompatible format"}, + {0x30,0x06,"Cannot format medium - incompatible medium"}, + {0x30,0x07,"Cleaning failure"}, + {0x30,0x08,"Cannot write - application code mismatch"}, + {0x30,0x09,"Current session not fixated for append"}, + {0x30,0x0A,"Cleaning request rejected"}, + {0x30,0x0B,"Cleaning tape expired"}, + {0x30,0x0C,"WORM medium - overwrite attempted"}, + {0x30,0x0D,"WORM medium - integrity check"}, + {0x30,0x10,"Medium not formatted"}, + {0x30,0x11,"Incompatible volume type"}, + {0x30,0x12,"Incompatible volume qualifier"}, + {0x30,0x13,"Cleaning volume expired"}, + {0x31,0x00,"Medium format corrupted"}, + {0x31,0x01,"Format command failed"}, + {0x31,0x02,"Zoned formatting failed due to spare linking"}, + {0x31,0x03,"Sanitize command failed"}, + {0x32,0x00,"No defect spare location available"}, + {0x32,0x01,"Defect list update failure"}, + {0x33,0x00,"Tape length error"}, + {0x34,0x00,"Enclosure failure"}, + {0x35,0x00,"Enclosure services failure"}, + {0x35,0x01,"Unsupported enclosure function"}, + {0x35,0x02,"Enclosure services unavailable"}, + {0x35,0x03,"Enclosure services transfer failure"}, + {0x35,0x04,"Enclosure services transfer refused"}, + {0x35,0x05,"Enclosure services checksum error"}, + {0x36,0x00,"Ribbon, ink, or toner failure"}, + {0x37,0x00,"Rounded parameter"}, + {0x38,0x00,"Event status notification"}, + {0x38,0x02,"Esn - power management class event"}, + {0x38,0x04,"Esn - media class event"}, + {0x38,0x06,"Esn - device busy class event"}, + {0x38,0x07,"Thin provisioning soft threshold reached"}, + {0x39,0x00,"Saving parameters not supported"}, + {0x3A,0x00,"Medium not present"}, + {0x3A,0x01,"Medium not present - tray closed"}, + {0x3A,0x02,"Medium not present - tray open"}, + {0x3A,0x03,"Medium not present - loadable"}, + {0x3A,0x04,"Medium not present - medium auxiliary memory accessible"}, + {0x3B,0x00,"Sequential positioning error"}, + {0x3B,0x01,"Tape position error at beginning-of-medium"}, + {0x3B,0x02,"Tape position error at end-of-medium"}, + {0x3B,0x03,"Tape or electronic vertical forms unit not ready"}, + {0x3B,0x04,"Slew failure"}, + {0x3B,0x05,"Paper jam"}, + {0x3B,0x06,"Failed to sense top-of-form"}, + {0x3B,0x07,"Failed to sense bottom-of-form"}, + {0x3B,0x08,"Reposition error"}, + {0x3B,0x09,"Read past end of medium"}, + {0x3B,0x0A,"Read past beginning of medium"}, + {0x3B,0x0B,"Position past end of medium"}, + {0x3B,0x0C,"Position past beginning of medium"}, + {0x3B,0x0D,"Medium destination element full"}, + {0x3B,0x0E,"Medium source element empty"}, + {0x3B,0x0F,"End of medium reached"}, + {0x3B,0x11,"Medium magazine not accessible"}, + {0x3B,0x12,"Medium magazine removed"}, + {0x3B,0x13,"Medium magazine inserted"}, + {0x3B,0x14,"Medium magazine locked"}, + {0x3B,0x15,"Medium magazine unlocked"}, + {0x3B,0x16,"Mechanical positioning or changer error"}, + {0x3B,0x17,"Read past end of user object"}, + {0x3B,0x18,"Element disabled"}, + {0x3B,0x19,"Element enabled"}, + {0x3B,0x1a,"Data transfer device removed"}, + {0x3B,0x1b,"Data transfer device inserted"}, + {0x3B,0x1c,"Too many logical objects on partition to support operation"}, + {0x3D,0x00,"Invalid bits in identify message"}, + {0x3E,0x00,"Logical unit has not self-configured yet"}, + {0x3E,0x01,"Logical unit failure"}, + {0x3E,0x02,"Timeout on logical unit"}, + {0x3E,0x03,"Logical unit failed self-test"}, + {0x3E,0x04,"Logical unit unable to update self-test log"}, + {0x3F,0x00,"Target operating conditions have changed"}, + {0x3F,0x01,"Microcode has been changed"}, + {0x3F,0x02,"Changed operating definition"}, + {0x3F,0x03,"Inquiry data has changed"}, + {0x3F,0x04,"Component device attached"}, + {0x3F,0x05,"Device identifier changed"}, + {0x3F,0x06,"Redundancy group created or modified"}, + {0x3F,0x07,"Redundancy group deleted"}, + {0x3F,0x08,"Spare created or modified"}, + {0x3F,0x09,"Spare deleted"}, + {0x3F,0x0A,"Volume set created or modified"}, + {0x3F,0x0B,"Volume set deleted"}, + {0x3F,0x0C,"Volume set deassigned"}, + {0x3F,0x0D,"Volume set reassigned"}, + {0x3F,0x0E,"Reported luns data has changed"}, + {0x3F,0x0F,"Echo buffer overwritten"}, + {0x3F,0x10,"Medium loadable"}, + {0x3F,0x11,"Medium auxiliary memory accessible"}, + {0x3F,0x12,"iSCSI IP address added"}, + {0x3F,0x13,"iSCSI IP address removed"}, + {0x3F,0x14,"iSCSI IP address changed"}, + {0x3F,0x15,"Inspect referrals sense descriptors"}, + {0x3F,0x16,"Microcode has been changed without reset"}, + {0x3F,0x17,"Zone transition to full"}, + {0x3F,0x18,"Bind completed"}, + {0x3F,0x19,"Bind redirected"}, + {0x3F,0x1A,"Subsidiary binding changed"}, + + /* + * ASC 0x40, 0x41 and 0x42 overridden by "additional2" array entries + * for ascq > 1. Preferred error message for this group is + * "Diagnostic failure on component nn (80h-ffh)". + */ + {0x40,0x00,"Ram failure (should use 40 nn)"}, + {0x41,0x00,"Data path failure (should use 40 nn)"}, + {0x42,0x00,"Power-on or self-test failure (should use 40 nn)"}, + + {0x43,0x00,"Message error"}, + {0x44,0x00,"Internal target failure"}, + {0x44,0x01,"Persistent reservation information lost"}, + {0x44,0x71,"ATA device failed Set Features"}, + {0x45,0x00,"Select or reselect failure"}, + {0x46,0x00,"Unsuccessful soft reset"}, + {0x47,0x00,"SCSI parity error"}, + {0x47,0x01,"Data phase CRC error detected"}, + {0x47,0x02,"SCSI parity error detected during st data phase"}, + {0x47,0x03,"Information unit iuCRC error detected"}, + {0x47,0x04,"Asynchronous information protection error detected"}, + {0x47,0x05,"Protocol service CRC error"}, + {0x47,0x06,"Phy test function in progress"}, + {0x47,0x7F,"Some commands cleared by iSCSI protocol event"}, + {0x48,0x00,"Initiator detected error message received"}, + {0x49,0x00,"Invalid message error"}, + {0x4A,0x00,"Command phase error"}, + {0x4B,0x00,"Data phase error"}, + {0x4B,0x01,"Invalid target port transfer tag received"}, + {0x4B,0x02,"Too much write data"}, + {0x4B,0x03,"Ack/nak timeout"}, + {0x4B,0x04,"Nak received"}, + {0x4B,0x05,"Data offset error"}, + {0x4B,0x06,"Initiator response timeout"}, + {0x4B,0x07,"Connection lost"}, + {0x4B,0x08,"Data-in buffer overflow - data buffer size"}, + {0x4B,0x09,"Data-in buffer overflow - data buffer descriptor area"}, + {0x4B,0x0A,"Data-in buffer error"}, + {0x4B,0x0B,"Data-out buffer overflow - data buffer size"}, + {0x4B,0x0C,"Data-out buffer overflow - data buffer descriptor area"}, + {0x4B,0x0D,"Data-out buffer error"}, + {0x4B,0x0E,"PCIe fabric error"}, + {0x4B,0x0f,"PCIe completion timeout"}, + {0x4B,0x10,"PCIe completer abort"}, + {0x4B,0x11,"PCIe poisoned tlp received"}, + {0x4B,0x12,"PCIe ecrc check failed"}, + {0x4B,0x13,"PCIe unsupported request"}, + {0x4B,0x14,"PCIe acs violation"}, + {0x4B,0x15,"PCIe tlp prefix blocked"}, + {0x4C,0x00,"Logical unit failed self-configuration"}, + /* + * ASC 0x4D overridden by an "additional2" array entry + * so there is no need to have them here. + */ + /* {0x4D,0x00,"Tagged overlapped commands (nn = queue tag)"}, */ + + {0x4E,0x00,"Overlapped commands attempted"}, + {0x50,0x00,"Write append error"}, + {0x50,0x01,"Write append position error"}, + {0x50,0x02,"Position error related to timing"}, + {0x51,0x00,"Erase failure"}, + {0x51,0x01,"Erase failure - incomplete erase operation detected"}, + {0x52,0x00,"Cartridge fault"}, + {0x53,0x00,"Media load or eject failed"}, + {0x53,0x01,"Unload tape failure"}, + {0x53,0x02,"Medium removal prevented"}, + {0x53,0x03,"Medium removal prevented by data transfer element"}, + {0x53,0x04,"Medium thread or unthread failure"}, + {0x53,0x05,"Volume identifier invalid"}, + {0x53,0x06,"Volume identifier missing"}, + {0x53,0x07,"Duplicate volume identifier"}, + {0x53,0x08,"Element status unknown"}, + {0x53,0x09,"Data transfer device error - load failed"}, + {0x53,0x0A,"Data transfer device error - unload failed"}, + {0x53,0x0B,"Data transfer device error - unload missing"}, + {0x53,0x0C,"Data transfer device error - eject failed"}, + {0x53,0x0D,"Data transfer device error - library communication failed"}, + {0x54,0x00,"SCSI to host system interface failure"}, + {0x55,0x00,"System resource failure"}, + {0x55,0x01,"System buffer full"}, + {0x55,0x02,"Insufficient reservation resources"}, + {0x55,0x03,"Insufficient resources"}, + {0x55,0x04,"Insufficient registration resources"}, + {0x55,0x05,"Insufficient access control resources"}, + {0x55,0x06,"Auxiliary memory out of space"}, + {0x55,0x07,"Quota error"}, + {0x55,0x08,"Maximum number of supplemental decryption keys exceeded"}, + {0x55,0x09,"Medium auxiliary memory not accessible"}, + {0x55,0x0a,"Data currently unavailable"}, + {0x55,0x0b,"Insufficient power for operation"}, + {0x55,0x0c,"Insufficient resources to create rod"}, + {0x55,0x0d,"Insufficient resources to create rod token"}, + {0x55,0x0e,"Insufficient zone resources"}, + {0x55,0x0f,"Insufficient zone resources to complete write"}, + {0x55,0x10,"Maximum number of streams open"}, + {0x55,0x11,"Insufficient resources to bind"}, + {0x57,0x00,"Unable to recover table-of-contents"}, + {0x58,0x00,"Generation does not exist"}, + {0x59,0x00,"Updated block read"}, + {0x5A,0x00,"Operator request or state change input"}, + {0x5A,0x01,"Operator medium removal request"}, + {0x5A,0x02,"Operator selected write protect"}, + {0x5A,0x03,"Operator selected write permit"}, + {0x5B,0x00,"Log exception"}, + {0x5B,0x01,"Threshold condition met"}, + {0x5B,0x02,"Log counter at maximum"}, + {0x5B,0x03,"Log list codes exhausted"}, + {0x5C,0x00,"Rpl status change"}, + {0x5C,0x01,"Spindles synchronized"}, + {0x5C,0x02,"Spindles not synchronized"}, + {0x5D,0x00,"Failure prediction threshold exceeded"}, + {0x5D,0x01,"Media failure prediction threshold exceeded"}, + {0x5D,0x02,"Logical unit failure prediction threshold exceeded"}, + {0x5D,0x03,"spare area exhaustion prediction threshold exceeded"}, + {0x5D,0x10,"Hardware impending failure general hard drive failure"}, + {0x5D,0x11,"Hardware impending failure drive error rate too high" }, + {0x5D,0x12,"Hardware impending failure data error rate too high" }, + {0x5D,0x13,"Hardware impending failure seek error rate too high" }, + {0x5D,0x14,"Hardware impending failure too many block reassigns"}, + {0x5D,0x15,"Hardware impending failure access times too high" }, + {0x5D,0x16,"Hardware impending failure start unit times too high" }, + {0x5D,0x17,"Hardware impending failure channel parametrics"}, + {0x5D,0x18,"Hardware impending failure controller detected"}, + {0x5D,0x19,"Hardware impending failure throughput performance"}, + {0x5D,0x1A,"Hardware impending failure seek time performance"}, + {0x5D,0x1B,"Hardware impending failure spin-up retry count"}, + {0x5D,0x1C,"Hardware impending failure drive calibration retry count"}, + {0x5D,0x1D,"Hardware impending failure power loss protection circuit"}, + {0x5D,0x20,"Controller impending failure general hard drive failure"}, + {0x5D,0x21,"Controller impending failure drive error rate too high" }, + {0x5D,0x22,"Controller impending failure data error rate too high" }, + {0x5D,0x23,"Controller impending failure seek error rate too high" }, + {0x5D,0x24,"Controller impending failure too many block reassigns"}, + {0x5D,0x25,"Controller impending failure access times too high" }, + {0x5D,0x26,"Controller impending failure start unit times too high" }, + {0x5D,0x27,"Controller impending failure channel parametrics"}, + {0x5D,0x28,"Controller impending failure controller detected"}, + {0x5D,0x29,"Controller impending failure throughput performance"}, + {0x5D,0x2A,"Controller impending failure seek time performance"}, + {0x5D,0x2B,"Controller impending failure spin-up retry count"}, + {0x5D,0x2C,"Controller impending failure drive calibration retry count"}, + {0x5D,0x30,"Data channel impending failure general hard drive failure"}, + {0x5D,0x31,"Data channel impending failure drive error rate too high" }, + {0x5D,0x32,"Data channel impending failure data error rate too high" }, + {0x5D,0x33,"Data channel impending failure seek error rate too high" }, + {0x5D,0x34,"Data channel impending failure too many block reassigns"}, + {0x5D,0x35,"Data channel impending failure access times too high" }, + {0x5D,0x36,"Data channel impending failure start unit times too high" }, + {0x5D,0x37,"Data channel impending failure channel parametrics"}, + {0x5D,0x38,"Data channel impending failure controller detected"}, + {0x5D,0x39,"Data channel impending failure throughput performance"}, + {0x5D,0x3A,"Data channel impending failure seek time performance"}, + {0x5D,0x3B,"Data channel impending failure spin-up retry count"}, + {0x5D,0x3C,"Data channel impending failure drive calibration retry count"}, + {0x5D,0x40,"Servo impending failure general hard drive failure"}, + {0x5D,0x41,"Servo impending failure drive error rate too high" }, + {0x5D,0x42,"Servo impending failure data error rate too high" }, + {0x5D,0x43,"Servo impending failure seek error rate too high" }, + {0x5D,0x44,"Servo impending failure too many block reassigns"}, + {0x5D,0x45,"Servo impending failure access times too high" }, + {0x5D,0x46,"Servo impending failure start unit times too high" }, + {0x5D,0x47,"Servo impending failure channel parametrics"}, + {0x5D,0x48,"Servo impending failure controller detected"}, + {0x5D,0x49,"Servo impending failure throughput performance"}, + {0x5D,0x4A,"Servo impending failure seek time performance"}, + {0x5D,0x4B,"Servo impending failure spin-up retry count"}, + {0x5D,0x4C,"Servo impending failure drive calibration retry count"}, + {0x5D,0x50,"Spindle impending failure general hard drive failure"}, + {0x5D,0x51,"Spindle impending failure drive error rate too high" }, + {0x5D,0x52,"Spindle impending failure data error rate too high" }, + {0x5D,0x53,"Spindle impending failure seek error rate too high" }, + {0x5D,0x54,"Spindle impending failure too many block reassigns"}, + {0x5D,0x55,"Spindle impending failure access times too high" }, + {0x5D,0x56,"Spindle impending failure start unit times too high" }, + {0x5D,0x57,"Spindle impending failure channel parametrics"}, + {0x5D,0x58,"Spindle impending failure controller detected"}, + {0x5D,0x59,"Spindle impending failure throughput performance"}, + {0x5D,0x5A,"Spindle impending failure seek time performance"}, + {0x5D,0x5B,"Spindle impending failure spin-up retry count"}, + {0x5D,0x5C,"Spindle impending failure drive calibration retry count"}, + {0x5D,0x60,"Firmware impending failure general hard drive failure"}, + {0x5D,0x61,"Firmware impending failure drive error rate too high" }, + {0x5D,0x62,"Firmware impending failure data error rate too high" }, + {0x5D,0x63,"Firmware impending failure seek error rate too high" }, + {0x5D,0x64,"Firmware impending failure too many block reassigns"}, + {0x5D,0x65,"Firmware impending failure access times too high" }, + {0x5D,0x66,"Firmware impending failure start unit times too high" }, + {0x5D,0x67,"Firmware impending failure channel parametrics"}, + {0x5D,0x68,"Firmware impending failure controller detected"}, + {0x5D,0x69,"Firmware impending failure throughput performance"}, + {0x5D,0x6A,"Firmware impending failure seek time performance"}, + {0x5D,0x6B,"Firmware impending failure spin-up retry count"}, + {0x5D,0x6C,"Firmware impending failure drive calibration retry count"}, + {0x5D,0x73,"Media impending failure endurance limit met"}, + {0x5D,0xFF,"Failure prediction threshold exceeded (false)"}, + {0x5E,0x00,"Low power condition on"}, + {0x5E,0x01,"Idle condition activated by timer"}, + {0x5E,0x02,"Standby condition activated by timer"}, + {0x5E,0x03,"Idle condition activated by command"}, + {0x5E,0x04,"Standby condition activated by command"}, + {0x5E,0x05,"Idle_b condition activated by timer"}, + {0x5E,0x06,"Idle_b condition activated by command"}, + {0x5E,0x07,"Idle_c condition activated by timer"}, + {0x5E,0x08,"Idle_c condition activated by command"}, + {0x5E,0x09,"Standby_y condition activated by timer"}, + {0x5E,0x0a,"Standby_y condition activated by command"}, + {0x5E,0x41,"Power state change to active"}, + {0x5E,0x42,"Power state change to idle"}, + {0x5E,0x43,"Power state change to standby"}, + {0x5E,0x45,"Power state change to sleep"}, + {0x5E,0x47,"Power state change to device control"}, + {0x60,0x00,"Lamp failure"}, + {0x61,0x00,"Video acquisition error"}, + {0x61,0x01,"Unable to acquire video"}, + {0x61,0x02,"Out of focus"}, + {0x62,0x00,"Scan head positioning error"}, + {0x63,0x00,"End of user area encountered on this track"}, + {0x63,0x01,"Packet does not fit in available space"}, + {0x64,0x00,"Illegal mode for this track"}, + {0x64,0x01,"Invalid packet size"}, + {0x65,0x00,"Voltage fault"}, + {0x66,0x00,"Automatic document feeder cover up"}, + {0x66,0x01,"Automatic document feeder lift up"}, + {0x66,0x02,"Document jam in automatic document feeder"}, + {0x66,0x03,"Document miss feed automatic in document feeder"}, + {0x67,0x00,"Configuration failure"}, + {0x67,0x01,"Configuration of incapable logical units failed"}, + {0x67,0x02,"Add logical unit failed"}, + {0x67,0x03,"Modification of logical unit failed"}, + {0x67,0x04,"Exchange of logical unit failed"}, + {0x67,0x05,"Remove of logical unit failed"}, + {0x67,0x06,"Attachment of logical unit failed"}, + {0x67,0x07,"Creation of logical unit failed"}, + {0x67,0x08,"Assign failure occurred"}, + {0x67,0x09,"Multiply assigned logical unit"}, + {0x67,0x0A,"Set target port groups command failed"}, + {0x67,0x0B,"ATA device feature not enabled"}, + {0x67,0x0C,"Command rejected"}, + {0x67,0x0D,"Explicit bind not allowed"}, + {0x68,0x00,"Logical unit not configured"}, + {0x68,0x01,"Subsidiary logical unit not configured"}, + {0x69,0x00,"Data loss on logical unit"}, + {0x69,0x01,"Multiple logical unit failures"}, + {0x69,0x02,"Parity/data mismatch"}, + {0x6A,0x00,"Informational, refer to log"}, + {0x6B,0x00,"State change has occurred"}, + {0x6B,0x01,"Redundancy level got better"}, + {0x6B,0x02,"Redundancy level got worse"}, + {0x6C,0x00,"Rebuild failure occurred"}, + {0x6D,0x00,"Recalculate failure occurred"}, + {0x6E,0x00,"Command to logical unit failed"}, + {0x6F,0x00,"Copy protection key exchange failure - authentication " + "failure"}, + {0x6F,0x01,"Copy protection key exchange failure - key not present"}, + {0x6F,0x02,"Copy protection key exchange failure - key not established"}, + {0x6F,0x03,"Read of scrambled sector without authentication"}, + {0x6F,0x04,"Media region code is mismatched to logical unit region"}, + {0x6F,0x05,"Drive region must be permanent/region reset count error"}, + {0x6F,0x06,"Insufficient block count for binding nonce recording"}, + {0x6F,0x07,"Conflict in binding nonce recording"}, + {0x6F,0x08,"Insufficient permission"}, + {0x6F,0x09,"Invalid drive-host pairing server"}, + {0x6F,0x0A,"Drive-host pairing suspended"}, + /* + * ASC 0x70 overridden by an "additional2" array entry + * so there is no need to have them here. + */ + /* {0x70,0x00,"Decompression exception short algorithm id of nn"}, */ + + {0x71,0x00,"Decompression exception long algorithm id"}, + {0x72,0x00,"Session fixation error"}, + {0x72,0x01,"Session fixation error writing lead-in"}, + {0x72,0x02,"Session fixation error writing lead-out"}, + {0x72,0x03,"Session fixation error - incomplete track in session"}, + {0x72,0x04,"Empty or partially written reserved track"}, + {0x72,0x05,"No more track reservations allowed"}, + {0x72,0x06,"RMZ extension is not allowed"}, + {0x72,0x07,"No more test zone extensions are allowed"}, + {0x73,0x00,"CD control error"}, + {0x73,0x01,"Power calibration area almost full"}, + {0x73,0x02,"Power calibration area is full"}, + {0x73,0x03,"Power calibration area error"}, + {0x73,0x04,"Program memory area update failure"}, + {0x73,0x05,"Program memory area is full"}, + {0x73,0x06,"RMA/PMA is almost full"}, + {0x73,0x10,"Current power calibration area almost full"}, + {0x73,0x11,"Current power calibration area is full"}, + {0x73,0x17,"RDZ is full"}, + {0x74,0x00,"Security error"}, + {0x74,0x01,"Unable to decrypt data"}, + {0x74,0x02,"Unencrypted data encountered while decrypting"}, + {0x74,0x03,"Incorrect data encryption key"}, + {0x74,0x04,"Cryptographic integrity validation failed"}, + {0x74,0x05,"Error decrypting data"}, + {0x74,0x06,"Unknown signature verification key"}, + {0x74,0x07,"Encryption parameters not useable"}, + {0x74,0x08,"Digital signature validation failure"}, + {0x74,0x09,"Encryption mode mismatch on read"}, + {0x74,0x0a,"Encrypted block not raw read enabled"}, + {0x74,0x0b,"Incorrect Encryption parameters"}, + {0x74,0x0c,"Unable to decrypt parameter list"}, + {0x74,0x0d,"Encryption algorithm disabled"}, + {0x74,0x10,"SA creation parameter value invalid"}, + {0x74,0x11,"SA creation parameter value rejected"}, + {0x74,0x12,"Invalid SA usage"}, + {0x74,0x21,"Data encryption configuration prevented"}, + {0x74,0x30,"SA creation parameter not supported"}, + {0x74,0x40,"Authentication failed"}, + {0x74,0x61,"External data encryption key manager access error"}, + {0x74,0x62,"External data encryption key manager error"}, + {0x74,0x63,"External data encryption key not found"}, + {0x74,0x64,"External data encryption request not authorized"}, + {0x74,0x6e,"External data encryption control timeout"}, + {0x74,0x6f,"External data encryption control error"}, + {0x74,0x71,"Logical unit access not authorized"}, + {0x74,0x79,"Security conflict in translated device"}, + {0, 0, NULL} +}; + +#else /* SG_SCSI_STRINGS */ + +struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] = +{ + {0, 0, 0, NULL} +}; + +struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] = +{ + {0, 0, NULL} +}; +#endif /* SG_SCSI_STRINGS */ + +const char * sg_lib_sense_key_desc[] = { + "No Sense", /* Filemark, ILI and/or EOM; progress + indication (during FORMAT); power + condition sensing (REQUEST SENSE) */ + "Recovered Error", /* The last command completed successfully + but used error correction */ + "Not Ready", /* The addressed target is not ready */ + "Medium Error", /* Data error detected on the medium */ + "Hardware Error", /* Controller or device failure */ + "Illegal Request", + "Unit Attention", /* Removable medium was changed, or + the target has been reset */ + "Data Protect", /* Access to the data is blocked */ + "Blank Check", /* Reached unexpected written or unwritten + region of the medium */ + "Vendor specific(9)", /* Vendor specific */ + "Copy Aborted", /* COPY or COMPARE was aborted */ + "Aborted Command", /* The target aborted the command */ + "Equal", /* SEARCH DATA found data equal (obsolete) */ + "Volume Overflow", /* Medium full with data to be written */ + "Miscompare", /* Source data and data on the medium + do not agree */ + "Completed" /* may occur for successful cmd (spc4r23) */ +}; + +const char * sg_lib_pdt_strs[32] = { /* should have 2**5 elements */ + /* 0 */ "disk", + "tape", + "printer", /* obsolete, spc5r01 */ + "processor", /* often SAF-TE device, copy manager */ + "write once optical disk", /* obsolete, spc5r01 */ + /* 5 */ "cd/dvd", + "scanner", /* obsolete */ + "optical memory device", + "medium changer", + "communications", /* obsolete */ + /* 0xa */ "graphics [0xa]", /* obsolete */ + "graphics [0xb]", /* obsolete */ + "storage array controller", + "enclosure services device", + "simplified direct access device", + "optical card reader/writer device", + /* 0x10 */ "bridge controller commands", + "object based storage", + "automation/driver interface", + "security manager device", /* obsolete, spc5r01 */ + "host managed zoned block", + "0x15", "0x16", "0x17", "0x18", + "0x19", "0x1a", "0x1b", "0x1c", "0x1d", + "well known logical unit", + "unknown or no device type", /* coupled with PQ=3 for not accessible + via this lu's port (try the other) */ +}; + +const char * sg_lib_transport_proto_strs[] = +{ + "Fibre Channel Protocol for SCSI (FCP-4)", + "SCSI Parallel Interface (SPI-5)", /* obsolete in spc5r01 */ + "Serial Storage Architecture SCSI-3 Protocol (SSA-S3P)", + "Serial Bus Protocol for IEEE 1394 (SBP-3)", + "SCSI RDMA Protocol (SRP)", + "Internet SCSI (iSCSI)", + "Serial Attached SCSI Protocol (SPL-4)", + "Automation/Drive Interface Transport (ADT-2)", + "AT Attachment Interface (ACS-2)", /* 0x8 */ + "USB Attached SCSI (UAS-2)", + "SCSI over PCI Express (SOP)", + "PCIe", /* added in spc5r02 */ + "Oxc", "Oxd", "Oxe", + "No specific protocol" +}; + +/* SCSI Feature Sets array. code->value, pdt->peri_dev_type (-1 for SPC) */ +struct sg_lib_value_name_t sg_lib_scsi_feature_sets[] = +{ + {SCSI_FS_SPC_DISCOVERY_2016, -1, "Discovery 2016"}, + {SCSI_FS_SBC_BASE_2010, PDT_DISK, "SBC Base 2010"}, + {SCSI_FS_SBC_BASE_2016, PDT_DISK, "SBC Base 2016"}, + {SCSI_FS_SBC_BASIC_PROV_2016, PDT_DISK, "Basic provisioning 2016"}, + {SCSI_FS_SBC_DRIVE_MAINT_2016, PDT_DISK, "Drive maintenance 2016"}, + {0x0, 0, NULL}, /* 0x0 is reserved sfs; trailing sentinel */ +}; + +#if (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) + +/* Commands sent to the NVMe Admin Queue (queue id 0) have the following + * names in the NVM Express 1.3a document dated 20171024 */ +struct sg_lib_simple_value_name_t sg_lib_nvme_admin_cmd_arr[] = +{ + {0x0, "Delete I/O Submission Queue"}, /* first mandatory command */ + {0x1, "Create I/O Submission Queue"}, + {0x2, "Get Log Page"}, + {0x4, "Delete I/O Completion Queue"}, + {0x5, "Create I/O Completion Queue"}, + {0x6, "Identify"}, + {0x8, "Abort"}, + {0x9, "Set Features"}, + {0xa, "Get Features"}, + {0xc, "Asynchronous Event Request"}, /* last mandatory command */ + {0xd, "Namespace Management"}, /* first optional command */ + {0x10, "Firmware commit"}, + {0x11, "Firmware image download"}, + {0x14, "Device Self-test"}, + {0x15, "Namespace Attachment"}, + {0x18, "Keep Alive"}, + {0x19, "Directive Send"}, + {0x1a, "Directive Receive"}, + {0x1c, "Virtualization Management"}, + {0x1d, "NVMe-MI Send"}, /* SES SEND DIAGNOSTIC cmd passes thru here */ + {0x1e, "NVMe-MI Receive"}, /* RECEIVE DIAGNOSTIC RESULTS thru here */ + {0x7c, "Doorbell Buffer Config"}, + {0x7f, "NVMe over Fabrics"}, + + /* I/O command set specific 0x80 to 0xbf */ + {0x80, "Format NVM"}, /* first NVM specific */ + {0x81, "Security Send"}, + {0x82, "Security Receive"}, + {0x84, "Sanitize"}, /* last NVM specific in 1.3a */ + /* Vendor specific 0xc0 to 0xff */ + {0xffff, NULL}, /* Sentinel */ +}; + +/* Commands sent any NVMe non-Admin Queue (queue id >0) for the NVM command + * set have the following names in the NVM Express 1.3a document dated + * 20171024 */ +struct sg_lib_simple_value_name_t sg_lib_nvme_nvm_cmd_arr[] = +{ + {0x0, "Flush"}, /* first mandatory command */ + {0x1, "Write"}, + {0x2, "Read"}, /* last mandatory command */ + {0x4, "Write Uncorrectable"}, /* first optional command */ + {0x5, "Compare"}, + {0x8, "Write Zeroes"}, + {0x9, "Dataset Management"}, + {0xd, "Reservation Register"}, + {0xe, "Reservation Report"}, + {0x11, "Reservation Acquire"}, + {0x15, "Reservation Release"}, /* last optional command in 1.3a */ + + /* Vendor specific 0x80 to 0xff */ + {0xffff, NULL}, /* Sentinel */ +}; + + +/* .value is completion queue's DW3 as follows: ((DW3 >> 17) & 0x3ff) + * .peri_dev_type is an index for the sg_lib_scsi_status_sense_arr[] + * .name is taken from NVMe 1.3a document, section 4.6.1.2.1 with less + * capitalization. + * NVMe term bits 31:17 of DW3 in the completion field as the "Status + * Field" (SF). Bit 31 is "Do not retry" (DNR) and bit 30 is "More" (M). + * Bits 29:28 are reserved, bit 27:25 are the "Status Code Type" (SCT) + * and bits 24:17 are the Status Code (SC). This table is in ascending + * order of its .value field so a binary search could be done on it. */ +struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] = +{ + /* Generic command status values, Status Code Type (SCT): 0h + * Lowest 8 bits are the Status Code (SC), in this case: + * 00h - 7Fh: Applicable to Admin Command Set, or across multiple + * command sets + * 80h - BFh: I/O Command Set Specific status codes + * c0h - FFh: I/O Vendor Specific status codes */ + {0x0, 0, "Successful completion"}, + {0x1, 1, "Invalid command opcode"}, + {0x2, 2, "Invalid field in command"}, + {0x3, 2, "Command id conflict"}, + {0x4, 3, "Data transfer error"}, + {0x5, 4, "Command aborted due to power loss notication"}, + {0x6, 5, "Internal error"}, + {0x7, 6, "Command abort requested"}, + {0x8, 6, "Command aborted due to SQ deletion"}, + {0x9, 6, "Command aborted due to failed fused command"}, + {0xa, 6, "Command aborted due to missing fused command"}, + {0xb, 7, "Invalid namespace or format"}, + {0xc, 5, "Command sequence error"}, + {0xd, 5, "Invalid SGL segment descriptor"}, + {0xe, 5, "Invalid number of SGL descriptors"}, + {0xf, 5, "Data SGL length invalid"}, + {0x10, 5, "Matadata SGL length invalid"}, + {0x11, 5, "SGL descriptor type invalid"}, + {0x12, 5, "Invalid use of controller memory buffer"}, + {0x13, 5, "PRP offset invalid"}, + {0x14, 2, "Atomic write unit exceeded"}, + {0x15, 8, "Operation denied"}, + {0x16, 5, "SGL offset invalid"}, + {0x17, 5, "Reserved [0x17]"}, + {0x18, 5, "Host identifier inconsistent format"}, + {0x19, 5, "Keep alive timeout expired"}, + {0x1a, 5, "Keep alive timeout invalid"}, + {0x1b, 6, "Command aborted due to Preempt and Abort"}, + {0x1c, 10, "Sanitize failed"}, + {0x1d, 11, "Sanitize in progress"}, + {0x1e, 5, "SGL data block granularity invalid"}, + {0x1f, 5, "Command not supported for queue in CMB"}, + + /* Generic command status values, NVM (I/O) Command Set */ + {0x80, 12, "LBA out of range"}, + {0x81, 3, "Capacity exceeded"}, + {0x82, 13, "Namespace not ready"}, + {0x83, 14, "Reservation conflict"}, + {0x84, 15, "Format in progress"}, + /* 0xc0 - 0xff: vendor specific */ + + /* Command specific status values, Status Code Type (SCT): 1h */ + {0x100, 5, "Completion queue invalid"}, + {0x101, 5, "Invalid queue identifier"}, + {0x102, 5, "Invalid queue size"}, + {0x103, 5, "Abort command limit exceeded"}, + {0x104, 5, "Reserved [0x104]"}, + {0x105, 5, "Asynchronous event request limit exceeded"}, + {0x106, 5, "Invalid firmware slot"}, + {0x107, 5, "Invalid firmware image"}, + {0x108, 5, "Invalid interrupt vector"}, + {0x109, 5, "Invalid log page"}, + {0x10a,16, "Invalid format"}, + {0x10b, 5, "Firmware activation requires conventional reset"}, + {0x10c, 5, "Invalid queue deletion"}, + {0x10d, 5, "Feature identifier not saveable"}, + {0x10e, 5, "Feature not changeable"}, + {0x10f, 5, "Feature not namespace specific"}, + {0x110, 5, "Firmware activation requires NVM subsystem reset"}, + {0x111, 5, "Firmware activation requires reset"}, + {0x112, 5, "Firmware activation requires maximum time violation"}, + {0x113, 5, "Firmware activation prohibited"}, + {0x114, 5, "Overlapping range"}, + {0x115, 5, "Namespace insufficient capacity"}, + {0x116, 5, "Namespace identifier unavailable"}, + {0x117, 5, "Reserved [0x107]"}, + {0x118, 5, "Namespace already attached"}, + {0x119, 5, "Namespace is private"}, + {0x11a, 5, "Namespace not attached"}, + {0x11b, 3, "Thin provisioning not supported"}, + {0x11c, 3, "Controller list invalid"}, + {0x11d,17, "Device self-test in progress"}, + {0x11e,18, "Boot partition write prohibited"}, + {0x11f, 5, "Invalid controller identifier"}, + {0x120, 5, "Invalid secondary controller state"}, + {0x121, 5, "Invalid number of controller resources"}, + {0x122, 5, "Invalid resource identifier"}, + + /* Command specific status values, Status Code Type (SCT): 1h + * for NVM (I/O) Command Set */ + {0x180, 2, "Conflicting attributes"}, + {0x181,19, "Invalid protection information"}, + {0x182,18, "Attempted write to read only range"}, + /* 0x1c0 - 0x1ff: vendor specific */ + + /* Media and Data Integrity error values, Status Code Type (SCT): 2h */ + {0x280,20, "Write fault"}, + {0x281,21, "Unrecovered read error"}, + {0x282,22, "End-to-end guard check error"}, + {0x283,23, "End-to-end application tag check error"}, + {0x284,24, "End-to-end reference tag check error"}, + {0x285,25, "Compare failure"}, + {0x286, 8, "Access denied"}, + {0x287,26, "Deallocated or unwritten logical block"}, + /* 0x2c0 - 0x2ff: vendor specific */ + + /* Leave this Sentinel value at end of this array */ + {0x3ff, 0, NULL}, +}; + +/* The sg_lib_nvme_cmd_status_arr[n].peri_dev_type field is an index + * to this array. It allows an NVMe status (error) value to be mapped + * to this SCSI tuple: status, sense_key, additional sense code (asc) and + * asc qualifier (ascq). For brevity SAM_STAT_CHECK_CONDITION is written + * as 0x2. */ +struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] = +{ + {SAM_STAT_GOOD, SPC_SK_NO_SENSE, 0, 0}, /* it's all good */ /* 0 */ + {SAM_STAT_CHECK_CONDITION, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x0},/* opcode */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x24, 0x0}, /* field in cdb */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x0, 0x0}, + {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0xb, 0x8}, + {0x2, SPC_SK_HARDWARE_ERROR, 0x44, 0x0}, /* internal error */ /* 5 */ + {SAM_STAT_TASK_ABORTED, SPC_SK_ABORTED_COMMAND, 0x0, 0x0}, + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x9}, /* invalid LU */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x20, 0x2}, /* access denied */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x2c, 0x0}, /* cmd sequence error */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x31, 0x3}, /* sanitize failed */ /* 10 */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x1b}, /* sanitize in progress */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x21, 0x0}, /* LBA out of range */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x0}, /* not reportable; 0x1: becoming */ + {SAM_STAT_RESERVATION_CONFLICT, 0x0, 0x0, 0x0}, + {0x2, SPC_SK_NOT_READY, 0x4, 0x4}, /* format in progress */ /* 15 */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x31, 0x1}, /* format failed */ + {0x2, SPC_SK_NOT_READY, 0x4, 0x9}, /* self-test in progress */ + {0x2, SPC_SK_DATA_PROTECT, 0x27, 0x0}, /* write prohibited */ + {0x2, SPC_SK_ILLEGAL_REQUEST, 0x10, 0x5}, /* protection info */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x3, 0x0}, /* periph dev w fault */ /* 20 */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x11, 0x0}, /* unrecoc rd */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x1}, /* PI guard */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x10, 0x2}, /* PI app tag */ + {0x2, SPC_SK_MISCOMPARE, 0x1d, 0x0}, /* during verify */ /* 25 */ + {0x2, SPC_SK_MEDIUM_ERROR, 0x21, 0x6}, /* read invalid data */ + + /* Leave this Sentinel value at end of this array */ + {0xff, 0xff, 0xff, 0xff}, +}; + +/* These are the error (or warning) exit status values and their associated + * strings. They combine utility input syntax errors, SCSI status and sense + * key categories, OS errors (e.g. ENODEV for device not found), one that + * indicates NVMe non-zero status plus listing those that a Unix OS generates + * for any executable (that fails). The convention is 0 means no error and + * that in Unix the exit status is an (unsigned) 8 bit value. */ +struct sg_value_2names_t sg_exit_str_arr[] = { + {0, "No errors", "may also convey true"}, + {1, "Syntax error", "command line options (usually)"}, + {2, "Device not ready", "type: sense key"}, + {3, "Medium or hardware error", "type: sense key (plus blank check for " + "tape)"}, + {5, "Illegal request", "type: sense key, apart from Invalid opcode"}, + {6, "Unit attention", "type: sense key"}, + {7, "Data protect", "type: sense key; write protected media?"}, + {9, "Illegal request, Invalid opcode", "type: sense key + asc,ascq"}, + {10, "Copy aborted", "type: sense key"}, + {11, "Aborted command", + "type: sense key, other than protection related (asc=0x10)"}, + {14, "Miscompare", "type: sense key"}, + {15, "File error", NULL}, + {17, "Illegal request with Info field", NULL}, + {18, "Medium or hardware error with Info", NULL}, + {20, "No sense key", "type: probably additional sense code"}, + {21, "Recovered error (warning)", "tye: sense key"}, + /* N.B. this is a warning not error */ + {22, "LBA out of range", NULL}, + {24, "Reservation conflict", "type: SCSI status"}, + {25, "Condition met", "type: SCSI status"}, /* from PRE-FETCH command */ + {26, "Busy", "type: SCSI status"}, /* could be transport issue */ + {27, "Task set full", "type: SCSI status"}, + {28, "ACA aactive", "type: SCSI status"}, + {29, "Task aborted", "type: SCSI status"}, + {31, "Contradict", "command line options contradict or select bad mode"}, + {32, "Logic error", "unexpected situation, contact author"}, + {33, "SCSI command timeout", NULL}, /* OS timed out command */ + {36, "No errors (false)", NULL}, + {40, "Aborted command, protection error", NULL}, + {41, "Aborted command, protection error with Info field", NULL}, + {47, "flock (Unix system call) error", NULL}, /* ddpt */ + {48, "NVMe command with non-zero status", NULL}, + {50, "An OS error occurred", "(errno > 46 or negative)"}, + /* OS errors (errno in Unix) from 1 to 46 mapped into this range */ + {97, "Malformed SCSI command", "trouble building command"}, + {98, "Some other sense error", "try '-v' option for more information"}, + {99, "Some other error", "possible transport of driver issue"}, + {100, "Parameter list length error", NULL}, /* these for ddpt, xcopy */ + {101, "Invalid field in parameter", NULL}, + {102, "Too many segments in parameters", NULL}, + {103, "Target underrun", NULL}, + {104, "Target overrun", NULL}, + {105, "Operation in progress", NULL}, + {106, "Insufficient resources to create ROD", NULL}, + {107, "Insufficient resources to create ROD token", NULL}, + {108, "Commands cleared by device server", NULL}, + {109, "See leave_reason for error", NULL}, /* internal error */ + /* DDPT_CAT_TOKOP_BASE: asc=0x23, ascq=110 follow */ + {110, "Invalid token operation, cause not reportable", NULL}, + {111, "Invalid token operation, unsupported token type", NULL}, + {112, "Invalid token operation, remote token usage not supported", NULL}, + {113, "Invalid token operation, remote token creation not supported", + NULL}, + {114, "Invalid token operation, token unknown", NULL}, + {115, "Invalid token operation, token corrupt", NULL}, + {116, "Invalid token operation, token revoked", NULL}, + {117, "Invalid token operation, token expired", NULL}, + {118, "Invalid token operation, token cancelled", NULL}, + {119, "Invalid token operation, token deleted", NULL}, + {120, "Invalid token operation, invalid token length", NULL}, + + /* The following error codes are generated by a Unix OS */ + {126, "Utility found but did not have execute permissions", NULL}, + {127, "Utility to be executed was not found", NULL}, + {128, "Utility stopped/aborted by signal number: 0", "signal # 0 ??"}, + /* 128 + : signal number that aborted the utility. + real time signals start at offset SIGRTMIN */ + /* OS signals from 1 to 126 mapped into this range (129 to 254) */ + {255, "Utility returned 255 or higher", "Windows error number?"}, + {0xffff, NULL, NULL}, /* end marking sentinel */ +}; + +#else /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */ + +struct sg_lib_simple_value_name_t sg_lib_nvme_admin_cmd_arr[] = +{ + + /* Vendor specific 0x80 to 0xff */ + {0xffff, NULL}, /* Sentinel */ +}; + +struct sg_lib_simple_value_name_t sg_lib_nvme_nvm_cmd_arr[] = +{ + + /* Vendor specific 0x80 to 0xff */ + {0xffff, NULL}, /* Sentinel */ +}; + +struct sg_lib_value_name_t sg_lib_nvme_cmd_status_arr[] = +{ + + /* Leave this Sentinel value at end of this array */ + {0x3ff, 0, NULL}, +}; + +struct sg_lib_4tuple_u8 sg_lib_scsi_status_sense_arr[] = +{ + + /* Leave this Sentinel value at end of this array */ + {0xff, 0xff, 0xff, 0xff}, +}; + +struct sg_value_2names_t sg_exit_str_arr[] = { + {0xffff, NULL, NULL}, /* end marking sentinel */ +}; + +#endif /* (SG_SCSI_STRINGS && HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/lib/sg_pt_common.c b/lib/sg_pt_common.c new file mode 100644 index 0000000..2a7a479 --- /dev/null +++ b/lib/sg_pt_common.c @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2009-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. + */ + +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_pt.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" +#include "sg_pr2serr.h" + +#if (HAVE_NVME && (! IGNORE_NVME)) +#include "sg_pt_nvme.h" +#endif + +static const char * scsi_pt_version_str = "3.09 20180712"; + + +const char * +scsi_pt_version() +{ + return scsi_pt_version_str; +} + +const char * +sg_pt_version() +{ + return scsi_pt_version_str; +} + + +#if (HAVE_NVME && (! IGNORE_NVME)) +/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ + +#define SAVING_PARAMS_UNSUP 0x39 +#define INVALID_FIELD_IN_CDB 0x24 +#define INVALID_FIELD_IN_PARAM_LIST 0x26 +#define PARAMETER_LIST_LENGTH_ERR 0x1a + +static const char * nvme_scsi_vendor_str = "NVMe "; + + +#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */ +#define F_SA_HIGH 0x100 /* as used by variable length cdbs */ +#define FF_SA (F_SA_HIGH | F_SA_LOW) +#define F_INV_OP 0x200 + +/* Table of SCSI operation code (opcodes) supported by SNTL */ +struct sg_opcode_info_t sg_opcode_info_arr[] = +{ + {0x0, 0, 0, {6, /* TEST UNIT READY */ + 0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x3, 0, 0, {6, /* REQUEST SENSE */ + 0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x12, 0, 0, {6, /* INQUIRY */ + 0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x1c, 0, 0, {6, /* RECEIVE DIAGNOSTIC RESULTS */ + 0x1, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x1d, 0, 0, {6, /* SEND DIAGNOSTIC */ + 0xf7, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, + {0x55, 0, 0, {10, /* MODE SELECT(10) */ + 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0} }, + {0x5a, 0, 0, {10, /* MODE SENSE(10) */ + 0x18, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0} }, + {0xa0, 0, 0, {12, /* REPORT LUNS */ + 0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} }, + {0xa3, 0xc, F_SA_LOW, {12, /* REPORT SUPPORTED OPERATION CODES */ + 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, + 0} }, + {0xa3, 0xd, F_SA_LOW, {12, /* REPORT SUPPORTED TASK MAN. FUNCTIONS */ + 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, 0, 0} }, + + {0xff, 0xffff, 0xffff, {0, /* Sentinel, keep as last element */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, +}; + + + +/* Given the NVMe Identify controller response and optionally the NVMe + * Identify namespace response (NULL otherwise), generate the SCSI VPD + * page 0x83 (device identification) descriptor(s) in dop. Return the + * number of bytes written which will not exceed max_do_len. Probably use + * Peripheral Device Type (pdt) of 0 (disk) for don't know. Transport + * protocol (tproto) should be -1 if not known, else SCSI value. + * N.B. Does not write total VPD page length into dop[2:3] . */ +int +sg_make_vpd_devid_for_nvme(const uint8_t * nvme_id_ctl_p, + const uint8_t * nvme_id_ns_p, int pdt, + int tproto, uint8_t * dop, int max_do_len) +{ + bool have_nguid, have_eui64; + int k, n; + char b[4]; + + if ((NULL == nvme_id_ctl_p) || (NULL == dop) || (max_do_len < 56)) + return 0; + + memset(dop, 0, max_do_len); + dop[0] = 0x1f & pdt; /* (PQ=0)<<5 | (PDT=pdt); 0 or 0xd (SES) */ + dop[1] = 0x83; /* Device Identification VPD page number */ + /* Build a T10 Vendor ID based designator (desig_id=1) for controller */ + if (tproto >= 0) { + dop[4] = ((0xf & tproto) << 4) | 0x2; + dop[5] = 0xa1; /* PIV=1, ASSOC=2 (target device), desig_id=1 */ + } else { + dop[4] = 0x2; /* Prococol id=0, code_set=2 (ASCII) */ + dop[5] = 0x21; /* PIV=0, ASSOC=2 (target device), desig_id=1 */ + } + memcpy(dop + 8, nvme_scsi_vendor_str, 8); /* N.B. this is "NVMe " */ + memcpy(dop + 16, nvme_id_ctl_p + 24, 40); /* MN */ + for (k = 40; k > 0; --k) { + if (' ' == dop[15 + k]) + dop[15 + k] = '_'; /* convert trailing spaces */ + else + break; + } + if (40 == k) + --k; + n = 16 + 1 + k; + if (max_do_len < (n + 20)) + return 0; + memcpy(dop + n, nvme_id_ctl_p + 4, 20); /* SN */ + for (k = 20; k > 0; --k) { /* trim trailing spaces */ + if (' ' == dop[n + k - 1]) + dop[n + k - 1] = '\0'; + else + break; + } + n += k; + if (0 != (n % 4)) + n = ((n / 4) + 1) * 4; /* round up to next modulo 4 */ + dop[7] = n - 8; + if (NULL == nvme_id_ns_p) + return n; + + /* Look for NGUID (16 byte identifier) or EUI64 (8 byte) fields in + * NVME Identify for namespace. If found form a EUI and a SCSI string + * descriptor for non-zero NGUID or EUI64 (prefer NGUID if both). */ + have_nguid = ! sg_all_zeros(nvme_id_ns_p + 104, 16); + have_eui64 = ! sg_all_zeros(nvme_id_ns_p + 120, 8); + if ((! have_nguid) && (! have_eui64)) + return n; + if (have_nguid) { + if (max_do_len < (n + 20)) + return n; + dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */ + dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */ + dop[n + 3] = 16; + memcpy(dop + n + 4, nvme_id_ns_p + 104, 16); + n += 20; + if (max_do_len < (n + 40)) + return n; + dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */ + dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */ + dop[n + 3] = 36; + memcpy(dop + n + 4, "eui.", 4); + for (k = 0; k < 16; ++k) { + snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[104 + k]); + memcpy(dop + n + 8 + (2 * k), b, 2); + } + return n + 40; + } else { /* have_eui64 is true, 8 byte identifier */ + if (max_do_len < (n + 12)) + return n; + dop[n + 0] = 0x1; /* Prococol id=0, code_set=1 (binary) */ + dop[n + 1] = 0x02; /* PIV=0, ASSOC=0 (lu), desig_id=2 (eui) */ + dop[n + 3] = 8; + memcpy(dop + n + 4, nvme_id_ns_p + 120, 8); + n += 12; + if (max_do_len < (n + 24)) + return n; + dop[n + 0] = 0x3; /* Prococol id=0, code_set=3 (utf8) */ + dop[n + 1] = 0x08; /* PIV=0, ASSOC=0 (lu), desig_id=8 (scsi string) */ + dop[n + 3] = 20; + memcpy(dop + n + 4, "eui.", 4); + for (k = 0; k < 8; ++k) { + snprintf(b, sizeof(b), "%02X", nvme_id_ns_p[120 + k]); + memcpy(dop + n + 8 + (2 * k), b, 2); + } + return n + 24; + } +} + +/* Disconnect-Reconnect page for mode_sense */ +static int +resp_disconnect_pg(uint8_t * p, int pcontrol) +{ + uint8_t disconnect_pg[] = {0x2, 0xe, 128, 128, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + + memcpy(p, disconnect_pg, sizeof(disconnect_pg)); + if (1 == pcontrol) + memset(p + 2, 0, sizeof(disconnect_pg) - 2); + return sizeof(disconnect_pg); +} + +static uint8_t ctrl_m_pg[] = {0xa, 10, 2, 0, 0, 0, 0, 0, + 0, 0, 0x2, 0x4b}; + +/* Control mode page for mode_sense */ +static int +resp_ctrl_m_pg(uint8_t *p, int pcontrol) +{ + uint8_t ch_ctrl_m_pg[] = {/* 0xa, 10, */ 0x6, 0, 0, 0, 0, 0, + 0, 0, 0, 0}; + uint8_t d_ctrl_m_pg[] = {0xa, 10, 2, 0, 0, 0, 0, 0, + 0, 0, 0x2, 0x4b}; + + memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg)); + if (1 == pcontrol) + memcpy(p + 2, ch_ctrl_m_pg, sizeof(ch_ctrl_m_pg)); + else if (2 == pcontrol) + memcpy(p, d_ctrl_m_pg, sizeof(d_ctrl_m_pg)); + return sizeof(ctrl_m_pg); +} + +static uint8_t ctrl_ext_m_pg[] = {0x4a, 0x1, 0, 0x1c, 0, 0, 0x40, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; + +/* Control Extension mode page [0xa,0x1] for mode_sense */ +static int +resp_ctrl_ext_m_pg(uint8_t *p, int pcontrol) +{ + uint8_t ch_ctrl_ext_m_pg[] = {/* 0x4a, 0x1, 0, 0x1c, */ 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; + uint8_t d_ctrl_ext_m_pg[] = {0x4a, 0x1, 0, 0x1c, 0, 0, 0x40, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, }; + + memcpy(p, ctrl_ext_m_pg, sizeof(ctrl_ext_m_pg)); + if (1 == pcontrol) + memcpy(p + 4, ch_ctrl_ext_m_pg, sizeof(ch_ctrl_ext_m_pg)); + else if (2 == pcontrol) + memcpy(p, d_ctrl_ext_m_pg, sizeof(d_ctrl_ext_m_pg)); + return sizeof(ctrl_ext_m_pg); +} + +static uint8_t iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, 0, 0, 0x0, 0x0}; + +/* Informational Exceptions control mode page for mode_sense */ +static int +resp_iec_m_pg(uint8_t *p, int pcontrol) +{ + uint8_t ch_iec_m_pg[] = {/* 0x1c, 0xa, */ 0x4, 0xf, 0, 0, 0, 0, 0, 0, + 0x0, 0x0}; + uint8_t d_iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, 0, 0, 0x0, 0x0}; + + memcpy(p, iec_m_pg, sizeof(iec_m_pg)); + if (1 == pcontrol) + memcpy(p + 2, ch_iec_m_pg, sizeof(ch_iec_m_pg)); + else if (2 == pcontrol) + memcpy(p, d_iec_m_pg, sizeof(d_iec_m_pg)); + return sizeof(iec_m_pg); +} + +static uint8_t vs_ua_m_pg[] = {0x0, 0xe, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0}; + +/* Vendor specific Unit Attention mode page for mode_sense */ +static int +resp_vs_ua_m_pg(uint8_t *p, int pcontrol) +{ + uint8_t ch_vs_ua_m_pg[] = {/* 0x0, 0xe, */ 0xff, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t d_vs_ua_m_pg[] = {0x0, 0xe, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + + memcpy(p, vs_ua_m_pg, sizeof(vs_ua_m_pg)); + if (1 == pcontrol) + memcpy(p + 2, ch_vs_ua_m_pg, sizeof(ch_vs_ua_m_pg)); + else if (2 == pcontrol) + memcpy(p, d_vs_ua_m_pg, sizeof(d_vs_ua_m_pg)); + return sizeof(vs_ua_m_pg); +} + +void +sntl_init_dev_stat(struct sg_sntl_dev_state_t * dsp) +{ + if (dsp) { + dsp->scsi_dsense = !! (0x4 & ctrl_m_pg[2]); + dsp->enclosure_override = vs_ua_m_pg[2]; + } +} + + +#define SDEBUG_MAX_MSENSE_SZ 256 + +/* Only support MODE SENSE(10). Returns the number of bytes written to dip, + * or -1 if error info placed in resp. */ +int +sntl_resp_mode_sense10(const struct sg_sntl_dev_state_t * dsp, + const uint8_t * cdbp, uint8_t * dip, int mx_di_len, + struct sg_sntl_result_t * resp) +{ + bool dbd, llbaa, is_disk, bad_pcode; + int pcontrol, pcode, subpcode, bd_len, alloc_len, offset, len; + const uint32_t num_blocks = 0x100000; /* made up */ + const uint32_t lb_size = 512; /* guess */ + uint8_t dev_spec; + uint8_t * ap; + uint8_t arr[SDEBUG_MAX_MSENSE_SZ]; + + memset(resp, 0, sizeof(*resp)); + dbd = !! (cdbp[1] & 0x8); /* disable block descriptors */ + pcontrol = (cdbp[2] & 0xc0) >> 6; + pcode = cdbp[2] & 0x3f; + subpcode = cdbp[3]; + llbaa = !!(cdbp[1] & 0x10); + is_disk = (dsp->pdt == PDT_DISK); + if (is_disk && !dbd) + bd_len = llbaa ? 16 : 8; + else + bd_len = 0; + alloc_len = sg_get_unaligned_be16(cdbp + 7); + memset(arr, 0, SDEBUG_MAX_MSENSE_SZ); + if (0x3 == pcontrol) { /* Saving values not supported */ + resp->asc = SAVING_PARAMS_UNSUP; + goto err_out; + } + /* for disks set DPOFUA bit and clear write protect (WP) bit */ + if (is_disk) + dev_spec = 0x10; /* =0x90 if WP=1 implies read-only */ + else + dev_spec = 0x0; + arr[3] = dev_spec; + if (16 == bd_len) + arr[4] = 0x1; /* set LONGLBA bit */ + arr[7] = bd_len; /* assume 255 or less */ + offset = 8; + ap = arr + offset; + + if (8 == bd_len) { + sg_put_unaligned_be32(num_blocks, ap + 0); + sg_put_unaligned_be16((uint16_t)lb_size, ap + 6); + offset += bd_len; + ap = arr + offset; + } else if (16 == bd_len) { + sg_put_unaligned_be64(num_blocks, ap + 0); + sg_put_unaligned_be32(lb_size, ap + 12); + offset += bd_len; + ap = arr + offset; + } + bad_pcode = false; + + switch (pcode) { + case 0x2: /* Disconnect-Reconnect page, all devices */ + if (0x0 == subpcode) + len = resp_disconnect_pg(ap, pcontrol); + else { + len = 0; + bad_pcode = true; + } + offset += len; + break; + case 0xa: /* Control Mode page, all devices */ + if (0x0 == subpcode) + len = resp_ctrl_m_pg(ap, pcontrol); + else if (0x1 == subpcode) + len = resp_ctrl_ext_m_pg(ap, pcontrol); + else { + len = 0; + bad_pcode = true; + } + offset += len; + break; + case 0x1c: /* Informational Exceptions Mode page, all devices */ + if (0x0 == subpcode) + len = resp_iec_m_pg(ap, pcontrol); + else { + len = 0; + bad_pcode = true; + } + offset += len; + break; + case 0x3f: /* Read all Mode pages */ + if ((0 == subpcode) || (0xff == subpcode)) { + len = 0; + len = resp_disconnect_pg(ap + len, pcontrol); + len += resp_ctrl_m_pg(ap + len, pcontrol); + if (0xff == subpcode) + len += resp_ctrl_ext_m_pg(ap + len, pcontrol); + len += resp_iec_m_pg(ap + len, pcontrol); + len += resp_vs_ua_m_pg(ap + len, pcontrol); + offset += len; + } else { + resp->asc = INVALID_FIELD_IN_CDB; + resp->in_byte = 3; + resp->in_bit = 255; + goto err_out; + } + break; + case 0x0: /* Vendor specific "Unit Attention" mode page */ + /* all sub-page codes ?? */ + len = resp_vs_ua_m_pg(ap, pcontrol); + offset += len; + break; /* vendor is "NVMe " (from INQUIRY field) */ + default: + bad_pcode = true; + break; + } + if (bad_pcode) { + resp->asc = INVALID_FIELD_IN_CDB; + resp->in_byte = 2; + resp->in_bit = 5; + goto err_out; + } + sg_put_unaligned_be16(offset - 2, arr + 0); + len = (alloc_len < offset) ? alloc_len : offset; + len = (len < mx_di_len) ? len : mx_di_len; + memcpy(dip, arr, len); + return len; + +err_out: + resp->sstatus = SAM_STAT_CHECK_CONDITION; + resp->sk = SPC_SK_ILLEGAL_REQUEST; + return -1; +} + +#define SDEBUG_MAX_MSELECT_SZ 512 + +/* Only support MODE SELECT(10). Returns number of bytes used from dop, + * else -1 on error with sense code placed in resp. */ +int +sntl_resp_mode_select10(struct sg_sntl_dev_state_t * dsp, + const uint8_t * cdbp, const uint8_t * dop, int do_len, + struct sg_sntl_result_t * resp) +{ + int pf, sp, ps, md_len, bd_len, off, spf, pg_len, rlen, param_len, mpage; + int sub_mpage; + uint8_t arr[SDEBUG_MAX_MSELECT_SZ]; + + memset(resp, 0, sizeof(*resp)); + memset(arr, 0, sizeof(arr)); + pf = cdbp[1] & 0x10; + sp = cdbp[1] & 0x1; + param_len = sg_get_unaligned_be16(cdbp + 7); + if ((0 == pf) || sp || (param_len > SDEBUG_MAX_MSELECT_SZ)) { + resp->asc = INVALID_FIELD_IN_CDB; + resp->in_byte = 1; + if (sp) + resp->in_bit = 0; + else if (0 == pf) + resp->in_bit = 4; + else { + resp->in_byte = 7; + resp->in_bit = 255; + } + goto err_out; + } + rlen = (do_len < param_len) ? do_len : param_len; + memcpy(arr, dop, rlen); + md_len = sg_get_unaligned_be16(arr + 0) + 2; + bd_len = sg_get_unaligned_be16(arr + 6); + if (md_len > 2) { + resp->asc = INVALID_FIELD_IN_PARAM_LIST; + resp->in_byte = 0; + resp->in_bit = 255; + goto err_out; + } + off = bd_len + 8; + mpage = arr[off] & 0x3f; + ps = !!(arr[off] & 0x80); + if (ps) { + resp->asc = INVALID_FIELD_IN_PARAM_LIST; + resp->in_byte = off; + resp->in_bit = 7; + goto err_out; + } + spf = !!(arr[off] & 0x40); + pg_len = spf ? (sg_get_unaligned_be16(arr + off + 2) + 4) : + (arr[off + 1] + 2); + sub_mpage = spf ? arr[off + 1] : 0; + if ((pg_len + off) > param_len) { + resp->asc = PARAMETER_LIST_LENGTH_ERR; + goto err_out; + } + switch (mpage) { + case 0xa: /* Control Mode page */ + if (0x0 == sub_mpage) { + if (ctrl_m_pg[1] == arr[off + 1]) { + memcpy(ctrl_m_pg + 2, arr + off + 2, + sizeof(ctrl_m_pg) - 2); + dsp->scsi_dsense = !!(ctrl_m_pg[2] & 0x4); + break; + } + } + goto def_case; + case 0x1c: /* Informational Exceptions Mode page (SBC) */ + if (0x0 == sub_mpage) { + if (iec_m_pg[1] == arr[off + 1]) { + memcpy(iec_m_pg + 2, arr + off + 2, + sizeof(iec_m_pg) - 2); + break; + } + } + goto def_case; + case 0x0: /* Vendor specific "Unit Attention" mode page */ + if (vs_ua_m_pg[1] == arr[off + 1]) { + memcpy(vs_ua_m_pg + 2, arr + off + 2, + sizeof(vs_ua_m_pg) - 2); + dsp->enclosure_override = vs_ua_m_pg[2]; + } + break; + default: +def_case: + resp->asc = INVALID_FIELD_IN_PARAM_LIST; + resp->in_byte = off; + resp->in_bit = 5; + goto err_out; + } + return rlen; + +err_out: + resp->sk = SPC_SK_ILLEGAL_REQUEST; + resp->sstatus = SAM_STAT_CHECK_CONDITION; + return -1; +} + +#endif /* (HAVE_NVME && (! IGNORE_NVME)) [near line 140] */ diff --git a/lib/sg_pt_freebsd.c b/lib/sg_pt_freebsd.c new file mode 100644 index 0000000..b24d9e6 --- /dev/null +++ b/lib/sg_pt_freebsd.c @@ -0,0 +1,2202 @@ +/* + * 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. + */ + +/* sg_pt_freebsd version 1.32 20180627 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for basename */ +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include /* from PRIx macros */ +#include +#include +#include +// #include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_pt.h" +#include "sg_lib.h" +#include "sg_unaligned.h" +#include "sg_pt_nvme.h" +#include "sg_pr2serr.h" + +#if (HAVE_NVME && (! IGNORE_NVME)) +#include "freebsd_nvme_ioctl.h" +#else +#define NVME_CTRLR_PREFIX "/dev/nvme" +#define NVME_NS_PREFIX "ns" +#endif + + +#define FREEBSD_MAXDEV 64 +#define FREEBSD_FDOFFSET 16; + + +struct freebsd_dev_channel { + int unitnum; // the SCSI unit number + bool is_nvme; /* OS device type, if false ignore nvme_direct */ + bool nvme_direct; /* false: our SNTL; true: received NVMe command */ + bool is_char; + uint32_t nsid; + uint32_t nv_ctrlid; + int dev_fd; // for NVMe, use -1 to indicate not provided + uint32_t nvme_result; // cdw0 from completion + uint16_t nvme_status; // from completion: ((sct << 8) | sc) + char* devname; // the device name + struct cam_device* cam_dev; + uint8_t * nvme_id_ctlp; + uint8_t * free_nvme_id_ctlp; + uint8_t cq_dw0_3[16]; + struct sg_sntl_dev_state_t dev_stat; // owner +}; + +// Private table of open devices: guaranteed zero on startup since +// part of static data. +static struct freebsd_dev_channel *devicetable[FREEBSD_MAXDEV]; + +#define DEF_TIMEOUT 60000 /* 60,000 milliseconds (60 seconds) */ + +struct sg_pt_freebsd_scsi { + struct cam_device* cam_dev; // copy held for error processing + union ccb *ccb; + uint8_t * cdb; + int cdb_len; + uint8_t * sense; + int sense_len; + uint8_t * dxferp; + int dxfer_len; + int dxfer_dir; + uint8_t * dxferip; + uint8_t * dxferop; + uint8_t * mdxferp; + uint32_t dxfer_ilen; + uint32_t dxfer_olen; + uint32_t mdxfer_len; + bool mdxfer_out; + int timeout_ms; + int scsi_status; + int resid; + int sense_resid; + int in_err; + int os_err; + int transport_err; + int dev_han; // should be >= FREEBSD_FDOFFSET then + // (dev_han - FREEBSD_FDOFFSET) is the + // index into devicetable[] + bool is_nvme; // copy of same field in fdc object + bool nvme_direct; // copy of same field in fdc object + struct sg_sntl_dev_state_t * dev_statp; // points to associated fdc +}; + +struct sg_pt_base { + struct sg_pt_freebsd_scsi impl; +}; + +static const uint32_t broadcast_nsid = SG_NVME_BROADCAST_NSID; + +static int sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int vb); + + + +static struct freebsd_dev_channel * +get_fdc_p(struct sg_pt_freebsd_scsi * ptp) +{ + int han = ptp->dev_han - FREEBSD_FDOFFSET; + + if ((han < 0) || (han >= FREEBSD_MAXDEV)) + return NULL; + return devicetable[han]; +} + +static const struct freebsd_dev_channel * +get_fdc_cp(const struct sg_pt_freebsd_scsi * ptp) +{ + int han = ptp->dev_han - FREEBSD_FDOFFSET; + + if ((han < 0) || (han >= FREEBSD_MAXDEV)) + return NULL; + return devicetable[han]; +} + +/* Returns >= 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_open_device(const char * device_name, bool read_only, int vb) +{ + int oflags = 0 /* O_NONBLOCK*/ ; + + oflags |= (read_only ? O_RDONLY : O_RDWR); + return scsi_pt_open_flags(device_name, oflags, vb); +} + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. The 'oflags' is only used on NVMe devices. It is ignored on + * SCSI and ATA devices in FreeBSD. + * Returns >= 0 if successful, otherwise returns negated errno. */ +int +scsi_pt_open_flags(const char * device_name, int oflags, int vb) +{ + bool is_char, is_block, possible_nvme; + char tmp; + int k, err, dev_fd, ret; + uint32_t nsid, nv_ctrlid; + ssize_t s; + struct freebsd_dev_channel *fdc_p = NULL; + struct cam_device* cam_dev; + struct stat a_stat; + char b[PATH_MAX]; + char full_path[64]; + + // Search table for a free entry + for (k = 0; k < FREEBSD_MAXDEV; k++) + if (! devicetable[k]) + break; + + // If no free entry found, return error. We have max allowed number + // of "file descriptors" already allocated. + if (k == FREEBSD_MAXDEV) { + if (vb) + pr2ws("too many open file descriptors (%d)\n", FREEBSD_MAXDEV); + ret = -EMFILE; + goto err_out; + } + if (stat(device_name, &a_stat) < 0) { + err = errno; + pr2ws("%s: unable to stat(%s): %s\n", __func__, device_name, + strerror(err)); + ret = -err; + goto err_out; + } + is_block = S_ISBLK(a_stat.st_mode); + is_char = S_ISCHR(a_stat.st_mode); + if (! (is_block || is_char)) { + if (vb) + pr2ws("%s: %s is not char nor block device\n", __func__, + device_name); + ret = -ENODEV; + goto err_out; + } + s = readlink(device_name, b, sizeof(b)); + if (s <= 0) { + strncpy(b, device_name, PATH_MAX - 1); + b[PATH_MAX - 1] = '\0'; + } + + /* Some code borrowed from smartmontools, Christian Franke */ + nsid = broadcast_nsid; + nv_ctrlid = broadcast_nsid; + possible_nvme = false; + while (true) { /* dummy loop, so can 'break' out */ + if(sscanf(b, NVME_CTRLR_PREFIX "%u%c", &nv_ctrlid, &tmp) == 1) { + if(nv_ctrlid == broadcast_nsid) + break; + } else if (sscanf(b, NVME_CTRLR_PREFIX "%d" NVME_NS_PREFIX "%d%c", + &nv_ctrlid, &nsid, &tmp) == 2) { + if((nv_ctrlid == broadcast_nsid) || (nsid == broadcast_nsid)) + break; + } else + break; + possible_nvme = true; + break; + } + + fdc_p = (struct freebsd_dev_channel *) + calloc(1,sizeof(struct freebsd_dev_channel)); + if (fdc_p == NULL) { + // errno already set by call to calloc() + ret = -ENOMEM; + goto err_out; + } + fdc_p->dev_fd = -1; +#if (HAVE_NVME && (! IGNORE_NVME)) + sntl_init_dev_stat(&fdc_p->dev_stat); +#endif + if (! (fdc_p->devname = (char *)calloc(1, DEV_IDLEN+1))) { + ret = -ENOMEM; + goto err_out; + } + + if (possible_nvme) { + // we should always open controller, not namespace device + snprintf(fdc_p->devname, DEV_IDLEN, NVME_CTRLR_PREFIX"%d", + nv_ctrlid); + dev_fd = open(fdc_p->devname, oflags); + if (dev_fd < 0) { + err = errno; + if (vb) + pr2ws("%s: open(%s) failed: %s (errno=%d), try SCSI/ATA\n", + __func__, full_path, strerror(err), err); + goto scsi_ata_try; + } + fdc_p->is_nvme = true; + fdc_p->nvme_direct = false; + fdc_p->is_char = is_char; + fdc_p->nsid = (broadcast_nsid == nsid) ? 0 : nsid; + fdc_p->nv_ctrlid = nv_ctrlid; + fdc_p->dev_fd = dev_fd; + devicetable[k] = fdc_p; + return k + FREEBSD_FDOFFSET; + } + +scsi_ata_try: + fdc_p->is_char = is_char; + if (cam_get_device(device_name, fdc_p->devname, DEV_IDLEN, + &(fdc_p->unitnum)) == -1) { + if (vb) + pr2ws("bad device name structure\n"); + errno = EINVAL; + ret = -errno; + goto err_out; + } + if (vb > 4) + pr2ws("%s: cam_get_device, f->devname: %s, f->unitnum=%d\n", __func__, + fdc_p->devname, fdc_p->unitnum); + + if (! (cam_dev = cam_open_spec_device(fdc_p->devname, + fdc_p->unitnum, O_RDWR, NULL))) { + if (vb) + pr2ws("cam_open_spec_device: %s\n", cam_errbuf); + errno = EPERM; /* permissions or not CAM device (NVMe ?) */ + ret = -errno; + goto err_out; + } + fdc_p->cam_dev = cam_dev; + // return pointer to "file descriptor" table entry, properly offset. + devicetable[k] = fdc_p; + return k + FREEBSD_FDOFFSET; + +err_out: /* ret should be negative value (negated errno) */ + if (fdc_p) { + if (fdc_p->devname) + free(fdc_p->devname); + free(fdc_p); + fdc_p = NULL; + } + return ret; +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_close_device(int device_han) +{ + struct freebsd_dev_channel *fdc_p; + int han = device_han - FREEBSD_FDOFFSET; + + if ((han < 0) || (han >= FREEBSD_MAXDEV)) { + errno = ENODEV; + return -errno; + } + fdc_p = devicetable[han]; + if (NULL == fdc_p) { + errno = ENODEV; + return -errno; + } + if (fdc_p->devname) + free(fdc_p->devname); + if (fdc_p->cam_dev) + cam_close_device(fdc_p->cam_dev); + if (fdc_p->is_nvme) { + if (fdc_p->dev_fd >= 0) + close(fdc_p->dev_fd); + if (fdc_p->free_nvme_id_ctlp) { + free(fdc_p->free_nvme_id_ctlp); + fdc_p->nvme_id_ctlp = NULL; + fdc_p->free_nvme_id_ctlp = NULL; + } + } + free(fdc_p); + devicetable[han] = NULL; + errno = 0; + return 0; +} + +/* Assumes device_han is an "open" file handle associated with some device. + * Returns 1 if SCSI generic pass-though device, returns 2 if secondary + * SCSI pass-through device (in Linux a bsg device); returns 3 is char + * NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes + * NSID), or 0 if something else (e.g. ATA block device) or device_han < 0. + * If error, returns negated errno (operating system) value. */ +int +check_pt_file_handle(int device_han, const char * device_name, int vb) +{ + struct freebsd_dev_channel *fdc_p; + int han = device_han - FREEBSD_FDOFFSET; + + if ((han < 0) || (han >= FREEBSD_MAXDEV)) + return -ENODEV; + fdc_p = devicetable[han]; + if (NULL == fdc_p) + return -ENODEV; + if (fdc_p->is_nvme) + return 4 - (int)fdc_p->is_char; + else if (fdc_p->cam_dev) + return 2 - (int)fdc_p->is_char; + else { + if (vb) + pr2ws("%s: neither SCSI nor NVMe ... hmm, dvice name: %s\n", + __func__, device_name); + return 0; + } +} + +#if (HAVE_NVME && (! IGNORE_NVME)) +static bool checked_ev_dsense = false; +static bool ev_dsense = false; +#endif + +struct sg_pt_base * +construct_scsi_pt_obj_with_fd(int dev_han, int vb) +{ + struct sg_pt_freebsd_scsi * ptp; + + ptp = (struct sg_pt_freebsd_scsi *) + calloc(1, sizeof(struct sg_pt_freebsd_scsi)); + if (ptp) { + ptp->dxfer_dir = CAM_DIR_NONE; + ptp->dev_han = (dev_han < 0) ? -1 : dev_han; + if (ptp->dev_han >= 0) { + struct freebsd_dev_channel *fdc_p; + + fdc_p = get_fdc_p(ptp); + if (fdc_p) { + ptp->is_nvme = fdc_p->is_nvme; + ptp->cam_dev = fdc_p->cam_dev; + ptp->dev_statp = &fdc_p->dev_stat; +#if (HAVE_NVME && (! IGNORE_NVME)) + sntl_init_dev_stat(ptp->dev_statp); + if (! checked_ev_dsense) { + ev_dsense = sg_get_initial_dsense(); + checked_ev_dsense = true; + } + fdc_p->dev_stat.scsi_dsense = ev_dsense; +#endif + } else if (vb) + pr2ws("%s: bad dev_han=%d\n", __func__, dev_han); + } + } else if (vb) + pr2ws("%s: calloc() out of memory\n", __func__); + return (struct sg_pt_base *)ptp; +} + + +struct sg_pt_base * +construct_scsi_pt_obj() +{ + return construct_scsi_pt_obj_with_fd(-1, 0); +} + +void +destruct_scsi_pt_obj(struct sg_pt_base * vp) +{ + struct sg_pt_freebsd_scsi * ptp; + + if (NULL == vp) { + pr2ws(">>>> %s: given NULL pointer\n", __func__); + return; + } + if ((ptp = &vp->impl)) { + if (ptp->ccb) + cam_freeccb(ptp->ccb); + free(ptp); + } +} + +void +clear_scsi_pt_obj(struct sg_pt_base * vp) +{ + bool is_nvme; + int dev_han; + struct sg_pt_freebsd_scsi * ptp; + struct cam_device* cam_dev; + struct sg_sntl_dev_state_t * dsp; + + if (NULL == vp) { + pr2ws(">>>>> %s: NULL pointer given\n", __func__); + return; + } + if ((ptp = &vp->impl)) { + if (ptp->ccb) + cam_freeccb(ptp->ccb); + is_nvme = ptp->is_nvme; + dev_han = ptp->dev_han; + cam_dev = ptp->cam_dev; + dsp = ptp->dev_statp; + memset(ptp, 0, sizeof(struct sg_pt_freebsd_scsi)); + ptp->dxfer_dir = CAM_DIR_NONE; + ptp->dev_han = dev_han; + ptp->is_nvme = is_nvme; + ptp->cam_dev = cam_dev; + ptp->dev_statp = dsp; + } +} + +/* Forget any previous dev_han and install the one given. May attempt to + * find file type (e.g. if pass-though) from OS so there could be an error. + * Returns 0 for success or the same value as get_scsi_pt_os_err() + * will return. dev_han should be >= 0 for a valid file handle or -1 . */ +int +set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb) +{ + struct sg_pt_freebsd_scsi * ptp; + + if (NULL == vp) { + if (vb) + pr2ws(">>>> %s: pointer to object is NULL\n", __func__); + return EINVAL; + } + if ((ptp = &vp->impl)) { + struct freebsd_dev_channel *fdc_p; + + if (dev_han < 0) { + ptp->dev_han = -1; + ptp->dxfer_dir = CAM_DIR_NONE; + ptp->is_nvme = false; + ptp->cam_dev = NULL; + return 0; + } + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + if (vb) + pr2ws("%s: dev_han (%d) is invalid\n", __func__, dev_han); + ptp->os_err = EINVAL; + return ptp->os_err; + } + ptp->os_err = 0; + ptp->transport_err = 0; + ptp->in_err = 0; + ptp->scsi_status = 0; + ptp->dev_han = dev_han; + ptp->dxfer_dir = CAM_DIR_NONE; + ptp->is_nvme = fdc_p->is_nvme; + ptp->cam_dev = fdc_p->cam_dev; + ptp->dev_statp = &fdc_p->dev_stat; + } + return 0; +} + +/* Valid file handles (which is the return value) are >= 0 . Returns -1 + * if there is no valid file handle. */ +int +get_pt_file_handle(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + return ptp ? ptp->dev_han : -1; +} + +void +set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb, int cdb_len) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->cdb) + ++ptp->in_err; + ptp->cdb = (uint8_t *)cdb; + ptp->cdb_len = cdb_len; +} + +void +set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, + int max_sense_len) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->sense) + ++ptp->in_err; + memset(sense, 0, max_sense_len); + ptp->sense = sense; + ptp->sense_len = max_sense_len; +} + +/* Setup for data transfer from device */ +void +set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->dxferip) + ++ptp->in_err; + ptp->dxferip = dxferp; + ptp->dxfer_ilen = dxfer_len; + if (dxfer_len > 0) { + ptp->dxferp = dxferp; + ptp->dxfer_len = dxfer_len; + ptp->dxfer_dir = CAM_DIR_IN; + } +} + +/* Setup for data transfer toward device */ +void +set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->dxferop) + ++ptp->in_err; + ptp->dxferop = (uint8_t *)dxferp; + ptp->dxfer_olen = dxfer_len; + if (dxfer_len > 0) { + ptp->dxferp = (uint8_t *)dxferp; + ptp->dxfer_len = dxfer_len; + ptp->dxfer_dir = CAM_DIR_OUT; + } +} + +void +set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp, + uint32_t mdxfer_len, bool out_true) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->mdxferp) + ++ptp->in_err; + ptp->mdxferp = mdxferp; + ptp->mdxfer_len = mdxfer_len; + if (mdxfer_len > 0) + ptp->mdxfer_out = out_true; +} + +void +set_scsi_pt_packet_id(struct sg_pt_base * vp __attribute__ ((unused)), + int pack_id __attribute__ ((unused))) +{ +} + +void +set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag __attribute__ ((unused))) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + ++ptp->in_err; +} + +void +set_scsi_pt_task_management(struct sg_pt_base * vp, + int tmf_code __attribute__ ((unused))) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + ++ptp->in_err; +} + +void +set_scsi_pt_task_attr(struct sg_pt_base * vp, + int attrib __attribute__ ((unused)), + int priority __attribute__ ((unused))) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + ++ptp->in_err; +} + +void +set_scsi_pt_flags(struct sg_pt_base * objp, int flags) +{ + if (objp) { ; } /* unused, suppress warning */ + if (flags) { ; } /* unused, suppress warning */ +} + +/* Executes SCSI command (or at least forwards it to lower layers). + * Clears os_err field prior to active call (whose result may set it + * again). */ +int +do_scsi_pt(struct sg_pt_base * vp, int dev_han, int time_secs, int vb) +{ + int len; + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + struct freebsd_dev_channel *fdc_p; + union ccb *ccb; + + ptp->os_err = 0; + if (ptp->in_err) { + if (vb) + pr2ws("Replicated or unused set_scsi_pt...\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (dev_han < 0) { + if (ptp->dev_han < 0) { + if (vb) + pr2ws("%s: No device file handle given\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + dev_han = ptp->dev_han; + } else { + if (ptp->dev_han >= 0) { + if (dev_han != ptp->dev_han) { + if (vb) + pr2ws("%s: file handle given to create and this " + "differ\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } else + ptp->dev_han = dev_han; + } + + if (NULL == ptp->cdb) { + if (vb) + pr2ws("No command (cdb) given\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (ptp->is_nvme) + return sg_do_nvme_pt(vp, -1, vb); + + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + if (vb) + pr2ws("File descriptor bad or closed??\n"); + ptp->os_err = ENODEV; + return -ptp->os_err; + } + ptp->is_nvme = fdc_p->is_nvme; + ptp->dev_statp = &fdc_p->dev_stat; + if (fdc_p->is_nvme) + return sg_do_nvme_pt(vp, -1, vb); + + if (NULL == fdc_p->cam_dev) { + if (vb) + pr2ws("No open CAM device\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + + if (NULL == ptp->ccb) { /* re-use if we have one already */ + if (! (ccb = cam_getccb(fdc_p->cam_dev))) { + if (vb) + pr2ws("cam_getccb: failed\n"); + ptp->os_err = ENOMEM; + return -ptp->os_err; + } + ptp->ccb = ccb; + } else + ccb = ptp->ccb; + + // clear out structure, except for header that was filled in for us + bzero(&(&ccb->ccb_h)[1], + sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); + + ptp->timeout_ms = (time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT; + cam_fill_csio(&ccb->csio, + /* retries */ 1, + /* cbfcnp */ NULL, + /* flags */ ptp->dxfer_dir, + /* tagaction */ MSG_SIMPLE_Q_TAG, + /* dataptr */ ptp->dxferp, + /* datalen */ ptp->dxfer_len, + /* senselen */ ptp->sense_len, + /* cdblen */ ptp->cdb_len, + /* timeout (millisecs) */ ptp->timeout_ms); + memcpy(ccb->csio.cdb_io.cdb_bytes, ptp->cdb, ptp->cdb_len); + + if (cam_send_ccb(fdc_p->cam_dev, ccb) < 0) { + if (vb) { + warn("error sending SCSI ccb"); + #if __FreeBSD_version > 500000 + cam_error_print(fdc_p->cam_dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + #endif + } + cam_freeccb(ptp->ccb); + ptp->ccb = NULL; + ptp->os_err = EIO; + return -ptp->os_err; + } + + if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) || + ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR)) { + ptp->scsi_status = ccb->csio.scsi_status; + ptp->resid = ccb->csio.resid; + ptp->sense_resid = ccb->csio.sense_resid; + + if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status)) { + if (ptp->sense_resid > ptp->sense_len) + len = ptp->sense_len; /* crazy; ignore sense_resid */ + else + len = ptp->sense_len - ptp->sense_resid; + if (len > 0) + memcpy(ptp->sense, &(ccb->csio.sense_data), len); + } + } else + ptp->transport_err = 1; + + ptp->cam_dev = fdc_p->cam_dev; // for error processing + return 0; +} + +int +get_scsi_pt_result_category(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->os_err) + return SCSI_PT_RESULT_OS_ERR; + else if (ptp->transport_err) + return SCSI_PT_RESULT_TRANSPORT_ERR; + else if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status)) + return SCSI_PT_RESULT_SENSE; + else if (ptp->scsi_status) + return SCSI_PT_RESULT_STATUS; + else + return SCSI_PT_RESULT_GOOD; +} + +int +get_scsi_pt_resid(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + return ptp->nvme_direct ? 0 : ptp->resid; +} + +/* Returns SCSI status value (from device that received the command). If an + * NVMe command was issued directly (i.e. through do_scsi_pt() then return + * NVMe status (i.e. ((SCT << 8) | SC)). If problem returns -1. */ +int +get_scsi_pt_status_response(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp) { + if (ptp->nvme_direct) { + const struct freebsd_dev_channel *fdc_p; + + fdc_p = get_fdc_cp(ptp); + if (NULL == fdc_p) + return -1; + return (int)fdc_p->nvme_status; + } else + return ptp->scsi_status; + } + return -1; +} + +/* For NVMe command: CDW0 from completion (32 bits); for SCSI: the status */ +uint32_t +get_pt_result(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp) { + if (ptp->nvme_direct) { + const struct freebsd_dev_channel *fdc_p; + + fdc_p = get_fdc_cp(ptp); + if (NULL == fdc_p) + return -1; + return fdc_p->nvme_result; + } else + return (uint32_t)ptp->scsi_status; + } + return 0xffffffff; +} + +int +get_scsi_pt_sense_len(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp->sense_resid > ptp->sense_len) + return ptp->sense_len; /* strange; ignore ptp->sense_resid */ + else + return ptp->sense_len - ptp->sense_resid; +} + +/* Not impemented so return -1 . */ +int +get_scsi_pt_duration_ms(const struct sg_pt_base * vp __attribute__ ((unused))) +{ + // const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + return -1; +} + +int +get_scsi_pt_transport_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + return ptp->transport_err; +} + +void +set_scsi_pt_transport_err(struct sg_pt_base * vp, int err) +{ + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + ptp->transport_err = err; +} + +int +get_scsi_pt_os_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + return ptp->os_err; +} + +char * +get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, + char * b) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (0 == ptp->transport_err) { + strncpy(b, "no transport error available", max_b_len); + b[max_b_len - 1] = '\0'; + return b; + } + if (ptp->is_nvme) { + snprintf(b, max_b_len, "NVMe has no transport errors at present " + "but tranport_err=%d ??\n", ptp->transport_err); + return b; + } +#if __FreeBSD_version > 500000 + if (ptp->cam_dev) + cam_error_string(ptp->cam_dev, ptp->ccb, b, max_b_len, CAM_ESF_ALL, + CAM_EPF_ALL); + else { + strncpy(b, "no transport error available", max_b_len); + b[max_b_len - 1] = '\0'; + } +#else + strncpy(b, "no transport error available", max_b_len); + b[max_b_len - 1] = '\0'; +#endif + return b; +} + +bool +pt_device_is_nvme(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp && (ptp->dev_han >= 0)) { + const struct freebsd_dev_channel *fdc_p; + + fdc_p = get_fdc_cp(ptp); + if (NULL == fdc_p) { + pr2ws("%s: unable to find fdc_p\n", __func__); + errno = ENODEV; + return false; + } + /* if unequal, cast away const and drive fdc_p value into ptp */ + if (ptp->is_nvme != fdc_p->is_nvme) /* indicates logic error */ + ((struct sg_pt_freebsd_scsi *)ptp)->is_nvme = fdc_p->is_nvme; + return fdc_p->is_nvme; + } + return false; +} + +/* If a NVMe block device (which includes the NSID) handle is associated + * with 'objp', then its NSID is returned (values range from 0x1 to + * 0xffffffe). Otherwise 0 is returned. */ +uint32_t +get_pt_nvme_nsid(const struct sg_pt_base * vp) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + + if (ptp && (ptp->dev_han >= 0)) { + const struct freebsd_dev_channel *fdc_p; + + fdc_p = get_fdc_cp(ptp); + if (NULL == fdc_p) + return 0; + return fdc_p->nsid; + } + return 0; +} + +char * +get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) +{ + const struct sg_pt_freebsd_scsi * ptp = &vp->impl; + const char * cp; + + cp = safe_strerror(ptp->os_err); + strncpy(b, cp, max_b_len); + if ((int)strlen(cp) >= max_b_len) + b[max_b_len - 1] = '\0'; + return b; +} + + +#define SCSI_INQUIRY_OPC 0x12 +#define SCSI_REPORT_LUNS_OPC 0xa0 +#define SCSI_TEST_UNIT_READY_OPC 0x0 +#define SCSI_REQUEST_SENSE_OPC 0x3 +#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d +#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c +#define SCSI_MAINT_IN_OPC 0xa3 +#define SCSI_REP_SUP_OPCS_OPC 0xc +#define SCSI_REP_SUP_TMFS_OPC 0xd +#define SCSI_MODE_SENSE10_OPC 0x5a +#define SCSI_MODE_SELECT10_OPC 0x55 + +/* Additional Sense Code (ASC) */ +#define NO_ADDITIONAL_SENSE 0x0 +#define LOGICAL_UNIT_NOT_READY 0x4 +#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8 +#define UNRECOVERED_READ_ERR 0x11 +#define PARAMETER_LIST_LENGTH_ERR 0x1a +#define INVALID_OPCODE 0x20 +#define LBA_OUT_OF_RANGE 0x21 +#define INVALID_FIELD_IN_CDB 0x24 +#define INVALID_FIELD_IN_PARAM_LIST 0x26 +#define UA_RESET_ASC 0x29 +#define UA_CHANGED_ASC 0x2a +#define TARGET_CHANGED_ASC 0x3f +#define LUNS_CHANGED_ASCQ 0x0e +#define INSUFF_RES_ASC 0x55 +#define INSUFF_RES_ASCQ 0x3 +#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */ +#define POWER_ON_RESET_ASCQ 0x0 +#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */ +#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */ +#define CAPACITY_CHANGED_ASCQ 0x9 +#define SAVING_PARAMS_UNSUP 0x39 +#define TRANSPORT_PROBLEM 0x4b +#define THRESHOLD_EXCEEDED 0x5d +#define LOW_POWER_COND_ON 0x5e +#define MISCOMPARE_VERIFY_ASC 0x1d +#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ +#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 + +#if (HAVE_NVME && (! IGNORE_NVME)) + +static void +mk_sense_asc_ascq(struct sg_pt_freebsd_scsi * ptp, int sk, int asc, int ascq, + int vb) +{ + bool dsense = ptp->dev_statp->scsi_dsense; + int n; + uint8_t * sbp = ptp->sense; + + ptp->scsi_status = SAM_STAT_CHECK_CONDITION; + n = ptp->sense_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, n); + return; + } else + ptp->sense_resid = ptp->sense_len - + (dsense ? 8 : ((n < 18) ? n : 18)); + memset(sbp, 0, n); + sg_build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, + sk, asc, ascq); +} + +static void +mk_sense_from_nvme_status(struct sg_pt_freebsd_scsi * ptp, uint16_t sct_sc, + int vb) +{ + bool ok; + bool dsense = ptp->dev_statp->scsi_dsense; + int n; + uint8_t sstatus, sk, asc, ascq; + uint8_t * sbp = ptp->sense; + + ok = sg_nvme_status2scsi(sct_sc, &sstatus, &sk, &asc, &ascq); + if (! ok) { /* can't find a mapping to a SCSI error, so ... */ + sstatus = SAM_STAT_CHECK_CONDITION; + sk = SPC_SK_ILLEGAL_REQUEST; + asc = 0xb; + ascq = 0x0; /* asc: "WARNING" purposely vague */ + } + + ptp->scsi_status = sstatus; + n = ptp->sense_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, n); + return; + } else + ptp->sense_resid = ptp->sense_len - + (dsense ? 8 : ((n < 18) ? n : 18)); + memset(sbp, 0, n); + sg_build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, + sk, asc, ascq); + if (dsense && (sct_sc > 0) && (ptp->sense_resid > 7)) { + sg_nvme_desc2sense(sbp, 0x4000 & sct_sc /* dnr */, + 0x2000 & sct_sc /* more */, 0x7ff & sct_sc); + ptp->sense_resid -= 8; + } +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_invalid_fld(struct sg_pt_freebsd_scsi * ptp, bool in_cdb, + int in_byte, int in_bit, int vb) +{ + bool ds = ptp->dev_statp->scsi_dsense; + int sl, asc, n; + uint8_t * sbp = (uint8_t *)ptp->sense; + uint8_t sks[4]; + + ptp->scsi_status = SAM_STAT_CHECK_CONDITION; + asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST; + n = ptp->sense_len; + if ((n < 8) || ((! ds) && (n < 14))) { + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->sense_resid = ptp->sense_len - (ds ? 8 : ((n < 18) ? n : 18)); + memset(sbp, 0, n); + sg_build_sense_buffer(ds, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0); + memset(sks, 0, sizeof(sks)); + sks[0] = 0x80; + if (in_cdb) + sks[0] |= 0x40; + if (in_bit >= 0) { + sks[0] |= 0x8; + sks[0] |= (0x7 & in_bit); + } + sg_put_unaligned_be16(in_byte, sks + 1); + if (ds) { + sl = sbp[7] + 8; + sbp[7] = sl; + sbp[sl] = 0x2; + sbp[sl + 1] = 0x6; + memcpy(sbp + sl + 4, sks, 3); + } else + memcpy(sbp + 15, sks, 3); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n", + __func__, asc, in_cdb ? 'C' : 'D', in_byte, + ((in_bit > 0) ? (0x7 & in_bit) : 0)); +} + +/* Does actual ioctl(NVME_PASSTHROUGH_CMD). Returns 0 on success; negative + * values are Unix negated errno values; positive values are NVMe status + * (i.e. ((SCT << 8) | SC) ). */ +static int +nvme_pt_low(struct freebsd_dev_channel *fdc_p, void * dxferp, uint32_t len, + bool is_read, struct nvme_pt_command * npcp, int vb) +{ + int err; + uint16_t sct_sc; + uint8_t opcode; + char b[80]; + + if (fdc_p->dev_fd < 0) { + if (vb) + pr2ws("%s: is_nvme is true but dev_fd<0, inconsistent\n", + __func__); + return -EINVAL; + } + npcp->buf = dxferp; + npcp->len = len; + npcp->is_read = (uint32_t)is_read; + opcode = npcp->cmd.opc; + err = ioctl(fdc_p->dev_fd, NVME_PASSTHROUGH_CMD, npcp); + if (err < 0) + return -errno; /* Assume Unix error in normal place */ + sct_sc = ((npcp->cpl.status.sct << 8) | npcp->cpl.status.sc); + fdc_p->nvme_result = npcp->cpl.cdw0; + sg_put_unaligned_le32(npcp->cpl.cdw0, + fdc_p->cq_dw0_3 + SG_NVME_PT_CQ_RESULT); + sg_put_unaligned_le32(npcp->cpl.rsvd1, fdc_p->cq_dw0_3 + 4); + sg_put_unaligned_le16(npcp->cpl.sqhd, fdc_p->cq_dw0_3 + 8); + sg_put_unaligned_le16(npcp->cpl.sqid, fdc_p->cq_dw0_3 + 10); + sg_put_unaligned_le16(npcp->cpl.cid, fdc_p->cq_dw0_3 + 12); + sg_put_unaligned_le16(*((const uint16_t *)&(npcp->cpl.status)), + fdc_p->cq_dw0_3 + SG_NVME_PT_CQ_STATUS_P); + if (sct_sc && (vb > 1)) { + char nam[64]; + + sg_get_nvme_opcode_name(opcode, true, sizeof(nam), nam); + pr2ws("%s: %s [0x%x], status: %s\n", __func__, nam, opcode, + sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b)); + } + return sct_sc; +} + +static void +sntl_check_enclosure_override(struct freebsd_dev_channel * fdc_p, int vb) +{ + uint8_t * up = fdc_p->nvme_id_ctlp; + uint8_t nvmsr; + + if (NULL == up) + return; + nvmsr = up[253]; + if (vb > 3) + pr2ws("%s: enter, nvmsr=%u\n", __func__, nvmsr); + fdc_p->dev_stat.id_ctl253 = nvmsr; + switch (fdc_p->dev_stat.enclosure_override) { + case 0x0: /* no override */ + if (0x3 & nvmsr) { + fdc_p->dev_stat.pdt = PDT_DISK; + fdc_p->dev_stat.enc_serv = 1; + } else if (0x2 & nvmsr) { + fdc_p->dev_stat.pdt = PDT_SES; + fdc_p->dev_stat.enc_serv = 1; + } else if (0x1 & nvmsr) { + fdc_p->dev_stat.pdt = PDT_DISK; + fdc_p->dev_stat.enc_serv = 0; + } else { + uint32_t nn = sg_get_unaligned_le32(up + 516); + + fdc_p->dev_stat.pdt = nn ? PDT_DISK : PDT_UNKNOWN; + fdc_p->dev_stat.enc_serv = 0; + } + break; + case 0x1: /* override to SES device */ + fdc_p->dev_stat.pdt = PDT_SES; + fdc_p->dev_stat.enc_serv = 1; + break; + case 0x2: /* override to disk with attached SES device */ + fdc_p->dev_stat.pdt = PDT_DISK; + fdc_p->dev_stat.enc_serv = 1; + break; + case 0x3: /* override to SAFTE device (PDT_PROCESSOR) */ + fdc_p->dev_stat.pdt = PDT_PROCESSOR; + fdc_p->dev_stat.enc_serv = 1; + break; + case 0xff: /* override to normal disk */ + fdc_p->dev_stat.pdt = PDT_DISK; + fdc_p->dev_stat.enc_serv = 0; + break; + default: + pr2ws("%s: unknown enclosure_override value: %d\n", __func__, + fdc_p->dev_stat.enclosure_override); + break; + } +} + +/* Currently only caches associated controller response (4096 bytes) */ +static int +sntl_cache_identity(struct freebsd_dev_channel * fdc_p, int vb) +{ + int err; + struct nvme_pt_command npc; + uint8_t * npc_up = (uint8_t *)&npc; + uint32_t pg_sz = sg_get_page_size(); + + fdc_p->nvme_id_ctlp = sg_memalign(pg_sz, pg_sz, + &fdc_p->free_nvme_id_ctlp, vb > 3); + if (NULL == fdc_p->nvme_id_ctlp) { + pr2ws("%s: sg_memalign() failed to get memory\n", __func__); + return -ENOMEM; + } + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0x6; /* Identify */ + sg_put_unaligned_le32(0x0, npc_up + SG_NVME_PT_NSID); + /* CNS=0x1 Identify: controller */ + sg_put_unaligned_le32(0x1, npc_up + SG_NVME_PT_CDW10); + sg_put_unaligned_le64((sg_uintptr_t)fdc_p->nvme_id_ctlp, + npc_up + SG_NVME_PT_ADDR); + sg_put_unaligned_le32(pg_sz, npc_up + SG_NVME_PT_DATA_LEN); + err = nvme_pt_low(fdc_p, fdc_p->nvme_id_ctlp, pg_sz, true, &npc, vb); + if (err) { + if (err < 0) { + if (vb > 1) + pr2ws("%s: do_nvme_pt() failed: %s (errno=%d)\n", __func__, + strerror(-err), -err); + return err; + } else { /* non-zero NVMe command status */ + fdc_p->nvme_status = err; + return SG_LIB_NVME_STATUS; + } + } + sntl_check_enclosure_override(fdc_p, vb); + return 0; +} + +static const char * nvme_scsi_vendor_str = "NVMe "; +static const uint16_t inq_resp_len = 36; + +static int +sntl_inq(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + bool evpd; + bool cp_id_ctl = false; + int res; + uint16_t n, alloc_len, pg_cd; + uint32_t pg_sz = sg_get_page_size(); + struct freebsd_dev_channel * fdc_p; + uint8_t * nvme_id_ns = NULL; + uint8_t * free_nvme_id_ns = NULL; + uint8_t inq_dout[256]; + + if (vb > 3) + pr2ws("%s: starting\n", __func__); + + if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */ + mk_sense_invalid_fld(ptp, true, 1, 1, vb); + return 0; + } + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + if (NULL == fdc_p->nvme_id_ctlp) { + res = sntl_cache_identity(fdc_p, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, fdc_p->nvme_status, vb); + return 0; + } else if (res) /* should be negative errno */ + return res; + } + memset(inq_dout, 0, sizeof(inq_dout)); + alloc_len = sg_get_unaligned_be16(cdbp + 3); + evpd = !!(0x1 & cdbp[1]); + pg_cd = cdbp[2]; + if (evpd) { /* VPD page responses */ + switch (pg_cd) { + case 0: /* Supported VPD pages VPD page */ + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 11; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x0; + inq_dout[5] = 0x80; + inq_dout[6] = 0x83; + inq_dout[7] = 0x86; + inq_dout[8] = 0x87; + inq_dout[9] = 0x92; + inq_dout[n - 1] = SG_NVME_VPD_NICR; /* last VPD number */ + break; + case 0x80: /* Serial number VPD page */ + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 24; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + memcpy(inq_dout + 4, fdc_p->nvme_id_ctlp + 4, 20); /* SN */ + break; + case 0x83: /* Device identification VPD page */ + if ((fdc_p->nsid > 0) && (fdc_p->nsid < SG_NVME_BROADCAST_NSID)) { + nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns, + vb > 3); + if (nvme_id_ns) { + struct nvme_pt_command npc; + uint8_t * npc_up = (uint8_t *)&npc; + + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0x6; /* Identify */ + sg_put_unaligned_le32(fdc_p->nsid, + npc_up + SG_NVME_PT_NSID); + /* CNS=0x0 Identify: namespace */ + sg_put_unaligned_le32(0x0, npc_up + SG_NVME_PT_CDW10); + sg_put_unaligned_le64((sg_uintptr_t)nvme_id_ns, + npc_up + SG_NVME_PT_ADDR); + sg_put_unaligned_le32(pg_sz, + npc_up + SG_NVME_PT_DATA_LEN); + res = nvme_pt_low(fdc_p, nvme_id_ns, pg_sz, true, &npc, + vb > 3); + if (res) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + } + } + n = sg_make_vpd_devid_for_nvme(fdc_p->nvme_id_ctlp, nvme_id_ns, 0, + -1, inq_dout, sizeof(inq_dout)); + if (n > 3) + sg_put_unaligned_be16(n - 4, inq_dout + 2); + if (free_nvme_id_ns) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + break; + case 0x86: /* Extended INQUIRY (per SFS SPC Discovery 2016) */ + inq_dout[1] = pg_cd; + n = 64; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[5] = 0x1; /* SIMPSUP=1 */ + inq_dout[7] = 0x1; /* LUICLR=1 */ + inq_dout[13] = 0x40; /* max supported sense data length */ + break; + case 0x87: /* Mode page policy (per SFS SPC Discovery 2016) */ + inq_dout[1] = pg_cd; + n = 8; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x3f; /* all mode pages */ + inq_dout[5] = 0xff; /* and their sub-pages */ + inq_dout[6] = 0x80; /* MLUS=1, policy=shared */ + break; + case 0x92: /* SCSI Feature set: only SPC Discovery 2016 */ + inq_dout[1] = pg_cd; + n = 10; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[9] = 0x1; /* SFS SPC Discovery 2016 */ + break; + case SG_NVME_VPD_NICR: /* 0xde */ + inq_dout[1] = pg_cd; + sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2); + n = 16 + 4096; + cp_id_ctl = true; + break; + default: /* Point to page_code field in cdb */ + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->dxfer_len) ? n : ptp->dxfer_len; + ptp->resid = ptp->dxfer_len - n; + if (n > 0) { + if (cp_id_ctl) { + memcpy((uint8_t *)ptp->dxferp, inq_dout, + (n < 16 ? n : 16)); + if (n > 16) + memcpy((uint8_t *)ptp->dxferp + 16, + fdc_p->nvme_id_ctlp, n - 16); + } else + memcpy((uint8_t *)ptp->dxferp, inq_dout, n); + } + } + } else { /* Standard INQUIRY response */ + /* pdt=0 --> disk; pdt=0xd --> SES; pdt=3 --> processor (safte) */ + inq_dout[0] = (0x1f & fdc_p->dev_stat.pdt); /* (PQ=0)<<5 */ + /* inq_dout[1] = (RMD=0)<<7 | (LU_CONG=0)<<6; rest reserved */ + inq_dout[2] = 6; /* version: SPC-4 */ + inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */ + inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */ + inq_dout[6] = fdc_p->dev_stat.enc_serv ? 0x40 : 0; + inq_dout[7] = 0x2; /* CMDQUE=1 */ + memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */ + memcpy(inq_dout + 16, fdc_p->nvme_id_ctlp + 24, 16);/* Prod <-- MN */ + memcpy(inq_dout + 32, fdc_p->nvme_id_ctlp + 64, 4); /* Rev <-- FR */ + if (alloc_len > 0) { + n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len; + n = (n < ptp->dxfer_len) ? n : ptp->dxfer_len; + if (n > 0) + memcpy((uint8_t *)ptp->dxferp, inq_dout, n); + } + } + return 0; +} + +static int +sntl_rluns(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + int res; + uint16_t sel_report; + uint32_t alloc_len, k, n, num, max_nsid; + struct freebsd_dev_channel * fdc_p; + uint8_t * rl_doutp; + uint8_t * up; + + if (vb > 3) + pr2ws("%s: starting\n", __func__); + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + sel_report = cdbp[2]; + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (NULL == fdc_p->nvme_id_ctlp) { + res = sntl_cache_identity(fdc_p, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, fdc_p->nvme_status, vb); + return 0; + } else if (res) + return res; + } + max_nsid = sg_get_unaligned_le32(fdc_p->nvme_id_ctlp + 516); + switch (sel_report) { + case 0: + case 2: + num = max_nsid; + break; + case 1: + case 0x10: + case 0x12: + num = 0; + break; + case 0x11: + num = (1 == fdc_p->nsid) ? max_nsid : 0; + break; + default: + if (vb > 1) + pr2ws("%s: bad select_report value: 0x%x\n", __func__, + sel_report); + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + rl_doutp = (uint8_t *)calloc(num + 1, 8); + if (NULL == rl_doutp) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8) + sg_put_unaligned_be16(k, up); + n = num * 8; + sg_put_unaligned_be32(n, rl_doutp); + n+= 8; + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < (uint32_t)ptp->dxfer_len) ? n : (uint32_t)ptp->dxfer_len; + ptp->resid = ptp->dxfer_len - (int)n; + if (n > 0) + memcpy((uint8_t *)ptp->dxferp, rl_doutp, n); + } + res = 0; + free(rl_doutp); + return res; +} + +static int +sntl_tur(struct sg_pt_freebsd_scsi * ptp, int vb) +{ + int res, err; + uint32_t pow_state; + struct nvme_pt_command npc; + uint8_t * npc_up = (uint8_t *)&npc; + struct freebsd_dev_channel * fdc_p; + + if (vb > 3) + pr2ws("%s: starting\n", __func__); + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + if (NULL == fdc_p->nvme_id_ctlp) { + res = sntl_cache_identity(fdc_p, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, fdc_p->nvme_status, vb); + return 0; + } else if (res) + return res; + } + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0xa; /* Get feature */ + sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, npc_up + SG_NVME_PT_NSID); + /* SEL=0 (current), Feature=2 Power Management */ + sg_put_unaligned_le32(0x2, npc_up + SG_NVME_PT_CDW10); + err = nvme_pt_low(fdc_p, NULL, 0, false, &npc, vb); + if (err) { + if (err < 0) { + if (vb > 1) + pr2ws("%s: do_nvme_pt() failed: %s (errno=%d)\n", __func__, + strerror(-err), -err); + return err; + } else { + fdc_p->nvme_status = err; + mk_sense_from_nvme_status(ptp, err, vb); + return 0; + } + } + pow_state = (0x1f & fdc_p->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); +#if 0 /* pow_state bounces around too much on laptop */ + if (pow_state) + mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0, + vb); +#endif + return 0; +} + +static int +sntl_req_sense(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + bool desc; + int res, err; + uint32_t pow_state, alloc_len, n; + struct nvme_pt_command npc; + uint8_t * npc_up = (uint8_t *)&npc; + struct freebsd_dev_channel * fdc_p; + uint8_t rs_dout[64]; + + if (vb > 3) + pr2ws("%s: starting\n", __func__); + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + if (NULL == fdc_p->nvme_id_ctlp) { + res = sntl_cache_identity(fdc_p, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, fdc_p->nvme_status, vb); + return 0; + } else if (res) + return res; + } + desc = !!(0x1 & cdbp[1]); + alloc_len = cdbp[4]; + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0xa; /* Get feature */ + sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, npc_up + SG_NVME_PT_NSID); + /* SEL=0 (current), Feature=2 Power Management */ + sg_put_unaligned_le32(0x2, npc_up + SG_NVME_PT_CDW10); + err = nvme_pt_low(fdc_p, NULL, 0, false, &npc, vb); + if (err) { + if (err < 0) { + if (vb > 1) + pr2ws("%s: do_nvme_pt() failed: %s (errno=%d)\n", __func__, + strerror(-err), -err); + return err; + } else { + fdc_p->nvme_status = err; + mk_sense_from_nvme_status(ptp, err, vb); + return 0; + } + } + pow_state = (0x1f & fdc_p->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); + memset(rs_dout, 0, sizeof(rs_dout)); + if (pow_state) + sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + LOW_POWER_COND_ON_ASC, 0); + else + sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + NO_ADDITIONAL_SENSE, 0); + n = desc ? 8 : 18; + n = (n < alloc_len) ? n : alloc_len; + n = (n < (uint32_t)ptp->dxfer_len) ? n : (uint32_t)ptp->dxfer_len; + ptp->resid = ptp->dxfer_len - (int)n; + if (n > 0) + memcpy((uint8_t *)ptp->dxferp, rs_dout, n); + return 0; +} + +static int +sntl_mode_ss(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + bool is_msense = (SCSI_MODE_SENSE10_OPC == cdbp[0]); + int res, n, len; + uint8_t * bp; + struct freebsd_dev_channel * fdc_p; + struct sg_sntl_result_t sntl_result; + + if (vb > 3) + pr2ws("%s: mse%s\n", __func__, (is_msense ? "nse" : "lect")); + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + if (NULL == fdc_p->nvme_id_ctlp) { + res = sntl_cache_identity(fdc_p, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, fdc_p->nvme_status, vb); + return 0; + } else if (res) + return res; + } + if (is_msense) { /* MODE SENSE(10) */ + len = ptp->dxfer_len; + bp = ptp->dxferp; + n = sntl_resp_mode_sense10(&fdc_p->dev_stat, cdbp, bp, len, + &sntl_result); + ptp->resid = (n >= 0) ? len - n : len; + } else { /* MODE SELECT(10) */ + uint8_t pre_enc_ov = fdc_p->dev_stat.enclosure_override; + + len = ptp->dxfer_len; + bp = ptp->dxferp; + n = sntl_resp_mode_select10(&fdc_p->dev_stat, cdbp, bp, len, + &sntl_result); + if (pre_enc_ov != fdc_p->dev_stat.enclosure_override) + sntl_check_enclosure_override(fdc_p, vb); /* ENC_OV has changed */ + } + if (n < 0) { + int in_bit = (255 == sntl_result.in_bit) ? (int)sntl_result.in_bit : + -1; + if ((SAM_STAT_CHECK_CONDITION == sntl_result.sstatus) && + (SPC_SK_ILLEGAL_REQUEST == sntl_result.sk)) { + if (INVALID_FIELD_IN_CDB == sntl_result.asc) + mk_sense_invalid_fld(ptp, true, sntl_result.in_byte, in_bit, + vb); + else if (INVALID_FIELD_IN_PARAM_LIST == sntl_result.asc) + mk_sense_invalid_fld(ptp, false, sntl_result.in_byte, in_bit, + vb); + else + mk_sense_asc_ascq(ptp, sntl_result.sk, sntl_result.asc, + sntl_result.ascq, vb); + } else + pr2ws("%s: error but no sense?? n=%d\n", __func__, n); + } + return 0; +} + +/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI + * has a special command (SES Send) to tunnel through pages to an + * enclosure. The NVMe enclosure is meant to understand the SES + * (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_senddiag(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + bool pf, self_test; + int err; + uint8_t st_cd, dpg_cd; + uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst; + const uint8_t * dop; + struct nvme_pt_command npc; + uint8_t * npc_up = (uint8_t *)&npc; + struct freebsd_dev_channel * fdc_p; + + st_cd = 0x7 & (cdbp[1] >> 5); + pf = !! (0x4 & cdbp[1]); + self_test = !! (0x10 & cdbp[1]); + if (vb > 3) + pr2ws("%s: pf=%d, self_test=%d, st_code=%d\n", __func__, (int)pf, + (int)self_test, (int)st_cd); + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + if (self_test || st_cd) { + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0x14; /* Device self-test */ + /* just this namespace (if there is one) and controller */ + sg_put_unaligned_le32(fdc_p->nsid, npc_up + SG_NVME_PT_NSID); + switch (st_cd) { + case 0: /* Here if self_test is set, do short self-test */ + case 1: /* Background short */ + case 5: /* Foreground short */ + nvme_dst = 1; + break; + case 2: /* Background extended */ + case 6: /* Foreground extended */ + nvme_dst = 2; + break; + case 4: /* Abort self-test */ + nvme_dst = 0xf; + break; + default: + pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd); + mk_sense_invalid_fld(ptp, true, 1, 7, vb); + return 0; + } + sg_put_unaligned_le32(nvme_dst, npc_up + SG_NVME_PT_CDW10); + err = nvme_pt_low(fdc_p, NULL, 0x0, false, &npc, vb); + goto do_low; + } + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + dout_len = ptp->dxfer_len; + if (pf) { + if (0 == alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: PF bit set bit param_list_len=0\n", __func__); + return 0; + } + } else { /* PF bit clear */ + if (alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: param_list_len>0 but PF clear\n", __func__); + return 0; + } else + return 0; /* nothing to do */ + if (dout_len > 0) { + if (vb) + pr2ws("%s: dout given but PF clear\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } + if (dout_len < 4) { + if (vb) + pr2ws("%s: dout length (%u bytes) too short\n", __func__, + dout_len); + return SCSI_PT_DO_BAD_PARAMS; + } + n = dout_len; + n = (n < alloc_len) ? n : alloc_len; + dop = (const uint8_t *)ptp->dxferp; + if (! sg_is_aligned(dop, 0)) { + if (vb) + pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->dxferp); + return SCSI_PT_DO_BAD_PARAMS; + } + dpg_cd = dop[0]; + dpg_len = sg_get_unaligned_be16(dop + 2) + 4; + /* should we allow for more than one D_PG is dout ?? */ + n = (n < dpg_len) ? n : dpg_len; /* not yet ... */ + + if (vb) + pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n", + __func__, dpg_cd, dpg_len); + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0x1d; /* MI send; same opcode as SEND DIAG */ + sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferp, + npc_up + SG_NVME_PT_ADDR); + /* NVMe 4k page size. Maybe determine this? */ + /* dout_len > 0x1000, is this a problem?? */ + sg_put_unaligned_le32(0x1000, npc_up + SG_NVME_PT_DATA_LEN); + /* NVMe Message Header */ + sg_put_unaligned_le32(0x0804, npc_up + SG_NVME_PT_CDW10); + /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */ + sg_put_unaligned_le32(0x9, npc_up + SG_NVME_PT_CDW11); + /* data-out length I hope */ + sg_put_unaligned_le32(n, npc_up + SG_NVME_PT_CDW13); + err = nvme_pt_low(fdc_p, ptp->dxferp, 0x1000, false, &npc, vb); +do_low: + if (err) { + if (err < 0) { + if (vb > 1) + pr2ws("%s: do_nvme_pt() failed: %s (errno=%d)\n", + __func__, strerror(-err), -err); + return err; + } else { + fdc_p->nvme_status = err; + mk_sense_from_nvme_status(ptp, err, vb); + return 0; + } + } + return 0; +} + +/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1) + * NVMe-MI has a special command (SES Receive) to read pages through a + * tunnel from an enclosure. The NVMe enclosure is meant to understand the + * SES (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_recvdiag(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + bool pcv; + int err; + uint8_t dpg_cd; + uint32_t alloc_len, n, din_len; + const uint8_t * dip; + struct nvme_pt_command npc; + uint8_t * npc_up = (uint8_t *)&npc; + struct freebsd_dev_channel * fdc_p; + + pcv = !! (0x1 & cdbp[1]); + dpg_cd = cdbp[2]; + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + if (vb > 3) + pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__, + dpg_cd, (int)pcv, alloc_len); + fdc_p = get_fdc_p(ptp); + if (NULL == fdc_p) { + pr2ws("%s: get_fdc_p() failed, no file descriptor ?\n", __func__); + return -EINVAL; + } + din_len = ptp->dxfer_len; + if (pcv) { + if (0 == alloc_len) { + /* T10 says not an error, hmmm */ + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: PCV bit set bit but alloc_len=0\n", __func__); + return 0; + } + } else { /* PCV bit clear */ + if (alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: alloc_len>0 but PCV clear\n", __func__); + return 0; + } else + return 0; /* nothing to do */ + if (din_len > 0) { + if (vb) + pr2ws("%s: din given but PCV clear\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } + n = din_len; + n = (n < alloc_len) ? n : alloc_len; + dip = (const uint8_t *)ptp->dxferp; + if (! sg_is_aligned(dip, 0)) { + if (vb) + pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->dxferp); + return SCSI_PT_DO_BAD_PARAMS; + } + + if (vb) + pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__, + dpg_cd); + memset(npc_up, 0, sizeof(npc)); + npc_up[SG_NVME_PT_OPCODE] = 0x1e; /* MI receive */ + sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferp, + npc_up + SG_NVME_PT_ADDR); + /* NVMe 4k page size. Maybe determine this? */ + /* dout_len > 0x1000, is this a problem?? */ + sg_put_unaligned_le32(0x1000, npc_up + SG_NVME_PT_DATA_LEN); + /* NVMe Message Header */ + sg_put_unaligned_le32(0x0804, npc_up + SG_NVME_PT_CDW10); + /* nvme_mi_ses_receive */ + sg_put_unaligned_le32(0x8, npc_up + SG_NVME_PT_CDW11); + sg_put_unaligned_le32(dpg_cd, npc_up + SG_NVME_PT_CDW12); + /* data-in length I hope */ + sg_put_unaligned_le32(n, npc_up + SG_NVME_PT_CDW13); + err = nvme_pt_low(fdc_p, ptp->dxferp, 0x1000, true, &npc, vb); + if (err) { + if (err < 0) { + if (vb > 1) + pr2ws("%s: do_nvme_pt() failed: %s (errno=%d)\n", + __func__, strerror(-err), -err); + return err; + } else { + fdc_p->nvme_status = err; + mk_sense_from_nvme_status(ptp, err, vb); + return 0; + } + } + ptp->resid = din_len - n; + return 0; +} + +#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */ +#define F_SA_HIGH 0x100 /* as used by variable length cdbs */ +#define FF_SA (F_SA_HIGH | F_SA_LOW) +#define F_INV_OP 0x200 + +static int +sntl_rep_opcodes(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, + int vb) +{ + bool rctd; + uint8_t reporting_opts, req_opcode, supp; + uint16_t req_sa, u; + uint32_t alloc_len, offset, a_len; + uint32_t pg_sz = sg_get_page_size(); + int k, len, count, bump; + const struct sg_opcode_info_t *oip; + uint8_t *arr; + uint8_t *free_arr; + + if (vb > 3) + pr2ws("%s: start\n", __func__); + rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */ + reporting_opts = cdbp[2] & 0x7; + req_opcode = cdbp[3]; + req_sa = sg_get_unaligned_be16(cdbp + 4); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4 || alloc_len > 0xffff) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + a_len = pg_sz - 72; + arr = sg_memalign(pg_sz, pg_sz, &free_arr, vb > 3); + if (NULL == arr) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + switch (reporting_opts) { + case 0: /* all commands */ + count = 0; + bump = rctd ? 20 : 8; + for (offset = 4, oip = sg_opcode_info_arr; + (oip->flags != 0xffff) && (offset < a_len); ++oip) { + if (F_INV_OP & oip->flags) + continue; + ++count; + arr[offset] = oip->opcode; + sg_put_unaligned_be16(oip->sa, arr + offset + 2); + if (rctd) + arr[offset + 5] |= 0x2; + if (FF_SA & oip->flags) + arr[offset + 5] |= 0x1; + sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6); + if (rctd) + sg_put_unaligned_be16(0xa, arr + offset + 8); + offset += bump; + } + sg_put_unaligned_be32(count * bump, arr + 0); + break; + case 1: /* one command: opcode only */ + case 2: /* one command: opcode plus service action */ + case 3: /* one command: if sa==0 then opcode only else opcode+sa */ + for (oip = sg_opcode_info_arr; oip->flags != 0xffff; ++oip) { + if ((req_opcode == oip->opcode) && (req_sa == oip->sa)) + break; + } + if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) { + supp = 1; + offset = 4; + } else { + if (1 == reporting_opts) { + if (FF_SA & oip->flags) { + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + req_sa = 0; + } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) { + mk_sense_invalid_fld(ptp, true, 4, -1, vb); + free(free_arr); + return 0; + } + if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode)) + supp = 3; + else if (0 == (FF_SA & oip->flags)) + supp = 1; + else if (req_sa != oip->sa) + supp = 1; + else + supp = 3; + if (3 == supp) { + u = oip->len_mask[0]; + sg_put_unaligned_be16(u, arr + 2); + arr[4] = oip->opcode; + for (k = 1; k < u; ++k) + arr[4 + k] = (k < 16) ? + oip->len_mask[k] : 0xff; + offset = 4 + u; + } else + offset = 4; + } + arr[1] = (rctd ? 0x80 : 0) | supp; + if (rctd) { + sg_put_unaligned_be16(0xa, arr + offset); + offset += 12; + } + break; + default: + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + offset = (offset < a_len) ? offset : a_len; + len = (offset < alloc_len) ? offset : alloc_len; + ptp->resid = ptp->dxfer_len - (int)len; + if (len > 0) + memcpy((uint8_t *)ptp->dxferp, arr, len); + free(free_arr); + return 0; +} + +static int +sntl_rep_tmfs(struct sg_pt_freebsd_scsi * ptp, const uint8_t * cdbp, int vb) +{ + bool repd; + uint32_t alloc_len, len; + uint8_t arr[16]; + + if (vb > 3) + pr2ws("%s: start\n", __func__); + memset(arr, 0, sizeof(arr)); + repd = !!(cdbp[2] & 0x80); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + arr[0] = 0xc8; /* ATS | ATSS | LURS */ + arr[1] = 0x1; /* ITNRS */ + if (repd) { + arr[3] = 0xc; + len = 16; + } else + len = 4; + + len = (len < alloc_len) ? len : alloc_len; + ptp->resid = ptp->dxfer_len - (int)len; + if (len > 0) + memcpy((uint8_t *)ptp->dxferp, arr, len); + return 0; +} + +/* Executes NVMe Admin command (or at least forwards it to lower layers). + * Returns 0 for success, negative numbers are negated 'errno' values from + * OS system calls. Positive return values are errors from this package. + * The time_secs argument is ignored. */ +static int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int vb) +{ + bool scsi_cdb, in_xfer; + int n, err, len, io_len; + uint16_t sct_sc, sa; + uint8_t * dxferp; + uint8_t * npc_up; + struct freebsd_dev_channel * fdc_p; + struct sg_pt_freebsd_scsi * ptp = &vp->impl; + const uint8_t * cdbp; + struct nvme_pt_command npc; + + npc_up = (uint8_t *)&npc; + if (vb > 3) + pr2ws("%s: fd=%d\n", __func__, fd); + if (! ptp->cdb) { + if (vb) + pr2ws("%s: No NVMe command given (set_scsi_pt_cdb())\n", + __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + fdc_p = get_fdc_p(ptp); + if (fd < 0) { + if (NULL == fdc_p) { + pr2ws("%s: no device handle in object or fd ?\n", __func__); + return -EINVAL; + } + } else { + int han = fd - FREEBSD_FDOFFSET; + + if ((han < 0) || (han >= FREEBSD_MAXDEV)) { + pr2ws("%s: argument 'fd' is bad\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + if (NULL == devicetable[han]) { + pr2ws("%s: argument 'fd' is bad (2)\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + if (fdc_p && (fdc_p != devicetable[han])) { + pr2ws("%s: different device handle in object and fd ?\n", + __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + if (NULL == fdc_p) { + ptp->dev_han = fd; + fdc_p = devicetable[han]; + } + } + + n = ptp->cdb_len; + cdbp = (const uint8_t *)ptp->cdb; + if (vb > 3) + pr2ws("%s: opcode=0x%x, fd=%d\n", __func__, cdbp[0], fd); + scsi_cdb = sg_is_scsi_cdb(cdbp, n); + /* nvme_direct is true when NVMe command (64 byte) has been given */ + ptp->nvme_direct = ! scsi_cdb; + fdc_p->nvme_direct = ptp->nvme_direct; + if (scsi_cdb) { + switch (cdbp[0]) { + case SCSI_INQUIRY_OPC: + return sntl_inq(ptp, cdbp, vb); + case SCSI_REPORT_LUNS_OPC: + return sntl_rluns(ptp, cdbp, vb); + case SCSI_TEST_UNIT_READY_OPC: + return sntl_tur(ptp, vb); + case SCSI_REQUEST_SENSE_OPC: + return sntl_req_sense(ptp, cdbp, vb); + case SCSI_SEND_DIAGNOSTIC_OPC: + return sntl_senddiag(ptp, cdbp, vb); + case SCSI_RECEIVE_DIAGNOSTIC_OPC: + return sntl_recvdiag(ptp, cdbp, vb); + case SCSI_MODE_SENSE10_OPC: + case SCSI_MODE_SELECT10_OPC: + return sntl_mode_ss(ptp, cdbp, vb); + case SCSI_MAINT_IN_OPC: + sa = 0x1f & cdbp[1]; /* service action */ + if (SCSI_REP_SUP_OPCS_OPC == sa) + return sntl_rep_opcodes(ptp, cdbp, vb); + else if (SCSI_REP_SUP_TMFS_OPC == sa) + return sntl_rep_tmfs(ptp, cdbp, vb); + /* fall through */ + default: + if (vb > 2) { + char b[64]; + + sg_get_command_name(cdbp, -1, sizeof(b), b); + pr2ws("%s: no translation to NVMe for SCSI %s command\n", + __func__, b); + } + mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE, + 0, vb); + return 0; + } + } + /* NVMe command given to pass-through */ + len = (int)sizeof(npc.cmd); + n = (n < len) ? n : len; + if (n < 64) { + if (vb) + pr2ws("%s: command length of %d bytes is too short\n", __func__, + n); + return SCSI_PT_DO_BAD_PARAMS; + } + memcpy(npc_up, (const uint8_t *)ptp->cdb, n); + if (n < len) /* zero out rest of 'npc' */ + memset(npc_up + n, 0, len - n); + in_xfer = false; + io_len = 0; + dxferp = NULL; + if (ptp->dxfer_ilen > 0) { + in_xfer = true; + io_len = ptp->dxfer_ilen; + dxferp = ptp->dxferip; + sg_put_unaligned_le32(ptp->dxfer_ilen, npc_up + SG_NVME_PT_DATA_LEN); + sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferip, + npc_up + SG_NVME_PT_ADDR); + } else if (ptp->dxfer_olen > 0) { + in_xfer = false; + io_len = ptp->dxfer_olen; + dxferp = ptp->dxferop; + sg_put_unaligned_le32(ptp->dxfer_olen, npc_up + SG_NVME_PT_DATA_LEN); + sg_put_unaligned_le64((sg_uintptr_t)ptp->dxferop, + npc_up + SG_NVME_PT_ADDR); + } + err = nvme_pt_low(fdc_p, dxferp, io_len, in_xfer, &npc, vb); + if (err < 0) { + if (vb > 1) + pr2ws("%s: do_nvme_pt() failed: %s (errno=%d)\n", + __func__, strerror(-err), -err); + return err; + } + sct_sc = err; /* ((SCT << 8) | SC) which may be 0 */ + fdc_p->nvme_status = sct_sc; + if (ptp->sense && (ptp->sense_len > 0)) { + uint32_t k = sizeof(fdc_p->cq_dw0_3); + + if ((int)k < ptp->sense_len) + ptp->sense_resid = ptp->sense_len - (int)k; + else { + k = ptp->sense_len; + ptp->sense_resid = 0; + } + memcpy(ptp->sense, fdc_p->cq_dw0_3, k); + } + if (in_xfer) + ptp->resid = 0; /* Just hoping ... */ + return sct_sc ? SG_LIB_NVME_STATUS : 0; +} + +#else /* if not(HAVE_NVME && (! IGNORE_NVME)) */ + +static int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int vb) +{ + if (vb) { + pr2ws("%s: not supported, ", __func__); +#ifdef HAVE_NVME + pr2ws("HAVE_NVME, "); +#else + pr2ws("don't HAVE_NVME, "); +#endif + +#ifdef IGNORE_NVME + pr2ws("IGNORE_NVME"); +#else + pr2ws("don't IGNORE_NVME"); +#endif + pr2ws("\n"); + if (NULL == vp) + pr2ws("%s: object pointer NULL; fd=%d\n", __func__, fd); + } + return -ENOTTY; /* inappropriate ioctl error */ +} + +#endif /* (HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/lib/sg_pt_linux.c b/lib/sg_pt_linux.c new file mode 100644 index 0000000..64956b3 --- /dev/null +++ b/lib/sg_pt_linux.c @@ -0,0 +1,955 @@ +/* + * 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. + */ + +/* sg_pt_linux version 1.43 20180603 */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* to define 'major' */ +#ifndef major +#include +#endif + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "sg_pt.h" +#include "sg_lib.h" +#include "sg_linux_inc.h" +#include "sg_pt_linux.h" +#include "sg_pr2serr.h" + + +#ifdef major +#define SG_DEV_MAJOR major +#else +#ifdef HAVE_LINUX_KDEV_T_H +#include +#endif +#define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */ +#endif + +#ifndef BLOCK_EXT_MAJOR +#define BLOCK_EXT_MAJOR 259 +#endif + +#define DEF_TIMEOUT 60000 /* 60,000 millisecs (60 seconds) */ + +static const char * linux_host_bytes[] = { + "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT", + "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR", + "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR", + "DID_IMM_RETRY", "DID_REQUEUE" /* 0xd */, + "DID_TRANSPORT_DISRUPTED", "DID_TRANSPORT_FAILFAST", + "DID_TARGET_FAILURE" /* 0x10 */, + "DID_NEXUS_FAILURE (reservation conflict)", + "DID_ALLOC_FAILURE", + "DID_MEDIUM_ERROR", +}; + +static const char * linux_driver_bytes[] = { + "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA", + "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD", + "DRIVER_SENSE" +}; + +#if 0 + +static const char * linux_driver_suggests[] = { + "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP", + "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN", + "SUGGEST_SENSE" +}; + +#endif + +/* + * These defines are for constants that should be visible in the + * /usr/include/scsi directory (brought in by sg_linux_inc.h). + * Redefined and aliased here to decouple this code from + * sg_io_linux.h N.B. the SUGGEST_* constants are no longer used. + */ +#ifndef DRIVER_MASK +#define DRIVER_MASK 0x0f +#endif +#ifndef SUGGEST_MASK +#define SUGGEST_MASK 0xf0 /* Suggest mask is obsolete */ +#endif +#ifndef DRIVER_SENSE +#define DRIVER_SENSE 0x08 +#endif +#define SG_LIB_DRIVER_MASK DRIVER_MASK +#define SG_LIB_SUGGEST_MASK SUGGEST_MASK +#define SG_LIB_DRIVER_SENSE DRIVER_SENSE + +bool sg_bsg_nvme_char_major_checked = false; +int sg_bsg_major = 0; +volatile int sg_nvme_char_major = 0; + +long sg_lin_page_size = 4096; /* default, overridden with correct value */ + + +/* This function only needs to be called once (unless a NVMe controller + * can be hot-plugged into system in which case it should be called + * (again) after that event). */ +void +sg_find_bsg_nvme_char_major(int verbose) +{ + bool got_one = false; + int n; + const char * proc_devices = "/proc/devices"; + char * cp; + FILE *fp; + char a[128]; + char b[128]; + + sg_lin_page_size = sysconf(_SC_PAGESIZE); + if (NULL == (fp = fopen(proc_devices, "r"))) { + if (verbose) + pr2ws("fopen %s failed: %s\n", proc_devices, strerror(errno)); + return; + } + while ((cp = fgets(b, sizeof(b), fp))) { + if ((1 == sscanf(b, "%126s", a)) && + (0 == memcmp(a, "Character", 9))) + break; + } + while (cp && (cp = fgets(b, sizeof(b), fp))) { + if (2 == sscanf(b, "%d %126s", &n, a)) { + if (0 == strcmp("bsg", a)) { + sg_bsg_major = n; + if (got_one) + break; + got_one = true; + } else if (0 == strcmp("nvme", a)) { + sg_nvme_char_major = n; + if (got_one) + break; + got_one = true; + } + } else + break; + } + if (verbose > 3) { + if (cp) { + if (sg_bsg_major > 0) + pr2ws("found sg_bsg_major=%d\n", sg_bsg_major); + if (sg_nvme_char_major > 0) + pr2ws("found sg_nvme_char_major=%d\n", sg_nvme_char_major); + } else + pr2ws("found no bsg not nvme char device in %s\n", proc_devices); + } + fclose(fp); +} + +/* Assumes that sg_find_bsg_nvme_char_major() has already been called. Returns + * true if dev_fd is a scsi generic pass-through device. If yields + * *is_nvme_p = true with *nsid_p = 0 then dev_fd is a NVMe char device. + * If yields *nsid_p > 0 then dev_fd is a NVMe block device. */ +static bool +check_file_type(int dev_fd, struct stat * dev_statp, bool * is_bsg_p, + bool * is_nvme_p, uint32_t * nsid_p, int * os_err_p, + int verbose) +{ + bool is_nvme = false; + bool is_sg = false; + bool is_bsg = false; + bool is_block = false; + int os_err = 0; + int major_num; + uint32_t nsid = 0; /* invalid NSID */ + + if (dev_fd >= 0) { + if (fstat(dev_fd, dev_statp) < 0) { + os_err = errno; + if (verbose) + pr2ws("%s: fstat() failed: %s (errno=%d)\n", __func__, + safe_strerror(os_err), os_err); + goto skip_out; + } + major_num = (int)SG_DEV_MAJOR(dev_statp->st_rdev); + if (S_ISCHR(dev_statp->st_mode)) { + if (SCSI_GENERIC_MAJOR == major_num) + is_sg = true; + else if (sg_bsg_major == major_num) + is_bsg = true; + else if (sg_nvme_char_major == major_num) + is_nvme = true; + } else if (S_ISBLK(dev_statp->st_mode)) { + is_block = true; + if (BLOCK_EXT_MAJOR == major_num) { + is_nvme = true; + nsid = ioctl(dev_fd, NVME_IOCTL_ID, NULL); + if (SG_NVME_BROADCAST_NSID == nsid) { /* means ioctl error */ + os_err = errno; + if (verbose) + pr2ws("%s: ioctl(NVME_IOCTL_ID) failed: %s " + "(errno=%d)\n", __func__, safe_strerror(os_err), + os_err); + } else + os_err = 0; + } + } + } else { + os_err = EBADF; + if (verbose) + pr2ws("%s: invalid file descriptor (%d)\n", __func__, dev_fd); + } +skip_out: + if (verbose > 3) { + pr2ws("%s: file descriptor is ", __func__); + if (is_sg) + pr2ws("sg device\n"); + else if (is_bsg) + pr2ws("bsg device\n"); + else if (is_nvme && (0 == nsid)) + pr2ws("NVMe char device\n"); + else if (is_nvme) + pr2ws("NVMe block device, nsid=%lld\n", + ((uint32_t)-1 == nsid) ? -1LL : (long long)nsid); + else if (is_block) + pr2ws("block device\n"); + else + pr2ws("undetermined device, could be regular file\n"); + } + if (is_bsg_p) + *is_bsg_p = is_bsg; + if (is_nvme_p) + *is_nvme_p = is_nvme; + if (nsid_p) + *nsid_p = nsid; + if (os_err_p) + *os_err_p = os_err; + return is_sg; +} + +/* Assumes dev_fd is an "open" file handle associated with device_name. If + * the implementation (possibly for one OS) cannot determine from dev_fd if + * a SCSI or NVMe pass-through is referenced, then it might guess based on + * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if + * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is + * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes + * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0. + * If error, returns negated errno (operating system) value. */ +int +check_pt_file_handle(int dev_fd, const char * device_name, int verbose) +{ + if (verbose > 4) + pr2ws("%s: dev_fd=%d, device_name: %s\n", __func__, dev_fd, + device_name); + /* Linux doesn't need device_name to determine which pass-through */ + if (! sg_bsg_nvme_char_major_checked) { + sg_bsg_nvme_char_major_checked = true; + sg_find_bsg_nvme_char_major(verbose); + } + if (dev_fd >= 0) { + bool is_sg, is_bsg, is_nvme; + int err; + uint32_t nsid; + struct stat a_stat; + + is_sg = check_file_type(dev_fd, &a_stat, &is_bsg, &is_nvme, &nsid, + &err, verbose); + if (err) + return -err; + else if (is_sg) + return 1; + else if (is_bsg) + return 2; + else if (is_nvme && (0 == nsid)) + return 3; + else if (is_nvme) + return 4; + else + return 0; + } else + return 0; +} + +/* + * We make a runtime decision whether to use the sg v3 interface or the sg + * v4 interface (currently exclusively used by the bsg driver). If all the + * following are true we use sg v4 which is only currently supported on bsg + * device nodes: + * a) there is a bsg entry in the /proc/devices file + * b) the device node given to scsi_pt_open() is a char device + * c) the char major number of the device node given to scsi_pt_open() + * matches the char major number of the bsg entry in /proc/devices + * Otherwise the sg v3 interface is used. + * + * Note that in either case we prepare the data in a sg v4 structure. If + * the runtime tests indicate that the v3 interface is needed then + * do_scsi_pt_v3() transfers the input data into a v3 structure and + * then the output data is transferred back into a sg v4 structure. + * That implementation detail could change in the future. + * + * [20120806] Only use MAJOR() macro in kdev_t.h if that header file is + * available and major() macro [N.B. lower case] is not available. + */ + + +#ifdef major +#define SG_DEV_MAJOR major +#else +#ifdef HAVE_LINUX_KDEV_T_H +#include +#endif +#define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */ +#endif + + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */ +/* together. The 'flags' argument is advisory and may be ignored. */ +/* Returns >= 0 if successful, otherwise returns negated errno. */ +int +scsi_pt_open_flags(const char * device_name, int flags, int verbose) +{ + int fd; + + if (! sg_bsg_nvme_char_major_checked) { + sg_bsg_nvme_char_major_checked = true; + sg_find_bsg_nvme_char_major(verbose); + } + if (verbose > 1) { + pr2ws("open %s with flags=0x%x\n", device_name, flags); + } + fd = open(device_name, flags); + if (fd < 0) { + fd = -errno; + if (verbose > 1) + pr2ws("%s: open(%s, 0x%x) failed: %s\n", __func__, device_name, + flags, safe_strerror(-fd)); + } + return fd; +} + +/* Returns >= 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_open_device(const char * device_name, bool read_only, int verbose) +{ + int oflags = O_NONBLOCK; + + oflags |= (read_only ? O_RDONLY : O_RDWR); + return scsi_pt_open_flags(device_name, oflags, verbose); +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_close_device(int device_fd) +{ + int res; + + res = close(device_fd); + if (res < 0) + res = -errno; + return res; +} + +#if (HAVE_NVME && (! IGNORE_NVME)) +static bool checked_ev_dsense = false; +static bool ev_dsense = false; +#endif + + +/* Caller should additionally call get_scsi_pt_os_err() after this call */ +struct sg_pt_base * +construct_scsi_pt_obj_with_fd(int dev_fd, int verbose) +{ + int err; + struct sg_pt_linux_scsi * ptp; + + ptp = (struct sg_pt_linux_scsi *) + calloc(1, sizeof(struct sg_pt_linux_scsi)); + if (ptp) { +#if (HAVE_NVME && (! IGNORE_NVME)) + sntl_init_dev_stat(&ptp->dev_stat); + if (! checked_ev_dsense) { + ev_dsense = sg_get_initial_dsense(); + checked_ev_dsense = true; + } + ptp->dev_stat.scsi_dsense = ev_dsense; +#endif + err = set_pt_file_handle((struct sg_pt_base *)ptp, dev_fd, verbose); + if ((0 == err) && (! ptp->is_nvme)) { + ptp->io_hdr.guard = 'Q'; +#ifdef BSG_PROTOCOL_SCSI + ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI; +#endif +#ifdef BSG_SUB_PROTOCOL_SCSI_CMD + ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; +#endif + } + } else if (verbose) + pr2ws("%s: calloc() failed, out of memory?\n", __func__); + + return (struct sg_pt_base *)ptp; +} + +struct sg_pt_base * +construct_scsi_pt_obj() +{ + return construct_scsi_pt_obj_with_fd(-1 /* dev_fd */, 0 /* verbose */); +} + +void +destruct_scsi_pt_obj(struct sg_pt_base * vp) +{ + + if (NULL == vp) + pr2ws(">>>>>>> Warning: %s called with NULL pointer\n", __func__); + else { + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (ptp->free_nvme_id_ctlp) { + free(ptp->free_nvme_id_ctlp); + ptp->free_nvme_id_ctlp = NULL; + ptp->nvme_id_ctlp = NULL; + } + if (ptp) + free(ptp); + } +} + +/* Remembers previous device file descriptor */ +void +clear_scsi_pt_obj(struct sg_pt_base * vp) +{ + bool is_sg, is_bsg, is_nvme; + int fd; + uint32_t nvme_nsid; + struct sg_sntl_dev_state_t dev_stat; + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (ptp) { + fd = ptp->dev_fd; + is_sg = ptp->is_sg; + is_bsg = ptp->is_bsg; + is_nvme = ptp->is_nvme; + nvme_nsid = ptp->nvme_nsid; + dev_stat = ptp->dev_stat; + if (ptp->free_nvme_id_ctlp) + free(ptp->free_nvme_id_ctlp); + memset(ptp, 0, sizeof(struct sg_pt_linux_scsi)); + ptp->io_hdr.guard = 'Q'; +#ifdef BSG_PROTOCOL_SCSI + ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI; +#endif +#ifdef BSG_SUB_PROTOCOL_SCSI_CMD + ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; +#endif + ptp->dev_fd = fd; + ptp->is_sg = is_sg; + ptp->is_bsg = is_bsg; + ptp->is_nvme = is_nvme; + ptp->nvme_direct = false; + ptp->nvme_nsid = nvme_nsid; + ptp->dev_stat = dev_stat; + } +} + +/* Forget any previous dev_fd and install the one given. May attempt to + * find file type (e.g. if pass-though) from OS so there could be an error. + * Returns 0 for success or the same value as get_scsi_pt_os_err() + * will return. dev_fd should be >= 0 for a valid file handle or -1 . */ +int +set_pt_file_handle(struct sg_pt_base * vp, int dev_fd, int verbose) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + struct stat a_stat; + + if (! sg_bsg_nvme_char_major_checked) { + sg_bsg_nvme_char_major_checked = true; + sg_find_bsg_nvme_char_major(verbose); + } + ptp->dev_fd = dev_fd; + if (dev_fd >= 0) + ptp->is_sg = check_file_type(dev_fd, &a_stat, &ptp->is_bsg, + &ptp->is_nvme, &ptp->nvme_nsid, + &ptp->os_err, verbose); + else { + ptp->is_sg = false; + ptp->is_bsg = false; + ptp->is_nvme = false; + ptp->nvme_direct = false; + ptp->nvme_nsid = 0; + ptp->os_err = 0; + } + return ptp->os_err; +} + +/* Valid file handles (which is the return value) are >= 0 . Returns -1 + * if there is no valid file handle. */ +int +get_pt_file_handle(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->dev_fd; +} + +void +set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb, + int cdb_len) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (ptp->io_hdr.request) + ++ptp->in_err; + ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb; + ptp->io_hdr.request_len = cdb_len; +} + +void +set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, + int max_sense_len) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (ptp->io_hdr.response) + ++ptp->in_err; + memset(sense, 0, max_sense_len); + ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense; + ptp->io_hdr.max_response_len = max_sense_len; +} + +/* Setup for data transfer from device */ +void +set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp, + int dxfer_ilen) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (ptp->io_hdr.din_xferp) + ++ptp->in_err; + if (dxfer_ilen > 0) { + ptp->io_hdr.din_xferp = (__u64)(sg_uintptr_t)dxferp; + ptp->io_hdr.din_xfer_len = dxfer_ilen; + } +} + +/* Setup for data transfer toward device */ +void +set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp, + int dxfer_olen) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (ptp->io_hdr.dout_xferp) + ++ptp->in_err; + if (dxfer_olen > 0) { + ptp->io_hdr.dout_xferp = (__u64)(sg_uintptr_t)dxferp; + ptp->io_hdr.dout_xfer_len = dxfer_olen; + } +} + +void +set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * dxferp, + uint32_t dxfer_len, bool out_true) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (dxfer_len > 0) { + ptp->mdxferp = dxferp; + ptp->mdxfer_len = dxfer_len; + ptp->mdxfer_out = out_true; + } +} + +void +set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + ptp->io_hdr.spare_in = pack_id; +} + +void +set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + ptp->io_hdr.request_tag = tag; +} + +/* Note that task management function codes are transport specific */ +void +set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + ptp->io_hdr.subprotocol = 1; /* SCSI task management function */ + ptp->tmf_request[0] = (uint8_t)tmf_code; /* assume it fits */ + ptp->io_hdr.request = (__u64)(sg_uintptr_t)(&(ptp->tmf_request[0])); + ptp->io_hdr.request_len = 1; +} + +void +set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + ptp->io_hdr.request_attr = attribute; + ptp->io_hdr.request_priority = priority; +} + +#ifndef BSG_FLAG_Q_AT_TAIL +#define BSG_FLAG_Q_AT_TAIL 0x10 +#endif +#ifndef BSG_FLAG_Q_AT_HEAD +#define BSG_FLAG_Q_AT_HEAD 0x20 +#endif + +/* Need this later if translated to v3 interface */ +#ifndef SG_FLAG_Q_AT_TAIL +#define SG_FLAG_Q_AT_TAIL 0x10 +#endif +#ifndef SG_FLAG_Q_AT_HEAD +#define SG_FLAG_Q_AT_HEAD 0x20 +#endif + +void +set_scsi_pt_flags(struct sg_pt_base * vp, int flags) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + /* default action of bsg driver (sg v4) is QUEUE_AT_HEAD */ + /* default action of block layer SG_IO ioctl is QUEUE_AT_TAIL */ + if (SCSI_PT_FLAGS_QUEUE_AT_HEAD & flags) { /* favour AT_HEAD */ + ptp->io_hdr.flags |= BSG_FLAG_Q_AT_HEAD; + ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_TAIL; + } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) { + ptp->io_hdr.flags |= BSG_FLAG_Q_AT_TAIL; + ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_HEAD; + } +} + +/* N.B. Returns din_resid and ignores dout_resid */ +int +get_scsi_pt_resid(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (NULL == ptp) + return 0; + return ptp->nvme_direct ? 0 : ptp->io_hdr.din_resid; +} + +int +get_scsi_pt_status_response(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (NULL == ptp) + return 0; + return (int)(ptp->nvme_direct ? ptp->nvme_status : + ptp->io_hdr.device_status); +} + +uint32_t +get_pt_result(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + if (NULL == ptp) + return 0; + return ptp->nvme_direct ? ptp->nvme_result : + ptp->io_hdr.device_status; +} + +int +get_scsi_pt_sense_len(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->io_hdr.response_len; +} + +int +get_scsi_pt_duration_ms(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->io_hdr.duration; +} + +int +get_scsi_pt_transport_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->io_hdr.transport_status; +} + +void +set_scsi_pt_transport_err(struct sg_pt_base * vp, int err) +{ + struct sg_pt_linux_scsi * ptp = &vp->impl; + + ptp->io_hdr.transport_status = err; +} + +/* Returns b which will contain a null char terminated string (if + * max_b_len > 0). Combined driver and transport (called "host" in Linux + * kernel) statuses */ +char * +get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, + char * b) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + int ds = ptp->io_hdr.driver_status; + int hs = ptp->io_hdr.transport_status; + int n, m; + char * cp = b; + int driv; + const char * driv_cp = "invalid"; + + if (max_b_len < 1) + return b; + m = max_b_len; + n = 0; + if (hs) { + if ((hs < 0) || (hs >= (int)SG_ARRAY_SIZE(linux_host_bytes))) + n = snprintf(cp, m, "Host_status=0x%02x is invalid\n", hs); + else + n = snprintf(cp, m, "Host_status=0x%02x [%s]\n", hs, + linux_host_bytes[hs]); + } + m -= n; + if (m < 1) { + b[max_b_len - 1] = '\0'; + return b; + } + cp += n; + driv = ds & SG_LIB_DRIVER_MASK; + if (driv < (int)SG_ARRAY_SIZE(linux_driver_bytes)) + driv_cp = linux_driver_bytes[driv]; +#if 0 + sugg = (ds & SG_LIB_SUGGEST_MASK) >> 4; + if (sugg < SG_ARRAY_SIZE(linux_driver_suggests) + sugg_cp = linux_driver_suggests[sugg]; +#endif + n = snprintf(cp, m, "Driver_status=0x%02x [%s]\n", ds, driv_cp); + m -= n; + if (m < 1) + b[max_b_len - 1] = '\0'; + return b; +} + +int +get_scsi_pt_result_category(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + int dr_st = ptp->io_hdr.driver_status & SG_LIB_DRIVER_MASK; + int scsi_st = ptp->io_hdr.device_status & 0x7e; + + if (ptp->os_err) + return SCSI_PT_RESULT_OS_ERR; + else if (ptp->io_hdr.transport_status) + return SCSI_PT_RESULT_TRANSPORT_ERR; + else if (dr_st && (SG_LIB_DRIVER_SENSE != dr_st)) + return SCSI_PT_RESULT_TRANSPORT_ERR; + else if ((SG_LIB_DRIVER_SENSE == dr_st) || + (SAM_STAT_CHECK_CONDITION == scsi_st) || + (SAM_STAT_COMMAND_TERMINATED == scsi_st)) + return SCSI_PT_RESULT_SENSE; + else if (scsi_st) + return SCSI_PT_RESULT_STATUS; + else + return SCSI_PT_RESULT_GOOD; +} + +int +get_scsi_pt_os_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->os_err; +} + +char * +get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + const char * cp; + + cp = safe_strerror(ptp->os_err); + strncpy(b, cp, max_b_len); + if ((int)strlen(cp) >= max_b_len) + b[max_b_len - 1] = '\0'; + return b; +} + +bool +pt_device_is_nvme(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->is_nvme; +} + +/* If a NVMe block device (which includes the NSID) handle is associated + * with 'vp', then its NSID is returned (values range from 0x1 to + * 0xffffffe). Otherwise 0 is returned. */ +uint32_t +get_pt_nvme_nsid(const struct sg_pt_base * vp) +{ + const struct sg_pt_linux_scsi * ptp = &vp->impl; + + return ptp->nvme_nsid; +} + +/* Executes SCSI command using sg v3 interface */ +static int +do_scsi_pt_v3(struct sg_pt_linux_scsi * ptp, int fd, int time_secs, + int verbose) +{ + struct sg_io_hdr v3_hdr; + + memset(&v3_hdr, 0, sizeof(v3_hdr)); + /* convert v4 to v3 header */ + v3_hdr.interface_id = 'S'; + v3_hdr.dxfer_direction = SG_DXFER_NONE; + v3_hdr.cmdp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.request; + v3_hdr.cmd_len = (uint8_t)ptp->io_hdr.request_len; + if (ptp->io_hdr.din_xfer_len > 0) { + if (ptp->io_hdr.dout_xfer_len > 0) { + if (verbose) + pr2ws("sgv3 doesn't support bidi\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + v3_hdr.dxferp = (void *)(long)ptp->io_hdr.din_xferp; + v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.din_xfer_len; + v3_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + } else if (ptp->io_hdr.dout_xfer_len > 0) { + v3_hdr.dxferp = (void *)(long)ptp->io_hdr.dout_xferp; + v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.dout_xfer_len; + v3_hdr.dxfer_direction = SG_DXFER_TO_DEV; + } + if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) { + v3_hdr.sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response; + v3_hdr.mx_sb_len = (uint8_t)ptp->io_hdr.max_response_len; + } + v3_hdr.pack_id = (int)ptp->io_hdr.spare_in; + if (BSG_FLAG_Q_AT_HEAD & ptp->io_hdr.flags) + v3_hdr.flags |= SG_FLAG_Q_AT_HEAD; /* favour AT_HEAD */ + else if (BSG_FLAG_Q_AT_TAIL & ptp->io_hdr.flags) + v3_hdr.flags |= SG_FLAG_Q_AT_TAIL; + + if (NULL == v3_hdr.cmdp) { + if (verbose) + pr2ws("No SCSI command (cdb) given\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + /* io_hdr.timeout is in milliseconds, if greater than zero */ + v3_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT); + /* Finally do the v3 SG_IO ioctl */ + if (ioctl(fd, SG_IO, &v3_hdr) < 0) { + ptp->os_err = errno; + if (verbose > 1) + pr2ws("ioctl(SG_IO v3) failed: %s (errno=%d)\n", + safe_strerror(ptp->os_err), ptp->os_err); + return -ptp->os_err; + } + ptp->io_hdr.device_status = (__u32)v3_hdr.status; + ptp->io_hdr.driver_status = (__u32)v3_hdr.driver_status; + ptp->io_hdr.transport_status = (__u32)v3_hdr.host_status; + ptp->io_hdr.response_len = (__u32)v3_hdr.sb_len_wr; + ptp->io_hdr.duration = (__u32)v3_hdr.duration; + ptp->io_hdr.din_resid = (__s32)v3_hdr.resid; + /* v3_hdr.info not passed back since no mapping defined (yet) */ + return 0; +} + +/* Executes SCSI command (or at least forwards it to lower layers). + * Returns 0 for success, negative numbers are negated 'errno' values from + * OS system calls. Positive return values are errors from this package. */ +int +do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose) +{ + int err; + struct sg_pt_linux_scsi * ptp = &vp->impl; + bool have_checked_for_type = (ptp->dev_fd >= 0); + + if (! sg_bsg_nvme_char_major_checked) { + sg_bsg_nvme_char_major_checked = true; + sg_find_bsg_nvme_char_major(verbose); + } + if (ptp->in_err) { + if (verbose) + pr2ws("Replicated or unused set_scsi_pt... functions\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (fd >= 0) { + if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) { + if (verbose) + pr2ws("%s: file descriptor given to create() and here " + "differ\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + ptp->dev_fd = fd; + } else if (ptp->dev_fd < 0) { + if (verbose) + pr2ws("%s: invalid file descriptors\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } else + fd = ptp->dev_fd; + if (! have_checked_for_type) { + err = set_pt_file_handle(vp, ptp->dev_fd, verbose); + if (err) + return -ptp->os_err; + } + if (ptp->os_err) + return -ptp->os_err; + if (ptp->is_nvme) + return sg_do_nvme_pt(vp, -1, time_secs, verbose); + else if (sg_bsg_major <= 0) + return do_scsi_pt_v3(ptp, fd, time_secs, verbose); + else if (ptp->is_bsg) + ; /* drop through to sg v4 implementation */ + else + return do_scsi_pt_v3(ptp, fd, time_secs, verbose); + + if (! ptp->io_hdr.request) { + if (verbose) + pr2ws("No SCSI command (cdb) given (v4)\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + /* io_hdr.timeout is in milliseconds */ + ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : + DEF_TIMEOUT); +#if 0 + /* sense buffer already zeroed */ + if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) { + void * p; + + p = (void *)(sg_uintptr_t)ptp->io_hdr.response; + memset(p, 0, ptp->io_hdr.max_response_len); + } +#endif + if (ioctl(fd, SG_IO, &ptp->io_hdr) < 0) { + ptp->os_err = errno; + if (verbose > 1) + pr2ws("ioctl(SG_IO v4) failed: %s (errno=%d)\n", + safe_strerror(ptp->os_err), ptp->os_err); + return -ptp->os_err; + } + return 0; +} diff --git a/lib/sg_pt_linux_nvme.c b/lib/sg_pt_linux_nvme.c new file mode 100644 index 0000000..18b1374 --- /dev/null +++ b/lib/sg_pt_linux_nvme.c @@ -0,0 +1,1291 @@ +/* + * Copyright (c) 2017-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * The code to use the NVMe Management Interface (MI) SES pass-through + * was provided by WDC in November 2017. + */ + +/* + * Copyright 2017, Western Digital Corporation + * + * Written by Berck Nash + * + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * Based on the NVM-Express command line utility, which bore the following + * notice: + * + * Copyright (c) 2014-2015, Intel Corporation. + * + * Written by Keith Busch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* sg_pt_linux_nvme version 1.05 20180602 */ + +/* This file contains a small "SPC-only" SNTL to support the SES pass-through + * of SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC RESULTS through NVME-MI + * SES Send and SES Receive. */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include /* to define 'major' */ +#ifndef major +#include +#endif + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "sg_pt.h" +#include "sg_lib.h" +#include "sg_linux_inc.h" +#include "sg_pt_linux.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +#define SCSI_INQUIRY_OPC 0x12 +#define SCSI_REPORT_LUNS_OPC 0xa0 +#define SCSI_TEST_UNIT_READY_OPC 0x0 +#define SCSI_REQUEST_SENSE_OPC 0x3 +#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d +#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c +#define SCSI_MAINT_IN_OPC 0xa3 +#define SCSI_REP_SUP_OPCS_OPC 0xc +#define SCSI_REP_SUP_TMFS_OPC 0xd +#define SCSI_MODE_SENSE10_OPC 0x5a +#define SCSI_MODE_SELECT10_OPC 0x55 + +/* Additional Sense Code (ASC) */ +#define NO_ADDITIONAL_SENSE 0x0 +#define LOGICAL_UNIT_NOT_READY 0x4 +#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8 +#define UNRECOVERED_READ_ERR 0x11 +#define PARAMETER_LIST_LENGTH_ERR 0x1a +#define INVALID_OPCODE 0x20 +#define LBA_OUT_OF_RANGE 0x21 +#define INVALID_FIELD_IN_CDB 0x24 +#define INVALID_FIELD_IN_PARAM_LIST 0x26 +#define UA_RESET_ASC 0x29 +#define UA_CHANGED_ASC 0x2a +#define TARGET_CHANGED_ASC 0x3f +#define LUNS_CHANGED_ASCQ 0x0e +#define INSUFF_RES_ASC 0x55 +#define INSUFF_RES_ASCQ 0x3 +#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */ +#define POWER_ON_RESET_ASCQ 0x0 +#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */ +#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */ +#define CAPACITY_CHANGED_ASCQ 0x9 +#define SAVING_PARAMS_UNSUP 0x39 +#define TRANSPORT_PROBLEM 0x4b +#define THRESHOLD_EXCEEDED 0x5d +#define LOW_POWER_COND_ON 0x5e +#define MISCOMPARE_VERIFY_ASC 0x1d +#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ +#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 + + + +#if (HAVE_NVME && (! IGNORE_NVME)) + +/* This trims given NVMe block device name in Linux (e.g. /dev/nvme0n1p5) + * to the name of its associated char device (e.g. /dev/nvme0). If this + * occurs true is returned and the char device name is placed in 'b' (as + * long as b_len is sufficient). Otherwise false is returned. */ +bool +sg_get_nvme_char_devname(const char * nvme_block_devname, uint32_t b_len, + char * b) +{ + uint32_t n, tlen; + const char * cp; + char buff[8]; + + if ((NULL == b) || (b_len < 5)) + return false; /* degenerate cases */ + cp = strstr(nvme_block_devname, "nvme"); + if (NULL == cp) + return false; /* expected to find "nvme" in given name */ + if (1 != sscanf(cp, "nvme%u", &n)) + return false; /* didn't find valid "nvme" */ + snprintf(buff, sizeof(buff), "%u", n); + tlen = (cp - nvme_block_devname) + 4 + strlen(buff); + if ((tlen + 1) > b_len) + return false; /* b isn't long enough to fit output */ + memcpy(b, nvme_block_devname, tlen); + b[tlen] = '\0'; + return true; +} + +static void +mk_sense_asc_ascq(struct sg_pt_linux_scsi * ptp, int sk, int asc, int ascq, + int vb) +{ + bool dsense = !! ptp->dev_stat.scsi_dsense; + int n; + uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response; + + ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? n : ((n < 18) ? n : 18); + memset(sbp, 0, n); + sg_build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk, + asc, ascq); +} + +static void +mk_sense_from_nvme_status(struct sg_pt_linux_scsi * ptp, int vb) +{ + bool ok; + bool dsense = !! ptp->dev_stat.scsi_dsense; + int n; + uint8_t sstatus, sk, asc, ascq; + uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response; + + ok = sg_nvme_status2scsi(ptp->nvme_status, &sstatus, &sk, &asc, &ascq); + if (! ok) { /* can't find a mapping to a SCSI error, so ... */ + sstatus = SAM_STAT_CHECK_CONDITION; + sk = SPC_SK_ILLEGAL_REQUEST; + asc = 0xb; + ascq = 0x0; /* asc: "WARNING" purposely vague */ + } + + ptp->io_hdr.device_status = sstatus; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? n : ((n < 18) ? n : 18); + memset(sbp, 0, n); + sg_build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (dsense && (ptp->nvme_status > 0)) + sg_nvme_desc2sense(sbp, ptp->nvme_stat_dnr, ptp->nvme_stat_more, + ptp->nvme_status); + if (vb > 3) + pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n", + __func__, sstatus, sk, asc, ascq); +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_invalid_fld(struct sg_pt_linux_scsi * ptp, bool in_cdb, int in_byte, + int in_bit, int vb) +{ + bool dsense = !! ptp->dev_stat.scsi_dsense; + int sl, asc, n; + uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response; + uint8_t sks[4]; + + ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION; + asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST; + n = ptp->io_hdr.max_response_len; + if ((n < 8) || ((! dsense) && (n < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, n); + return; + } else + ptp->io_hdr.response_len = dsense ? n : ((n < 18) ? n : 18); + + memset(sbp, 0, n); + sg_build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0); + memset(sks, 0, sizeof(sks)); + sks[0] = 0x80; + if (in_cdb) + sks[0] |= 0x40; + if (in_bit >= 0) { + sks[0] |= 0x8; + sks[0] |= (0x7 & in_bit); + } + sg_put_unaligned_be16(in_byte, sks + 1); + if (dsense) { + sl = sbp[7] + 8; + sbp[7] = sl; + sbp[sl] = 0x2; + sbp[sl + 1] = 0x6; + memcpy(sbp + sl + 4, sks, 3); + } else + memcpy(sbp + 15, sks, 3); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n", + __func__, asc, in_cdb ? 'C' : 'D', in_byte, + ((in_bit > 0) ? (0x7 & in_bit) : 0)); +} + +/* Returns 0 for success. Returns SG_LIB_NVME_STATUS if there is non-zero + * NVMe status (from the completion queue) with the value placed in + * ptp->nvme_status. If Unix error from ioctl then return negated value + * (equivalent -errno from basic Unix system functions like open()). + * CDW0 from the completion queue is placed in ptp->nvme_result in the + * absence of a Unix error. If time_secs is negative it is treated as + * a timeout in milliseconds (of abs(time_secs) ). */ +static int +do_nvme_admin_cmd(struct sg_pt_linux_scsi * ptp, + struct sg_nvme_passthru_cmd *cmdp, void * dp, bool is_read, + int time_secs, int vb) +{ + const uint32_t cmd_len = sizeof(struct sg_nvme_passthru_cmd); + int res; + uint32_t n; + uint16_t sct_sc; + const uint8_t * up = ((const uint8_t *)cmdp) + SG_NVME_PT_OPCODE; + char nam[64]; + + if (vb) + sg_get_nvme_opcode_name(*up, true, sizeof(nam), nam); + else + nam[0] = '\0'; + cmdp->timeout_ms = (time_secs < 0) ? (-time_secs) : (1000 * time_secs); + ptp->os_err = 0; + if (vb > 2) { + pr2ws("NVMe Admin command: %s\n", nam); + hex2stderr((const uint8_t *)cmdp, cmd_len, 1); + if ((vb > 3) && (! is_read) && dp) { + uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN); + + if (len > 0) { + n = len; + if ((len < 512) || (vb > 5)) + pr2ws("\nData-out buffer (%u bytes):\n", n); + else { + pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n); + n = 512; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + } + res = ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, cmdp); + if (res < 0) { /* OS error (errno negated) */ + ptp->os_err = -res; + if (vb > 1) { + pr2ws("%s: ioctl for %s [0x%x] failed: %s " + "(errno=%d)\n", __func__, nam, *up, strerror(-res), -res); + } + return res; + } + + /* Now res contains NVMe completion queue CDW3 31:17 (15 bits) */ + ptp->nvme_result = cmdp->result; + if (ptp->nvme_direct && ptp->io_hdr.response && + (ptp->io_hdr.max_response_len > 3)) { + /* build 32 byte "sense" buffer */ + uint8_t * sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response; + uint16_t st = (uint16_t)res; + + n = ptp->io_hdr.max_response_len; + n = (n < 32) ? n : 32; + memset(sbp, 0 , n); + ptp->io_hdr.response_len = n; + sg_put_unaligned_le32(cmdp->result, + sbp + SG_NVME_PT_CQ_RESULT); + if (n > 15) /* LSBit will be 0 (Phase bit) after (st << 1) */ + sg_put_unaligned_le16(st << 1, sbp + SG_NVME_PT_CQ_STATUS_P); + } + /* clear upper bits (DNR and More) leaving ((SCT << 8) | SC) */ + sct_sc = 0x7ff & res; /* 11 bits */ + ptp->nvme_status = sct_sc; + ptp->nvme_stat_dnr = !!(0x4000 & res); + ptp->nvme_stat_more = !!(0x2000 & res); + if (sct_sc) { /* when non-zero, treat as command error */ + if (vb > 1) { + char b[80]; + + pr2ws("%s: ioctl for %s [0x%x] failed, status: %s [0x%x]\n", + __func__, nam, *up, + sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b), sct_sc); + } + return SG_LIB_NVME_STATUS; /* == SCSI_PT_DO_NVME_STATUS */ + } + if ((vb > 3) && is_read && dp) { + uint32_t len = sg_get_unaligned_le32(up + SG_NVME_PT_DATA_LEN); + + if (len > 0) { + n = len; + if ((len < 1024) || (vb > 5)) + pr2ws("\nData-in buffer (%u bytes):\n", n); + else { + pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n); + n = 1024; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + return 0; +} + +static void +sntl_check_enclosure_override(struct sg_pt_linux_scsi * ptp, int vb) +{ + uint8_t * up = ptp->nvme_id_ctlp; + uint8_t nvmsr; + + if (NULL == up) + return; + nvmsr = up[253]; + if (vb > 3) + pr2ws("%s: enter, nvmsr=%u\n", __func__, nvmsr); + ptp->dev_stat.id_ctl253 = nvmsr; + switch (ptp->dev_stat.enclosure_override) { + case 0x0: /* no override */ + if (0x3 & nvmsr) { + ptp->dev_stat.pdt = PDT_DISK; + ptp->dev_stat.enc_serv = 1; + } else if (0x2 & nvmsr) { + ptp->dev_stat.pdt = PDT_SES; + ptp->dev_stat.enc_serv = 1; + } else if (0x1 & nvmsr) { + ptp->dev_stat.pdt = PDT_DISK; + ptp->dev_stat.enc_serv = 0; + } else { + uint32_t nn = sg_get_unaligned_le32(up + 516); + + ptp->dev_stat.pdt = nn ? PDT_DISK : PDT_UNKNOWN; + ptp->dev_stat.enc_serv = 0; + } + break; + case 0x1: /* override to SES device */ + ptp->dev_stat.pdt = PDT_SES; + ptp->dev_stat.enc_serv = 1; + break; + case 0x2: /* override to disk with attached SES device */ + ptp->dev_stat.pdt = PDT_DISK; + ptp->dev_stat.enc_serv = 1; + break; + case 0x3: /* override to SAFTE device (PDT_PROCESSOR) */ + ptp->dev_stat.pdt = PDT_PROCESSOR; + ptp->dev_stat.enc_serv = 1; + break; + case 0xff: /* override to normal disk */ + ptp->dev_stat.pdt = PDT_DISK; + ptp->dev_stat.enc_serv = 0; + break; + default: + pr2ws("%s: unknown enclosure_override value: %d\n", __func__, + ptp->dev_stat.enclosure_override); + break; + } +} + + +/* Currently only caches associated identify controller response (4096 bytes). + * Returns 0 on success; otherwise a positive value is returned */ +static int +sntl_cache_identity(struct sg_pt_linux_scsi * ptp, int time_secs, int vb) +{ + int ret; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * up; + struct sg_nvme_passthru_cmd cmd; + + up = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp, false); + ptp->nvme_id_ctlp = up; + if (NULL == up) { + pr2ws("%s: sg_memalign() failed to get memory\n", __func__); + return sg_convert_errno(ENOMEM); + } + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x6; /* Identify */ + cmd.cdw10 = 0x1; /* CNS=0x1 Identify controller */ + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->nvme_id_ctlp; + cmd.data_len = pg_sz; + ret = do_nvme_admin_cmd(ptp, &cmd, up, true, time_secs, vb); + if (0 == ret) + sntl_check_enclosure_override(ptp, vb); + return (ret < 0) ? sg_convert_errno(-ret) : ret; +} + +static const char * nvme_scsi_vendor_str = "NVMe "; +static const uint16_t inq_resp_len = 36; + +static int +sntl_inq(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs, + int vb) +{ + bool evpd; + bool cp_id_ctl = false; + int res; + uint16_t n, alloc_len, pg_cd; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * nvme_id_ns = NULL; + uint8_t * free_nvme_id_ns = NULL; + uint8_t inq_dout[256]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */ + mk_sense_invalid_fld(ptp, true, 1, 1, vb); + return 0; + } + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) /* should be negative errno */ + return res; + } + memset(inq_dout, 0, sizeof(inq_dout)); + alloc_len = sg_get_unaligned_be16(cdbp + 3); + evpd = !!(0x1 & cdbp[1]); + pg_cd = cdbp[2]; + if (evpd) { /* VPD page responses */ + switch (pg_cd) { + case 0: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 11; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x0; + inq_dout[5] = 0x80; + inq_dout[6] = 0x83; + inq_dout[7] = 0x86; + inq_dout[8] = 0x87; + inq_dout[9] = 0x92; + inq_dout[n - 1] = SG_NVME_VPD_NICR; /* last VPD number */ + break; + case 0x80: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 24; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20); /* SN */ + break; + case 0x83: + if ((ptp->nvme_nsid > 0) && + (ptp->nvme_nsid < SG_NVME_BROADCAST_NSID)) { + nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns, + false); + if (nvme_id_ns) { + struct sg_nvme_passthru_cmd cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x6; /* Identify */ + cmd.nsid = ptp->nvme_nsid; + cmd.cdw10 = 0x0; /* CNS=0x0 Identify namespace */ + cmd.addr = (uint64_t)(sg_uintptr_t)nvme_id_ns; + cmd.data_len = pg_sz; + res = do_nvme_admin_cmd(ptp, &cmd, nvme_id_ns, true, + time_secs, vb > 3); + if (res) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + } + } + n = sg_make_vpd_devid_for_nvme(ptp->nvme_id_ctlp, nvme_id_ns, + 0 /* pdt */, -1 /*tproto */, + inq_dout, sizeof(inq_dout)); + if (n > 3) + sg_put_unaligned_be16(n - 4, inq_dout + 2); + if (free_nvme_id_ns) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + break; + case 0x86: /* Extended INQUIRY (per SFS SPC Discovery 2016) */ + inq_dout[1] = pg_cd; + n = 64; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[5] = 0x1; /* SIMPSUP=1 */ + inq_dout[7] = 0x1; /* LUICLR=1 */ + inq_dout[13] = 0x40; /* max supported sense data length */ + break; + case 0x87: /* Mode page policy (per SFS SPC Discovery 2016) */ + inq_dout[1] = pg_cd; + n = 8; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x3f; /* all mode pages */ + inq_dout[5] = 0xff; /* and their sub-pages */ + inq_dout[6] = 0x80; /* MLUS=1, policy=shared */ + break; + case 0x92: /* SCSI Feature set: only SPC Discovery 2016 */ + inq_dout[1] = pg_cd; + n = 10; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[9] = 0x1; /* SFS SPC Discovery 2016 */ + break; + case SG_NVME_VPD_NICR: /* 0xde (vendor (sg3_utils) specific) */ + inq_dout[1] = pg_cd; + sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2); + n = 16 + 4096; + cp_id_ctl = true; + break; + default: /* Point to page_code field in cdb */ + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) { + uint8_t * dp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp; + + if (cp_id_ctl) { + memcpy(dp, inq_dout, (n < 16 ? n : 16)); + if (n > 16) + memcpy(dp + 16, ptp->nvme_id_ctlp, n - 16); + } else + memcpy(dp, inq_dout, n); + } + } + } else { /* Standard INQUIRY response */ + /* pdt=0 --> disk; pdt=0xd --> SES; pdt=3 --> processor (safte) */ + inq_dout[0] = (0x1f & ptp->dev_stat.pdt); /* (PQ=0)<<5 */ + /* inq_dout[1] = (RMD=0)<<7 | (LU_CONG=0)<<6; rest reserved */ + inq_dout[2] = 6; /* version: SPC-4 */ + inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */ + inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */ + inq_dout[6] = ptp->dev_stat.enc_serv ? 0x40 : 0; + inq_dout[7] = 0x2; /* CMDQUE=1 */ + memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */ + memcpy(inq_dout + 16, ptp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */ + memcpy(inq_dout + 32, ptp->nvme_id_ctlp + 64, 4); /* Rev <-- FR */ + if (alloc_len > 0) { + n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, + inq_dout, n); + } + } + return 0; +} + +static int +sntl_rluns(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int time_secs, + int vb) +{ + int res; + uint16_t sel_report; + uint32_t alloc_len, k, n, num, max_nsid; + uint8_t * rl_doutp; + uint8_t * up; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + sel_report = cdbp[2]; + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + max_nsid = sg_get_unaligned_le32(ptp->nvme_id_ctlp + 516); + switch (sel_report) { + case 0: + case 2: + num = max_nsid; + break; + case 1: + case 0x10: + case 0x12: + num = 0; + break; + case 0x11: + num = (1 == ptp->nvme_nsid) ? max_nsid : 0; + break; + default: + if (vb > 1) + pr2ws("%s: bad select_report value: 0x%x\n", __func__, + sel_report); + mk_sense_invalid_fld(ptp, true, 2, 7, vb); + return 0; + } + rl_doutp = (uint8_t *)calloc(num + 1, 8); + if (NULL == rl_doutp) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8) + sg_put_unaligned_be16(k, up); + n = num * 8; + sg_put_unaligned_be32(n, rl_doutp); + n+= 8; + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, rl_doutp, + n); + } + res = 0; + free(rl_doutp); + return res; +} + +static int +sntl_tur(struct sg_pt_linux_scsi * ptp, int time_secs, int vb) +{ + int res; + uint32_t pow_state; + struct sg_nvme_passthru_cmd cmd; + + if (vb > 4) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0xa; /* Get feature */ + cmd.nsid = SG_NVME_BROADCAST_NSID; + cmd.cdw10 = 0x2; /* SEL=0 (current), Feature=2 Power Management */ + cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } else { + ptp->os_err = 0; + ptp->nvme_status = 0; + } + pow_state = (0x1f & ptp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); +#if 0 /* pow_state bounces around too much on laptop */ + if (pow_state) + mk_sense_asc_ascq(ptp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0, + vb); +#endif + return 0; +} + +static int +sntl_req_sense(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool desc; + int res; + uint32_t pow_state, alloc_len, n; + struct sg_nvme_passthru_cmd cmd; + uint8_t rs_dout[64]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + desc = !!(0x1 & cdbp[1]); + alloc_len = cdbp[4]; + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0xa; /* Get feature */ + cmd.nsid = SG_NVME_BROADCAST_NSID; + cmd.cdw10 = 0x2; /* SEL=0 (current), Feature=2 Power Management */ + cmd.timeout_ms = (time_secs < 0) ? 0 : (1000 * time_secs); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } else { + ptp->os_err = 0; + ptp->nvme_status = 0; + } + ptp->io_hdr.response_len = 0; + pow_state = (0x1f & ptp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); + memset(rs_dout, 0, sizeof(rs_dout)); + if (pow_state) + sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + LOW_POWER_COND_ON_ASC, 0); + else + sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + NO_ADDITIONAL_SENSE, 0); + n = desc ? 8 : 18; + n = (n < alloc_len) ? n : alloc_len; + n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - n; + if (n > 0) + memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, rs_dout, n); + return 0; +} + +static int +sntl_mode_ss(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool is_msense = (SCSI_MODE_SENSE10_OPC == cdbp[0]); + int res, n, len; + uint8_t * bp; + struct sg_sntl_result_t sntl_result; + + if (vb > 3) + pr2ws("%s: mse%s, time_secs=%d\n", __func__, + (is_msense ? "nse" : "lect"), time_secs); + if (NULL == ptp->nvme_id_ctlp) { + res = sntl_cache_identity(ptp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else if (res) + return res; + } + if (is_msense) { /* MODE SENSE(10) */ + len = ptp->io_hdr.din_xfer_len; + bp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp; + n = sntl_resp_mode_sense10(&ptp->dev_stat, cdbp, bp, len, + &sntl_result); + ptp->io_hdr.din_resid = (n >= 0) ? len - n : len; + } else { /* MODE SELECT(10) */ + uint8_t pre_enc_ov = ptp->dev_stat.enclosure_override; + + len = ptp->io_hdr.dout_xfer_len; + bp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.dout_xferp; + n = sntl_resp_mode_select10(&ptp->dev_stat, cdbp, bp, len, + &sntl_result); + if (pre_enc_ov != ptp->dev_stat.enclosure_override) + sntl_check_enclosure_override(ptp, vb); /* ENC_OV has changed */ + } + if (n < 0) { + int in_bit = (255 == sntl_result.in_bit) ? (int)sntl_result.in_bit : + -1; + if ((SAM_STAT_CHECK_CONDITION == sntl_result.sstatus) && + (SPC_SK_ILLEGAL_REQUEST == sntl_result.sk)) { + if (INVALID_FIELD_IN_CDB == sntl_result.asc) + mk_sense_invalid_fld(ptp, true, sntl_result.in_byte, in_bit, + vb); + else if (INVALID_FIELD_IN_PARAM_LIST == sntl_result.asc) + mk_sense_invalid_fld(ptp, false, sntl_result.in_byte, in_bit, + vb); + else + mk_sense_asc_ascq(ptp, sntl_result.sk, sntl_result.asc, + sntl_result.ascq, vb); + } else + pr2ws("%s: error but no sense?? n=%d\n", __func__, n); + } + return 0; +} + +/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI + * has a special command (SES Send) to tunnel through pages to an + * enclosure. The NVMe enclosure is meant to understand the SES + * (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_senddiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool pf, self_test; + int res; + uint8_t st_cd, dpg_cd; + uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst; + const uint32_t pg_sz = sg_get_page_size(); + uint8_t * dop; + struct sg_nvme_passthru_cmd cmd; + uint8_t * cmd_up = (uint8_t *)&cmd; + + st_cd = 0x7 & (cdbp[1] >> 5); + self_test = !! (0x4 & cdbp[1]); + pf = !! (0x10 & cdbp[1]); + if (vb > 3) + pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf, + (int)self_test, (int)st_cd); + if (self_test || st_cd) { + memset(cmd_up, 0, sizeof(cmd)); + cmd_up[SG_NVME_PT_OPCODE] = 0x14; /* Device self-test */ + /* just this namespace (if there is one) and controller */ + sg_put_unaligned_le32(ptp->nvme_nsid, cmd_up + SG_NVME_PT_NSID); + switch (st_cd) { + case 0: /* Here if self_test is set, do short self-test */ + case 1: /* Background short */ + case 5: /* Foreground short */ + nvme_dst = 1; + break; + case 2: /* Background extended */ + case 6: /* Foreground extended */ + nvme_dst = 2; + break; + case 4: /* Abort self-test */ + nvme_dst = 0xf; + break; + default: + pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd); + mk_sense_invalid_fld(ptp, true, 1, 7, vb); + return 0; + } + sg_put_unaligned_le32(nvme_dst, cmd_up + SG_NVME_PT_CDW10); + res = do_nvme_admin_cmd(ptp, &cmd, NULL, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } + } + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + dout_len = ptp->io_hdr.dout_xfer_len; + if (pf) { + if (0 == alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: PF bit set bit param_list_len=0\n", __func__); + return 0; + } + } else { /* PF bit clear */ + if (alloc_len) { + mk_sense_invalid_fld(ptp, true, 3, 7, vb); + if (vb) + pr2ws("%s: param_list_len>0 but PF clear\n", __func__); + return 0; + } else + return 0; /* nothing to do */ + if (dout_len > 0) { + if (vb) + pr2ws("%s: dout given but PF clear\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } + if (dout_len < 4) { + if (vb) + pr2ws("%s: dout length (%u bytes) too short\n", __func__, + dout_len); + return SCSI_PT_DO_BAD_PARAMS; + } + n = dout_len; + n = (n < alloc_len) ? n : alloc_len; + dop = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.dout_xferp; + if (! sg_is_aligned(dop, pg_sz)) { /* is dop page aligned ? */ + if (vb) + pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->io_hdr.dout_xferp); + return SCSI_PT_DO_BAD_PARAMS; + } + dpg_cd = dop[0]; + dpg_len = sg_get_unaligned_be16(dop + 2) + 4; + /* should we allow for more than one D_PG is dout ?? */ + n = (n < dpg_len) ? n : dpg_len; /* not yet ... */ + + if (vb) + pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n", + __func__, dpg_cd, dpg_len); + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x1d; /* MI send; hmmm same opcode as SEND DIAG */ + cmd.addr = (uint64_t)(sg_uintptr_t)dop; + cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */ + /* dout_len > 0x1000, is this a problem?? */ + cmd.cdw10 = 0x0804; /* NVMe Message Header */ + cmd.cdw11 = 0x9; /* nvme_mi_ses_send; (0x8 -> mi_ses_recv) */ + cmd.cdw13 = n; + res = do_nvme_admin_cmd(ptp, &cmd, dop, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } + } + return res; +} + +/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1) + * NVMe-MI has a special command (SES Receive) to read pages through a + * tunnel from an enclosure. The NVMe enclosure is meant to understand the + * SES (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_recvdiag(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool pcv; + int res; + uint8_t dpg_cd; + uint32_t alloc_len, n, din_len; + uint32_t pg_sz = sg_get_page_size(); + uint8_t * dip; + struct sg_nvme_passthru_cmd cmd; + + pcv = !! (0x1 & cdbp[1]); + dpg_cd = cdbp[2]; + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + if (vb > 3) + pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__, + dpg_cd, (int)pcv, alloc_len); + din_len = ptp->io_hdr.din_xfer_len; + n = din_len; + n = (n < alloc_len) ? n : alloc_len; + dip = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp; + if (! sg_is_aligned(dip, pg_sz)) { + if (vb) + pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)ptp->io_hdr.din_xferp); + return SCSI_PT_DO_BAD_PARAMS; + } + + if (vb) + pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__, + dpg_cd); + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = 0x1e; /* MI receive */ + cmd.addr = (uint64_t)(sg_uintptr_t)dip; + cmd.data_len = 0x1000; /* NVMe 4k page size. Maybe determine this? */ + /* din_len > 0x1000, is this a problem?? */ + cmd.cdw10 = 0x0804; /* NVMe Message Header */ + cmd.cdw11 = 0x8; /* nvme_mi_ses_receive */ + cmd.cdw12 = dpg_cd; + cmd.cdw13 = n; + res = do_nvme_admin_cmd(ptp, &cmd, dip, true, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(ptp, vb); + return 0; + } else + return res; + } + ptp->io_hdr.din_resid = din_len - n; + return res; +} + +#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */ +#define F_SA_HIGH 0x100 /* as used by variable length cdbs */ +#define FF_SA (F_SA_HIGH | F_SA_LOW) +#define F_INV_OP 0x200 + +static int +sntl_rep_opcodes(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool rctd; + uint8_t reporting_opts, req_opcode, supp; + uint16_t req_sa, u; + uint32_t alloc_len, offset, a_len; + uint32_t pg_sz = sg_get_page_size(); + int k, len, count, bump; + const struct sg_opcode_info_t *oip; + uint8_t *arr; + uint8_t *free_arr; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */ + reporting_opts = cdbp[2] & 0x7; + req_opcode = cdbp[3]; + req_sa = sg_get_unaligned_be16(cdbp + 4); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4 || alloc_len > 0xffff) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + a_len = pg_sz - 72; + arr = sg_memalign(pg_sz, pg_sz, &free_arr, false); + if (NULL == arr) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + switch (reporting_opts) { + case 0: /* all commands */ + count = 0; + bump = rctd ? 20 : 8; + for (offset = 4, oip = sg_opcode_info_arr; + (oip->flags != 0xffff) && (offset < a_len); ++oip) { + if (F_INV_OP & oip->flags) + continue; + ++count; + arr[offset] = oip->opcode; + sg_put_unaligned_be16(oip->sa, arr + offset + 2); + if (rctd) + arr[offset + 5] |= 0x2; + if (FF_SA & oip->flags) + arr[offset + 5] |= 0x1; + sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6); + if (rctd) + sg_put_unaligned_be16(0xa, arr + offset + 8); + offset += bump; + } + sg_put_unaligned_be32(count * bump, arr + 0); + break; + case 1: /* one command: opcode only */ + case 2: /* one command: opcode plus service action */ + case 3: /* one command: if sa==0 then opcode only else opcode+sa */ + for (oip = sg_opcode_info_arr; oip->flags != 0xffff; ++oip) { + if ((req_opcode == oip->opcode) && (req_sa == oip->sa)) + break; + } + if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) { + supp = 1; + offset = 4; + } else { + if (1 == reporting_opts) { + if (FF_SA & oip->flags) { + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + req_sa = 0; + } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) { + mk_sense_invalid_fld(ptp, true, 4, -1, vb); + free(free_arr); + return 0; + } + if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode)) + supp = 3; + else if (0 == (FF_SA & oip->flags)) + supp = 1; + else if (req_sa != oip->sa) + supp = 1; + else + supp = 3; + if (3 == supp) { + u = oip->len_mask[0]; + sg_put_unaligned_be16(u, arr + 2); + arr[4] = oip->opcode; + for (k = 1; k < u; ++k) + arr[4 + k] = (k < 16) ? + oip->len_mask[k] : 0xff; + offset = 4 + u; + } else + offset = 4; + } + arr[1] = (rctd ? 0x80 : 0) | supp; + if (rctd) { + sg_put_unaligned_be16(0xa, arr + offset); + offset += 12; + } + break; + default: + mk_sense_invalid_fld(ptp, true, 2, 2, vb); + free(free_arr); + return 0; + } + offset = (offset < a_len) ? offset : a_len; + len = (offset < alloc_len) ? offset : alloc_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len; + if (len > 0) + memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, arr, len); + free(free_arr); + return 0; +} + +static int +sntl_rep_tmfs(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, + int time_secs, int vb) +{ + bool repd; + uint32_t alloc_len, len; + uint8_t arr[16]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + memset(arr, 0, sizeof(arr)); + repd = !!(cdbp[2] & 0x80); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4) { + mk_sense_invalid_fld(ptp, true, 6, -1, vb); + return 0; + } + arr[0] = 0xc8; /* ATS | ATSS | LURS */ + arr[1] = 0x1; /* ITNRS */ + if (repd) { + arr[3] = 0xc; + len = 16; + } else + len = 4; + + len = (len < alloc_len) ? len : alloc_len; + ptp->io_hdr.din_resid = ptp->io_hdr.din_xfer_len - len; + if (len > 0) + memcpy((uint8_t *)(sg_uintptr_t)ptp->io_hdr.din_xferp, arr, len); + return 0; +} + +/* Executes NVMe Admin command (or at least forwards it to lower layers). + * Returns 0 for success, negative numbers are negated 'errno' values from + * OS system calls. Positive return values are errors from this package. + * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds + * is used. */ +int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) +{ + bool scsi_cdb; + bool is_read = false; + int n, len, hold_dev_fd; + uint16_t sa; + struct sg_pt_linux_scsi * ptp = &vp->impl; + struct sg_nvme_passthru_cmd cmd; + const uint8_t * cdbp; + void * dp = NULL; +; + if (! ptp->io_hdr.request) { + if (vb) + pr2ws("No NVMe command given (set_scsi_pt_cdb())\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + hold_dev_fd = ptp->dev_fd; + if (fd >= 0) { + if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) { + if (vb) + pr2ws("%s: file descriptor given to create() and here " + "differ\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + ptp->dev_fd = fd; + } else if (ptp->dev_fd < 0) { + if (vb) + pr2ws("%s: invalid file descriptors\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + n = ptp->io_hdr.request_len; + cdbp = (const uint8_t *)(sg_uintptr_t)ptp->io_hdr.request; + if (vb > 3) + pr2ws("%s: opcode=0x%x, fd=%d (dev_fd=%d), time_secs=%d\n", __func__, + cdbp[0], fd, hold_dev_fd, time_secs); + scsi_cdb = sg_is_scsi_cdb(cdbp, n); + /* direct NVMe command (i.e. 64 bytes long) or SNTL */ + ptp->nvme_direct = ! scsi_cdb; + if (scsi_cdb) { + switch (cdbp[0]) { + case SCSI_INQUIRY_OPC: + return sntl_inq(ptp, cdbp, time_secs, vb); + case SCSI_REPORT_LUNS_OPC: + return sntl_rluns(ptp, cdbp, time_secs, vb); + case SCSI_TEST_UNIT_READY_OPC: + return sntl_tur(ptp, time_secs, vb); + case SCSI_REQUEST_SENSE_OPC: + return sntl_req_sense(ptp, cdbp, time_secs, vb); + case SCSI_SEND_DIAGNOSTIC_OPC: + return sntl_senddiag(ptp, cdbp, time_secs, vb); + case SCSI_RECEIVE_DIAGNOSTIC_OPC: + return sntl_recvdiag(ptp, cdbp, time_secs, vb); + case SCSI_MODE_SENSE10_OPC: + case SCSI_MODE_SELECT10_OPC: + return sntl_mode_ss(ptp, cdbp, time_secs, vb); + case SCSI_MAINT_IN_OPC: + sa = 0x1f & cdbp[1]; /* service action */ + if (SCSI_REP_SUP_OPCS_OPC == sa) + return sntl_rep_opcodes(ptp, cdbp, time_secs, vb); + else if (SCSI_REP_SUP_TMFS_OPC == sa) + return sntl_rep_tmfs(ptp, cdbp, time_secs, vb); + /* fall through */ + default: + if (vb > 2) { + char b[64]; + + sg_get_command_name(cdbp, -1, sizeof(b), b); + pr2ws("%s: no translation to NVMe for SCSI %s command\n", + __func__, b); + } + mk_sense_asc_ascq(ptp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE, + 0, vb); + return 0; + } + } + len = (int)sizeof(cmd); + n = (n < len) ? n : len; + if (n < 64) { + if (vb) + pr2ws("%s: command length of %d bytes is too short\n", __func__, + n); + return SCSI_PT_DO_BAD_PARAMS; + } + memcpy(&cmd, (const uint8_t *)(sg_uintptr_t)ptp->io_hdr.request, n); + if (n < len) /* zero out rest of 'cmd' */ + memset((uint8_t *)&cmd + n, 0, len - n); + if (ptp->io_hdr.din_xfer_len > 0) { + cmd.data_len = ptp->io_hdr.din_xfer_len; + dp = (void *)(sg_uintptr_t)ptp->io_hdr.din_xferp; + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.din_xferp; + is_read = true; + } else if (ptp->io_hdr.dout_xfer_len > 0) { + cmd.data_len = ptp->io_hdr.dout_xfer_len; + dp = (void *)(sg_uintptr_t)ptp->io_hdr.dout_xferp; + cmd.addr = (uint64_t)(sg_uintptr_t)ptp->io_hdr.dout_xferp; + is_read = false; + } + return do_nvme_admin_cmd(ptp, &cmd, dp, is_read, time_secs, vb); +} + +#else /* (HAVE_NVME && (! IGNORE_NVME)) [around line 140] */ + +int +sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) +{ + if (vb) { + pr2ws("%s: not supported, ", __func__); +#ifdef HAVE_NVME + pr2ws("HAVE_NVME, "); +#else + pr2ws("don't HAVE_NVME, "); +#endif + +#ifdef IGNORE_NVME + pr2ws("IGNORE_NVME"); +#else + pr2ws("don't IGNORE_NVME"); +#endif + pr2ws("\n"); + } + if (vp) { ; } /* suppress warning */ + if (fd) { ; } /* suppress warning */ + if (time_secs) { ; } /* suppress warning */ + return -ENOTTY; /* inappropriate ioctl error */ +} + +#endif /* (HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/lib/sg_pt_osf1.c b/lib/sg_pt_osf1.c new file mode 100644 index 0000000..35ca067 --- /dev/null +++ b/lib/sg_pt_osf1.c @@ -0,0 +1,540 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sg_pt.h" +#include "sg_lib.h" +#include "sg_pr2serr.h" + + +#define OSF1_MAXDEV 64 + +struct osf1_dev_channel { + int bus; + int tgt; + int lun; +}; + +// Private table of open devices: guaranteed zero on startup since +// part of static data. +static struct osf1_dev_channel *devicetable[OSF1_MAXDEV] = {0}; +static char *cam_dev = "/dev/cam"; +static int camfd; +static int camopened = 0; + +struct sg_pt_osf1_scsi { + uint8_t * cdb; + int cdb_len; + uint8_t * sense; + int sense_len; + uint8_t * dxferp; + int dxfer_len; + int dxfer_dir; + int scsi_status; + int resid; + int sense_resid; + int in_err; + int os_err; + int transport_err; + bool is_nvme; + int dev_fd; +}; + +struct sg_pt_base { + struct sg_pt_osf1_scsi impl; +}; + + + +/* Returns >= 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_open_device(const char * device_name, bool read_only, int verbose) +{ + int oflags = 0 /* O_NONBLOCK*/ ; + + oflags |= (read_only ? O_RDONLY : O_RDWR); + return scsi_pt_open_flags(device_name, oflags, verbose); +} + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. The 'flags' argument is ignored in OSF-1. + * Returns >= 0 if successful, otherwise returns negated errno. */ +int +scsi_pt_open_flags(const char * device_name, int flags, int verbose) +{ + struct osf1_dev_channel *fdchan; + int fd, k; + + if (!camopened) { + camfd = open(cam_dev, O_RDWR, 0); + if (camfd < 0) + return -1; + camopened++; + } + + // Search table for a free entry + for (k = 0; k < OSF1_MAXDEV; k++) + if (! devicetable[k]) + break; + + if (k == OSF1_MAXDEV) { + if (verbose) + pr2ws("too many open devices (%d)\n", OSF1_MAXDEV); + errno=EMFILE; + return -1; + } + + fdchan = (struct osf1_dev_channel *)calloc(1, + sizeof(struct osf1_dev_channel)); + if (fdchan == NULL) { + // errno already set by call to malloc() + return -1; + } + + fd = open(device_name, O_RDONLY|O_NONBLOCK); + if (fd > 0) { + device_info_t devinfo; + bzero(&devinfo, sizeof(devinfo)); + if (ioctl(fd, DEVGETINFO, &devinfo) == 0) { + fdchan->bus = devinfo.v1.businfo.bus.scsi.bus_num; + fdchan->tgt = devinfo.v1.businfo.bus.scsi.tgt_id; + fdchan->lun = devinfo.v1.businfo.bus.scsi.lun; + } + close (fd); + } else { + free(fdchan); + return -1; + } + + devicetable[k] = fdchan; + return k; +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_close_device(int device_fd) +{ + struct osf1_dev_channel *fdchan; + int i; + + if ((device_fd < 0) || (device_fd >= OSF1_MAXDEV)) { + errno = ENODEV; + return -1; + } + + fdchan = devicetable[device_fd]; + if (NULL == fdchan) { + errno = ENODEV; + return -1; + } + + free(fdchan); + devicetable[device_fd] = NULL; + + for (i = 0; i < OSF1_MAXDEV; i++) { + if (devicetable[i]) + break; + } + if (i == OSF1_MAXDEV) { + close(camfd); + camopened = 0; + } + return 0; +} + +struct sg_pt_base * +construct_scsi_pt_obj_with_fd(int device_fd, int verbose) +{ + struct sg_pt_osf1_scsi * ptp; + + ptp = (struct sg_pt_osf1_scsi *)malloc(sizeof(struct sg_pt_osf1_scsi)); + if (ptp) { + bzero(ptp, sizeof(struct sg_pt_osf1_scsi)); + ptp->dev_fd = (device_fd < 0) ? -1 : device_fd; + ptp->is_nvme = false; + ptp->dxfer_dir = CAM_DIR_NONE; + } else if (verbose) + pr2ws("%s: malloc() out of memory\n", __func__); + return (struct sg_pt_base *)ptp; +} + +struct sg_pt_base * +construct_scsi_pt_obj(void) +{ + return construct_scsi_pt_obj_with_fd(-1, 0); +} + +void +destruct_scsi_pt_obj(struct sg_pt_base * vp) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp) + free(ptp); +} + +void +clear_scsi_pt_obj(struct sg_pt_base * vp) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp) { + bzero(ptp, sizeof(struct sg_pt_osf1_scsi)); + ptp->dxfer_dir = CAM_DIR_NONE; + } +} + +void +set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb, + int cdb_len) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp->cdb) + ++ptp->in_err; + ptp->cdb = (uint8_t *)cdb; + ptp->cdb_len = cdb_len; +} + +void +set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, + int max_sense_len) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp->sense) + ++ptp->in_err; + bzero(sense, max_sense_len); + ptp->sense = sense; + ptp->sense_len = max_sense_len; +} + +/* from device */ +void +set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp->dxferp) + ++ptp->in_err; + if (dxfer_len > 0) { + ptp->dxferp = dxferp; + ptp->dxfer_len = dxfer_len; + ptp->dxfer_dir = CAM_DIR_IN; + } +} + +/* to device */ +void +set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp->dxferp) + ++ptp->in_err; + if (dxfer_len > 0) { + ptp->dxferp = (uint8_t *)dxferp; + ptp->dxfer_len = dxfer_len; + ptp->dxfer_dir = CAM_DIR_OUT; + } +} + +void +set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id) +{ +} + +void +set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + ++ptp->in_err; +} + +void +set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + ++ptp->in_err; +} + +void +set_scsi_pt_task_attr(struct sg_pt_base * vp, int attrib, int priority) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + + ++ptp->in_err; +} + +void +set_scsi_pt_flags(struct sg_pt_base * objp, int flags) +{ + /* do nothing, suppress warnings */ + objp = objp; + flags = flags; +} + +static int +release_sim(struct sg_pt_base *vp, int device_fd, int verbose) { + struct sg_pt_osf1_scsi * ptp = &vp->impl; + struct osf1_dev_channel *fdchan = devicetable[device_fd]; + UAGT_CAM_CCB uagt; + CCB_RELSIM relsim; + int retval; + + bzero(&uagt, sizeof(uagt)); + bzero(&relsim, sizeof(relsim)); + + uagt.uagt_ccb = (CCB_HEADER *) &relsim; + uagt.uagt_ccblen = sizeof(relsim); + + relsim.cam_ch.cam_ccb_len = sizeof(relsim); + relsim.cam_ch.cam_func_code = XPT_REL_SIMQ; + relsim.cam_ch.cam_flags = CAM_DIR_IN | CAM_DIS_CALLBACK; + relsim.cam_ch.cam_path_id = fdchan->bus; + relsim.cam_ch.cam_target_id = fdchan->tgt; + relsim.cam_ch.cam_target_lun = fdchan->lun; + + retval = ioctl(camfd, UAGT_CAM_IO, &uagt); + if (retval < 0) { + if (verbose) + pr2ws("CAM ioctl error (Release SIM Queue)\n"); + } + return retval; +} + +int +do_scsi_pt(struct sg_pt_base * vp, int device_fd, int time_secs, int verbose) +{ + struct sg_pt_osf1_scsi * ptp = &vp->impl; + struct osf1_dev_channel *fdchan; + int len, retval; + CCB_SCSIIO ccb; + UAGT_CAM_CCB uagt; + uint8_t sensep[ADDL_SENSE_LENGTH]; + + + ptp->os_err = 0; + if (ptp->in_err) { + if (verbose) + pr2ws("Replicated or unused set_scsi_pt...\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (device_fd < 0) { + if (ptp->dev_fd < 0) { + if (verbose) + pr2ws("%s: No device file descriptor given\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } else { + if (ptp->dev_fd >= 0) { + if (device_fd != ptp->dev_fd) { + if (verbose) + pr2ws("%s: file descriptor given to create and this " + "differ\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } else + ptp->dev_fd = device_fd; + } + if (NULL == ptp->cdb) { + if (verbose) + pr2ws("No command (cdb) given\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + + if ((ptp->dev_fd < 0) || (ptp->dev_fd >= OSF1_MAXDEV)) { + if (verbose) + pr2ws("Bad file descriptor\n"); + ptp->os_err = ENODEV; + return -ptp->os_err; + } + fdchan = devicetable[ptp->dev_fd]; + if (NULL == fdchan) { + if (verbose) + pr2ws("File descriptor closed??\n"); + ptp->os_err = ENODEV; + return -ptp->os_err; + } + if (0 == camopened) { + if (verbose) + pr2ws("No open CAM device\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + + bzero(&uagt, sizeof(uagt)); + bzero(&ccb, sizeof(ccb)); + + uagt.uagt_ccb = (CCB_HEADER *) &ccb; + uagt.uagt_ccblen = sizeof(ccb); + uagt.uagt_snsbuf = ccb.cam_sense_ptr = ptp->sense ? ptp->sense : sensep; + uagt.uagt_snslen = ccb.cam_sense_len = ptp->sense ? ptp->sense_len : + sizeof sensep; + uagt.uagt_buffer = ccb.cam_data_ptr = ptp->dxferp; + uagt.uagt_buflen = ccb.cam_dxfer_len = ptp->dxfer_len; + + ccb.cam_timeout = time_secs; + ccb.cam_ch.my_addr = (CCB_HEADER *) &ccb; + ccb.cam_ch.cam_ccb_len = sizeof(ccb); + ccb.cam_ch.cam_func_code = XPT_SCSI_IO; + ccb.cam_ch.cam_flags = ptp->dxfer_dir; + ccb.cam_cdb_len = ptp->cdb_len; + memcpy(ccb.cam_cdb_io.cam_cdb_bytes, ptp->cdb, ptp->cdb_len); + ccb.cam_ch.cam_path_id = fdchan->bus; + ccb.cam_ch.cam_target_id = fdchan->tgt; + ccb.cam_ch.cam_target_lun = fdchan->lun; + + if (ioctl(camfd, UAGT_CAM_IO, &uagt) < 0) { + if (verbose) + pr2ws("CAN I/O Error\n"); + ptp->os_err = EIO; + return -ptp->os_err; + } + + if (((ccb.cam_ch.cam_status & CAM_STATUS_MASK) == CAM_REQ_CMP) || + ((ccb.cam_ch.cam_status & CAM_STATUS_MASK) == CAM_REQ_CMP_ERR)) { + ptp->scsi_status = ccb.cam_scsi_status; + ptp->resid = ccb.cam_resid; + if (ptp->sense) + ptp->sense_resid = ccb.cam_sense_resid; + } else { + ptp->transport_err = 1; + } + + /* If the SIM queue is frozen, release SIM queue. */ + if (ccb.cam_ch.cam_status & CAM_SIM_QFRZN) + release_sim(vp, ptp->dev_fd, verbose); + + return 0; +} + +int +get_scsi_pt_result_category(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (ptp->os_err) + return SCSI_PT_RESULT_OS_ERR; + else if (ptp->transport_err) + return SCSI_PT_RESULT_TRANSPORT_ERR; + else if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status)) + return SCSI_PT_RESULT_SENSE; + else if (ptp->scsi_status) + return SCSI_PT_RESULT_STATUS; + else + return SCSI_PT_RESULT_GOOD; +} + +int +get_scsi_pt_resid(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + return ptp->resid; +} + +int +get_scsi_pt_status_response(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + return ptp->scsi_status; +} + +int +get_scsi_pt_sense_len(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + int len; + + len = ptp->sense_len - ptp->sense_resid; + return (len > 0) ? len : 0; +} + +int +get_scsi_pt_duration_ms(const struct sg_pt_base * vp) +{ + // const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + return -1; +} + +int +get_scsi_pt_transport_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + return ptp->transport_err; +} + +int +get_scsi_pt_os_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + return ptp->os_err; +} + +bool +pt_device_is_nvme(const struct sg_pt_base * vp) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + return ptp ? ptp->is_nvme : false; +} + +char * +get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, + char * b) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + + if (0 == ptp->transport_err) { + strncpy(b, "no transport error available", max_b_len); + b[max_b_len - 1] = '\0'; + return b; + } + strncpy(b, "no transport error available", max_b_len); + b[max_b_len - 1] = '\0'; + return b; +} + +char * +get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) +{ + const struct sg_pt_osf1_scsi * ptp = &vp->impl; + const char * cp; + + cp = safe_strerror(ptp->os_err); + strncpy(b, cp, max_b_len); + if ((int)strlen(cp) >= max_b_len) + b[max_b_len - 1] = '\0'; + return b; +} diff --git a/lib/sg_pt_solaris.c b/lib/sg_pt_solaris.c new file mode 100644 index 0000000..dffb084 --- /dev/null +++ b/lib/sg_pt_solaris.c @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2007-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. + */ + +/* sg_pt_solaris version 1.10 20180808 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Solaris headers */ +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_pt.h" +#include "sg_lib.h" + + +#define DEF_TIMEOUT 60 /* 60 seconds */ + +struct sg_pt_solaris_scsi { + struct uscsi_cmd uscsi; + int max_sense_len; + int in_err; + int os_err; + bool is_nvme; + int dev_fd; +}; + +struct sg_pt_base { + struct sg_pt_solaris_scsi impl; +}; + + +/* Returns >= 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_open_device(const char * device_name, bool read_only, int verbose) +{ + int oflags = 0 /* O_NONBLOCK*/ ; + + oflags |= (read_only ? O_RDONLY : O_RDWR); + return scsi_pt_open_flags(device_name, oflags, verbose); +} + +/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. The 'flags' argument is ignored in Solaris. + * Returns >= 0 if successful, otherwise returns negated errno. */ +int +scsi_pt_open_flags(const char * device_name, int flags_arg, int verbose) +{ + int oflags = O_NONBLOCK | O_RDWR; + int fd; + + flags_arg = flags_arg; /* ignore flags argument, suppress warning */ + if (verbose > 1) { + fprintf(sg_warnings_strm ? sg_warnings_strm : stderr, + "open %s with flags=0x%x\n", device_name, oflags); + } + fd = open(device_name, oflags); + if (fd < 0) + fd = -errno; + return fd; +} + +/* Returns 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_close_device(int device_fd) +{ + int res; + + res = close(device_fd); + if (res < 0) + res = -errno; + return res; +} + +struct sg_pt_base * +construct_scsi_pt_obj_with_fd(int dev_fd, int verbose) +{ + struct sg_pt_solaris_scsi * ptp; + + ptp = (struct sg_pt_solaris_scsi *) + calloc(1, sizeof(struct sg_pt_solaris_scsi)); + if (ptp) { + ptp->dev_fd = (dev_fd < 0) ? -1 : dev_fd; + ptp->is_nvme = false; + ptp->uscsi.uscsi_timeout = DEF_TIMEOUT; + ptp->uscsi.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_RQENABLE; + ptp->uscsi.uscsi_timeout = DEF_TIMEOUT; + } else if (verbose) + fprintf(sg_warnings_strm ? sg_warnings_strm : stderr, + "%s: calloc() out of memory\n", __func__); + return (struct sg_pt_base *)ptp; +} + +struct sg_pt_base * +construct_scsi_pt_obj() +{ + return construct_scsi_pt_obj_with_fd(-1, 0); +} + +void +destruct_scsi_pt_obj(struct sg_pt_base * vp) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (ptp) + free(ptp); +} + +void +clear_scsi_pt_obj(struct sg_pt_base * vp) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (ptp) { + memset(ptp, 0, sizeof(struct sg_pt_solaris_scsi)); + ptp->uscsi.uscsi_timeout = DEF_TIMEOUT; + ptp->uscsi.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_RQENABLE; + ptp->uscsi.uscsi_timeout = DEF_TIMEOUT; + } +} + +void +set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb, + int cdb_len) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (ptp->uscsi.uscsi_cdb) + ++ptp->in_err; + ptp->uscsi.uscsi_cdb = (char *)cdb; + ptp->uscsi.uscsi_cdblen = cdb_len; +} + +void +set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, + int max_sense_len) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (ptp->uscsi.uscsi_rqbuf) + ++ptp->in_err; + memset(sense, 0, max_sense_len); + ptp->uscsi.uscsi_rqbuf = (char *)sense; + ptp->uscsi.uscsi_rqlen = max_sense_len; + ptp->max_sense_len = max_sense_len; +} + +/* from device */ +void +set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (ptp->uscsi.uscsi_bufaddr) + ++ptp->in_err; + if (dxfer_len > 0) { + ptp->uscsi.uscsi_bufaddr = (char *)dxferp; + ptp->uscsi.uscsi_buflen = dxfer_len; + ptp->uscsi.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_RQENABLE; + } +} + +/* to device */ +void +set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (ptp->uscsi.uscsi_bufaddr) + ++ptp->in_err; + if (dxfer_len > 0) { + ptp->uscsi.uscsi_bufaddr = (char *)dxferp; + ptp->uscsi.uscsi_buflen = dxfer_len; + ptp->uscsi.uscsi_flags = USCSI_WRITE | USCSI_ISOLATE | USCSI_RQENABLE; + } +} + +void +set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id) +{ + // struct sg_pt_solaris_scsi * ptp = &vp->impl; + + vp = vp; /* ignore and suppress warning */ + pack_id = pack_id; /* ignore and suppress warning */ +} + +void +set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag) +{ + // struct sg_pt_solaris_scsi * ptp = &vp->impl; + + vp = vp; /* ignore and suppress warning */ + tag = tag; /* ignore and suppress warning */ +} + +/* Note that task management function codes are transport specific */ +void +set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + ++ptp->in_err; + tmf_code = tmf_code; /* dummy to silence compiler */ +} + +void +set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + + ++ptp->in_err; + attribute = attribute; /* dummy to silence compiler */ + priority = priority; /* dummy to silence compiler */ +} + +void +set_scsi_pt_flags(struct sg_pt_base * objp, int flags) +{ + /* do nothing, suppress warnings */ + objp = objp; + flags = flags; +} + +/* Executes SCSI command (or at least forwards it to lower layers). + * Clears os_err field prior to active call (whose result may set it + * again). */ +int +do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose) +{ + struct sg_pt_solaris_scsi * ptp = &vp->impl; + FILE * ferr = sg_warnings_strm ? sg_warnings_strm : stderr; + + ptp->os_err = 0; + if (ptp->in_err) { + if (verbose) + fprintf(ferr, "Replicated or unused set_scsi_pt... functions\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (fd < 0) { + if (ptp->dev_fd < 0) { + if (verbose) + fprintf(ferr, "%s: No device file descriptor given\n", + __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } else { + if (ptp->dev_fd >= 0) { + if (fd != ptp->dev_fd) { + if (verbose) + fprintf(ferr, "%s: file descriptor given to create and " + "this differ\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } else + ptp->dev_fd = fd; + } + if (NULL == ptp->uscsi.uscsi_cdb) { + if (verbose) + fprintf(ferr, "%s: No SCSI command (cdb) given\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + if (time_secs > 0) + ptp->uscsi.uscsi_timeout = time_secs; + + if (ioctl(ptp->dev_fd, USCSICMD, &ptp->uscsi)) { + ptp->os_err = errno; + if ((EIO == ptp->os_err) && ptp->uscsi.uscsi_status) { + ptp->os_err = 0; + return 0; + } + if (verbose) + fprintf(ferr, "%s: ioctl(USCSICMD) failed with os_err (errno) " + "= %d\n", __func__, ptp->os_err); + return -ptp->os_err; + } + return 0; +} + +int +get_scsi_pt_result_category(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + int scsi_st = ptp->uscsi.uscsi_status; + + if (ptp->os_err) + return SCSI_PT_RESULT_OS_ERR; + else if ((SAM_STAT_CHECK_CONDITION == scsi_st) || + (SAM_STAT_COMMAND_TERMINATED == scsi_st)) + return SCSI_PT_RESULT_SENSE; + else if (scsi_st) + return SCSI_PT_RESULT_STATUS; + else + return SCSI_PT_RESULT_GOOD; +} + +uint32_t +get_pt_result(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + return (uint32_t)ptp->uscsi.uscsi_status; +} + +int +get_scsi_pt_resid(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + return ptp->uscsi.uscsi_resid; +} + +int +get_scsi_pt_status_response(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + return ptp->uscsi.uscsi_status; +} + +int +get_scsi_pt_sense_len(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + int res; + + if (ptp->max_sense_len > 0) { + res = ptp->max_sense_len - ptp->uscsi.uscsi_rqresid; + return (res > 0) ? res : 0; + } + return 0; +} + +int +get_scsi_pt_duration_ms(const struct sg_pt_base * vp) +{ + // const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + vp = vp; /* ignore and suppress warning */ + return -1; /* not available */ +} + +int +get_scsi_pt_transport_err(const struct sg_pt_base * vp) +{ + // const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (vp) { ; } /* ignore and suppress warning */ + return 0; +} + +void +set_scsi_pt_transport_err(struct sg_pt_base * vp, int err) +{ + // const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + if (vp) { ; } /* ignore and suppress warning */ + if (err) { ; } /* ignore and suppress warning */ +} + +int +get_scsi_pt_os_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + return ptp->os_err; +} + +bool +pt_device_is_nvme(const struct sg_pt_base * vp) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + return ptp ? ptp->is_nvme : false; +} + +char * +get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, + char * b) +{ + // const struct sg_pt_solaris_scsi * ptp = &vp->impl; + + vp = vp; /* ignore and suppress warning */ + if (max_b_len > 0) + b[0] = '\0'; + + return b; +} + +char * +get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) +{ + const struct sg_pt_solaris_scsi * ptp = &vp->impl; + const char * cp; + + cp = safe_strerror(ptp->os_err); + strncpy(b, cp, max_b_len); + if ((int)strlen(cp) >= max_b_len) + b[max_b_len - 1] = '\0'; + return b; +} diff --git a/lib/sg_pt_win32.c b/lib/sg_pt_win32.c new file mode 100644 index 0000000..1cbc8de --- /dev/null +++ b/lib/sg_pt_win32.c @@ -0,0 +1,3041 @@ +/* + * Copyright (c) 2006-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. + */ + +/* sg_pt_win32 version 1.28 20180615 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_unaligned.h" +#include "sg_pt.h" +#include "sg_pt_win32.h" +#include "sg_pt_nvme.h" +#include "sg_pr2serr.h" + + +/* Comment the following line out to use the pre-W10 NVMe pass-through */ +#define W10_NVME_NON_PASSTHRU 1 + +#ifndef O_EXCL +// #define O_EXCL 0x80 // cygwin ?? +// #define O_EXCL 0x80 // Linux +#define O_EXCL 0x400 // mingw +#warning "O_EXCL not defined" +#endif + +#define SCSI_INQUIRY_OPC 0x12 +#define SCSI_REPORT_LUNS_OPC 0xa0 +#define SCSI_TEST_UNIT_READY_OPC 0x0 +#define SCSI_REQUEST_SENSE_OPC 0x3 +#define SCSI_SEND_DIAGNOSTIC_OPC 0x1d +#define SCSI_RECEIVE_DIAGNOSTIC_OPC 0x1c +#define SCSI_MAINT_IN_OPC 0xa3 +#define SCSI_REP_SUP_OPCS_OPC 0xc +#define SCSI_REP_SUP_TMFS_OPC 0xd +#define SCSI_MODE_SENSE10_OPC 0x5a +#define SCSI_MODE_SELECT10_OPC 0x55 + +/* Additional Sense Code (ASC) */ +#define NO_ADDITIONAL_SENSE 0x0 +#define LOGICAL_UNIT_NOT_READY 0x4 +#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8 +#define UNRECOVERED_READ_ERR 0x11 +#define PARAMETER_LIST_LENGTH_ERR 0x1a +#define INVALID_OPCODE 0x20 +#define LBA_OUT_OF_RANGE 0x21 +#define INVALID_FIELD_IN_CDB 0x24 +#define INVALID_FIELD_IN_PARAM_LIST 0x26 +#define UA_RESET_ASC 0x29 +#define UA_CHANGED_ASC 0x2a +#define TARGET_CHANGED_ASC 0x3f +#define LUNS_CHANGED_ASCQ 0x0e +#define INSUFF_RES_ASC 0x55 +#define INSUFF_RES_ASCQ 0x3 +#define LOW_POWER_COND_ON_ASC 0x5e /* ASCQ=0 */ +#define POWER_ON_RESET_ASCQ 0x0 +#define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */ +#define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */ +#define CAPACITY_CHANGED_ASCQ 0x9 +#define SAVING_PARAMS_UNSUP 0x39 +#define TRANSPORT_PROBLEM 0x4b +#define THRESHOLD_EXCEEDED 0x5d +#define LOW_POWER_COND_ON 0x5e +#define MISCOMPARE_VERIFY_ASC 0x1d +#define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ +#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 + +/* Use the Microsoft SCSI Pass Through (SPT) interface. It has two + * variants: "SPT" where data is double buffered; and "SPTD" where data + * pointers to the user space are passed to the OS. Only Windows + * 2000 and later (i.e. not 95,98 or ME). + * There is no ASPI interface which relies on a dll from adaptec. + * This code uses cygwin facilities and is built in a cygwin + * shell. It can be run in a normal DOS shell if the cygwin1.dll + * file is put in an appropriate place. + * This code can build in a MinGW environment. + * + * N.B. MSDN says that the "SPT" interface (i.e. double buffered) + * should be used for small amounts of data (it says "< 16 KB"). + * The direct variant (i.e. IOCTL_SCSI_PASS_THROUGH_DIRECT) should + * be used for larger amounts of data but the buffer needs to be + * "cache aligned". Is that 16 byte alignment or greater? + * + * This code will default to indirect (i.e. double buffered) access + * unless the WIN32_SPT_DIRECT preprocessor constant is defined in + * config.h . In version 1.12 runtime selection of direct and indirect + * access was added; the default is still determined by the + * WIN32_SPT_DIRECT preprocessor constant. + */ + +#define DEF_TIMEOUT 60 /* 60 seconds */ +#define MAX_OPEN_SIMULT 8 +#define WIN32_FDOFFSET 32 + +union STORAGE_DEVICE_DESCRIPTOR_DATA { + STORAGE_DEVICE_DESCRIPTOR desc; + char raw[256]; +}; + +union STORAGE_DEVICE_UID_DATA { + STORAGE_DEVICE_UNIQUE_IDENTIFIER desc; + char raw[1060]; +}; + + +struct sg_pt_handle { + bool in_use; + bool not_claimed; + bool checked_handle; + bool bus_type_failed; + bool is_nvme; + bool got_physical_drive; + HANDLE fh; + char adapter[32]; /* for example: '\\.\scsi3' */ + int bus; /* a.k.a. PathId in MS docs */ + int target; + int lun; + int scsi_pdt; /* Peripheral Device Type, -1 if not known */ + // uint32_t nvme_nsid; /* how do we find this given file handle ?? */ + int verbose; /* tunnel verbose through to scsi_pt_close_device */ + char dname[20]; + struct sg_sntl_dev_state_t dev_stat; // owner +}; + +/* Start zeroed but need to zeroed before use because could be re-use */ +static struct sg_pt_handle handle_arr[MAX_OPEN_SIMULT]; + +struct sg_pt_win32_scsi { + bool is_nvme; + bool nvme_direct; /* false: our SNTL; true: received NVMe command */ + bool mdxfer_out; /* direction of metadata xfer, true->data-out */ + bool have_nvme_cmd; + bool is_read; + int sense_len; + int scsi_status; + int resid; + int sense_resid; + int in_err; + int os_err; /* pseudo unix error */ + int transport_err; /* windows error number */ + int dev_fd; /* -1 for no "file descriptor" given */ + uint32_t nvme_nsid; /* 1 to 0xfffffffe are possibly valid, 0 + * implies dev_fd is not a NVMe device + * (is_nvme=false) or has no storage (e.g. + * enclosure rather than disk) */ + uint32_t nvme_result; /* DW0 from completion queue */ + uint32_t nvme_status; /* SCT|SC: DW3 27:17 from completion queue, + * note: the DNR+More bit are not there. + * The whole 16 byte completion q entry is + * sent back as sense data */ + uint32_t dxfer_len; + uint32_t mdxfer_len; + uint8_t * dxferp; + uint8_t * mdxferp; /* NVMe has metadata buffer */ + uint8_t * sensep; + uint8_t * nvme_id_ctlp; + uint8_t * free_nvme_id_ctlp; + struct sg_sntl_dev_state_t * dev_statp; /* points to handle's dev_stat */ + uint8_t nvme_cmd[64]; + union { + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER swb_d; + /* Last entry in structure so data buffer can be extended */ + SCSI_PASS_THROUGH_WITH_BUFFERS swb_i; + }; +}; + +/* embed pointer so can change on fly if (non-direct) data buffer + * is not big enough */ +struct sg_pt_base { + struct sg_pt_win32_scsi * implp; +}; + +#ifdef WIN32_SPT_DIRECT +static int spt_direct = 1; +#else +static int spt_direct = 0; +#endif + +static int nvme_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + int time_secs, int vb); + + +/* Request SPT direct interface when state_direct is 1, state_direct set + * to 0 for the SPT indirect interface. */ +void +scsi_pt_win32_direct(int state_direct) +{ + spt_direct = state_direct; +} + +/* Returns current SPT interface state, 1 for direct, 0 for indirect */ +int +scsi_pt_win32_spt_state(void) +{ + return spt_direct; +} + +static const char * +bus_type_str(int bt) +{ + switch (bt) + { + case BusTypeUnknown: + return "Unknown"; + case BusTypeScsi: + return "Scsi"; + case BusTypeAtapi: + return "Atapi"; + case BusTypeAta: + return "Ata"; + case BusType1394: + return "1394"; + case BusTypeSsa: + return "Ssa"; + case BusTypeFibre: + return "Fibre"; + case BusTypeUsb: + return "Usb"; + case BusTypeRAID: + return "RAID"; + case BusTypeiScsi: + return "iScsi"; + case BusTypeSas: + return "Sas"; + case BusTypeSata: + return "Sata"; + case BusTypeSd: + return "Sd"; + case BusTypeMmc: + return "Mmc"; + case BusTypeVirtual: + return "Virt"; + case BusTypeFileBackedVirtual: + return "FBVir"; +#ifdef BusTypeSpaces + case BusTypeSpaces: +#else + case 0x10: +#endif + return "Spaces"; +#ifdef BusTypeNvme + case BusTypeNvme: +#else + case 0x11: +#endif + return "NVMe"; +#ifdef BusTypeSCM + case BusTypeSCM: +#else + case 0x12: +#endif + return "SCM"; +#ifdef BusTypeUfs + case BusTypeUfs: +#else + case 0x13: +#endif + return "Ufs"; + case 0x14: + return "Max"; + case 0x7f: + return "Max Reserved"; + default: + return "_unknown"; + } +} + +static char * +get_err_str(DWORD err, int max_b_len, char * b) +{ + LPVOID lpMsgBuf; + int k, num, ch; + + memset(b, 0, max_b_len); + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, NULL ); + num = lstrlen((LPCTSTR)lpMsgBuf); + if (num < 1) + return b; + num = (num < max_b_len) ? num : (max_b_len - 1); + for (k = 0; k < num; ++k) { + ch = *((LPCTSTR)lpMsgBuf + k); + if ((ch >= 0x0) && (ch < 0x7f)) + b[k] = ch & 0x7f; + else + b[k] = '?'; + } + return b; +} + +/* Returns pointer to sg_pt_handle object given Unix like device_fd. If + * device_fd is invalid or not open returns NULL. If psp is non-NULL and + * NULL is returned then ENODEV is placed in psp->os_err. */ +static struct sg_pt_handle * +get_open_pt_handle(struct sg_pt_win32_scsi * psp, int device_fd, bool vbb) +{ + int index = device_fd - WIN32_FDOFFSET; + struct sg_pt_handle * shp; + + if ((index < 0) || (index >= WIN32_FDOFFSET)) { + if (vbb) + pr2ws("Bad file descriptor\n"); + if (psp) + psp->os_err = EBADF; + return NULL; + } + shp = handle_arr + index; + if (! shp->in_use) { + if (vbb) + pr2ws("File descriptor closed??\n"); + if (psp) + psp->os_err = ENODEV; + return NULL; + } + return shp; +} + + +/* Returns >= 0 if successful. If error in Unix returns negated errno. */ +int +scsi_pt_open_device(const char * device_name, bool read_only, int vb) +{ + int oflags = 0 /* O_NONBLOCK*/ ; + + oflags |= (read_only ? 0 : 0); /* was ... ? O_RDONLY : O_RDWR) */ + return scsi_pt_open_flags(device_name, oflags, vb); +} + +/* + * Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed + * together. The 'flags' argument is ignored in Windows. + * Returns >= 0 if successful, otherwise returns negated errno. + * Optionally accept leading "\\.\". If given something of the form + * "SCSI:,," where the values in angle brackets + * are integers, then will attempt to open "\\.\SCSI:" and save the + * other three values for the DeviceIoControl call. The trailing "." + * is optionally and if not given 0 is assumed. Since "PhysicalDrive" + * is a lot of keystrokes, "PD" is accepted and converted to the longer + * form. + */ +int +scsi_pt_open_flags(const char * device_name, int flags, int vb) +{ + bool got_scsi_name = false; + int len, k, adapter_num, bus, target, lun, off, index, num, pd_num; + int share_mode; + struct sg_pt_handle * shp; + char buff[8]; + + share_mode = (O_EXCL & flags) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE); + /* lock */ + for (k = 0; k < MAX_OPEN_SIMULT; k++) + if (! handle_arr[k].in_use) + break; + if (k == MAX_OPEN_SIMULT) { + if (vb) + pr2ws("too many open handles (%d)\n", MAX_OPEN_SIMULT); + return -EMFILE; + } else { + /* clear any previous contents */ + memset(handle_arr + k, 0, sizeof(struct sg_pt_handle)); + handle_arr[k].in_use = true; + } + /* unlock */ + index = k; + shp = handle_arr + index; +#if (HAVE_NVME && (! IGNORE_NVME)) + sntl_init_dev_stat(&shp->dev_stat); +#endif + adapter_num = 0; + bus = 0; /* also known as 'PathId' in MS docs */ + target = 0; + lun = 0; + len = (int)strlen(device_name); + k = (int)sizeof(shp->dname); + if (len < k) + strcpy(shp->dname, device_name); + else if (len == k) + memcpy(shp->dname, device_name, k - 1); + else /* trim on left */ + memcpy(shp->dname, device_name + (len - k), k - 1); + shp->dname[k - 1] = '\0'; + if ((len > 4) && (0 == strncmp("\\\\.\\", device_name, 4))) + off = 4; + else + off = 0; + if (len > (off + 2)) { + buff[0] = toupper((int)device_name[off + 0]); + buff[1] = toupper((int)device_name[off + 1]); + if (0 == strncmp("PD", buff, 2)) { + num = sscanf(device_name + off + 2, "%d", &pd_num); + if (1 == num) + shp->got_physical_drive = true; + } + if (! shp->got_physical_drive) { + buff[2] = toupper((int)device_name[off + 2]); + buff[3] = toupper((int)device_name[off + 3]); + if (0 == strncmp("SCSI", buff, 4)) { + num = sscanf(device_name + off + 4, "%d:%d,%d,%d", + &adapter_num, &bus, &target, &lun); + if (num < 3) { + if (vb) + pr2ws("expected format like: " + "'SCSI:,[,]'\n"); + shp->in_use = false; + return -EINVAL; + } + got_scsi_name = true; + } + } + } + shp->bus = bus; + shp->target = target; + shp->lun = lun; + shp->scsi_pdt = -1; + shp->verbose = vb; + memset(shp->adapter, 0, sizeof(shp->adapter)); + strncpy(shp->adapter, "\\\\.\\", 4); + if (shp->got_physical_drive) + snprintf(shp->adapter + 4, sizeof(shp->adapter) - 5, + "PhysicalDrive%d", pd_num); + else if (got_scsi_name) + snprintf(shp->adapter + 4, sizeof(shp->adapter) - 5, "SCSI%d:", + adapter_num); + else + snprintf(shp->adapter + 4, sizeof(shp->adapter) - 5, "%s", + device_name + off); + if (vb > 4) + pr2ws("%s: CreateFile('%s'), bus=%d, target=%d, lun=%d\n", __func__, + shp->adapter, bus, target, lun); +#if 1 + shp->fh = CreateFile(shp->adapter, GENERIC_READ | GENERIC_WRITE, + share_mode, NULL, OPEN_EXISTING, 0, NULL); +#endif + +#if 0 + shp->fh = CreateFileA(shp->adapter, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, 0); + // No GENERIC_READ/WRITE access required, works without admin rights (W10) + shp->fh = CreateFileA(shp->adapter, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + (SECURITY_ATTRIBUTES *)0, OPEN_EXISTING, 0, (HANDLE)0); +#endif + if (shp->fh == INVALID_HANDLE_VALUE) { + if (vb) { + uint32_t err = (uint32_t)GetLastError(); + char b[128]; + + pr2ws("%s: CreateFile error: %s [%u]\n", __func__, + get_err_str(err, sizeof(b), b), err); + } + shp->in_use = false; + return -ENODEV; + } + return index + WIN32_FDOFFSET; +} + +/* Returns 0 if successful. If device_id seems wild returns -ENODEV, + * other errors return 0. If CloseHandle() fails and verbose > 0 then + * outputs warning with value from GetLastError(). The verbose value + * defaults to zero and is potentially set from the most recent call + * to scsi_pt_open_device() or do_scsi_pt(). */ +int +scsi_pt_close_device(int device_fd) +{ + struct sg_pt_handle * shp = get_open_pt_handle(NULL, device_fd, false); + + if (NULL == shp) + return -ENODEV; + if ((! CloseHandle(shp->fh)) && shp->verbose) + pr2ws("Windows CloseHandle error=%u\n", (unsigned int)GetLastError()); + shp->bus = 0; + shp->target = 0; + shp->lun = 0; + memset(shp->adapter, 0, sizeof(shp->adapter)); + shp->in_use = false; + shp->verbose = 0; + shp->dname[0] = '\0'; + return 0; +} + +/* Attempt to return device's SCSI peripheral device type (pdt), a number + * between 0 (disks) and 31 (not given) by calling IOCTL_SCSI_GET_INQUIRY_DATA + * on the adapter. Returns -EIO on error and -999 if not found. */ +static int +get_scsi_pdt(struct sg_pt_handle *shp, int vb) +{ + const int alloc_sz = 8192; + int j; + int ret = -999; + BOOL ok; + ULONG dummy; + DWORD err; + BYTE wbus; + uint8_t * inqBuf; + uint8_t * free_inqBuf; + char b[128]; + + if (vb > 2) + pr2ws("%s: enter, adapter: %s\n", __func__, shp->adapter); + inqBuf = sg_memalign(alloc_sz, 0 /* page size */, &free_inqBuf, false); + if (NULL == inqBuf) { + pr2ws("%s: unable to allocate %d bytes\n", __func__, alloc_sz); + return -ENOMEM; + } + ok = DeviceIoControl(shp->fh, IOCTL_SCSI_GET_INQUIRY_DATA, + NULL, 0, inqBuf, alloc_sz, &dummy, NULL); + if (ok) { + PSCSI_ADAPTER_BUS_INFO ai; + PSCSI_BUS_DATA pbd; + PSCSI_INQUIRY_DATA pid; + int num_lus, off; + + ai = (PSCSI_ADAPTER_BUS_INFO)inqBuf; + for (wbus = 0; wbus < ai->NumberOfBusses; ++wbus) { + pbd = ai->BusData + wbus; + num_lus = pbd->NumberOfLogicalUnits; + off = pbd->InquiryDataOffset; + for (j = 0; j < num_lus; ++j) { + if ((off < (int)sizeof(SCSI_ADAPTER_BUS_INFO)) || + (off > (alloc_sz - (int)sizeof(SCSI_INQUIRY_DATA)))) + break; + pid = (PSCSI_INQUIRY_DATA)(inqBuf + off); + if ((shp->bus == pid->PathId) && + (shp->target == pid->TargetId) && + (shp->lun == pid->Lun)) { /* got match */ + shp->scsi_pdt = pid->InquiryData[0] & 0x3f; + shp->not_claimed = ! pid->DeviceClaimed; + shp->checked_handle = true; + shp->bus_type_failed = false; + if (vb > 3) + pr2ws("%s: found, scsi_pdt=%d, claimed=%d, " + "target=%d, lun=%d\n", __func__, shp->scsi_pdt, + pid->DeviceClaimed, shp->target, shp->lun); + ret = shp->scsi_pdt; + goto fini; + } + off = pid->NextInquiryDataOffset; + } + } + } else { + err = GetLastError(); + if (vb > 1) + pr2ws("%s: IOCTL_SCSI_GET_INQUIRY_DATA failed err=%u\n\t%s", + shp->adapter, (unsigned int)err, + get_err_str(err, sizeof(b), b)); + ret = -EIO; + } +fini: + if (free_inqBuf) + free(free_inqBuf); + return ret; /* no match after checking all PathIds, Targets and LUs */ +} + +/* Returns 0 on success, negated errno if error */ +static int +get_bus_type(struct sg_pt_handle *shp, const char *dname, + STORAGE_BUS_TYPE * btp, int vb) +{ + DWORD num_out, err; + STORAGE_BUS_TYPE bt; + union STORAGE_DEVICE_DESCRIPTOR_DATA sddd; + STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty, + PropertyStandardQuery, {0} }; + char b[256]; + + memset(&sddd, 0, sizeof(sddd)); + if (! DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY, + &query, sizeof(query), &sddd, sizeof(sddd), + &num_out, NULL)) { + if (vb > 2) { + err = GetLastError(); + pr2ws("%s IOCTL_STORAGE_QUERY_PROPERTY(Devprop) failed, " + "Error: %s [%u]\n", dname, get_err_str(err, sizeof(b), b), + (uint32_t)err); + } + shp->bus_type_failed = true; + return -EIO; + } + bt = sddd.desc.BusType; + if (vb > 2) { + pr2ws("%s: Bus type: %s\n", __func__, bus_type_str((int)bt)); + if (vb > 3) { + pr2ws("Storage Device Descriptor Data:\n"); + hex2stderr((const uint8_t *)&sddd, num_out, 0); + } + } + if (shp) { + shp->checked_handle = true; + shp->bus_type_failed = false; + shp->is_nvme = (BusTypeNvme == bt); + } + if (btp) + *btp = bt; + return 0; +} + +/* Assumes dev_fd is an "open" file handle associated with device_name. If + * the implementation (possibly for one OS) cannot determine from dev_fd if + * a SCSI or NVMe pass-through is referenced, then it might guess based on + * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if + * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is + * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes + * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0. + * If error, returns negated errno (operating system) value. */ +int +check_pt_file_handle(int device_fd, const char * device_name, int vb) +{ + int res; + STORAGE_BUS_TYPE bt; + const char * dnp = device_name; + struct sg_pt_handle * shp; + + if (vb > 3) + pr2ws("%s: device_name: %s\n", __func__, dnp); + shp = get_open_pt_handle(NULL, device_fd, vb > 1); + if (NULL == shp) { + pr2ws("%s: device_fd (%s) bad or not in_use ??\n", __func__, + dnp ? dnp : ""); + return -ENODEV; + } + if (shp->bus_type_failed) { + if (vb > 2) + pr2ws("%s: skip because get_bus_type() has failed\n", __func__); + return 0; + } + dnp = dnp ? dnp : shp->dname; + res = get_bus_type(shp, dnp, &bt, vb); + if (res < 0) { + if (! shp->got_physical_drive) { + res = get_scsi_pdt(shp, vb); + if (res >= 0) + return 1; + } + return res; + } + return (BusTypeNvme == bt) ? 3 : 1; + /* NVMe "char" ?? device, could be enclosure: 3 */ + /* SCSI generic pass-though device: 1 */ +} + +#if (HAVE_NVME && (! IGNORE_NVME)) +static bool checked_ev_dsense = false; +static bool ev_dsense = false; +#endif + +struct sg_pt_base * +construct_scsi_pt_obj_with_fd(int dev_fd, int vb) +{ + int res; + struct sg_pt_win32_scsi * psp; + struct sg_pt_base * vp = NULL; + struct sg_pt_handle * shp = NULL; + + if (dev_fd >= 0) { + shp = get_open_pt_handle(NULL, dev_fd, vb > 1); + if (NULL == shp) { + if (vb) + pr2ws("%s: dev_fd is not open\n", __func__); + return NULL; + } + if (! (shp->bus_type_failed || shp->checked_handle)) { + res = get_bus_type(shp, shp->dname, NULL, vb); + if (res < 0) { + if (! shp->got_physical_drive) + res = get_scsi_pdt(shp, vb); + if ((res < 0) && (vb > 1)) + pr2ws("%s: get_bus_type() errno=%d, continue\n", __func__, + -res); + } + } + } + psp = (struct sg_pt_win32_scsi *)calloc(sizeof(struct sg_pt_win32_scsi), + 1); + if (psp) { + psp->dev_fd = (dev_fd < 0) ? -1 : dev_fd; + if (shp) { + psp->is_nvme = shp->is_nvme; + psp->dev_statp = &shp->dev_stat; +#if (HAVE_NVME && (! IGNORE_NVME)) + sntl_init_dev_stat(psp->dev_statp); + if (! checked_ev_dsense) { + ev_dsense = sg_get_initial_dsense(); + checked_ev_dsense = true; + } + shp->dev_stat.scsi_dsense = ev_dsense; +#endif + } + if (psp->is_nvme) { + ; /* should be 'psp->nvme_nsid = shp->nvme_nsid' */ + } else if (spt_direct) { + psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; + psp->swb_d.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN; + psp->swb_d.spt.SenseInfoOffset = + offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf); + psp->swb_d.spt.TimeOutValue = DEF_TIMEOUT; + } else { + psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; + psp->swb_i.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN; + psp->swb_i.spt.SenseInfoOffset = + offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf); + psp->swb_i.spt.TimeOutValue = DEF_TIMEOUT; + } + vp = (struct sg_pt_base *)malloc(sizeof(struct sg_pt_win32_scsi *)); + /* yes, allocating the size of a pointer (4 or 8 bytes) */ + if (vp) + vp->implp = psp; + else + free(psp); + } + if ((NULL == vp) && vb) + pr2ws("%s: about to return NULL, space problem\n", __func__); + return vp; +} + +struct sg_pt_base * +construct_scsi_pt_obj(void) +{ + return construct_scsi_pt_obj_with_fd(-1, 0); +} + +void +destruct_scsi_pt_obj(struct sg_pt_base * vp) +{ + if (vp) { + struct sg_pt_win32_scsi * psp = vp->implp; + + if (psp) { + free(psp); + } + free(vp); + } +} + +/* Forget any previous dev_han and install the one given. May attempt to + * find file type (e.g. if pass-though) from OS so there could be an error. + * Returns 0 for success or the same value as get_scsi_pt_os_err() + * will return. dev_han should be >= 0 for a valid file handle or -1 . */ +int +set_pt_file_handle(struct sg_pt_base * vp, int dev_han, int vb) +{ + int res; + struct sg_pt_win32_scsi * psp; + + if (NULL == vp) { + if (vb) + pr2ws(">>>> %s: pointer to object is NULL\n", __func__); + return EINVAL; + } + if ((psp = vp->implp)) { + struct sg_pt_handle * shp; + + if (dev_han < 0) { + psp->dev_fd = -1; + psp->is_nvme = false; + psp->nvme_nsid = 0; + return 0; + } + shp = get_open_pt_handle(psp, dev_han, vb > 1); + if (NULL == shp) { + if (vb) + pr2ws("%s: dev_han (%d) is invalid\n", __func__, dev_han); + psp->os_err = EINVAL; + return psp->os_err; + } + psp->os_err = 0; + psp->transport_err = 0; + psp->in_err = 0; + psp->scsi_status = 0; + psp->dev_fd = dev_han; + if (! (shp->bus_type_failed || shp->checked_handle)) { + res = get_bus_type(shp, shp->dname, NULL, vb); + if (res < 0) { + res = get_scsi_pdt(shp, vb); + if (res >= 0) /* clears shp->bus_type_failed on success */ + psp->os_err = 0; + } + if ((res < 0) && (vb > 2)) + pr2ws("%s: get_bus_type() errno=%d\n", __func__, -res); + } + if (shp->bus_type_failed) + psp->os_err = EIO; + if (psp->os_err) + return psp->os_err; + psp->is_nvme = shp->is_nvme; + psp->nvme_nsid = 0; /* should be 'psp->nvme_nsid = shp->nvme_nsid' */ + psp->dev_statp = &shp->dev_stat; + } + return 0; +} + +/* Valid file handles (which is the return value) are >= 0 . Returns -1 + * if there is no valid file handle. */ +int +get_pt_file_handle(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp; + + if (vp) { + psp = vp->implp; + return psp ? psp->dev_fd : -1; + } + return -1; +} + +/* Keep state information such as dev_fd and nvme_nsid */ +void +clear_scsi_pt_obj(struct sg_pt_base * vp) +{ + bool is_nvme; + int dev_fd; + uint32_t nvme_nsid; + struct sg_pt_win32_scsi * psp = vp->implp; + struct sg_sntl_dev_state_t * dsp; + + if (psp) { + dev_fd = psp->dev_fd; + is_nvme = psp->is_nvme; + nvme_nsid = psp->nvme_nsid; + dsp = psp->dev_statp; + memset(psp, 0, sizeof(struct sg_pt_win32_scsi)); + if (spt_direct) { + psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; + psp->swb_d.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN; + psp->swb_d.spt.SenseInfoOffset = + offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf); + psp->swb_d.spt.TimeOutValue = DEF_TIMEOUT; + } else { + psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; + psp->swb_i.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN; + psp->swb_i.spt.SenseInfoOffset = + offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf); + psp->swb_i.spt.TimeOutValue = DEF_TIMEOUT; + } + psp->dev_fd = dev_fd; + psp->is_nvme = is_nvme; + psp->nvme_nsid = nvme_nsid; + psp->dev_statp = dsp; + } +} + +void +set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb, + int cdb_len) +{ + bool scsi_cdb = sg_is_scsi_cdb(cdb, cdb_len); + struct sg_pt_win32_scsi * psp = vp->implp; + + if (! scsi_cdb) { + if (psp->have_nvme_cmd) + ++psp->in_err; + else + psp->have_nvme_cmd = true; + memcpy(psp->nvme_cmd, cdb, cdb_len); + } else if (spt_direct) { + if (psp->swb_d.spt.CdbLength > 0) + ++psp->in_err; + if (cdb_len > (int)sizeof(psp->swb_d.spt.Cdb)) { + ++psp->in_err; + return; + } + memcpy(psp->swb_d.spt.Cdb, cdb, cdb_len); + psp->swb_d.spt.CdbLength = cdb_len; + } else { + if (psp->swb_i.spt.CdbLength > 0) + ++psp->in_err; + if (cdb_len > (int)sizeof(psp->swb_i.spt.Cdb)) { + ++psp->in_err; + return; + } + memcpy(psp->swb_i.spt.Cdb, cdb, cdb_len); + psp->swb_i.spt.CdbLength = cdb_len; + } +} + +void +set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, + int sense_len) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + if (psp->sensep) + ++psp->in_err; + memset(sense, 0, sense_len); + psp->sensep = sense; + psp->sense_len = sense_len; +} + +/* from device */ +void +set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + if (psp->dxferp) + ++psp->in_err; + if (dxfer_len > 0) { + psp->dxferp = dxferp; + psp->dxfer_len = (uint32_t)dxfer_len; + psp->is_read = true; + if (spt_direct) + psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_IN; + else + psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_IN; + } +} + +/* to device */ +void +set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp, + int dxfer_len) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + if (psp->dxferp) + ++psp->in_err; + if (dxfer_len > 0) { + psp->dxferp = (uint8_t *)dxferp; + psp->dxfer_len = (uint32_t)dxfer_len; + if (spt_direct) + psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_OUT; + else + psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_OUT; + } +} + +void +set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * mdxferp, + uint32_t mdxfer_len, bool out_true) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + if (psp->mdxferp) + ++psp->in_err; + if (mdxfer_len > 0) { + psp->mdxferp = mdxferp; + psp->mdxfer_len = mdxfer_len; + psp->mdxfer_out = out_true; + } +} + +void +set_scsi_pt_packet_id(struct sg_pt_base * vp __attribute__ ((unused)), + int pack_id __attribute__ ((unused))) +{ +} + +void +set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag __attribute__ ((unused))) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + ++psp->in_err; +} + +void +set_scsi_pt_task_management(struct sg_pt_base * vp, + int tmf_code __attribute__ ((unused))) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + ++psp->in_err; +} + +void +set_scsi_pt_task_attr(struct sg_pt_base * vp, + int attrib __attribute__ ((unused)), + int priority __attribute__ ((unused))) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + ++psp->in_err; +} + +void +set_scsi_pt_flags(struct sg_pt_base * objp, int flags) +{ + /* do nothing, suppress warnings */ + objp = objp; + flags = flags; +} + +/* Executes SCSI command (or at least forwards it to lower layers) + * using direct interface. Clears os_err field prior to active call (whose + * result may set it again). */ +static int +scsi_pt_direct(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + int time_secs, int vb) +{ + BOOL status; + DWORD returned; + + psp->os_err = 0; + if (0 == psp->swb_d.spt.CdbLength) { + if (vb) + pr2ws("No command (cdb) given\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + psp->swb_d.spt.Length = sizeof (SCSI_PASS_THROUGH_DIRECT); + psp->swb_d.spt.PathId = shp->bus; + psp->swb_d.spt.TargetId = shp->target; + psp->swb_d.spt.Lun = shp->lun; + psp->swb_d.spt.TimeOutValue = time_secs; + psp->swb_d.spt.DataTransferLength = psp->dxfer_len; + if (vb > 4) { + pr2ws(" spt_direct, adapter: %s Length=%d ScsiStatus=%d PathId=%d " + "TargetId=%d Lun=%d\n", shp->adapter, + (int)psp->swb_d.spt.Length, (int)psp->swb_d.spt.ScsiStatus, + (int)psp->swb_d.spt.PathId, (int)psp->swb_d.spt.TargetId, + (int)psp->swb_d.spt.Lun); + pr2ws(" CdbLength=%d SenseInfoLength=%d DataIn=%d " + "DataTransferLength=%u\n", + (int)psp->swb_d.spt.CdbLength, + (int)psp->swb_d.spt.SenseInfoLength, + (int)psp->swb_d.spt.DataIn, + (unsigned int)psp->swb_d.spt.DataTransferLength); + pr2ws(" TimeOutValue=%u SenseInfoOffset=%u\n", + (unsigned int)psp->swb_d.spt.TimeOutValue, + (unsigned int)psp->swb_d.spt.SenseInfoOffset); + } + psp->swb_d.spt.DataBuffer = psp->dxferp; + status = DeviceIoControl(shp->fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, + &psp->swb_d, + sizeof(psp->swb_d), + &psp->swb_d, + sizeof(psp->swb_d), + &returned, + NULL); + if (! status) { + unsigned int u; + + u = (unsigned int)GetLastError(); + if (vb) { + char b[128]; + + pr2ws("%s: DeviceIoControl: %s [%u]\n", __func__, + get_err_str(u, sizeof(b), b), u); + } + psp->transport_err = (int)u; + psp->os_err = EIO; + return 0; /* let app find transport error */ + } + + psp->scsi_status = psp->swb_d.spt.ScsiStatus; + if ((SAM_STAT_CHECK_CONDITION == psp->scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == psp->scsi_status)) + memcpy(psp->sensep, psp->swb_d.ucSenseBuf, psp->sense_len); + else + psp->sense_len = 0; + psp->sense_resid = 0; + if ((psp->dxfer_len > 0) && (psp->swb_d.spt.DataTransferLength > 0)) + psp->resid = psp->dxfer_len - psp->swb_d.spt.DataTransferLength; + else + psp->resid = 0; + + return 0; +} + +/* Executes SCSI command (or at least forwards it to lower layers) using + * indirect interface. Clears os_err field prior to active call (whose + * result may set it again). */ +static int +scsi_pt_indirect(struct sg_pt_base * vp, struct sg_pt_handle * shp, + int time_secs, int vb) +{ + BOOL status; + DWORD returned; + struct sg_pt_win32_scsi * psp = vp->implp; + + if (0 == psp->swb_i.spt.CdbLength) { + if (vb) + pr2ws("No command (cdb) given\n"); + return SCSI_PT_DO_BAD_PARAMS; + } + if (psp->dxfer_len > (int)sizeof(psp->swb_i.ucDataBuf)) { + int extra = psp->dxfer_len - (int)sizeof(psp->swb_i.ucDataBuf); + struct sg_pt_win32_scsi * epsp; + + if (vb > 4) + pr2ws("spt_indirect: dxfer_len (%d) too large for initial data\n" + " buffer (%d bytes), try enlarging\n", psp->dxfer_len, + (int)sizeof(psp->swb_i.ucDataBuf)); + epsp = (struct sg_pt_win32_scsi *) + calloc(sizeof(struct sg_pt_win32_scsi) + extra, 1); + if (NULL == epsp) { + pr2ws("%s: failed to enlarge data buffer to %d bytes\n", __func__, + psp->dxfer_len); + psp->os_err = ENOMEM; + return -psp->os_err; + } + memcpy(epsp, psp, sizeof(struct sg_pt_win32_scsi)); + free(psp); + vp->implp = epsp; + psp = epsp; + } + psp->swb_i.spt.Length = sizeof (SCSI_PASS_THROUGH); + psp->swb_i.spt.DataBufferOffset = + offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf); + psp->swb_i.spt.PathId = shp->bus; + psp->swb_i.spt.TargetId = shp->target; + psp->swb_i.spt.Lun = shp->lun; + psp->swb_i.spt.TimeOutValue = time_secs; + psp->swb_i.spt.DataTransferLength = psp->dxfer_len; + if (vb > 4) { + pr2ws(" spt_indirect, adapter: %s Length=%d ScsiStatus=%d PathId=%d " + "TargetId=%d Lun=%d\n", shp->adapter, + (int)psp->swb_i.spt.Length, (int)psp->swb_i.spt.ScsiStatus, + (int)psp->swb_i.spt.PathId, (int)psp->swb_i.spt.TargetId, + (int)psp->swb_i.spt.Lun); + pr2ws(" CdbLength=%d SenseInfoLength=%d DataIn=%d " + "DataTransferLength=%u\n", + (int)psp->swb_i.spt.CdbLength, + (int)psp->swb_i.spt.SenseInfoLength, + (int)psp->swb_i.spt.DataIn, + (unsigned int)psp->swb_i.spt.DataTransferLength); + pr2ws(" TimeOutValue=%u DataBufferOffset=%u " + "SenseInfoOffset=%u\n", + (unsigned int)psp->swb_i.spt.TimeOutValue, + (unsigned int)psp->swb_i.spt.DataBufferOffset, + (unsigned int)psp->swb_i.spt.SenseInfoOffset); + } + if ((psp->dxfer_len > 0) && + (SCSI_IOCTL_DATA_OUT == psp->swb_i.spt.DataIn)) + memcpy(psp->swb_i.ucDataBuf, psp->dxferp, psp->dxfer_len); + status = DeviceIoControl(shp->fh, IOCTL_SCSI_PASS_THROUGH, + &psp->swb_i, + sizeof(psp->swb_i), + &psp->swb_i, + sizeof(psp->swb_i), + &returned, + NULL); + if (! status) { + uint32_t u = (uint32_t)GetLastError(); + + if (vb) { + char b[128]; + + pr2ws("%s: DeviceIoControl: %s [%u]\n", __func__, + get_err_str(u, sizeof(b), b), u); + } + psp->transport_err = (int)u; + psp->os_err = EIO; + return 0; /* let app find transport error */ + } + if ((psp->dxfer_len > 0) && (SCSI_IOCTL_DATA_IN == psp->swb_i.spt.DataIn)) + memcpy(psp->dxferp, psp->swb_i.ucDataBuf, psp->dxfer_len); + + psp->scsi_status = psp->swb_i.spt.ScsiStatus; + if ((SAM_STAT_CHECK_CONDITION == psp->scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == psp->scsi_status)) + memcpy(psp->sensep, psp->swb_i.ucSenseBuf, psp->sense_len); + else + psp->sense_len = 0; + psp->sense_resid = 0; + if ((psp->dxfer_len > 0) && (psp->swb_i.spt.DataTransferLength > 0)) + psp->resid = psp->dxfer_len - psp->swb_i.spt.DataTransferLength; + else + psp->resid = 0; + + return 0; +} + +/* Executes SCSI or NVME command (or at least forwards it to lower layers). + * Clears os_err field prior to active call (whose result may set it + * again). Returns 0 on success, positive SCSI_PT_DO_* errors for syntax + * like errors and negated errnos for OS errors. For Windows its errors + * are placed in psp->transport_err and a errno is simulated. */ +int +do_scsi_pt(struct sg_pt_base * vp, int dev_fd, int time_secs, int vb) +{ + int res; + struct sg_pt_win32_scsi * psp = vp->implp; + struct sg_pt_handle * shp; + + if (! (vp && ((psp = vp->implp)))) { + if (vb) + pr2ws("%s: NULL 1st argument to this function\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + psp->os_err = 0; + if (dev_fd >= 0) { + if ((psp->dev_fd >= 0) && (dev_fd != psp->dev_fd)) { + if (vb) + pr2ws("%s: file descriptor given to create() and here " + "differ\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + psp->dev_fd = dev_fd; + } else if (psp->dev_fd < 0) { /* so no dev_fd in ctor */ + if (vb) + pr2ws("%s: missing device file descriptor\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } else + dev_fd = psp->dev_fd; + shp = get_open_pt_handle(psp, dev_fd, vb > 3); + if (NULL == shp) + return -psp->os_err; + + if (! (shp->bus_type_failed || shp->checked_handle)) { + res = get_bus_type(shp, shp->dname, NULL, vb); + if (res < 0) { + res = get_scsi_pdt(shp, vb); + if (res >= 0) /* clears shp->bus_type_failed on success */ + psp->os_err = 0; + } + if ((res < 0) && (vb > 2)) + pr2ws("%s: get_bus_type() errno=%d\n", __func__, -res); + } + if (shp->bus_type_failed) + psp->os_err = EIO; + if (psp->os_err) + return -psp->os_err; + psp->is_nvme = shp->is_nvme; + psp->dev_statp = &shp->dev_stat; + + if (psp->is_nvme) + return nvme_pt(psp, shp, time_secs, vb); + else if (spt_direct) + return scsi_pt_direct(psp, shp, time_secs, vb); + else + return scsi_pt_indirect(vp, shp, time_secs, vb); +} + +int +get_scsi_pt_result_category(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + if (psp->transport_err) /* give transport error highest priority */ + return SCSI_PT_RESULT_TRANSPORT_ERR; + else if (psp->os_err) + return SCSI_PT_RESULT_OS_ERR; + else if ((SAM_STAT_CHECK_CONDITION == psp->scsi_status) || + (SAM_STAT_COMMAND_TERMINATED == psp->scsi_status)) + return SCSI_PT_RESULT_SENSE; + else if (psp->scsi_status) + return SCSI_PT_RESULT_STATUS; + else + return SCSI_PT_RESULT_GOOD; +} + +int +get_scsi_pt_resid(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + return psp->resid; +} + +int +get_scsi_pt_status_response(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + if (NULL == psp) + return 0; + return psp->nvme_direct ? (int)psp->nvme_status : psp->scsi_status; +} + +uint32_t +get_pt_result(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + if (NULL == psp) + return 0; + return psp->nvme_direct ? psp->nvme_result : (uint32_t)psp->scsi_status; +} + +int +get_scsi_pt_sense_len(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + int len; + + len = psp->sense_len - psp->sense_resid; + return (len > 0) ? len : 0; +} + +int +get_scsi_pt_duration_ms(const struct sg_pt_base * vp __attribute__ ((unused))) +{ + // const struct sg_pt_win32_scsi * psp = vp->implp; + + return -1; +} + +int +get_scsi_pt_transport_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + return psp->transport_err; +} + +void +set_scsi_pt_transport_err(struct sg_pt_base * vp, int err) +{ + struct sg_pt_win32_scsi * psp = vp->implp; + + psp->transport_err = err; +} + +int +get_scsi_pt_os_err(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + return psp->os_err; +} + +bool +pt_device_is_nvme(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + return psp ? psp->is_nvme : false; +} + +/* If a NVMe block device (which includes the NSID) handle is associated + * * with 'vp', then its NSID is returned (values range from 0x1 to + * * 0xffffffe). Otherwise 0 is returned. */ +uint32_t +get_pt_nvme_nsid(const struct sg_pt_base * vp) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + + return psp->nvme_nsid; +} + +/* Use the transport_err for Windows errors. */ +char * +get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len, + char * b) +{ + struct sg_pt_win32_scsi * psp = (struct sg_pt_win32_scsi *)vp->implp; + + if ((max_b_len < 2) || (NULL == psp) || (NULL == b)) { + if (b && (max_b_len > 0)) + b[0] = '\0'; + return b; + } + return get_err_str(psp->transport_err, max_b_len, b); +} + +char * +get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b) +{ + const struct sg_pt_win32_scsi * psp = vp->implp; + const char * cp; + + cp = safe_strerror(psp->os_err); + strncpy(b, cp, max_b_len); + if ((int)strlen(cp) >= max_b_len) + b[max_b_len - 1] = '\0'; + return b; +} + +#if (HAVE_NVME && (! IGNORE_NVME)) + +static void +mk_sense_asc_ascq(struct sg_pt_win32_scsi * psp, int sk, int asc, int ascq, + int vb) +{ + bool dsense = psp->dev_statp->scsi_dsense; + int slen = psp->sense_len; + int n; + uint8_t * sbp = (uint8_t *)psp->sensep; + + psp->scsi_status = SAM_STAT_CHECK_CONDITION; + if ((slen < 8) || ((! dsense) && (slen < 14))) { + if (vb) + pr2ws("%s: sense_len=%d too short, want 14 or more\n", + __func__, slen); + return; + } + if (dsense) + n = (slen > 32) ? 32 : slen; + else + n = (slen < 18) ? slen : 18; + psp->sense_resid = (slen > n) ? (slen - n) : 0; + memset(sbp, 0, slen); + sg_build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", __func__, sk, + asc, ascq); +} + +static void +mk_sense_from_nvme_status(struct sg_pt_win32_scsi * psp, int vb) +{ + bool ok; + bool dsense = psp->dev_statp->scsi_dsense; + int n; + int slen = psp->sense_len; + uint8_t sstatus, sk, asc, ascq; + uint8_t * sbp = (uint8_t *)psp->sensep; + + ok = sg_nvme_status2scsi(psp->nvme_status, &sstatus, &sk, &asc, &ascq); + if (! ok) { /* can't find a mapping to a SCSI error, so ... */ + sstatus = SAM_STAT_CHECK_CONDITION; + sk = SPC_SK_ILLEGAL_REQUEST; + asc = 0xb; + ascq = 0x0; /* asc: "WARNING" purposely vague */ + } + + psp->scsi_status = sstatus; + if ((slen < 8) || ((! dsense) && (slen < 14))) { + if (vb) + pr2ws("%s: sense_len=%d too short, want 14 or more\n", __func__, + slen); + return; + } + if (dsense) + n = (slen > 32) ? 32 : slen; + else + n = (slen < 18) ? slen : 18; + psp->sense_resid = (slen > n) ? slen - n : 0; + memset(sbp, 0, slen); + sg_build_sense_buffer(dsense, sbp, sk, asc, ascq); + if (dsense && (psp->nvme_status > 0)) + sg_nvme_desc2sense(sbp, false /* dnr */, false /* more */, + psp->nvme_status); + if (vb > 3) + pr2ws("%s: [status, sense_key,asc,ascq]: [0x%x, 0x%x,0x%x,0x%x]\n", + __func__, sstatus, sk, asc, ascq); +} + +/* Set in_bit to -1 to indicate no bit position of invalid field */ +static void +mk_sense_invalid_fld(struct sg_pt_win32_scsi * psp, bool in_cdb, int in_byte, + int in_bit, int vb) +{ + bool dsense = psp->dev_statp->scsi_dsense; + int sl, asc, n; + int slen = psp->sense_len; + uint8_t * sbp = (uint8_t *)psp->sensep; + uint8_t sks[4]; + + psp->scsi_status = SAM_STAT_CHECK_CONDITION; + asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST; + if ((slen < 8) || ((! dsense) && (slen < 14))) { + if (vb) + pr2ws("%s: max_response_len=%d too short, want 14 or more\n", + __func__, slen); + return; + } + if (dsense) + n = (slen > 32) ? 32 : slen; + else + n = (slen < 18) ? slen : 18; + psp->sense_resid = (slen > n) ? (slen - n) : 0; + memset(sbp, 0, slen); + sg_build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0); + memset(sks, 0, sizeof(sks)); + sks[0] = 0x80; + if (in_cdb) + sks[0] |= 0x40; + if (in_bit >= 0) { + sks[0] |= 0x8; + sks[0] |= (0x7 & in_bit); + } + sg_put_unaligned_be16(in_byte, sks + 1); + if (dsense) { + sl = sbp[7] + 8; + sbp[7] = sl; + sbp[sl] = 0x2; + sbp[sl + 1] = 0x6; + memcpy(sbp + sl + 4, sks, 3); + } else + memcpy(sbp + 15, sks, 3); + if (vb > 3) + pr2ws("%s: [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n", + __func__, asc, in_cdb ? 'C' : 'D', in_byte, + ((in_bit > 0) ? (0x7 & in_bit) : 0)); +} + +#if W10_NVME_NON_PASSTHRU /* W10 and later, no real pass-through ?? */ + +#ifndef NVME_MAX_LOG_SIZE +#define NVME_MAX_LOG_SIZE 4096 +#endif + +static int +nvme_identify(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, int vb) +{ + bool id_ctrl; + int res = 0; + const uint32_t pg_sz = sg_get_page_size(); + uint32_t cdw10, nsid, n; + const uint8_t * bp; + BOOL result; + PVOID buffer = NULL; + uint8_t * free_buffer = NULL; + ULONG bufferLength = 0; + ULONG returnedLength = 0; + STORAGE_PROPERTY_QUERY * query = NULL; + STORAGE_PROTOCOL_SPECIFIC_DATA * protocolData = NULL; + STORAGE_PROTOCOL_DATA_DESCRIPTOR * protocolDataDescr = NULL; + + nsid = sg_get_unaligned_le32(cmdp + SG_NVME_PT_NSID); + cdw10 = sg_get_unaligned_le32(cmdp + SG_NVME_PT_CDW10); + id_ctrl = (0x1 == cdw10); + n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen; + bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) + + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + n; + buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false); + if (buffer == NULL) { + res = sg_convert_errno(ENOMEM); + if (vb > 1) + pr2ws("%s: unable to allocate memory\n", __func__); + psp->os_err = res; + return -res; + } + query = (STORAGE_PROPERTY_QUERY *)buffer; + + query->PropertyId = id_ctrl ? StorageAdapterProtocolSpecificProperty : + StorageDeviceProtocolSpecificProperty; + query->QueryType = PropertyStandardQuery; + protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR *)buffer; + protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA *) + query->AdditionalParameters; + + protocolData->ProtocolType = ProtocolTypeNvme; + protocolData->DataType = NVMeDataTypeIdentify; + protocolData->ProtocolDataRequestValue = cdw10; + if (! id_ctrl) + protocolData->ProtocolDataRequestSubValue = nsid; + protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA); + protocolData->ProtocolDataLength = dlen; + + result = DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY, + buffer, bufferLength, buffer, bufferLength, + &returnedLength, (OVERLAPPED*)0); + if ((! result) || (0 == returnedLength)) { + n = (uint32_t)GetLastError(); + psp->transport_err = n; + psp->os_err = EIO; /* simulate Unix error, */ + if (vb > 2) { + char b[128]; + + pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_%s) failed: %s " + "[%u]\n", __func__, (id_ctrl ? "ctrl" : "ns"), + get_err_str(n, sizeof(b), b), n); + } + res = -psp->os_err; + goto err_out; + } + if (dlen > 0) { + protocolData = &protocolDataDescr->ProtocolSpecificData; + bp = (const uint8_t *)protocolData + protocolData->ProtocolDataOffset; + memcpy(dp, bp, dlen); + if (0 == psp->nvme_nsid) { + uint32_t nn = sg_get_unaligned_le32(bp + 516); + + if (1 == nn) /* if physical drive has only 1 namespace */ + psp->nvme_nsid = 1; /* then its nsid must be 1 */ + /* N.B. Need better get_nsid_from _handle technique when 2 or + * more namespaces. Suggestions? */ + } + } + psp->nvme_status = 0; + psp->nvme_result = + protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData; + if (vb > 3) + pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) success, " + "returnedLength=%u\n", __func__, (uint32_t)returnedLength); + res = 0; +err_out: + if (free_buffer) + free(free_buffer); + return res; +} + +static int +nvme_get_features(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, int vb) +{ + int res = 0; + const uint32_t pg_sz = sg_get_page_size(); + uint32_t cdw10, nsid, n; + const uint8_t * bp; + BOOL result; + PVOID buffer = NULL; + uint8_t * free_buffer = NULL; + ULONG bufferLength = 0; + ULONG returnedLength = 0; + STORAGE_PROPERTY_QUERY * query = NULL; + STORAGE_PROTOCOL_SPECIFIC_DATA * protocolData = NULL; + STORAGE_PROTOCOL_DATA_DESCRIPTOR * protocolDataDescr = NULL; + + nsid = sg_get_unaligned_le32(cmdp + SG_NVME_PT_NSID); + cdw10 = sg_get_unaligned_le32(cmdp + SG_NVME_PT_CDW10); + n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen; + bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) + + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + n; + buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false); + if (buffer == NULL) { + res = sg_convert_errno(ENOMEM); + if (vb > 1) + pr2ws("%s: unable to allocate memory\n", __func__); + psp->os_err = res; + return -res; + } + query = (STORAGE_PROPERTY_QUERY *)buffer; + + query->PropertyId = StorageDeviceProtocolSpecificProperty; + query->QueryType = PropertyStandardQuery; + protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR *)buffer; + protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA *) + query->AdditionalParameters; + + protocolData->ProtocolType = ProtocolTypeNvme; + protocolData->DataType = NVMeDataTypeFeature; /* Get Features */ + protocolData->ProtocolDataRequestValue = cdw10; + protocolData->ProtocolDataRequestSubValue = nsid; + protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA); + protocolData->ProtocolDataLength = dlen; + + result = DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY, + buffer, bufferLength, buffer, bufferLength, + &returnedLength, (OVERLAPPED*)0); + if ((! result) || (0 == returnedLength)) { + n = (uint32_t)GetLastError(); + psp->transport_err = n; + psp->os_err = EIO; /* simulate Unix error, */ + if (vb > 2) { + char b[128]; + + pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) failed: %s " + "[%u]\n", __func__, get_err_str(n, sizeof(b), b), n); + } + res = -psp->os_err; + goto err_out; + } + if (dlen > 0) { + protocolData = &protocolDataDescr->ProtocolSpecificData; + bp = (const uint8_t *)protocolData + protocolData->ProtocolDataOffset; + memcpy(dp, bp, dlen); + } + psp->nvme_status = 0; + psp->nvme_result = + protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData; + if (vb > 3) + pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) success, " + "returnedLength=%u\n", __func__, (uint32_t)returnedLength); + res = 0; +err_out: + if (free_buffer) + free(free_buffer); + return res; +} + +static int +nvme_get_log_page(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, int vb) +{ + int res = 0; + const uint32_t pg_sz = sg_get_page_size(); + uint32_t cdw10, nsid, n; + const uint8_t * bp; + BOOL result; + PVOID buffer = NULL; + uint8_t * free_buffer = NULL; + ULONG bufferLength = 0; + ULONG returnedLength = 0; + STORAGE_PROPERTY_QUERY * query = NULL; + STORAGE_PROTOCOL_SPECIFIC_DATA * protocolData = NULL; + STORAGE_PROTOCOL_DATA_DESCRIPTOR * protocolDataDescr = NULL; + + nsid = sg_get_unaligned_le32(cmdp + SG_NVME_PT_NSID); + cdw10 = sg_get_unaligned_le32(cmdp + SG_NVME_PT_CDW10); + n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen; + bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) + + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + n; + buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false); + if (buffer == NULL) { + res = sg_convert_errno(ENOMEM); + if (vb > 1) + pr2ws("%s: unable to allocate memory\n", __func__); + psp->os_err = res; + return -res; + } + query = (STORAGE_PROPERTY_QUERY *)buffer; + + query->PropertyId = StorageDeviceProtocolSpecificProperty; + query->QueryType = PropertyStandardQuery; + protocolDataDescr = (STORAGE_PROTOCOL_DATA_DESCRIPTOR *)buffer; + protocolData = (STORAGE_PROTOCOL_SPECIFIC_DATA *) + query->AdditionalParameters; + + protocolData->ProtocolType = ProtocolTypeNvme; + protocolData->DataType = NVMeDataTypeLogPage; /* Get Log Page */ + protocolData->ProtocolDataRequestValue = cdw10; + protocolData->ProtocolDataRequestSubValue = nsid; + protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA); + protocolData->ProtocolDataLength = dlen; + + result = DeviceIoControl(shp->fh, IOCTL_STORAGE_QUERY_PROPERTY, + buffer, bufferLength, buffer, bufferLength, + &returnedLength, (OVERLAPPED*)0); + if ((! result) || (0 == returnedLength)) { + n = (uint32_t)GetLastError(); + psp->transport_err = n; + psp->os_err = EIO; /* simulate Unix error, */ + if (vb > 2) { + char b[128]; + + pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) failed: %s " + "[%u]\n", __func__, get_err_str(n, sizeof(b), b), n); + } + res = -psp->os_err; + goto err_out; + } + if (dlen > 0) { + protocolData = &protocolDataDescr->ProtocolSpecificData; + bp = (const uint8_t *)protocolData + protocolData->ProtocolDataOffset; + memcpy(dp, bp, dlen); + } + psp->nvme_status = 0; + psp->nvme_result = + protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData; + if (vb > 3) + pr2ws("%s: IOCTL_STORAGE_QUERY_PROPERTY(id_ctrl) success, " + "returnedLength=%u\n", __func__, (uint32_t)returnedLength); + res = 0; +err_out: + if (free_buffer) + free(free_buffer); + return res; +} + +static int +nvme_real_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, bool is_read, + int time_secs, int vb) +{ + int res = 0; + const uint32_t cmd_len = 64; + const uint32_t pg_sz = sg_get_page_size(); + uint32_t n, k; + uint32_t rd_off = 0; + uint32_t slen = psp->sense_len; + uint8_t * bp; + uint8_t * sbp = psp->sensep; + BOOL ok; + PVOID buffer = NULL; + uint8_t * free_buffer = NULL; + ULONG bufferLength = 0; + ULONG returnLength = 0; + STORAGE_PROTOCOL_COMMAND * protoCmdp; + const NVME_ERROR_INFO_LOG * neilp; + + n = dlen < NVME_MAX_LOG_SIZE ? NVME_MAX_LOG_SIZE : dlen; + bufferLength = offsetof(STORAGE_PROTOCOL_COMMAND, Command) + + cmd_len + + sizeof(NVME_ERROR_INFO_LOG) + n; + buffer = sg_memalign(bufferLength, pg_sz, &free_buffer, false); + if (buffer == NULL) { + res = sg_convert_errno(ENOMEM); + if (vb > 1) + pr2ws("%s: unable to allocate memory\n", __func__); + psp->os_err = res; + return -res; + } + protoCmdp = (STORAGE_PROTOCOL_COMMAND *)buffer; + protoCmdp->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION; + protoCmdp->Length = sizeof(STORAGE_PROTOCOL_COMMAND); + protoCmdp->ProtocolType = ProtocolTypeNvme; + /* without *_ADAPTER_REQUEST flag, goes to device */ + protoCmdp->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST; + /* protoCmdp->Flags = 0; */ + protoCmdp->CommandLength = cmd_len; + protoCmdp->ErrorInfoLength = sizeof(NVME_ERROR_INFO_LOG); + if (dlen > 0) { + if (is_read) + protoCmdp->DataFromDeviceTransferLength = dlen; + else + protoCmdp->DataToDeviceTransferLength = dlen; + } + protoCmdp->TimeOutValue = (time_secs > 0) ? time_secs : DEF_TIMEOUT; + protoCmdp->ErrorInfoOffset = + offsetof(STORAGE_PROTOCOL_COMMAND, Command) + cmd_len; + n = protoCmdp->ErrorInfoOffset + protoCmdp->ErrorInfoLength; + if (is_read) { + protoCmdp->DataFromDeviceBufferOffset = n; + rd_off = n; + } else + protoCmdp->DataToDeviceBufferOffset = n; + protoCmdp->CommandSpecific = + STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND; + memcpy(protoCmdp->Command, cmdp, cmd_len); + if ((dlen > 0) && (! is_read)) { + bp = (uint8_t *)protoCmdp + n; + memcpy(bp, dp, dlen); + } + + ok = DeviceIoControl(shp->fh, IOCTL_STORAGE_PROTOCOL_COMMAND, + buffer, bufferLength, buffer, bufferLength, + &returnLength, (OVERLAPPED*)0); + if (! ok) { + n = (uint32_t)GetLastError(); + psp->transport_err = n; + psp->os_err = EIO; /* simulate Unix error, */ + if (vb > 2) { + char b[128]; + + pr2ws("%s: IOCTL_STORAGE_PROTOCOL_COMMAND failed: %s " + "[%u]\n", __func__, get_err_str(n, sizeof(b), b), n); + pr2ws(" ... ReturnStatus=0x%x, ReturnLength=%u\n", + (uint32_t)protoCmdp->ReturnStatus, (uint32_t)returnLength); + } + res = -psp->os_err; + goto err_out; + } + bp = (uint8_t *)protoCmdp + protoCmdp->ErrorInfoOffset; + neilp = (const NVME_ERROR_INFO_LOG *)bp; + /* Shift over top of Phase tag bit */ + psp->nvme_status = 0x3ff & (neilp->Status.AsUshort >> 1); + if ((dlen > 0) && is_read) { + bp = (uint8_t *)protoCmdp + rd_off; + memcpy(dp, bp, dlen); + } + psp->nvme_result = protoCmdp->FixedProtocolReturnData; + if (psp->nvme_direct && sbp && (slen > 3)) { + /* build 16 byte "sense" buffer from completion queue entry */ + n = (slen < 16) ? slen : 16; + memset(sbp, 0 , n); + psp->sense_resid = (slen > 16) ? (slen - 16) : 0; + sg_put_unaligned_le32(psp->nvme_result, sbp + SG_NVME_PT_CQ_DW0); + if (n > 11) { + k = neilp->SQID; + sg_put_unaligned_le32((k << 16), sbp + SG_NVME_PT_CQ_DW2); + if (n > 15) { + k = ((uint32_t)neilp->Status.AsUshort << 16) | neilp->CMDID; + sg_put_unaligned_le32(k, sbp + SG_NVME_PT_CQ_DW3); + } + } + } + if (vb > 3) + pr2ws("%s: opcode=0x%x, status=0x%x, result=0x%x\n", + __func__, cmdp[0], psp->nvme_status, psp->nvme_result); + res = psp->nvme_status ? SG_LIB_NVME_STATUS : 0; +err_out: + if (free_buffer) + free(free_buffer); + return res; +} + +static int +do_nvme_admin_cmd(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, + bool is_read, int time_secs, int vb) +{ + const uint32_t cmd_len = 64; + int res; + uint32_t n; + uint8_t opcode; + + psp->os_err = 0; + psp->transport_err = 0; + if (NULL == cmdp) { + if (! psp->have_nvme_cmd) + return SCSI_PT_DO_BAD_PARAMS; + cmdp = psp->nvme_cmd; + is_read = psp->is_read; + dlen = psp->dxfer_len; + dp = psp->dxferp; + } + if (vb > 2) { + pr2ws("NVMe is_read=%s, dlen=%u, command:\n", + (is_read ? "true" : "false"), dlen); + hex2stderr((const uint8_t *)cmdp, cmd_len, 1); + if ((vb > 3) && (! is_read) && dp) { + if (dlen > 0) { + n = dlen; + if ((dlen < 512) || (vb > 5)) + pr2ws("\nData-out buffer (%u bytes):\n", n); + else { + pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n); + n = 512; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + } + opcode = cmdp[0]; + switch (opcode) { /* The matches below are cached by W10 */ + case 0x6: /* Identify (controller + namespace */ + res = nvme_identify(psp, shp, cmdp, dp, dlen, vb); + if (res) + goto err_out; + break; + case 0xa: /* Get features */ + res = nvme_get_features(psp, shp, cmdp, dp, dlen, vb); + if (res) + goto err_out; + break; + case 0x2: /* Get Log Page */ + res = nvme_get_log_page(psp, shp, cmdp, dp, dlen, vb); + if (res) + goto err_out; + break; + default: + res = nvme_real_pt(psp, shp, cmdp, dp, dlen, is_read, time_secs, vb); + if (res) + goto err_out; + break; + /* IOCTL_STORAGE_PROTOCOL_COMMAND base pass-through goes here */ + res = -EINVAL; + goto err_out; + } + + if ((vb > 3) && is_read && dp && (dlen > 0)) { + n = dlen; + if ((dlen < 1024) || (vb > 5)) + pr2ws("\nData-in buffer (%u bytes):\n", n); + else { + pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n); + n = 1024; + } + hex2stderr((const uint8_t *)dp, n, 0); + } +err_out: + return res; +} + +#else /* W10_NVME_NON_PASSTHRU */ + +/* If cmdp is NULL then dp, dlen and is_read are ignored, those values are + * obtained from psp. Returns 0 for success. Returns SG_LIB_NVME_STATUS if + * there is non-zero NVMe status (SCT|SC from the completion queue) with the + * value placed in psp->nvme_status. If Unix error from ioctl then return + * negated value (equivalent -errno from basic Unix system functions like + * open()). CDW0 from the completion queue is placed in psp->nvme_result in + * the absence of an error. + * The following code is based on os_win32.cpp in smartmontools: + * Copyright (C) 2004-17 Christian Franke + * The code is licensed with a GPL-2. */ +static int +do_nvme_admin_cmd(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cmdp, uint8_t * dp, uint32_t dlen, + bool is_read, int time_secs, int vb) +{ + const uint32_t cmd_len = 64; + int res; + uint32_t n, alloc_len; + const uint32_t pg_sz = sg_get_page_size(); + uint32_t slen = psp->sense_len; + uint8_t * sbp = psp->sensep; + NVME_PASS_THROUGH_IOCTL * pthru; + uint8_t * free_pthru; + DWORD num_out = 0; + BOOL ok; + + psp->os_err = 0; + psp->transport_err = 0; + if (NULL == cmdp) { + if (! psp->have_nvme_cmd) + return SCSI_PT_DO_BAD_PARAMS; + cmdp = psp->nvme_cmd; + is_read = psp->is_read; + dlen = psp->dxfer_len; + dp = psp->dxferp; + } + if (vb > 2) { + pr2ws("NVMe is_read=%s, dlen=%u, command:\n", + (is_read ? "true" : "false"), dlen); + hex2stderr((const uint8_t *)cmdp, cmd_len, 1); + if ((vb > 3) && (! is_read) && dp) { + if (dlen > 0) { + n = dlen; + if ((dlen < 512) || (vb > 5)) + pr2ws("\nData-out buffer (%u bytes):\n", n); + else { + pr2ws("\nData-out buffer (first 512 of %u bytes):\n", n); + n = 512; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + } + alloc_len = sizeof(NVME_PASS_THROUGH_IOCTL) + dlen; + pthru = (NVME_PASS_THROUGH_IOCTL *)sg_memalign(alloc_len, pg_sz, + &free_pthru, false); + if (NULL == pthru) { + res = sg_convert_errno(ENOMEM); + if (vb > 1) + pr2ws("%s: unable to allocate memory\n", __func__); + psp->os_err = res; + return -res; + } + if (dp && (dlen > 0) && (! is_read)) + memcpy(pthru->DataBuffer, dp, dlen); /* dout-out buffer */ + /* Set NVMe command */ + pthru->SrbIoCtrl.HeaderLength = sizeof(SRB_IO_CONTROL); + memcpy(pthru->SrbIoCtrl.Signature, NVME_SIG_STR, sizeof(NVME_SIG_STR)-1); + pthru->SrbIoCtrl.Timeout = (time_secs > 0) ? time_secs : DEF_TIMEOUT; + pthru->SrbIoCtrl.ControlCode = NVME_PASS_THROUGH_SRB_IO_CODE; + pthru->SrbIoCtrl.ReturnCode = 0; + pthru->SrbIoCtrl.Length = alloc_len - sizeof(SRB_IO_CONTROL); + + memcpy(pthru->NVMeCmd, cmdp, cmd_len); + if (dlen > 0) + pthru->Direction = is_read ? 2 : 1; + else + pthru->Direction = 0; + pthru->ReturnBufferLen = alloc_len; + shp = get_open_pt_handle(psp, psp->dev_fd, vb > 1); + if (NULL == shp) { + res = -psp->os_err; /* -ENODEV */ + goto err_out; + } + + ok = DeviceIoControl(shp->fh, IOCTL_SCSI_MINIPORT, pthru, alloc_len, + pthru, alloc_len, &num_out, (OVERLAPPED*)0); + if (! ok) { + n = (uint32_t)GetLastError(); + psp->transport_err = n; + psp->os_err = EIO; /* simulate Unix error, */ + if (vb > 2) { + char b[128]; + + pr2ws("%s: IOCTL_SCSI_MINIPORT failed: %s [%u]\n", __func__, + get_err_str(n, sizeof(b), b), n); + } + } + /* nvme_status is SCT|SC, therefor it excludes DNR+More */ + psp->nvme_status = 0x3ff & (pthru->CplEntry[3] >> 17); + if (psp->nvme_status && (vb > 1)) { + uint16_t s = psp->nvme_status; + char b[80]; + + pr2ws("%s: opcode=0x%x failed: NVMe status: %s [0x%x]\n", __func__, + cmdp[0], sg_get_nvme_cmd_status_str(s, sizeof(b), b), s); + } + psp->nvme_result = sg_get_unaligned_le32(pthru->CplEntry + 0); + + psp->sense_resid = 0; + if (psp->nvme_direct && sbp && (slen > 3)) { + /* build 16 byte "sense" buffer */ + n = (slen < 16) ? slen : 16; + memset(sbp, 0 , n); + psp->sense_resid = (slen > 16) ? (slen - 16) : 0; + sg_put_unaligned_le32(pthru->CplEntry[0], sbp + SG_NVME_PT_CQ_DW0); + if (n > 7) { + sg_put_unaligned_le32(pthru->CplEntry[1], + sbp + SG_NVME_PT_CQ_DW1); + if (n > 11) { + sg_put_unaligned_le32(pthru->CplEntry[2], + sbp + SG_NVME_PT_CQ_DW2); + if (n > 15) + sg_put_unaligned_le32(pthru->CplEntry[3], + sbp + SG_NVME_PT_CQ_DW3); + } + } + } + if (! ok) { + res = -psp->os_err; + goto err_out; + } else if (psp->nvme_status) { + res = SG_LIB_NVME_STATUS; + goto err_out; + } + + if (dp && (dlen > 0) && is_read) { + memcpy(dp, pthru->DataBuffer, dlen); /* data-in buffer */ + if (vb > 3) { + n = dlen; + if ((dlen < 1024) || (vb > 5)) + pr2ws("\nData-in buffer (%u bytes):\n", n); + else { + pr2ws("\nData-in buffer (first 1024 of %u bytes):\n", n); + n = 1024; + } + hex2stderr((const uint8_t *)dp, n, 0); + } + } + res = 0; +err_out: + if (free_pthru) + free(free_pthru); + return res; +} + +#endif /* W10_NVME_NON_PASSTHRU */ + + +static void +sntl_check_enclosure_override(struct sg_pt_win32_scsi * psp, + struct sg_pt_handle * shp, int vb) +{ + uint8_t * up = psp->nvme_id_ctlp; + uint8_t nvmsr; + + if (NULL == up) + return; + nvmsr = up[253]; + if (vb > 3) + pr2ws("%s: enter, nvmsr=%u\n", __func__, nvmsr); + shp->dev_stat.id_ctl253 = nvmsr; + switch (shp->dev_stat.enclosure_override) { + case 0x0: /* no override */ + if (0x3 & nvmsr) { + shp->dev_stat.pdt = PDT_DISK; + shp->dev_stat.enc_serv = 1; + } else if (0x2 & nvmsr) { + shp->dev_stat.pdt = PDT_SES; + shp->dev_stat.enc_serv = 1; + } else if (0x1 & nvmsr) { + shp->dev_stat.pdt = PDT_DISK; + shp->dev_stat.enc_serv = 0; + } else { + uint32_t nn = sg_get_unaligned_le32(up + 516); + + shp->dev_stat.pdt = nn ? PDT_DISK : PDT_UNKNOWN; + shp->dev_stat.enc_serv = 0; + } + break; + case 0x1: /* override to SES device */ + shp->dev_stat.pdt = PDT_SES; + shp->dev_stat.enc_serv = 1; + break; + case 0x2: /* override to disk with attached SES device */ + shp->dev_stat.pdt = PDT_DISK; + shp->dev_stat.enc_serv = 1; + break; + case 0x3: /* override to SAFTE device (PDT_PROCESSOR) */ + shp->dev_stat.pdt = PDT_PROCESSOR; + shp->dev_stat.enc_serv = 1; + break; + case 0xff: /* override to normal disk */ + shp->dev_stat.pdt = PDT_DISK; + shp->dev_stat.enc_serv = 0; + break; + default: + pr2ws("%s: unknown enclosure_override value: %d\n", __func__, + shp->dev_stat.enclosure_override); + break; + } +} + +/* Returns 0 on success; otherwise a positive value is returned */ +static int +sntl_cache_identity(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + int time_secs, int vb) +{ + static const bool is_read = true; + const uint32_t pg_sz = sg_get_page_size(); + int ret; + uint8_t * up; + uint8_t * cmdp; + + up = sg_memalign(((pg_sz < 4096) ? 4096 : pg_sz), pg_sz, + &psp->free_nvme_id_ctlp, false); + psp->nvme_id_ctlp = up; + if (NULL == up) { + pr2ws("%s: sg_memalign() failed to get memory\n", __func__); + return -ENOMEM; + } + cmdp = psp->nvme_cmd; + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[0] = 0x6; /* Identify */ + /* leave nsid as 0, should it be broadcast (0xffffffff) ? */ + /* CNS=0x1 Identify controller: */ + sg_put_unaligned_le32(0x1, cmdp + SG_NVME_PT_CDW10); + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)up, cmdp + SG_NVME_PT_ADDR); + sg_put_unaligned_le32(pg_sz, cmdp + SG_NVME_PT_DATA_LEN); + ret = do_nvme_admin_cmd(psp, shp, cmdp, up, 4096, is_read, time_secs, + vb); + if (0 == ret) + sntl_check_enclosure_override(psp, shp, vb); + return ret; +} + + +static const char * nvme_scsi_vendor_str = "NVMe "; +static const uint16_t inq_resp_len = 36; + +static int +sntl_inq(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool evpd; + bool cp_id_ctl = false; + int res; + uint16_t n, alloc_len, pg_cd; + const uint32_t pg_sz = sg_get_page_size(); + uint8_t * nvme_id_ns = NULL; + uint8_t * free_nvme_id_ns = NULL; + uint8_t inq_dout[256]; + uint8_t * cmdp; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (0x2 & cdbp[1]) { /* Reject CmdDt=1 */ + mk_sense_invalid_fld(psp, true, 1, 1, vb); + return 0; + } + if (NULL == psp->nvme_id_ctlp) { + res = sntl_cache_identity(psp, shp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else if (res) /* should be negative errno */ + return res; + } + memset(inq_dout, 0, sizeof(inq_dout)); + alloc_len = sg_get_unaligned_be16(cdbp + 3); + evpd = !!(0x1 & cdbp[1]); + pg_cd = cdbp[2]; + if (evpd) { /* VPD page responses */ + switch (pg_cd) { + case 0: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 11; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x0; + inq_dout[5] = 0x80; + inq_dout[6] = 0x83; + inq_dout[7] = 0x86; + inq_dout[8] = 0x87; + inq_dout[9] = 0x92; + inq_dout[n - 1] = SG_NVME_VPD_NICR; /* last VPD number */ + break; + case 0x80: + /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */ + inq_dout[1] = pg_cd; + n = 24; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + memcpy(inq_dout + 4, psp->nvme_id_ctlp + 4, 20); /* SN */ + break; + case 0x83: + if ((psp->nvme_nsid > 0) && + (psp->nvme_nsid < SG_NVME_BROADCAST_NSID)) { + nvme_id_ns = sg_memalign(pg_sz, pg_sz, &free_nvme_id_ns, + false); + if (nvme_id_ns) { + cmdp = psp->nvme_cmd; + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[SG_NVME_PT_OPCODE] = 0x6; /* Identify */ + sg_put_unaligned_le32(psp->nvme_nsid, + cmdp + SG_NVME_PT_NSID); + /* CNS=0x0 Identify controller: */ + sg_put_unaligned_le32(0x0, cmdp + SG_NVME_PT_CDW10); + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)nvme_id_ns, + cmdp + SG_NVME_PT_ADDR); + sg_put_unaligned_le32(pg_sz, cmdp + SG_NVME_PT_DATA_LEN); + res = do_nvme_admin_cmd(psp, shp, cmdp, nvme_id_ns, pg_sz, + true, time_secs, vb > 3); + if (res) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + } + } + n = sg_make_vpd_devid_for_nvme(psp->nvme_id_ctlp, nvme_id_ns, + 0 /* pdt */, -1 /*tproto */, + inq_dout, sizeof(inq_dout)); + if (n > 3) + sg_put_unaligned_be16(n - 4, inq_dout + 2); + if (free_nvme_id_ns) { + free(free_nvme_id_ns); + free_nvme_id_ns = NULL; + nvme_id_ns = NULL; + } + break; + case 0x86: /* Extended INQUIRY (per SFS SPC Discovery 2016) */ + inq_dout[1] = pg_cd; + n = 64; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[5] = 0x1; /* SIMPSUP=1 */ + inq_dout[7] = 0x1; /* LUICLR=1 */ + inq_dout[13] = 0x40; /* max supported sense data length */ + break; + case 0x87: /* Mode page policy (per SFS SPC Discovery 2016) */ + inq_dout[1] = pg_cd; + n = 8; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[4] = 0x3f; /* all mode pages */ + inq_dout[5] = 0xff; /* and their sub-pages */ + inq_dout[6] = 0x80; /* MLUS=1, policy=shared */ + break; + case 0x92: /* SCSI Feature set: only SPC Discovery 2016 */ + inq_dout[1] = pg_cd; + n = 10; + sg_put_unaligned_be16(n - 4, inq_dout + 2); + inq_dout[9] = 0x1; /* SFS SPC Discovery 2016 */ + break; + case SG_NVME_VPD_NICR: /* 0xde */ + inq_dout[1] = pg_cd; + sg_put_unaligned_be16((16 + 4096) - 4, inq_dout + 2); + n = 16 + 4096; + cp_id_ctl = true; + break; + default: /* Point to page_code field in cdb */ + mk_sense_invalid_fld(psp, true, 2, 7, vb); + return 0; + } + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < psp->dxfer_len) ? n : psp->dxfer_len; + psp->resid = psp->dxfer_len - n; + if (n > 0) { + if (cp_id_ctl) { + memcpy(psp->dxferp, inq_dout, (n < 16 ? n : 16)); + if (n > 16) + memcpy(psp->dxferp + 16, + psp->nvme_id_ctlp, n - 16); + } else + memcpy(psp->dxferp, inq_dout, n); + } + } + } else { /* Standard INQUIRY response */ + /* pdt=0 --> disk; pdt=0xd --> SES; pdt=3 --> processor (safte) */ + inq_dout[0] = (0x1f & shp->dev_stat.pdt); /* (PQ=0)<<5 */ + /* inq_dout[1] = (RMD=0)<<7 | (LU_CONG=0)<<6; rest reserved */ + inq_dout[2] = 6; /* version: SPC-4 */ + inq_dout[3] = 2; /* NORMACA=0, HISUP=0, response data format: 2 */ + inq_dout[4] = 31; /* so response length is (or could be) 36 bytes */ + inq_dout[6] = shp->dev_stat.enc_serv ? 0x40 : 0; + inq_dout[7] = 0x2; /* CMDQUE=1 */ + memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8); /* NVMe not Intel */ + memcpy(inq_dout + 16, psp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */ + memcpy(inq_dout + 32, psp->nvme_id_ctlp + 64, 4); /* Rev <-- FR */ + if (alloc_len > 0) { + n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len; + n = (n < psp->dxfer_len) ? n : psp->dxfer_len; + psp->resid = psp->dxfer_len - n; + if (n > 0) + memcpy(psp->dxferp, inq_dout, n); + } + } + return 0; +} + +static int +sntl_rluns(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + int res; + uint16_t sel_report; + uint32_t alloc_len, k, n, num, max_nsid; + uint8_t * rl_doutp; + uint8_t * up; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + + sel_report = cdbp[2]; + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (NULL == psp->nvme_id_ctlp) { + res = sntl_cache_identity(psp, shp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else if (res) + return res; + } + max_nsid = sg_get_unaligned_le32(psp->nvme_id_ctlp + 516); + switch (sel_report) { + case 0: + case 2: + num = max_nsid; + break; + case 1: + case 0x10: + case 0x12: + num = 0; + break; + case 0x11: + num = (1 == psp->nvme_nsid) ? max_nsid : 0; + break; + default: + if (vb > 1) + pr2ws("%s: bad select_report value: 0x%x\n", __func__, + sel_report); + mk_sense_invalid_fld(psp, true, 2, 7, vb); + return 0; + } + rl_doutp = (uint8_t *)calloc(num + 1, 8); + if (NULL == rl_doutp) { + pr2ws("%s: calloc() failed to get memory\n", __func__); + return -ENOMEM; + } + for (k = 0, up = rl_doutp + 8; k < num; ++k, up += 8) + sg_put_unaligned_be16(k, up); + n = num * 8; + sg_put_unaligned_be32(n, rl_doutp); + n+= 8; + if (alloc_len > 0) { + n = (alloc_len < n) ? alloc_len : n; + n = (n < psp->dxfer_len) ? n : psp->dxfer_len; + psp->resid = psp->dxfer_len - n; + if (n > 0) + memcpy(psp->dxferp, rl_doutp, n); + } + res = 0; + free(rl_doutp); + return res; +} + +static int +sntl_tur(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + int time_secs, int vb) +{ + int res; + uint32_t pow_state; + uint8_t * cmdp; + + if (vb > 4) + pr2ws("%s: enter\n", __func__); + if (NULL == psp->nvme_id_ctlp) { + res = sntl_cache_identity(psp, shp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else if (res) + return res; + } + cmdp = psp->nvme_cmd; + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[SG_NVME_PT_OPCODE] = 0xa; /* Get features */ + sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, cmdp + SG_NVME_PT_NSID); + /* SEL=0 (current), Feature=2 Power Management */ + sg_put_unaligned_le32(0x2, cmdp + SG_NVME_PT_CDW10); + res = do_nvme_admin_cmd(psp, shp, cmdp, NULL, 0, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else + return res; + } else { + psp->os_err = 0; + psp->nvme_status = 0; + } + pow_state = (0x1f & psp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); +#if 0 /* pow_state bounces around too much on laptop */ + if (pow_state) + mk_sense_asc_ascq(psp, SPC_SK_NOT_READY, LOW_POWER_COND_ON_ASC, 0, + vb); +#endif + return 0; +} + +static int +sntl_req_sense(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool desc; + int res; + uint32_t pow_state, alloc_len, n; + uint8_t rs_dout[64]; + uint8_t * cmdp; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (NULL == psp->nvme_id_ctlp) { + res = sntl_cache_identity(psp, shp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else if (res) + return res; + } + desc = !!(0x1 & cdbp[1]); + alloc_len = cdbp[4]; + cmdp = psp->nvme_cmd; + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[SG_NVME_PT_OPCODE] = 0xa; /* Get features */ + sg_put_unaligned_le32(SG_NVME_BROADCAST_NSID, cmdp + SG_NVME_PT_NSID); + /* SEL=0 (current), Feature=2 Power Management */ + sg_put_unaligned_le32(0x2, cmdp + SG_NVME_PT_CDW10); + res = do_nvme_admin_cmd(psp, shp, cmdp, NULL, 0, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else + return res; + } else { + psp->os_err = 0; + psp->nvme_status = 0; + } + psp->sense_resid = psp->sense_len; + pow_state = (0x1f & psp->nvme_result); + if (vb > 3) + pr2ws("%s: pow_state=%u\n", __func__, pow_state); + memset(rs_dout, 0, sizeof(rs_dout)); + if (pow_state) + sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + LOW_POWER_COND_ON_ASC, 0); + else + sg_build_sense_buffer(desc, rs_dout, SPC_SK_NO_SENSE, + NO_ADDITIONAL_SENSE, 0); + n = desc ? 8 : 18; + n = (n < alloc_len) ? n : alloc_len; + n = (n < psp->dxfer_len) ? n : psp->dxfer_len; + psp->resid = psp->dxfer_len - n; + if (n > 0) + memcpy(psp->dxferp, rs_dout, n); + return 0; +} + +static int +sntl_mode_ss(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool is_msense = (SCSI_MODE_SENSE10_OPC == cdbp[0]); + int res, n, len; + uint8_t * bp; + struct sg_sntl_result_t sntl_result; + + if (vb > 3) + pr2ws("%s: mse%s, time_secs=%d\n", __func__, + (is_msense ? "nse" : "lect"), time_secs); + if (NULL == psp->nvme_id_ctlp) { + res = sntl_cache_identity(psp, shp, time_secs, vb); + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else if (res) + return res; + } + if (is_msense) { /* MODE SENSE(10) */ + len = psp->dxfer_len; + bp = psp->dxferp; + n = sntl_resp_mode_sense10(&shp->dev_stat, cdbp, bp, len, + &sntl_result); + psp->resid = (n >= 0) ? len - n : len; + } else { /* MODE SELECT(10) */ + uint8_t pre_enc_ov = shp->dev_stat.enclosure_override; + + len = psp->dxfer_len; + bp = psp->dxferp; + n = sntl_resp_mode_select10(&shp->dev_stat, cdbp, bp, len, + &sntl_result); + if (pre_enc_ov != shp->dev_stat.enclosure_override) + sntl_check_enclosure_override(psp, shp, vb); /* ENC_OV changed */ + } + if (n < 0) { + int in_bit = (255 == sntl_result.in_bit) ? (int)sntl_result.in_bit : + -1; + if ((SAM_STAT_CHECK_CONDITION == sntl_result.sstatus) && + (SPC_SK_ILLEGAL_REQUEST == sntl_result.sk)) { + if (INVALID_FIELD_IN_CDB == sntl_result.asc) + mk_sense_invalid_fld(psp, true, sntl_result.in_byte, in_bit, + vb); + else if (INVALID_FIELD_IN_PARAM_LIST == sntl_result.asc) + mk_sense_invalid_fld(psp, false, sntl_result.in_byte, in_bit, + vb); + else + mk_sense_asc_ascq(psp, sntl_result.sk, sntl_result.asc, + sntl_result.ascq, vb); + } else + pr2ws("%s: error but no sense?? n=%d\n", __func__, n); + } + return 0; +} + +/* This is not really a SNTL. For SCSI SEND DIAGNOSTIC(PF=1) NVMe-MI + * has a special command (SES Send) to tunnel through pages to an + * enclosure. The NVMe enclosure is meant to understand the SES + * (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_senddiag(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool pf, self_test; + int res; + uint8_t st_cd, dpg_cd; + uint32_t alloc_len, n, dout_len, dpg_len, nvme_dst; + uint8_t * dop; + uint8_t * cmdp; + + st_cd = 0x7 & (cdbp[1] >> 5); + self_test = !! (0x4 & cdbp[1]); + pf = !! (0x10 & cdbp[1]); + if (vb > 3) + pr2ws("%s: pf=%d, self_test=%d (st_code=%d)\n", __func__, (int)pf, + (int)self_test, (int)st_cd); + cmdp = psp->nvme_cmd; + if (self_test || st_cd) { + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[SG_NVME_PT_OPCODE] = 0x14; /* Device self-test */ + /* just this namespace (if there is one) and controller */ + sg_put_unaligned_le32(psp->nvme_nsid, cmdp + SG_NVME_PT_NSID); + switch (st_cd) { + case 0: /* Here if self_test is set, do short self-test */ + case 1: /* Background short */ + case 5: /* Foreground short */ + nvme_dst = 1; + break; + case 2: /* Background extended */ + case 6: /* Foreground extended */ + nvme_dst = 2; + break; + case 4: /* Abort self-test */ + nvme_dst = 0xf; + break; + default: + pr2ws("%s: bad self-test code [0x%x]\n", __func__, st_cd); + mk_sense_invalid_fld(psp, true, 1, 7, vb); + return 0; + } + sg_put_unaligned_le32(nvme_dst, cmdp + SG_NVME_PT_CDW10); + res = do_nvme_admin_cmd(psp, shp, cmdp, NULL, 0, false, time_secs, + vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else + return res; + } + } + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + dout_len = psp->dxfer_len; + if (pf) { + if (0 == alloc_len) { + mk_sense_invalid_fld(psp, true, 3, 7, vb); + if (vb) + pr2ws("%s: PF bit set bit param_list_len=0\n", __func__); + return 0; + } + } else { /* PF bit clear */ + if (alloc_len) { + mk_sense_invalid_fld(psp, true, 3, 7, vb); + if (vb) + pr2ws("%s: param_list_len>0 but PF clear\n", __func__); + return 0; + } else + return 0; /* nothing to do */ + if (dout_len > 0) { + if (vb) + pr2ws("%s: dout given but PF clear\n", __func__); + return SCSI_PT_DO_BAD_PARAMS; + } + } + if (dout_len < 4) { + if (vb) + pr2ws("%s: dout length (%u bytes) too short\n", __func__, + dout_len); + return SCSI_PT_DO_BAD_PARAMS; + } + n = dout_len; + n = (n < alloc_len) ? n : alloc_len; + dop = psp->dxferp; + if (! sg_is_aligned(dop, 0)) { /* page aligned ? */ + if (vb) + pr2ws("%s: dout [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)(sg_uintptr_t)psp->dxferp); + return SCSI_PT_DO_BAD_PARAMS; + } + dpg_cd = dop[0]; + dpg_len = sg_get_unaligned_be16(dop + 2) + 4; + /* should we allow for more than one D_PG is dout ?? */ + n = (n < dpg_len) ? n : dpg_len; /* not yet ... */ + + if (vb) + pr2ws("%s: passing through d_pg=0x%x, len=%u to NVME_MI SES send\n", + __func__, dpg_cd, dpg_len); + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[SG_NVME_PT_OPCODE] = 0x1d; /* MI Send */ + /* And 0x1d is same opcode as the SCSI SEND DIAGNOSTIC command */ + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)dop, + cmdp + SG_NVME_PT_ADDR); + /* NVMe 4k page size. Maybe determine this? */ + /* N.B. Maybe n > 0x1000, is this a problem?? */ + sg_put_unaligned_le32(0x1000, cmdp + SG_NVME_PT_DATA_LEN); + /* NVMe Message Header */ + sg_put_unaligned_le32(0x0804, cmdp + SG_NVME_PT_CDW10); + /* NVME-MI SES Send; (0x8 -> NVME-MI SES Receive) */ + sg_put_unaligned_le32(0x9, cmdp + SG_NVME_PT_CDW11); + /* 'n' is number of bytes SEND DIAGNOSTIC dpage */ + sg_put_unaligned_le32(n, cmdp + SG_NVME_PT_CDW13); + res = do_nvme_admin_cmd(psp, shp, cmdp, dop, n, false, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } + } + return res; +} + +/* This is not really a SNTL. For SCSI RECEIVE DIAGNOSTIC RESULTS(PCV=1) + * NVMe-MI has a special command (SES Receive) to read pages through a + * tunnel from an enclosure. The NVMe enclosure is meant to understand the + * SES (SCSI Enclosure Services) use of diagnostics pages that are + * related to SES. */ +static int +sntl_recvdiag(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool pcv; + int res; + uint8_t dpg_cd; + uint32_t alloc_len, n, din_len; + uint8_t * dip; + uint8_t * cmdp; + + pcv = !! (0x1 & cdbp[1]); + dpg_cd = cdbp[2]; + alloc_len = sg_get_unaligned_be16(cdbp + 3); /* parameter list length */ + if (vb > 3) + pr2ws("%s: dpg_cd=0x%x, pcv=%d, alloc_len=0x%x\n", __func__, + dpg_cd, (int)pcv, alloc_len); + din_len = psp->dxfer_len; + n = (din_len < alloc_len) ? din_len : alloc_len; + dip = psp->dxferp; + if (! sg_is_aligned(dip, 0)) { /* page aligned ? */ + if (vb) + pr2ws("%s: din [0x%" PRIx64 "] not page aligned\n", __func__, + (uint64_t)(sg_uintptr_t)psp->dxferp); + return SCSI_PT_DO_BAD_PARAMS; + } + + if (vb) + pr2ws("%s: expecting d_pg=0x%x from NVME_MI SES receive\n", __func__, + dpg_cd); + cmdp = psp->nvme_cmd; + memset(cmdp, 0, sizeof(psp->nvme_cmd)); + cmdp[SG_NVME_PT_OPCODE] = 0x1e; /* MI Receive */ + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)dip, + cmdp + SG_NVME_PT_ADDR); + /* NVMe 4k page size. Maybe determine this? */ + /* N.B. Maybe n > 0x1000, is this a problem?? */ + sg_put_unaligned_le32(0x1000, cmdp + SG_NVME_PT_DATA_LEN); + /* NVMe Message Header */ + sg_put_unaligned_le32(0x0804, cmdp + SG_NVME_PT_CDW10); + /* NVME-MI SES Receive */ + sg_put_unaligned_le32(0x8, cmdp + SG_NVME_PT_CDW11); + /* Diagnostic page code */ + sg_put_unaligned_le32(dpg_cd, cmdp + SG_NVME_PT_CDW12); + /* 'n' is number of bytes expected in diagnostic page */ + sg_put_unaligned_le32(n, cmdp + SG_NVME_PT_CDW13); + res = do_nvme_admin_cmd(psp, shp, cmdp, dip, n, true, time_secs, vb); + if (0 != res) { + if (SG_LIB_NVME_STATUS == res) { + mk_sense_from_nvme_status(psp, vb); + return 0; + } else + return res; + } + psp->resid = din_len - n; + return res; +} + +#define F_SA_LOW 0x80 /* cdb byte 1, bits 4 to 0 */ +#define F_SA_HIGH 0x100 /* as used by variable length cdbs */ +#define FF_SA (F_SA_HIGH | F_SA_LOW) +#define F_INV_OP 0x200 + +static int +sntl_rep_opcodes(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool rctd; + uint8_t reporting_opts, req_opcode, supp; + uint16_t req_sa, u; + uint32_t alloc_len, offset, a_len; + const uint32_t pg_sz = sg_get_page_size(); + int k, len, count, bump; + const struct sg_opcode_info_t *oip; + uint8_t *arr; + uint8_t *free_arr; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (shp) { ; } /* suppress warning */ + rctd = !!(cdbp[2] & 0x80); /* report command timeout desc. */ + reporting_opts = cdbp[2] & 0x7; + req_opcode = cdbp[3]; + req_sa = sg_get_unaligned_be16(cdbp + 4); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4 || alloc_len > 0xffff) { + mk_sense_invalid_fld(psp, true, 6, -1, vb); + return 0; + } + a_len = pg_sz - 72; + arr = sg_memalign(pg_sz, pg_sz, &free_arr, false); + if (NULL == arr) { + pr2ws("%s: sg_memalign() failed to get memory\n", __func__); + return -ENOMEM; + } + switch (reporting_opts) { + case 0: /* all commands */ + count = 0; + bump = rctd ? 20 : 8; + for (offset = 4, oip = sg_opcode_info_arr; + (oip->flags != 0xffff) && (offset < a_len); ++oip) { + if (F_INV_OP & oip->flags) + continue; + ++count; + arr[offset] = oip->opcode; + sg_put_unaligned_be16(oip->sa, arr + offset + 2); + if (rctd) + arr[offset + 5] |= 0x2; + if (FF_SA & oip->flags) + arr[offset + 5] |= 0x1; + sg_put_unaligned_be16(oip->len_mask[0], arr + offset + 6); + if (rctd) + sg_put_unaligned_be16(0xa, arr + offset + 8); + offset += bump; + } + sg_put_unaligned_be32(count * bump, arr + 0); + break; + case 1: /* one command: opcode only */ + case 2: /* one command: opcode plus service action */ + case 3: /* one command: if sa==0 then opcode only else opcode+sa */ + for (oip = sg_opcode_info_arr; oip->flags != 0xffff; ++oip) { + if ((req_opcode == oip->opcode) && (req_sa == oip->sa)) + break; + } + if ((0xffff == oip->flags) || (F_INV_OP & oip->flags)) { + supp = 1; + offset = 4; + } else { + if (1 == reporting_opts) { + if (FF_SA & oip->flags) { + mk_sense_invalid_fld(psp, true, 2, 2, vb); + free(free_arr); + return 0; + } + req_sa = 0; + } else if ((2 == reporting_opts) && 0 == (FF_SA & oip->flags)) { + mk_sense_invalid_fld(psp, true, 4, -1, vb); + free(free_arr); + return 0; + } + if ((0 == (FF_SA & oip->flags)) && (req_opcode == oip->opcode)) + supp = 3; + else if (0 == (FF_SA & oip->flags)) + supp = 1; + else if (req_sa != oip->sa) + supp = 1; + else + supp = 3; + if (3 == supp) { + u = oip->len_mask[0]; + sg_put_unaligned_be16(u, arr + 2); + arr[4] = oip->opcode; + for (k = 1; k < u; ++k) + arr[4 + k] = (k < 16) ? + oip->len_mask[k] : 0xff; + offset = 4 + u; + } else + offset = 4; + } + arr[1] = (rctd ? 0x80 : 0) | supp; + if (rctd) { + sg_put_unaligned_be16(0xa, arr + offset); + offset += 12; + } + break; + default: + mk_sense_invalid_fld(psp, true, 2, 2, vb); + free(free_arr); + return 0; + } + offset = (offset < a_len) ? offset : a_len; + len = (offset < alloc_len) ? offset : alloc_len; + psp->resid = psp->dxfer_len - len; + if (len > 0) + memcpy(psp->dxferp, arr, len); + free(free_arr); + return 0; +} + +static int +sntl_rep_tmfs(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + const uint8_t * cdbp, int time_secs, int vb) +{ + bool repd; + uint32_t alloc_len, len; + uint8_t arr[16]; + + if (vb > 3) + pr2ws("%s: time_secs=%d\n", __func__, time_secs); + if (shp) { ; } /* suppress warning */ + memset(arr, 0, sizeof(arr)); + repd = !!(cdbp[2] & 0x80); + alloc_len = sg_get_unaligned_be32(cdbp + 6); + if (alloc_len < 4) { + mk_sense_invalid_fld(psp, true, 6, -1, vb); + return 0; + } + arr[0] = 0xc8; /* ATS | ATSS | LURS */ + arr[1] = 0x1; /* ITNRS */ + if (repd) { + arr[3] = 0xc; + len = 16; + } else + len = 4; + + len = (len < alloc_len) ? len : alloc_len; + psp->resid = psp->dxfer_len - len; + if (len > 0) + memcpy(psp->dxferp, arr, len); + return 0; +} + +/* Executes NVMe Admin command (or at least forwards it to lower layers). + * Returns 0 for success, negative numbers are negated 'errno' values from + * OS system calls. Positive return values are errors from this package. + * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds + * is used. */ +static int +nvme_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + int time_secs, int vb) +{ + bool scsi_cdb = false; + uint32_t cmd_len = 0; + uint16_t sa; + const uint8_t * cdbp = NULL; + + if (psp->have_nvme_cmd) { + cdbp = psp->nvme_cmd; + cmd_len = 64; + psp->nvme_direct = true; + } else if (spt_direct) { + if (psp->swb_d.spt.CdbLength > 0) { + cdbp = psp->swb_d.spt.Cdb; + cmd_len = psp->swb_d.spt.CdbLength; + scsi_cdb = true; + psp->nvme_direct = false; + } + } else { + if (psp->swb_i.spt.CdbLength > 0) { + cdbp = psp->swb_i.spt.Cdb; + cmd_len = psp->swb_i.spt.CdbLength; + scsi_cdb = true; + psp->nvme_direct = false; + } + } + if (NULL == cdbp) { + if (vb) + pr2ws("%s: Missing NVMe or SCSI command (set_scsi_pt_cdb())" + " cmd_len=%u\n", __func__, cmd_len); + return SCSI_PT_DO_BAD_PARAMS; + } + if (vb > 3) + pr2ws("%s: opcode=0x%x, cmd_len=%u, fdev_name: %s, dlen=%u\n", + __func__, cdbp[0], cmd_len, shp->dname, psp->dxfer_len); + /* direct NVMe command (i.e. 64 bytes long) or SNTL */ + if (scsi_cdb) { + switch (cdbp[0]) { + case SCSI_INQUIRY_OPC: + return sntl_inq(psp, shp, cdbp, time_secs, vb); + case SCSI_REPORT_LUNS_OPC: + return sntl_rluns(psp, shp, cdbp, time_secs, vb); + case SCSI_TEST_UNIT_READY_OPC: + return sntl_tur(psp, shp, time_secs, vb); + case SCSI_REQUEST_SENSE_OPC: + return sntl_req_sense(psp, shp, cdbp, time_secs, vb); + case SCSI_SEND_DIAGNOSTIC_OPC: + return sntl_senddiag(psp, shp, cdbp, time_secs, vb); + case SCSI_RECEIVE_DIAGNOSTIC_OPC: + return sntl_recvdiag(psp, shp, cdbp, time_secs, vb); + case SCSI_MODE_SENSE10_OPC: + case SCSI_MODE_SELECT10_OPC: + return sntl_mode_ss(psp, shp, cdbp, time_secs, vb); + case SCSI_MAINT_IN_OPC: + sa = 0x1f & cdbp[1]; /* service action */ + if (SCSI_REP_SUP_OPCS_OPC == sa) + return sntl_rep_opcodes(psp, shp, cdbp, time_secs, + vb); + else if (SCSI_REP_SUP_TMFS_OPC == sa) + return sntl_rep_tmfs(psp, shp, cdbp, time_secs, vb); + /* fall through */ + default: + if (vb > 2) { + char b[64]; + + sg_get_command_name(cdbp, -1, sizeof(b), b); + pr2ws("%s: no translation to NVMe for SCSI %s command\n", + __func__, b); + } + mk_sense_asc_ascq(psp, SPC_SK_ILLEGAL_REQUEST, INVALID_OPCODE, + 0, vb); + return 0; + } + } + if(psp->dxfer_len > 0) { + uint8_t * cmdp = psp->nvme_cmd; + + sg_put_unaligned_le32(psp->dxfer_len, cmdp + SG_NVME_PT_DATA_LEN); + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)psp->dxferp, + cmdp + SG_NVME_PT_ADDR); + if (vb > 2) + pr2ws("%s: NVMe command, dlen=%u, dxferp=0x%p\n", __func__, + psp->dxfer_len, psp->dxferp); + } + return do_nvme_admin_cmd(psp, shp, NULL, NULL, 0, true, time_secs, vb); +} + +#else /* (HAVE_NVME && (! IGNORE_NVME)) */ + +static int +nvme_pt(struct sg_pt_win32_scsi * psp, struct sg_pt_handle * shp, + int time_secs, int vb) +{ + if (vb) + pr2ws("%s: not supported [time_secs=%d]\n", __func__, time_secs); + if (psp) { ; } /* suppress warning */ + if (shp) { ; } /* suppress warning */ + return -ENOTTY; /* inappropriate ioctl error */ +} + +#endif /* (HAVE_NVME && (! IGNORE_NVME)) */ diff --git a/scripts/40-usb-blacklist.rules b/scripts/40-usb-blacklist.rules new file mode 100644 index 0000000..fd821f1 --- /dev/null +++ b/scripts/40-usb-blacklist.rules @@ -0,0 +1,10 @@ +# +# Blacklist specific USB devices +# +# don't inquire sn and di on broken devices (https://bugzilla.suse.com/show_bug.cgi?id=840054) + +# unkown device +ATTRS{idVendor}=="0aec", ATTRS{idProduct}=="3260", ENV{ID_SCSI_DI}="1" +# Sony/JMicron port replicator +ATTRS{idVendor}=="054c", ATTRS{idProduct}=="06a0", ENV{ID_SCSI_DI}="1" + diff --git a/scripts/55-scsi-sg3_id.rules b/scripts/55-scsi-sg3_id.rules new file mode 100644 index 0000000..4975bc5 --- /dev/null +++ b/scripts/55-scsi-sg3_id.rules @@ -0,0 +1,55 @@ +# SCSI-ID mappings for sg3_utils + +ACTION=="remove", GOTO="sg3_utils_id_end" + +SUBSYSTEM!="block", GOTO="sg3_utils_id_end" + +# Import values for partitions +ENV{DEVTYPE}=="partition", IMPORT{parent}="SCSI_*", ENV{ID_SCSI}="1" +# SCSI INQUIRY values +# If the 'inquiry' sysfs attribute is present the kernel will already +# have scanned for VPD pages, so if the vpd page attribute is not +# present it is not supported (or deemed unsafe to access). +# Hence we can skip the call to sg_inq and avoid I/O altogether. +# Set 'ID_SCSI_INQUIRY=0' in an earlier udev rule if the kernel +# fails to scan VPD pages correctly; the rules will then fall +# back to calling sg_vpd directly. +KERNEL=="sd*[!0-9]|sr*", ENV{ID_SCSI_INQUIRY}!="?*", IMPORT{program}="/usr/bin/sg_inq --export --inhex=/sys/block/$kernel/device/inquiry --raw", ENV{ID_SCSI}="1", ENV{ID_SCSI_INQUIRY}="1" +KERNEL=="sd*[!0-9]|sr*", ENV{ID_SCSI}!="1", IMPORT{program}="/usr/bin/sg_inq --export $tempnode", ENV{ID_SCSI}="1" +# scsi_id compat mappings +ENV{SCSI_VENDOR}=="?*", ENV{ID_VENDOR}="$env{SCSI_VENDOR}" +ENV{SCSI_VENDOR_ENC}=="?*", ENV{ID_VENDOR_ENC}="$env{SCSI_VENDOR_ENC}" +ENV{SCSI_MODEL}=="?*", ENV{ID_MODEL}="$env{SCSI_MODEL}" +ENV{SCSI_MODEL_ENC}=="?*", ENV{ID_MODEL_ENC}="$env{SCSI_MODEL_ENC}" +ENV{SCSI_REVISION}=="?*", ENV{ID_REVISION}="$env{SCSI_REVISION}" +ENV{SCSI_TYPE}=="?*", ENV{ID_TYPE}="$env{SCSI_TYPE}" +# SCSI EVPD page 0x80 values +KERNEL=="sd*[!0-9]|sr*", ENV{ID_SCSI}=="1", ENV{ID_SCSI_INQUIRY}=="1", IMPORT{program}="/usr/bin/sg_inq --export --inhex=/sys/block/$kernel/device/vpd_pg80 --raw" +KERNEL=="sd*[!0-9]|sr*", ENV{ID_SCSI}=="1", ENV{ID_SCSI_INQUIRY}!="1", IMPORT{program}="/usr/bin/sg_inq --export --page=sn $tempnode" +# SCSI EVPD page 0x83 values +KERNEL=="sd*[!0-9]", ENV{ID_SCSI}=="1", ENV{ID_SCSI_INQUIRY}=="1", IMPORT{program}="/usr/bin/sg_inq --export --inhex=/sys/block/$kernel/device/vpd_pg83 --raw" +KERNEL=="sd*[!0-9]|sr*", ENV{ID_SCSI}=="1", ENV{ID_SCSI_INQUIRY}!="1", IMPORT{program}="/usr/bin/sg_inq --export --page=di $tempnode" + +# ID_WWN compat mapping +ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{ID_WWN}!="?*", ENV{ID_WWN}="0x$env{SCSI_IDENT_LUN_NAA_REGEXT}" +ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{ID_WWN}!="?*", ENV{ID_WWN}="0x$env{SCSI_IDENT_LUN_NAA_REG}" +ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{ID_WWN}!="?*", ENV{ID_WWN}="0x$env{SCSI_IDENT_LUN_NAA_EXT}" +ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{ID_WWN}!="?*", ENV{ID_WWN}="0x$env{SCSI_IDENT_LUN_NAA_LOCAL}" +ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="0x$env{SCSI_IDENT_LUN_NAA_REGEXT}" +ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="0x$env{SCSI_IDENT_LUN_NAA_EXT}" +ENV{ID_WWN}=="?*", ENV{ID_WWN_WITH_EXTENSION}!="?*", ENV{ID_WWN_WITH_EXTENSION}="$env{ID_WWN}" + +# ata_id compability +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_ATA}=="?*", ENV{ID_BUS}="ata", ENV{ID_ATA}="1", ENV{ID_SERIAL}="$env{SCSI_IDENT_LUN_ATA}" +ENV{ID_SERIAL_SHORT}!="?*", ENV{SCSI_VENDOR}=="ATA", ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_VENDOR}" +# Compat ID_SERIAL setting +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_REGEXT}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_REGEXT}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_REG}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_REG}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_EXT}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_EUI64}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="2$env{SCSI_IDENT_LUN_EUI64}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_EUI64}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAME}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="8$env{SCSI_IDENT_LUN_NAME}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAME}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_T10}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="1$env{SCSI_IDENT_LUN_T10}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_T10}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="3$env{SCSI_IDENT_LUN_NAA_LOCAL}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_NAA_LOCAL}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="0$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_LUN_VENDOR}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_LUN_VENDOR}" +ENV{ID_SERIAL}!="?*", ENV{SCSI_IDENT_SERIAL}=="?*", ENV{ID_BUS}="scsi", ENV{ID_SERIAL}="S$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_SERIAL}", ENV{ID_SERIAL_SHORT}="$env{SCSI_IDENT_SERIAL}" +LABEL="sg3_utils_id_end" diff --git a/scripts/58-scsi-sg3_symlink.rules b/scripts/58-scsi-sg3_symlink.rules new file mode 100644 index 0000000..2e8dd96 --- /dev/null +++ b/scripts/58-scsi-sg3_symlink.rules @@ -0,0 +1,40 @@ +# SCSI-ID symlinks for sg3_utils + +ACTION=="remove", GOTO="sg3_utils_symlink_end" + +SUBSYSTEM!="block", GOTO="sg3_utils_symlink_end" + +# Skip symlink generation for multipath +ENV{DM_MULTIPATH_DEVICE_PATH}=="1", GOTO="sg3_utils_symlink_end" + +# Select which identifier to use per default +# 0: vpd page 0x80 identifier +ENV{SCSI_IDENT_SERIAL}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-S$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_SERIAL}" +ENV{SCSI_IDENT_SERIAL}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-S$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_SERIAL}-part%n" +# NAA identifier (prefix 3) +# 1: IEEE Registered Extended first +ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REGEXT}" +ENV{SCSI_IDENT_LUN_NAA_REGEXT}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REGEXT}-part%n" +# 2: IEEE Registered +ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REG}" +ENV{SCSI_IDENT_LUN_NAA_REG}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_REG}-part%n" +# 3: IEEE Extended +ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_EXT}" +ENV{SCSI_IDENT_LUN_NAA_EXT}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_EXT}-part%n" +# 4: EUI-64 identifer (prefix 2) +ENV{SCSI_IDENT_LUN_EUI64}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-2$env{SCSI_IDENT_LUN_EUI64}" +ENV{SCSI_IDENT_LUN_EUI64}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-2$env{SCSI_IDENT_LUN_EUI64}-part%n" +# 5: SCSI name identifier (prefix 8) +ENV{SCSI_IDENT_LUN_NAME}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-8$env{SCSI_IDENT_LUN_NAME}" +ENV{SCSI_IDENT_LUN_NAME}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-8$env{SCSI_IDENT_LUN_NAME}-part%n" +# 6: T10 Vendor identifer (prefix 1) +ENV{SCSI_IDENT_LUN_T10}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-1$env{SCSI_IDENT_LUN_T10}" +ENV{SCSI_IDENT_LUN_T10}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-1$env{SCSI_IDENT_LUN_T10}-part%n" +# 7: IEEE Locally assigned +ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_LOCAL}" +ENV{SCSI_IDENT_LUN_NAA_LOCAL}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-3$env{SCSI_IDENT_LUN_NAA_LOCAL}-part%n" +# 8: Vendor-specific identifier (prefix 0) +ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-id/scsi-0$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_LUN_VENDOR}" +ENV{SCSI_IDENT_LUN_VENDOR}=="?*", ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-id/scsi-0$env{SCSI_VENDOR}_$env{SCSI_MODEL}_$env{SCSI_IDENT_LUN_VENDOR}-part%n" + +LABEL="sg3_utils_symlink_end" diff --git a/scripts/59-fc-wwpn-id.rules b/scripts/59-fc-wwpn-id.rules new file mode 100644 index 0000000..81a20ad --- /dev/null +++ b/scripts/59-fc-wwpn-id.rules @@ -0,0 +1,17 @@ +# +# FC WWPN-based by-path links +# + +ACTION!="add|change", GOTO="fc_wwpn_end" +KERNEL!="sd*", GOTO="fc_wwpn_end" + +ENV{DEVTYPE}=="disk", IMPORT{program}="fc_wwpn_id %p" +ENV{DEVTYPE}=="partition", IMPORT{parent}="FC_*" +ENV{FC_TARGET_WWPN}!="$*"; GOTO="fc_wwpn_end" +ENV{FC_INITIATOR_WWPN}!="$*"; GOTO="fc_wwpn_end" +ENV{FC_TARGET_LUN}!="$*"; GOTO="fc_wwpn_end" + +ENV{DEVTYPE}=="disk", SYMLINK+="disk/by-path/fc-$env{FC_INITIATOR_WWPN}-$env{FC_TARGET_WWPN}-lun-$env{FC_TARGET_LUN}" +ENV{DEVTYPE}=="partition", SYMLINK+="disk/by-path/fc-$env{FC_INITIATOR_WWPN}-$env{FC_TARGET_WWPN}-lun-$env{FC_TARGET_LUN}-part%n" + +LABEL="fc_wwpn_end" diff --git a/scripts/59-scsi-cciss_id.rules b/scripts/59-scsi-cciss_id.rules new file mode 100644 index 0000000..4eb4561 --- /dev/null +++ b/scripts/59-scsi-cciss_id.rules @@ -0,0 +1,18 @@ +# cciss compat rules + +ACTION!="add|change", GOTO="cciss_compat_end" +KERNEL!="sd*", GOTO="cciss_compat_end" +ENV{ID_VENDOR}!="HP", ENV{ID_VENDOR}!="COMPAQ", GOTO="cciss_compat_end" +ENV{ID_MODEL}!="LOGICAL_VOLUME", GOTO="cciss_compat_end" + +ENV{DEVTYPE}=="disk", DRIVERS=="hpsa", IMPORT{program}="cciss_id %p" +ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*" +ENV{ID_CCISS}!="?*", GOTO="cciss_compat_end" + +ENV{DEVTYPE}=="disk", SYMLINK+="cciss/$env{ID_CCISS}" +ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/cciss-$env{ID_SERIAL}" + +ENV{DEVTYPE}=="partition", SYMLINK+="cciss/$env{ID_CCISS}p%n" +ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/cciss-$env{ID_SERIAL}-part%n" + +LABEL="cciss_compat_end" diff --git a/scripts/Makefile.am b/scripts/Makefile.am new file mode 100644 index 0000000..4fe4d14 --- /dev/null +++ b/scripts/Makefile.am @@ -0,0 +1,3 @@ +bin_SCRIPTS = scsi_logging_level scsi_mandat scsi_readcap scsi_ready \ + scsi_satl scsi_start scsi_stop scsi_temperature \ + rescan-scsi-bus.sh diff --git a/scripts/README b/scripts/README new file mode 100644 index 0000000..082e0e6 --- /dev/null +++ b/scripts/README @@ -0,0 +1,56 @@ + README for sg3_utils/scripts + ============================ +Introduction +============ +This directory contains bash shell scripts. Most of them call one or +more utilities from the sg3_utils package. They assume the sg3_utils +package utilities are on the PATH of the user. + +rescan-scsi-bus.sh is written by Kurt Garloff (see +http://www.garloff.de/kurt/linux/ under the "Rescan SCSI bus" heading) +with patches from Hannes Reinecke. + +scsi_logging_level is written by Andreas Herrmann . It sets the logging level of the SCSI subsystem in the Linux +2.6 series kernels. See that file for more information. + +The other scripts are written by the author. Some do testing while others +do bulk tasks (e.g. stopping multiple disks). + +Details +======= +Each script supplies more information, typically by supplying a '-h' +or '--help' option. The script source often contains explanatory +information. Following is a usage summary with a one line description: + rescan-scsi-bus.sh [OPTIONS] + - see the output of 'rescan-scsi-bus.sh --help' + scsi_logging_level [OPTIONS] + - set Linux SCSI subsystem logging level + scsi_mandat [-h] [-L] [-q] + - check for mandatory SCSI command support + scsi_readcap [-b] [-h] [-v] + + - fetch capacity/size information for each + scsi_ready [-h] [-v] + + - check the media ready status on each + scsi_satl [-h] [-L] [-q] [-v] + - check for SCSI to ATA Translation Layer (SATL) + scsi_start [-h] [-v] [-w] + + - start media (i.e. spin up) in each + scsi_stop [-h] [-v] [-w] + + - stop media (i.e. spin down) in each + scsi_temperature [-h] [-v] + + - check temperature in each + +These scripts assume that the main sg3_utils utilities are installed +and are on the user's PATH. + +This directory, prior to sg3_utils-1.28, contained the sas_disk_blink +script. Since it depends on the sdparm utility it has been moved to +the sdparm package in its scripts directory. + +59-scsi-sg3_utils.rules is a Linux specific file for udev. These rules use +'sg_inq --export' to help udev create identifying device nodes, for example +/dev/disk/by-id/wwn-0x5001501234567890-part1. + +Douglas Gilbert +8th March 2014 diff --git a/scripts/cciss_id b/scripts/cciss_id new file mode 100755 index 0000000..8ac11d5 --- /dev/null +++ b/scripts/cciss_id @@ -0,0 +1,66 @@ +#!/bin/bash +# +# cciss_id +# +# Generates device node names according to the cciss naming rules +# +# Copyright (C) 2011 SUSE Linux Products GmbH +# Author: +# Hannes Reinecke +# +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation version 2 of the License. +# +# This script generates a device node name which is compatible +# with the 'cciss' device naming rules. +# It is intended to provide backward-compatible names for the +# 'hpsa' driver. +# + +cciss_enumerate() +{ + local last_pci_dev=${1##0000:} + local cur_pci_dev + local cciss_num=0 + + for cur_pci_dev in $(lspci -n | tac | sed -n 's/\(..:..\..\) .* 103c:\(3220\|3230\|3238\|323a\|323b\) .*/\1/p') ; do + if [ "$cur_pci_dev" == "$last_pci_dev" ] ; then + echo "$cciss_num" + return; + fi + cciss_num=$(($cciss_num + 1)) + done + echo "$cciss_num" +} + +hpsa_lun_offset() +{ + local scsi_host=$1 + + scsi_id=$(lsscsi 2>/dev/null | sed -n "s/.\(${scsi_host}:[0-9]*:[0-9]*:[0-9]*\)..*disk .*/\1/p" | head -1) + echo ${scsi_id##*:} +} + +DEVPATH=$1 +SCSIPATH=$(cd -P /sys$DEVPATH/device; echo $PWD) +SCSIID=${SCSIPATH##*/} +HOSTID=${SCSIID%%:*} +LUNID=${SCSIID##*:} +PCIPATH=${SCSIPATH%%/host*} +PCIDEV=${PCIPATH##*/} +HOSTPATH=${PCIPATH}/host${HOSTID}/scsi_host/host${HOSTID} +read controller 2>/dev/null <${HOSTPATH}/ctlr_num || controller=$(cciss_enumerate $PCIDEV) + +# hpsa lies about the LUN ... +disk_offset=$(hpsa_lun_offset $HOSTID) +if [ "$disk_offset" ] ; then + disk=$(( $LUNID - $disk_offset )) +else + disk=$LUNID +fi + +if [ "$controller" ] && [ "$disk" ] ; then + echo "ID_CCISS=c${controller}d${disk}" +fi diff --git a/scripts/fc_wwpn_id b/scripts/fc_wwpn_id new file mode 100644 index 0000000..c8d0189 --- /dev/null +++ b/scripts/fc_wwpn_id @@ -0,0 +1,49 @@ +#!/bin/bash +# +# fc_wwpn_id +# +# Generates device node names links based on FC WWPN +# Copyright (c) 2016 Hannes Reinecke, SUSE Linux GmbH +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation version 2 of the License. +# + +DEVPATH=$1 +SCSIPATH=$(cd -P "/sys$DEVPATH/device" || exit; echo "$PWD") + +d=$SCSIPATH +[ -d "$d/scsi_disk" ] || exit 0 +target_lun=${d##*:} + +while [ -n "$d" ] ; do + d=${d%/*} + e=${d##*/} + case "$e" in + rport*) + rport=$e + rport_dir="/sys/class/fc_remote_ports/$rport" + if [ -d "$rport_dir" ] ; then + rport_wwpn=$(cat "$rport_dir/port_name") + fi + ;; + host*) + host=$e + host_dir="/sys/class/fc_host/$host" + if [ -d "$host_dir" ] ; then + host_wwpn=$(cat "$host_dir/port_name") + break; + fi + esac +done + +echo "FC_TARGET_LUN=$target_lun" + +if [ -n "$rport_wwpn" ] ; then + echo "FC_TARGET_WWPN=$rport_wwpn" +fi + +if [ -n "$host_wwpn" ] ; then + echo "FC_INITIATOR_WWPN=$host_wwpn" +fi diff --git a/scripts/lunmask.service b/scripts/lunmask.service new file mode 100644 index 0000000..03fdd96 --- /dev/null +++ b/scripts/lunmask.service @@ -0,0 +1,11 @@ +[Unit] +Description=Disable LUN masking and scan SCSI Hosts +After=systemd-udev-trigger.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/lib/systemd/scripts/scsi-enable-target-scan.sh + +[Install] +WantedBy=multi-user.target diff --git a/scripts/rescan-scsi-bus.sh b/scripts/rescan-scsi-bus.sh new file mode 100755 index 0000000..6989208 --- /dev/null +++ b/scripts/rescan-scsi-bus.sh @@ -0,0 +1,1359 @@ +#!/bin/bash +# Script to rescan SCSI bus, using the scsi add-single-device mechanism. +# (c) 1998--2010 Kurt Garloff , GNU GPL v2 or v3 +# (c) 2006--2018 Hannes Reinecke, GNU GPL v2 or later +# $Id: rescan-scsi-bus.sh,v 1.57 2012/03/31 14:08:48 garloff Exp $ + +VERSION="20180615" +SCAN_WILD_CARD=4294967295 + +setcolor () +{ + red="\e[0;31m" + green="\e[0;32m" + yellow="\e[0;33m" + bold="\e[0;1m" + norm="\e[0;0m" +} + +unsetcolor () +{ + red=""; green="" + yellow=""; norm="" +} + +echo_debug() +{ + if [ $debug -eq 1 ] ; then + echo "$1" + fi +} + +# Output some text and return cursor to previous position +# (only works for simple strings) +# Stores length of string in LN and returns it +print_and_scroll_back () +{ + STRG="$1" + LN=${#STRG} + BK="" + declare -i cntr=0 + while test $cntr -lt $LN; do BK="$BK\e[D"; let cntr+=1; done + echo -en "$STRG$BK" + return $LN +} + +# Overwrite a text of length $LN with whitespace +white_out () +{ + BK=""; WH="" + declare -i cntr=0 + while test $cntr -lt $LN; do BK="$BK\e[D"; WH="$WH "; let cntr+=1; done + echo -en "$WH$BK" +} + +# Return hosts. sysfs must be mounted +findhosts_26 () +{ + hosts=`find /sys/class/scsi_host/host* -maxdepth 4 -type d -o -type l 2> /dev/null | awk -F'/' '{print $5}' | sed -e 's~host~~' | sort -nu` + scsi_host_data=`echo "$hosts" | sed -e 's~^~/sys/class/scsi_host/host~'` + for hostdir in $scsi_host_data; do + hostno=${hostdir#/sys/class/scsi_host/host} + if [ -f $hostdir/isp_name ] ; then + hostname="qla2xxx" + elif [ -f $hostdir/lpfc_drvr_version ] ; then + hostname="lpfc" + else + hostname=`cat $hostdir/proc_name` + fi + #hosts="$hosts $hostno" + echo_debug "Host adapter $hostno ($hostname) found." + done + if [ -z "$hosts" ] ; then + echo "No SCSI host adapters found in sysfs" + exit 1; + fi + # Not necessary just use double quotes around variable to preserve new lines + #hosts=`echo $hosts | tr ' ' '\n'` +} + +# Return hosts. /proc/scsi/HOSTADAPTER/? must exist +findhosts () +{ + hosts= + for driverdir in /proc/scsi/*; do + driver=${driverdir#/proc/scsi/} + if test $driver = scsi -o $driver = sg -o $driver = dummy -o $driver = device_info; then continue; fi + for hostdir in $driverdir/*; do + name=${hostdir#/proc/scsi/*/} + if test $name = add_map -o $name = map -o $name = mod_parm; then continue; fi + num=$name + driverinfo=$driver + if test -r "$hostdir/status"; then + num=$(printf '%d\n' "$(sed -n 's/SCSI host number://p' "$hostdir/status")") + driverinfo="$driver:$name" + fi + hosts="$hosts $num" + echo "Host adapter $num ($driverinfo) found." + done + done +} + +printtype () +{ + local type=$1 + + case "$type" in + 0) echo "Direct-Access" ;; + 1) echo "Sequential-Access" ;; + 2) echo "Printer" ;; + 3) echo "Processor" ;; + 4) echo "WORM" ;; + 5) echo "CD-ROM" ;; + 6) echo "Scanner" ;; + 7) echo "Optical-Device" ;; + 8) echo "Medium-Changer" ;; + 9) echo "Communications" ;; + 10) echo "Unknown" ;; + 11) echo "Unknown" ;; + 12) echo "RAID" ;; + 13) echo "Enclosure" ;; + 14) echo "Direct-Access-RBC" ;; + *) echo "Unknown" ;; + esac +} + +print02i() +{ + if [ "$1" = "*" ] ; then + echo "00" + else + printf "%02i" "$1" + fi +} + +# Get /proc/scsi/scsi info for device $host:$channel:$id:$lun +# Optional parameter: Number of lines after first (default = 2), +# result in SCSISTR, return code 1 means empty. +procscsiscsi () +{ + if test -z "$1"; then LN=2; else LN=$1; fi + CHANNEL=`print02i "$channel"` + ID=`print02i "$id"` + LUN=`print02i "$lun"` + if [ -d /sys/class/scsi_device ]; then + SCSIPATH="/sys/class/scsi_device/${host}:${channel}:${id}:${lun}" + if [ -d "$SCSIPATH" ] ; then + SCSISTR="Host: scsi${host} Channel: $CHANNEL Id: $ID Lun: $LUN" + if [ "$LN" -gt 0 ] ; then + IVEND=$(cat ${SCSIPATH}/device/vendor) + IPROD=$(cat ${SCSIPATH}/device/model) + IPREV=$(cat ${SCSIPATH}/device/rev) + SCSIDEV=$(printf ' Vendor: %-08s Model: %-16s Rev: %-4s' "$IVEND" "$IPROD" "$IPREV") + SCSISTR="$SCSISTR +$SCSIDEV" + fi + if [ "$LN" -gt 1 ] ; then + ILVL=$(cat ${SCSIPATH}/device/scsi_level) + type=$(cat ${SCSIPATH}/device/type) + ITYPE=$(printtype $type) + SCSITMP=$(printf ' Type: %-17s ANSI SCSI revision: %02d' "$ITYPE" "$((ILVL - 1))") + SCSISTR="$SCSISTR +$SCSITMP" + fi + else + return 1 + fi + else + grepstr="scsi$host Channel: $CHANNEL Id: $ID Lun: $LUN" + SCSISTR=$(grep -A "$LN" -e "$grepstr" /proc/scsi/scsi) + fi + if test -z "$SCSISTR"; then return 1; else return 0; fi +} + +# Find sg device with 2.6 sysfs support +sgdevice26 () +{ + local gendev + + gendev=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/generic + if test -e "$gendev"; then + SGDEV=$(basename "$(readlink "$gendev")") + else + for SGDEV in /sys/class/scsi_generic/sg*; do + DEV=`readlink $SGDEV/device` + if test "${DEV##*/}" = "$host:$channel:$id:$lun"; then + SGDEV=`basename $SGDEV`; return + fi + done + SGDEV="" + fi +} + +# Find sg device with 2.4 report-devs extensions +sgdevice24 () +{ + if procscsiscsi 3; then + SGDEV=`echo "$SCSISTR" | grep 'Attached drivers:' | sed 's/^ *Attached drivers: \(sg[0-9]*\).*/\1/'` + fi +} + +# Find sg device that belongs to SCSI device $host $channel $id $lun +# and return in SGDEV +sgdevice () +{ + SGDEV= + if test -d /sys/class/scsi_device; then + sgdevice26 + else + DRV=`grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null` + repdevstat=$((1-$?)) + if [ $repdevstat = 0 ]; then + echo "scsi report-devs 1" >/proc/scsi/scsi + DRV=`grep 'Attached drivers:' /proc/scsi/scsi 2>/dev/null` + if [ $? = 1 ]; then return; fi + fi + if ! echo "$DRV" | grep -q 'drivers: sg'; then + modprobe sg + fi + sgdevice24 + if [ $repdevstat = 0 ]; then + echo "scsi report-devs 0" >/proc/scsi/scsi + fi + fi +} + +# Whether or not the RMB (removable) bit has been set in the INQUIRY response. +# Uses ${host}, ${channel}, ${id} and ${lun}. Assumes that sg_device() has +# already been called. How to test this function: copy/paste this function +# in a shell and run +# (cd /sys/class/scsi_device && for d in *; do set ${d//:/ }; echo -n "$d $( "; SGDEV=bsg/$d host=$1 channel=$2 id=$3 lun=$4 is_removable; done) +is_removable () +{ + local b p + + p=/sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/inquiry + # Extract the second byte of the INQUIRY response and check bit 7 (mask 0x80). + b=$(od -tx1 -j1 -N1 "$p" 2>/dev/null | + { read -r offset byte rest; echo -n "$byte"; }) + if [ -n "$b" ]; then + echo $(((0x$b & 0x80) != 0)) + else + sg_inq /dev/$SGDEV 2>/dev/null | sed -n 's/^.*RMB=\([0-9]*\).*$/\1/p' + fi +} + +# Test if SCSI device is still responding to commands +# Return values: +# 0 device is present +# 1 device has changed +# 2 device has been removed +testonline () +{ + local ctr RC RMB + + : testonline + ctr=0 + RC=0 + # Set default values + IPTYPE=31 + IPQUAL=3 + if test ! -x /usr/bin/sg_turs; then return 0; fi + sgdevice + if test -z "$SGDEV"; then return 0; fi + sg_turs /dev/$SGDEV >/dev/null 2>&1 + RC=$? + + # Handle in progress of becoming ready and unit attention + while test $RC = 2 -o $RC = 6 && test $ctr -le 30; do + if test $RC = 2 -a "$RMB" != "1"; then echo -n "."; let LN+=1; sleep 1 + else sleep 0.02; fi + let ctr+=1 + sg_turs /dev/$SGDEV >/dev/null 2>&1 + RC=$? + # Check for removable device; TEST UNIT READY obviously will + # fail for a removable device with no medium + RMB=$(is_removable) + print_and_scroll_back "$host:$channel:$id:$lun $SGDEV ($RMB) " + test $RC = 2 -a "$RMB" = "1" && break + done + if test $ctr != 0; then white_out; fi + # echo -e "\e[A\e[A\e[A${yellow}Test existence of $SGDEV = $RC ${norm} \n\n\n" + if test $RC = 1; then return $RC; fi + # Reset RC (might be !=0 for passive paths) + RC=0 + # OK, device online, compare INQUIRY string + INQ=`sg_inq $sg_len_arg /dev/$SGDEV 2>/dev/null` + if [ -z "$INQ" ] ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}INQUIRY failed${norm} \n\n\n" + return 2 + fi + IVEND=`echo "$INQ" | grep 'Vendor identification:' | sed 's/^[^:]*: \(.*\)$/\1/'` + IPROD=`echo "$INQ" | grep 'Product identification:' | sed 's/^[^:]*: \(.*\)$/\1/'` + IPREV=`echo "$INQ" | grep 'Product revision level:' | sed 's/^[^:]*: \(.*\)$/\1/'` + STR=`printf " Vendor: %-08s Model: %-16s Rev: %-4s" "$IVEND" "$IPROD" "$IPREV"` + IPTYPE=`echo "$INQ" | sed -n 's/.* Device_type=\([0-9]*\) .*/\1/p'` + IPQUAL=`echo "$INQ" | sed -n 's/ *PQual=\([0-9]*\) Device.*/\1/p'` + if [ "$IPQUAL" != 0 ] ; then + [ -z "$IPQUAL" ] && IPQUAL=3 + [ -z "$IPTYPE" ] && IPTYPE=31 + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}LU not available (PQual $IPQUAL)${norm} \n\n\n" + return 2 + fi + + TYPE=$(printtype $IPTYPE) + if ! procscsiscsi ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV removed.\n\n\n" + return 2 + fi + TMPSTR=`echo "$SCSISTR" | grep 'Vendor:'` + if test $ignore_rev -eq 0 ; then + if [ "$TMPSTR" != "$STR" ]; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n" + return 1 + fi + else + # Ignore disk revision change + local old_str_no_rev=`echo "$TMPSTR" | sed -e 's/.\{4\}$//'` + local new_str_no_rev=`echo "$STR" | sed -e 's/.\{4\}$//'` + if [ "$old_str_no_rev" != "$new_str_no_rev" ]; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${SCSISTR#* } \nto: $STR ${norm} \n\n\n" + return 1 + fi + fi + TMPSTR=`echo "$SCSISTR" | sed -n 's/.*Type: *\(.*\) *ANSI.*/\1/p' | sed 's/ *$//g'` + if [ "$TMPSTR" != "$TYPE" ] ; then + echo -e "\e[A\e[A\e[A\e[A${red}$SGDEV changed: ${bold}\nfrom:${TMPSTR} \nto: $TYPE ${norm} \n\n\n" + return 1 + fi + return $RC +} + +# Test if SCSI device $host $channel $id $lun exists +# Outputs description from /proc/scsi/scsi (unless arg passed) +# Returns SCSISTR (empty if no dev) +testexist () +{ + : testexist + SCSISTR= + if procscsiscsi && test -z "$1"; then + echo "$SCSISTR" | head -n1 + echo "$SCSISTR" | tail -n2 | pr -o4 -l1 + fi +} + +# Returns the list of existing channels per host +chanlist () +{ + local hcil + local cil + local chan + local tmpchan + + for dev in /sys/class/scsi_device/${host}:* ; do + [ -d $dev ] || continue; + hcil=${dev##*/} + cil=${hcil#*:} + chan=${cil%%:*} + for tmpchan in $channelsearch ; do + if test "$chan" -eq $tmpchan ; then + chan= + fi + done + if test -n "$chan" ; then + channelsearch="$channelsearch $chan" + fi + done + if test -z "$channelsearch"; then channelsearch="0"; fi +} + +# Returns the list of existing targets per host +idlist () +{ + local tmpid + local newid + local oldid + + oldlist=$(ls /sys/class/scsi_device/ | sed -n "s/${host}:${channel}:\([0-9]*:[0-9]*\)/\1/p" | uniq) + # Rescan LUN 0 to check if we found new targets + echo "${channel} - 0" > /sys/class/scsi_host/host${host}/scan + newlist=$(ls /sys/class/scsi_device/ | sed -n "s/${host}:${channel}:\([0-9]*:[0-9]*\)/\1/p" | uniq) + for newid in $newlist ; do + oldid=$newid + for tmpid in $oldlist ; do + if test $newid = $tmpid ; then + oldid= + break + fi + done + if test -n "$oldid" ; then + id=${oldid%%:*} + lun=${oldid##*:} + dev=/sys/class/scsi_device/${host}:${channel}:${id}:${lun} + if [ -d $dev ] ; then + hcil=${dev##*/} + printf "\r${green}NEW: $norm" + testexist + if test "$SCSISTR" ; then + incrfound "$hcil" + fi + fi + fi + done + idsearch=$(ls /sys/class/scsi_device/ | sed -n "s/${host}:${channel}:\([0-9]*\):[0-9]*/\1/p" | uniq) +} + +# Returns the list of existing LUNs from device $host $channel $id $lun +# and returns list to stdout +getluns() +{ + sgdevice + if test -z "$SGDEV"; then return 1; fi + if test ! -x /usr/bin/sg_luns; then echo 0; return 1; fi + LLUN=`sg_luns /dev/$SGDEV 2>/dev/null | sed -n 's/ \(.*\)/\1/p'` + # Added -z $LLUN condition because $? gets the RC from sed, not sg_luns + if test $? != 0 -o -z "$LLUN"; then echo 0; return 1; fi + for lun in $LLUN ; do + # Swap LUN number + l0=0x$lun + l1=$(( ($l0 >> 48) & 0xffff )) + l2=$(( ($l0 >> 32) & 0xffff )) + l3=$(( ($l0 >> 16) & 0xffff )) + l4=$(( $l0 & 0xffff )) + l0=$(( ( ( ($l4 * 0xffff) + $l3 ) * 0xffff + $l2 ) * 0xffff + $l1 )) + printf "%u\n" $l0 + done + return 0 +} + +# Wait for udev to settle (create device nodes etc.) +udevadm_settle() +{ + local tmo=60 + if test -x /sbin/udevadm; then + print_and_scroll_back " Calling udevadm settle (can take a while) " + # Loop for up to 60 seconds if sd devices still are settling.. + # This allows us to continue if udev events are stuck on multipaths in recovery mode + while [ $tmo -gt 0 ] ; do + if ! /sbin/udevadm settle --timeout=1 | egrep -q sd[a-z]+ ; then + break; + fi + let tmo=$tmo-1 + done + white_out + elif test -x /sbin/udevsettle; then + print_and_scroll_back " Calling udevsettle (can take a while) " + /sbin/udevsettle + white_out + else + sleep 0.02 + fi +} + +# Perform scan on a single lun $host $channel $id $lun +dolunscan() +{ + local remappedlun0= + SCSISTR= + devnr="$host $channel $id $lun" + echo -e " Scanning for device $devnr ... " + printf "${yellow}OLD: $norm" + testexist + # Device exists: Test whether it's still online + # (testonline returns 2 if it's gone and 1 if it has changed) + if test "$SCSISTR" ; then + testonline + RC=$? + # Well known lun transition case. Only for Direct-Access devs (type 0) + # If block directory exists && and PQUAL != 0, we unmapped lun0 and just have a well-known lun + # If block directory doesn't exist && PQUAL == 0, we mapped a real lun0 + if test $lun -eq 0 -a $IPTYPE -eq 0 ; then + if test $RC = 2 ; then + if test -e /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device; then + if test -d /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/block ; then + remappedlun0=2 # Transition from real lun 0 to well-known + else + RC=0 # Set this so the system leaves the existing well known lun alone. This is a lun 0 with no block directory + fi + fi + elif test $RC = 0 -a $IPTYPE -eq 0; then + if test -e /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device; then + if test ! -d /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/block ; then + remappedlun0=1 # Transition from well-known to real lun 0 + fi + fi + fi + fi + fi + + # Special case: lun 0 just got added (for reportlunscan), + # so make sure we correctly treat it as new + if test "$lun" = "0" -a "$1" = "1" -a -z "$remappedlun0"; then + SCSISTR="" + printf "\r\e[A\e[A\e[A" + fi + + : f $remove s $SCSISTR + if test "$remove" -a "$SCSISTR" -o "$remappedlun0" = "1"; then + if test $RC != 0 -o ! -z "$forceremove" -o -n "$remappedlun0"; then + if test "$remappedlun0" != "1" ; then + echo -en "\r\e[A\e[A\e[A${red}REM: " + echo "$SCSISTR" | head -n1 + echo -e "${norm}\e[B\e[B" + fi + if test -e /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device; then + # have to preemptively do this so we can figure out the mpath device + # Don't do this if we're deleting a well known lun to replace it + if test "$remappedlun0" != "1" ; then + incrrmvd "$host:$channel:$id:$lun" + fi + echo 1 > /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/delete + sleep 0.02 + else + echo "scsi remove-single-device $devnr" > /proc/scsi/scsi + if test $RC -eq 1 -o $lun -eq 0 ; then + # Try readding, should fail if device is gone + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + fi + fi + if test $RC = 0 -o "$forcerescan" ; then + if test -e /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device; then + echo 1 > /sys/class/scsi_device/${host}:${channel}:${id}:${lun}/device/rescan + fi + fi + printf "\r\e[A\e[A\e[A${yellow}OLD: $norm" + testexist + if test -z "$SCSISTR" -a $RC != 1 -a "$remappedlun0" != "1"; then + printf "\r${red}DEL: $norm\r\n\n" + # In the event we're replacing with a well known node, we need to let it continue, to create the replacement node + test "$remappedlun0" != "2" && return 1 + fi + fi + if test -z "$SCSISTR" -o -n "$remappedlun0"; then + if test "$remappedlun0" != "2" ; then + # Device does not exist, try to add + printf "\r${green}NEW: $norm" + fi + if test -e /sys/class/scsi_host/host${host}/scan; then + echo "$channel $id $lun" > /sys/class/scsi_host/host${host}/scan 2> /dev/null + else + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + testexist + if test -z "$SCSISTR"; then + # Device not present + printf "\r\e[A"; + # Optimization: if lun==0, stop here (only if in non-remove mode) + if test $lun = 0 -a -z "$remove" -a $optscan = 1; then + break; + fi + else + if test "$remappedlun0" != "2" ; then + incrfound "$host:$channel:$id:$lun" + fi + fi + fi +} + +# Perform report lun scan on $host $channel $id using REPORT_LUNS +doreportlun() +{ + lun=0 + SCSISTR= + devnr="$host $channel $id $lun" + echo -en " Scanning for device $devnr ...\r" + lun0added= + #printf "${yellow}OLD: $norm" + # Phase one: If LUN0 does not exist, try to add + testexist -q + if test -z "$SCSISTR"; then + # Device does not exist, try to add + #printf "\r${green}NEW: $norm" + if test -e /sys/class/scsi_host/host${host}/scan; then + echo "$channel $id $lun" > /sys/class/scsi_host/host${host}/scan 2> /dev/null + udevadm_settle + else + echo "scsi add-single-device $devnr" > /proc/scsi/scsi + fi + testexist -q + if test -n "$SCSISTR"; then + lun0added=1 + #testonline + else + # Device not present + # return + # Find alternative LUN to send getluns to + for dev in /sys/class/scsi_device/${host}:${channel}:${id}:*; do + [ -d "$dev" ] || continue + lun=${dev##*:} + break + done + fi + fi + targetluns=`getluns` + REPLUNSTAT=$? + lunremove= + #echo "getluns reports " $targetluns + olddev=`find /sys/class/scsi_device/ -name "$host:$channel:$id:*" 2>/dev/null | sort -t: -k4 -n` + oldtargets="$targetluns" + # OK -- if we don't have a LUN to send a REPORT_LUNS to, we could + # fall back to wildcard scanning. Same thing if the device does not + # support REPORT_LUNS + # TODO: We might be better off to ALWAYS use wildcard scanning if + # it works + if test "$REPLUNSTAT" = "1"; then + if test -e /sys/class/scsi_host/host${host}/scan; then + echo "$channel $id -" > /sys/class/scsi_host/host${host}/scan 2> /dev/null + udevadm_settle + else + echo "scsi add-single-device $host $channel $id $SCAN_WILD_CARD" > /proc/scsi/scsi + fi + targetluns=`find /sys/class/scsi_device/ -name "$host:$channel:$id:*" 2>/dev/null | awk -F'/' '{print $5}' | awk -F':' '{print $4}' | sort -n` + let found+=`echo "$targetluns" | wc -l` + let found-=`echo "$olddev" | wc -l` + fi + if test -z "$targetluns"; then targetluns="$oldtargets"; fi + # Check existing luns + for dev in $olddev; do + [ -d "$dev" ] || continue + lun=${dev##*:} + newsearch= + inlist= + # OK, is existing $lun (still) in reported list + for tmplun in $targetluns; do + if test $tmplun -eq $lun ; then + inlist=1 + dolunscan $lun0added + else + newsearch="$newsearch $tmplun" + fi + done + # OK, we have now done a lunscan on $lun and + # $newsearch is the old $targetluns without $lun + if [ -z "$inlist" ]; then + # Stale lun + lunremove="$lunremove $lun" + fi + # $lun removed from $lunsearch (echo for whitespace cleanup) + targetluns=`echo $newsearch` + done + # Add new ones and check stale ones + for lun in $targetluns $lunremove; do + dolunscan $lun0added + done +} + +# Perform search (scan $host) +dosearch () +{ + if test -z "$channelsearch" ; then + chanlist + fi + for channel in $channelsearch; do + if test -z "$idsearch" ; then + if test -z "$lunsearch" ; then + idlist + else + idsearch=$(ls /sys/class/scsi_device/ | sed -n "s/${host}:${channel}:\([0-9]*\):[0-9]*/\1/p" | uniq) + fi + fi + for id in $idsearch; do + if test -z "$lunsearch" ; then + doreportlun + else + for lun in $lunsearch; do + dolunscan + done + fi + done + done +} + +expandlist () +{ + list=$1 + result="" + first=${list%%,*} + rest=${list#*,} + while test ! -z "$first"; do + beg=${first%%-*}; + if test "$beg" = "$first"; then + result="$result $beg"; + else + end=${first#*-} + result="$result `seq $beg $end`" + fi + test "$rest" = "$first" && rest="" + first=${rest%%,*} + rest=${rest#*,} + done + echo $result +} + +searchexisting() +{ + local tmpch; + local tmpid + local match=0 + local targets=`ls -d /sys/class/scsi_device/$host:* 2> /dev/null | egrep -o $host:[0-9]+:[0-9]+ | sort | uniq` + + # Nothing came back on this host, so we should skip it + test -z "$targets" && return + + local target=; + for target in $targets ; do + channel=`echo $target | cut -d":" -f2` + id=`echo $target | cut -d":" -f 3` + if [ -n "$channelsearch" ] ; then + for tmpch in $channelsearch ; do + test $tmpch -eq $channel && match=1 + done + else + match=1 + fi + + test $match -eq 0 && continue + match=0 + + if [ $filter_ids -eq 1 ] ; then + for tmpid in $idsearch ; do + if [ $tmpid -eq $id ] ; then + match=1 + fi + done + else + match=1 + fi + + test $match -eq 0 && continue + + if [ -z "$lunsearch" ] ; then + doreportlun + else + for lun in $lunsearch ; do + dolunscan + done + fi + done +} + +# Go through all of the existing devices and figure out any that have been remapped +findremapped() +{ + local hctl=; + local devs=`ls /sys/class/scsi_device/` + local sddev= + local id_serial= + local id_serial_old= + local remapped= + mpaths="" + local tmpfile=$(mktemp /tmp/rescan-scsi-bus.XXXXXXXX 2> /dev/null) + + if [ -z "$tmpfile" ] ; then + tmpfile="/tmp/rescan-scsi-bus.$$" + rm -f $tmpfile + fi + + # Get all of the ID_SERIAL attributes, after finding their sd node + for hctl in $devs ; do + if [ -d /sys/class/scsi_device/$hctl/device/block ] ; then + sddev=`ls /sys/class/scsi_device/$hctl/device/block` + id_serial_old=`udevadm info -q all -n $sddev | grep "ID_SERIAL=" | cut -d"=" -f2` + [ -z "$id_serial_old" ] && id_serial_old="none" + echo "$hctl $sddev $id_serial_old" >> $tmpfile + fi + done + + # Trigger udev to update the info + echo -n "Triggering udev to update device information... " + /sbin/udevadm trigger + udevadm_settle 2>&1 /dev/null + echo "Done" + + # See what changed and reload the respective multipath device if applicable + while read hctl sddev id_serial_old ; do + remapped=0 + id_serial=`udevadm info -q all -n $sddev | grep "ID_SERIAL=" | cut -d"=" -f2` + [ -z "$id_serial" ] && id_serial="none" + if [ "$id_serial_old" != "$id_serial" ] ; then + remapped=1 + fi + # If udev events updated the disks already, but the multipath device isn't update + # check for old devices to make sure we found remapped luns + if [ -n "$mp_enable" ] && [ $remapped -eq 0 ]; then + findmultipath $sddev $id_serial + if [ $? -eq 1 ] ; then + remapped=1 + fi + fi + + # if uuid is 1, it's unmapped, so we don't want to treat it as a remap + # if remapped flag is 0, just skip the rest of the logic + if [ "$id_serial" = "1" ] || [ $remapped -eq 0 ] ; then + continue + fi + printf "${yellow}REMAPPED: $norm" + host=`echo $hctl | cut -d":" -f1` + channel=`echo $hctl | cut -d":" -f2` + id=`echo $hctl | cut -d":" -f3` + lun=`echo $hctl | cut -d":" -f4` + procscsiscsi + echo "$SCSISTR" + incrchgd "$hctl" + done < $tmpfile + rm -f $tmpfile + + if test -n "$mp_enable" -a -n "$mpaths" ; then + echo "Updating multipath device mappings" + flushmpaths + $MULTIPATH | grep "create:" 2> /dev/null + fi +} + +incrfound() +{ + local hctl="$1" + if test -n "$hctl" ; then + let found+=1 + FOUNDDEVS="$FOUNDDEVS\t[$hctl]\n" + else + return + fi +} + +incrchgd() +{ + local hctl="$1" + if test -n "$hctl" ; then + if ! echo $CHGDEVS | grep -q "\[$hctl\]"; then + let updated+=1 + CHGDEVS="$CHGDEVS\t[$hctl]\n" + fi + else + return + fi + + if test -n "$mp_enable" ; then + local sdev="`findsddev \"$hctl\"`" + if test -n "$sdev" ; then + findmultipath "$sdev" + fi + fi +} + +incrrmvd() +{ + local hctl="$1" + if test -n "$hctl" ; then + let rmvd+=1; + RMVDDEVS="$RMVDDEVS\t[$hctl]\n" + else + return + fi + + if test -n "$mp_enable" ; then + local sdev="`findsddev \"$hctl\"`" + if test -n "$sdev" ; then + findmultipath "$sdev" + fi + fi +} + +findsddev() +{ + local hctl="$1" + local sddev= + + if test ! -e /sys/class/scsi_device/$hctl/device/block ; then + return 1 + fi + + sddev=`ls /sys/class/scsi_device/$hctl/device/block` + echo $sddev + + return 0 +} + +addmpathtolist() +{ + local mp="$1" + local mp2= + + for mp2 in $mpaths ; do + # The multipath device is already in the list + if [ "$mp2" = "$mp" ] ; then + return + fi + done + mpaths="$mpaths $mp" +} + +findmultipath() +{ + local dev="$1" + local find_mismatch="$2" + local mp= + local mp2= + local found_dup=0 + + # Need a sdev, and executable multipath and dmsetup command here + if [ -z "$dev" ] || [ ! -x $DMSETUP ] || [ ! -x "$MULTIPATH" ] ; then + return 1 + fi + + local maj_min=`cat /sys/block/$dev/dev` + for mp in $($DMSETUP ls --target=multipath | cut -f 1) ; do + [ "$mp" = "No" ] && break; + if $DMSETUP status $mp | grep -q " $maj_min "; then + # With two arguments, look up current uuid from sysfs + # if it doesn't match what was passed, this multipath + # device is not updated, so this is a remapped LUN + if [ -n "$find_mismatch" ] ; then + mp2=$($MULTIPATH -l "$mp" | egrep -o "dm-[0-9]+") + mp2=$(cut -f2 -d- "/sys/block/$mp2/dm/uuid") + if [ "$find_mismatch" != "$mp2" ] ; then + addmpathtolist $mp + found_dup=1 + fi + continue + fi + # Normal mode: Find the first multipath with the sdev + # and add it to the list + addmpathtolist $mp + return + fi + done + + # Return 1 to signal that a duplicate was found to the calling function + if [ $found_dup -eq 1 ] ; then + return 1 + else + return 0 + fi +} + +reloadmpaths() +{ + local mpath + if [ ! -x "$MULTIPATH" ] ; then + echo "no -x multipath" + return + fi + + # Pass 1 as the argument to reload all mpaths + if [ "$1" = "1" ] ; then + echo "Reloading all multipath devices" + $MULTIPATH -r > /dev/null 2>&1 + return + fi + + # Reload the multipath devices + for mpath in $mpaths ; do + echo -n "Reloading multipath device $mpath... " + $MULTIPATH -r $mpath > /dev/null 2>&1 + if test "$?" = "0" ; then + echo "Done" + else + echo "Fail" + fi + done +} + +resizempaths() +{ + local mpath + + for mpath in $mpaths ; do + echo -n "Resizing multipath map $mpath ..." + multipathd -k"resize map $mpath" + let updated+=1 + done +} + +flushmpaths() +{ + local mpath + local remove="" + local i + local flush_retries=5 + + if test -n "$1" ; then + for mpath in $($DMSETUP ls --target=multipath | cut -f 1) ; do + [ "$mpath" = "No" ] && break + num=$($DMSETUP status $mpath | awk 'BEGIN{RS=" ";active=0}/[0-9]+:[0-9]+/{dev=1}/A/{if (dev == 1) active++; dev=0} END{ print active }') + if [ $num -eq 0 ] ; then + remove="$remove $mpath" + fi + done + else + remove="$mpaths" + fi + + for mpath in $remove ; do + i=0 + echo -n "Flushing multipath device $mpath... " + while [ $i -lt $flush_retries ] ; do + $DMSETUP message $mpath 0 fail_if_no_path > /dev/null 2>&1 + $MULTIPATH -f $mpath > /dev/null 2>&1 + if test "$?" = "0" ; then + echo "Done ($i retries)" + break + elif test $i -eq $flush_retries ; then + echo "Fail" + fi + sleep 0.02 + let i=$i+1 + done + done +} + + +# Find resized luns +findresized() +{ + local devs= + local size= + local new_size= + local sysfs_path= + local sddev= + local i= + local m= + local mpathsize= + declare -a mpathsizes + + if [ -z "$lunsearch" ] ; then + devs=`ls /sys/class/scsi_device/` + else + for lun in $lunsearch ; do + devs="$devs `(cd /sys/class/scsi_device/ && ls -d *:${lun})`" + done + fi + + for hctl in $devs ; do + sysfs_path="/sys/class/scsi_device/$hctl/device" + if [ -d "$sysfs_path/block" ] ; then + sddev=`ls $sysfs_path/block` + size=`cat $sysfs_path/block/$sddev/size` + + echo 1 > $sysfs_path/rescan + new_size=`cat $sysfs_path/block/$sddev/size` + + if [ "$size" != "$new_size" ] && [ "$size" != "0" ] && [ "$new_size" != "0" ] ; then + printf "${yellow}RESIZED: $norm" + host=`echo $hctl | cut -d":" -f1` + channel=`echo $hctl | cut -d":" -f2` + id=`echo $hctl | cut -d":" -f3` + lun=`echo $hctl | cut -d":" -f4` + + procscsiscsi + echo "$SCSISTR" + incrchgd "$hctl" + fi + fi + done + + if test -n "$mp_enable" -a -n "$mpaths" ; then + i=0 + for m in $mpaths ; do + mpathsizes[$i]="`$MULTIPATH -l $m | egrep -o [0-9]+.[0-9]+[KMGT]`" + let i=$i+1 + done + resizempaths + i=0 + for m in $mpaths ; do + mpathsize="`$MULTIPATH -l $m | egrep -o [0-9\.]+[KMGT]`" + echo "$m ${mpathsizes[$i]} => $mpathsize" + let i=$i+1 + done + fi +} + +FOUNDDEVS="" +CHGDEVS="" +RMVDDEVS="" + +# main +if test @$1 = @--help -o @$1 = @-h -o @$1 = @-?; then + echo "Usage: rescan-scsi-bus.sh [options] [host [host ...]]" + echo "Options:" + echo " -a scan all targets, not just currently existing [default: disabled]" + echo " -c enables scanning of channels 0 1 [default: 0 / all detected ones]" + echo " -d enable debug [default: 0]" + echo " -f flush failed multipath devices [default: disabled]" + echo " -h help: print this usage message then exit" + echo " -i issue a FibreChannel LIP reset [default: disabled]" + echo " -I SECS issue a FibreChannel LIP reset and wait for SECS seconds [default: disabled]" + echo " -l activates scanning for LUNs 0--7 [default: 0]" + echo " -L NUM activates scanning for LUNs 0--NUM [default: 0]" + echo " -m update multipath devices [default: disabled]" + echo " -r enables removing of devices [default: disabled]" + echo " -s look for resized disks and reload associated multipath devices, if applicable" + echo " -u look for existing disks that have been remapped" + echo " -V print version date then exit" + echo " -w scan for target device IDs 0--15 [default: 0--7]" + echo "--alltargets: same as -a" + echo "--attachpq3: Tell kernel to attach sg to LUN 0 that reports PQ=3" + echo "--channels=LIST: Scan only channel(s) in LIST" + echo "--color: use coloured prefixes OLD/NEW/DEL" + echo "--flush: same as -f" + echo "--forceremove: Remove stale devices (DANGEROUS)" + echo "--forcerescan: Remove and readd existing devices (DANGEROUS)" + echo "--help: print this usage message then exit" + echo "--hosts=LIST: Scan only host(s) in LIST" + echo "--ids=LIST: Scan only target ID(s) in LIST" + echo "--ignore-rev: Ignore the revision change" + echo "--issue-lip: same as -i" + echo "--issue-lip-wait=SECS: same as -I" + echo "--largelun: Tell kernel to support LUNs > 7 even on SCSI2 devs" + echo "--luns=LIST: Scan only lun(s) in LIST" + echo "--multipath: same as -m" + echo "--nooptscan: don't stop looking for LUNs is 0 is not found" + echo "--remove: same as -r" + echo "--reportlun2: Tell kernel to try REPORT_LUN even on SCSI2 devices" + echo "--resize: same as -s" + echo "--sparselun: Tell kernel to support sparse LUN numbering" + echo "--sync/nosync: Issue a sync / no sync [default: sync if remove]" + echo "--update: same as -u" + echo "--version: same as -V" + echo "--wide: same as -w" + echo "" + echo "Host numbers may thus be specified either directly on cmd line (deprecated)" + echo "or with the --hosts=LIST parameter (recommended)." + echo "LIST: A[-B][,C[-D]]... is a comma separated list of single values and ranges" + echo "(No spaces allowed.)" + exit 0 +fi + +if test @$1 = @--version -o @$1 = @-V ; then + echo ${VERSION} + exit 0 +fi + +if test ! -d /sys/class/scsi_host/ -a ! -d /proc/scsi/; then + echo "Error: SCSI subsystem not active" + exit 1 +fi + +# Make sure sg is there +modprobe sg >/dev/null 2>&1 + +if test -x /usr/bin/sg_inq; then + sg_version=$(sg_inq -V 2>&1 | cut -d " " -f 3) + if test -n "$sg_version"; then + sg_ver_maj=${sg_version:0:1} + sg_version=${sg_version##?.} + let sg_version+=$((100*$sg_ver_maj)) + fi + sg_version=${sg_version##0.} + #echo "\"$sg_version\"" + if [ -z "$sg_version" -o "$sg_version" -lt 70 ] ; then + sg_len_arg="-36" + else + sg_len_arg="--len=36" + fi +else + echo "WARN: /usr/bin/sg_inq not present -- please install sg3_utils" + echo " or rescan-scsi-bus.sh might not fully work." +fi + +# defaults +unsetcolor +debug=0 +lunsearch= +opt_idsearch=`seq 0 7` +filter_ids=0 +opt_channelsearch= +remove= +updated=0 +update=0 +resize=0 +forceremove= +optscan=1 +sync=1 +existing_targets=1 +mp_enable= +lipreset=-1 +declare -i scan_flags=0 +ignore_rev=0 + +# Scan options +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + a) existing_targets=;; #Scan ALL targets when specified + c) opt_channelsearch="0 1" ;; + d) debug=1 ;; + f) flush=1 ;; + i) lipreset=0 ;; + I) shift; lipreset=$opt ;; + l) lunsearch=`seq 0 7` ;; + L) lunsearch=`seq 0 $2`; shift ;; + m) mp_enable=1 ;; + r) remove=1 ;; + s) resize=1; mp_enable=1 ;; + u) update=1 ;; + w) opt_idsearch=`seq 0 15` ;; + -alltargets) existing_targets=;; + -attachpq3) scan_flags=$(($scan_flags|0x1000000)) ;; + -channels=*) arg=${opt#-channels=};opt_channelsearch=`expandlist $arg` ;; + -color) setcolor ;; + -flush) flush=1 ;; + -forceremove) remove=1; forceremove=1 ;; + -forcerescan) remove=1; forcerescan=1 ;; + -hosts=*) arg=${opt#-hosts=}; hosts=`expandlist $arg` ;; + -ids=*) arg=${opt#-ids=}; opt_idsearch=`expandlist $arg` ; filter_ids=1;; + -ignore-rev) ignore_rev=1;; + -issue-lip) lipreset=0 ;; + -issue-lip-wait) lipreset=${opt#-issue-lip-wait=};; + -largelun) scan_flags=$(($scan_flags|0x200)) ;; + -luns=*) arg=${opt#-luns=}; lunsearch=`expandlist $arg` ;; + -multipath) mp_enable=1 ;; + -nooptscan) optscan=0 ;; + -nosync) sync=0 ;; + -remove) remove=1 ;; + -reportlun2) scan_flags=$(($scan_flags|0x20000)) ;; + -resize) resize=1;; + -sparselun) scan_flags=$((scan_flags|0x40)) ;; + -sync) sync=2 ;; + -update) update=1;; + -wide) opt_idsearch=`seq 0 15` ;; + *) echo "Unknown option -$opt !" ;; + esac + shift + opt="$1" +done + +if [ -z "$hosts" ] ; then + if test -d /sys/class/scsi_host; then + findhosts_26 + else + findhosts + fi +fi + +if [ -d /sys/class/scsi_host -a ! -w /sys/class/scsi_host ]; then + echo "You need to run scsi-rescan-bus.sh as root" + exit 2 +fi +if test "$sync" = 1 -a "$remove" = 1; then sync=2; fi +if test "$sync" = 2; then echo "Syncing file systems"; sync; fi +if test -w /sys/module/scsi_mod/parameters/default_dev_flags -a $scan_flags != 0; then + OLD_SCANFLAGS=`cat /sys/module/scsi_mod/parameters/default_dev_flags` + NEW_SCANFLAGS=$(($OLD_SCANFLAGS|$scan_flags)) + if test "$OLD_SCANFLAGS" != "$NEW_SCANFLAGS"; then + echo -n "Temporarily setting kernel scanning flags from " + printf "0x%08x to 0x%08x\n" $OLD_SCANFLAGS $NEW_SCANFLAGS + echo $NEW_SCANFLAGS > /sys/module/scsi_mod/parameters/default_dev_flags + else + unset OLD_SCANFLAGS + fi +fi +DMSETUP=$(which dmsetup) +[ -z "$DMSETUP" ] && flush= && mp_enable= +MULTIPATH=$(which multipath) +[ -z "$MULTIPATH" ] && flush= && mp_enable= + +echo -n "Scanning SCSI subsystem for new devices" +test -z "$flush" || echo -n ", flush failed multipath devices," +test -z "$remove" || echo -n " and remove devices that have disappeared" +echo +declare -i found=0 +declare -i updated=0 +declare -i rmvd=0 + +if [ -n "$flush" ] ; then + if [ -x "$MULTIPATH" ] ; then + flushmpaths 1 + fi +fi + +# Update existing mappings +if [ $update -eq 1 ] ; then + echo "Searching for remapped LUNs" + findremapped + # If you've changed the mapping, there's a chance it's a different size + mpaths="" + findresized +# Search for resized LUNs +elif [ $resize -eq 1 ] ; then + echo "Searching for resized LUNs" + findresized +# Normal rescan mode +else + for host in $hosts; do + echo -n "Scanning host $host " + if test -e /sys/class/fc_host/host$host ; then + # It's pointless to do a target scan on FC + issue_lip=/sys/class/fc_host/host$host/issue_lip + if test -e $issue_lip -a $lipreset -ge 0 ; then + echo 1 > $issue_lip 2> /dev/null; + udevadm_settle + [ $lipreset -gt 0 ] && sleep $lipreset + fi + channelsearch= + idsearch= + else + channelsearch=$opt_channelsearch + idsearch=$opt_idsearch + fi + [ -n "$channelsearch" ] && echo -n "channels $channelsearch " + echo -n "for " + if [ -n "$idsearch" ] ; then + echo -n " SCSI target IDs " $idsearch + else + echo -n " all SCSI target IDs" + fi + if [ -n "$lunsearch" ] ; then + echo ", LUNs " $lunsearch + else + echo ", all LUNs" + fi + + if [ -n "$existing_targets" ] ; then + searchexisting + else + dosearch + fi + done + if test -n "$OLD_SCANFLAGS"; then + echo $OLD_SCANFLAGS > /sys/module/scsi_mod/parameters/default_dev_flags + fi +fi + +let rmvd_found=$rmvd+$found +if test -n "$mp_enable" -a $rmvd_found -gt 0 ; then + echo "Attempting to update multipath devices..." + if test $rmvd -gt 0 ; then + udevadm_settle + echo "Removing multipath mappings for removed devices if all paths are now failed... " + flushmpaths 1 + fi + if test $found -gt 0 ; then + /sbin/udevadm trigger --sysname-match=sd* + udevadm_settle + if [ -x "$MULTIPATH" ] ; then + echo "Trying to discover new multipath mappings for newly discovered devices... " + $MULTIPATH | grep "create:" 2> /dev/null + fi + fi +fi + +echo "$found new or changed device(s) found. " +if test ! -z "$FOUNDDEVS" ; then + printf "$FOUNDDEVS" +fi +echo "$updated remapped or resized device(s) found." +if test ! -z "$CHGDEVS" ; then + printf "$CHGDEVS" +fi +echo "$rmvd device(s) removed. " +if test ! -z "$RMVDDEVS" ; then + printf "$RMVDDEVS" +fi + +# Local Variables: +# sh-basic-offset: 2 +# End: + diff --git a/scripts/scsi_logging_level b/scripts/scsi_logging_level new file mode 100755 index 0000000..2fba2b7 --- /dev/null +++ b/scripts/scsi_logging_level @@ -0,0 +1,268 @@ +#! /bin/bash +############################################################################### +# Conveniently create and set scsi logging level, show SCSI_LOG fields in human +# readable form. +# +# (C) Copyright IBM Corp. 2006 +# +# Modified by D. Gilbert to replace the use of sysctl [20080218] +# Lat change: D. Gilbert 20150219 +############################################################################### + + +REVISION="1.0" +SCRIPTNAME="scsi_logging_level" + +declare -i LOG_ERROR=0 +declare -i LOG_TIMEOUT=0 +declare -i LOG_SCAN=0 +declare -i LOG_MLQUEUE=0 +declare -i LOG_MLCOMPLETE=0 +declare -i LOG_LLQUEUE=0 +declare -i LOG_LLCOMPLETE=0 +declare -i LOG_HLQUEUE=0 +declare -i LOG_HLCOMPLETE=0 +declare -i LOG_IOCTL=0 + +declare -i LEVEL=0 + +SET=0 +GET=0 +CREATE=0 + +OPTS=$(getopt -o hvcgsa:E:T:S:I:M:L:H: --long \ +help,version,create,get,set,all:,error:,timeout:,scan:,ioctl:,\ +midlevel:,mlqueue:,mlcomplete:,lowlevel:,llqueue:,llcomplete:,\ +highlevel:,hlqueue:,hlcomplete: -n \'$SCRIPTNAME\' -- "$@") +eval set -- "$OPTS" + +# print version info +printversion() +{ + cat <>3)) + LOG_TIMEOUT=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)) + LOG_SCAN=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)) + LOG_MLQUEUE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)) + LOG_MLCOMPLETE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)) + LOG_LLQUEUE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)) + LOG_LLCOMPLETE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)) + LOG_HLQUEUE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)); + LOG_HLCOMPLETE=$((LEVEL & 7)); LEVEL=$((LEVEL>>3)); + LOG_IOCTL=$((LEVEL & 7)) + + echo "SCSI_LOG_ERROR=$LOG_ERROR" + echo "SCSI_LOG_TIMEOUT=$LOG_TIMEOUT" + echo "SCSI_LOG_SCAN=$LOG_SCAN" + echo "SCSI_LOG_MLQUEUE=$LOG_MLQUEUE" + echo "SCSI_LOG_MLCOMPLETE=$LOG_MLCOMPLETE" + echo "SCSI_LOG_LLQUEUE=$LOG_LLQUEUE" + echo "SCSI_LOG_LLCOMPLETE=$LOG_LLCOMPLETE" + echo "SCSI_LOG_HLQUEUE=$LOG_HLQUEUE" + echo "SCSI_LOG_HLCOMPLETE=$LOG_HLCOMPLETE" + echo "SCSI_LOG_IOCTL=$LOG_IOCTL" +} + +set_logging_level() +{ + echo "New scsi logging level:" +# sysctl -q -w dev.scsi.logging_level=$LEVEL + echo $LEVEL > /proc/sys/dev/scsi/logging_level + if [ $? != 0 ] + then + echo "$SCRIPTNAME: could not write scsi logging level $LEVEL" + echo " kernel does not have SCSI_LOGGING support or needs superuser" + exit 1 + fi +} +create_logging_level() +{ + LEVEL=$((LOG_IOCTL & 7)); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_HLCOMPLETE & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_HLQUEUE & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_LLCOMPLETE & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_LLQUEUE & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_MLCOMPLETE & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_MLQUEUE & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_SCAN & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_TIMEOUT & 7))); LEVEL=$((LEVEL<<3)) + LEVEL=$((LEVEL|(LOG_ERROR & 7))) +} + +check_cmdline "$@" + +if [ $SET = "1" ] +then + create_logging_level + set_logging_level + show_logging_level +elif [ $GET = "1" ] +then + get_logging_level + show_logging_level +elif [ $CREATE = "1" ] +then + create_logging_level + show_logging_level +else + invalid_cmdline missing option \'-g\', \'-s\' or \'-c\' +fi diff --git a/scripts/scsi_mandat b/scripts/scsi_mandat new file mode 100755 index 0000000..1f72b40 --- /dev/null +++ b/scripts/scsi_mandat @@ -0,0 +1,133 @@ +#!/bin/bash +# scsi_mandat +# +# Script to test compliance with SCSI mandatory commands. +# The vintage is SPC-3 and SPC-4 (see www.t10.org). +# +# Coverage: +# Command Standard/Draft (is mandatory in) +# ------------------------------------------------------- +# INQUIRY (standard) SCSI-2, SPC, SPC-2, SPC-3, SPC-4 +# INQUIRY (VPD pages 0, 0x83) SPC-2, SPC-3, SPC-4 +# REPORT LUNS SPC-3, SPC-4 +# TEST UNIT READY SCSI-2, SPC, SPC-2, SPC-3, SPC-4 +# REQUEST SENSE SCSI-2, SBC, SBC-2,3, MMC-4,5, SSC-2,3 +# SEND DIAGNOSTIC SBC, SBC-2,3, SSC-2,3 +# +# This script uses utilities frim sg3_utils package (version +# 1.21 or later) +# +# Douglas Gilbert 20131016 + + +log=0 +quiet=0 +verbose="" + +file_err=0 +inv_opcode=0 +illeg_req=0 +not_ready=0 +medium=0 +other_err=0 +recovered=0 +sanity=0 +syntax=0 +timeout=0 +unit_attention=0 +aborted_command=0 + +## total_err=0 + +usage() +{ + echo "Usage: scsi_mandat [-h] [-L] [-q] [-v] " + echo " where: -h, --help print usage message" + echo " -L, --log append stderr to 'scsi_mandat.err'" + echo " -q, --quiet suppress some output" + echo " -v, --verbose increase verbosity of output" + echo "" + echo "Check for mandatory SCSI command support" +} + + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + h|-help) usage ; exit 0 ;; + L|-log) let log=$log+1 ;; + q|-quiet) let quiet=$quiet+1 ;; + v|-verbose) verbose="-v" ;; + vv) verbose="-vv" ;; + vvv) verbose="-vvv" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for command in "sg_inq" "sg_luns" "sg_turs" "sg_requests" "sg_vpd" \ + "sg_vpd -i" "sg_senddiag -t" +do + if [ $quiet -eq 0 ] + then echo "$command" $verbose "$1" + fi + + if [ $verbose ] + then + if [ $log -eq 0 ] + then + $command $verbose "$1" + else + $command $verbose "$1" >> scsi_mandat.err 2>> scsi_mandat.err + fi + else + if [ $log -eq 0 ] + then + $command "$1" > /dev/null 2>> /dev/null + else + $command "$1" > /dev/null 2>> scsi_mandat.err + fi + fi + res=$? + case "$res" in + 0) ;; + 1) echo " syntax error" ; let syntax=$syntax+1 ;; + 2) echo " not ready" ; let not_ready=$not_ready+1 ;; + 3) echo " medium error" ; let medium=$medium+1 ;; + 5) echo " illegal request, general" ; let illeg_req=$illeg_req+1 ;; + 6) echo " unit attention" ; let unit_attention=$unit_attention+1 ;; + 9) echo " illegal request, invalid opcode" ; let inv_opcode=$inv_opcode+1 ;; + 11) echo " aborted command" ; let aborted_command=$aborted_command+1 ;; + 15) echo " file error with $1 " ; let file_err=$file_err+1 ;; + 20) echo " no sense" ; let other_err=$other_err+1 ;; + 21) echo " recovered error" ; let recovered=$recovered+1 ;; + 33) echo " timeout" ; let timeout=$timeout+1 ;; + 97) echo " response fails sanity" ; let sanity=$sanity+1 ;; + 98) echo " other SCSI error" ; let other_err=$other_err+1 ;; + 99) echo " other error" ; let other_err=$other_err+1 ;; + *) echo " unknown exit status for sg_inq: $res" ; let other_err=$other_err+1 ;; + esac +done + +echo "" +let total_bad_err=$file_err+$inv_opcode+$illeg_req+$medium+$aborted_command +let total_bad_err+=$other_err+$recovered+$sanity+$syntax+$timeout + +let total_allow_err=$not_ready+$unit_attention + + echo "total number of bad errors: $total_bad_err " + +if [ $total_allow_err -gt 0 ] + then + echo "total number of allowable errors: $total_allow_err " +fi + +exit $total_bad_err diff --git a/scripts/scsi_readcap b/scripts/scsi_readcap new file mode 100755 index 0000000..8f308f4 --- /dev/null +++ b/scripts/scsi_readcap @@ -0,0 +1,57 @@ +#!/bin/bash + +################################################################### +# +# Fetch READ CAPACITY information for the given SCSI device(s). +# +# This script assumes the sg3_utils package is installed. +# +################################################################## + +verbose="" +brief="" +long_opt="" + +usage() +{ + echo "Usage: scsi_readcap [-b] [-h] [-l] [-v] +" + echo " where:" + echo " -b, --brief output brief capacity data" + echo " -h, --help print usage message" + echo " -l, --long send longer SCSI READ CAPACITY (16) cdb" + echo " -v, --verbose more verbose output" + echo "" + echo "Use SCSI READ CAPACITY command to fetch the size of each " +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + b|-brief) brief="-b" ;; + h|-help) usage ; exit 0 ;; + l|-long) long_opt="--16" ;; + v|-verbose) verbose="-v" ;; + vv) verbose="-vv" ;; + vvv) verbose="-vvv" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for i +do + if [ $brief ] ; then + sg_readcap $brief $long_opt $verbose $i 2> /dev/null + else + echo "sg_readcap $brief $long_opt $verbose $i" + sg_readcap $brief $long_opt $verbose $i + fi +done diff --git a/scripts/scsi_ready b/scripts/scsi_ready new file mode 100755 index 0000000..724c2c6 --- /dev/null +++ b/scripts/scsi_ready @@ -0,0 +1,56 @@ +#!/bin/bash + +################################################ +# +# Send a TEST UNIT READY SCSI command to each given device. +# +# This script assumes the sg3_utils package is installed and uses +# the sg_turs utility.. +# +############################################### + +verbose="" +brief="" + +usage() +{ + echo "Usage: scsi_ready [-b] [-h] [-v] +" + echo " where:" + echo " -b, --brief print 'ready' or 'device not ready' only" + echo " -h, --help print usage message" + echo " -v, --verbose more verbose output" + echo "" + echo "Send SCSI TEST UNIT READY to each " +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + b|-brief) brief="1" ;; + h|-help) usage ; exit 0 ;; + v|-verbose) verbose="-v" ;; + vv) verbose="-vv" ;; + vvv) verbose="-vvv" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for i +do + if [ ! $brief ] ; then + echo "sg_turs $verbose $i" + fi + echo -n " " + if sg_turs $verbose $i ; then + echo "ready" + fi +done diff --git a/scripts/scsi_satl b/scripts/scsi_satl new file mode 100755 index 0000000..c79249b --- /dev/null +++ b/scripts/scsi_satl @@ -0,0 +1,134 @@ +#!/bin/bash +# scsi_satl +# +# Script to test compliance of SCSI commands on a SCSI to ATA +# Translation (SAT) Layer (SATL). This script was compiled using +# sat-r09.pdf found at www.t10.org . +# The scripts still seems to be valid for sat2r09.pdf . +# The vintage is SPC-3 and SPC-4 (see www.t10.org). +# +# Coverage: +# Command SATL notes +# ------------------------------------------------------- +# INQUIRY (standard) +# INQUIRY (VPD: 0) +# INQUIRY (VPD: 0x83) Device identification VPD page +# INQUIRY (VPD: 0x89) ATA Information VPD page +# REPORT LUNS SPC-3, SPC-4 (hardly mentioned in sat-r08c) +# TEST UNIT READY +# REQUEST SENSE +# SEND DIAGNOSTIC default self test +# MODE SENSE(10) draft unclear which mode pages, so ask for all +# ATA PASS THROUGH(16) send IDENTIFY DEVICE command. Assume non-packet +# device, if packet device add "-p" option +# +# This script uses utilities from sg3_utils package (version +# 1.22 or later) +# +# Douglas Gilbert 20090930 + + +log=0 +quiet=0 +verbose="" + +file_err=0 +inv_opcode=0 +illeg_req=0 +not_ready=0 +medium=0 +other_err=0 +recovered=0 +sanity=0 +syntax=0 +timeout=0 +unit_attention=0 +aborted_command=0 + +## total_err=0 + +usage() +{ + echo "Usage: scsi_satl [-h] [-L] [-q] [-v] " + echo " where: -h, --help print usage message" + echo " -L, --log append stderr to 'scsi_satl.err'" + echo " -q, --quiet suppress some output" + echo " -v, --verbose more verbose output" + echo "" + echo "Check for SCSI to ATA Translation Layer (SATL) support" +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + h|-help) usage ; exit 1 ;; + L|-log) let log=$log+1 ;; + q|-quiet) let quiet=$quiet+1 ;; + v|-verbose) verbose="-v" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for command in "sg_inq" "sg_vpd" "sg_vpd -p di" "sg_vpd -p ai" "sg_luns" \ + "sg_turs" "sg_requests -s" "sg_senddiag -t" "sg_modes -a" \ + "sg_sat_identify" +do + if [ $quiet -eq 0 ] + then echo "$command" "$1" + fi + + if [ $log -eq 0 ] + then + if [ $verbose ] + then + $command $verbose "$1" > /dev/null + else + $command "$1" > /dev/null 2>> /dev/null + fi + else + $command $verbose "$1" > /dev/null 2>> scsi_satl.err + fi + res=$? + case "$res" in + 0) ;; + 1) echo " syntax error" ; let syntax=$syntax+1 ;; + 2) echo " not ready" ; let not_ready=$not_ready+1 ;; + 3) echo " medium error" ; let medium=$medium+1 ;; + 5) echo " illegal request, general" ; let illeg_req=$illeg_req+1 ;; + 6) echo " unit attention" ; let unit_attention=$unit_attention+1 ;; + 9) echo " illegal request, invalid opcode" ; let inv_opcode=$inv_opcode+1 ;; + 11) echo " aborted command" ; let aborted_command=$aborted_command+1 ;; + 15) echo " file error with $1 " ; let file_err=$file_err+1 ;; + 20) echo " no sense" ; let other_err=$other_err+1 ;; + 21) echo " recovered error" ; let recovered=$recovered+1 ;; + 33) echo " timeout" ; let timeout=$timeout+1 ;; + 97) echo " response fails sanity" ; let sanity=$sanity+1 ;; + 98) echo " other SCSI error" ; let other_err=$other_err+1 ;; + 99) echo " other error" ; let other_err=$other_err+1 ;; + *) echo " unknown exit status for sg_inq: $res" ; let other_err=$other_err+1 ;; + esac +done + +echo "" +let total_bad_err=$file_err+$inv_opcode+$illeg_req+$medium+$aborted_command +let total_bad_err+=$other_err+$recovered+$sanity+$syntax+$timeout + +let total_allow_err=$not_ready+$unit_attention + + echo "total number of bad errors: $total_bad_err " + +if [ $total_allow_err -gt 0 ] + then + echo "total number of allowable errors: $total_allow_err " +fi + +exit $total_bad_err diff --git a/scripts/scsi_start b/scripts/scsi_start new file mode 100755 index 0000000..aec7ab9 --- /dev/null +++ b/scripts/scsi_start @@ -0,0 +1,55 @@ +#!/bin/bash + +################################################ +# +# Spin up the given SCSI disk(s). +# +# SCSI disks (or disks that understand SCSI commands) +# are assumed. By default, the immediate bit is set so the +# command should return immediately. The disk however will +# take 10 seconds or more to spin up. The '-w' option +# causes each start to wait until the disk reports that it +# has started. +# +# This script assumes the sg3_utils package is installed. +# +############################################### + +verbose="" +immediate="-i" + +usage() +{ + echo "Usage: scsi_start [-h] [-v] [-w] +" + echo " where:" + echo " -h, --help print usage message" + echo " -v, --verbose more verbose output" + echo " -w, --wait wait for each start to complete" + echo "" + echo "Send SCSI START STOP UNIT command to start each " +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + h|-help) usage ; exit 0 ;; + v|-verbose) verbose="-v" ;; + w|-wait) immediate="" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for i +do + echo "sg_start $immediate 1 $verbose $i" + sg_start $immediate 1 $verbose $i +done diff --git a/scripts/scsi_stop b/scripts/scsi_stop new file mode 100755 index 0000000..7680723 --- /dev/null +++ b/scripts/scsi_stop @@ -0,0 +1,58 @@ +#!/bin/bash + +################################################ +# +# Spin down the given SCS disk(s). +# +# SCSI disks (or disks that understand SCSI commands) +# are assumed. By default, the immediate bit is set so the +# command should return immediately. The disk however will +# take 10 seconds or more to spin down. The '-w' option +# causes each stop to wait until the disk reports that it +# has stopped. +# +# This script assumes the sg3_utils package is installed. +# +############################################### + +verbose="" +immediate="-i" + +usage() +{ + echo "Usage: scsi_stop [-h] [-v] [-w] +" + echo " where:" + echo " -h, --help print usage message" + echo " -v, --verbose more verbose output" + echo " -w, --wait wait for each stop to complete" + echo "" + echo "Send SCSI START STOP UNIT command to stop each " +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + h|-help) usage ; exit 0 ;; + v|-verbose) verbose="-v" ;; + w|-wait) immediate="" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for i +do +# Use '-r' (read-only) otherwise using a block device node +# (e.g. 'sg_start 0 /dev/sdb') can result in a change of state +# event causing the disk to spin up again immediately. + echo "sg_start -r $immediate 0 $verbose $i" + sg_start -r $immediate 0 $verbose $i +done diff --git a/scripts/scsi_temperature b/scripts/scsi_temperature new file mode 100755 index 0000000..f7d041c --- /dev/null +++ b/scripts/scsi_temperature @@ -0,0 +1,46 @@ +#!/bin/bash + +################################################################### +# +# Check the temperature of the given SCSI device(s). +# +# This script assumes the sg3_utils package is installed. +# +################################################################## + +verbose="" + +usage() +{ + echo "Usage: scsi_temperature [-h] [-v] +" + echo " where:" + echo " -h, --help print usage message" + echo " -v, --verbose more verbose output" + echo "" + echo "Use SCSI LOG SENSE command to fetch temperature of each " +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + h|-help) usage ; exit 0 ;; + v|-verbose) verbose="-v" ;; + vv) verbose="-vv" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +for i +do + echo "sg_logs -t $verbose $i" + sg_logs -t $verbose $i +done diff --git a/sg3_utils.spec b/sg3_utils.spec new file mode 100644 index 0000000..8c63074 --- /dev/null +++ b/sg3_utils.spec @@ -0,0 +1,265 @@ +Summary: Utilities for devices that use SCSI command sets +Name: sg3_utils +Version: 1.44 +# Release: 1%{?dist} +Release: 1 +License: GPL +Group: Utilities/System +Source: ftp://sg.danny.cz/sg/p/sg3_utils-%{version}.tgz +Url: http://sg.danny.cz/sg/sg3_utils.html +Provides: sg_utils +# BuildRequires: libtool +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Packager: Douglas Gilbert + +%description +Collection of Linux utilities for devices that use the SCSI command set. +Includes utilities to copy data based on "dd" syntax and semantics (called +sg_dd, sgp_dd and sgm_dd); check INQUIRY data and VPD pages (sg_inq); check +mode and log pages (sginfo, sg_modes and sg_logs); spin up and down +disks (sg_start); do self tests (sg_senddiag); and various other functions. +See the README, ChangeLog and COVERAGE files. Requires the linux kernel 2.4 +series or later. In the 2.4 series SCSI generic device names (e.g. /dev/sg0) +must be used. In the 2.6 series and later other device names may be used as +well (e.g. /dev/sda). Also some support for NVMe devices, esspecially with +sg_ses to NVMe enclosures. + +Warning: Some of these tools access the internals of your system +and the incorrect usage of them may render your system inoperable. + +%package libs +Summary: Shared library for %{name} +Group: System/Libraries + +%description libs +This package contains the shared library for %{name}. + +%package devel +Summary: Static library and header files for the sgutils library +Group: Development/C +Requires: %{name}-libs = %{version}-%{release} + +%description devel +This package contains the static %{name} library and its header files for +developing applications. + +%prep +%setup -q + +%build +%configure + +# Don't use rpath! +sed -i 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool +sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool + +%install +if [ "$RPM_BUILD_ROOT" != "/" ]; then + rm -rf $RPM_BUILD_ROOT +fi + +make install \ + DESTDIR=$RPM_BUILD_ROOT + +%clean +if [ "$RPM_BUILD_ROOT" != "/" ]; then + rm -rf $RPM_BUILD_ROOT +fi + +%files +%defattr(-,root,root) +%doc AUTHORS ChangeLog COPYING COVERAGE CREDITS INSTALL NEWS README README.sg_start +%attr(755,root,root) %{_bindir}/* +%{_mandir}/man8/* + +%files libs +%defattr(-,root,root) +%{_libdir}/*.so.* + +%files devel +%defattr(-,root,root) +%{_includedir}/scsi/*.h +%{_libdir}/*.so +%{_libdir}/*.a +%{_libdir}/*.la + +%changelog +* Wed Sep 12 2018 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.44 + +* Tue Sep 11 2018 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.43 + +* Wed Feb 17 2016 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.42 + +* Tue Apr 28 2015 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.41 + +* Mon Nov 10 2014 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.40 + +* Thu Jun 12 2014 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.39 + +* Tue Apr 01 2014 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.38 + +* Mon Oct 14 2013 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.37 + +* Fri May 31 2013 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.36 + +* Thu Jan 17 2013 - dgilbert at interlog dot com +- add sg_compare_and_write, track t10 changes + * sg3_utils-1.35 + +* Sat Oct 13 2012 - dgilbert at interlog dot com +- add sg_xcopy and sg_copy_results; track t10 changes + * sg3_utils-1.34 + +* Wed Jan 18 2012 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.33 + +* Wed Jun 22 2011 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.32 + +* Wed Feb 16 2011 - dgilbert at interlog dot com +- add sg_decode_sense; track t10 changes + * sg3_utils-1.31 + +* Fri Nov 05 2010 - dgilbert at interlog dot com +- add sg_referrals; track t10 changes + * sg3_utils-1.30 + +* Wed Mar 31 2010 - dgilbert at interlog dot com +- track t10 changes + * sg3_utils-1.29 + +* Fri Oct 02 2009 - dgilbert at interlog dot com +- add sg_get_lba_status, sg_unmap, sg_read_block_limits + * sg3_utils-1.28 + +* Sat Apr 11 2009 - dgilbert at interlog dot com +- add sg_write_same; sg_dd split; spc4r18 sync + * sg3_utils-1.27 + +* Wed Jun 25 2008 - dgilbert at interlog dot com +- add sg_sat_phy_event, sync with drafts prior to this date + * sg3_utils-1.26 + +* Tue Oct 16 2007 - dgilbert at interlog dot com +- add sg_sat_set_features, sg_stpg, sg_safte; sg_dd oflag=sparse,null + * sg3_utils-1.25 + +* Mon May 07 2007 - dgilbert at interlog dot com +- add sg_raw; sg_rtpg, sg_log, sg_inq and sg_format updates + * sg3_utils-1.24 + +* Wed Jan 31 2007 - dgilbert at interlog dot com +- add sg_read_buffer + sg_write_buffer + * sg3_utils-1.23 + +* Mon Oct 16 2006 - dgilbert at interlog dot com +- add sg_sat_identify, expand sg_format and sg_requests + * sg3_utils-1.22 + +* Thu Jul 06 2006 - dgilbert at interlog dot com +- add sg_vpd and sg_rdac, uniform exit statuses + * sg3_utils-1.21 + +* Tue Apr 18 2006 - dgilbert at interlog dot com +- sg_logs: sas port specific page decoding, sg*_dd updates + * sg3_utils-1.20 + +* Fri Jan 27 2006 - dgilbert at interlog dot com +- sg_get_config: resync features with mmc5 rev 1 + * sg3_utils-1.19 + +* Fri Nov 18 2005 - dgilbert at interlog dot com +- add sg_map26; sg_inq '-rr' option to play with hdparm + * sg3_utils-1.18 + +* Thu Sep 22 2005 - dgilbert at interlog dot com +- add ATA information VPD page to sg_inq + * sg3_utils-1.17 + +* Wed Aug 10 2005 - dgilbert at interlog dot com +- add sg_ident, sg_inq VPD page extensions + * sg3_utils-1.16 + +* Sun Jun 05 2005 - dgilbert at interlog dot com +- use O_NONBLOCK on all fds that use SG_IO ioctl + * sg3_utils-1.15 + +* Fri May 06 2005 - dgilbert at interlog dot com +- produce libsgutils (+ -devel variant) as well as sg3_utils binary rpm + * sg3_utils-1.14 + +* Sun Mar 13 2005 - dgilbert at interlog dot com +- add sg_format, sg_dd extensions + * sg3_utils-1.13 + +* Fri Jan 21 2005 - dgilbert at interlog dot com +- add sg_wr_mode, sg_rtpg + sg_reassign; sginfo sas tweaks + * sg3_utils-1.12 + +* Fri Nov 26 2004 - dgilbert at interlog dot com +- add sg_sync, sg_prevent and sg_get_config; fix sg_requests + * sg3_utils-1.11 + +* Sat Oct 30 2004 - dgilbert at interlog dot com +- fix read capacity (10+16), add sg_luns + * sg3_utils-1.10 + +* Thu Oct 21 2004 - dgilbert at interlog dot com +- sg_requests, sg_ses, sg_verify, libsgutils(sg_lib.c+sg_cmds.c), devel rpm + * sg3_utils-1.09 + +* Tue Aug 31 2004 - dgilbert at interlog dot com +- 'register+move' in sg_persist, sg_opcodes sorts, sg_write_long + * sg3_utils-1.08 + +* Thu Jul 08 2004 - dgilbert at interlog dot com +- add '-fHead' to sginfo, '-i' for sg_inq, new sg_opcodes + sg_persist + * sg3_utils-1.07 + +* Mon Apr 26 2004 - dgilbert at interlog dot com +- sg3_utils.spec for mandrake; more sginfo work, sg_scan, sg_logs + * sg3_utils-1.06 + +* Wed Nov 12 2003 - dgilbert at interlog dot com +- sg_readcap: sizes; sg_logs: double fetch; sg_map 256 sg devices; sginfo + * sg3_utils-1.05 + +* Tue May 13 2003 - dgilbert at interlog dot com +- default sg_turs '-n=' to 1, sg_logs gets '-t' for temperature, CREDITS + * sg3_utils-1.04 + +* Wed Apr 02 2003 - dgilbert at interlog dot com +- 6 byte CDBs for sg_modes, sg_start on block devs, sg_senddiag, man pages + * sg3_utils-1.03 + +* Wed Jan 01 2003 - dgilbert at interlog dot com +- interwork with block SG_IO, fix in sginfo, '-t' for sg_turs + * sg3_utils-1.02 + +* Wed Aug 14 2002 - dgilbert at interlog dot com +- raw switch in sg_inq + * sg3_utils-1.01 + +* Sun Jul 28 2002 - dgilbert at interlog dot com +- decode sg_logs pages, add dio to sgm_dd, drop "gen=1" arg, "of=/dev/null" + * sg3_utils-1.00 diff --git a/src/BSD_LICENSE b/src/BSD_LICENSE new file mode 100644 index 0000000..7f1906b --- /dev/null +++ b/src/BSD_LICENSE @@ -0,0 +1,24 @@ + +Copyright (c) 1999-2018, Douglas Gilbert +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..881cf29 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,196 @@ + +bin_PROGRAMS = \ + sg_bg_ctl sg_compare_and_write sg_decode_sense sg_format \ + sg_get_config 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_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_scan_SOURCES = + + +if OS_LINUX +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 + + +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-8 +## CC = g++ +## CC = clang +## CC = clang++ +## CC = powerpc64-linux-gnu-gcc + +# -std= 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 -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++14 +# AM_CFLAGS = -Wall -W -pedantic -std=c++1z + +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_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_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_requests_LDADD = ../lib/libsgutils2.la + +sg_referrals_LDADD = ../lib/libsgutils2.la + +sg_rep_zones_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 + +sg_unmap_LDADD = ../lib/libsgutils2.la + +sg_verify_LDADD = ../lib/libsgutils2.la + +sg_vpd_SOURCES = sg_vpd.c sg_vpd_vendor.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 diff --git a/src/sg_bg_ctl.c b/src/sg_bg_ctl.c new file mode 100644 index 0000000..2c62b1d --- /dev/null +++ b/src/sg_bg_ctl.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2016-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.09 20180625"; + +#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 k, 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]; + struct sg_pt_base * ptvp; + + if (bo_ctl) + bcCDB[2] |= (bo_ctl & 0x3) << 6; + if (bo_time) + bcCDB[3] = bo_time; + if (verbose) { + pr2serr(" %s cdb: ", cmd_name); + for (k = 0; k < (int)sizeof(bcCDB); ++k) + pr2serr("%02x ", bcCDB[k]); + pr2serr("\n"); + } + + 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, SG_NO_DATA_IN, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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 0000000..159e877 --- /dev/null +++ b/src/sg_compare_and_write.c @@ -0,0 +1,611 @@ +/* +* Copyright (c) 2012-2018, 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 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 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. + * + * This command performs a SCSI COMPARE AND WRITE. See SBC-3 at + * http://www.t10.org + * + */ + +#ifndef __sun +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include + +#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.25 20180625"; + +#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 (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 & 0x1f); + 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 k, 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]; + + 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) { + pr2serr(" Compare and write cdb: "); + for (k = 0; k < COMPARE_AND_WRITE_CDB_SIZE; ++k) + pr2serr("%02x ", cawCmd[k]); + pr2serr("\n"); + } + 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, + SG_NO_DATA_IN, sense_b, noisy, verbose, + &sense_cat); + if (-1 == ret) + 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; + 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 0000000..864ca8e --- /dev/null +++ b/src/sg_copy_results.c @@ -0,0 +1,499 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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. + + 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(" <>\n"); + return; + } + len = sg_get_unaligned_be32(rcBuff + 0); + if (len + 4 > rcBuffLen) { + pr2serr(" < %d too long for internal buffer, output " + "truncated\n", len, rcBuffLen); + } + if (len < 52) { + pr2serr(" <>\n"); + return; + } + len = sg_get_unaligned_be32(rcBuff + 0); + if (len + 4 > rcBuffLen) { + pr2serr(" < %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(" < %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 0000000..edd8f9c --- /dev/null +++ b/src/sg_dd.c @@ -0,0 +1,2282 @@ +/* A utility program for copying files. Specialised for "files" that + * represent devices that understand the SCSI command set. + * + * Copyright (C) 1999 - 2018 D. Gilbert and P. Allworth + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is a specialisation of the Unix "dd" command in which + * either the input or the output file is a scsi generic device, raw + * device, a block device or a normal file. The block size ('bs') is + * assumed to be 512 if not given. This program complains if 'ibs' or + * 'obs' are given with a value that differs from 'bs' (or the default 512). + * If 'if' is not given or 'if=-' then stdin is assumed. If 'of' is + * not given or 'of=-' then stdout assumed. + * + * A non-standard argument "bpt" (blocks per transfer) is added to control + * the maximum number of blocks in each transfer. The default value is 128. + * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16 KiB + * in this case) is transferred to or from the sg device in a single SCSI + * command. 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.4, 2.6, 3 and 4 series. + */ + +#define _XOPEN_SOURCE 600 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#include +#include +#ifndef major +#include +#endif +#include /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */ +#include /* for BLKSSZGET and friends */ + +#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 = "6.04 20180811"; + + +#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 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 DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */ + +#ifndef RAW_MAJOR +#define RAW_MAJOR 255 /*unlikey value */ +#endif + +#define SG_LIB_FLOCK_ERR 90 + +#define FT_OTHER 1 /* filetype is probably normal */ +#define FT_SG 2 /* filetype is sg char device or supports + SG_IO ioctl */ +#define FT_RAW 4 /* filetype is raw char device */ +#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */ +#define FT_ST 16 /* filetype is st char device (tape) */ +#define FT_BLOCK 32 /* filetype is block device */ +#define FT_FIFO 64 /* filetype is a fifo (name pipe) */ +#define FT_ERROR 128 /* couldn't "stat" file */ + +#define DEV_NULL_MINOR_NUM 3 + +/* 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 + +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 read_longs = 0; +static int num_retries = 0; +static int dry_run = 0; + +static bool do_time = false; +static bool start_tm_valid = false; +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 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 const char * proc_allow_dio = "/proc/scsi/sg/allow_dio"; + +struct flags_t { + bool append; + bool dio; + bool direct; + bool dpo; + bool dsync; + bool excl; + bool flock; + bool fua; + bool sgio; + bool sparse; + int cdbsz; + 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 out\n", str, out_full - out_partial, + out_partial); + 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 (iflag.coe || oflag.coe) { + pr2serr("%s%d unrecovered errors\n", str, unrecovered_errs); + pr2serr("%s%d read_longs fetched part of unrecovered read errors\n", + str, read_longs); + } else if (unrecovered_errs) + pr2serr("%s%d unrecovered error(s)\n", str, unrecovered_errs); +} + + +static void +interrupt_handler(int sig) +{ + struct sigaction sigact; + + sigact.sa_handler = SIG_DFL; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(sig, &sigact, NULL); + 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 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; + } 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_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] [count=COUNT] [ibs=BS] [if=IFILE] " + "[iflag=FLAGS]\n" + " [obs=BS] [of=OFILE] [oflag=FLAGS] " + "[seek=SEEK] [skip=SKIP]\n" + " [--dry-run] [--help] [--verbose] [--version]\n\n" + " [blk_sgio=0|1] [bpt=BPT] [cdbsz=6|10|12|16] " + "[coe=0|1|2|3]\n" + " [coe_limit=CL] [dio=0|1] [odir=0|1] " + "[of2=OFILE2] [retries=RETR]\n" + " [sync=0|1] [time=0|1] [verbose=VERB]\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 block size (default is 512)\n"); + pr2serr(" cdbsz size of SCSI READ or WRITE cdb (default is " + "10)\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" + " count number of blocks to copy (def: device size)\n" + " dio for direct IO, 1->attempt, 0->indirect IO (def)\n" + " ibs input block size (if given must be same as " + "'bs=')\n" + " if file or device to read from (def: stdin)\n" + " iflag comma separated list from: [coe,dio,direct," + "dpo,dsync,excl,\n" + " flock,fua,nocache,null,sgio]\n" + " obs output 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,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" + " verbose 0->quiet(def), 1->some noise, 2->more noise, " + "etc\n" + " --dry-run do preparation but bypass copy (or read)\n" + " --help print out this usage message then exit\n" + " --verbose same as 'verbose=1', can be used multiple " + "times\n" + " --version print version information then exit\n\n" + "copy from IFILE to OFILE, similar to dd command; " + "specialized for SCSI devices\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 "], " + "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 or ). */ +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 + "], 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 + "], 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(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; + 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; + 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(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; +} + + +/* 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; + int res, k, slen; + const uint8_t * sbp; + uint8_t rdCmd[MAX_SCSI_CDBSZ]; + uint8_t senseBuff[SENSE_BUFF_LEN]; + struct sg_io_hdr io_hdr; + + if (sg_build_scsi_cdb(rdCmd, ifp->cdbsz, blocks, from_block, false, + ifp->fua, ifp->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 = 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 = DEF_TIMEOUT; + io_hdr.pack_id = (int)from_block; + if (diop && *diop) + io_hdr.flags |= SG_FLAG_DIRECT_IO; + + if (verbose > 2) { + pr2serr(" read cdb: "); + for (k = 0; k < ifp->cdbsz; ++k) + pr2serr("%02x ", rdCmd[k]); + pr2serr("\n"); + } + while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) && + ((EINTR == errno) || (EAGAIN == 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: + 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 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); + 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; +} + + +/* 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 ? ret : -1; +} + + +/* 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 */ +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, k; + uint64_t io_addr = 0; + uint8_t wrCmd[MAX_SCSI_CDBSZ]; + uint8_t senseBuff[SENSE_BUFF_LEN]; + struct sg_io_hdr io_hdr; + + if (sg_build_scsi_cdb(wrCmd, ofp->cdbsz, blocks, to_block, true, ofp->fua, + ofp->dpo)) { + 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 = DEF_TIMEOUT; + io_hdr.pack_id = (int)to_block; + if (diop && *diop) + io_hdr.flags |= SG_FLAG_DIRECT_IO; + + if (verbose > 2) { + pr2serr(" write cdb: "); + for (k = 0; k < ofp->cdbsz; ++k) + pr2serr("%02x ", wrCmd[k]); + pr2serr("\n"); + } + while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) && + ((EINTR == errno) || (EAGAIN == errno))) + ; + if (res < 0) { + if (ENOMEM == errno) + return -2; + 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: + 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("writing", &io_hdr, true); + } else { + pr2serr("Recovered error: [no info] writing to block=0x%" PRIx64 + ", num=%d\n", to_block, blocks); + sg_chk_n_print3("writing", &io_hdr, verbose > 1); + } + break; + case SG_LIB_CAT_ABORTED_COMMAND: + case SG_LIB_CAT_UNIT_ATTENTION: + sg_chk_n_print3("writing", &io_hdr, verbose > 1); + 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("writing", &io_hdr, verbose > 1); + ++unrecovered_errs; + if (ofp->coe) { + pr2serr(">> ignored errors for out blk=%" PRId64 " for %d " + "bytes\n", to_block, bs * blocks); + return 0; /* 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 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 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; + 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, "fua")) + fp->fua = true; + else if (0 == strcmp(cp, "nocache")) + ++fp->nocache; + else if (0 == strcmp(cp, "null")) + ; + 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 + if (0 == strcmp(cp, "fdatasync")) + ++ofp->fdatasync; + else if (0 == strcmp(cp, "fsync")) + ++ofp->fsync; +#endif + 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 ddpt so ignore */ + else if (0 == strcmp(cp, "null")) + ; +#if 0 + else if (0 == strcmp(cp, "sparing")) + ++ofp->sparing; +#endif + else if (0 == strcmp(cp, "sparse")) + ofp->sparse = true; + else if (0 == strcmp(cp, "sync")) + ; /* dd(susv4): pad errored block(s) with zeros but ddpt does + * that by default. Typical dd use: 'conv=noerror,sync' */ +#if 0 + else if (0 == strcmp(cp, "trunc")) + ++ofp->trunc; +#endif + 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, 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 { + 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) { + 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: + return -SG_LIB_FILE_ERROR; +other_err: + 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) +{ + int outfd, 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)); + + 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_DEV_NULL & *out_typep) + outfd = -1; /* don't bother opening */ + else { + if (! (FT_RAW & *out_typep)) { + flags = O_WRONLY | 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; + } + } else { + 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; + } + } + if (vb) + pr2serr(" %s output, flags=0x%x\n", + ((O_CREAT & flags) ? "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) { + res = flock(outfd, LOCK_EX | LOCK_NB); + if (res < 0) { + close(outfd); + snprintf(ebuff, EBUFF_SZ, ME "flock(LOCK_EX | LOCK_NB) on %s " + "failed", outf); + perror(ebuff); + return -SG_LIB_FLOCK_ERR; + } + } + return outfd; + +file_err: + return -SG_LIB_FILE_ERROR; +other_err: + 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; +} + + +int +main(int argc, char * argv[]) +{ + bool bpt_given = false; + bool cdbsz_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 out2_off = 0; + int64_t in_num_sect = -1; + int64_t out_num_sect = -1; + char * key; + char * buf; + uint8_t * wrkBuff; + 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); + 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")) { + iflag.cdbsz = sg_get_num(buf); + oflag.cdbsz = iflag.cdbsz; + cdbsz_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 (-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, "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); + else if (strcmp(key, "if") == 0) { + if ('\0' != inf[0]) { + pr2serr("Second IFILE argument??\n"); + return SG_LIB_SYNTAX_ERROR; + } else + strncpy(inf, buf, INOUTF_SZ - 1); + } 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); + 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 + strncpy(outf, buf, INOUTF_SZ - 1); + } else if (strcmp(key, "of2") == 0) { + if ('\0' != out2f[0]) { + pr2serr("Second OFILE2 argument??\n"); + return SG_LIB_CONTRADICT; + } else + strncpy(out2f, buf, INOUTF_SZ - 1); + } 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 (-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, "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, '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 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, "--verb", 6)) { + verbose_given = true; + ++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 "version: %s\n", version_str); + return 0; + } + 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' (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) { + pr2serr("bpt must be greater than 0\n"); + 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 (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 (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: 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("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: 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("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 { + while (((res = read(infd, wrkPos, blocks * blk_sz)) < 0) && + ((EINTR == errno) || (EAGAIN == 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))) + ; + 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; + out2_off += 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 = 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) + 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 write to %d blocks per loop\n", + 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 write, lba=0x%" PRIx64 "\n", + (uint64_t)seek); + --retries_tmp; + ++num_retries; + if (unrecovered_errs > 0) + --unrecovered_errs; + } else + break; + first = false; + } + 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))) + ; + 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; + } /* 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))) + ; + 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); + + free(wrkBuff); + if (free_zeros_buff) + free(free_zeros_buff); + if (STDIN_FILENO != infd) + close(infd); + if (! ((STDOUT_FILENO == outfd) || (FT_DEV_NULL & out_type))) + 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(proc_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + pr2serr(">>> %s set to '0' but should be set to '1' for " + "direct IO\n", proc_allow_dio); + } + close(fd); + } + } + if (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 0000000..6bb08ec --- /dev/null +++ b/src/sg_decode_sense.c @@ -0,0 +1,577 @@ +/* + * Copyright (c) 2010-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.19 20180714"; + +#define MAX_SENSE_LEN 1024 /* 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'}, + {"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 do_hex; + bool no_space; + bool do_status; + bool verbose_given; + bool version_given; + bool err_given; + bool file_given; + const char * fname; + int es_val; + int sense_len; + int sstatus; + int verbose; + const char * wfname; + const char * no_space_str; + uint8_t sense[MAX_SENSE_LEN + 4]; +}; + +static char concat_buff[1024]; + + +static void +usage() +{ + pr2serr("Usage: sg_decode_sense [--binary=FN] [--cdb] [--err=ES] " + "[--file=FN]\n" + " [--help] [--hex] [--nospace] [--status=SS] " + "[--verbose]\n" + " [--version] [--write=WFN] H1 H2 H3 ...\n" + " where:\n" + " --binary=FN|-b FN FN is a file name to read sense " + "data in\n" + " binary from. If FN 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=FN|-f FN FN 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" + " --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:hHns:vVw:", long_options, NULL); + if (c == -1) + break; + + switch (c) { + case 'b': + if (op->fname) { + pr2serr("expect only one '--binary=FN' or '--file=FN' " + "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=FN' or '--file=FN' " + "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->do_hex = true; + break; + case 'n': + op->no_space = 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; +} + +/* Read ASCII hex bytes from fname (a file named '-' taken as stdin). + * There should be either one entry per line or a comma, space or tab + * separated list of bytes. If no_space is set then a string of ACSII hex + * digits is expected, 2 per byte. Everything from and including a '#' + * on a line is ignored. Returns 0 if ok, sg3_utils code value. */ +static int +f2hex_arr(const char * fname, bool no_space, uint8_t * mp_arr, + int * mp_arr_len, int max_arr_len) +{ + bool split_line; + int fn_len, in_len, k, j, m, err; + int off = 0; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return SG_LIB_LOGIC_ERROR; + fn_len = strlen(fname); + if (0 == fn_len) + return SG_LIB_SYNTAX_ERROR; + if ((1 == fn_len) && ('-' == fname[0])) /* read from stdin */ + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + err = errno; + pr2serr("Unable to open %s for reading: %s\n", fname, + safe_strerror(err)); + return sg_convert_errno(err); + } + } + + carry_over[0] = 0; + for (j = 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(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); + 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; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, j + 1, + m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + 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) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + if (stdin != fp) + fclose(fp); + return 0; +bad: + if (stdin != fp) + fclose(fp); + return SG_LIB_SYNTAX_ERROR; +} + +static void +write2wfn(FILE * fp, struct opts_t * op) +{ + int k, n; + size_t s; + char b[128]; + + if (op->do_hex) { + 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); + } + } 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); + } +} + + +int +main(int argc, char *argv[]) +{ + int k, err; + int ret = 0; + unsigned int ui; + size_t s; + struct opts_t * op; + FILE * fp = NULL; + const char * cp; + char b[2048]; + struct opts_t opts; + + op = &opts; + memset(op, 0, sizeof(opts)); + memset(b, 0, sizeof(b)); + 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); + return 0; + } + if (ret != 0) { + usage(); + return ret; + } else if (op->do_help) { + usage(); + return 0; + } + + 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, sizeof(b) - 1, 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(cp[k]) && isxdigit(cp[k + 1]); k += 2) { + if (1 != sscanf(cp + k, "%2x", &ui)) { + pr2serr("bad no_space hex string: %s\n", cp); + return SG_LIB_SYNTAX_ERROR; + } + op->sense[op->sense_len++] = (uint8_t)ui; + } + } + + if ((0 == op->sense_len) && (! op->do_binary) && (! op->file_given)) { + if (op->do_status) + return 0; + pr2serr(">> Need sense data on the command line or in a file\n\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + 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"); + return SG_LIB_CONTRADICT; + } + if (op->do_binary && op->file_given) { + pr2serr(">> Either a binary file or a ASCII hexadecimal, file not " + "both\n\n"); + return SG_LIB_CONTRADICT; + } + + 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)); + return sg_convert_errno(err); + } + s = fread(op->sense, 1, MAX_SENSE_LEN, fp); + fclose(fp); + if (0 == s) { + pr2serr("read nothing from file: %s\n", op->fname); + return SG_LIB_SYNTAX_ERROR; + } + op->sense_len = s; + } else if (op->file_given) { + ret = f2hex_arr(op->fname, op->no_space, op->sense, &op->sense_len, + MAX_SENSE_LEN); + if (ret) { + pr2serr("unable to decode ASCII hex from file: %s\n", op->fname); + return ret; + } + } + + if (op->sense_len) { + if (op->wfname) { + if ((fp = fopen(op->wfname, "w"))) { + write2wfn(fp, op); + fclose(fp); + } else { + err =errno; + perror("open"); + pr2serr("trying to write to %s\n", op->wfname); + ret = sg_convert_errno(err); + } + } + 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, sizeof(b), b); + } else + sg_get_sense_str(NULL, op->sense, op->sense_len, + op->verbose, sizeof(b) - 1, b); + printf("%s\n", b); + } +fini: + return ret; +} diff --git a/src/sg_emc_trespass.c b/src/sg_emc_trespass.c new file mode 100644 index 0000000..fc3219a --- /dev/null +++ b/src/sg_emc_trespass.c @@ -0,0 +1,174 @@ +/* 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 + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 0000000..af1a033 --- /dev/null +++ b/src/sg_format.c @@ -0,0 +1,1501 @@ +/* + * 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-2018 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. + * + * See http://www.t10.org for relevant standards and drafts. The most recent + * draft is SBC-4 revision 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.55 20180830"; + + +#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 + +#if defined(MSC_VER) || defined(__MINGW32__) +#define HAVE_MS_SLEEP +#endif + +#ifdef HAVE_MS_SLEEP +#include +#define sleep_for(seconds) Sleep( (seconds) * 1000) +#else +#define sleep_for(seconds) sleep(seconds) +#endif + +/* FORMAT UNIT (SBC) and FORMAT MEDIUM (SSC) share the same opcode */ +#define SG_FORMAT_MEDIUM_CMD 0x4 +#define SG_FORMAT_MEDIUM_CMDLEN 6 +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ + +struct opts_t { + bool cmplst; /* -C value */ + bool cmplst_given; + bool dcrt; /* -D */ + bool dry_run; /* -d */ + bool early; /* -e */ + bool fwait; /* -w (negate for 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 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 lblk_sz; /* -s value */ + int ffmt; /* -t value; fast_format if > 0 */ + int fmtpinfo; + int format; /* -F */ + int mode_page; /* -M value */ + int pfu; /* -P value */ + int pie; /* -q value */ + int sec_init; /* -S */ + int tape; /* -T , 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'}, + {"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'}, + {"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 void +usage() +{ + printf("Usage:\n" + " sg_format [--cmplst=0|1] [--count=COUNT] [--dcrt] " + "[--dry-run] [--early]\n" + " [--ffmt=FFMT] [--fmtpinfo=FPI] [--format] " + "[--help] [--ip-def]\n" + " [--long] [--mode=MP] [--pfu=PFU] [--pie=PIE] " + "[--pinfo]\n" + " [--poll=PT] [--quick] [--resize] [--rto_req] " + "[--security]\n" + " [--six] [--size=LB_SZ] [--tape=FM] " + "[--timeout=SECS] [--verbose]\n" + " [--verify] [--version] [--wait] 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" + " --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" + " --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))\n"); + printf(" --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 command waits until format " + "operation completes\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].\n"); + printf("WARNING: This utility will destroy all the data on " + "DEVICE when '--format'\n\t or '--tape' is given. Check that " + "you have specified the correct\n\t 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 k, 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]; + 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) { + pr2serr(" Format medium cdb: "); + for (k = 0; k < SG_FORMAT_MEDIUM_CMDLEN; ++k) + pr2serr("%02x ", fm_cdb[k]); + pr2serr("\n"); + } + + 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, "format medium", res, transfer_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + 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; +} + +/* 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_hdr, longlist, ip_desc; + 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 (op->dcrt) + param[1] |= 0xa0; /* FOV=1, DCRT=1 */ + 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_hdr = (immed || op->cmplst || op->dcrt || ip_desc || + (op->pfu > 0) || (op->pie > 0)); + param_sz = need_hdr ? + (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_hdr) { + pr2serr(" FU would have received parameter " + "list: "); + hex2stderr(param, max_param_sz, -1); + } else + pr2serr(" FU would not have received a " + "parameter list\n"); + pr2serr(" FU cdb fields: fmtpinfo=0x%x, " + "longlist=%d, fmtdata=%d, cmplst=%d, " + "ffmt=%d [timeout=%d secs]\n", + op->fmtpinfo, longlist, need_hdr, op->cmplst, + op->ffmt, tmout); + } + } else + res = sg_ll_format_unit_v2(fd, op->fmtpinfo, longlist, + need_hdr, 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("Format unit command: %s\n", b); + return res; + } + if (! immed) + return 0; + + if (! op->dry_run) + printf("\nFormat unit has started\n"); + + if (op->early) { + if (immed) + printf("Format continuing,\n request sense or " + "test unit ready can be used to monitor " + "progress\n"); + 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(;;) { + sleep_for(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("Format in progress, %d.%02d%% done\n", + pr, rem); + } else + 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(;;) { + sleep_for(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("Format in progress, %d.%02d%% done\n", + pr, rem); + } else + 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) +{ + int res, progress, pr, rem, resp_len, tmout; + int vb = op->verbose; + bool immed = ! op->fwait; + 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 UNIT " + "command\n"); + } 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("Format medium command: %s\n", b); + return res; + } + if (! immed) + return 0; + + if (! op->dry_run) + printf("\nFormat medium has started\n"); + if (op->early) { + if (immed) + printf("Format continuing,\n request sense or " + "test unit ready can be used to monitor " + "progress\n"); + return 0; + } + + if (op->dry_run) { + printf("No point in polling for progress, so exit\n"); + return 0; + } + if (! op->poll_type) { + for(;;) { + sleep_for(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("Format in progress, %d.%02d%% done\n", + pr, rem); + } else + 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(;;) { + sleep_for(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("Format in progress, %d.%02d%% done\n", + pr, rem); + } else + break; + } + if (free_reqSense) + free(free_reqSense); + } + printf("FORMAT MEDIUM 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] & 0x1f; + 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]) { + if (has_di) { + if (op->verbose) + pr2serr("VPD_SUPPORTED_VPDS " + "dis-ordered\n"); + goto out; + } + ++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 " + "<>\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 <>\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; + + 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, + "c:C:dDef:FhIlm:M:pP:q:QrRs:St:T:vVwx:y6", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + 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 = true; + break; + case 'e': + op->early = true; + 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 (op->format && (op->tape >= 0)) { + pr2serr("Cannot choose both '--format' and '--tape='; disk " + "or tape, choose one only\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) + goto out; + pdt = 0x1f & inq_resp[0]; + if (op->format) { + if ((PDT_DISK != pdt) && (PDT_OPTICAL != pdt) && + (PDT_RBC != pdt)) { + pr2serr("This format is only defined for disks " + "(using SBC-2 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; + } + + ret = fetch_block_desc(fd, dbuff, &calc_len, &bd_lb_sz, op); + if (ret) + 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 1 + if (op->quick) + goto skip_f_unit_reconsider; + printf("\nA FORMAT UNIT will commence in 15 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", + op->device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nA FORMAT UNIT will commence in 10 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", + op->device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nA FORMAT UNIT will commence in 5 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", + op->device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); +skip_f_unit_reconsider: + 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"); + } +#else + pr2serr("FORMAT UNIT ignored, testing\n"); +#endif + } + goto out; + +format_med: + if (! op->poll_type_given) /* SSC-5 specifies REQUEST SENSE polling */ + op->poll_type = true; + if (op->quick) + goto skip_f_med_reconsider; + printf("\nA FORMAT MEDIUM will commence in 15 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", + op->device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nA FORMAT MEDIUM will commence in 10 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", + op->device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nA FORMAT MEDIUM will commence in 5 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", + op->device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); +skip_f_med_reconsider: + 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"); + } + +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 0000000..28af720 --- /dev/null +++ b/src/sg_get_config.c @@ -0,0 +1,1143 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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("<<>>\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_lba_status.c b/src/sg_get_lba_status.c new file mode 100644 index 0000000..91c7a96 --- /dev/null +++ b/src/sg_get_lba_status.c @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2009-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.18 20180812"; /* sbc4r15 */ + +#ifndef UINT32_MAX +#define UINT32_MAX ((uint32_t)-1) +#endif + +#define MAX_GLBAS_BUFF_LEN (1024 * 1024) +#define DEF_GLBAS_BUFF_LEN 24 + +static uint8_t glbasBuff[DEF_GLBAS_BUFF_LEN]; + + +static struct option long_options[] = { + {"16", no_argument, 0, 'S'}, + {"32", no_argument, 0, 'T'}, + {"brief", 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'}, + {"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][--brief] " + "[--element-id=EI]\n" + " [--help] [--hex] " + "[--lba=LBA] [--maxlen=LEN]\n" + " [--raw] [--readonly] " + "[--report-type=RT]\n" + " [--scan-len=SL] [--verbose] " + "[--version] DEVICE\n" + " where:\n" + " --16|-S use GET LBA STATUS(16) cdb (def)\n" + " --32|-T use GET LBA STATUS(32) cdb\n" + " --brief|-b a descriptor per line:\n" + " \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" + " --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\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\n(SBC-3 and SBC-4)\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; +} + + +int +main(int argc, char * argv[]) +{ + bool do_16 = false; + bool do_32 = false; + bool do_raw = false; + bool o_readonly = false; + bool verbose_given = false; + bool version_given = false; + int sg_fd, k, j, res, c, rlen, num_descs, completion_cond; + 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 uint8_t * bp; + uint8_t * glbasBuffp = glbasBuff; + uint8_t * free_glbasBuffp = NULL; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "be:hHl:m:rRs:St:TvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'b': + ++do_brief; + 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 '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; + } + 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 (NULL == device_name) { + pr2serr("missing device name!\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + 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 (do_raw) { + if (sg_set_binary_mode(STDOUT_FILENO) < 0) { + perror("sg_set_binary_mode"); + ret = SG_LIB_FILE_ERROR; + goto free_buff; + } + } + 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 free_buff; + } + + 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, element_id, scan_len, rt, + glbasBuffp, maxlen, true, verbose); + + ret = res; + if (0 == res) { + /* 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 the_end; + } + if (do_hex) { + hex2stdout(glbasBuffp, 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; + + if (do_brief > 1) { + if (rlen < 24) { + pr2serr("Need maxlen and response length to be at least 24, " + "have %d bytes\n", rlen); + ret = SG_LIB_CAT_OTHER; + goto the_end; + } + 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 the_end; + } + 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 the_end; + } + printf("%d\n", res); + goto the_end; + } + + if (rlen < 24) { + printf("No complete LBA status descriptors available\n"); + goto the_end; + } + num_descs = (rlen - 8) / 16; + completion_cond = (*(glbasBuffp + 7) >> 1) & 7; /* added sbc4r14 */ + if (do_brief) + printf("Completion condition=%d\n", completion_cond); + else { + switch (completion_cond) { + case 0: + printf("No indication of the completion condition\n"); + break; + case 1: + printf("Command completed due to meeting allocation length " + "(--maxlen=LEN (def 24))\n"); + break; + case 2: + printf("Command completed due to meeting scan length " + "(--scan-len=SL)\n"); + break; + case 3: + printf("Command completed due to meeting capacity of " + "medium\n"); + break; + default: + printf("Command completion is reserved [%d]\n", + completion_cond); + break; + } + } + printf("RTP=%d\n", *(glbasBuffp + 7) & 0x1); /* added sbc4r12 */ + if (verbose) + pr2serr("%d complete LBA status descriptors found\n", num_descs); + 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 (do_brief) { + printf("0x"); + for (j = 0; j < 8; ++j) + printf("%02x", bp[j]); + printf(" 0x%x %d\n", (unsigned int)d_blocks, res); + } else { + printf("descriptor LBA: 0x"); + for (j = 0; j < 8; ++j) + printf("%02x", bp[j]); + printf(" blocks: %u", (unsigned int)d_blocks); + switch (res) { + case 0: + printf(" mapped (or unknown)"); + break; + case 1: + printf(" deallocated"); + break; + case 2: + printf(" anchored"); + break; + case 3: + printf(" mapped"); /* sbc4r12 */ + break; + case 4: + printf(" unknown"); /* sbc4r12 */ + break; + default: + printf(" Provisioning status: %d", res); + break; + } + switch (add_status) { + case 0: + printf("\n"); + break; + case 1: + printf(" [may return unrecovered errors]\n"); + break; + default: + printf(" [add_status: 0x%x]\n", (unsigned int)add_status); + break; + } + } + } + if ((num_descs * 16) + 8 < rlen) + pr2serr("incomplete trailing LBA status descriptors found\n"); + } else 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 { + char b[80]; + + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Get LBA Status command: %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_glbasBuffp) + free(free_glbasBuffp); + if (0 == verbose) { + if (! sg_if_can2stderr("sg_get_lba_status 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_ident.c b/src/sg_ident.c new file mode 100644 index 0000000..f75fb11 --- /dev/null +++ b/src/sg_ident.c @@ -0,0 +1,316 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 0000000..b32ab87 --- /dev/null +++ b/src/sg_inq.c @@ -0,0 +1,4851 @@ +/* A utility program originally written for the Linux OS SCSI subsystem. + * Copyright (C) 2000-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. + * + * This program outputs information provided by a SCSI INQUIRY command. + * It is mainly based on the SCSI SPC-5 document at http://www.t10.org . + * + * Acknowledgment: + * - Martin Schwenke added the raw switch and + * other improvements [20020814] + * - Lars Marowsky-Bree contributed Unit Path Report + * VPD page decoding for EMC CLARiiON devices [20041016] + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include + +#ifdef SG_LIB_LINUX +#include +#include +#include +#include +#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 + +static const char * version_str = "1.98 20180828"; /* SPC-5 rev 19 */ + +/* 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. + */ + + +/* Following VPD pages are in ascending page number order */ +#define VPD_SUPPORTED_VPDS 0x0 +#define VPD_UNIT_SERIAL_NUM 0x80 +#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 +#define VPD_PROTO_LU 0x90 +#define VPD_PROTO_PORT 0x91 +#define VPD_SCSI_FEATURE_SETS 0x92 /* spc5r11 */ +#define VPD_BLOCK_LIMITS 0xb0 +#define VPD_BLOCK_DEV_CHARS 0xb1 +#define VPD_MAN_ASS_SN 0xb1 +#define VPD_LB_PROVISIONING 0xb2 +#define VPD_REFERRALS 0xb3 +#define VPD_SUP_BLOCK_LENS 0xb4 /* sbc4r01 */ +#define VPD_BLOCK_DEV_C_EXTENS 0xb5 /* sbc4r02 */ +#define VPD_ZBC_DEV_CHARS 0xb6 /* zbc-r01b */ +#define VPD_BLOCK_LIMITS_EXT 0xb7 /* sbc4r08 */ + +#ifndef SG_NVME_VPD_NICR +#define SG_NVME_VPD_NICR 0xde +#endif + +#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 */ + + +static 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, int do_hex, int verbose); + +#if defined(SG_LIB_LINUX) && defined(SG_SCSI_STRINGS) && \ + defined(HDIO_GET_IDENTITY) +static int try_ata_identify(int ata_fd, int do_hex, int do_raw, + int verbose); +struct opts_t; +static void prepare_ata_identify(const struct opts_t * op, int inhex_len); +#endif + +/* This structure is a duplicate of one of the same name in sg_vpd_vendor.c . + Take care that both have the same fields (and types). */ +struct svpd_values_name_t { + int value; + int subvalue; + int pdt; /* peripheral device type id, -1 is the default */ + /* (all or not applicable) value */ + int vendor; /* vendor flag */ + const char * acron; + const char * name; +}; + +/* Note that this table is sorted by acronym */ +static struct svpd_values_name_t vpd_pg[] = { + {VPD_ATA_INFO, 0, -1, 0, "ai", "ATA information (SAT)"}, + {VPD_BLOCK_DEV_CHARS, 0, 0, 0, "bdc", + "Block device characteristics (SBC)"}, + {VPD_BLOCK_DEV_C_EXTENS, 0, 0, 0, "bdce", "Block device characteristics " + "extension (SBC)"}, + {VPD_BLOCK_LIMITS, 0, 0, 0, "bl", "Block limits (SBC)"}, + {VPD_BLOCK_LIMITS_EXT, 0, 0, 0, "ble", "Block limits extension (SBC)"}, + {VPD_DEVICE_ID, 0, -1, 0, "di", "Device identification"}, +#if 0 /* following found in sg_vpd */ + {VPD_DEVICE_ID, VPD_DI_SEL_AS_IS, -1, 0, "di_asis", "Like 'di' " + "but designators ordered as found"}, + {VPD_DEVICE_ID, VPD_DI_SEL_LU, -1, 0, "di_lu", "Device identification, " + "lu only"}, + {VPD_DEVICE_ID, VPD_DI_SEL_TPORT, -1, 0, "di_port", "Device " + "identification, target port only"}, + {VPD_DEVICE_ID, VPD_DI_SEL_TARGET, -1, 0, "di_target", "Device " + "identification, target device only"}, +#endif + {VPD_EXT_INQ, 0, -1, 0, "ei", "Extended inquiry data"}, + {VPD_LB_PROVISIONING, 0, 0, 0, "lbpv", "Logical block provisioning " + "(SBC)"}, + {VPD_MAN_NET_ADDR, 0, -1, 0, "mna", "Management network addresses"}, + {VPD_MODE_PG_POLICY, 0, -1, 0, "mpp", "Mode page policy"}, + {VPD_POWER_CONDITION, 0, -1, 0, "po", "Power condition"}, + {VPD_POWER_CONSUMPTION, 0, -1, 0, "psm", "Power consumption"}, + {VPD_PROTO_LU, 0, 0x0, 0, "pslu", "Protocol-specific logical unit " + "information"}, + {VPD_PROTO_PORT, 0, 0x0, 0, "pspo", "Protocol-specific port information"}, + {VPD_REFERRALS, 0, 0, 0, "ref", "Referrals (SBC)"}, + {VPD_SUP_BLOCK_LENS, 0, 0, 0, "sbl", "Supported block lengths and " + "protection types (SBC)"}, + {VPD_SCSI_FEATURE_SETS, 0, -1, 0, "sfs", "SCSI Feature sets"}, + {VPD_SOFTW_INF_ID, 0, -1, 0, "sii", "Software interface identification"}, + {VPD_NOPE_WANT_STD_INQ, 0, -1, 0, "sinq", "Standard inquiry response"}, + {VPD_UNIT_SERIAL_NUM, 0, -1, 0, "sn", "Unit serial number"}, + {VPD_SCSI_PORTS, 0, -1, 0, "sp", "SCSI ports"}, + {VPD_SUPPORTED_VPDS, 0, -1, 0, "sv", "Supported VPD pages"}, + {VPD_3PARTY_COPY, 0, -1, 0, "tpc", "Third party copy"}, + {VPD_ZBC_DEV_CHARS, 0, -1, 0, "zbdc", "Zoned block device " + "characteristics"}, + /* Following are vendor specific */ + {SG_NVME_VPD_NICR, 0, -1, 1, "nicr", + "NVMe Identify Controller Response (sg3_utils)"}, + {VPD_RDAC_VAC, 0, -1, 1, "rdac_vac", "RDAC volume access control (RDAC)"}, + {VPD_RDAC_VERS, 0, -1, 1, "rdac_vers", "RDAC software version (RDAC)"}, + {VPD_UPR_EMC, 0, -1, 1, "upr", "Unit path report (EMC)"}, + {0, 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'}, + {"vendor", no_argument, 0, 's'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"vpd", no_argument, 0, 'e'}, + {0, 0, 0, 0}, +}; + +struct opts_t { + bool do_ata; + bool do_decode; + bool do_descriptors; + bool do_export; + bool do_force; + bool do_only; /* --only after standard inq don't fetch VPD page 0x80 */ + bool verbose_given; + bool version_given; + bool do_vpd; + bool page_given; + bool possible_nvme; + int do_block; + int do_cmddt; + int do_help; + int do_hex; + int do_long; + int do_raw; + int do_vendor; + int verbose; + int resp_len; + int page_num; + int page_pdt; + int num_pages; + const char * page_arg; + const char * device_name; + const char * inhex_fn; +#ifdef SG_SCSI_STRINGS + bool opt_new; +#endif +}; + + +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] " + "[--len=LEN]\n" + " [--long] [--maxlen=LEN] [--only] [--page=PG] " + "[--raw]\n" + " [--vendor] [--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] " + "[--len=LEN]\n" + " [--long] [--maxlen=LEN] [--only] [--page=PG] " + "[--raw]\n" + " [--verbose] [--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__= 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" + " --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" + " --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" + "Performs a SCSI INQUIRY command on DEVICE or decodes INQUIRY " + "response\nheld in file FN. If no options given then does a " + "'standard' INQUIRY.\nCan list VPD pages with '--vpd' or " + "'--page=PG' option. The sg_vpd\nand sdparm utilities decode " + "more VPD pages than this utility.\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] [-l=LEN] [-L] [-m] [-M] " + "[-o]\n" + " [-p=VPD_PG] [-P] [-r] [-s] [-u] [-U] [-v] [-V] " + "[-x]\n" + " [-36] [-?] DEVICE\n" + " where:\n" + " -a decode ATA information VPD page (0x89)\n" + " -A treat 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" + " -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__= 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 does a standard SCSI INQUIRY\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:l:Lm:NoOp:rsuvVx", + long_options, &option_index); +#else + c = getopt_long(argc, argv, "B:cdeEfhHiI:l:Lm:op:rsuvVx", + long_options, &option_index); +#endif /* SG_SCSI_STRINGS */ +#else /* SG_LIB_LINUX */ +#ifdef SG_SCSI_STRINGS + c = getopt_long(argc, argv, "B:cdeEfhHiI:l:Lm:NoOp:rsuvVx", + long_options, &option_index); +#else + c = getopt_long(argc, argv, "B:cdeEfhHiI:l:Lm:op: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->page_num = VPD_EXT_INQ; + op->page_given = true; + break; + case 'f': + op->do_force = true; + break; + case 'h': + ++op->do_help; + 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->page_num = 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; + } + op->resp_len = n; + 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_arg = optarg; + op->page_given = true; + 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->resp_len = 36; + --plen; + ++cp; + } else + jmp_out = true; + break; + case 'a': + op->page_num = 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->page_num = 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->page_num = VPD_DEVICE_ID; + op->do_vpd = true; + op->page_given = true; + ++op->num_pages; + break; + case 'L': + ++op->do_long; + break; + case 'm': + op->page_num = VPD_MAN_NET_ADDR; + op->do_vpd = true; + ++op->num_pages; + op->page_given = true; + break; + case 'M': + op->page_num = 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->page_num = VPD_UPR_EMC; + op->do_vpd = true; + op->page_given = true; + ++op->num_pages; + break; + case 'r': + ++op->do_raw; + break; + case 's': + op->page_num = 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->page_num = 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 (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; + } + op->resp_len = n; + } else if (0 == strncmp("p=", cp, 2)) { + op->page_arg = 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 */ + + +/* Read ASCII hex bytes or binary from fname (a file named '-' taken as + * stdin). If reading ASCII hex then there should be either one entry per + * line or a comma, space or tab separated list of bytes. If no_space is + * set then a string of ACSII hex digits is expected, 2 per byte. Everything + * from and including a '#' on a line is ignored. Returns 0 if ok, or a + * negated errno or SG_LIB_SYNTAX_ERROR or SG_LIB_FILE_ERROR . */ +static int +f2hex_arr(const char * fname, int as_binary, int no_space, + uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) +{ + bool has_stdin; + bool split_line; + int fn_len, in_len, k, j, m, fd, err; + int off = 0; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return SG_LIB_LOGIC_ERROR; + fn_len = strlen(fname); + if (0 == fn_len) + return SG_LIB_SYNTAX_ERROR; + has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ + if (as_binary) { + if (has_stdin) { + fd = STDIN_FILENO; + if (sg_set_binary_mode(STDIN_FILENO) < 0) + perror("sg_set_binary_mode"); + } else { + fd = open(fname, O_RDONLY); + if (fd < 0) { + err = errno; + pr2serr("unable to open binary file %s: %s\n", fname, + safe_strerror(err)); + return -err; + } else if (sg_set_binary_mode(fd) < 0) + perror("sg_set_binary_mode"); + } + k = read(fd, mp_arr, max_arr_len); + if (k <= 0) { + if (0 == k) + pr2serr("read 0 bytes from binary file %s\n", fname); + else + pr2serr("read from binary file %s: %s\n", fname, + safe_strerror(errno)); + if (! has_stdin) + close(fd); + return SG_LIB_SYNTAX_ERROR; + } + *mp_arr_len = k; + if (! has_stdin) + close(fd); + return 0; + } else { /* So read the file as ASCII hex */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + err = errno; + pr2serr("Unable to open %s for reading\n", fname); + return -err; + } + } + } + + carry_over[0] = 0; + for (j = 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(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); + 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; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + 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) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + err = ferror(fp) ? SG_LIB_FILE_ERROR : 0; + if (stdin != fp) + fclose(fp); + return err; +bad: + err = SG_LIB_SYNTAX_ERROR; + if (stdin != fp) + fclose(fp); + return err; +} + +static const struct svpd_values_name_t * +sdp_find_vpd_by_acron(const char * ap) +{ + const struct svpd_values_name_t * vnp; + + for (vnp = 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; + + for (vnp = 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; +} + +struct vpd_name { + int number; + int peri_type; + const char * name; +}; + +/* In numerical order */ +static struct vpd_name vpd_name_arr[] = { + {VPD_SUPPORTED_VPDS, 0, "Supported VPD pages"}, /* 0x0 */ + {VPD_UNIT_SERIAL_NUM, 0, "Unit serial number"}, /* 0x80 */ + {0x81, 0, "Implemented operating definitions (obsolete)"}, + {0x82, 0, "ASCII implemented operating definition (obsolete)"}, + {VPD_DEVICE_ID, 0, "Device identification"}, + {VPD_SOFTW_INF_ID, 0, "Software interface identification"}, + {VPD_MAN_NET_ADDR, 0, "Management network addresses"}, + {VPD_EXT_INQ, 0, "Extended INQUIRY data"}, + {VPD_MODE_PG_POLICY, 0, "Mode page policy"}, + {VPD_SCSI_PORTS, 0, "SCSI ports"}, + {VPD_ATA_INFO, 0, "ATA information"}, + {VPD_POWER_CONDITION, 0, "Power condition"}, + {VPD_DEVICE_CONSTITUENTS, 0, "Device constituents"}, + {VPD_CFA_PROFILE_INFO, 0, "CFA profile information"}, /* 0x8c */ + {VPD_POWER_CONSUMPTION, 0, "Power consumption"}, /* 0x8d */ + {VPD_3PARTY_COPY, 0, "Third party copy"}, /* 0x8f */ + {VPD_PROTO_LU, 0, "Protocol-specific logical unit information"}, /* 0x90 */ + {VPD_PROTO_PORT, 0, "Protocol-specific port information"}, /* 0x91 */ + {VPD_SCSI_FEATURE_SETS, 0, "SCSI Feature sets"}, /* 0x92 */ + /* 0xb0 to 0xbf are per peripheral device type */ + {VPD_BLOCK_LIMITS, 0, "Block limits (sbc2)"}, /* 0xb0 */ + {VPD_BLOCK_DEV_CHARS, 0, "Block device characteristics (sbc3)"}, + {VPD_LB_PROVISIONING, 0, "Logical block provisioning (sbc3)"}, + {VPD_REFERRALS, 0, "Referrals (sbc3)"}, + {0xb0, PDT_TAPE, "Sequential access device capabilities (ssc3)"}, + {0xb2, PDT_TAPE, "TapeAlert supported flags (ssc3)"}, + {0xb0, PDT_OSD, "OSD information (osd)"}, + {0xb1, PDT_OSD, "Security token (osd)"}, + /* 0xc0 to 0xff are vendor specific */ + {0xc0, 0, "vendor: Firmware numbers (seagate); Unit path report (EMC)"}, + {0xc1, 0, "vendor: Date code (seagate)"}, + {0xc2, 0, "vendor: Jumper settings (seagate); Software version (RDAC)"}, + {0xc3, 0, "vendor: Device behavior (seagate)"}, + {0xc9, 0, "Volume Access Control (RDAC)"}, +}; + +static const char * +get_vpd_page_str(int vpd_page_num, int scsi_ptype) +{ + int k; + int vpd_name_arr_sz = (int)SG_ARRAY_SIZE(vpd_name_arr); + + if ((vpd_page_num >= 0xb0) && (vpd_page_num < 0xc0)) { + /* peripheral device type relevant for 0xb0..0xbf range */ + for (k = 0; k < vpd_name_arr_sz; ++k) { + if ((vpd_name_arr[k].number == vpd_page_num) && + (vpd_name_arr[k].peri_type == scsi_ptype)) + break; + } + if (k < vpd_name_arr_sz) + return vpd_name_arr[k].name; + for (k = 0; k < vpd_name_arr_sz; ++k) { + if ((vpd_name_arr[k].number == vpd_page_num) && + (vpd_name_arr[k].peri_type == 0)) + break; + } + if (k < vpd_name_arr_sz) + return vpd_name_arr[k].name; + else + return NULL; + } else { + /* rest of 0x0..0xff range doesn't depend on peripheral type */ + for (k = 0; k < vpd_name_arr_sz; ++k) { + if (vpd_name_arr[k].number == vpd_page_num) + break; + } + if (k < vpd_name_arr_sz) + return vpd_name_arr[k].name; + else + return NULL; + } +} + +static void +decode_supported_vpd(uint8_t * buff, int len, int do_hex) +{ + int vpd, k, rlen, pdt; + const char * cp; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + if (len < 4) { + pr2serr("Supported VPD pages VPD page length too short=%d\n", len); + return; + } + pdt = 0x1f & 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; + printf(" Supported VPD pages:\n"); + for (k = 0; k < len - 4; ++k) { + vpd = buff[4 + k]; + cp = get_vpd_page_str(vpd, pdt); + if (cp) + printf(" 0x%x\t%s\n", vpd, cp); + else + printf(" 0x%x\n", vpd); + } +} + +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; +} + +static bool +vpd_page_not_supported(uint8_t * vpd_pg0, int v0_len, int pg_num, int vb) +{ + return ! vpd_page_is_supported(vpd_pg0, v0_len, pg_num, vb); +} + +/* ASCII Information VPD pages (page numbers: 0x1 to 0x7f) */ +static void +decode_ascii_inf(uint8_t * buff, int len, int do_hex) +{ + int al, k, bump; + uint8_t * bp; + uint8_t * p; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + 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) { + printf(" %.*s\n", al - k, (const char *)bp); + break; + } + printf(" %s\n", (const char *)bp); + bump = (p - bp) + 1; + } + bp = buff + 5 + al; + if (bp < (buff + len)) { + printf("Vendor specific information in hex:\n"); + hex2stdout(bp, len - (al + 5), 0); + } +} + +static void +decode_id_vpd(uint8_t * buff, int len, int do_hex, int verbose) +{ + if (len < 4) { + pr2serr("Device identification VPD page length too " + "short=%d\n", len); + return; + } + decode_dev_ids("Device identification", buff + 4, len - 4, do_hex, + verbose); +} + +static const char * assoc_arr[] = +{ + "addressed logical unit", + "target port", /* that received request; unless SCSI ports VPD */ + "target device that contains addressed lu", + "reserved [0x3]", +}; + +static const char * network_service_type_arr[] = +{ + "unspecified", + "storage configuration service", + "diagnostics", + "status", + "logging", + "code download", + "copy service", + "administrative configuration service", + "[0x8]", "[0x9]", "[0xa]", "[0xb]", "[0xc]", "[0xd]", + "[0xe]", "[0xf]", "[0x10]", "[0x11]", "[0x12]", "[0x13]", "[0x14]", + "[0x15]", "[0x16]", "[0x17]", "[0x18]", "[0x19]", "[0x1a]", + "[0x1b]", "[0x1c]", "[0x1d]", "[0x1e]", "[0x1f]", +}; + +/* VPD_MAN_NET_ADDR */ +static void +decode_net_man_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump, na_len; + uint8_t * bp; + + if (len < 4) { + pr2serr("Management network addresses VPD page length too short=%d\n", + len); + return; + } + if (do_hex > 2) { + hex2stdout(buff, len, -1); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0; k < len; k += bump, bp += bump) { + printf(" %s, Service type: %s\n", + assoc_arr[(bp[0] >> 5) & 0x3], + network_service_type_arr[bp[0] & 0x1f]); + na_len = sg_get_unaligned_be16(bp + 2); + 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; + } + if (na_len > 0) { + if (do_hex) { + printf(" Network address:\n"); + hex2stdout(bp + 4, na_len, 0); + } else + printf(" %s\n", bp + 4); + } + } +} + +static const char * mode_page_policy_arr[] = +{ + "shared", + "per target port", + "per initiator port", + "per I_T nexus", +}; + +/* VPD_MODE_PG_POLICY */ +static void +decode_mode_policy_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump; + uint8_t * bp; + + if (len < 4) { + pr2serr("Mode page policy VPD page length too short=%d\n", len); + return; + } + if (do_hex > 2) { + hex2stdout(buff, len, -1); + 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 (do_hex) + hex2stdout(bp, 4, (1 == do_hex) ? 1 : -1); + else { + printf(" Policy page code: 0x%x", (bp[0] & 0x3f)); + if (bp[1]) + printf(", subpage code: 0x%x\n", bp[1]); + else + printf("\n"); + printf(" MLUS=%d, Policy: %s\n", !!(bp[2] & 0x80), + mode_page_policy_arr[bp[2] & 0x3]); + } + } +} + +/* VPD_SCSI_PORTS */ +static void +decode_scsi_ports_vpd(uint8_t * buff, int len, int do_hex, int verbose) +{ + int k, bump, rel_port, ip_tid_len, tpd_len; + uint8_t * bp; + + if (len < 4) { + pr2serr("SCSI Ports VPD page length too short=%d\n", len); + return; + } + if (do_hex > 2) { + hex2stdout(buff, len, -1); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0; k < len; k += bump, bp += bump) { + rel_port = sg_get_unaligned_be16(bp + 2); + printf("Relative port=%d\n", 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 (do_hex) { + printf(" Initiator port transport id:\n"); + hex2stdout((bp + 8), ip_tid_len, (1 == do_hex) ? 1 : -1); + } else { + char b[1024]; + + printf("%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) { + printf(" Target port descriptor(s):\n"); + if (do_hex) + hex2stdout(bp + bump + 4, tpd_len, (1 == do_hex) ? 1 : -1); + else + decode_dev_ids("SCSI Ports", bp + bump + 4, tpd_len, + do_hex, verbose); + } + bump += tpd_len + 4; + } +} + +/* 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, int do_hex, + int verbose) +{ + 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; + uint64_t vsei; + uint64_t id_ext; + const uint8_t * bp; + const uint8_t * ip; + char b[64]; + const char * cp; + + if (buff[2] != 0) { + /* + * 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; + printf(" 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; + printf(" 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))) + printf(" transport: %s\n", + sg_get_trans_proto_str(p_id, sizeof(b), b)); + cp = sg_get_desig_type_str(desig_type); + printf(" designator_type: %s, ", cp ? cp : "-"); + cp = sg_get_desig_code_set_str(c_set); + printf("code_set: %s\n", cp ? cp : "-"); + cp = sg_get_desig_assoc_str(assoc); + printf(" associated with the %s\n", cp ? cp : "-"); + if (do_hex) { + printf(" designator header(hex): %.2x %.2x %.2x %.2x\n", + bp[0], bp[1], bp[2], bp[3]); + printf(" 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) + printf(" vendor specific: %.*s\n", i_len, ip); + else { + printf(" vendor specific:\n"); + hex2stdout(ip, i_len, -1); + } + break; + case 1: /* T10 vendor identification */ + printf(" vendor id: %.8s\n", ip); + if (i_len > 8) { + if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */ + printf(" vendor specific: %.*s\n", i_len - 8, ip + 8); + } else { + printf(" vendor specific: 0x"); + for (m = 8; m < i_len; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("\n"); + } + } + break; + case 2: /* EUI-64 based */ + printf(" 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; + if (16 == i_len) { + ci_off = 8; + id_ext = sg_get_unaligned_be64(ip); + printf(" 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; + } + c_id = sg_get_unaligned_be24(ip + ci_off); + printf(" IEEE Company_id: 0x%x\n", c_id); + vsei = sg_get_unaligned_be48(ip + ci_off + 3); + printf(" Vendor Specific Extension Identifier: 0x%" PRIx64 + "\n", vsei); + if (12 == i_len) { + d_id = sg_get_unaligned_be32(ip + 8); + printf(" Directory ID: 0x%x\n", d_id); + } + 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, -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); + printf(" NAA 2, vendor specific identifier A: 0x%x\n", + d_id); + printf(" IEEE Company_id: 0x%x\n", c_id); + printf(" vendor specific identifier B: 0x%x\n", vsi); + printf(" [0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("]\n"); + 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; + } + printf(" NAA 3, Locally assigned:\n"); + printf(" [0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("]\n"); + 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]; + } + printf(" NAA 5, IEEE Company_id: 0x%x\n", c_id); + printf(" Vendor Specific Identifier: 0x%" PRIx64 + "\n", vsei); + printf(" [0x"); + for (m = 0; m < 8; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("]\n"); + 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]; + } + printf(" NAA 6, IEEE Company_id: 0x%x\n", c_id); + printf(" Vendor Specific Identifier: 0x%" PRIx64 "\n", + vsei); + vsei = sg_get_unaligned_be64(ip + 8); + printf(" Vendor Specific Identifier Extension: " + "0x%" PRIx64 "\n", vsei); + printf(" [0x"); + for (m = 0; m < 16; ++m) + printf("%02x", (unsigned int)ip[m]); + printf("]\n"); + 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); + printf(" Relative target port: 0x%x\n", d_id); + break; + case 5: /* (primary) Target port group */ + if ((1 != c_set) || (1 != assoc) || (4 != i_len)) { + 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); + printf(" 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); + printf(" 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; + } + printf(" MD5 logical unit identifier:\n"); + hex2stdout(ip, i_len, -1); + break; + case 8: /* SCSI name string */ + if (3 != c_set) { + if (2 == c_set) { + if (verbose) + pr2serr(" << expected UTF-8, use ASCII>>\n"); + } else { + pr2serr(" << expected UTF-8 code_set>>\n"); + hex2stderr(ip, i_len, -1); + break; + } + } + printf(" SCSI name string:\n"); + /* does %s print out UTF-8 ok?? + * Seems to depend on the locale. Looks ok here with my + * locale setting: en_AU.UTF-8 + */ + printf(" %.*s\n", 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) + printf(" >>>> Protocol specific port identifier " + "expects protocol\n" + " identifier to be valid and it is not\n"); + if (TPROTO_UAS == p_id) { + printf(" USB device address: 0x%x\n", 0x7f & ip[0]); + printf(" USB interface number: 0x%x\n", ip[2]); + } else if (TPROTO_SOP == p_id) { + printf(" PCIe routing ID, bus number: 0x%x\n", ip[0]); + printf(" function number: 0x%x\n", ip[1]); + printf(" [or device number: 0x%x, function number: " + "0x%x]\n", (0x1f & (ip[1] >> 3)), 0x7 & ip[1]); + } else + printf(" >>>> unexpected protocol indentifier: %s\n" + " with Protocol specific port " + "identifier\n", + sg_get_trans_proto_str(p_id, sizeof(b), b)); + 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; + } + printf(" Locally assigned UUID: "); + 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 */ + 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); +} + +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_EXT_INQ Extended Inquiry [0x86] */ +static void +decode_x_inq_vpd(uint8_t * buff, int len, int do_hex) +{ + if (len < 7) { + pr2serr("Extended INQUIRY data VPD page length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + printf(" ACTIVATE_MICROCODE=%d SPT=%d GRD_CHK=%d APP_CHK=%d " + "REF_CHK=%d\n", ((buff[4] >> 6) & 0x3), ((buff[4] >> 3) & 0x7), + !!(buff[4] & 0x4), !!(buff[4] & 0x2), !!(buff[4] & 0x1)); + printf(" UASK_SUP=%d GROUP_SUP=%d PRIOR_SUP=%d HEADSUP=%d ORDSUP=%d " + "SIMPSUP=%d\n", !!(buff[5] & 0x20), !!(buff[5] & 0x10), + !!(buff[5] & 0x8), !!(buff[5] & 0x4), !!(buff[5] & 0x2), + !!(buff[5] & 0x1)); + /* CRD_SUP made obsolete in spc5r04 */ + printf(" WU_SUP=%d [CRD_SUP=%d] NV_SUP=%d V_SUP=%d\n", + !!(buff[6] & 0x8), !!(buff[6] & 0x4), !!(buff[6] & 0x2), + !!(buff[6] & 0x1)); + /* NO_PI_CHK and HSSRELEF added in spc5r02 */ + printf(" NO_PI_CHK=%d P_I_I_SUP=%d LUICLR=%d\n", !!(buff[7] & 0x20), + !!(buff[7] & 0x10), !!(buff[7] & 0x1)); + /* RTD_SUP added in spc5r11, LU_COLL_TYPE added in spc5r09, + * HSSRELEF added in spc5r02; CBCS obsolete in spc5r01 */ + printf(" LU_COLL_TYPE=%d R_SUP=%d RTD_SUP=%d HSSRELEF=%d [CBCS=%d]\n", + (buff[8] >> 5) & 0x7, !!(buff[8] & 0x10), !!(buff[8] & 0x8), + !!(buff[8] & 0x2), !!(buff[8] & 0x1)); + printf(" Multi I_T nexus microcode download=%d\n", buff[9] & 0xf); + printf(" Extended self-test completion minutes=%d\n", + sg_get_unaligned_be16(buff + 10)); /* spc4r27 */ + printf(" POA_SUP=%d HRA_SUP=%d VSA_SUP=%d DMS_VALID=%d\n", + !!(buff[12] & 0x80), !!(buff[12] & 0x40), !!(buff[12] & 0x20), + !!(buff[12] & 0x10)); /* spc4r32 + 17-142r5 */ + printf(" Maximum supported sense data length=%d\n", + buff[13]); /* spc4r34 */ + /* All byte 14 bits added in spc5r09 */ + printf(" IBS=%d IAS=%d SAC=%d NRD1=%d NRD0=%d\n", + !!(buff[14] & 0x80), !!(buff[14] & 0x40), !!(buff[14] & 0x4), + !!(buff[14] & 0x2), !!(buff[14] & 0x1)); + printf(" Maximum inquiry change logs=%u\n", + sg_get_unaligned_be16(buff + 15)); /* spc5r17 */ + printf(" Maximum mode page change logs=%u\n", + sg_get_unaligned_be16(buff + 17)); /* spc5r17 */ + printf(" DM_MD_4=%d DM_MD_5=%d DM_MD_6=%d DM_MD_7=%d\n", + !!(buff[19] & 0x80), !!(buff[19] & 0x40), !!(buff[19] & 0x20), + !!(buff[19] & 0x10)); /* 17-142r5 */ + printf(" DM_MD_D=%d DM_MD_E=%d DM_MD_F=%d\n", + !!(buff[19] & 0x8), !!(buff[19] & 0x4), !!(buff[19] & 0x2)); +} + +/* VPD_SOFTW_INF_ID [0x84] */ +static void +decode_softw_inf_id(uint8_t * buff, int len, int do_hex) +{ + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + len -= 4; + buff += 4; + for ( ; len > 5; len -= 6, buff += 6) + printf(" IEEE Company_id: 0x%06x, vendor specific extension " + "id: 0x%06x\n", sg_get_unaligned_be24(buff + 0), + sg_get_unaligned_be24(buff + 3)); +} + +/* VPD_ATA_INFO [0x89] */ +static void +decode_ata_info_vpd(uint8_t * buff, int len, int do_hex) +{ + char b[80]; + int is_be, num; + + if (len < 36) { + pr2serr("ATA information VPD page length too short=%d\n", len); + return; + } + if (do_hex && (2 != do_hex)) { + hex2stdout(buff, len, (3 == do_hex) ? 0 : -1); + return; + } + memcpy(b, buff + 8, 8); + b[8] = '\0'; + printf(" SAT Vendor identification: %s\n", b); + memcpy(b, buff + 16, 16); + b[16] = '\0'; + printf(" SAT Product identification: %s\n", b); + memcpy(b, buff + 32, 4); + b[4] = '\0'; + printf(" SAT Product revision level: %s\n", b); + if (len < 56) + return; + printf(" Signature (Device to host FIS):\n"); + hex2stdout(buff + 36, 20, 1); + if (len < 60) + return; + is_be = sg_is_big_endian(); + if ((0xec == buff[56]) || (0xa1 == buff[56])) { + printf(" ATA command IDENTIFY %sDEVICE response summary:\n", + ((0xa1 == buff[56]) ? "PACKET " : "")); + num = sg_ata_get_chars((const unsigned short *)(buff + 60), 27, 20, + is_be, b); + b[num] = '\0'; + printf(" model: %s\n", b); + num = sg_ata_get_chars((const unsigned short *)(buff + 60), 10, 10, + is_be, b); + b[num] = '\0'; + printf(" serial number: %s\n", b); + num = sg_ata_get_chars((const unsigned short *)(buff + 60), 23, 4, + is_be, b); + b[num] = '\0'; + printf(" firmware revision: %s\n", b); + printf(" response in hex:\n"); + } else + printf(" ATA command 0x%x got following response:\n", + (unsigned int)buff[56]); + if (len < 572) + return; + if (2 == do_hex) + hex2stdout(buff + 60, 512, 0); + else + dWordHex((const unsigned short *)(buff + 60), 256, 0, + sg_is_big_endian()); +} + +/* VPD_POWER_CONDITION [0x8a] */ +static void +decode_power_condition(uint8_t * buff, int len, int do_hex) +{ + if (len < 18) { + pr2serr("Power condition VPD page length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + printf(" 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)); + printf(" Stopped condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 6)); + printf(" Standby_z condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 8)); + printf(" Standby_y condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 10)); + printf(" Idle_a condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 12)); + printf(" Idle_b condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 14)); + printf(" Idle_c condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 16)); +} + +/* VPD_SCSI_FEATURE_SETS [0x92] (sfs) */ +static void +decode_feature_sets_vpd(uint8_t * buff, int len, + const struct opts_t * op) +{ + int k, bump; + uint16_t sf_code; + bool found; + uint8_t * bp; + char b[64]; + + 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) { + 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 { + printf(" %s", sg_get_sfs_str(sf_code, -2, sizeof(b), b, + &found, op->verbose)); + if (op->verbose == 1) + printf(" [0x%x]\n", (unsigned int)sf_code); + else if (op->verbose > 1) + printf(" [0x%x] found=%s\n", (unsigned int)sf_code, + found ? "true" : "false"); + else + printf("\n"); + } + } +} + +/* VPD_BLOCK_LIMITS sbc */ +/* Sequential access device characteristics, ssc+smc */ +/* OSD information, osd */ +static void +decode_b0_vpd(uint8_t * buff, int len, int do_hex) +{ + int pdt; + unsigned int u; + uint64_t ull; + bool ugavalid; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + pdt = 0x1f & buff[0]; + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: + if (len < 16) { + pr2serr("Block limits VPD page length too short=%d\n", len); + return; + } + u = buff[5]; + printf(" Maximum compare and write length: "); + if (0 == u) + printf("0 blocks [Command not implemented]\n"); + else + printf("%u blocks\n", buff[5]); + u = sg_get_unaligned_be16(buff + 6); + printf(" Optimal transfer length granularity: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 8); + printf(" Maximum transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 12); + printf(" Optimal transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + if (len > 19) { /* added in sbc3r09 */ + u = sg_get_unaligned_be32(buff + 16); + printf(" Maximum prefetch transfer length: "); + if (0 == u) + printf("0 blocks [ignored]\n"); + else + printf("%u blocks\n", u); + } + if (len > 27) { /* added in sbc3r18 */ + u = sg_get_unaligned_be32(buff + 20); + printf(" Maximum unmap LBA count: "); + if (0 == u) + printf("0 [Unmap command not implemented]\n"); + else if (SG_LIB_UNBOUNDED_32BIT == u) + printf("-1 [unbounded]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(buff + 24); + printf(" Maximum unmap block descriptor count: "); + if (0 == u) + printf("0 [Unmap command not implemented]\n"); + else if (SG_LIB_UNBOUNDED_32BIT == u) + printf("-1 [unbounded]\n"); + else + printf("%u\n", u); + } + if (len > 35) { /* added in sbc3r19 */ + u = sg_get_unaligned_be32(buff + 28); + printf(" Optimal unmap granularity: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + + ugavalid = !!(buff[32] & 0x80); + printf(" Unmap granularity alignment valid: %s\n", + ugavalid ? "true" : "false"); + u = 0x7fffffff & sg_get_unaligned_be32(buff + 32); + printf(" Unmap granularity alignment: %u%s\n", u, + ugavalid ? "" : " [invalid]"); + } + if (len > 43) { /* added in sbc3r26 */ + ull = sg_get_unaligned_be64(buff + 36); + printf(" Maximum write same length: "); + if (0 == ull) + printf("0 blocks [not reported]\n"); + else + printf("0x%" PRIx64 " blocks\n", ull); + } + if (len > 44) { /* added in sbc4r02 */ + u = sg_get_unaligned_be32(buff + 44); + printf(" Maximum atomic transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 48); + printf(" Atomic alignment: "); + if (0 == u) + printf("0 [unaligned atomic writes permitted]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(buff + 52); + printf(" Atomic transfer length granularity: "); + if (0 == u) + printf("0 [no granularity requirement\n"); + else + printf("%u\n", u); + } + if (len > 56) { + u = sg_get_unaligned_be32(buff + 56); + printf(" Maximum atomic transfer length with atomic " + "boundary: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 60); + printf(" Maximum atomic boundary size: "); + if (0 == u) + printf("0 blocks [can only write atomic 1 block]\n"); + else + printf("%u blocks\n", u); + } + break; + case PDT_TAPE: case PDT_MCHANGER: + printf(" WORM=%d\n", !!(buff[4] & 0x1)); + break; + case PDT_OSD: + default: + printf(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stdout(buff, len, 0); + break; + } +} + +/* VPD_BLOCK_DEV_CHARS sbc */ +/* VPD_MAN_ASS_SN ssc */ +static void +decode_b1_vpd(uint8_t * buff, int len, int do_hex) +{ + int pdt; + unsigned int u; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + pdt = 0x1f & buff[0]; + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: + if (len < 64) { + pr2serr("Block device characteristics VPD page length too " + "short=%d\n", len); + return; + } + u = sg_get_unaligned_be16(buff + 4); + if (0 == u) + printf(" Medium rotation rate is not reported\n"); + else if (1 == u) + printf(" Non-rotating medium (e.g. solid state)\n"); + else if ((u < 0x401) || (0xffff == u)) + printf(" Reserved [0x%x]\n", u); + else + printf(" Nominal rotation rate: %d rpm\n", u); + printf(" Product type=%d\n", buff[6]); + printf(" WABEREQ=%d\n", (buff[7] >> 6) & 0x3); + printf(" WACEREQ=%d\n", (buff[7] >> 4) & 0x3); + u = buff[7] & 0xf; + printf(" Nominal form factor "); + switch(u) { + case 0: + printf("is not reported\n"); + break; + case 1: + printf("5.25 inches\n"); + break; + case 2: + printf("3.5 inches\n"); + break; + case 3: + printf("2.5 inches\n"); + break; + case 4: + printf("1.8 inches\n"); + break; + case 5: + printf("less then 1.8 inches\n"); + break; + default: + printf("reserved [%u]\n", u); + break; + } + printf(" ZONED=%d\n", (buff[8] >> 4) & 0x3); /* sbc4r04 */ + printf(" FUAB=%d\n", buff[8] & 0x2); + printf(" VBULS=%d\n", buff[8] & 0x1); + printf(" DEPOPULATION_TIME=%u (seconds)\n", + sg_get_unaligned_be32(buff + 12)); /* added sbc4r14 */ + break; + case PDT_TAPE: case PDT_MCHANGER: case PDT_ADC: + printf(" Manufacturer-assigned serial number: %.*s\n", + len - 4, buff + 4); + break; + default: + printf(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stdout(buff, len, 0); + break; + } +} + +/* VPD_REFERRALS sbc */ +static void +decode_b3_vpd(uint8_t * buff, int len, int do_hex) +{ + int pdt; + unsigned int s, m; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + pdt = 0x1f & buff[0]; + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: + if (len < 0x10) { + pr2serr("Referrals VPD page length too short=%d\n", len); + return; + } + s = sg_get_unaligned_be32(buff + 8); + m = sg_get_unaligned_be32(buff + 12); + if (0 == s) + printf(" Single user data segment\n"); + else if (0 == m) + printf(" Segment size specified by user data segment " + "descriptor\n"); + else + printf(" Segment size: %u, segment multiplier: %u\n", s, m); + break; + default: + printf(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stdout(buff, len, 0); + break; + } +} + +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)", +}; + +static void +decode_upr_vpd_c0_emc(uint8_t * buff, int len, int do_hex) +{ + int k, ip_mgmt, vpp80, lun_z; + + if (len < 3) { + pr2serr("EMC upr VPD page [0xc0]: length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 1 : -1); + return; + } + if (buff[9] != 0x00) { + pr2serr("Unsupported page revision %d, decoding not possible.\n", + buff[9]); + return; + } + printf(" LUN WWN: "); + for (k = 0; k < 16; ++k) + printf("%02x", buff[10 + k]); + printf("\n"); + printf(" Array Serial Number: "); + dStrRaw((const char *)&buff[50], buff[49]); + printf("\n"); + + printf(" LUN State: "); + if (buff[4] > 0x02) + printf("Unknown (%x)\n", buff[4]); + else + printf("%s\n", lun_state_arr[buff[4]]); + + printf(" This path connects to: "); + if (buff[8] > 0x01) + printf("Unknown SP (%x)", buff[8]); + else + printf("%s", sp_arr[buff[8]]); + printf(", Port Number: %u\n", buff[7]); + + printf(" Default Owner: "); + if (buff[5] > 0x01) + printf("Unknown (%x)\n", buff[5]); + else + printf("%s\n", sp_arr[buff[5]]); + + printf(" NO_ATF: %s, Access Logix: %s\n", + buff[6] & 0x80 ? "set" : "not set", + buff[6] & 0x40 ? "supported" : "not supported"); + + ip_mgmt = (buff[6] >> 4) & 0x3; + + printf(" SP IP Management Mode: %s\n", ip_mgmt_arr[ip_mgmt]); + if (ip_mgmt == 2) + printf(" SP IPv4 address: %u.%u.%u.%u\n", + buff[44], buff[45], buff[46], buff[47]); + else { + printf(" SP IPv6 address: "); + for (k = 0; k < 16; ++k) + printf("%02x", buff[32 + k]); + printf("\n"); + } + + vpp80 = buff[30] & 0x08; + lun_z = buff[30] & 0x04; + + printf(" System Type: %x, Failover mode: %s\n", + buff[27], failover_mode_arr[buff[28] & 0x0f]); + + printf(" Inquiry VPP 0x80 returns: %s, Arraycommpath: %s\n", + vpp80 ? "array serial#" : "LUN serial#", + lun_z ? "Set to 1" : "Unknown"); + + printf(" Lun operations: %s\n", + buff[48] > 1 ? "undefined" : lun_op_arr[buff[48]]); + + return; +} + +static void +decode_rdac_vpd_c2(uint8_t * buff, int len, int do_hex) +{ + if (len < 3) { + pr2serr("Software Version VPD page length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 1 : -1); + 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; + } + printf(" Software Version: %02x.%02x.%02x\n", buff[8], buff[9], buff[10]); + printf(" Software Date: %02d/%02d/%02d\n", buff[11], buff[12], buff[13]); + printf(" Features:"); + if (buff[14] & 0x01) + printf(" Dual Active,"); + if (buff[14] & 0x02) + printf(" Series 3,"); + if (buff[14] & 0x04) + printf(" Multiple Sub-enclosures,"); + if (buff[14] & 0x08) + printf(" DCE/DRM/DSS/DVE,"); + if (buff[14] & 0x10) + printf(" Asymmetric Logical Unit Access,"); + printf("\n"); + printf(" Max. #of LUNS: %d\n", buff[15]); + return; +} + +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, int do_hex) +{ + if (len < 3) { + pr2serr("Volume Access Control VPD page length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 1 : -1); + 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; +} + +extern const char * sg_ansi_version_arr[]; + +static const char * +get_ansi_version_str(int version, char * buff, int buff_len) +{ + version &= 0xf; + buff[buff_len - 1] = '\0'; + strncpy(buff, sg_ansi_version_arr[version], buff_len - 1); + return buff; +} + +static void +std_inq_decode(const struct opts_t * op, int act_len) +{ + int len, pqual, peri_type, ansi_version, k, j; + const char * cp; + int vdesc_arr[8]; + char buff[48]; + const uint8_t * rp; + + rp = rsp_buff; + memset(vdesc_arr, 0, sizeof(vdesc_arr)); + if (op->do_raw) { + dStrRaw((const char *)rp, act_len); + return; + } else if (op->do_hex) { + /* with -H, print with address, -HH without */ + hex2stdout(rp, act_len, ((1 == op->do_hex) ? 0 : -1)); + return; + } + pqual = (rp[0] & 0xe0) >> 5; + if (! op->do_raw && ! op->do_export) { + printf("standard INQUIRY:"); + if (0 == pqual) + printf("\n"); + else if (1 == pqual) + printf(" [PQ indicates LU temporarily unavailable]\n"); + else if (3 == pqual) + printf(" [PQ indicates LU not accessible via this port]\n"); + else + printf(" [reserved or vendor specific qualifier [%d]]\n", 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 */ + peri_type = rp[0] & 0x1f; + if (op->do_export) { + printf("SCSI_TPGS=%d\n", (rp[5] & 0x30) >> 4); + cp = sg_get_pdt_str(peri_type, sizeof(buff), buff); + if (strlen(cp) > 0) + printf("SCSI_TYPE=%s\n", cp); + } else { + printf(" PQual=%d Device_type=%d RMB=%d LU_CONG=%d " + "version=0x%02x ", pqual, peri_type, !!(rp[1] & 0x80), + !!(rp[1] & 0x40), (unsigned int)rp[2]); + printf(" [%s]\n", get_ansi_version_str(ansi_version, buff, + sizeof(buff))); + printf(" [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)); + printf("ACC=%d TPGS=%d 3PC=%d Protect=%d ", !!(rp[5] & 0x40), + ((rp[5] & 0x30) >> 4), !!(rp[5] & 0x08), !!(rp[5] & 0x01)); + printf(" [BQue=%d]\n EncServ=%d ", !!(rp[6] & 0x80), + !!(rp[6] & 0x40)); + if (rp[6] & 0x10) + printf("MultiP=1 (VS=%d) ", !!(rp[6] & 0x20)); + else + printf("MultiP=0 "); + printf("[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n [RelAdr=%d] ", + !!(rp[6] & 0x08), !!(rp[6] & 0x04), !!(rp[6] & 0x01), + !!(rp[7] & 0x80)); + printf("WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ", + !!(rp[7] & 0x20), !!(rp[7] & 0x10), !!(rp[7] & 0x08), + !!(rp[7] & 0x04)); + printf("CmdQue=%d\n", !!(rp[7] & 0x02)); + if (act_len > 56) + printf(" [SPI: Clocking=0x%x QAS=%d IUS=%d]\n", + (rp[56] & 0x0c) >> 2, !!(rp[56] & 0x2), !!(rp[56] & 0x1)); + if (act_len >= len) + printf(" length=%d (0x%x)", len, len); + else + printf(" length=%d (0x%x), but only fetched %d bytes", len, + len, act_len); + if ((ansi_version >= 2) && (len < SAFE_STD_INQ_RESP_LEN)) + printf("\n [for SCSI>=2, len>=36 is expected]"); + cp = sg_get_pdt_str(peri_type, sizeof(buff), buff); + if (strlen(cp) > 0) + printf(" Peripheral device type: %s\n", cp); + } + if (act_len <= 8) { + if (! op->do_export) + printf(" Inquiry response length=%d, no vendor, product or " + "revision data\n", act_len); + } 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 + printf(" Vendor identification: %s\n", xtra_buff); + if (act_len <= 16) { + if (! op->do_export) + printf(" Product identification: \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 + printf(" Product identification: %s\n", xtra_buff); + } + if (act_len <= 32) { + if (! op->do_export) + printf(" Product revision level: \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 + printf(" Product revision level: %s\n", xtra_buff); + } + if (op->do_vendor && (act_len > 36) && ('\0' != rp[36]) && + (' ' != rp[36])) { + memcpy(xtra_buff, &rp[36], act_len < 56 ? act_len - 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 + printf(" Vendor specific: %s\n", xtra_buff); + } + if (op->do_descriptors) { + for (j = 0, k = 58; ((j < 8) && ((k + 1) < act_len)); + k +=2, ++j) + vdesc_arr[j] = sg_get_unaligned_be16(rp + k); + } + if ((op->do_vendor > 1) && (act_len > 96)) { + memcpy(xtra_buff, &rp[96], act_len - 96); + if (op->do_export) { + len = encode_whitespaces((uint8_t *)xtra_buff, + act_len - 96); + if (len > 0) + printf("VENDOR_SPECIFIC=%s\n", xtra_buff); + } else + printf(" Vendor specific: %s\n", xtra_buff); + } + } + if (! op->do_export) { + if ((0 == op->resp_len) && usn_buff[0]) + printf(" Unit serial number: %s\n", usn_buff); + if (op->do_descriptors) { + if (0 == vdesc_arr[0]) + printf("\n No version descriptors available\n"); + else { + printf("\n Version descriptors:\n"); + for (k = 0; k < 8; ++k) { + if (0 == vdesc_arr[k]) + break; + cp = find_version_descriptor_str(vdesc_arr[k]); + if (cp) + printf(" %s\n", cp); + else + printf(" [unrecognised version descriptor " + "code: 0x%x]\n", vdesc_arr[k]); + } + } + } + } +} + +/* When sg_fd >= 0 fetch VPD page from device; mxlen is command line + * --maxlen=LEN option (def: 0) or -1 for a VPD page with a short length + * field (1 byte). When sg_fd < 0 then mxlen bytes have been read from + * --inhex=FN file. Returns 0 for success. */ +static int +vpd_fetch_page_from_dev(int sg_fd, uint8_t * rp, int page, + int mxlen, 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, + true, 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"); + 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 { + /* First response indicated that not enough bytes of response were + * requested, so try again, this time requesting more. */ + res = sg_ll_inquiry_v2(sg_fd, true, page, rp, len, DEF_PT_TIMEOUT, + &resid, true, 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; + } +} + +/* 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_from_dev(sg_fd, b, VPD_SUPPORTED_VPDS, + -1 /* 1 byte alloc_len */, 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_not_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_from_dev(sg_fd, b, VPD_UNIT_SERIAL_NUM, -1, 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 response. Returns 0 if successful */ +static int +std_inq_process(int sg_fd, const struct opts_t * op, int inhex_len) +{ + int res, len, rlen, act_len; + char buff[48]; + int vb, resid; + + if (sg_fd < 0) { /* assume --inhex=FD usage */ + std_inq_decode(op, inhex_len); + return 0; + } + rlen = (op->resp_len > 0) ? op->resp_len : 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->resp_len)) { + 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->resp_len > 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->resp_len)) { + if (fetch_unit_serial_num(sg_fd, usn_buff, sizeof(usn_buff), vb)) + usn_buff[0] = '\0'; + } + std_inq_decode(op, act_len); + 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, peri_type, 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) { + peri_type = rsp_buff[0] & 0x1f; + 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, peri_type, + 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->page_num, + rsp_buff, DEF_ALLOC_LEN, true, op->verbose); + if (0 == res) { + peri_type = rsp_buff[0] & 0x1f; + if (! op->do_raw) { + printf("CmdDt INQUIRY, opcode=0x%.2x: [", op->page_num); + sg_get_opcode_name((uint8_t)op->page_num, peri_type, + 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, (1 == op->do_hex) ? 0 : -1); + 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; + default: desc_p = "impossible value > 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->page_num); + sg_get_opcode_name((uint8_t)op->page_num, 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->page_num); + } + } + 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, const struct opts_t * op, int inhex_len) +{ + int res, len; + char b[128]; + const char * cp; + uint8_t * rp; + + rp = rsp_buff; + if ((! op->do_raw) && (op->do_hex < 2)) + printf("VPD INQUIRY, page code=0x%.2x:\n", op->page_num); + if (sg_fd < 0) { + len = sg_get_unaligned_be16(rp + 2) + 4; + if (op->verbose && (len > inhex_len)) + pr2serr("warning: VPD page's length (%d) > bytes in --inhex=FN " + "file (%d)\n", len , inhex_len); + res = 0; + } else { + memset(rp, 0, DEF_ALLOC_LEN); + res = vpd_fetch_page_from_dev(sg_fd, rp, op->page_num, op->resp_len, + op->verbose, &len); + } + if (0 == res) { + if (op->do_raw) + dStrRaw((const char *)rp, len); + else { + if (0 == op->page_num) + decode_supported_vpd(rp, len, op->do_hex); + else { + if (op->verbose) { + cp = sg_get_pdt_str(rp[0] & 0x1f, sizeof(b), b); + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, cp); + } + hex2stdout(rp, len, ((1 == op->do_hex) ? 0 : -1)); + } + } + } 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; +} + +/* Returns 0 if successful */ +static int +vpd_decode(int sg_fd, const struct opts_t * op, int inhex_len) +{ + int len, pdt, pn, vb, mxlen; + int res = 0; + uint8_t * rp; + + pn = op->page_num; + rp = rsp_buff; + vb = op->verbose; + if (sg_fd >= 0) + mxlen = op->resp_len; + else + mxlen = inhex_len; + if (sg_fd != -1 && !op->do_force && pn != VPD_SUPPORTED_VPDS) { + res = vpd_fetch_page_from_dev(sg_fd, rp, VPD_SUPPORTED_VPDS, mxlen, + vb, &len); + if (res) + goto out; + if (vpd_page_not_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: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Supported VPD pages page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else if (op->do_hex) + hex2stdout(rp, len, (1 == op->do_hex) ? 0 : -1); + else + decode_supported_vpd(rp, len, 0x1f & rp[0]); + break; + case VPD_UNIT_SERIAL_NUM: + if (! op->do_raw && ! op->do_export && (op->do_hex < 2)) + printf("VPD INQUIRY: Unit serial number page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else if (op->do_hex) + hex2stdout(rp, len, (1 == op->do_hex) ? 0 : -1); + 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 { + k = encode_unicode((uint8_t *)obuff, len); + if (k > 0) + printf(" Unit serial number: %s\n", obuff); + } + } + break; + case VPD_DEVICE_ID: + if (! op->do_raw && ! op->do_export && (op->do_hex < 3)) + printf("VPD INQUIRY: Device Identification page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, 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) + export_dev_ids(rp + 4, len - 4, op->verbose); + else + decode_id_vpd(rp, len, op->do_hex, op->verbose); + break; + case VPD_SOFTW_INF_ID: + if (! op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Software interface identification page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_softw_inf_id(rp, len, op->do_hex); + break; + case VPD_MAN_NET_ADDR: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Management network addresses page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_net_man_vpd(rp, len, op->do_hex); + break; + case VPD_MODE_PG_POLICY: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Mode page policy\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_mode_policy_vpd(rp, len, op->do_hex); + break; + case VPD_EXT_INQ: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: extended INQUIRY data page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_x_inq_vpd(rp, len, op->do_hex); + break; + case VPD_ATA_INFO: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: ATA information page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, 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 + decode_ata_info_vpd(rp, len, op->do_hex); + break; + case VPD_POWER_CONDITION: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Power condition page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_power_condition(rp, len, op->do_hex); + break; + case VPD_SCSI_FEATURE_SETS: /* 0x92 */ + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: SCSI Feature sets\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_feature_sets_vpd(rp, len, op); + break; + case 0xb0: /* VPD pages in B0h to BFh range depend on pdt */ + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (! op->do_raw && (op->do_hex < 2)) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: + printf("VPD INQUIRY: Block limits page (SBC)\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("VPD INQUIRY: Sequential access device " + "capabilities (SSC)\n"); + break; + case PDT_OSD: + printf("VPD INQUIRY: OSD information (OSD)\n"); + break; + default: + printf("VPD INQUIRY: page=0x%x, pdt=0x%x\n", 0xb0, pdt); + break; + } + } + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_b0_vpd(rp, len, op->do_hex); + } 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_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (! op->do_raw && (op->do_hex < 2)) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: + printf("VPD INQUIRY: Block device characteristcis page " + "(SBC)\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("Manufactured assigned serial number VPD page " + "(SSC):\n"); + break; + case PDT_OSD: + printf("Security token VPD page (OSD):\n"); + break; + case PDT_ADC: + printf("Manufactured assigned serial number VPD page " + "(ADC):\n"); + break; + default: + printf("VPD INQUIRY: page=0x%x, pdt=0x%x\n", 0xb1, pdt); + break; + } + } + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_b1_vpd(rp, len, op->do_hex); + } else if (! op->do_raw) + pr2serr("VPD INQUIRY: page=0xb1\n"); + break; + case 0xb2: /* VPD pages in B0h to BFh range depend on pdt */ + if (!op->do_raw && (op->do_hex < 2)) + pr2serr(" Only hex output supported. The sg_vpd utility decodes " + "the B2h page.\n"); + return vpd_mainly_hex(sg_fd, op, inhex_len); + case 0xb3: /* VPD pages in B0h to BFh range depend on pdt */ + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (! op->do_raw && (op->do_hex < 2)) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: + printf("VPD INQUIRY: Referrals VPD page (SBC)\n"); + break; + default: + printf("VPD INQUIRY: page=0x%x, pdt=0x%x\n", 0xb3, pdt); + break; + } + } + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_b3_vpd(rp, len, op->do_hex); + } else if (! op->do_raw) + pr2serr("VPD INQUIRY: page=0xb3\n"); + break; + case VPD_UPR_EMC: /* 0xc0 */ + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Unit Path Report Page (EMC)\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, -1, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_upr_vpd_c0_emc(rp, len, op->do_hex); + break; + case VPD_RDAC_VERS: /* 0xc2 */ + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Software Version (RDAC)\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, -1, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_rdac_vpd_c2(rp, len, op->do_hex); + break; + case VPD_RDAC_VAC: /* 0xc9 */ + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: Volume Access Control (RDAC)\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, -1, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_rdac_vpd_c9(rp, len, op->do_hex); + break; + case VPD_SCSI_PORTS: + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: SCSI Ports page\n"); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (res) + break; + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_scsi_ports_vpd(rp, len, op->do_hex, op->verbose); + break; + default: + if ((pn > 0) && (pn < 0x80)) { + if (!op->do_raw && (op->do_hex < 2)) + printf("VPD INQUIRY: ASCII information page, FRU code=0x%x\n", + pn); + res = vpd_fetch_page_from_dev(sg_fd, rp, pn, mxlen, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw((const char *)rp, len); + else + decode_ascii_inf(rp, len, op->do_hex); + } + } else { + if (op->do_hex < 2) + pr2serr(" Only hex output supported. The sg_vpd and sdparm " + "utilies decode more VPD pages.\n"); + return vpd_mainly_hex(sg_fd, op, inhex_len); + } + } +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 */ + 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_ctl(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 (0x100 & oacs) + printf(" Doorbell buffer config\n"); + if (0x80 & oacs) + printf(" Virtualization management\n"); + if (0x40 & oacs) + printf(" NVMe-MI send and NVMe-MI receive\n"); + if (0x20 & oacs) + printf(" Directive send and directive receive\n"); + if (0x10 & oacs) + printf(" Device self-test\n"); + if (0x8 & oacs) + printf(" Namespace management and attachment\n"); + if (0x4 & oacs) + printf(" Firmware download and commit\n"); + if (0x2 & oacs) + printf(" Format NVM\n"); + if (0x1 & oacs) + printf(" Security send and receive\n"); + } else + printf(" No optional admin command support\n"); + oncs = sg_get_unaligned_le16(dinp + 256); + if (0x7f & oncs) { + printf(" Optional NVM command support:\n"); + if (0x40 & oncs) + printf(" Timestamp feature\n"); + if (0x20 & oncs) + printf(" Reservations\n"); + if (0x10 & oncs) + printf(" Save and Select fields non-zero\n"); + if (0x8 & oncs) + printf(" Write zeroes\n"); + if (0x4 & oncs) + printf(" Dataset management\n"); + if (0x2 & oncs) + printf(" Write uncorrectable\n"); + if (0x1 & oncs) + printf(" Compare\n"); + } else + printf(" No optional NVM command support\n"); + printf(" PCI vendor ID VID/SSVID: 0x%x/0x%x\n", + sg_get_unaligned_le16(dinp + 0), + sg_get_unaligned_le16(dinp + 2)); + printf(" IEEE OUI Identifier: 0x%x\n", + sg_get_unaligned_le24(dinp + 73)); + got_fguid = ! sg_all_zeros(dinp + 112, 16); + if (got_fguid) { + printf(" FGUID: 0x%02x", dinp[112]); + for (k = 1; k < 16; ++k) + printf("%02x", dinp[112 + k]); + printf("\n"); + } else if (do_long) + printf(" FGUID: 0x0\n"); + printf(" Controller ID: 0x%x\n", sg_get_unaligned_le16(dinp + 78)); + if (do_long) { /* 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("\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, nsid=0) 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(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); + /* leave id_cmdp->nsid at 0 */ + id_cmdp->cdw10 = 0x1; /* CNS=0x1 Identify controller */ + 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_ctl(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[]) +{ + int res, n, err; + int sg_fd = -1; + int ret = 0; + int inhex_len = 0; + const struct svpd_values_name_t * vnp; + struct opts_t opts; + struct opts_t * op; + + op = &opts; + memset(op, 0, sizeof(opts)); + op->page_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; + } + if (op->page_arg) { + if (op->page_num >= 0) { + pr2serr("Given '-p' option and another option that " + "implies a page\n"); + return SG_LIB_CONTRADICT; + } + if (isalpha(op->page_arg[0])) { + vnp = sdp_find_vpd_by_acron(op->page_arg); + if (NULL == vnp) { +#ifdef SG_SCSI_STRINGS + if (op->opt_new) + pr2serr("abbreviation %s given to '--page=' " + "not recognized\n", op->page_arg); + else + pr2serr("abbreviation %s given to '-p=' " + "not recognized\n", op->page_arg); +#else + pr2serr("abbreviation %s given to '--page=' " + "not recognized\n", op->page_arg); +#endif + pr2serr(">>> Available abbreviations:\n"); + enumerate_vpds(); + return SG_LIB_SYNTAX_ERROR; + } + if ((1 != op->do_hex) && (0 == op->do_raw)) + op->do_decode = true; + op->page_num = vnp->value; + op->page_pdt = vnp->pdt; + } else if ('-' == op->page_arg[0]) + op->page_num = VPD_NOPE_WANT_STD_INQ; + else { +#ifdef SG_SCSI_STRINGS + if (op->opt_new) { + n = sg_get_num(op->page_arg); + 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_arg, "%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_arg); + 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->page_num = n; + } + } + 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->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 = f2hex_arr(op->inhex_fn, op->do_raw, 0, 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->page_num) { /* may be able to deduce VPD page */ + if (op->page_pdt < 0) + op->page_pdt = 0x1f & 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->page_num = 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 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->page_num) + op->page_num = -1; /* now past guessing, set to normal indication */ + + if (op->do_export) { + if (op->page_num != -1) { + if (op->page_num != VPD_DEVICE_ID && + op->page_num != 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->page_num >= 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->page_num < 0)) + op->page_num = 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->resp_len > 0) && (op->resp_len < 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, inhex_len); + else + ret = vpd_mainly_hex(-1, op, inhex_len); + 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 { + ret = std_inq_process(-1, op, inhex_len); + 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=%d\n", n, + op->page_given); + if ((3 == n) || (4 == n)) { /* NVMe char or NVMe block */ + op->possible_nvme = true; + if (! op->page_given) { + ret = do_nvme_identify(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, -1); + if (ret) + goto err_out; + } else if (op->do_cmddt) { + if (op->page_num < 0) + op->page_num = 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, -1); + if (ret) + goto err_out; + } else { + ret = vpd_mainly_hex(sg_fd, op, -1); + 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) + return sg_convert_errno(-res); + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} + + +#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 + + */ +#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 0000000..5b6b533 --- /dev/null +++ b/src/sg_inq_data.c @@ -0,0 +1,548 @@ +/* A utility program originally written for the Linux OS SCSI subsystem. +* Copyright (C) 2000-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. + + This is an auxiliary file holding data tables for the sg_inq utility. + It is mainly based on the SCSI SPC-5 document at http://www.t10.org . + +*/ + +#include +#include +#include +#include + +#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", + "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 : http://www.t10.org/lists/stds.txt 20170114 */ +/* Corrected against spc5r18 on 20180201 */ + +#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)"}, + {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 (no version claimed)"}, + {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"}, + {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 0000000..61443ff --- /dev/null +++ b/src/sg_logs.c @@ -0,0 +1,7256 @@ +/* A utility program originally written for the Linux OS SCSI subsystem. + * Copyright (C) 2000-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. + * + * This program outputs information provided by a SCSI LOG SENSE command + * and in some cases issues a LOG SELECT command. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.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 = "1.69 20180911"; /* spc5r19 + sbc4r11 */ + +#define MX_ALLOC_LEN (0xfffc) +#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 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 /* page: any */ +#define SUPP_SPGS_SUBPG 0xff /* page: any */ +#define PENDING_DEFECTS_SUBPG 0x1 /* page 0x15 */ +#define BACKGROUND_OP_SUBPG 0x2 /* page 0x15 */ +#define CACHE_STATS_SUBPG 0x20 /* 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 identifiers followed by associated mask values */ +#define VP_NONE (-1) +#define VP_SEAG 0 +#define VP_HITA 1 +#define VP_TOSH 2 +#define VP_SMSTR 3 +#define VP_LTO5 4 +#define VP_LTO6 5 +#define VP_ALL 99 + +#define MVP_OFFSET 8 +/* MVO_STD or-ed with MVP_ is T10 defined lpage with vendor specific + * parameter codes */ +#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_SMSTR (1 << (VP_SMSTR + 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 const int rsp_buff_sz = MX_ALLOC_LEN + 4; +static const int parr_sz = 4096; + +static struct option long_options[] = { + {"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'}, + {"filter", required_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"in", required_argument, 0, 'i'}, + {"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'}, + {"vendor", required_argument, 0, 'M'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +struct opts_t { + 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 filter_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 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 */ + const char * device_name; + const char * in_fn; + const char * pg_arg; + const char * vend_prod; + const struct log_elem * lep; +}; + + +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, + const struct opts_t * op); + /* 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 bool show_supported_pgs_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_supported_pgs_sub_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_buffer_over_under_run_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_error_counter_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_non_medium_error_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_last_n_error_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_format_status_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_last_n_deferred_error_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_last_n_inq_data_ch_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_last_n_mode_pg_data_ch_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_lb_provisioning_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_sequential_access_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_temperature_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_start_stop_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_utilization_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_app_client_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_self_test_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_solid_state_media_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_device_stats_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_media_stats_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_dt_device_status_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_tapealert_response_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_requested_recovery_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_background_scan_results_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_zoned_block_dev_stats(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_pending_defects_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_background_op_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_lps_misalignment_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_element_stats_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_service_buffer_info_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_ata_pt_results_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_tape_diag_data_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_mchanger_diag_data_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_non_volatile_cache_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_volume_stats_pages(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_protocol_specific_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_stats_perform_pages(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_cache_stats_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_power_condition_transitions_page(const uint8_t * resp, + int len, const struct opts_t * op); +static bool show_environmental_reporting_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_environmental_limits_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_data_compression_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_tape_alert_ssc_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_ie_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_tape_usage_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_tape_capacity_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_seagate_cache_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_seagate_factory_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_hgst_perf_page(const uint8_t * resp, int len, + const struct opts_t * op); +static bool show_hgst_misc_page(const uint8_t * resp, int len, + const struct opts_t * op); + +/* 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", "we", + show_error_counter_page}, /* 0x2, 0x0 */ + {READ_ERR_LPAGE, 0, 0, -1, MVP_STD, "Read error", "re", + show_error_counter_page}, /* 0x3, 0x0 */ + {READ_REV_ERR_LPAGE, 0, 0, -1, MVP_STD, "Read reverse error", "rre", + show_error_counter_page}, /* 0x4, 0x0 */ + {VERIFY_ERR_LPAGE, 0, 0, -1, MVP_STD, "Verify error", "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 */ + {0x13, 0, 0, PDT_TAPE, MVP_STD, "Requested recovery", "rr", + show_requested_recovery_page}, /* 0x13, 0x0 SSC,ADC */ + {0x14, 0, 0, PDT_TAPE, MVP_STD, "Device statistics", "ds", + show_device_stats_page}, /* 0x14, 0x0 SSC,ADC */ + {0x14, 0, 0, PDT_MCHANGER, MVP_STD, "Media changer statistics", "mcs", + show_media_stats_page}, /* 0x14, 0x0 SMC */ + {0x14, ZONED_BLOCK_DEV_STATS_SUBPG, 0, 0, MVP_STD, /* 0x14,0x1 zbc2r01 */ + "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_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, 0x20, 0, -1, MVP_STD, "Cache memory statistics", "cms", + show_cache_stats_page}, /* 0x19, 0x20 */ + {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_SMSTR), "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_SMSTR, "smstr", "SmrtStor (Sandisk)", "SmrtStor", 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" + " [--filter=FL] [--help] [--hex] [--in=FN] " + "[--list]\n" + " [--no_inq] [--maxlen=LEN] [--name] [--page=PG]\n" + " [--paramp=PP] [--pcb] [--ppc] [--pdt=DT] " + "[--raw]\n" + " [--readonly] [--reset] [--select] [--sp] " + "[--temperature]\n" + " [--transport] [--vendor=VP] [--verbose] " + "[--version]\n" + " 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" + " --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.\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" + " --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 parameter pointer (decimal) (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" + " --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] [-f=FL] " + "[-h]\n" + " [-H] [-i=FN] [-l] [-L] [-m=LEN] [-M=VP] [-n] " + "[-p=PG]\n" + " [-paramp=PP] [-pcb] [-ppc] [-r] [-select] [-sp] " + "[-t] [-T]\n" + " [-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" + " -f=FL filter match parameter code or pdt\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" + " -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 k, j; + struct log_elem * lep; + struct log_elem ** lepp; + struct log_elem ** lep_arr; + + if (op->do_enumerate < 3) { /* -e, -ee: sort by acronym */ + 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) +{ + size_t len, k; + const struct vp_name_t * vpp; + + for (vpp = vp_arr; vpp->acron; ++vpp) { + len = strlen(vpp->acron); + for (k = 0; k < len; ++k) { + if (tolower(vp_ap[k]) != vpp->acron[k]) + break; + } + if (k < len) + continue; + return vpp->vend_prod_num; + } + return VP_NONE; +} + +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); + bool matched; + 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) { + 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 +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[]) +{ + int c, n; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "aAbc:D:ef:hHi:lLm:M:nNOp:P:qQrRsStTvV" + "xX", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'a': + ++op->do_all; + break; + case 'A': /* not documented: compatibility with old interface */ + 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': + 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 '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 'h': + case '?': + ++op->do_help; + break; + case 'H': + ++op->do_hex; + break; + case 'i': + op->in_fn = optarg; + 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) || (n > 0xffff)) { + pr2serr("bad argument to '--maxlen=', from 2 to 65535 " + "(inclusive) expected\n"); + usage(2); + return SG_LIB_SYNTAX_ERROR; + } + op->maxlen = n; + 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 '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, plen, num, n; + 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 '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 '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 '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) || (n > MX_ALLOC_LEN)) { + pr2serr("Bad maximum response length after '-m=' " + "option\n"); + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + 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; + char * xp; + const struct log_elem * lep; + char b[80]; + + if (isalpha(ccp[0])) { + 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 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 in_hex, + char * b, int blen) +{ + if (sg_all_ffs(xp, num_bytes)) + snprintf(b, blen, "unknown"); + else { + uint64_t num = sg_get_unaligned_be(num_bytes, xp); + + if (in_hex) + snprintf(b, blen, "0x%" PRIx64, num); + else + snprintf(b, blen, "%" PRIu64, num); + } + return b; +} + +/* Read ASCII hex bytes or binary from fname (a file named '-' taken as + * stdin). If reading ASCII hex then there should be either one entry per + * line or a comma, space or tab separated list of bytes. If no_space is + * set then a string of ACSII hex digits is expected, 2 per byte. Everything + * from and including a '#' on a line is ignored. Returns 0 if ok, error + * code. */ +static int +f2hex_arr(const char * fname, bool as_binary, bool no_space, + uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) +{ + bool split_line, has_stdin; + int fn_len, in_len, k, j, m, fd, err; + int off = 0; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return SG_LIB_LOGIC_ERROR; + fn_len = strlen(fname); + if (0 == fn_len) + return SG_LIB_SYNTAX_ERROR; + has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ + if (as_binary) { + if (has_stdin) { + fd = STDIN_FILENO; + if (sg_set_binary_mode(STDIN_FILENO) < 0) + perror("sg_set_binary_mode"); + } else { + fd = open(fname, O_RDONLY); + if (fd < 0) { + err = errno; + pr2serr("unable to open binary file %s: %s\n", fname, + safe_strerror(err)); + return sg_convert_errno(err); + } else if (sg_set_binary_mode(fd) < 0) + perror("sg_set_binary_mode"); + } + k = read(fd, mp_arr, max_arr_len); + if (k <= 0) { + if (0 == k) + pr2serr("read 0 bytes from binary file %s\n", fname); + else + pr2serr("read from binary file %s: %s\n", fname, + safe_strerror(errno)); + if (! has_stdin) + close(fd); + return SG_LIB_SYNTAX_ERROR; + } + *mp_arr_len = k; + if (! has_stdin) + close(fd); + return 0; + } else { /* So read the file as ASCII hex */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + err = errno; + pr2serr("Unable to open %s for reading: %s\n", fname, + safe_strerror(err)); + return sg_convert_errno(err); + } + } + } + + carry_over[0] = 0; + for (j = 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(line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%4x", &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); + 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; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%4x", &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) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + if (stdin != fp) + fclose(fp); + return 0; +bad: + if (stdin != fp) + fclose(fp); + return SG_LIB_SYNTAX_ERROR; +} + + +/* 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; +} + +/* 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; +} + +/* SUPP_PAGES_LPAGE [0x0,0x0] */ +static bool +show_supported_pgs_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, k, pg_code; + const uint8_t * bp; + const struct log_elem * lep; + char b[64]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Supported log pages [0x0]:\n"); /* introduced: SPC-2 */ + num = len - 4; + bp = &resp[0] + 4; + for (k = 0; k < num; ++k) { + pg_code = bp[k]; + 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) + printf(" %s\n", lep->name); + else if (op->do_brief) + printf("%s%s\n", b, lep->name); + else + printf("%s%s [%s]\n", b, lep->name, lep->acron); + } else + printf("%s\n", b); + } + return true; +} + +/* SUPP_PAGES_LPAGE,SUPP_SPGS_SUBPG [0x0,0xff] or all subpages of a given + * page code: [,0xff] */ +static bool +show_supported_pgs_sub_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, k, pg_code, subpg_code; + const uint8_t * bp; + const struct log_elem * lep; + char b[64]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) { + if (op->pg_code > 0) + printf("Supported subpages [0x%x, 0xff]:\n", op->pg_code); + else + printf("Supported log pages and subpages [0x0, 0xff]:\n"); + } + num = len - 4; + bp = &resp[0] + 4; + for (k = 0; k < num; k += 2) { + pg_code = bp[k]; + subpg_code = bp[k + 1]; + 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); + lep = pg_subpg_pdt_search(pg_code, subpg_code, op->dev_pdt, -1); + if (lep) { + if (op->do_brief > 1) + printf(" %s\n", lep->name); + else if (op->do_brief) + printf("%s%s\n", b, lep->name); + else + printf("%s%s [%s]\n", b, lep->name, lep->acron); + } else + printf("%s\n", b); + } + return true; +} + +/* BUFF_OVER_UNDER_LPAGE [0x1] introduced: SPC-2 */ +static bool +show_buffer_over_under_run_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + uint64_t count; + const uint8_t * bp; + const char * cp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Buffer over-run/under-run page [0x1]\n"); + num = len - 4; + bp = &resp[0] + 4; + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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: + printf(" undefined parameter code [0x%x], count = %" PRIu64 "", + pc, count); + break; + } + if (cp) + printf(" %s = %" PRIu64 "", cp, count); + + printf("\n"); + 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 true; +} + +/* WRITE_ERR_LPAGE; READ_ERR_LPAGE; READ_REV_ERR_LPAGE; VERIFY_ERR_LPAGE */ +/* [0x2, 0x3, 0x4, 0x5] introduced: SPC-3 */ +static bool +show_error_counter_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, pg_code; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + pg_code = resp[0] & 0x3f; + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) { + switch(pg_code) { + case WRITE_ERR_LPAGE: + printf("Write error counter page [0x%x]\n", pg_code); + break; + case READ_ERR_LPAGE: + printf("Read error counter page [0x%x]\n", pg_code); + break; + case READ_REV_ERR_LPAGE: + printf("Read Reverse error counter page [0x%x]\n", + pg_code); + break; + case VERIFY_ERR_LPAGE: + printf("Verify error counter page [0x%x]\n", pg_code); + break; + default: + pr2serr("expecting error counter page, got page = 0x%x\n", + resp[0]); + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0: printf(" Errors corrected without substantial delay"); break; + case 1: printf(" Errors corrected with possible delays"); break; + case 2: printf(" Total rewrites or rereads"); break; + case 3: printf(" Total errors corrected"); break; + case 4: printf(" Total times correction algorithm processed"); break; + case 5: printf(" Total bytes processed"); break; + case 6: printf(" Total uncorrected errors"); break; + case 0x8009: printf(" Track following errors [Hitachi]"); break; + case 0x8015: printf(" Positioning errors [Hitachi]"); break; + default: printf(" Reserved or vendor specific [0x%x]", pc); break; + } + 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* NON_MEDIUM_LPAGE [0x6] introduced: SPC-2 */ +static bool +show_non_medium_error_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Non-medium error page [0x6]\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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0: + printf(" Non-medium error count"); + break; + default: + if (pc <= 0x7fff) + printf(" Reserved [0x%x]", pc); + else + printf(" Vendor specific [0x%x]", pc); + break; + } + 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* PCT_LPAGE [0x1a] introduced: SPC-4 */ +static bool +show_power_condition_transitions_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Power condition transitions page [0x1a]\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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 1: + printf(" Accumulated transitions to active"); break; + case 2: + printf(" Accumulated transitions to idle_a"); break; + case 3: + printf(" Accumulated transitions to idle_b"); break; + case 4: + printf(" Accumulated transitions to idle_c"); break; + case 8: + printf(" Accumulated transitions to standby_z"); break; + case 9: + printf(" Accumulated transitions to standby_y"); break; + default: + printf(" Reserved [0x%x]", pc); + } + 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))); + 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, "not available"); + 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, "not available"); + else + snprintf(b, blen, "no limit"); + } else if (h <= 100) + snprintf(b, blen, "%u %%", h); + else + snprintf(b, blen, "reserved value [%u]", h); + return b; +} + +/* ENV_REPORTING_SUBPG [0xd,0x1] introduced: SPC-5 (rev 02). "mounted" + * changed to "other" in spc5r11 */ +static bool +show_environmental_reporting_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, blen; + bool other_valid; + const uint8_t * bp; + char str[PCB_STR_LEN]; + char b[32]; + + blen = sizeof(b); + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Environmental reporting page [0xd,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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + other_valid = !!(bp[4] & 1); + if (pc < 0x100) { + if (pl < 12) { + printf(" <>\n", pc, pl); + goto skip; + } + printf(" parameter code=0x%x\n", pc); + printf(" OTV=%d\n", (int)other_valid); + printf(" Temperature: %s\n", + temperature_str(bp[5], true, b, blen)); + printf(" Lifetime maximum temperature: %s\n", + temperature_str(bp[6], true, b, blen)); + printf(" Lifetime minimum temperature: %s\n", + temperature_str(bp[7], true, b, blen)); + printf(" Maximum temperature since power on: %s\n", + temperature_str(bp[8], true, b, blen)); + printf(" Minimum temperature since power on: %s\n", + temperature_str(bp[9], true, b, blen)); + if (other_valid) { + printf(" Maximum other temperature: %s\n", + temperature_str(bp[10], true, b, blen)); + printf(" Minimum other temperature: %s\n", + temperature_str(bp[11], true, b, blen)); + } + } else if (pc < 0x200) { + if (pl < 12) { + printf(" <>\n", pc, pl); + goto skip; + } + printf(" parameter code=0x%x\n", pc); + printf(" ORHV=%d\n", (int)other_valid); + printf(" Relative humidity: %s\n", + humidity_str(bp[5], true, b, blen)); + printf(" Lifetime maximum relative humidity: %s\n", + humidity_str(bp[6], true, b, blen)); + printf(" Lifetime minimum relative humidity: %s\n", + humidity_str(bp[7], true, b, blen)); + printf(" Maximum relative humidity since power on: %s\n", + humidity_str(bp[8], true, b, blen)); + printf(" Minimum relative humidity since power on: %s\n", + humidity_str(bp[9], true, b, blen)); + if (other_valid) { + printf(" Maximum other relative humidity: %s\n", + temperature_str(bp[10], true, b, blen)); + printf(" Minimum other relative humidity: %s\n", + temperature_str(bp[11], true, b, blen)); + } + } else + printf(" <do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* ENV_LIMITS_SUBPG [0xd,0x2] introduced: SPC-5 (rev 02) */ +static bool +show_environmental_limits_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, blen; + const uint8_t * bp; + char str[PCB_STR_LEN]; + char b[32]; + + blen = sizeof(b); + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Environmental limits page [0xd,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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + if (pc < 0x100) { + if (pl < 12) { + printf(" <>\n", pc, pl); + goto skip; + } + printf(" High critical temperature limit trigger: %s\n", + temperature_str(bp[4], false, b, blen)); + printf(" High critical temperature limit reset: %s\n", + temperature_str(bp[5], false, b, blen)); + printf(" Low critical temperature limit reset: %s\n", + temperature_str(bp[6], false, b, blen)); + printf(" Low critical temperature limit trigger: %s\n", + temperature_str(bp[7], false, b, blen)); + printf(" High operating temperature limit trigger: %s\n", + temperature_str(bp[8], false, b, blen)); + printf(" High operating temperature limit reset: %s\n", + temperature_str(bp[9], false, b, blen)); + printf(" Low operating temperature limit reset: %s\n", + temperature_str(bp[10], false, b, blen)); + printf(" Low operating temperature limit trigger: %s\n", + temperature_str(bp[11], false, b, blen)); + } else if (pc < 0x200) { + printf(" High critical relative humidity limit trigger: %s\n", + humidity_str(bp[4], false, b, blen)); + printf(" High critical relative humidity limit reset: %s\n", + humidity_str(bp[5], false, b, blen)); + printf(" Low critical relative humidity limit reset: %s\n", + humidity_str(bp[6], false, b, blen)); + printf(" Low critical relative humidity limit trigger: %s\n", + humidity_str(bp[7], false, b, blen)); + printf(" High operating relative humidity limit trigger: %s\n", + humidity_str(bp[8], false, b, blen)); + printf(" High operating relative humidity limit reset: %s\n", + humidity_str(bp[9], false, b, blen)); + printf(" Low operating relative humidity limit reset: %s\n", + humidity_str(bp[10], false, b, blen)); + printf(" Low operating relative humidity limit trigger: %s\n", + humidity_str(bp[11], false, b, blen)); + } else + printf(" <do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + 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, const struct opts_t * op) +{ + int k, num, extra, pc; + unsigned int n; + uint64_t ull; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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) { + 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, ((1 == op->do_hex) ? 1 : -1)); + 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 parameter code = 0x%x, contents in " + "hex:\n", 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, const struct opts_t * op) +{ + bool valid = false; + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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) { + 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, ((1 == op->do_hex) ? 1 : -1)); + 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 parameter code = 0x%x", 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, + const struct opts_t * op) +{ + int k, num, extra, pc; + unsigned int n; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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) { + 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, ((1 == op->do_hex) ? 1 : -1)); + 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 parameter code = 0x%x, contents in " + "hex:\n", 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 */ +static bool +show_data_compression_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int k, j, pl, num, extra, pc, pg_code; + uint64_t n; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + if ((0 == pl) || (pl > 8)) { + printf("badly formed data compression log parameter\n"); + printf(" parameter code = 0x%x, contents in hex:\n", pc); + hex2stdout(bp, extra, 1); + 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 parameter code = 0x%x, contents in " + "hex:\n", pc); + hex2stdout(bp, extra, 1); + 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] introduced: SPC-2 */ +static bool +show_last_n_error_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int k, num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + num = len - 4; + bp = &resp[0] + 4; + if (num < 4) { + printf("No error events logged\n"); + return true; + } + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Last n error events page [0x7]\n"); + for (k = num; k > 0; k -= pl, bp += pl) { + if (k < 3) { + printf("short Last n error events page\n"); + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + printf(" Error event %d:\n", pc); + if (pl > 4) { + if ((bp[2] & 0x1) && (bp[2] & 0x2)) { + printf(" [binary]:\n"); + hex2stdout(bp + 4, pl - 4, 1); + } else if (bp[2] & 0x1) + printf(" %.*s\n", pl - 4, (const char *)(bp + 4)); + else { + printf(" [data counter?? (LP bit should be set)]:\n"); + hex2stdout(bp + 4, pl - 4, 1); + } + } + if (op->do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + if (op->filter_given) + break; + } + return true; +} + +/* LAST_N_DEFERRED_LPAGE [0xb] introduced: SPC-2 */ +static bool +show_last_n_deferred_error_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int k, num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + num = len - 4; + bp = &resp[0] + 4; + if (num < 4) { + printf("No deferred errors logged\n"); + return true; + } + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Last n deferred errors page [0xb]\n"); + for (k = num; k > 0; k -= pl, bp += pl) { + if (k < 3) { + printf("short Last n deferred errors page\n"); + return true; + } + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + printf(" Deferred error %d:\n", pc); + hex2stdout(bp + 4, pl - 4, 1); + if (op->do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + if (op->filter_given) + break; + } + return true; +} + +/* LAST_N_INQUIRY_DATA_CH_SUBPG [0xb,0x1] introduced: SPC-5 (rev 17) */ +static bool +show_last_n_inq_data_ch_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int j, num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Last n Inquiry data changed [0xb,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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + if (0 == pc) { + if (pl < 8) { + printf(" <>\n", pc, pl); + goto skip; + } + printf(" Generation number of Inquiry data changed indication: " + "%u\n", sg_get_unaligned_be32(bp + 4)); + if (pl > 11) + printf(" Generation number of Mode page data changed " + "indication: %u\n", sg_get_unaligned_be32(bp + 8)); + for (j = 12; j < pl; j +=4) + printf(" Generation number of indication [0x%x]: %u\n", + (j / 4), sg_get_unaligned_be32(bp + j)); + } else { + printf(" Parameter code 0x%x, ", pc); + if (1 & *(bp + 4)) + printf("VPD page 0x%x changed\n", *(bp + 5)); + else + printf("Standard Inquiry data changed\n"); + } + 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 true; +} + +/* LAST_N_MODE_PG_DATA_CH_SUBPG [0xb,0x2] introduced: SPC-5 (rev 17) */ +static bool +show_last_n_mode_pg_data_ch_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int j, num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Last n Mode page data changed [0xb,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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + if (0 == pc) { /* Same as LAST_N_INQUIRY_DATA_CH_SUBPG [0xb,0x1] */ + if (pl < 8) { + printf(" <>\n", pc, pl); + goto skip; + } + printf(" Generation number of Inquiry data changed indication: " + "%u\n", sg_get_unaligned_be32(bp + 4)); + if (pl > 11) + printf(" Generation number of Mode page data changed " + "indication: %u\n", sg_get_unaligned_be32(bp + 8)); + for (j = 12; j < pl; j +=4) + printf(" Generation number of indication [0x%x]: %u\n", + (j / 4), sg_get_unaligned_be32(bp + j)); + } else { + printf(" Parameter code 0x%x, ", pc); + if (0x40 & *(bp + 5)) /* SPF bit set */ + printf("Mode page 0x%x,0%x changed\n", (0x3f & *(bp + 5)), + *(bp + 6)); + else + printf("Mode page 0x%x changed\n", (0x3f & *(bp + 5))); + } + 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 true; +} + +static const char * self_test_code[] = { + "default", "background short", "background extended", "reserved", + "aborted background", "foreground short", "foreground extended", + "reserved"}; + +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", + "reserved", "reserved", "reserved", "reserved", "reserved", "reserved", + "reserved", + "self test in progress"}; + +/* SELF_TEST_LPAGE [0x10] introduced: SPC-3 */ +static bool +show_self_test_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int k, num, n, res, pc, pl; + unsigned int v; + const uint8_t * bp; + uint64_t ull; + char str[PCB_STR_LEN]; + char b[80]; + + num = len - 4; + if (num < 0x190) { + pr2serr("short self-test results page [length 0x%x rather than " + "0x190 bytes]\n", num); + return true; + } + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Self-test results page [0x10]\n"); + for (k = 0, bp = resp + 4; k < 20; ++k, bp += 20 ) { + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + n = sg_get_unaligned_be16(bp + 6); + if ((0 == n) && (0 == bp[4])) + break; + printf(" Parameter code = %d, accumulated power-on hours = %d\n", + pc, n); + printf(" self-test code: %s [%d]\n", + self_test_code[(bp[4] >> 5) & 0x7], (bp[4] >> 5) & 0x7); + res = bp[4] & 0xf; + printf(" self-test result: %s [%d]\n", self_test_result[res], res); + if (bp[5]) + printf(" self-test number = %d\n", (int)bp[5]); + if (! sg_all_ffs(bp + 8, 8)) { + ull = sg_get_unaligned_be64(bp + 8); + if ((res > 0) && ( res < 0xf)) + printf(" address of first error = 0x%" PRIx64 "\n", ull); + } + v = bp[16] & 0xf; + if (v) { + printf(" sense key = 0x%x [%s] , asc = 0x%x, ascq = 0x%x", + v, sg_get_sense_key_str(v, sizeof(b), b), bp[17], + bp[18]); + if (bp[17] || bp[18]) + printf(" [%s]\n", sg_get_asc_ascq_str(bp[17], bp[18], + sizeof(b), b)); + } + printf("\n"); + if (op->do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + if (op->filter_given) + break; + } + return true; +} + +/* TEMPERATURE_LPAGE [0xd] introduced: SPC-3 */ +static bool +show_temperature_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int k, num, extra, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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) + printf("Temperature page [0xd]\n"); + } + for (k = num; k > 0; k -= extra, bp += extra) { + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, extra, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0: + if ((extra > 5) && (k > 5)) { + if (0 == bp[5]) + printf(" Current temperature = 0 C (or less)\n"); + else if (bp[5] < 0xff) + printf(" Current temperature = %d C\n", bp[5]); + else + printf(" Current temperature = \n"); + } + break; + case 1: + if ((extra > 5) && (k > 5)) { + if (bp[5] < 0xff) + printf(" Reference temperature = %d C\n", bp[5]); + else + printf(" Reference temperature = \n"); + } + break; + default: + if (! op->do_temperature) { + printf(" unknown parameter code = 0x%x, contents in " + "hex:\n", pc); + hex2stdout(bp, extra, 1); + } else + continue; + break; + } + if (op->do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + if (op->filter_given) + break; + } + return true; +} + +/* START_STOP_LPAGE [0xe] introduced: SPC-3 */ +static bool +show_start_stop_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int k, num, extra, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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))) + printf("Start-stop cycle counter page [0xe]\n"); + for (k = num; k > 0; k -= extra, bp += extra) { + if (k < 3) { + pr2serr("short Start-stop cycle counter 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, extra, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 1: + if (10 == extra) + printf(" Date of manufacture, year: %.4s, week: %.2s", + &bp[4], &bp[8]); + else if (op->verbose) { + pr2serr(" Date of manufacture parameter length strange: " + "%d\n", extra - 4); + hex2stderr(bp, extra, 1); + } + break; + case 2: + if (10 == extra) + printf(" Accounting date, year: %.4s, week: %.2s", + &bp[4], &bp[8]); + else if (op->verbose) { + pr2serr(" Accounting date parameter length strange: %d\n", + extra - 4); + hex2stderr(bp, extra, 1); + } + break; + case 3: + if (extra > 7) { + if (sg_all_ffs(bp + 4, 4)) + printf(" Specified cycle count over device lifetime " + "= -1"); + else + printf(" Specified cycle count over device lifetime " + "= %u", sg_get_unaligned_be32(bp + 4)); + } + break; + case 4: + if (extra > 7) { + if (sg_all_ffs(bp + 4, 4)) + printf(" Accumulated start-stop cycles = -1"); + else + printf(" Accumulated start-stop cycles = %u", + sg_get_unaligned_be32(bp + 4)); + } + break; + case 5: + if (extra > 7) { + if (sg_all_ffs(bp + 4, 4)) + printf(" Specified load-unload count over device " + "lifetime = -1"); + else + printf(" Specified load-unload count over device " + "lifetime = %u", sg_get_unaligned_be32(bp + 4)); + } + break; + case 6: + if (extra > 7) { + if (sg_all_ffs(bp + 4, 4)) + printf(" Accumulated load-unload cycles = -1"); + else + printf(" Accumulated load-unload cycles = %u", + sg_get_unaligned_be32(bp + 4)); + } + break; + default: + printf(" unknown parameter code = 0x%x, contents in " + "hex:\n", 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; +} + +/* APP_CLIENT_LPAGE [0xf] introduced: SPC-3 */ +static bool +show_app_client_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int k, num, extra, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + num = len - 4; + bp = &resp[0] + 4; + if (num < 4) { + pr2serr("badly formed Application Client page\n"); + return false; + } + if (op->verbose || ((! op->do_raw) && (op->do_hex == 0))) + printf("Application client page [0xf]\n"); + if (0 == op->filter_given) { + if ((len > 128) && (0 == op->do_hex)) { + hex2stdout(resp, 64, 1); + printf(" ..... [truncated after 64 of %d bytes (use '-H' to " + "see the rest)]\n", len); + } + else + hex2stdout(resp, len, 1); + return true; + } + /* only here if filter_given set */ + for (k = num; k > 0; k -= extra, bp += extra) { + if (k < 3) { + pr2serr("short Application client page\n"); + return true; + } + extra = bp[3] + 4; + pc = sg_get_unaligned_be16(bp + 0); + if (op->filter != pc) + continue; + if (op->do_raw) + dStrRaw(bp, extra); + else if (0 == op->do_hex) + hex2stdout(bp, extra, 0); + else if (1 == op->do_hex) + hex2stdout(bp, extra, 1); + else + hex2stdout(bp, extra, -1); + printf("\n"); + if (op->do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + break; + } + return true; +} + +/* IE_LPAGE [0x2f] "Informational Exceptions" introduced: SPC-3 */ +static bool +show_ie_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int k, num, param_len, pc; + const uint8_t * bp; + const char * cp; + char str[PCB_STR_LEN]; + char b[256]; + char bb[32]; + bool full, decoded; + bool has_header = false; + bool is_smstr = op->lep ? (MVP_SMSTR & op->lep->flags) : + (VP_SMSTR == op->vend_prod_num); + + full = ! op->do_temperature; + if ('\0' != t10_vendor_str[0]) { + if (0 != strcmp(vp_arr[VP_SMSTR].t10_vendorp, t10_vendor_str)) + is_smstr = false; /* Inquiry vendor string says not SmrtStor */ + } + num = len - 4; + bp = &resp[0] + 4; + if (num < 4) { + pr2serr("badly formed Informational Exceptions page\n"); + return false; + } + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) { + if (full) + printf("Informational Exceptions page [0x2f]\n"); + } + for (k = num; k > 0; k -= param_len, bp += param_len) { + if (k < 3) { + printf("short Informational Exceptions page\n"); + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, param_len, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + decoded = true; + cp = NULL; + + switch (pc) { + case 0x0: + if (param_len > 5) { + if (full) { + printf(" IE asc = 0x%x, ascq = 0x%x", bp[4], bp[5]); + if (bp[4] || bp[5]) + if(sg_get_asc_ascq_str(bp[4], bp[5], sizeof(b), b)) + printf("\n [%s]", b); + } + if (param_len > 6) { + if (bp[6] < 0xff) + printf("\n Current temperature = %d C", bp[6]); + else + printf("\n Current temperature = "); + if (param_len > 7) { + if (bp[7] < 0xff) + printf("\n Threshold temperature = %d C " + "[common extension]", bp[7]); + else + printf("\n Threshold temperature = "); + if ((param_len > 8) && (bp[8] >= bp[6])) { + if (bp[8] < 0xff) + printf("\n Maximum temperature = %d C " + "[(since new), extension]", bp[8]); + else + printf("\n Maximum temperature = "); + } + } + } + decoded = true; + } + break; + default: + if ((! is_smstr) || (param_len < 24)) { + decoded = false; + break; + } + switch (pc) { + case 0x1: + cp = "Read error rate"; + break; + case 0x2: + cp = "Flash rom check"; + break; + case 0x5: + cp = "Realloc block count"; + break; + case 0x9: + cp = "Power on hours"; + break; + case 0xc: + cp = "Power cycles"; + break; + case 0xd: + cp = "Ecc rate"; + break; + case 0x20: + cp = "Write amp"; + break; + case 0xb1: /* 177 */ + cp = "Percent life remaining"; + break; + case 0xb4: /* 180 */ + cp = "Unused reserved block count"; + break; + case 0xb5: /* 181 */ + cp = "Program fail count"; + break; + case 0xb6: /* 182 */ + cp = "Erase fail count"; + break; + case 0xbe: /* 190 */ + cp = "Drive temperature warn"; + break; + case 0xc2: /* 194 */ + cp = "Drive temperature"; + break; + case 0xc3: /* 195 */ + cp = "Uncorrected error count"; + break; + case 0xc6: /* 198 */ + cp = "Offline scan uncorrected sector count"; + break; + case 0xe9: /* 233 */ + cp = "Number of writes"; + break; + default: + snprintf(bb, sizeof(bb), "parameter_code=0x%x (%d)", + pc, pc); + cp = bb; + break; + } + break; + } /* end of switch statement */ + if (cp && (param_len >= 24)) { + if (! has_header) { + has_header = true; + printf(" Has|Ever %% to worst %% Current " + "Worst Threshold Attribute\n"); + printf(" tripped fail to fail " + "value value\n"); + } + printf(" %2d %2d %4d %4d %10u %10u %10u %s", + !!(0x80 & bp[4]), !!(0x40 & bp[4]), bp[5], bp[6], + sg_get_unaligned_be32(bp + 8), + sg_get_unaligned_be32(bp + 12), + sg_get_unaligned_be32(bp + 16), + cp); + /* decoded = true; */ + } else if ((! decoded) && full) { + printf(" parameter code = 0x%x, contents in hex:\n", pc); + hex2stdout(bp, param_len, 1); + } + printf("\n"); + if (op->do_pcb) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + if (op->filter_given) + break; + } /* end of for loop */ + return true; +} + +/* called for SAS port of PROTO_SPECIFIC_LPAGE [0x18] */ +static void +show_sas_phy_event_info(int pes, unsigned int val, unsigned int thresh_val) +{ + unsigned int u; + + switch (pes) { + case 0: + printf(" No event\n"); + break; + case 0x1: + printf(" Invalid word count: %u\n", val); + break; + case 0x2: + printf(" Running disparity error count: %u\n", val); + break; + case 0x3: + printf(" Loss of dword synchronization count: %u\n", val); + break; + case 0x4: + printf(" Phy reset problem count: %u\n", val); + break; + case 0x5: + printf(" Elasticity buffer overflow count: %u\n", val); + break; + case 0x6: + printf(" Received ERROR count: %u\n", val); + break; + case 0x7: + printf(" Invalid SPL packet count: %u\n", val); + break; + case 0x8: + printf(" Loss of SPL packet synchronization count: %u\n", val); + break; + case 0x20: + printf(" Received address frame error count: %u\n", val); + break; + case 0x21: + printf(" Transmitted abandon-class OPEN_REJECT count: %u\n", val); + break; + case 0x22: + printf(" Received abandon-class OPEN_REJECT count: %u\n", val); + break; + case 0x23: + printf(" Transmitted retry-class OPEN_REJECT count: %u\n", val); + break; + case 0x24: + printf(" Received retry-class OPEN_REJECT count: %u\n", val); + break; + case 0x25: + printf(" Received AIP (WATING ON PARTIAL) count: %u\n", val); + break; + case 0x26: + printf(" Received AIP (WAITING ON CONNECTION) count: %u\n", val); + break; + case 0x27: + printf(" Transmitted BREAK count: %u\n", val); + break; + case 0x28: + printf(" Received BREAK count: %u\n", val); + break; + case 0x29: + printf(" Break timeout count: %u\n", val); + break; + case 0x2a: + printf(" Connection count: %u\n", val); + break; + case 0x2b: + printf(" Peak transmitted pathway blocked count: %u\n", + val & 0xff); + printf(" Peak value detector threshold: %u\n", + thresh_val & 0xff); + break; + case 0x2c: + u = val & 0xffff; + if (u < 0x8000) + printf(" Peak transmitted arbitration wait time (us): " + "%u\n", u); + else + printf(" Peak transmitted arbitration wait time (ms): " + "%u\n", 33 + (u - 0x8000)); + u = thresh_val & 0xffff; + if (u < 0x8000) + printf(" Peak value detector threshold (us): %u\n", + u); + else + printf(" Peak value detector threshold (ms): %u\n", + 33 + (u - 0x8000)); + break; + case 0x2d: + printf(" Peak arbitration time (us): %u\n", val); + printf(" Peak value detector threshold: %u\n", thresh_val); + break; + case 0x2e: + printf(" Peak connection time (us): %u\n", val); + printf(" Peak value detector threshold: %u\n", thresh_val); + break; + case 0x2f: + printf(" Persistent connection count: %u\n", val); + break; + case 0x40: + printf(" Transmitted SSP frame count: %u\n", val); + break; + case 0x41: + printf(" Received SSP frame count: %u\n", val); + break; + case 0x42: + printf(" Transmitted SSP frame error count: %u\n", val); + break; + case 0x43: + printf(" Received SSP frame error count: %u\n", val); + break; + case 0x44: + printf(" Transmitted CREDIT_BLOCKED count: %u\n", val); + break; + case 0x45: + printf(" Received CREDIT_BLOCKED count: %u\n", val); + break; + case 0x50: + printf(" Transmitted SATA frame count: %u\n", val); + break; + case 0x51: + printf(" Received SATA frame count: %u\n", val); + break; + case 0x52: + printf(" SATA flow control buffer overflow count: %u\n", val); + break; + case 0x60: + printf(" Transmitted SMP frame count: %u\n", val); + break; + case 0x61: + printf(" Received SMP frame count: %u\n", val); + break; + case 0x63: + printf(" Received SMP frame error count: %u\n", val); + break; + default: + printf(" Unknown phy event source: %d, val=%u, thresh_val=%u\n", + pes, val, thresh_val); + break; + } +} + +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, + const struct opts_t * op) +{ + int j, m, nphys, t, sz, spld_len; + const uint8_t * vcp; + uint64_t ull; + unsigned int ui; + char str[PCB_STR_LEN]; + char s[64]; + + sz = sizeof(s); + t = sg_get_unaligned_be16(bp + 0); + if (op->do_name) + printf("rel_target_port=%d\n", t); + else + printf("relative target port id = %d\n", t); + if (op->do_name) + printf(" gen_code=%d\n", bp[6]); + else + printf(" generation code = %d\n", bp[6]); + nphys = bp[7]; + if (op->do_name) + printf(" num_phys=%d\n", nphys); + else { + printf(" number of phys = %d\n", nphys); + if ((op->do_pcb) && (! op->do_name)) + printf(" <%s>\n", get_pcb_str(bp[2], str, sizeof(str))); + } + + for (j = 0, vcp = bp + 8; j < (param_len - 8); + vcp += spld_len, j += spld_len) { + if (op->do_name) + printf(" phy_id=%d\n", vcp[1]); + else + printf(" phy identifier = %d\n", vcp[1]); + 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); + printf(" att_dev_type=%d\n", t); + printf(" att_iport_mask=0x%x\n", vcp[6]); + printf(" att_phy_id=%d\n", vcp[24]); + printf(" att_reason=0x%x\n", (vcp[4] & 0xf)); + ull = sg_get_unaligned_be64(vcp + 16); + printf(" att_sas_addr=0x%" PRIx64 "\n", ull); + printf(" att_tport_mask=0x%x\n", vcp[7]); + ui = sg_get_unaligned_be32(vcp + 32); + printf(" inv_dwords=%u\n", ui); + ui = sg_get_unaligned_be32(vcp + 40); + printf(" loss_dword_sync=%u\n", ui); + printf(" neg_log_lrate=%d\n", 0xf & vcp[5]); + ui = sg_get_unaligned_be32(vcp + 44); + printf(" phy_reset_probs=%u\n", ui); + ui = sg_get_unaligned_be32(vcp + 36); + printf(" running_disparity=%u\n", ui); + printf(" reason=0x%x\n", (vcp[5] & 0xf0) >> 4); + ull = sg_get_unaligned_be64(vcp + 8); + printf(" 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 attached"); 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, "reserved [%d]", t); break; + } + /* the word 'SAS' in following added in spl4r01 */ + printf(" attached SAS device type: %s\n", s); + t = 0xf & vcp[4]; + switch (t) { + case 0: snprintf(s, sz, "unknown"); 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, "reserved [0x%x]", t); break; + } + printf(" attached reason: %s\n", s); + t = (vcp[5] & 0xf0) >> 4; + switch (t) { + case 0: snprintf(s, sz, "unknown"); 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, "reserved [0x%x]", t); break; + } + printf(" reason: %s\n", s); + printf(" negotiated logical link rate: %s\n", + sas_negot_link_rate((0xf & vcp[5]), s, sz)); + printf(" attached initiator port: ssp=%d stp=%d smp=%d\n", + !! (vcp[6] & 8), !! (vcp[6] & 4), !! (vcp[6] & 2)); + printf(" attached target port: ssp=%d stp=%d smp=%d\n", + !! (vcp[7] & 8), !! (vcp[7] & 4), !! (vcp[7] & 2)); + ull = sg_get_unaligned_be64(vcp + 8); + printf(" SAS address = 0x%" PRIx64 "\n", ull); + ull = sg_get_unaligned_be64(vcp + 16); + printf(" attached SAS address = 0x%" PRIx64 "\n", ull); + printf(" attached phy identifier = %d\n", vcp[24]); + ui = sg_get_unaligned_be32(vcp + 32); + printf(" Invalid DWORD count = %u\n", ui); + ui = sg_get_unaligned_be32(vcp + 36); + printf(" Running disparity error count = %u\n", ui); + ui = sg_get_unaligned_be32(vcp + 40); + printf(" Loss of DWORD synchronization count = %u\n", ui); + ui = sg_get_unaligned_be32(vcp + 44); + printf(" Phy reset problem count = %u\n", ui); + } + if (spld_len > 51) { + int num_ped, pes; + const uint8_t * xcp; + unsigned int pvdt; + + num_ped = vcp[51]; + if (op->verbose > 1) + printf(" <>\n", num_ped, spld_len, + (spld_len - 52) / 12); + if (num_ped > 0) { + if (op->do_name) { + printf(" phy_event_desc_num=%d\n", num_ped); + return; /* don't decode at this stage */ + } else + printf(" Phy event descriptors:\n"); + } + xcp = vcp + 52; + for (m = 0; m < (num_ped * 12); m += 12, xcp += 12) { + pes = xcp[3]; + ui = sg_get_unaligned_be32(xcp + 4); + pvdt = sg_get_unaligned_be32(xcp + 8); + show_sas_phy_event_info(pes, ui, pvdt); + } + } else if (op->verbose) + printf(" <>\n"); + } +} + +/* PROTO_SPECIFIC_LPAGE [0x18] */ +static bool +show_protocol_specific_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int k, num, pl, pc, pid; + const uint8_t * bp; + + num = len - 4; + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) { + if (op->do_name) + printf("log_page=0x%x\n", PROTO_SPECIFIC_LPAGE); + } + for (k = 0, bp = resp + 4; k < num; ) { + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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 ((0 == k) && (! op->do_name)) + printf("Protocol Specific port page for SAS SSP (sas-2) " + "[0x18]\n"); + show_sas_port_param(bp, pl, op); + 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 introduced: SPC-4 */ +static bool +show_stats_perform_pages(const uint8_t * resp, int len, + const struct opts_t * op) +{ + bool nam, spf; + int k, num, param_len, param_code, subpg_code, extra; + unsigned int ui; + uint64_t ull; + const uint8_t * bp; + const char * ccp; + char str[PCB_STR_LEN]; + + nam = op->do_name; + num = len - 4; + bp = resp + 4; + spf = !!(resp[0] & 0x40); + subpg_code = spf ? resp[1] : 0; + 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 { + if (0 == subpg_code) + printf("General Statistics and Performance [0x19]\n"); + else + printf("Group Statistics and Performance (%d) " + "[0x19,0x%x]\n", subpg_code, subpg_code); + } + } + if (subpg_code > 31) + return false; + if (0 == subpg_code) { /* General statistics and performance log page */ + if (num < 0x5c) + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, extra, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (param_code) { + case 1: /* Statistics and performance log parameter */ + ccp = nam ? "parameter_code=1" : "Statistics and performance " + "log parameter"; + printf("%s\n", ccp); + ull = sg_get_unaligned_be64(bp + 4); + ccp = nam ? "read_commands=" : "number of read commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 12); + ccp = nam ? "write_commands=" : "number of write commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 20); + ccp = nam ? "lb_received=" + : "number of logical blocks received = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 28); + ccp = nam ? "lb_transmitted=" + : "number of logical blocks transmitted = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 36); + ccp = nam ? "read_proc_intervals=" + : "read command processing intervals = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 44); + ccp = nam ? "write_proc_intervals=" + : "write command processing intervals = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + break; + case 2: /* Idle time log parameter */ + ccp = nam ? "parameter_code=2" : "Idle time log parameter"; + printf("%s\n", ccp); + ull = sg_get_unaligned_be64(bp + 4); + ccp = nam ? "idle_time_intervals=" : "idle time " + "intervals = "; + printf(" %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"; + 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; + case 4: /* FUA statistics and performance log parameter */ + ccp = nam ? "parameter_code=4" : "Force unit access " + "statistics and performance log parameter "; + printf("%s\n", ccp); + ull = sg_get_unaligned_be64(bp + 4); + ccp = nam ? "read_fua_commands=" : "number of read FUA " + "commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 12); + ccp = nam ? "write_fua_commands=" : "number of write FUA " + "commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 20); + ccp = nam ? "read_fua_nv_commands=" + : "number of read FUA_NV commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 28); + ccp = nam ? "write_fua_nv_commands=" + : "number of write FUA_NV commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 36); + ccp = nam ? "read_fua_proc_intervals=" + : "read FUA command processing intervals = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 44); + ccp = nam ? "write_fua_proc_intervals=" + : "write FUA command processing intervals = "; + printf(" %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 = "; + printf(" %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 = "; + 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 for cache stats"; + printf("%s\n", ccp); + ull = sg_get_unaligned_be64(bp + 4); + ccp = nam ? "time_interval_neg_exp=" : "time interval " + "negative exponent = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 8); + ccp = nam ? "time_interval_int=" : "time interval " + "integer = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + break; + default: + if (nam) { + printf("parameter_code=%d\n", param_code); + printf(" unknown=1\n"); + } else + pr2serr("show_performance... unknown parameter code " + "%d\n", param_code); + 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))); + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, extra, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (param_code) { + case 1: /* Group n Statistics and performance log parameter */ + if (nam) + printf("parameter_code=1\n"); + else + printf("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 = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 12); + ccp = nam ? "gn_write_commands=" : "group n number of write " + "commands = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 20); + ccp = nam ? "gn_lb_received=" + : "group n number of logical blocks received = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 28); + ccp = nam ? "gn_lb_transmitted=" + : "group n number of logical blocks transmitted = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 36); + ccp = nam ? "gn_read_proc_intervals=" + : "group n read command processing intervals = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + ull = sg_get_unaligned_be64(bp + 44); + ccp = nam ? "gn_write_proc_intervals=" + : "group n write command processing intervals = "; + printf(" %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"; + printf("%s\n", ccp); + ull = sg_get_unaligned_be64(bp + 4); + ccp = nam ? "gn_read_fua_commands=" + : "group n number of read FUA commands = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %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 = "; + printf(" %s%" PRIu64 "\n", ccp, ull); + break; + default: + if (nam) { + printf("parameter_code=%d\n", param_code); + printf(" unknown=1\n"); + } else + pr2serr("show_performance... unknown parameter code " + "%d\n", param_code); + 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))); + if (op->filter_given) + break; + } + } + return true; +} + +/* Returns true if processed page, false otherwise */ +/* STATS_LPAGE [0x19], CACHE_STATS_SUBPG [0x20] introduced: SPC-4 */ +static bool +show_cache_stats_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int k, num, pc, subpg_code, extra; + bool nam, spf; + unsigned int ui; + const uint8_t * bp; + const char * ccp; + uint64_t ull; + char str[PCB_STR_LEN]; + + 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] : 0; + 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) { + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, extra, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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 parameter code %d\n", + 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))); + if (op->filter_given) + break; + } + return true; +} + +/* FORMAT_STATUS_LPAGE [0x8] introduced: SBC-2 */ +static bool +show_format_status_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int k, num, pl, pc; + bool is_count; + const uint8_t * bp; + const uint8_t * xp; + uint64_t ull; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Format status page [0x8]\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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + is_count = true; + switch (pc) { + case 0: + if (pl < 5) + printf(" Format data out: \n"); + else { + if (sg_all_ffs(bp + 4, pl - 4)) + printf(" Format data out: \n"); + else { + printf(" Format data out:\n"); + hex2stdout(bp + 4, pl - 4, 0); + } + } + is_count = false; + break; + case 1: + printf(" Grown defects during certification"); + break; + case 2: + printf(" Total blocks reassigned during format"); + break; + case 3: + printf(" Total new blocks reassigned"); + break; + case 4: + printf(" Power on minutes since format"); + break; + default: + printf(" Unknown Format parameter code = 0x%x\n", pc); + is_count = false; + hex2stdout(bp, pl, 0); + break; + } + if (is_count) { + k = pl - 4; + xp = bp + 4; + if (sg_all_ffs(xp, k)) + printf(" \n"); + else { + if (k > (int)sizeof(ull)) { + xp += (k - sizeof(ull)); + k = sizeof(ull); + } + ull = sg_get_unaligned_be(k, xp); + printf(" = %" PRIu64 "\n", ull); + } + } + 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 true; +} + +/* Non-volatile cache page [0x17] introduced: SBC-2 */ +static bool +show_non_volatile_cache_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int j, num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Non-volatile cache page [0x17]\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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0: + printf(" Remaining non-volatile time: "); + if (3 == bp[4]) { + j = sg_get_unaligned_be24(bp + 5); + switch (j) { + case 0: + printf("0 (i.e. it is now volatile)\n"); + break; + case 1: + printf("\n"); + break; + case 0xffffff: + printf("\n"); + break; + default: + printf("%d minutes [%d:%d]\n", j, (j / 60), (j % 60)); + break; + } + } else + printf("\n", bp[4]); + break; + case 1: + printf(" Maximum non-volatile time: "); + if (3 == bp[4]) { + j = sg_get_unaligned_be24(bp + 5); + switch (j) { + case 0: + printf("0 (i.e. it is now volatile)\n"); + break; + case 1: + printf("\n"); + break; + case 0xffffff: + printf("\n"); + break; + default: + printf("%d minutes [%d:%d]\n", j, (j / 60), (j % 60)); + break; + } + } else + printf("\n", bp[4]); + break; + default: + printf(" Unknown parameter code = 0x%x\n", pc); + hex2stdout(bp, pl, 0); + 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 true; +} + +/* LB_PROV_LPAGE [0xc] introduced: SBC-3 */ +static bool +show_lb_provisioning_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + const char * cp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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 reported"; break; + case 1: cp = "dedicated to lu"; break; + case 2: cp = "not dedicated to lu"; break; + case 3: cp = "reserved"; break; + } + printf(" Scope: %s\n", cp); + } + } else if ((pc >= 0xfff0) && (pc <= 0xffff)) { + printf(" Vendor specific [0x%x]:", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } else { + printf(" Reserved [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } + 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 true; +} + +/* UTILIZATION_SUBPG [0xe,0x1] introduced: SBC-4 */ +static bool +show_utilization_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int num, pl, pc, k; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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), 0); + 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 true; +} + +/* SOLID_STATE_MEDIA_LPAGE [0x11] introduced: SBC-3 */ +static bool +show_solid_state_media_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Solid state media page [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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0x1: + printf(" Percentage used endurance indicator:"); + 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(" %u %%\n", bp[7]); + break; + default: + printf(" Reserved [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + 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 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] (ssc, adc) */ +static bool +show_dt_device_status_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, j; + const uint8_t * bp; + char str[PCB_STR_LEN]; + char b[64]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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("Vendor specific [0x%x]\n", 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; + } + hex2stdout(bp + 4, 8, 1); + 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; + } + hex2stdout(bp + 4, 12, 1); + 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"); + hex2stdout(bp + 4, ((pl < num) ? pl : num) - 4, 0); + } + } else if (pc >= 0x8000) { + printf(" Vendor specific [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } else { + printf(" Reserved [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } + 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 true; +} + +/* TapeAlert response [0x12] (adc,ssc) */ +static bool +show_tapealert_response_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, k, mod, div; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } else { + printf(" Vendor specific [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } + 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 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 */ +}; + +/* Requested recovery [0x13] (ssc) */ +static bool +show_requested_recovery_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, j, k; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } else { + printf(" Vendor specific [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } + 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 true; +} + +/* SAT_ATA_RESULTS_LPAGE (SAT-2) [0x16] */ +static bool +show_ata_pt_results_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + const uint8_t * dp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } else { + printf(" short parameter length: %d [parameter_code=0x%x]:\n", + pl, pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + } + 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 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)", /* 8 */ + "background scan halted - scan results list full", + "background scan halted - pre-scan time limit timer expired" /* 10 */, +}; + +static const char * reassign_status[] = { + "Reassign status: Reserved [0x0]", + "Reassignment pending receipt of Reassign or Write command", + "Logical block successfully reassigned by device server", + "Reassign status: 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] for disk introduced: SBC-3 */ +static bool +show_background_scan_results_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int j, m, num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("Background scan results page [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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0: + printf(" Status parameters:\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; + } + printf(" Accumulated power on minutes: "); + j = sg_get_unaligned_be32(bp + 4); + printf("%d [h:m %d:%d]\n", j, (j / 60), (j % 60)); + printf(" Status: "); + j = bp[9]; + if (j < (int)SG_ARRAY_SIZE(bms_status)) + printf("%s\n", bms_status[j]); + else + printf("unknown [0x%x] background scan status value\n", j); + j = sg_get_unaligned_be16(bp + 10); + printf(" Number of background scans performed: %d\n", j); + j = sg_get_unaligned_be16(bp + 12); +#ifdef SG_LIB_MINGW + printf(" Background medium scan progress: %g %%\n", + (double)(j * 100.0 / 65536.0)); +#else + printf(" Background medium scan progress: %.2f %%\n", + (double)(j * 100.0 / 65536.0)); +#endif + j = sg_get_unaligned_be16(bp + 14); + if (0 == j) + printf(" Number of background medium scans performed: 0 " + "[not reported]\n"); + else + printf(" Number of background medium scans performed: " + "%d\n", j); + break; + default: + if (pc > 0x800) { + if ((pc >= 0x8000) && (pc <= 0xafff)) + printf(" Medium scan parameter # %d [0x%x], vendor " + "specific\n", pc, pc); + else + printf(" Medium scan parameter # %d [0x%x], " + "reserved\n", pc, pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + break; + } else + printf(" Medium scan parameter # %d [0x%x]\n", pc, pc); + 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; + } + printf(" Power on minutes when error detected: "); + j = sg_get_unaligned_be32(bp + 4); + printf("%d [%d:%d]\n", j, (j / 60), (j % 60)); + j = (bp[8] >> 4) & 0xf; + if (j < (int)SG_ARRAY_SIZE(reassign_status)) + printf(" %s\n", reassign_status[j]); + else + printf(" Reassign status: reserved [0x%x]\n", j); + printf(" sense key: %s [sk,asc,ascq: 0x%x,0x%x,0x%x]\n", + sg_get_sense_key_str(bp[8] & 0xf, sizeof(str), str), + bp[8] & 0xf, bp[9], bp[10]); + if (bp[9] || bp[10]) + printf(" %s\n", sg_get_asc_ascq_str(bp[9], bp[10], + sizeof(str), str)); + if (op->verbose) { + printf(" vendor bytes [11 -> 15]: "); + for (m = 0; m < 5; ++m) + printf("0x%02x ", bp[11 + m]); + printf("\n"); + } + printf(" LBA (associated with medium error): 0x"); + if (sg_all_zeros(bp + 16, 8)) + printf("0\n"); + else { + for (m = 0; m < 8; ++m) + printf("%02x", bp[16 + m]); + printf("\n"); + } + 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 true; +} + +/* ZONED_BLOCK_DEV_STATS_SUBPG [0x14,0x1] introduced: zbc2r01 */ +static bool +show_zoned_block_dev_stats(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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) { + 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, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0x0: + printf(" Maximum open zones:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x1: + printf(" Maximum explicitly open zones:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x2: + printf(" Maximum implicitly open zones:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x3: + printf(" Minimum empty zones:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x4: + printf(" Maximum number of non-sequential zones:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x5: + printf(" Zones emptied:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x6: + printf(" Suboptimal write commands:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x7: + printf(" Commands exceeding optimal limit:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x8: + printf(" Failed explicit opens:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0x9: + printf(" Read rule violations:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + case 0xa: + printf(" Write rule violations:"); + 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(" %" PRIu64 "\n", sg_get_unaligned_be64(bp + 4)); + break; + default: + printf(" Reserved [parameter_code=0x%x]:\n", pc); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + 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 true; +} + +/* PENDING_DEFECTS_SUBPG [0x15,0x1] introduced: SBC-4 */ +static bool +show_pending_defects_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + uint32_t count; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* BACKGROUND_OP_SUBPG [0x15,0x2] introduced: SBC-4 rev 7 */ +static bool +show_background_op_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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); + hex2stdout(bp, ((pl < num) ? pl : num), 0); + 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 true; +} + +/* LPS misalignment page [0x15,0x3] 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, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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("\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("\n", + pc, bp[4]); + } else { + printf("\n", pc); + hex2stdout(bp, pl, 0); + } + 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 true; +} + +/* Service buffer information [0x15] (adc) */ +static bool +show_service_buffer_info_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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); + hex2stdout(bp + 4, pl - 4, 0); + } else { + printf(" parameter_code=0x%x, Vendor-specific, parameter in " + "hex:\n", pc); + hex2stdout(bp + 4, pl - 4, 0); + } + 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 true; +} + +/* Sequential access device page [0xc] for tape */ +static bool +show_sequential_access_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + uint64_t ull, gbytes; + bool all_set; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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) + 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* 0x14 for tape and ADC */ +static bool +show_device_stats_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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 parameter code [0x%x] data in hex:\n", pc); + hex2stdout(bp + 4, pl - 4, 0); + 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(" Density code: 0x%x, Medium type: 0x%x\n", + p[2], p[3]); + printf(" Medium motion hours: %u\n", + sg_get_unaligned_be32(p + 4)); + } + break; + default: + if (pc >= 0x8000) + printf(" Vendor specific parameter [0x%x], dump in " + "hex:\n", pc); + else + printf(" Reserved parameter [0x%x], dump in hex:\n", pc); + hex2stdout(bp + 4, pl - 4, 0); + 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 true; +} + +/* 0x14 for media changer */ +static bool +show_media_stats_page(const uint8_t * resp, int len, const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + uint64_t ull; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* Element statistics page, 0x15 for SMC */ +static bool +show_element_stats_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + unsigned int v; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +/* 0x16 for tape */ +static bool +show_tape_diag_data_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int k, num, pl, pc; + unsigned int v; + const uint8_t * bp; + char str[PCB_STR_LEN]; + char b[80]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + printf(" Parameter code: %d\n", 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 { + printf(" Medium id number (in hex):\n"); + hex2stdout(bp + 32, 32, 0); + } + printf(" Timestamp origin: 0x%x\n", bp[64] & 0xf); + // Check Timestamp for all zeros + for (k = 66; k < 72; ++k) { + if(bp[k]) + break; + } + if (72 == k) + printf(" Timestamp is all zeros:\n"); + else { + printf(" Timestamp:\n"); + hex2stdout(bp + 66, 6, 1); + } + if (pl > 72) { + printf(" Vendor specific:\n"); + hex2stdout(bp + 72, pl - 72, 0); + } + 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 true; +} + +/* 0x16 for media changer */ +static bool +show_mchanger_diag_data_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + unsigned int v; + const uint8_t * bp; + char str[PCB_STR_LEN]; + char b[80]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + printf(" Parameter code: %d\n", 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"); + hex2stdout((bp + 56), 36, 0); + } + if (pl > 99) { + printf(" Timestamp origin: 0x%x\n", bp[92] & 0xf); + printf(" Timestamp:\n"); + hex2stdout((bp + 94), 6, 1); + } + 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 true; +} + +/* Helper for show_volume_stats_pages() */ +static void +volume_stats_partition(const uint8_t * xp, int len, bool in_hex) +{ + int dl, pn; + bool all_ffs, ffs_last_fe; + uint64_t ull; + + while (len > 3) { + 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 (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) +{ + int dl, mhi; + + while (len > 3) { + 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] */ +static bool +show_volume_stats_pages(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, subpg_code; + bool spf; + const uint8_t * bp; + char str[PCB_STR_LEN]; + char b[64]; + + spf = !!(resp[0] & 0x40); + subpg_code = spf ? resp[1] : 0; + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + + 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 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) + printf(" Vendor specific parameter code (0x%x), payload " + "in hex\n", pc); + else + printf(" Reserved parameter code (0x%x), payload in hex\n", + pc); + hex2stdout(bp + 4, pl - 4, 0); + 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 true; +} + +static const char * tape_alert_strs[] = { + "", /* 0x0 */ + "Read warning", + "Write warning", + "Hard error", + "Media", + "Read failure", + "Write failure", + "Media life", + "Not data grade", /* 0x8 */ + "Write protect", + "No removal", + "Cleaning media", + "Unsupported format", + "Recoverable mechanical cartridge failure", + "Unrecoverable mechanical cartridge failure", + "Memory chip in cartridge failure", + "Forced eject", /* 0x10 */ + "Read only format", + "Tape directory corrupted on load", + "Nearing media life", + "Cleaning required", + "Cleaning requested", + "Expired cleaning media", + "Invalid cleaning tape", + "Retension requested", /* 0x18 */ + "Dual port interface error", + "Cooling fan failing", + "Power supply failure", + "Power consumption", + "Drive maintenance", + "Hardware A", + "Hardware B", + "Interface", /* 0x20 */ + "Eject media", + "Microcode update fail", + "Drive humidity", + "Drive temperature", + "Drive voltage", + "Predictive failure", + "Diagnostics required", + "Obsolete (28h)", /* 0x28 */ + "Obsolete (29h)", + "Obsolete (2Ah)", + "Obsolete (2Bh)", + "Obsolete (2Ch)", + "Obsolete (2Dh)", + "Obsolete (2Eh)", + "Reserved (2Fh)", + "Reserved (30h)", /* 0x30 */ + "Reserved (31h)", + "Lost statistics", + "Tape directory invalid at unload", + "Tape system area write failure", + "Tape system area read failure", + "No start of data", + "Loading failure", + "Unrecoverable unload failure", /* 0x38 */ + "Automation interface failure", + "Firmware failure", + "WORM medium - integrity check failed", + "WORM medium - overwrite attempted", +}; + +/* TAPE_ALERT_LPAGE [0x2e] */ +static bool +show_tape_alert_ssc_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc, flag; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + /* 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + flag = bp[4] & 1; + if (op->verbose && (0 == op->do_brief) && flag) + printf(" >>>> "); + if ((0 == op->do_brief) || op->verbose || flag) { + if (pc < (int)SG_ARRAY_SIZE(tape_alert_strs)) + printf(" %s: %d\n", tape_alert_strs[pc], flag); + else + printf(" Reserved parameter code 0x%x, flag: %d\n", pc, + flag); + } + 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 true; +} + +/* 0x37 */ +static bool +show_seagate_cache_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + switch (pc) { + case 0: printf(" Blocks sent to initiator"); 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 parameter code = 0x%x", pc); break; + } + 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))); + 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, const struct opts_t * op) +{ + bool valid = false; + int num, pl, pc; + const uint8_t * bp; + char str[PCB_STR_LEN]; + + if (op->verbose || ((! op->do_raw) && (0 == op->do_hex))) + printf("HGST/WDC miscellaneous page [0x37]\n"); + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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 parameter code = 0x%x", 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; +} + +/* 0x3e */ +static bool +show_seagate_factory_page(const uint8_t * resp, int len, + const struct opts_t * op) +{ + bool valid = false; + int num, pl, pc; + const uint8_t * bp; + uint64_t ull; + char str[PCB_STR_LEN]; + + 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); + break; + } else if (op->do_hex) { + hex2stdout(bp, pl, ((1 == op->do_hex) ? 1 : -1)); + break; + } + } + 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 parameter code = 0x%x", 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))); + if (op->filter_given) + break; +skip: + num -= pl; + bp += pl; + } + return true; +} + +static void +decode_page_contents(const uint8_t * resp, int len, const struct opts_t * op) +{ + 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; + subpg_code = spf ? resp[1] : 0; + if ((SUPP_SPGS_SUBPG == subpg_code) && (SUPP_PAGES_LPAGE != pg_code)) { + done = show_supported_pgs_sub_page(resp, len, op); + 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); + if (lep && lep->show_pagep) + done = (*lep->show_pagep)(resp, len, op); + + if (! done) { + if (spf) + printf("Unable to decode page = 0x%x, subpage = 0x%x, here is " + "hex:\n", pg_code, subpg_code); + else + printf("Unable to decode page = 0x%x, here is hex:\n", pg_code); + if (len > 128) { + hex2stdout(resp, 64, 1); + printf(" ..... [truncated after 64 of %d bytes (use '-H' to " + "see the rest)]\n", len); + } + else + hex2stdout(resp, len, 1); + } +} + +static int +fetchTemperature(int sg_fd, uint8_t * resp, int max_len, struct opts_t * op) +{ + 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, (1 == op->do_hex)); + else + show_temperature_page(resp, len, op); + } 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, (1 == op->do_hex)); + else + show_ie_page(resp, len, op); + } 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 n, nn; + const struct log_elem * lep; + char * cp; + char b[80]; + + if (isalpha(op->pg_arg[0])) { + 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' */ + 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; +} + + +int +main(int argc, char * argv[]) +{ + int k, pg_len, res, resp_len, vb; + int in_len = -1; + int sg_fd = -1; + int ret = 0; + uint8_t * parr; + uint8_t * free_parr = NULL; + struct sg_simple_inquiry_resp inq_out; + struct opts_t opts; + struct opts_t * op; + + op = &opts; + memset(op, 0, sizeof(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; + } +#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 (op->vend_prod) { + if (isdigit(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; + } + 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) { + const struct log_elem * lep; + const uint8_t * bp; + int pg_code, subpg_code, pdt, n; + uint16_t u; + + if ((ret = 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 && (0 == op->do_brief)) + pr2serr(">>> --page=%s option is being ignored, using values " + "in file: %s\n", op->pg_arg, op->in_fn); + for (bp = rsp_buff, k = 0; k < in_len; bp += n, k += n) { + pg_code = bp[0] & 0x3f; + subpg_code = (bp[0] & 0x40) ? bp[1] : 0; + 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; + } + pdt = op->dev_pdt; + lep = pg_subpg_pdt_search(pg_code, subpg_code, pdt, + op->vend_prod_num); + if (lep) { + if (lep->show_pagep) + (*lep->show_pagep)(bp, n, op); + else + printf("Unable to decode %s [%s]\n", lep->name, + lep->acron); + } else { + printf("Unable to decode page=0x%x", pg_code); + if (subpg_code > 0) + printf(", subpage=0x%x", subpg_code); + if (pdt >= 0) + printf(", pdt=0x%x\n", pdt); + else + printf("\n"); + } + } + 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"); + usage_for(1, op); + ret = SG_LIB_SYNTAX_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->filter) { + pr2serr("--all conflicts with --filter\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 = 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; + } + pg_len = 0; + + 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)) + printf(" %.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); + 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 == res) + 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; + } + 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; + } + } else if (SG_LIB_CAT_INVALID_OP == res) + pr2serr("log_sense: not supported\n"); + else if (SG_LIB_CAT_NOT_READY == res) + pr2serr("log_sense: device not ready\n"); + else if (SG_LIB_CAT_ILLEGAL_REQ == res) + pr2serr("log_sense: field in cdb illegal\n"); + else if (SG_LIB_CAT_UNIT_ATTENTION == res) + pr2serr("log_sense: unit attention\n"); + else if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("log_sense: aborted command\n"); + if (0 == op->do_all) { + if (op->filter_given) { + if (op->do_hex > 2) + hex2stdout(rsp_buff, pg_len + 4, (op->do_hex < 4)); + else + decode_page_contents(rsp_buff, pg_len + 4, op); + } else if (op->do_raw) + dStrRaw(rsp_buff, pg_len + 4); + else if (op->do_hex > 1) + hex2stdout(rsp_buff, pg_len + 4, (2 == op->do_hex) ? 0 : -1); + 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, 1); + } + else + decode_page_contents(rsp_buff, pg_len + 4, op); + } + } + 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->do_raw) + printf("\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) + dStrRaw(rsp_buff, pg_len + 4); + else if (op->do_hex > 1) + hex2stdout(rsp_buff, pg_len + 4, + (2 == op->do_hex) ? 0 : -1); + else 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, 1); + } + else + decode_page_contents(rsp_buff, pg_len + 4, op); + } else if (SG_LIB_CAT_INVALID_OP == res) + pr2serr("log_sense: page=0x%x,0x%x not supported\n", + op->pg_code, op->subpg_code); + else if (SG_LIB_CAT_NOT_READY == res) + pr2serr("log_sense: device not ready\n"); + else if (SG_LIB_CAT_ILLEGAL_REQ == res) + pr2serr("log_sense: field in cdb illegal " + "[page=0x%x,0x%x]\n", op->pg_code, op->subpg_code); + else if (SG_LIB_CAT_UNIT_ATTENTION == res) + pr2serr("log_sense: unit attention\n"); + else if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("log_sense: aborted command\n"); + else + pr2serr("log_sense: failed, try '-v' for more information\n"); + } + } +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"); + } + 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 0000000..904b2ce --- /dev/null +++ b/src/sg_luns.c @@ -0,0 +1,712 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.42 20180626"; + +#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; + 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; + default: + printf("%s<<%s: faulty logic>>\n", l_leadin, __func__); + break; + } + if (next_level) + continue; + if ((2 == a_method) && (k < 3) && (lunp[2] || lunp[3])) + printf("%s<>\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; + } + 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(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) { + pr2serr("open error: %s: %s\n", device_name, safe_strerror(-sg_fd)); + 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(" <>\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 0000000..824334a --- /dev/null +++ b/src/sg_map.c @@ -0,0 +1,505 @@ +/* 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. + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); + printf(" -sr show mapping to cdroms (look for /dev/sr\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 0000000..a48fcda --- /dev/null +++ b/src/sg_map26.c @@ -0,0 +1,1283 @@ +/* + * 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. + */ + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* new location for major + minor */ +#ifndef major +#include +#endif +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_lib.h" + +static const char * version_str = "1.16 20180724"; + +#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 + 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]; + 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' 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" */ + 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 0000000..babb05d --- /dev/null +++ b/src/sg_modes.c @@ -0,0 +1,1542 @@ +/* + * Copyright (C) 2000-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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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.66 20180909"; + +#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}, +}; + +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"}, + {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, "fg", "Flexible geometry (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)"}, + {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}, +}; + +static struct page_code_desc pc_desc_t_sas[] = { + {0x18, 0x0, "pslu", "Protocol specific logical unit (SAS)"}, + {0x19, 0x0, "pspo", "Protocol specific port (SAS)"}, + {0x19, 0x1, "pcd", "Phy control and discover (SAS)"}, + {0x19, 0x2, "spc", "Shared port control (SAS)"}, + {0x19, 0x3, "sep", "Enhanced phy control (SAS)"}, + {0x19, 0x4, "oobm", "Out of band management control (SAS)"}, /* 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}, +}; + +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_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 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; + } + op->maxlen = n; + break; + case 'N': + break; /* ignore */ + case 'O': + op->opt_new = false; + return 0; + case 'p': + if (isalpha(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; + } + 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]); +} + +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]; + } + *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; + int num; + 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; + } + } + } + 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; + } + } + 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 (0 == 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 (0 == 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 0000000..21897c4 --- /dev/null +++ b/src/sg_opcodes.c @@ -0,0 +1,1199 @@ +/* A utility program originally written 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. + * + * This program outputs information provided by a SCSI REPORT SUPPORTED + * OPERATION CODES [0xa3/0xc] and REPORT SUPPORTED TASK MANAGEMENT + * FUNCTIONS [0xa3/0xd] commands. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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.62 20180626"; /* spc5r19+ */ + + +#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 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'}, + {"mask", no_argument, 0, 'm'}, + {"mlu", no_argument, 0, 'M'}, + {"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; + 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; +}; + + +static void +usage() +{ + pr2serr("Usage: sg_opcodes [--alpha] [--compact] [--enumerate] " + "[--help] [--hex]\n" + " [--mask] [--mlu] [--no-inquiry] " + "[--opcode=OP[,SA]]\n" + " [--pdt=DT] [--raw] [--rctd] [--repd] " + "[--sa=SA] [--tmf]\n" + " [--unsorted] [--verbose] [--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\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|-o OP first byte of command to query\n" + " (decimal, prefix with '0x' for hex)\n" + " --opcode=OP,SA|-o OP,SA opcode (OP) and service action " + "(SA)\n" + " (decimal, each prefix with '0x' for " + "hex)\n" + " --pdt=DT|-p DT give peripheral device type for " + "'--no-inquiry'\n" + " '--enumerate'\n" + " --raw|-r output response in binary to stdout\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" + " (decimal, prefix with '0x' for hex)\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.\n"); +} + +static void +usage_old() +{ + pr2serr("Usage: sg_opcodes [-a] [-c] [-e] [-H] [-m] [-M] [-n] [-o=OP] " + "[-p=DT]\n" + " [-q] [-r] [-R] [-s=SA] [-t] [-u] [-v] [-V] " + "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" + " -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 k, 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]; + + 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) { + pr2serr(" %s cdb: ", rsoc_s); + for (k = 0; k < RSOC_CMD_LEN; ++k) + pr2serr("%02x ", rsoc_cdb[k]); + pr2serr("\n"); + } + 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, mx_resp_len, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) + 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 k, 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]; + + 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) { + pr2serr(" %s: ", rstmf_s); + for (k = 0; k < RSTMF_CMD_LEN; ++k) + pr2serr("%02x ", rstmf_cdb[k]); + pr2serr("\n"); + } + 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, mx_resp_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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, "acehHmMnNo: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 '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(optarg[0])) + n = sg_get_num(optarg); + else if ((2 == strlen(optarg)) && (0 == strcmp("-1", optarg))) + n = -1; + if ((n < -1) || (n > 0x1f)) { + 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 '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("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 > 0x1f) || (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); +} + +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, cd_len, serv_act, len, act_len, opcode, res; + uint8_t byt5; + unsigned int timeout; + uint8_t * bp; + uint8_t ** sort_arr = NULL; + char name_buff[NAME_BUFF_SZ]; + char sa_buff[8]; + + cd_len = sg_get_unaligned_be32(rsoc_buff + 0); + if (cd_len > (rsoc_len - 4)) { + printf("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) { + printf("sg_opcodes: no commands to display\n"); + return 0; + } + if (op->do_rctd) { /* Return command timeout descriptor */ + if (op->do_compact) { + printf("\nOpcode,sa Nominal Recommended Name\n"); + printf( " (hex) timeout timeout(sec) \n"); + printf("-----------------------------------------------" + "---------\n"); + } else { + printf("\nOpcode Service CDB Nominal Recommended Name\n"); + printf( "(hex) action(h) size timeout timeout(sec) \n"); + printf("-------------------------------------------------------" + "---------\n"); + } + } else { /* RCTD clear in cdb */ + if (op->do_compact) { + printf("\nOpcode,sa Name\n"); + printf( " (hex) \n"); + printf("---------------------------------------\n"); + } else if (op->do_mlu) { + printf("\nOpcode Service CDB CDLP, Name\n"); + printf( "(hex) action(h) size MLU \n"); + printf("-----------------------------------------------\n"); + } else { + printf("\nOpcode Service CDB CDLP Name\n"); + printf( "(hex) action(h) size \n"); + printf("-----------------------------------------------\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) { + printf("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)); + } + for (k = 0, j = 0; k < cd_len; ++j, k += len) { + 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); + serv_act = 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) { + if (byt5 & 0x2) { /* CTDP set */ + /* don't show CDLP because it makes line too long */ + if (op->do_compact) + printf(" %.2x%c%.4s", opcode, (sa_v ? ',' : ' '), + sa_buff); + else + printf(" %.2x %.4s %3d", opcode, sa_buff, + sg_get_unaligned_be16(bp + 6)); + timeout = sg_get_unaligned_be32(bp + 12); + if (0 == timeout) + printf(" -"); + else + printf(" %8u", timeout); + timeout = sg_get_unaligned_be32(bp + 16); + if (0 == timeout) + printf(" -"); + else + printf(" %8u", timeout); + printf(" %s\n", name_buff); + } else /* CTDP clear */ + if (op->do_compact) + printf(" %.2x%c%.4s %s\n", opcode, + (sa_v ? ',' : ' '), sa_buff, name_buff); + else + printf(" %.2x %.4s %3d " + "%s\n", opcode, sa_buff, + sg_get_unaligned_be16(bp + 6), name_buff); + } else { /* RCTD clear in cdb */ + if (op->do_compact) + printf(" %.2x%c%.4s %s\n", bp[0], (sa_v ? ',' : ' '), + sa_buff, name_buff); + else if (op->do_mlu) + printf(" %.2x %.4s %3d %2d,%d %s\n", bp[0], + sa_buff, sg_get_unaligned_be16(bp + 6), + (byt5 >> 2) & 0x3, !!(0x10 & byt5), name_buff); + else + printf(" %.2x %.4s %3d %2d %s\n", bp[0], + sa_buff, sg_get_unaligned_be16(bp + 6), + (byt5 >> 2) & 0x3, name_buff); + } + if (op->do_mask) { + int cdb_sz; + uint8_t b[64]; + + memset(b, 0, sizeof(b)); + res = do_rsoc(ptvp, false, (sa_v ? 2 : 1), opcode, serv_act, + b, sizeof(b), &act_len, true, op->verbose); + if (0 == res) { + cdb_sz = sg_get_unaligned_be16(b + 2); + cdb_sz = (cdb_sz < act_len) ? cdb_sz : act_len; + if ((cdb_sz > 0) && (cdb_sz <= 80)) { + if (op->do_compact) + printf(" usage: "); + else + printf(" cdb usage: "); + for (m = 0; m < cdb_sz; ++m) + printf("%.2x ", b[4 + m]); + printf("\n"); + } + } 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) +{ + int len; + unsigned int timeout; + + 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); + 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); + return; +} + +/* One command descriptor (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; + uint8_t * bp; + const char * cp; + const char * dlp; + char name_buff[NAME_BUFF_SZ]; + + + printf("\n Opcode=0x%.2x", op->opcode); + if (rep_opts > 1) + printf(" Service_action=0x%.4x", op->servact); + printf("\n"); + sg_get_opcode_sa_name(((op->opcode > 0) ? op->opcode : 0), + ((op->servact > 0) ? op->servact : 0), + peri_dtype, NAME_BUFF_SZ, name_buff); + printf(" Command_name: %s\n", name_buff); + switch((int)(rsoc_buff[1] & 7)) { /* SUPPORT field */ + 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; + } + k = 0x3 & (rsoc_buff[1] >> 3); + switch (k) { /* CDLP field */ + case 0: + dlp = "no command duration limit mode page"; + break; + case 1: + dlp = "command duration limit A mode page"; + break; + case 2: + dlp = "command duration limit B mode page"; + break; + default: + dlp = "reserved [CDLP=3]"; + break; + } + printf(" Command %s, [%s]\n", cp, dlp); + printf(" Multiple Logical Units (MLU): %d\n", !! (rsoc_buff[1] & 0x20)); + if (valid) { + printf(" Usage data: "); + bp = rsoc_buff + 4; + for (k = 0; k < cd_len; ++k) + printf("%.2x ", bp[k]); + printf("\n"); + } + if (0x80 & rsoc_buff[1]) { /* CTDP */ + bp = rsoc_buff + 4 + cd_len; + decode_cmd_timeout_desc(bp, NAME_BUFF_SZ, name_buff); + printf(" %s\n", name_buff); + } +} + + +int +main(int argc, char * argv[]) +{ + int cd_len, res, len, act_len, rq_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; + 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; + } +#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 ((NULL == op->device_name) && (! op->do_enumerate)) { + pr2serr("No DEVICE argument given\n"); + if (op->opt_new) + usage(); + else + usage_old(); + return SG_LIB_SYNTAX_ERROR; + } + 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(); + return SG_LIB_CONTRADICT; + } + 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"); + } + 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; + } + 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); + goto err_out; + } + + if (op->opcode < 0) { + /* Try to open read-only */ + if ((sg_fd = scsi_pt_open_device(op->device_name, true, vb)) < 0) { + pr2serr("sg_opcodes: error opening file (ro): %s: %s\n", + op->device_name, safe_strerror(-sg_fd)); + 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); + 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 (! (op->do_raw || op->no_inquiry)) { + 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; + 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); + 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); + 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); + goto err_out; + } + act_len = (rq_len < act_len) ? rq_len : act_len; + if (op->do_taskman) { + if (op->do_raw) { + dStrRaw((const char *)rsoc_buff, act_len); + goto err_out; + } + printf("\nTask Management Functions supported by device:\n"); + if (op->do_hex) { + hex2stdout(rsoc_buff, act_len, 1); + goto err_out; + } + if (rsoc_buff[0] & 0x80) + printf(" Abort task\n"); + if (rsoc_buff[0] & 0x40) + printf(" Abort task set\n"); + if (rsoc_buff[0] & 0x20) + printf(" Clear ACA\n"); + if (rsoc_buff[0] & 0x10) + printf(" Clear task set\n"); + if (rsoc_buff[0] & 0x8) + printf(" Logical unit reset\n"); + if (rsoc_buff[0] & 0x4) + printf(" Query task\n"); + if (rsoc_buff[0] & 0x2) + printf(" Target reset (obsolete)\n"); + if (rsoc_buff[0] & 0x1) + printf(" Wakeup (obsolete)\n"); + if (rsoc_buff[1] & 0x4) + printf(" Query asynchronous event\n"); + if (rsoc_buff[1] & 0x2) + printf(" Query task set\n"); + if (rsoc_buff[1] & 0x1) + printf(" 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; + goto err_out; + } else + printf(" Extended parameter data:\n"); + printf(" TMFTMOV=%d\n", !!(rsoc_buff[4] & 0x1)); + printf(" ATTS=%d\n", !!(rsoc_buff[6] & 0x80)); + printf(" ATSTS=%d\n", !!(rsoc_buff[6] & 0x40)); + printf(" CACATS=%d\n", !!(rsoc_buff[6] & 0x20)); + printf(" CTSTS=%d\n", !!(rsoc_buff[6] & 0x10)); + printf(" LURTS=%d\n", !!(rsoc_buff[6] & 0x8)); + printf(" QTTS=%d\n", !!(rsoc_buff[6] & 0x4)); + printf(" QAETS=%d\n", !!(rsoc_buff[7] & 0x4)); + printf(" QTSTS=%d\n", !!(rsoc_buff[7] & 0x2)); + printf(" ITNRTS=%d\n", !!(rsoc_buff[7] & 0x1)); + printf(" tmf long timeout: %u (100 ms units)\n", + sg_get_unaligned_be32(rsoc_buff + 8)); + printf(" 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 err_out; + } + if (op->do_hex) { + hex2stdout(rsoc_buff, len, 1); + goto err_out; + } + 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 err_out; + } + if (op->do_hex) { + hex2stdout(rsoc_buff, len, 1); + goto err_out; + } + list_one(rsoc_buff, cd_len, rep_opts, op); + } +fini: + res = 0; + +err_out: + if (free_rsoc_buff) + free(free_rsoc_buff); + if (ptvp) + destruct_scsi_pt_obj(ptvp); + if (sg_fd >= 0) + scsi_pt_close_device(sg_fd); + return res; +} diff --git a/src/sg_persist.c b/src/sg_persist.c new file mode 100644 index 0000000..f582d32 --- /dev/null +++ b/src/sg_persist.c @@ -0,0 +1,1320 @@ +/* A utility program originally written 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. + * + * This program issues the SCSI PERSISTENT IN and OUT commands. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 + +#include + +#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.66 20180615"; + + +#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(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; + } + tid_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)); + 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=") 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; + } + sg_cmds_close_device(sg_fd); + } + + 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 0000000..40ab163 --- /dev/null +++ b/src/sg_prevent.c @@ -0,0 +1,191 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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.12 20180627"; + +#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 0000000..92c2287 --- /dev/null +++ b/src/sg_raw.c @@ -0,0 +1,935 @@ +/* + * A utility program originally written for the Linux OS SCSI subsystem. + * + * Copyright (C) 2000-2018 Ingo van Lil + * + * 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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.27 (2018-06-27)" + +#define DEFAULT_TIMEOUT 20 +#define MIN_SCSI_CDBSZ 6 +#define MAX_SCSI_CDBSZ 260 +#define MAX_SCSI_DXLEN (64 * 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' }, + { "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' }, + { "outfile", required_argument, NULL, 'o' }, + { "raw", no_argument, NULL, 'w' }, + { "request", required_argument, NULL, 'r' }, + { "readonly", no_argument, NULL, 'R' }, + { "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_help; + bool verbose_given; + bool version_given; + int cdb_length; + int datain_len; + int dataout_len; + int timeout; + int raw; + int readonly; + 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-2018 Ingo van Lil \n" + "This is free software. You may redistribute copies of it " + "under the terms of\n" + "the GNU General Public License " + ".\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" + " --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 data to send from IFILE " + "(default:\n" + " stdin)\n" + " --nosense|-n Don't display sense information\n" + " --outfile=OFILE|-o OFILE Write binary data to OFILE (def: " + "hexdump\n" + " 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" + " --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"); +} + +/* Read ASCII hex bytes or binary from fname (a file named '-' taken as + * stdin). If reading ASCII hex then there should be either one entry per + * line or a comma, space or tab separated list of bytes. If no_space is + * true then a string of ACSII hex digits is expected, 2 per byte. Everything + * from and including a '#' on a line is ignored. Returns true if ok, or + * false if error. */ +static bool +f2hex_arr(const char * fname, bool as_binary, bool no_space, + uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) +{ + int fn_len, in_len, k, j, m, fd; + bool has_stdin, split_line; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + int off = 0; + struct stat a_stat; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return false; + fn_len = strlen(fname); + if (0 == fn_len) + return false; + has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ + if (as_binary) { + if (has_stdin) + fd = STDIN_FILENO; + else { + fd = open(fname, O_RDONLY); + if (fd < 0) { + pr2serr("unable to open binary file %s: %s\n", fname, + safe_strerror(errno)); + return false; + } + } + k = read(fd, mp_arr, max_arr_len); + if (k <= 0) { + if (0 == k) + pr2serr("read 0 bytes from binary file %s\n", fname); + else + pr2serr("read from binary file %s: %s\n", fname, + safe_strerror(errno)); + if (! has_stdin) + close(fd); + return false; + } + if ((0 == fstat(fd, &a_stat)) && S_ISFIFO(a_stat.st_mode)) { + /* pipe; keep reading till error or 0 read */ + while (k < max_arr_len) { + m = read(fd, mp_arr + k, max_arr_len - k); + if (0 == m) + break; + if (m < 0) { + pr2serr("read from binary pipe %s: %s\n", fname, + safe_strerror(errno)); + if (! has_stdin) + close(fd); + return false; + } + k += m; + } + } + *mp_arr_len = k; + if (! has_stdin) + close(fd); + return true; + } else { /* So read the file as ASCII hex */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + pr2serr("Unable to open %s for reading\n", fname); + return false; + } + } + } + + carry_over[0] = 0; + for (j = 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(line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%4x", &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); + 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; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%10x", &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) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + if (stdin != fp) + fclose(fp); + return true; +bad: + if (stdin != fp) + fclose(fp); + return false; +} + +static int +parse_cmd_line(struct opts_t * op, int argc, char *argv[]) +{ + while (1) { + int c, n; + + c = getopt_long(argc, argv, "bc:ehi:k:no: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 '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 'o': + if (op->datain_file) { + pr2serr("Too many '--outfile=' options\n"); + return SG_LIB_CONTRADICT; + } + op->datain_file = optarg; + 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 (optind >= argc) { + pr2serr("No device specified\n\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) { + bool ok; + + ok = f2hex_arr(op->cmd_file, (op->raw > 0) /* as_binary */, + false /* no_space */, op->cdb, &op->cdb_length, + MAX_SCSI_CDBSZ); + if (! ok) + 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 (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], true /* admin */, + 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, 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; + } + } + + buf = sg_memalign(op->dataout_len, 0 /* page_size */, free_buf, + op->verbose > 3); + if (buf == NULL) { + pr2serr("sg_memalign: failed to get %d bytes of memory\n", + op->dataout_len); + if (errp) + *errp = sg_convert_errno(ENOMEM); + goto bail; + } + + len = read(fd, buf, op->dataout_len); + if (len < 0) { + err = errno; + if (errp) + *errp = sg_convert_errno(err); + perror("Failed to read input data"); + goto bail; + } else if (len < op->dataout_len) { + if (errp) + *errp = SG_LIB_FILE_ERROR; + pr2serr("EOF on input file/stream\n"); + 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; + 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]; + 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) { + 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(); + if (ptvp == NULL) { + pr2serr("out of memory\n"); + ret = SG_LIB_CAT_OTHER; + goto done; + } + + is_scsi_cdb = sg_is_scsi_cdb(op->cdb, op->cdb_length); + 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, + op->verbose > 3); + 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) { + pr2serr(" %s to send: ", is_scsi_cdb ? "cdb" : "cmd"); + if (is_scsi_cdb) { + for (k = 0; k < op->cdb_length; ++k) + pr2serr("%02x ", op->cdb[k]); + pr2serr("\n"); + if (op->verbose > 1) { + sg_get_command_name(op->cdb, 0, b_len - 1, b); + b[b_len - 1] = '\0'; + pr2serr(" Command name: %s\n", b); + } + } 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], true /* admin */, + 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)); + + ret = do_scsi_pt(ptvp, sg_fd, 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; + 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 != 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 (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 0000000..61604d3 --- /dev/null +++ b/src/sg_rbuf.c @@ -0,0 +1,685 @@ +/* A utility program originally written for the Linux OS SCSI subsystem. + * Copyright (C) 1999-2018 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#include +#include + +#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.05 20180627"; + +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, j, 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]; + uint8_t rb_cdb [RB_CMD_LEN]; + 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; + + memset(rb_cdb, 0, RB_CMD_LEN); + 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) { + pr2serr(" Read buffer (%sdescriptor) cdb: ", + (op->do_echo ? "echo " : "")); + for (k = 0; k < RB_CMD_LEN; ++k) + pr2serr("%02x ", rb_cdb[k]); + pr2serr("\n"); + } + + /* 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) { + pr2serr(" Read buffer (%sdata) cdb: ", + (op->do_echo ? "echo " : "")); + for (j = 0; j < RB_CMD_LEN; ++j) + pr2serr("%02x ", rb_cdb[j]); + pr2serr("\n"); + } + + 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) { + 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 >= 0) ? res : SG_LIB_CAT_OTHER; +} diff --git a/src/sg_rdac.c b/src/sg_rdac.c new file mode 100644 index 0000000..73f4684 --- /dev/null +++ b/src/sg_rdac.c @@ -0,0 +1,514 @@ +/* + * sg_rdac + * + * Retrieve / set RDAC options. + * + * Copyright (C) 2006-2018 Hannes Reinecke + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#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 0000000..fb959c9 --- /dev/null +++ b/src/sg_read.c @@ -0,0 +1,920 @@ +/* A utility program for the Linux OS SCSI generic ("sg") device driver. +* Copyright (C) 2001 - 2018 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + 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, + a raw 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#ifndef major +#include +#endif +#include +#include +#include + +#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.34 20180811"; + +#define DEF_BLOCK_SIZE 512 +#define DEF_BLOCKS_PER_TRANSFER 128 +#define DEF_SCSI_CDBSZ 10 +#define MAX_SCSI_CDBSZ 16 + +#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 /*unlikey value */ +#endif + +#define FT_OTHER 1 /* filetype other than sg or raw device */ +#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 * proc_allow_dio = "/proc/scsi/sg/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 mmaped 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) +{ + int k; + uint8_t rdCmd[MAX_SCSI_CDBSZ]; + uint8_t senseBuff[SENSE_BUFF_LEN]; + 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) { + pr2serr( " read cdb: "); + for (k = 0; k < cdbsz; ++k) + pr2serr( "%02x ", rdCmd[k]); + pr2serr( "\n"); + } + + 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 (-1 == bpt) { + pr2serr( ME "bad argument to 'bpt'\n"); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strcmp(key,"bs")) { + bs = sg_get_num(buf); + if (-1 == bs) { + pr2serr( ME "bad argument to 'bs'\n"); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strcmp(key,"cdbsz")) + scsi_cdbsz = sg_get_num(buf); + else if (0 == strcmp(key,"count")) { + count_given = true; + if ('-' == *buf) { + dd_count = sg_get_llnum(buf + 1); + if (-1 == dd_count) { + pr2serr( ME "bad argument to 'count'\n"); + return SG_LIB_SYNTAX_ERROR; + } + dd_count = - dd_count; + } else { + dd_count = sg_get_llnum(buf); + if (-1 == dd_count) { + 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) + strncpy(inf, buf, INF_SZ - 1); + 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) + strncpy(outf, buf, INF_SZ - 1); + else if (0 == strcmp(key,"skip")) { + skip = sg_get_llnum(buf); + if (-1 == 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_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='\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + if (0 == strcmp("-", inf)) { + pr2serr("'-' (stdin) invalid as \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(proc_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + pr2serr(">>> %s set to '0' but should be set to '1' for " + "direct IO\n", proc_allow_dio); + } + close(fd); + } + } + if (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 0000000..2ae51bf --- /dev/null +++ b/src/sg_read_attr.c @@ -0,0 +1,1182 @@ +/* + * Copyright (c) 2016-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include + +#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.11 20180523"; + +#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 k, 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]; + 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) { + pr2serr(" Read attribute cdb: "); + for (k = 0; k < SG_READ_ATTRIBUTE_CMDLEN; ++k) + pr2serr("%02x ", ra_cdb[k]); + pr2serr("\n"); + } + + 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, op->maxlen, + sense_b, noisy, op->verbose, &sense_cat); + if (-1 == ret) + 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(*mp) != 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); +} + +/* Read ASCII hex bytes or binary from fname (a file named '-' taken as + * stdin). If reading ASCII hex then there should be either one entry per + * line or a comma, space or tab separated list of bytes. If no_space is + * set then a string of ACSII hex digits is expected, 2 per byte. Everything + * from and including a '#' on a line is ignored. Returns 0 if ok, or error + * code. */ +static int +f2hex_arr(const char * fname, bool as_binary, bool no_space, + uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) +{ + bool split_line, has_stdin; + int fn_len, in_len, k, j, m, fd, err; + int off = 0; + int ret = SG_LIB_SYNTAX_ERROR; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return SG_LIB_LOGIC_ERROR; + fn_len = strlen(fname); + if (0 == fn_len) + return SG_LIB_SYNTAX_ERROR; + has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ + if (as_binary) { + if (has_stdin) { + fd = STDIN_FILENO; + if (sg_set_binary_mode(STDIN_FILENO) < 0) + perror("sg_set_binary_mode"); + } else { + fd = open(fname, O_RDONLY); + if (fd < 0) { + err = errno; + pr2serr("unable to open binary file %s: %s\n", fname, + safe_strerror(err)); + return sg_convert_errno(err); + } else if (sg_set_binary_mode(fd) < 0) + perror("sg_set_binary_mode"); + } + k = read(fd, mp_arr, max_arr_len); + if (k <= 0) { + if (0 == k) + pr2serr("read 0 bytes from binary file %s\n", fname); + else { + err = errno; + pr2serr("read from binary file %s: %s\n", fname, + safe_strerror(err)); + ret = sg_convert_errno(err); + } + if (! has_stdin) + close(fd); + return ret; + } + *mp_arr_len = k; + if (! has_stdin) + close(fd); + return 0; + } else { /* So read the file as ASCII hex */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + pr2serr("Unable to open %s for reading\n", fname); + return 1; + } + } + } + + carry_over[0] = 0; + for (j = 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(line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%4x", &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); + 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; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%4x", &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) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + if (stdin != fp) + fclose(fp); + return 0; +bad: + if (stdin != fp) + fclose(fp); + return 1; +} + +/* 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(" [ff]\n"); + else if (2 == k) + printf("\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(" [ff]\n"); + else if (2 == k) + printf("\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(*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 = f2hex_arr(fname, op->do_raw, 0 /* 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 0000000..597c9b6 --- /dev/null +++ b/src/sg_read_block_limits.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2009-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.08 20180219"; + +#define MAX_READ_BLOCK_LIMITS_LEN 6 + +static uint8_t readBlkLmtBuff[MAX_READ_BLOCK_LIMITS_LEN]; + + +static struct option long_options[] = { + {"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_read_block_limits [--help] [--hex] [--raw] " + "[--readonly]\n" + " [--verbose] [--version] " + "DEVICE\n" + " where:\n" + " --help|-h print out usage message\n" + " --hex|-H output response in hexadecimal\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_raw = false; + bool readonly = false; + bool verbose_given = false; + bool version_given = false; + int sg_fd, k, m, res, c; + int do_hex = 0; + int verbose = 0; + int ret = 0; + uint32_t max_block_size; + uint16_t min_block_size; + const char * device_name = NULL; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "hHrRvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + usage(); + return 0; + case 'H': + ++do_hex; + 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) { + pr2serr("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; + } + + memset(readBlkLmtBuff, 0x0, 6); + res = sg_ll_read_block_limits(sg_fd, readBlkLmtBuff, 6, true, verbose); + ret = res; + if (0 == res) { + if (do_hex) { + hex2stdout(readBlkLmtBuff, sizeof(readBlkLmtBuff), 1); + goto the_end; + } else if (do_raw) { + dStrRaw((const char *)readBlkLmtBuff, sizeof(readBlkLmtBuff)); + goto the_end; + } + + max_block_size = sg_get_unaligned_be32(readBlkLmtBuff + 0); + min_block_size = sg_get_unaligned_be16(readBlkLmtBuff + 4); + k = min_block_size / 1024; + pr2serr("Read Block Limits results:\n"); + pr2serr("\tMinimum block size: %u byte(s)", + (unsigned int)min_block_size); + if (k != 0) + pr2serr(", %d KB", k); + pr2serr("\n"); + k = max_block_size / 1024; + m = max_block_size / 1048576; + pr2serr("\tMaximum block size: %u byte(s)", + (unsigned int)max_block_size); + if (k != 0) + pr2serr(", %d KB", k); + if (m != 0) + pr2serr(", %d MB", m); + pr2serr("\n"); + } else { + 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 0000000..2356402 --- /dev/null +++ b/src/sg_read_buffer.c @@ -0,0 +1,550 @@ +/* + * Copyright (c) 2006-2018 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.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.25 20180523"; + + +#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 SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + + +static struct option long_options[] = { + {"16", no_argument, 0, 'L'}, + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"id", required_argument, 0, 'i'}, + {"length", required_argument, 0, 'l'}, + {"long", no_argument, 0, 'L'}, + {"mode", required_argument, 0, 'm'}, + {"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 */ +}; + + +static void +usage() +{ + pr2serr("Usage: sg_read_buffer [--16] [--help] [--hex] [--id=ID] " + "[--length=LEN]\n" + " [--long] [--mode=MO] [--offset=OFF] " + "[--raw]\n" + " [--readonly] [--specific=MS] [--verbose] " + "[--version]\n" + " DEVICE\n" + " where:\n" + " --16|-L issue READ BUFFER(16) (def: 10)\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" + " --length=LEN|-l LEN length in bytes to read (def: 4)\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" + " --offset=OFF|-o OFF buffer offset (unit: bytes, def: 0)\n" + " --raw|-r output response 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. Numbers given in options are decimal " + "unless they have\na hex indicator (e.g. a leading '0x').\n" + ); +} + + +#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_EN_EX_ECHO 0x1A +#define MODE_ERR_HISTORY 0x1C + +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)"}, + { "en_ex", MODE_EN_EX_ECHO, + "enable expander communications protocol and echo buffer (spc-3)"}, + { "err_hist", 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(int sg_fd, int rb_mode, int rb_mode_sp, int rb_id, + uint32_t rb_offset, void * resp, int mx_resp_len, + int * residp, bool noisy, int verbose) +{ + int k, 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]; + struct sg_pt_base * ptvp; + + rb10_cb[1] = (uint8_t)(rb_mode & 0x1f); + if (rb_mode_sp) + rb10_cb[1] |= (uint8_t)((rb_mode_sp & 0x7) << 5); + rb10_cb[2] = (uint8_t)rb_id; + sg_put_unaligned_be24(rb_offset, rb10_cb + 3); + sg_put_unaligned_be24(mx_resp_len, rb10_cb + 6); + if (verbose) { + pr2serr(" Read buffer(10) cdb: "); + for (k = 0; k < SG_READ_BUFFER_10_CMDLEN; ++k) + pr2serr("%02x ", rb10_cb[k]); + pr2serr("\n"); + } + + 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, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, "Read buffer(10)", res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + 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(int sg_fd, int rb_mode, int rb_mode_sp, int rb_id, + uint64_t rb_offset, void * resp, int mx_resp_len, + int * residp, bool noisy, int verbose) +{ + int k, 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]; + struct sg_pt_base * ptvp; + + rb16_cb[1] = (uint8_t)(rb_mode & 0x1f); + if (rb_mode_sp) + rb16_cb[1] |= (uint8_t)((rb_mode_sp & 0x7) << 5); + sg_put_unaligned_be64(rb_offset, rb16_cb + 2); + sg_put_unaligned_be24(mx_resp_len, rb16_cb + 11); + rb16_cb[14] = (uint8_t)rb_id; + if (verbose) { + pr2serr(" Read buffer(16) cdb: "); + for (k = 0; k < SG_READ_BUFFER_16_CMDLEN; ++k) + pr2serr("%02x ", rb16_cb[k]); + pr2serr("\n"); + } + + ptvp = construct_scsi_pt_obj(); + if (NULL == ptvp) { + pr2serr("Read buffer(16): out of memory\n"); + 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, mx_resp_len); + res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose); + ret = sg_cmds_process_resp(ptvp, "Read buffer(16)", res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else { + if ((verbose > 2) && (ret > 0)) { + 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; +} + +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_long = false; + bool o_readonly = false; + bool do_raw = false; + bool verbose_given = false; + bool version_given = false; + int res, c, len, k; + int sg_fd = -1; + int do_help = 0; + int do_hex = 0; + int rb_id = 0; + int rb_len = 4; + int rb_mode = 0; + int rb_mode_sp = 0; + int resid = 0; + int verbose = 0; + int ret = 0; + int64_t ll; + uint64_t rb_offset = 0; + const char * device_name = NULL; + uint8_t * resp; + const struct mode_s * mp; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "hHi:l:Lm:o:rRS:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + ++do_help; + break; + case 'H': + ++do_hex; + break; + case 'i': + rb_id = sg_get_num(optarg); + if ((rb_id < 0) || (rb_id > 255)) { + pr2serr("argument to '--id' should be in the range 0 to " + "255\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'l': + rb_len = sg_get_num(optarg); + if (rb_len < 0) { + pr2serr("bad argument to '--length'\n"); + return SG_LIB_SYNTAX_ERROR; + } + if (rb_len > 0xffffff) { + pr2serr("argument to '--length' must be <= 0xffffff\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'L': + do_long = true; + break; + case 'm': + if (isdigit(*optarg)) { + rb_mode = sg_get_num(optarg); + if ((rb_mode < 0) || (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) { + if (0 == strncmp(mp->mode_string, optarg, len)) { + rb_mode = mp->mode; + break; + } + } + if (NULL == mp->mode_string) { + print_modes(); + return SG_LIB_SYNTAX_ERROR; + } + } + break; + case 'o': + ll = sg_get_llnum(optarg); + if (ll < 0) { + pr2serr("bad argument to '--offset'\n"); + return SG_LIB_SYNTAX_ERROR; + } + rb_offset = ll; + break; + case 'r': + do_raw = true; + break; + case 'R': + o_readonly = true; + break; + case 'S': + rb_mode_sp = sg_get_num(optarg); + if ((rb_mode_sp < 0) || (rb_mode_sp > 7)) { + pr2serr("expected argument to '--specific' to be 0 to 7\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; + } + + len = rb_len ? rb_len : 8; + resp = (uint8_t *)malloc(len); + if (NULL == resp) { + pr2serr("unable to allocate %d bytes on the heap\n", len); + return SG_LIB_CAT_OTHER; + } + memset(resp, 0, len); + + if (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 (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, 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 fini; + } + + if (do_long) + res = sg_ll_read_buffer_16(sg_fd, rb_mode, rb_mode_sp, rb_id, + rb_offset, resp, rb_len, &resid, true, + verbose); + else if (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(sg_fd, rb_mode, rb_mode_sp, rb_id, + (uint32_t)rb_offset, resp, rb_len, &resid, + true, verbose); + if (0 != res) { + char b[80]; + + ret = res; + if (res > 0) { + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Read buffer(%d) failed: %s\n", (do_long ? 16 : 10), b); + } + goto fini; + } + if (resid > 0) + rb_len -= resid; /* got back less than requested */ + if (rb_len > 0) { + if (do_raw) + dStrRaw(resp, rb_len); + else if (do_hex || (rb_len < 4)) + hex2stdout((const uint8_t *)resp, rb_len, ((do_hex > 1) ? 0 : 1)); + else { + switch (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; + default: + hex2stdout((const uint8_t *)resp, rb_len, + (verbose > 1 ? 0 : 1)); + break; + } + } + } + +fini: + if (resp) + free(resp); + 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_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 0000000..d094aab --- /dev/null +++ b/src/sg_read_long.c @@ -0,0 +1,323 @@ +/* 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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 0000000..70b40bf --- /dev/null +++ b/src/sg_readcap.c @@ -0,0 +1,680 @@ +/* This code is does a SCSI READ CAPACITY command on the given device + * and outputs the result. + * + * Copyright (C) 1999 - 2018 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.04 20180911"; + +#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 [--brief] [--help] [--hex] [--lba=LBA] " + "[--long] [--16]\n" + " [--pmi] [--raw] [--readonly] [--verbose] " + "[--version]\n" + " [--zbc] DEVICE\n" + " where:\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" + " --16 use READ CAPACITY (16) cdb (same as " + "--long)\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 0000000..73565c3 --- /dev/null +++ b/src/sg_reassign.c @@ -0,0 +1,502 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.26 20180523"; + +#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; + 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 0000000..8977560 --- /dev/null +++ b/src/sg_referrals.c @@ -0,0 +1,385 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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_rep_zones.c b/src/sg_rep_zones.c new file mode 100644 index 0000000..a0d01d2 --- /dev/null +++ b/src/sg_rep_zones.c @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2014-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 command to the given SCSI device + * and decodes the response. Based on zbc-r02.pdf + */ + +static const char * version_str = "1.17 20180628"; + +#define MAX_RZONES_BUFF_LEN (1024 * 1024) +#define DEF_RZONES_BUFF_LEN (1024 * 8) + +#define SG_ZONING_IN_CMDLEN 16 + +#define REPORT_ZONES_SA 0x0 + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"maxlen", required_argument, 0, 'm'}, + {"partial", no_argument, 0, 'p'}, + {"raw", no_argument, 0, 'r'}, + {"readonly", no_argument, 0, 'R'}, + {"report", required_argument, 0, 'o'}, + {"start", required_argument, 0, 's'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + + +static void +usage(int h) +{ + if (h > 1) goto h_twoormore; + pr2serr("Usage: " + "sg_rep_zones [--help] [--hex] [--maxlen=LEN] [--partial]\n" + " [--raw] [--readonly] [--report=OPT] " + "[--start=LBA]\n" + " [--verbose] [--version] DEVICE\n"); + pr2serr(" where:\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" + " --maxlen=LEN|-m LEN max response length (allocation " + "length in cdb)\n" + " (def: 0 -> 8192 bytes)\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" + " --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" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Sends a SCSI REPORT ZONES command and decodes the response. " + "Give\nhelp option twice (e.g. '-hh') to see reporting options " + "enumerated.\n"); + return; +h_twoormore: + pr2serr("Reporting options:\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" + " 0x10 list zones with RWP Recommended set to true\n" + " 0x11 list zones with Non-sequential write resources " + "active set to true\n" + " 0x3f list zones with a zone condition of NOT WRITE " + "POINTER\n"); +} + +/* Invokes a SCSI REPORT ZONES command (ZBC). Return of 0 -> success, + * various SG_LIB_CAT_* positive values or -1 -> other errors */ +static int +sg_ll_report_zones(int sg_fd, uint64_t zs_lba, bool partial, int report_opts, + void * resp, int mx_resp_len, int * residp, bool noisy, + int verbose) +{ + int k, 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]; + struct sg_pt_base * ptvp; + + 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 (verbose) { + pr2serr(" Report zones cdb: "); + for (k = 0; k < SG_ZONING_IN_CMDLEN; ++k) + pr2serr("%02x ", rz_cdb[k]); + pr2serr("\n"); + } + + 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, verbose); + ret = sg_cmds_process_resp(ptvp, "report zones", res, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + 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_type_str(int zt, char * b, int blen, int vb) +{ + const char * cp; + + if (NULL == b) + return "zone_type_str: NULL ptr)"; + switch (zt) { + case 1: + cp = "Conventional"; + break; + case 2: + cp = "Sequential write required"; + break; + case 3: + cp = "Sequential write preferred"; + break; + default: + cp = NULL; + break; + } + if (cp) { + if (vb) + snprintf(b, blen, "%s [0x%x]", cp, zt); + else + snprintf(b, blen, "%s", cp); + } else + snprintf(b, blen, "Reserved [0x%x]", zt); + return b; +} + +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 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]", +}; + + +int +main(int argc, char * argv[]) +{ + bool do_partial = false; + bool do_raw = false; + bool o_readonly = false; + bool verbose_given = false; + bool version_given = false; + int k, res, c, zl_len, len, zones, resid, rlen, zt, zc, same; + int sg_fd = -1; + int do_help = 0; + int do_hex = 0; + int maxlen = 0; + int reporting_opt = 0; + int ret = 0; + int verbose = 0; + uint64_t st_lba = 0; + int64_t ll; + const char * device_name = NULL; + uint8_t * reportZonesBuff = NULL; + uint8_t * free_rzbp = NULL; + uint8_t * bp; + char b[80]; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "hHm:o:prRs:vV", 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_RZONES_BUFF_LEN)) { + pr2serr("argument to '--maxlen' should be %d or " + "less\n", MAX_RZONES_BUFF_LEN); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'o': + reporting_opt = sg_get_num_nomult(optarg); + if ((reporting_opt < 0) || (reporting_opt > 63)) { + pr2serr("bad argument to '--report=OPT', expect 0 to " + "63\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'p': + do_partial = true; + break; + case 'r': + do_raw = true; + break; + case 'R': + o_readonly = true; + break; + case 's': + ll = sg_get_llnum(optarg); + if (-1 == ll) { + pr2serr("bad argument to '--start=LBA'\n"); + return SG_LIB_SYNTAX_ERROR; + } + st_lba = (uint64_t)ll; + 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; + } + } +#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(do_help); + return 0; + } + if (NULL == device_name) { + pr2serr("missing device name!\n"); + usage(1); + 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_RZONES_BUFF_LEN; + reportZonesBuff = (uint8_t *)sg_memalign(maxlen, 0, &free_rzbp, + verbose > 3); + if (NULL == reportZonesBuff) { + pr2serr("unable to sg_memalign %d bytes\n", maxlen); + return sg_convert_errno(ENOMEM); + } + + res = sg_ll_report_zones(sg_fd, st_lba, do_partial, reporting_opt, + reportZonesBuff, 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; + } + zl_len = sg_get_unaligned_be32(reportZonesBuff + 0) + 64; + if (zl_len > rlen) { + if (verbose) + pr2serr("zl_len available is %d, response length is %d\n", + zl_len, rlen); + len = rlen; + } else + len = zl_len; + if (do_raw) { + dStrRaw(reportZonesBuff, len); + goto the_end; + } + if (do_hex && (2 != do_hex)) { + hex2stdout(reportZonesBuff, len, + ((1 == do_hex) ? 1 : -1)); + goto the_end; + } + printf("Report zones response:\n"); + if (len < 64) { + pr2serr("Zone length [%d] too short (perhaps after truncation\n)", + len); + ret = SG_LIB_CAT_MALFORMED; + goto the_end; + } + same = reportZonesBuff[4] & 0xf; + printf(" Same=%d: %s\n\n", same, same_desc_arr[same]); + printf(" Maximum LBA: 0x%" PRIx64 "\n", + sg_get_unaligned_be64(reportZonesBuff + 8)); + zones = (len - 64) / 64; + for (k = 0, bp = reportZonesBuff + 64; k < zones; ++k, bp += 64) { + printf(" Zone descriptor: %d\n", k); + if (do_hex) { + hex2stdout(bp, len, -1); + continue; + } + zt = bp[0] & 0xf; + zc = (bp[1] >> 4) & 0xf; + printf(" Zone type: %s\n", zone_type_str(zt, b, sizeof(b), + verbose)); + printf(" Zone condition: %s\n", zone_condition_str(zc, b, + sizeof(b), verbose)); + printf(" Non_seq: %d\n", !!(bp[1] & 0x2)); + printf(" Reset: %d\n", bp[1] & 0x1); + printf(" Zone Length: 0x%" PRIx64 "\n", + sg_get_unaligned_be64(bp + 8)); + printf(" Zone start LBA: 0x%" PRIx64 "\n", + sg_get_unaligned_be64(bp + 16)); + printf(" Write pointer LBA: 0x%" PRIx64 "\n", + sg_get_unaligned_be64(bp + 24)); + } + if ((64 + (64 * zones)) < zl_len) + printf("\n>>> Beware: Zone list truncated, may need another " + "call\n"); + } else if (SG_LIB_CAT_INVALID_OP == res) + pr2serr("Report zones command not supported\n"); + else { + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Report zones command: %s\n", 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 == verbose) { + if (! sg_if_can2stderr("sg_rep_zones 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_requests.c b/src/sg_requests.c new file mode 100644 index 0000000..76053c4 --- /dev/null +++ b/src/sg_requests.c @@ -0,0 +1,388 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pr2serr.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.32 20180628"; + +#define MAX_REQS_RESP_LEN 255 +#define DEF_REQS_RESP_LEN 252 + +/* Not all environments support the Unix sleep() */ +#if defined(MSC_VER) || defined(__MINGW32__) +#define HAVE_MS_SLEEP +#endif +#ifdef HAVE_MS_SLEEP +#include +#define sleep_for(seconds) Sleep( (seconds) * 1000) +#else +#define sleep_for(seconds) sleep(seconds) +#endif + +#define ME "sg_requests: " + + +static struct option long_options[] = { + {"desc", no_argument, 0, 'd'}, + {"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] [--help] [--hex] [--maxlen=LEN] " + "[--num=NUM]\n" + " [--number=NUM] [--progress] [--raw] " + "[--status]\n" + " [--time] [--verbose] [--version] DEVICE\n" + " where:\n" + " --desc|-d set flag for descriptor sense " + "format\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 res, c, resp_len, k, progress; + int sg_fd = -1; + uint8_t requestSenseBuff[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; + int num_rs = 1; + int do_hex = 0; + int maxlen = 0; + int verbose = 0; + const char * device_name = NULL; + int ret = 0; + char b[80]; +#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, "dhHm:n:prstvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'd': + desc = true; + 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; + } + } + + sg_fd = sg_cmds_open_device(device_name, true /* ro */, 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 (do_progress) { + for (k = 0; k < num_rs; ++k) { + if (k > 0) + sleep_for(30); + memset(requestSenseBuff, 0x0, sizeof(requestSenseBuff)); + res = sg_ll_request_sense(sg_fd, desc, requestSenseBuff, maxlen, + true, verbose); + 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"); + else if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("Request Sense, aborted command\n"); + else { + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Request Sense command: %s\n", b); + } + break; + } + /* "Additional sense length" same in descriptor and fixed */ + resp_len = requestSenseBuff[7] + 8; + if (verbose > 1) { + pr2serr("Parameter data in hex\n"); + hex2stderr(requestSenseBuff, resp_len, 1); + } + progress = -1; + sg_get_sense_progress_fld(requestSenseBuff, resp_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); + } + goto finish; + } + +#ifndef SG_LIB_MINGW + if (do_time) { + start_tm.tv_sec = 0; + start_tm.tv_usec = 0; + gettimeofday(&start_tm, NULL); + } +#endif + + requestSenseBuff[0] = '\0'; + requestSenseBuff[7] = '\0'; + for (k = 0; k < num_rs; ++k) { + memset(requestSenseBuff, 0x0, sizeof(requestSenseBuff)); + res = sg_ll_request_sense(sg_fd, desc, requestSenseBuff, maxlen, + true, verbose); + ret = res; + if (0 == res) { + resp_len = requestSenseBuff[7] + 8; + if (do_raw) + dStrRaw(requestSenseBuff, resp_len); + else if (do_hex) + hex2stdout(requestSenseBuff, resp_len, 1); + else if (1 == num_rs) { + pr2serr("Decode parameter data as sense data:\n"); + sg_print_sense(NULL, requestSenseBuff, resp_len, 0); + if (verbose > 1) { + pr2serr("\nParameter data in hex\n"); + hex2stderr(requestSenseBuff, resp_len, 1); + } + } + continue; + } else 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"); + else if (SG_LIB_CAT_ABORTED_COMMAND == res) + pr2serr("Request Sense, aborted command\n"); + else { + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("Request Sense command: %s\n", b); + } + break; + } + if ((0 == ret) && do_status) { + resp_len = requestSenseBuff[7] + 8; + ret = sg_err_category_sense(requestSenseBuff, resp_len); + if (SG_LIB_CAT_NO_SENSE == ret) { + struct sg_scsi_sense_hdr ssh; + + if (sg_scsi_normalize_sense(requestSenseBuff, resp_len, &ssh)) { + if ((0 == ssh.asc) && (0 == ssh.ascq)) + ret = 0; + } + } + } +#ifndef SG_LIB_MINGW + if (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 (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_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 0000000..9f5648e --- /dev/null +++ b/src/sg_reset.c @@ -0,0 +1,312 @@ +/* A utility program originally written for the Linux OS SCSI subsystem. + * Copyright (C) 1999-2018 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sg_io_linux.h" + + +#define ME "sg_reset: " + +static const char * version_str = "0.66 20180515"; + +#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" + " --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 0000000..42ece34 --- /dev/null +++ b/src/sg_reset_wp.c @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2014-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.12 20180628"; + +#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 k, 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]; + 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) { + pr2serr(" Reset write pointer cdb: "); + for (k = 0; k < SG_ZONING_OUT_CMDLEN; ++k) + pr2serr("%02x ", rwp_cdb[k]); + pr2serr("\n"); + } + + 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, + SG_NO_DATA_IN, sense_b, noisy, verbose, + &sense_cat); + if (-1 == ret) + 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 0000000..332a9c7 --- /dev/null +++ b/src/sg_rmsn.c @@ -0,0 +1,229 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 0000000..ab95598 --- /dev/null +++ b/src/sg_rtpg.c @@ -0,0 +1,361 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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, tgt_port_count; + int sg_fd = -1; + int ret = 0; + int verbose = 0; + uint8_t reportTgtGrpBuff[REPORT_TGT_GRP_BUFF_LEN]; + 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; + } + + memset(reportTgtGrpBuff, 0x0, sizeof(reportTgtGrpBuff)); + /* trunc = 0; */ + + res = sg_ll_report_tgt_prt_grp2(sg_fd, reportTgtGrpBuff, + sizeof(reportTgtGrpBuff), + 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(" <> 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 (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 0000000..3a102e0 --- /dev/null +++ b/src/sg_safte.c @@ -0,0 +1,774 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 0000000..b45462f --- /dev/null +++ b/src/sg_sanitize.c @@ -0,0 +1,808 @@ +/* + * Copyright (c) 2011-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.11 20180628"; + +/* Not all environments support the Unix sleep() */ +#if defined(MSC_VER) || defined(__MINGW32__) +#define HAVE_MS_SLEEP +#endif +#ifdef HAVE_MS_SLEEP +#include +#define sleep_for(seconds) Sleep( (seconds) * 1000) +#else +#define sleep_for(seconds) sleep(seconds) +#endif + + +#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 " + "commnd\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 <<>>: 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 k, ret, res, sense_cat, timeout; + uint8_t san_cdb[SANITIZE_OP_LEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + 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) { + pr2serr(" Sanitize cdb: "); + for (k = 0; k < SANITIZE_OP_LEN; ++k) + pr2serr("%02x ", san_cdb[k]); + pr2serr("\n"); + 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, SG_NO_DATA_IN, sense_b, + true /*noisy */, op->verbose, &sense_cat); + if (-1 == ret) + 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; + + 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] & 0x1f; + 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]) { + if (has_di) { + if (verbose) + pr2serr("VPD_SUPPORTED_VPDS dis-ordered\n"); + return 0; + } + ++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)) { + printf("\nA SANITIZE will commence in 15 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nA SANITIZE will commence in 10 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nA SANITIZE will commence in 5 seconds\n"); + printf(" ALL data on %s will be DESTROYED\n", device_name); + printf(" Press control-C to abort\n"); + sleep_for(5); + } + + 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; + } + sleep_for(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); + /* 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 0000000..7016ef9 --- /dev/null +++ b/src/sg_sat_identify.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2006-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 http://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.17 20180515"; + +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) +{ + 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]; + uint8_t ata_return_desc[16]; + 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]; + const unsigned short * usp; + + sb_sz = sizeof(sense_buffer); + memset(sense_buffer, 0, sb_sz); + memset(apt32_cdb, 0, sizeof(apt32_cdb)); + memset(ata_return_desc, 0, sizeof(ata_return_desc)); + 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 0000000..dc9372b --- /dev/null +++ b/src/sg_sat_phy_event.c @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2006-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.13 20180628"; + +/* This program uses a ATA PASS-THROUGH SCSI command. This usage is + * defined in the SCSI to ATA Translation (SAT) drafts and standards. + * See http://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 false */ +static int +do_read_log_ext(int sg_fd, int log_addr, bool 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 */ + bool t_type = false;/* false -> 512 byte LBs, true -> device's LB size */ + 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]; + uint8_t ata_return_desc[16]; + 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); + memset(sense_buffer, 0, sb_sz); + memset(ata_return_desc, 0, sizeof(ata_return_desc)); + 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 (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, 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 (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, 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, + false /* 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 0000000..3ea7f5c --- /dev/null +++ b/src/sg_sat_read_gplog.c @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2014-2018 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 http://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.20 20180628"; + +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]; + uint8_t ata_return_desc[16]; + 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(sense_buffer, 0, sb_sz); + memset(ata_return_desc, 0, sizeof(ata_return_desc)); + 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 0000000..3433652 --- /dev/null +++ b/src/sg_sat_set_features.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2006-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 http://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.18 20180628"; + +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]; + uint8_t ata_return_desc[16]; + 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); + memset(sense_buffer, 0, sb_sz); + memset(ata_return_desc, 0, sizeof(ata_return_desc)); + 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 0000000..5c0fc2f --- /dev/null +++ b/src/sg_scan_linux.c @@ -0,0 +1,627 @@ +/* A utility program originally written for the Linux OS SCSI subsystem. + * Copyright (C) 1999 - 2018 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_pr2serr.h" + + +static const char * version_str = "4.17 20180219"; + +#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" 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; + } + + 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]; + struct sg_io_hdr io_hdr; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + 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] & 0x1f)); + 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 + + */ +#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 0000000..ae401d9 --- /dev/null +++ b/src/sg_scan_win32.c @@ -0,0 +1,713 @@ +/* + * Copyright (c) 2006-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. + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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.21 (win32) 20180202"; + +#define MAX_SCSI_ELEMS 2048 +#define MAX_ADAPTER_NUM 128 +#define MAX_PHYSICALDRIVE_NUM 1024 +#define MAX_CDROM_NUM 512 +#define MAX_TAPE_NUM 512 +#define MAX_HOLE_COUNT 16 + + +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 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 adapaters\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; + BYTE bus; + BOOL success; + char adapter_name[64]; + char inqDataBuff[8192]; + PSCSI_ADAPTER_BUS_INFO ai; + char b[256]; + + 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, inqDataBuff, + sizeof(inqDataBuff), &dummy, NULL); + if (success) { + PSCSI_BUS_DATA pbd; + PSCSI_INQUIRY_DATA pid; + int num_lus, off; + + ai = (PSCSI_ADAPTER_BUS_INFO)inqDataBuff; + 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 > ((int)sizeof(inqDataBuff) - + (int)sizeof(SCSI_INQUIRY_DATA)))) + break; + pid = (PSCSI_INQUIRY_DATA)(inqDataBuff + 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] % 0x3f, + ((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)); + } + 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_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 *)calloc(sizeof(struct storage_elem) * + MAX_SCSI_ELEMS, 1); + if (storage_arr) { + ret = sg_do_wscan(vol_letter, show_bt, scsi_scan); + free(storage_arr); + } else { + pr2serr("Failed to allocate storage_arr on heap\n"); + ret = SG_LIB_SYNTAX_ERROR; + } + return ret; +} diff --git a/src/sg_seek.c b/src/sg_seek.c new file mode 100644 index 0000000..093c6b7 --- /dev/null +++ b/src/sg_seek.c @@ -0,0 +1,424 @@ +/* + * Copyright (c) 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) +#include +#elif defined(HAVE_GETTIMEOFDAY) +#include +#include +#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.07 20180911"; + +#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) { + if (! sg_if_can2stderr("sg_seek 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_senddiag.c b/src/sg_senddiag.c new file mode 100644 index 0000000..60144e1 --- /dev/null +++ b/src/sg_senddiag.c @@ -0,0 +1,961 @@ +/* A utility program originally written for the Linux OS SCSI subsystem +* Copyright (C) 2003-2018 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + This program issues the SCSI SEND DIAGNOSTIC command and in one case + the SCSI RECEIVE DIAGNOSTIC command to list supported diagnostic pages. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#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.63 20180628"; + +#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(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 : "")); +} + + +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; + + secs = sg_get_unaligned_be16(rsp_buff + 8 + bd_len + 10); +#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) ? "" : + ""; + 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 0000000..abb1fea --- /dev/null +++ b/src/sg_ses.c @@ -0,0 +1,5891 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.43 20180810"; /* ses4r02 */ + +#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_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 +#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 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 [=] or + * :[:][=]. 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 to :: 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 stategies: + * [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 ... */ + {"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[] = { + {"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'}, + {"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, 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 [--descriptor=DES] [--dev-slot-num=SN] " + "[--eiioe=A_F]\n" + " [--filter] [--get=STR] [--hex] " + "[--index=IIA | =TIA,II]\n" + " [--inner-hex] [--join] [--maxlen=LEN] " + "[--page=PG] [--quiet]\n" + " [--raw] [--readonly] [--sas-addr=SA] [--status] " + "[--verbose]\n" + " [--warn] DEVICE\n\n" + " sg_ses [--byte1=B1] [--clear=STR] [--control] " + "[--data=H,H...]\n" + " [--descriptor=DES] [--dev-slot-num=SN] " + "[--index=IIA | =TIA,II]\n" + " [--mask] [--maxlen=LEN] [--nickid=SEID] " + "[--nickname=SEN]\n" + " [--page=PG] [--sas-addr=SA] [--set=STR] " + "[--verbose]\n" + " DEVICE\n\n" + " sg_ses --data=@FN --status [-rr] []\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 [-D DES] [-x SN] [-E A_F] [-f] [-G STR] [-H] " + "[-I IIA|TIA,II] [-i]\n" + " [-j] [-m LEN] [-p PG] [-q] [-r] [-R] [-A SA] " + "[-s] [-v] [-w] 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] []\n\n" + " sg_ses [-e] [-h] [-I IIA] [-l] [-V]\n" + ); + pr2serr("\nFor help use with '-h' one or more times\n"); + return; + } + pr2serr( + " where the main options are:\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" + " --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" + " --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 ':" + "[:][=]'\nor '[=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 char * cc3p; + 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 { + 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(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 */ + 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 for '--index', expect " + " 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 */ + 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 for '--index', " + "expect 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; + uint64_t saddr; + const char * cp; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "A:b:cC:d:D:eE:fG:hHiI:jln:N:m:Mp:qrRs" + "S:vVwx:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'A': /* SAS address, assumed to be hex */ + cp = optarg; + if ((strlen(optarg) > 2) && ('X' == toupper(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 < 4) + pr2serr("Warning: --maxlen=LEN less than 4 ignored\n"); + else + op->maxlen = n; + break; + case 'M': + op->mask_ign = true; + break; + case 'p': + if (isdigit(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; + 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) { + 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), op->verbose)) { + pr2serr("bad argument, expect '--data=H,H...', '--data=-' or " + "'--data=@FN'\n"); + return SG_LIB_SYNTAX_ERROR; + } + op->do_raw = 0; + if (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 (n > 1) + op->many_dpages = true; + else if (1 == n) { + op->page_code_given = true; + op->page_code = pc; + } else { + pr2serr("No dpage found --data= argument\n"); + goto err_help; + } + 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; + char * cp; + 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: [=]\n"); + return -1; + } + } + } + if (isalpha(buff[0])) + tavp->acron = buff; + else { + colp = strchr(buff, ':'); + if ((NULL == colp) || (buff == colp)) + return -1; + *colp = '\0'; + if (('0' == buff[0]) && ('X' == toupper(buff[1]))) { + if (1 != sscanf(buff + 2, "%x", &ui)) + return -1; + tavp->start_byte = ui; + } else if ('H' == toupper(*(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(" 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(" 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(" 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) +{ + const bool pf_bit = true; + int page_num, ret; + const char * cp; + + if (outgoing_pg && (verbose > 2)) { + page_num = ((const char *)outgoing_pg)[0]; + 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 */, 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(" <<>>\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, "SAS SlimLine 4i"); + else + snprintf(buff, buff_len, "SAS SlimLine 4i (SFF-8654) " + "[max 4 phys]"); + break; + case 0x16: + if (abridged) + snprintf(buff, buff_len, "SAS SlimLine 8i"); + else + snprintf(buff, buff_len, "SAS SlimLine 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 < 0x30) + snprintf(buff, buff_len, "unknown internal connector to end " + "device, 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; +} + +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), (((0x7 & statp[1]) << 8) + statp[2]) * 10, + actual_speed_desc[7 & statp[3]]); + 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: \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; + default: + 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; + uint16_t dcs; + + printf("%sIdent=%d, Fail=%d, Display mode status=%d", pad, + !!(statp[1] & 0x80), !!(statp[1] & 0x40), dms); + if ((1 == dms) || (2 == dms)) { + 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(" <>\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(" <<>>\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(" <>\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(" <<>>\n"); + return; +} + +static char * +reserved_or_num(char * buff, int buff_len, int num, int reserve_num) +{ + if (num == reserve_num) + strncpy(buff, "", 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\n", pad, + reserved_or_num(b, 128, tp[0] - TEMPERAT_OFF, -TEMPERAT_OFF), + reserved_or_num(b2, 128, tp[1] - TEMPERAT_OFF, -TEMPERAT_OFF)); + printf("%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, ""); + else + snprintf(b, sizeof(b), "%d", tp[2]); + printf("%slow warning=%s, ", pad, b); + if (0 == tp[3]) + strcpy(b, ""); + 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(" <>\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(" <<>>\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(" <>\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: \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: \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(" <<>>\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, print_sas_addr, saddr_nz; + 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) { + 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" : "")); + print_sas_addr = false; + 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 cid_valid, psn_valid, bdf_valid, 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 (1 == pcie_pt) + printf("%sPCIe protocol type: NVMe\n", pad); + else { + 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); + 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) { + printf("%sport index: %d\n", pad, j); + psn_valid = !!(0x4 & aep[0]); + bdf_valid = !!(0x2 & aep[0]); + cid_valid = !!(0x1 & aep[0]); + 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)); + 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)); + } + 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] + * 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(" <>\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(" <<>>\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(" \n"); + } + return; +truncated: + pr2serr(" <<>>\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(" \n"); + } + return; +truncated: + pr2serr(" <<>>\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(" <<>>\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 char * cp; + const struct diag_page_abbrev * ap; + + printf("%s:\n", leadin); + for (k = 0, prev = 0; k < (resp_len - 4); ++k, prev = code) { + 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(" [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(" <<>>\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, then the first skipped (since + * (it should be '@'). */ +static int +read_hex(const char * inp, uint8_t * arr, int mx_arr_len, int * arr_len, + bool in_hex, int vb) +{ + bool has_stdin, split_line; + int in_len, k, j, m, off; + 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; + 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 + 1, O_RDONLY); + if (fd < 0) { + pr2serr("unable to open binary file %s: %s\n", inp, + 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); + else + pr2serr("read from binary file %s: %s\n", inp, + 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, + 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 || ('@' == inp[0])) { /* read from stdin or file */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(inp + 1, "r"); + if (NULL == fp) { + pr2serr("%s: unable to open file: %s\n", __func__, inp + 1); + 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(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(" \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(" \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 ? 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) +{ + int m; + + 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 */ + /* 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("[: ,]\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) +{ + bool et_used_by_aes; + 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) { + 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 j, 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 = " <>\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) { + 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, c2; + + do { + 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 ret, len, s_byte, s_bit, n_bits, k; + uint64_t ui; + 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) { + 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 */ + if ((! op->mask_ign) && (jrp->etype < NUM_ETC)) { + 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) { + 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 ret, len, s_byte, s_bit, n_bits; + uint64_t ui; + 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) { + 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 { + 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) { + 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; + uint64_t ui; + 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) { + 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 ((NULL == jrp->enc_statp) || (k >= MX_JOIN_ROWS)) { + 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; + const struct element_type_t * etp; + const struct acronym2tuple * ap; + char b[64]; + char a[160]; + const char * cp; + + 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) { + 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 { + char bb[64]; + bool given_et = false; + + /* 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)", bb); + 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, 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 = 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; + } + 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))) { + if ((ret = sg_ll_inquiry_pt(ptvp, false, 0, enc_stat_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", enc_stat_rsp + 8, + enc_stat_rsp + 16, enc_stat_rsp + 32); + pd_type = 0x1f & enc_stat_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 & enc_stat_rsp[6]) + printf(" %s device has EncServ bit set\n", cp); + else { + if (0 != memcmp("NVMe", enc_stat_rsp + 8, 4)) + printf(" %s device (not an enclosure)\n", cp); + } + } + clear_scsi_pt_obj(ptvp); + /* finished using enc_stat_rsp so clear back to zero */ + memset(enc_stat_rsp, 0, enc_stat_rsp_sz); + } + } 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); + 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, 4096); + } +#endif + + if (ptvp) { + ret = sg_ll_request_sense_pt(ptvp, false, enc_stat_rsp, + REQUEST_SENSE_RESP_SZ, ! op->quiet, vb); + if (0 == ret) { + int sense_len = REQUEST_SENSE_RESP_SZ - 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, REQUEST_SENSE_RESP_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 infomation\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 0000000..4f9d70e --- /dev/null +++ b/src/sg_ses_microcode.c @@ -0,0 +1,941 @@ +/* + * Copyright (c) 2014-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 SEND DIAGNOSTIC and RECEIVE DIAGNOSTIC + * RESULTS commands in order to send microcode to the given SES device. + */ + +static const char * version_str = "1.16 20180628"; /* 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}, +}; + +struct diag_page_code { + int page_code; + const char * desc; +}; + +/* 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 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=,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 diag_page_code * mcsp; + + for (mcsp = mc_status_arr; mcsp->desc; ++mcsp) { + if (status_val == mcsp->page_code) + return mcsp->desc; + } + 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(" <<>>\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(*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_mocrocode 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 0000000..a5bf678 --- /dev/null +++ b/src/sg_start.c @@ -0,0 +1,616 @@ +/* + * Copyright (C) 1999-2018 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + + Start/Stop parameter by Kurt Garloff , 6/2000 + Sync cache parameter by Kurt Garloff , 1/2001 + Guard block device answering sg's ioctls. + 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 +#include +#include +#include +#include +#include +#include +#include + +#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.66 20180628"; /* 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" + " -N|--new 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 0000000..c592e80 --- /dev/null +++ b/src/sg_stpg.c @@ -0,0 +1,725 @@ +/* +* Copyright (c) 2004-2018 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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.19 20180628"; + +#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; + int off, u; + const uint8_t * bp; + const uint8_t * ip; + + *d_id = -1; + *d_tpg = -1; + off = -1; + while ((u = 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 traling + * '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(*lcp)) { + try_num = false; + switch (toupper(*lcp)) { + case 'A': + if ('N' == toupper(*(lcp + 1))) + state_arr[k] = 1; + else if ('O' == toupper(*(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(" <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 0000000..7e977a1 --- /dev/null +++ b/src/sg_stream_ctl.c @@ -0,0 +1,507 @@ +/* + * Copyright (c) 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.06 20180628"; + +#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]; + 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) { + pr2serr(" %s cdb: ", cmd_name); + for (k = 0; k < (int)sizeof(gssCdb); ++k) + pr2serr("%02x ", gssCdb[k]); + pr2serr("\n"); + } + + 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, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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]; + 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) { + pr2serr(" %s cdb: ", cmd_name); + for (k = 0; k < (int)sizeof(scCdb); ++k) + pr2serr("%02x ", scCdb[k]); + pr2serr("\n"); + } + + 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, alloc_len, sense_b, + noisy, verbose, &sense_cat); + if (-1 == ret) + 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 > 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) < 4) { + 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) + 4; + 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 - 4); 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 0000000..2686791 --- /dev/null +++ b/src/sg_sync.c @@ -0,0 +1,309 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.23 20180628"; + +#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, k, 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]; + 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 & 0x1f; + sg_put_unaligned_be32((uint32_t)num_lb, sc_cdb + 10); + + if (verbose) { + pr2serr(" synchronize cache(16) cdb: "); + for (k = 0; k < SYNCHRONIZE_CACHE16_CMDLEN; ++k) + pr2serr("%02x ", sc_cdb[k]); + pr2serr("\n"); + } + 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, + SG_NO_DATA_IN, sense_b, noisy, verbose, + &sense_cat); + if (-1 == ret) + 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 0000000..f187ba0 --- /dev/null +++ b/src/sg_test_rwbuf.c @@ -0,0 +1,578 @@ +/* + * (c) 2000 Kurt Garloff + * heavily based on Douglas Gilbert's sg_rbuf program. + * (c) 1999-2018 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. + * + * $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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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.18 20180628"; + +#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 k, 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) { + pr2serr(" read buffer [mode desc] cdb: "); + for (k = 0; k < (int)sizeof(rb_cdb); ++k) + pr2serr("%02x ", rb_cdb[k]); + pr2serr("\n"); + } + 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 (); + 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, k; + 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) { + pr2serr(" read buffer [mode data] cdb: "); + for (k = 0; k < (int)sizeof(rb_cdb); ++k) + pr2serr("%02x ", rb_cdb[k]); + pr2serr("\n"); + } + + 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 k, 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) { + pr2serr(" write buffer [mode data] cdb: "); + for (k = 0; k < (int)sizeof(wb_cdb); ++k) + pr2serr("%02x ", wb_cdb[k]); + pr2serr("\n"); + } + + 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 = true; + bool version_given = true; + 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 \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 " + "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 0000000..3168997 --- /dev/null +++ b/src/sg_timestamp.c @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2015-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.12 20180910"; + +#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 ' days hh:mm:ss.xxx' " + "where\n" + " '.xxx' is the remainder milliseconds. " + "Don't show\n" + " ' days' if 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=@' .\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]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32((uint32_t)mx_resp_len, rt_cdb + 6); + if (verbose) { + pr2serr(" Report timestamp cdb: "); + for (k = 0; k < REP_TIMESTAMP_CMDLEN; ++k) + pr2serr("%02x ", rt_cdb[k]); + pr2serr("\n"); + } + + 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, mx_resp_len, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + 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 k, 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]; + struct sg_pt_base * ptvp; + + sg_put_unaligned_be32(param_len, st_cdb + 6); + if (verbose) { + pr2serr(" Set timestamp cdb: "); + for (k = 0; k < SET_TIMESTAMP_CMDLEN; ++k) + pr2serr("%02x ", st_cdb[k]); + pr2serr("\n"); + 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, SG_NO_DATA_IN, + sense_b, noisy, verbose, &sense_cat); + if (-1 == ret) + 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 0000000..b68f1a4 --- /dev/null +++ b/src/sg_turs.c @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2000-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. + * + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) +#include +#elif defined(HAVE_GETTIMEOFDAY) +#include +#include +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_pt.h" +#include "sg_pr2serr.h" + + +static const char * version_str = "3.44 20180712"; + +#if defined(MSC_VER) || defined(__MINGW32__) +#define HAVE_MS_SLEEP +#endif + +#ifdef HAVE_MS_SLEEP +#include +#define sleep_for(seconds) Sleep( (seconds) * 1000) +#else +#define sleep_for(seconds) sleep(seconds) +#endif + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + + +static struct option long_options[] = { + {"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 do_low; + bool do_progress; + bool do_time; + bool opts_new; + bool verbose_given; + bool version_given; + 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 [--help] [--low] [--number=NUM] [--num=NUM] " + "[--progress]\n" + " [--time] [--verbose] [--version] DEVICE\n" + " where:\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" + " --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"); +} + +static void +usage_old() +{ + printf("Usage: sg_turs [-l] [-n=NUM] [-p] [-t] [-v] [-V] " + "DEVICE\n" + " where:\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, "hln:NOptvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + 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 '?': + usage_old(); + return 0; + default: + jmp_out = true; + break; + } + if (jmp_out) + break; + } + if (plen <= 0) + continue; + 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; +} + +/* 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 vb = op->verbose; + char b[80]; + + if (op->do_low) { + int rs, n, sense_cat; + uint8_t cdb[6]; + uint8_t sense_b[32]; + + for (k = 0; k < op->do_number; ++k) { + /* 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)); + rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, vb); + n = sg_cmds_process_resp(ptvp, "Test unit ready", rs, + SG_NO_DATA_IN, sense_b, + (0 == k), vb, &sense_cat); + if (-1 == n) { + 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) { + resp->ret = sense_cat; + printf("device not ready\n"); + 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; + 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; + } + } + clear_scsi_pt_obj(ptvp); + } + return k; + } else { + for (k = 0; k < op->do_number; ++k) { + /* 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) { + if (SG_LIB_CAT_NOT_READY == res) + printf("device not ready\n"); + 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 (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 (k > 0) + sleep_for(30); + 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 0000000..726eb23 --- /dev/null +++ b/src/sg_unmap.c @@ -0,0 +1,813 @@ +/* + * Copyright (c) 2009-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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" + +#if defined(MSC_VER) || defined(__MINGW32__) +#define HAVE_MS_SLEEP +#endif + +#ifdef HAVE_MS_SLEEP +#include +#define sleep_for(seconds) Sleep( (seconds) * 1000) +#else +#define sleep_for(seconds) sleep(seconds) +#endif + +/* 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.17 20180628"; + + +#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; + + 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 && (stdin != fp)) + fclose(fp); + return 0; + +bad_exit: + if (fp && (stdin != fp)) + 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); + sleep_for(3); + if (to_end_of_device) + snprintf(b, sizeof(b), "LBA 0x%" PRIx64 " to end of %s " + "(0x%" PRIx64 ")", all_start, device_name, all_last); + else + snprintf(b, sizeof(b), "LBA 0x%" PRIx64 " to 0x%" PRIx64 + " on %s", all_start, all_last, device_name); + printf("\nAn UNMAP (a.k.a. trim) will commence in 15 seconds\n"); + printf(" ALL data from %s will be LOST\n", b); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nAn UNMAP will commence in 10 seconds\n"); + printf(" ALL data from %s will be LOST\n", b); + printf(" Press control-C to abort\n"); + sleep_for(5); + printf("\nAn UNMAP (a.k.a. trim) will commence in 5 seconds\n"); + printf(" ALL data from %s will be LOST\n", b); + printf(" Press control-C to abort\n"); + sleep_for(7); + } + 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); + sleep_for(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"); + sleep_for(5); + printf("\nAn UNMAP will commence in 10 seconds\n"); + printf(" Some data will be LOST\n"); + printf(" Press control-C to abort\n"); + sleep_for(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"); + sleep_for(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 0000000..0e186a9 --- /dev/null +++ b/src/sg_verify.c @@ -0,0 +1,449 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.25 20180628"; /* sbc4r15 */ + +#define ME "sg_verify: " + +#define EBUFF_SZ 256 + + +static struct option long_options[] = { + {"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 [--16] [--bpc=BPC] [--count=COUNT] [--dpo] " + "[--ebytchk=BCH]\n" + " [--group=GN] [--help] [--in=IF] " + "[--lba=LBA] [--ndo=NDO]\n" + " [--quiet] [--readonly] [--verbose] " + "[--version]\n" + " [--vrprotect=VRP] DEVICE\n" + " where:\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" + " If BCH=3 then COUNT must " + "be 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" + " --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" + "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 got_stdin = false; + bool quiet = false; + bool readonly = false; + bool verbose_given = false; + bool verify16 = false; + bool version_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, "b:B:c:dE:g:hi:l:n:P:qrSvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + 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 < 1) || (bytchk > 3)) { + pr2serr("bad argument to '--ebytchk'\n"); + return SG_LIB_SYNTAX_ERROR; + } + 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 = false; + 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 ((3 == bytchk) && (1 != count)) { + pr2serr("count must be 1 when bytchk=3\n"); + return SG_LIB_SYNTAX_ERROR; + } + bpc = (int)count; + } else if (bytchk > 0) { + pr2serr("when the 'ebytchk=BCH' option is given, then '--bytchk=NDO' " + "must also be given\n"); + 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 ((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); + } + + if (NULL == device_name) { + pr2serr("missing device name!\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 reported MISCOMPARE\n", vc); + 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 0000000..a15ed74 --- /dev/null +++ b/src/sg_vpd.c @@ -0,0 +1,4003 @@ +/* + * Copyright (c) 2006-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include + +#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" + +/* 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 http://www.t10.org . + +*/ + +static const char * version_str = "1.48 20180910"; /* spc5r19 + sbc4r15 */ + +/* 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-4, SBC-3 */ +#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_NOPE_WANT_STD_INQ -2 /* request for standard inquiry */ + +/* 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 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 */ + + +/* These structures are duplicates of those of the same name in + * sg_vpd_vendor.c . Take care that both are the same. */ +struct opts_t { + bool do_all; + bool do_enum; + bool do_force; + bool do_long; + bool do_quiet; + bool verbose_given; + bool version_given; + int do_hex; + int vpd_pn; + int do_ident; + int maxlen; + int do_raw; + int vend_prod_num; + int verbose; + const char * device_name; + const char * page_str; + const char * inhex_fn; + const char * vend_prod; +}; + +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; +}; + + +/* Following functions also used by sg_vpd_vendor.c hence extern */ +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, 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); + +static int svpd_decode_t10(int sg_fd, struct opts_t * op, int subvalue, + int off); +static int svpd_unable_to_decode(int sg_fd, struct opts_t * op, int subvalue, + int off); + +static int decode_dev_ids(const char * print_if_found, uint8_t * buff, + int len, int m_assoc, int m_desig_type, + int m_code_set, const struct opts_t * op); + +uint8_t * rsp_buff; +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'}, + {"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'}, + {"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'}, + {"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_ATA_INFO, 0, -1, "ai", "ATA information (SAT)"}, + {VPD_ASCII_OP_DEF, 0, -1, "aod", + "ASCII implemented operating definition (obsolete)"}, + {VPD_AUTOMATION_DEV_SN, 0, 1, "adsn", "Automation device serial " + "number (SSC)"}, + {VPD_BLOCK_LIMITS, 0, 0, "bl", "Block limits (SBC)"}, + {VPD_BLOCK_LIMITS_EXT, 0, 0, "ble", "Block limits extension (SBC)"}, + {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_CFA_PROFILE_INFO, 0, 0, "cfa", "CFA profile information"}, + {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_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"}, + {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 response"}, + {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, "zbdc", "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] [--force] [--help] [--hex] " + "[--ident]\n" + " [--inhex=FN] [--long] [--maxlen=LEN] " + "[--page=PG] [--quiet]\n" + " [--raw] [--vendor=VP] [--verbose] [--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" + " --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" + " --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" + " --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' yields the standard INQUIRY " + "response.\n"); +} + +/* Read ASCII hex bytes or binary from fname (a file named '-' taken as + * stdin). If reading ASCII hex then there should be either one entry per + * line or a comma, space or tab separated list of bytes. If no_space is + * set then a string of ACSII hex digits is expected, 2 per byte. Everything + * from and including a '#' on a line is ignored. Returns 0 if ok, or an + * error code. */ +static int +f2hex_arr(const char * fname, int as_binary, int no_space, + uint8_t * mp_arr, int * mp_arr_len, int max_arr_len) +{ + int fn_len, in_len, k, j, m, fd, err; + bool has_stdin, split_line; + unsigned int h; + const char * lcp; + FILE * fp; + char line[512]; + char carry_over[4]; + int off = 0; + struct stat a_stat; + + if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) + return SG_LIB_LOGIC_ERROR; + fn_len = strlen(fname); + if (0 == fn_len) + return SG_LIB_SYNTAX_ERROR; + has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */ + if (as_binary) { + if (has_stdin) + fd = STDIN_FILENO; + else { + fd = open(fname, O_RDONLY); + if (fd < 0) { + err = errno; + pr2serr("unable to open binary file %s: %s\n", fname, + safe_strerror(err)); + return sg_convert_errno(err); + } + } + k = read(fd, mp_arr, max_arr_len); + if (k <= 0) { + int ret = SG_LIB_SYNTAX_ERROR; + + if (0 == k) + pr2serr("read 0 bytes from binary file %s\n", fname); + else { + ret = sg_convert_errno(errno); + pr2serr("read from binary file %s: %s\n", fname, + safe_strerror(errno)); + } + if (! has_stdin) + close(fd); + return ret; + } + if ((0 == fstat(fd, &a_stat)) && S_ISFIFO(a_stat.st_mode)) { + /* pipe; keep reading till error or 0 read */ + while (k < max_arr_len) { + m = read(fd, mp_arr + k, max_arr_len - k); + if (0 == m) + break; + if (m < 0) { + err = errno; + pr2serr("read from binary pipe %s: %s\n", fname, + safe_strerror(err)); + if (! has_stdin) + close(fd); + return sg_convert_errno(err); + } + k += m; + } + } + *mp_arr_len = k; + if (! has_stdin) + close(fd); + return 0; + } else { /* So read the file as ASCII hex */ + if (has_stdin) + fp = stdin; + else { + fp = fopen(fname, "r"); + if (NULL == fp) { + err = errno; + pr2serr("Unable to open %s for reading: %s\n", fname, + safe_strerror(err)); + return sg_convert_errno(err); + } + } + } + + carry_over[0] = 0; + for (j = 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(line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%4x", &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); + 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; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + goto bad; + } + if (no_space) { + for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1)); + ++k, lcp += 2) { + if (1 != sscanf(lcp, "%2x", &h)) { + pr2serr("%s: bad hex number in line %d, pos %d\n", + __func__, j + 1, (int)(lcp - line + 1)); + goto bad; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + } + if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1)))) + carry_over[0] = *lcp; + off += k; + } else { + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%10x", &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) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + goto bad; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if (('#' == *lcp) || ('\r' == *lcp)) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + goto bad; + } + } + off += (k + 1); + } + } + *mp_arr_len = off; + if (stdin != fp) + fclose(fp); + return 0; +bad: + if (stdin != fp) + fclose(fp); + return 1; +} + +/* 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; + } +} + +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; + + for (vnp = standard_vpd_pg, matches = 0; 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", + "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, int verbose) +{ + int pqual, n; + + if (len < 4) + return; + pqual = (b[0] & 0xe0) >> 5; + printf("standard INQUIRY:"); + if (0 == pqual) + printf("\n"); + else if (1 == pqual) + printf(" [PQ indicates LU temporarily unavailable]\n"); + else if (3 == pqual) + printf(" [PQ indicates LU not accessible via this port]\n"); + else + printf(" [reserved or vendor specific qualifier [%d]]\n", pqual); + printf(" PQual=%d Device_type=%d RMB=%d LU_CONG=%d version=0x%02x ", + pqual, b[0] & 0x1f, !!(b[1] & 0x80), !!(b[1] & 0x40), + (unsigned int)b[2]); + printf(" [%s]\n", sg_ansi_version_arr[b[2] & 0xf]); + printf(" [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d " + " Resp_data_format=%d\n", + !!(b[3] & 0x80), !!(b[3] & 0x40), !!(b[3] & 0x20), + !!(b[3] & 0x10), b[3] & 0x0f); + if (len < 5) + return; + n = b[4] + 5; + if (verbose) + pr2serr(">> requested %d bytes, %d bytes available\n", len, n); + printf(" SCCS=%d ACC=%d TPGS=%d 3PC=%d Protect=%d ", + !!(b[5] & 0x80), !!(b[5] & 0x40), ((b[5] & 0x30) >> 4), + !!(b[5] & 0x08), !!(b[5] & 0x01)); + printf(" [BQue=%d]\n EncServ=%d ", !!(b[6] & 0x80), !!(b[6] & 0x40)); + if (b[6] & 0x10) + printf("MultiP=1 (VS=%d) ", !!(b[6] & 0x20)); + else + printf("MultiP=0 "); + printf("[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n [RelAdr=%d] ", + !!(b[6] & 0x08), !!(b[6] & 0x04), !!(b[6] & 0x01), + !!(b[7] & 0x80)); + printf("WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ", + !!(b[7] & 0x20), !!(b[7] & 0x10), !!(b[7] & 0x08), + !!(b[7] & 0x04)); + printf("CmdQue=%d\n", !!(b[7] & 0x02)); + if (len < 36) + return; + printf(" Vendor_identification: %.8s\n", b + 8); + printf(" Product_identification: %.16s\n", b + 16); + printf(" Product_revision_level: %.4s\n", b + 32); +} + +static void +decode_id_vpd(uint8_t * buff, int len, int subvalue, + const struct opts_t * op) +{ + int m_a, m_d, m_cs, 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; + m_d = -1; + m_cs = -1; + if (0 == subvalue) { + decode_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_LU), b, blen, + VPD_ASSOC_LU, m_d, m_cs, op); + decode_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TPORT), b, blen, + VPD_ASSOC_TPORT, m_d, m_cs, op); + decode_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TDEVICE), b, blen, + VPD_ASSOC_TDEVICE, m_d, m_cs, op); + } else if (VPD_DI_SEL_AS_IS == subvalue) + decode_dev_ids(NULL, b, blen, m_a, m_d, m_cs, op); + else { + if (VPD_DI_SEL_LU & subvalue) + decode_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_LU), b, blen, + VPD_ASSOC_LU, m_d, m_cs, op); + if (VPD_DI_SEL_TPORT & subvalue) + decode_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TPORT), b, blen, + VPD_ASSOC_TPORT, m_d, m_cs, op); + if (VPD_DI_SEL_TARGET & subvalue) + decode_dev_ids(sg_get_desig_assoc_str(VPD_ASSOC_TDEVICE), + b, blen, VPD_ASSOC_TDEVICE, m_d, m_cs, op); + } +} + +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 */ +static void +decode_net_man_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump, na_len; + uint8_t * bp; + + if ((1 == do_hex) || (do_hex > 2)) { + hex2stdout(buff, len, (1 == 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) { + printf(" %s, Service type: %s\n", + sg_get_desig_assoc_str((bp[0] >> 5) & 0x3), + network_service_type_arr[bp[0] & 0x1f]); + na_len = sg_get_unaligned_be16(bp + 2); + 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; + } + if (na_len > 0) { + if (do_hex > 1) { + printf(" Network address:\n"); + hex2stdout((bp + 4), na_len, 0); + } else + printf(" %s\n", bp + 4); + } + } +} + +static const char * mode_page_policy_arr[] = +{ + "shared", + "per target port", + "per initiator port", + "per I_T nexus", +}; + +/* VPD_MODE_PG_POLICY */ +static void +decode_mode_policy_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump; + uint8_t * bp; + + if ((1 == do_hex) || (do_hex > 2)) { + hex2stdout(buff, len, (1 == 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 (do_hex > 1) + hex2stdout(bp, 4, 1); + else { + printf(" Policy page code: 0x%x", (bp[0] & 0x3f)); + if (bp[1]) + printf(", subpage code: 0x%x\n", bp[1]); + else + printf("\n"); + if ((0 == k) && (0x3f == (0x3f & bp[0])) && (0xff == bp[1])) + printf(" therefore the policy applies to all modes pages " + "and subpages\n"); + printf(" MLUS=%d, Policy: %s\n", !!(bp[2] & 0x80), + mode_page_policy_arr[bp[2] & 0x3]); + } + } +} + +/* VPD_SCSI_PORTS */ +static void +decode_scsi_ports_vpd(uint8_t * buff, int len, const struct opts_t * op) +{ + int k, bump, rel_port, ip_tid_len, tpd_len; + uint8_t * bp; + + if ((1 == op->do_hex) || (op->do_hex > 2)) { + hex2stdout(buff, len, (1 == op->do_hex) ? 1 : -1); + 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); + printf(" Relative port=%d\n", 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) { + printf(" Initiator port transport id:\n"); + hex2stdout((bp + 8), ip_tid_len, 1); + } else { + char b[1024]; + + printf("%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) { + printf(" Target port descriptor(s):\n"); + hex2stdout(bp + bump + 4, tpd_len, 1); + } else { + if ((0 == op->do_quiet) || (ip_tid_len > 0)) + printf(" Target port descriptor(s):\n"); + decode_dev_ids("SCSI Ports", bp + bump + 4, tpd_len, + VPD_ASSOC_TPORT, -1, -1, op); + } + } + bump += tpd_len + 4; + } +} + +/* Prints outs an abridged set of device identification designators + selected by association, designator type and/or code set. */ +static int +decode_dev_ids_quiet(uint8_t * buff, int len, int m_assoc, + int m_desig_type, int m_code_set) +{ + 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, m_desig_type, + m_code_set); + 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. */ +static int +decode_dev_ids(const char * print_if_found, uint8_t * buff, int len, + int m_assoc, int m_desig_type, int m_code_set, + const struct opts_t * op) +{ + int assoc, off, u, i_len; + bool printed; + const uint8_t * bp; + char b[1024]; + + if (op->do_quiet) + return decode_dev_ids_quiet(buff, len, m_assoc, m_desig_type, + m_code_set); + 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, m_desig_type, + m_code_set)) == 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; + printf(" %s:\n", print_if_found); + } + if (NULL == print_if_found) + printf(" %s:\n", sg_get_desig_assoc_str(assoc)); + sg_get_designation_descriptor_str("", bp, i_len + 4, false, + op->do_long, sizeof(b), b); + 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_EXT_INQ Extended Inquiry VPD */ +static void +decode_x_inq_vpd(uint8_t * b, int len, int do_hex, bool do_long, + bool protect) +{ + int n; + + if (len < 7) { + pr2serr("Extended INQUIRY data VPD page length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(b, len, (1 == do_hex) ? 0 : -1); + return; + } + if (do_long) { + n = (b[4] >> 6) & 0x3; + printf(" ACTIVATE_MICROCODE=%d", n); + if (1 == n) + printf(" [before final WRITE BUFFER]\n"); + else if (2 == n) + printf(" [after power on or hard reset]\n"); + else + printf("\n"); + n = (b[4] >> 3) & 0x7; + printf(" SPT=%d", n); + if (protect) { + switch (n) + { + case 0: + printf(" [protection type 1 supported]\n"); + break; + case 1: + printf(" [protection types 1 and 2 supported]\n"); + break; + case 2: + printf(" [protection type 2 supported]\n"); + break; + case 3: + printf(" [protection types 1 and 3 supported]\n"); + break; + case 4: + printf(" [protection type 3 supported]\n"); + break; + case 5: + printf(" [protection types 2 and 3 supported]\n"); + break; + case 6: + printf(" [see Supported block lengths and protection types " + "VPD page]\n"); + break; + case 7: + printf(" [protection types 1, 2 and 3 supported]\n"); + break; + default: + printf("\n"); + break; + } + } else + printf("\n"); + printf(" GRD_CHK=%d\n", !!(b[4] & 0x4)); + printf(" APP_CHK=%d\n", !!(b[4] & 0x2)); + printf(" REF_CHK=%d\n", !!(b[4] & 0x1)); + printf(" UASK_SUP=%d\n", !!(b[5] & 0x20)); + printf(" GROUP_SUP=%d\n", !!(b[5] & 0x10)); + printf(" PRIOR_SUP=%d\n", !!(b[5] & 0x8)); + printf(" HEADSUP=%d\n", !!(b[5] & 0x4)); + printf(" ORDSUP=%d\n", !!(b[5] & 0x2)); + printf(" SIMPSUP=%d\n", !!(b[5] & 0x1)); + printf(" WU_SUP=%d\n", !!(b[6] & 0x8)); + printf(" CRD_SUP=%d\n", !!(b[6] & 0x4)); + printf(" NV_SUP=%d\n", !!(b[6] & 0x2)); + printf(" V_SUP=%d\n", !!(b[6] & 0x1)); + printf(" NO_PI_CHK=%d\n", !!(b[7] & 0x10)); /* spc5r02 */ + printf(" P_I_I_SUP=%d\n", !!(b[7] & 0x10)); + printf(" LUICLR=%d\n", !!(b[7] & 0x1)); + printf(" LU_COLL_TYPE=%d\n", (b[8] >> 5) & 0x7); /* spc5r09 */ + printf(" R_SUP=%d\n", !!(b[8] & 0x10)); + printf(" RTD_SUP=%d\n", !!(b[8] & 0x8)); /* spc5r11 */ + printf(" HSSRELEF=%d\n", !!(b[8] & 0x2)); /* spc5r02 */ + printf(" CBCS=%d\n", !!(b[8] & 0x1)); /* obsolete in spc5r01 */ + printf(" Multi I_T nexus microcode download=%d\n", b[9] & 0xf); + printf(" Extended self-test completion minutes=%d\n", + sg_get_unaligned_be16(b + 10)); + printf(" POA_SUP=%d\n", !!(b[12] & 0x80)); /* spc4r32 */ + printf(" HRA_SUP=%d\n", !!(b[12] & 0x40)); /* spc4r32 */ + printf(" VSA_SUP=%d\n", !!(b[12] & 0x20)); /* spc4r32 */ + printf(" DMS_VALID=%d\n", !!(b[12] & 0x10)); /* 17-142r5 */ + printf(" Maximum supported sense data length=%d\n", + b[13]); /* spc4r34 */ + printf(" IBS=%d\n", !!(b[14] & 0x80)); /* spc5r09 */ + printf(" IAS=%d\n", !!(b[14] & 0x40)); /* spc5r09 */ + printf(" SAC=%d\n", !!(b[14] & 0x4)); /* spc5r09 */ + printf(" NRD1=%d\n", !!(b[14] & 0x2)); /* spc5r09 */ + printf(" NRD0=%d\n", !!(b[14] & 0x1)); /* spc5r09 */ + printf(" Maximum inquiry change logs=%u\n", + sg_get_unaligned_be16(b + 15)); /* spc5r17 */ + printf(" Maximum mode page change logs=%u\n", + sg_get_unaligned_be16(b + 17)); /* spc5r17 */ + printf(" DM_MD_4=%d\n", !!(b[19] & 0x80)); /* 17-142r5 */ + printf(" DM_MD_5=%d\n", !!(b[19] & 0x40)); /* 17-142r5 */ + printf(" DM_MD_6=%d\n", !!(b[19] & 0x20)); /* 17-142r5 */ + printf(" DM_MD_7=%d\n", !!(b[19] & 0x10)); /* 17-142r5 */ + printf(" DM_MD_D=%d\n", !!(b[19] & 0x8)); /* 17-142r5 */ + printf(" DM_MD_E=%d\n", !!(b[19] & 0x4)); /* 17-142r5 */ + printf(" DM_MD_F=%d\n", !!(b[19] & 0x2)); /* 17-142r5 */ + return; + } + printf(" 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)); + printf(" 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)); + printf(" 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)); + printf(" 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 */ + printf(" 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)); + printf(" Multi I_T nexus microcode download=%d\n", b[9] & 0xf); + printf(" Extended self-test completion minutes=%d\n", + sg_get_unaligned_be16(b + 10)); /* spc4r27 */ + printf(" 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)); /* spc4r32 + 17-142r5 */ + printf(" Maximum supported sense data length=%d\n", b[13]); /* spc4r34 */ + printf(" 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 */ + printf(" Maximum inquiry change logs=%u\n", + sg_get_unaligned_be16(b + 15)); /* spc5r17 */ + printf(" Maximum mode page change logs=%u\n", + sg_get_unaligned_be16(b + 17)); /* spc5r17 */ + printf(" 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)); /* 17-142r5 */ + printf(" 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 */ +static void +decode_softw_inf_id(uint8_t * buff, int len, int do_hex) +{ + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + len -= 4; + buff += 4; + for ( ; len > 5; len -= 6, buff += 6) { + printf(" IEEE Company_id: 0x%06x, vendor specific extension " + "id: 0x%06x\n", sg_get_unaligned_be24(buff), + sg_get_unaligned_be24(buff + 3)); + } +} + +/* VPD_ATA_INFO */ +static void +decode_ata_info_vpd(uint8_t * buff, int len, int do_long, int do_hex) +{ + char b[80]; + int num, is_be, cc; + const char * cp; + const char * ata_transp; + + if (len < 36) { + pr2serr("ATA information VPD page length too short=%d\n", len); + return; + } + if (do_hex && (2 != do_hex)) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + memcpy(b, buff + 8, 8); + b[8] = '\0'; + printf(" SAT Vendor identification: %s\n", b); + memcpy(b, buff + 16, 16); + b[16] = '\0'; + printf(" SAT Product identification: %s\n", b); + memcpy(b, buff + 32, 4); + b[4] = '\0'; + printf(" SAT Product revision level: %s\n", b); + if (len < 56) + return; + ata_transp = (0x34 == buff[36]) ? "SATA" : "PATA"; + if (do_long) { + printf(" Device signature [%s] (in hex):\n", ata_transp); + hex2stdout(buff + 36, 20, 0); + } else + printf(" Device signature indicates %s transport\n", ata_transp); + cc = buff[56]; /* 0xec for IDENTIFY DEVICE and 0xa1 for IDENTIFY + * PACKET DEVICE (obsolete) */ + printf(" Command code: 0x%x\n", cc); + if (len < 60) + return; + if (0xec == cc) + cp = ""; + else if (0xa1 == cc) + cp = "PACKET "; + else + cp = NULL; + is_be = sg_is_big_endian(); + if (cp) { + printf(" ATA command IDENTIFY %sDEVICE response summary:\n", cp); + num = sg_ata_get_chars((const unsigned short *)(buff + 60), 27, 20, + is_be, b); + b[num] = '\0'; + printf(" model: %s\n", b); + num = sg_ata_get_chars((const unsigned short *)(buff + 60), 10, 10, + is_be, b); + b[num] = '\0'; + printf(" serial number: %s\n", b); + num = sg_ata_get_chars((const unsigned short *)(buff + 60), 23, 4, + is_be, b); + b[num] = '\0'; + printf(" firmware revision: %s\n", b); + if (do_long) + printf(" ATA command IDENTIFY %sDEVICE response in hex:\n", cp); + } else if (do_long) + printf(" ATA command 0x%x got following response:\n", + (unsigned int)cc); + if (len < 572) + return; + if (2 == do_hex) + hex2stdout((buff + 60), 512, 0); + else if (do_long) + dWordHex((const unsigned short *)(buff + 60), 256, 0, is_be); +} + + +/* VPD_POWER_CONDITION 0x8a */ +static void +decode_power_condition(uint8_t * buff, int len, int do_hex) +{ + if (len < 18) { + pr2serr("Power condition VPD page length too short=%d\n", len); + return; + } + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + printf(" 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)); + printf(" Stopped condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 6)); + printf(" Standby_z condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 8)); + printf(" Standby_y condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 10)); + printf(" Idle_a condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 12)); + printf(" Idle_b condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 14)); + printf(" Idle_c condition recovery time (ms) %d\n", + sg_get_unaligned_be16(buff + 16)); +} + +static const char * constituent_type_arr[] = { + "Reserved", + "Virtual tape library", + "Virtual tape drive", + "Direct access block device", +}; + +/* VPD_DEVICE_CONSTITUENTS 0x8b */ +static void +decode_dev_constit_vpd(const uint8_t * buff, int len, struct opts_t * op) +{ + int k, j, res, bump, csd_len; + uint16_t constit_type; + const uint8_t * bp; + const char * dcp = "Device constituents VPD page"; + char b[64]; + + if ((1 == op->do_hex) || (op->do_hex > 2)) { + hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); + return; + } + if (len < 4) { + pr2serr("%s length too short=%d\n", dcp, len); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0, j = 0; k < len; k += bump, bp += bump, ++j) { + if (j > 0) + printf("\n"); + printf(" Constituent descriptor %d:\n", j + 1); + if ((k + 36) > len) { + pr2serr("%s, short descriptor length=36, left=%d\n", dcp, + (len - k)); + return; + } + constit_type = sg_get_unaligned_be16(bp + 0); + if (constit_type >= SG_ARRAY_SIZE(constituent_type_arr)) + printf(" Constituent type: unknown [0x%x]\n", constit_type); + else + printf(" Constituent type: %s [0x%x]\n", + constituent_type_arr[constit_type], constit_type); + printf(" Constituent device type: "); + if (0xff == bp[2]) + printf("Unknown [0xff]\n"); + else if (bp[2] >= 0x20) + printf("Reserved [0x%x]\n", bp[2]); + else + printf("%s [0x%x]\n", sg_get_pdt_str(0x1f & bp[2], sizeof(b), b), + bp[2]); + printf(" Vendor_identification: %.8s\n", bp + 4); + printf(" Product_identification: %.16s\n", bp + 12); + printf(" Product_revision_level: %.4s\n", bp + 28); + csd_len = sg_get_unaligned_be16(bp + 34); + bump = 36 + csd_len; + if ((k + bump) > len) { + pr2serr("%s, short descriptor length=%d, left=%d\n", dcp, bump, + (len - k)); + return; + } + if (csd_len > 0) { + int m, q, cs_bump; + uint8_t cs_type; + uint8_t cs_len; + const uint8_t * cs_bp; + + printf(" Constituent specific descriptors:\n"); + for (m = 0, q = 0, cs_bp = bp + 36; m < csd_len; + m += cs_bump, ++q, cs_bp += cs_bump) { + cs_type = cs_bp[0]; + cs_len = sg_get_unaligned_be16(cs_bp + 2); + cs_bump = cs_len + 4; + if (1 == cs_type) { /* VPD page */ + int off = cs_bp + 4 - buff; + + printf(" Constituent VPD page %d:\n", q + 1); + /* SPC-5 says these shall _not_ themselves be Device + * Constituent VPD pages. So no infinite recursion. */ + res = svpd_decode_t10(-1, op, 0, off); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(-1, op, off); + if (SG_LIB_CAT_OTHER == res) + svpd_unable_to_decode(-1, op, 0, off); + } + } else { + if (0xff == cs_type) + printf(" Vendor specific data (in hex):\n"); + else + printf(" Reserved [0x%x] specific data (in " + "hex):\n", cs_type); + hex2stdout(cs_bp + 4, cs_len, 0 /* plus ASCII */); + } + } /* end of Constituent specific descriptor loop */ + } + } /* end Constituent descriptor loop */ +} + +static const char * power_unit_arr[] = +{ + "Gigawatts", + "Megawatts", + "Kilowatts", + "Watts", + "Milliwatts", + "Microwatts", + "Unit reserved", + "Unit reserved", +}; + +/* VPD_POWER_CONSUMPTION */ +static void +decode_power_consumption_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump; + uint8_t * bp; + unsigned int value; + const char * pcp = "Power consumption VPD page"; + + if ((1 == do_hex) || (do_hex > 2)) { + hex2stdout(buff, len, (1 == do_hex) ? 1 : -1); + return; + } + if (len < 4) { + pr2serr("%s length too short=%d\n", pcp,len); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0; k < len; k += bump, bp += bump) { + bump = 4; + if ((k + bump) > len) { + pr2serr("%s, short descriptor length=%d, left=%d\n", pcp, bump, + (len - k)); + return; + } + if (do_hex > 1) + hex2stdout(bp, 4, 1); + else { + value = sg_get_unaligned_be16(bp + 2); + printf(" Power consumption identifier: 0x%x", bp[0]); + if (value >= 1000 && (bp[1] & 0x7) > 0) + printf(" Maximum power consumption: %d.%03d %s\n", + value / 1000, value % 1000, + power_unit_arr[(bp[1] & 0x7) - 1]); + else + printf(" Maximum power consumption: %u %s\n", + value, power_unit_arr[bp[1] & 0x7]); + } + } +} + +/* This is xcopy(LID4) related: "ROD" == Representation Of Data + * Used by VPD_3PARTY_COPY */ +static void +decode_rod_descriptor(const uint8_t * buff, int len) +{ + const uint8_t * bp = buff; + int k, bump; + + for (k = 0; k < len; k += bump, bp += bump) { + bump = sg_get_unaligned_be16(bp + 2) + 4; + switch (bp[0]) { + case 0: + /* Block ROD device type specific descriptor */ + printf(" Optimal block ROD length granularity: %d\n", + sg_get_unaligned_be16(bp + 6)); + printf(" Maximum Bytes in block ROD: %" PRIu64 "\n", + sg_get_unaligned_be64(bp + 8)); + printf(" Optimal Bytes in block ROD transfer: %" PRIu64 "\n", + sg_get_unaligned_be64(bp + 16)); + printf(" Optimal Bytes to token per segment: %" PRIu64 "\n", + sg_get_unaligned_be64(bp + 24)); + printf(" Optimal Bytes from token per segment:" + " %" PRIu64 "\n", sg_get_unaligned_be64(bp + 32)); + break; + case 1: + /* Stream ROD device type specific descriptor */ + printf(" Maximum Bytes in stream ROD: %" PRIu64 "\n", + sg_get_unaligned_be64(bp + 8)); + printf(" Optimal Bytes in stream ROD transfer:" + " %" PRIu64 "\n", sg_get_unaligned_be64(bp + 16)); + break; + case 3: + /* Copy manager ROD device type specific descriptor */ + printf(" Maximum Bytes in processor ROD: %" PRIu64 "\n", + sg_get_unaligned_be64(bp + 8)); + printf(" Optimal Bytes in processor ROD transfer:" + " %" PRIu64 "\n", sg_get_unaligned_be64(bp + 16)); + break; + default: + printf(" Unhandled descriptor (format %d, device type %d)\n", + bp[0] >> 5, bp[0] & 0x1F); + break; + } + } +} + +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 -> stream"}, + {0x9, "stream -> block"}, + {0xa, "block -> block"}, + {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, "tape -> tape"}, + {0x14, "register persistent reservation key"}, + {0x15, "third party persistent reservation source I_T nexus"}, + {0x16, "block -> block"}, + {0x17, "positioning -> tape"}, /* this and next added spc5r02 */ + {0x18, "tape -> 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 ""; +} + +/* VPD_3PARTY_COPY [3PC, third party copy] */ +static void +decode_3party_copy_vpd(uint8_t * buff, int len, int do_hex, int pdt, + int verbose) +{ + int j, k, m, bump, desc_type, desc_len, sa_len; + unsigned int u; + const uint8_t * bp; + const char * cp; + uint64_t ull; + char b[80]; + + if (len < 4) { + pr2serr("Third-party Copy VPD page length too short=%d\n", len); + return; + } + if (3 == do_hex) { + hex2stdout(buff, len, -1); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0; k < len; k += bump, bp += bump) { + desc_type = sg_get_unaligned_be16(bp); + desc_len = sg_get_unaligned_be16(bp + 2); + if (verbose) + printf("Descriptor type=%d [0x%x] , len %d\n", desc_type, + desc_type, desc_len); + bump = 4 + desc_len; + if ((k + bump) > len) { + pr2serr("Third-party Copy VPD page, short descriptor length=%d, " + "left=%d\n", bump, (len - k)); + return; + } + if (0 == desc_len) + continue; + if (2 == do_hex) + hex2stdout(bp + 4, desc_len, 1); + else if (do_hex > 2) + hex2stdout(bp, bump, 1); + else { + int csll; + + switch (desc_type) { + case 0x0000: /* Required if POPULATE TOKEN (or friend) used */ + printf(" Block Device ROD Token Limits:\n"); + u = sg_get_unaligned_be16(bp + 10); + printf(" Maximum range descriptors: "); + if (0 == u) + printf("0 [not reported]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(bp + 12); + printf(" Maximum inactivity timeout: "); + if (0 == u) + printf("0 [not reported]\n"); + else if (SG_LIB_UNBOUNDED_32BIT == u) + printf("-1 [no maximum given]\n"); + else + printf("%u seconds\n", u); + u = sg_get_unaligned_be32(bp + 16); + printf(" Default inactivity timeout: "); + if (0 == u) + printf("0 [not reported]\n"); + else + printf("%u seconds\n", u); + ull = sg_get_unaligned_be64(bp + 20); + printf(" Maximum token transfer size: "); + if (0 == ull) + printf("0 [not reported]\n"); + else + printf("%" PRIu64 "\n", ull); + ull = sg_get_unaligned_be64(bp + 28); + printf(" Optimal transfer count: "); + if (0 == ull) + printf("0 [not reported]\n"); + else + printf("%" PRIu64 "\n", ull); + break; + case 0x0001: /* Mandatory (SPC-4) */ + printf(" Supported commands:\n"); + 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) { + sa_len = bp[6 + j]; + for (m = 0; (m < sa_len) && ((j + m) < csll); ++m) { + sg_get_opcode_sa_name(bp[5 + j], bp[7 + j + m], + pdt, sizeof(b), b); + printf(" %s\n", b); + } + if (0 == sa_len) { + sg_get_opcode_name(bp[5 + j], pdt, sizeof(b), b); + printf(" %s\n", b); + } else if (m < sa_len) + pr2serr("Supported service actions list length (%d) " + "is too large\n", sa_len); + j += m + 2; + } + break; + case 0x0004: + printf(" Parameter data:\n"); + printf(" Maximum CSCD descriptor count: %d\n", + sg_get_unaligned_be16(bp + 8)); + printf(" Maximum segment descriptor count: %d\n", + sg_get_unaligned_be16(bp + 10)); + u = sg_get_unaligned_be32(bp + 12); + printf(" Maximum descriptor list length: %u\n", u); + u = sg_get_unaligned_be32(bp + 16); + printf(" Maximum inline data length: %u\n", u); + break; + case 0x0008: + printf(" Supported descriptors:\n"); + for (j = 0; j < bp[4]; j++) { + cp = get_tpc_desc_name(bp[5 + j]); + if (strlen(cp) > 0) + printf(" %s [0x%x]\n", cp, bp[5 + j]); + else + printf(" 0x%x\n", bp[5 + j]); + } + break; + case 0x000C: + printf(" Supported CSCD IDs (above 0x7ff):\n"); + for (j = 0; j < sg_get_unaligned_be16(bp + 4); j += 2) { + u = sg_get_unaligned_be16(bp + 6 + j); + cp = get_cscd_desc_id_name(u); + if (strlen(cp) > 0) + printf(" %s [0x%04x]\n", cp, u); + else + printf(" 0x%04x\n", u); + } + break; + case 0x0106: + printf(" ROD token features:\n"); + printf(" Remote tokens: %d\n", bp[4] & 0x0f); + u = sg_get_unaligned_be32(bp + 16); + printf(" Minimum token lifetime: %u seconds\n", u); + u = sg_get_unaligned_be32(bp + 20); + printf(" Maximum token lifetime: %u seconds\n", u); + u = sg_get_unaligned_be32(bp + 24); + printf(" Maximum token inactivity timeout: %u\n", u); + decode_rod_descriptor(bp + 48, + sg_get_unaligned_be16(bp + 46)); + break; + case 0x0108: + printf(" Supported ROD token and ROD types:\n"); + for (j = 0; j < sg_get_unaligned_be16(bp + 6); j+= 64) { + u = sg_get_unaligned_be32(bp + 8 + j); + cp = get_tpc_rod_name(u); + if (strlen(cp) > 0) + printf(" ROD type: %s [0x%x]\n", cp, u); + else + printf(" ROD type: 0x%x\n", u); + printf(" Internal: %s\n", + (bp[8 + j + 4] & 0x80) ? "yes" : "no"); + printf(" Token in: %s\n", + (bp[8 + j + 4] & 0x02) ? "yes" : "no"); + printf(" Token out: %s\n", + (bp[8 + j + 4] & 0x01) ? "yes" : "no"); + printf(" Preference: %d\n", + sg_get_unaligned_be16(bp + 8 + j + 6)); + } + break; + case 0x8001: /* Mandatory (SPC-4) */ + printf(" General copy operations:\n"); + u = sg_get_unaligned_be32(bp + 4); + printf(" Total concurrent copies: %u\n", u); + u = sg_get_unaligned_be32(bp + 8); + printf(" Maximum identified concurrent copies: %u\n", u); + u = sg_get_unaligned_be32(bp + 12); + printf(" Maximum segment length: %u\n", u); + ull = (1 << bp[16]); /* field is power of 2 */ + printf(" Data segment granularity: %" PRIu64 "\n", ull); + ull = (1 << bp[17]); + printf(" Inline data granularity: %" PRIu64 "\n", ull); + break; + case 0x9101: + printf(" Stream copy operations:\n"); + u = sg_get_unaligned_be32(bp + 4); + printf(" Maximum stream device transfer size: %u\n", u); + break; + case 0xC001: + printf(" Held data:\n"); + u = sg_get_unaligned_be32(bp + 4); + printf(" Held data limit: %u\n", u); + ull = (1 << bp[8]); + printf(" Held data granularity: %" PRIu64 "\n", ull); + break; + default: + pr2serr("Unexpected type=%d\n", desc_type); + hex2stderr(bp, bump, 1); + break; + } + } + } +} + +/* VPD_PROTO_LU */ +static void +decode_proto_lu_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump, rel_port, desc_len, proto; + uint8_t * bp; + + if ((1 == do_hex) || (do_hex > 2)) { + hex2stdout(buff, len, (1 == do_hex) ? 1 : -1); + return; + } + if (len < 4) { + pr2serr("Protocol-specific logical unit information 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); + printf(" Relative port=%d\n", rel_port); + proto = bp[2] & 0xf; + 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)); + return; + } + if (0 == desc_len) + continue; + if (2 == do_hex) + hex2stdout(bp + 8, desc_len, 1); + else if (do_hex > 2) + hex2stdout(bp, bump, 1); + else { + switch (proto) { + case TPROTO_SAS: + printf(" Protocol identifier: SAS\n"); + printf(" TLR control supported: %d\n", !!(bp[8] & 0x1)); + break; + default: + pr2serr("Unexpected proto=%d\n", proto); + hex2stderr(bp, bump, 1); + break; + } + } + } +} + +/* VPD_PROTO_PORT */ +static void +decode_proto_port_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, j, bump, rel_port, desc_len, proto; + uint8_t * bp; + uint8_t * pidp; + + if ((1 == do_hex) || (do_hex > 2)) { + hex2stdout(buff, len, (1 == do_hex) ? 1 : -1); + return; + } + if (len < 4) { + pr2serr("Protocol-specific port information 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); + printf(" Relative port=%d\n", rel_port); + proto = bp[2] & 0xf; + desc_len = sg_get_unaligned_be16(bp + 6); + bump = 8 + desc_len; + if ((k + bump) > len) { + pr2serr("Protocol-specific port VPD page, short descriptor " + "length=%d, left=%d\n", bump, (len - k)); + return; + } + if (0 == desc_len) + continue; + if (2 == do_hex) + hex2stdout(bp + 8, desc_len, 1); + else if (do_hex > 2) + hex2stdout(bp, bump, 1); + else { + switch (proto) { + case TPROTO_SAS: /* page added in spl3r02 */ + printf(" power disable supported (pwr_d_s)=%d\n", + !!(bp[3] & 0x1)); /* added spl3r03 */ + pidp = bp + 8; + for (j = 0; j < desc_len; j += 4, pidp += 4) + printf(" phy id=%d, SSP persistent capable=%d\n", + pidp[1], (0x1 & pidp[2])); + break; + default: + pr2serr("Unexpected proto=%d\n", proto); + hex2stderr(bp, bump, 1); + break; + } + } + } +} + +/* VPD_SCSI_FEATURE_SETS [0x92] (sfs) */ +static void +decode_feature_sets_vpd(uint8_t * buff, int len, const struct opts_t * op) +{ + int k, bump; + uint16_t sf_code; + bool found; + uint8_t * bp; + char b[64]; + + 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) { + 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 { + printf(" %s", sg_get_sfs_str(sf_code, -2, sizeof(b), b, + &found, op->verbose)); + if (op->verbose == 1) + printf(" [0x%x]\n", (unsigned int)sf_code); + else if (op->verbose > 1) + printf(" [0x%x] found=%s\n", (unsigned int)sf_code, + found ? "true" : "false"); + else + printf("\n"); + } + } +} + + +/* VPD_BLOCK_LIMITS sbc */ +/* VPD_SA_DEV_CAP ssc */ +/* VPD_OSD_INFO osd */ +static void +decode_b0_vpd(uint8_t * buff, int len, int do_hex, int pdt) +{ + unsigned int u; + uint64_t ull; + bool ugavalid; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + if (len < 16) { + pr2serr("Block limits VPD page length too short=%d\n", len); + return; + } + printf(" Write same non-zero (WSNZ): %d\n", !!(buff[4] & 0x1)); + u = buff[5]; + printf(" Maximum compare and write length: "); + if (0 == u) + printf("0 blocks [Command not implemented]\n"); + else + printf("%u blocks\n", buff[5]); + u = sg_get_unaligned_be16(buff + 6); + printf(" Optimal transfer length granularity: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 8); + printf(" Maximum transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 12); + printf(" Optimal transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + if (len > 19) { /* added in sbc3r09 */ + u = sg_get_unaligned_be32(buff + 16); + printf(" Maximum prefetch transfer length: "); + if (0 == u) + printf("0 blocks [ignored]\n"); + else + printf("%u blocks\n", u); + } + if (len > 27) { /* added in sbc3r18 */ + u = sg_get_unaligned_be32(buff + 20); + printf(" Maximum unmap LBA count: "); + if (0 == u) + printf("0 [Unmap command not implemented]\n"); + else if (SG_LIB_UNBOUNDED_32BIT == u) + printf("-1 [unbounded]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(buff + 24); + printf(" Maximum unmap block descriptor count: "); + if (0 == u) + printf("0 [Unmap command not implemented]\n"); + else if (SG_LIB_UNBOUNDED_32BIT == u) + printf("-1 [unbounded]\n"); + else + printf("%u\n", u); + } + if (len > 35) { /* added in sbc3r19 */ + u = sg_get_unaligned_be32(buff + 28); + printf(" Optimal unmap granularity: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + + ugavalid = !!(buff[32] & 0x80); + printf(" Unmap granularity alignment valid: %s\n", + ugavalid ? "true" : "false"); + u = 0x7fffffff & sg_get_unaligned_be32(buff + 32); + printf(" Unmap granularity alignment: %u%s\n", u, + ugavalid ? "" : " [invalid]"); + } + if (len > 43) { /* added in sbc3r26 */ + ull = sg_get_unaligned_be64(buff + 36); + printf(" Maximum write same length: "); + if (0 == ull) + printf("0 blocks [not reported]\n"); + else + printf("0x%" PRIx64 " blocks\n", ull); + } + if (len > 44) { /* added in sbc4r02 */ + u = sg_get_unaligned_be32(buff + 44); + printf(" Maximum atomic transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 48); + printf(" Atomic alignment: "); + if (0 == u) + printf("0 [unaligned atomic writes permitted]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(buff + 52); + printf(" Atomic transfer length granularity: "); + if (0 == u) + printf("0 [no granularity requirement\n"); + else + printf("%u\n", u); + } + if (len > 56) { + u = sg_get_unaligned_be32(buff + 56); + printf(" Maximum atomic transfer length with atomic " + "boundary: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be32(buff + 60); + printf(" Maximum atomic boundary size: "); + if (0 == u) + printf("0 blocks [can only write atomic 1 block]\n"); + else + printf("%u blocks\n", u); + } + break; + case PDT_TAPE: case PDT_MCHANGER: + printf(" WORM=%d\n", !!(buff[4] & 0x1)); + break; + case PDT_OSD: + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(buff, len, 0); + break; + } +} + +static const char * product_type_arr[] = +{ + "Not specified", + "CFast", + "CompactFlash", + "MemoryStick", + "MultiMediaCard", + "Secure Digital Card (SD)", + "XQD", + "Universal Flash Storage Card (UFS)", +}; + +/* VPD_BLOCK_DEV_CHARS sbc */ +/* 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, int do_hex, int pdt) +{ + unsigned int u, k; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + if (len < 64) { + pr2serr("Block device characteristics VPD page length too " + "short=%d\n", len); + return; + } + u = sg_get_unaligned_be16(buff + 4); + if (0 == u) + printf(" Medium rotation rate is not reported\n"); + else if (1 == u) + printf(" Non-rotating medium (e.g. solid state)\n"); + else if ((u < 0x401) || (0xffff == u)) + printf(" Reserved [0x%x]\n", u); + else + printf(" Nominal rotation rate: %u rpm\n", u); + u = buff[6]; + k = SG_ARRAY_SIZE(product_type_arr); + printf(" Product type: "); + if (u < k) + printf("%s\n", product_type_arr[u]); + else if (u < 0xf0) + printf("Reserved [0x%x]\n", u); + else + printf("Vendor specific [0x%x]\n", u); + printf(" WABEREQ=%d\n", (buff[7] >> 6) & 0x3); + printf(" WACEREQ=%d\n", (buff[7] >> 4) & 0x3); + u = buff[7] & 0xf; + printf(" Nominal form factor"); + switch (u) { + case 0: + printf(" not reported\n"); + break; + case 1: + printf(": 5.25 inch\n"); + break; + case 2: + printf(": 3.5 inch\n"); + break; + case 3: + printf(": 2.5 inch\n"); + break; + case 4: + printf(": 1.8 inch\n"); + break; + case 5: + printf(": less then 1.8 inch\n"); + break; + default: + printf(": reserved\n"); + break; + } + printf(" ZONED=%d\n", (buff[8] >> 4) & 0x3); /* sbc4r04 */ + printf(" RBWZ=%d\n", !!(buff[8] & 0x8)); /* sbc4r12 */ + printf(" BOCS=%d\n", !!(buff[8] & 0x4)); /* sbc4r07 */ + printf(" FUAB=%d\n", !!(buff[8] & 0x2)); + printf(" VBULS=%d\n", !!(buff[8] & 0x1)); + printf(" DEPOPULATION_TIME=%u (seconds)\n", + sg_get_unaligned_be32(buff + 12)); /* added sbc4r14 */ + break; + case PDT_TAPE: case PDT_MCHANGER: case PDT_ADC: + printf(" Manufacturer-assigned serial number: %.*s\n", + len - 4, buff + 4); + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(buff, len, 0); + break; + } +} + +static const char * prov_type_arr[8] = { + "not known or fully provisioned", + "resource provisioned", + "thin provisioned", + "reserved [0x3]", + "reserved [0x4]", + "reserved [0x5]", + "reserved [0x6]", + "reserved [0x7]", +}; + +/* VPD_LB_PROVISIONING 0xb2 */ +static int +decode_block_lb_prov_vpd(uint8_t * b, int len, const struct opts_t * op) +{ + int dp, pt; + unsigned int u; + + if (len < 4) { + pr2serr("Logical block provisioning page too short=%d\n", len); + return SG_LIB_CAT_MALFORMED; + } + pt = b[6] & 0x7; + printf(" Unmap command supported (LBPU): %d\n", !!(0x80 & b[5])); + printf(" Write same (16) with unmap bit supported (LBPWS): %d\n", + !!(0x40 & b[5])); + printf(" Write same (10) with unmap bit supported (LBPWS10): %d\n", + !!(0x20 & b[5])); + printf(" Logical block provisioning read zeros (LBPRZ): %d\n", + (0x7 & (b[5] >> 2))); /* expanded from 1 to 3 bits in sbc4r07 */ + printf(" Anchored LBAs supported (ANC_SUP): %d\n", !!(0x2 & b[5])); + dp = !!(b[5] & 0x1); + u = b[4]; + printf(" Threshold exponent: "); + if (0 == u) + printf("0 [threshold sets not supported]\n"); + else + printf("%u\n", u); + printf(" Descriptor present (DP): %d\n", dp); + printf(" Minimum percentage: "); + u = 0x1f & (b[6] >> 3); + if (0 == u) + printf("0 [not reported]\n"); + else + printf("%d\n", u); + printf(" Provisioning type: %d (%s)\n", pt, prov_type_arr[pt]); + printf(" Threshold percentage: "); + if (0 == b[7]) + printf("0 [percentages not supported]\n"); + else + printf("%u\n", b[7]); + if (dp && (len > 11)) { + int i_len; + const uint8_t * bp; + char bb[1024]; + + bp = b + 8; + i_len = bp[3]; + if (0 == i_len) { + pr2serr("LB provisioning page provisioning group descriptor too " + "short=%d\n", i_len); + return 0; + } + printf(" Provisioning group descriptor:\n"); + sg_get_designation_descriptor_str(" ", bp, i_len + 4, 0, + op->do_long, sizeof(bb), bb); + printf("%s", bb); + } + return 0; +} + +/* VPD_SUP_BLOCK_LENS 0xb4 (added sbc4r01) */ +static void +decode_sup_block_lens_vpd(uint8_t * buff, int len) +{ + int k; + unsigned int u; + uint8_t * bp; + + if (len < 4) { + pr2serr("Supported block lengths and protection types VPD page " + "length too short=%d\n", len); + return; + } + len -= 4; + bp = buff + 4; + for (k = 0; k < len; k += 8, bp += 8) { + u = sg_get_unaligned_be32(bp); + printf(" Logical block length: %u\n", u); + printf(" P_I_I_SUP: %d\n", !!(bp[4] & 0x40)); + printf(" NO_PI_CHK: %d\n", !!(bp[4] & 0x8)); /* sbc4r05 */ + printf(" GRD_CHK: %d\n", !!(bp[4] & 0x4)); + printf(" APP_CHK: %d\n", !!(bp[4] & 0x2)); + printf(" REF_CHK: %d\n", !!(bp[4] & 0x1)); + printf(" T3PS: %d\n", !!(bp[5] & 0x8)); + printf(" T2PS: %d\n", !!(bp[5] & 0x4)); + printf(" T1PS: %d\n", !!(bp[5] & 0x2)); + printf(" T0PS: %d\n", !!(bp[5] & 0x1)); + } +} + +/* VPD_BLOCK_DEV_C_EXTENS 0xb5 (added sbc4r02) */ +static void +decode_block_dev_char_ext_vpd(uint8_t * b, int len) +{ + if (len < 16) { + pr2serr("Block device characteristics extension VPD page " + "length too short=%d\n", len); + return; + } + printf(" Utilization type: "); + switch (b[5]) { + case 1: + printf("Combined writes and reads"); + break; + case 2: + printf("Writes only"); + break; + case 3: + printf("Separate writes and reads"); + break; + default: + printf("Reserved"); + break; + } + printf(" [0x%x]\n", b[5]); + printf(" Utilization units: "); + switch (b[6]) { + case 2: + printf("megabytes"); + break; + case 3: + printf("gigabytes"); + break; + case 4: + printf("terabytes"); + break; + case 5: + printf("petabytes"); + break; + case 6: + printf("exabytes"); + break; + default: + printf("Reserved"); + break; + } + printf(" [0x%x]\n", b[6]); + printf(" Utilization interval: "); + switch (b[7]) { + case 0xa: + printf("per day"); + break; + case 0xe: + printf("per year"); + break; + default: + printf("Reserved"); + break; + } + printf(" [0x%x]\n", b[7]); + printf(" Utilization B: %u\n", sg_get_unaligned_be32(b + 8)); + printf(" Utilization A: %u\n", sg_get_unaligned_be32(b + 12)); +} + +/* VPD_LB_PROTECTION 0xb5 (SSC) [added in ssc5r02a] */ +static void +decode_lb_protection_vpd(uint8_t * buff, int len, int do_hex) +{ + int k, bump; + uint8_t * bp; + + if ((1 == do_hex) || (do_hex > 2)) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + if (len < 8) { + pr2serr("Logical block protection VPD page length too short=%d\n", + len); + return; + } + len -= 8; + bp = buff + 8; + for (k = 0; k < len; k += bump, bp += bump) { + bump = 1 + bp[0]; + printf(" 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])); + if ((k + bump) > len) { + pr2serr("Logical block protection VPD page, short " + "descriptor length=%d, left=%d\n", bump, (len - k)); + return; + } + } +} + +/* VPD_TA_SUPPORTED 0xb2 */ +static int +decode_tapealert_supported_vpd(uint8_t * b, int len) +{ + int k, mod, div; + + if (len < 12) { + pr2serr("TapeAlert supported flags length too short=%d\n", len); + return SG_LIB_CAT_MALFORMED; + } + 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, !! (b[4 + div] & 0x80)); + } else + printf(" %02Xh: %d", k, !! (b[4 + div] & (1 << (7 - mod)))); + } + printf("\n"); + return 0; +} + +/* VPD_LB_PROVISIONING sbc */ +/* VPD_TA_SUPPORTED ssc */ +static void +decode_b2_vpd(uint8_t * buff, int len, int pdt, const struct opts_t * op) +{ + if (op->do_hex) { + hex2stdout(buff, len, (1 == op->do_hex) ? 0 : -1); + return; + } + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + decode_block_lb_prov_vpd(buff, len, op); + break; + case PDT_TAPE: case PDT_MCHANGER: + decode_tapealert_supported_vpd(buff, len); + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(buff, len, 0); + break; + } +} + +/* VPD_REFERRALS sbc */ +/* VPD_AUTOMATION_DEV_SN ssc */ +static void +decode_b3_vpd(uint8_t * b, int len, int do_hex, int pdt) +{ + char obuff[DEF_ALLOC_LEN]; + unsigned int u; + + 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: + if (len < 16) { + pr2serr("Referrals VPD page length too short=%d\n", len); + break; + } + u = sg_get_unaligned_be32(b + 8); + printf(" User data segment size: "); + if (0 == u) + printf("0 [per sense descriptor]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(b + 12); + printf(" User data segment multiplier: %u\n", u); + break; + case PDT_TAPE: case PDT_MCHANGER: + memset(obuff, 0, sizeof(obuff)); + len -= 4; + if (len >= (int)sizeof(obuff)) + len = sizeof(obuff) - 1; + memcpy(obuff, b + 4, len); + printf(" Automation device serial number: %s\n", obuff); + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(b, len, 0); + break; + } +} + +/* VPD_SUP_BLOCK_LENS sbc */ +/* VPD_DTDE_ADDRESS ssc */ +static void +decode_b4_vpd(uint8_t * b, int len, int do_hex, int pdt) +{ + int k; + + 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: + decode_sup_block_lens_vpd(b, len); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf(" Data transfer device element address: 0x"); + for (k = 4; k < len; ++k) + printf("%02x", (unsigned int)b[k]); + printf("\n"); + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(b, len, 0); + break; + } +} + +/* VPD_BLOCK_DEV_C_EXTENS sbc */ +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: + decode_block_dev_char_ext_vpd(b, len); + break; + case PDT_TAPE: case PDT_MCHANGER: + decode_lb_protection_vpd(b, len, do_hex); + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(b, len, 0); + break; + } +} + +/* VPD_ZBC_DEV_CHARS 0xb6 sbc or zbc */ +static void +decode_zbdc_vpd(uint8_t * b, int len, int do_hex) +{ + uint32_t u; + + if (do_hex) { + hex2stdout(b, len, (1 == do_hex) ? 0 : -1); + return; + } + if (len < 64) { + pr2serr("Zoned block device characteristics VPD page length too " + "short=%d\n", len); + return; + } + printf(" URSWRZ type: %d\n", !!(b[4] & 0x1)); + u = sg_get_unaligned_be32(b + 8); + printf(" Optimal number of open sequential write preferred zones: "); + if (SG_LIB_UNBOUNDED_32BIT == u) + printf("not reported\n"); + else + printf("%" PRIu32 "\n", u); + u = sg_get_unaligned_be32(b + 12); + printf(" Optimal number of non-sequentially written sequential write " + "preferred zones: "); + if (SG_LIB_UNBOUNDED_32BIT == u) + printf("not reported\n"); + else + printf("%" PRIu32 "\n", u); + u = sg_get_unaligned_be32(b + 16); + printf(" Maximum number of open sequential write required zones: "); + if (SG_LIB_UNBOUNDED_32BIT == u) + printf("no limit\n"); + else + printf("%" PRIu32 "\n", u); +} + +/* VPD_BLOCK_LIMITS_EXT [0xb7] sbc */ +static void +decode_b7_vpd(uint8_t * buff, int len, int do_hex, int pdt) +{ + unsigned int u; + + if (do_hex) { + hex2stdout(buff, len, (1 == do_hex) ? 0 : -1); + return; + } + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + if (len < 12) { + pr2serr("Block limits extension VPD page length too short=%d\n", + len); + return; + } + u = sg_get_unaligned_be16(buff + 6); + printf(" Maximum number of streams: "); + if (0 == u) + printf("0 [Stream control not supported]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be16(buff + 8); + printf(" Optimal stream write size: %u blocks\n", u); + u = sg_get_unaligned_be32(buff + 10); + printf(" Stream granularity size: %u\n", u); + if (len > 27) { + u = sg_get_unaligned_be32(buff + 16); + printf(" Maximum scattered LBA range transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + u = sg_get_unaligned_be16(buff + 22); + printf(" Maximum scattered LBA range descriptor count: "); + if (0 == u) + printf("0 [not reported]\n"); + else + printf("%u\n", u); + u = sg_get_unaligned_be32(buff + 24); + printf(" Maximum scattered transfer length: "); + if (0 == u) + printf("0 blocks [not reported]\n"); + else + printf("%u blocks\n", u); + } + break; + default: + pr2serr(" Unable to decode pdt=0x%x, in hex:\n", pdt); + hex2stderr(buff, len, 0); + break; + } +} + +/* Returns 0 if successful */ +static int +svpd_unable_to_decode(int sg_fd, struct opts_t * op, int subvalue, int off) +{ + int len, res; + uint8_t * rp; + + rp = rsp_buff + off; + if ((! op->do_hex) && (! op->do_raw)) + printf("Only hex output supported\n"); + if ((!op->do_raw) && (op->do_hex < 2)) { + if (subvalue) + printf("VPD page code=0x%.2x, subvalue=0x%.2x:\n", op->vpd_pn, + subvalue); + else if (op->vpd_pn >= 0) + printf("VPD page code=0x%.2x:\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 ((2 == op->do_hex) || (3 == op->do_hex)) + hex2stdout(rp, len, -1); + else if (VPD_ASCII_OP_DEF == op->vpd_pn) + hex2stdout(rp, len, 0); + else if (1 == op->do_hex) + hex2stdout(rp, len, (op->do_long ? 0 : 1)); + else + hex2stdout(rp, len, 0); + } + } else if (! op->do_quiet) { + 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; +} + +/* 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, int subvalue, int off) +{ + bool allow_name, long_notquiet, qt; + bool vpd_supported = false; + int len, pdt, num, k, resid, alloc_len, pn, vb; + int res = 0; + const struct svpd_values_name_t * vnp; + uint8_t * rp; + char obuff[DEF_ALLOC_LEN]; + char b[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)) + allow_name = false; + else + allow_name = true; + rp = rsp_buff + off; + pn = (-1 == sg_fd) ? rp[1] : op->vpd_pn; + if (sg_fd != -1 && !op->do_force && + 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); + } + } + switch(pn) { + case VPD_NOPE_WANT_STD_INQ: /* -2 (want standard inquiry response) */ + if (sg_fd >= 0) { + 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)) + printf("Standard Inquiry reponse:\n"); + hex2stdout(rp, alloc_len, (1 == op->do_hex) ? 0 : -1); + } else + std_inq_decode(rp, alloc_len, vb); + return 0; + } + break; + case VPD_SUPPORTED_VPDS: /* 0x0 */ + if (allow_name) + printf("Supported VPD pages VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, (1 == op->do_hex) ? 0 : -1); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + num = rp[3]; + if (num > (len - 4)) + num = (len - 4); + for (k = 0; k < num; ++k) { + pn = rp[4 + k]; + vnp = sdp_get_vpd_detail(pn, -1, pdt); + if (vnp) { + if (op->do_long) + printf(" 0x%02x %s [%s]\n", pn, vnp->name, + vnp->acron); + else + printf(" %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) + printf(" 0x%02x %s [%s]\n", pn, vnp->name, + vnp->acron); + else + printf(" %s [%s]\n", vnp->name, vnp->acron); + } else + printf(" 0x%x\n", pn); + } else + printf(" 0x%x\n", pn); + } + } + return 0; + } + break; + case VPD_UNIT_SERIAL_NUM: /* 0x80 */ + if (allow_name) + printf("Unit serial number VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, (1 == op->do_hex) ? 0 : -1); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + memset(obuff, 0, sizeof(obuff)); + len -= 4; + if (len >= (int)sizeof(obuff)) + len = sizeof(obuff) - 1; + memcpy(obuff, rp + 4, len); + printf(" Unit serial number: %s\n", obuff); + } + return 0; + } + break; + case VPD_DEVICE_ID: /* 0x83 */ + if (allow_name) + printf("Device Identification VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, (1 == op->do_hex) ? 0 : -1); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_id_vpd(rp, len, subvalue, op); + } + return 0; + } + break; + case VPD_SOFTW_INF_ID: /* 0x84 */ + if (allow_name) + printf("Software interface identification VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_softw_inf_id(rp, len, op->do_hex); + } + return 0; + } + break; + case VPD_MAN_NET_ADDR: /* 0x85 */ + if (allow_name) + printf("Management network addresses VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else + decode_net_man_vpd(rp, len, op->do_hex); + return 0; + } + break; + case VPD_EXT_INQ: /* 0x86 */ + if (allow_name) + printf("extended INQUIRY data VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + bool protect = false; + struct sg_simple_inquiry_resp sir; + + if ((sg_fd >= 0) && long_notquiet) { + 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); + } else + protect = !!(sir.byte_5 & 0x1); /* SPC-3 and later */ + } + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_x_inq_vpd(rp, len, op->do_hex, long_notquiet, protect); + } + return 0; + } + break; + case VPD_MODE_PG_POLICY: /* 0x87 */ + if (allow_name) + printf("Mode page policy VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_mode_policy_vpd(rp, len, op->do_hex); + } + return 0; + } + break; + case VPD_SCSI_PORTS: /* 0x88 */ + if (allow_name) + printf("SCSI Ports VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_scsi_ports_vpd(rp, len, op); + } + return 0; + } + break; + case VPD_ATA_INFO: /* 0x89 */ + if (allow_name) + printf("ATA information VPD page:\n"); + 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 ((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 { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_ata_info_vpd(rp, len, long_notquiet, op->do_hex); + } + return 0; + } + break; + case VPD_POWER_CONDITION: /* 0x8a */ + if (allow_name) + printf("Power condition VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_power_condition(rp, len, op->do_hex); + } + return 0; + } + break; + case VPD_DEVICE_CONSTITUENTS: /* 0x8b */ + if (allow_name) + printf("Device constituents VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else + decode_dev_constit_vpd(rp, len, op); + return 0; + } + break; + case VPD_POWER_CONSUMPTION: /* 0x8d */ + if (allow_name) + printf("Power consumption VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_power_consumption_vpd(rp, len, op->do_hex); + } + return 0; + } + break; + case VPD_3PARTY_COPY: /* 0x8f */ + if (allow_name) + printf("Third party copy VPD page:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else if (1 == op->do_hex) + hex2stdout(rp, len, 0); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_3party_copy_vpd(rp, len, op->do_hex, pdt, vb); + } + return 0; + } + break; + case VPD_PROTO_LU: /* 0x90 */ + if (allow_name) + printf("Protocol-specific logical unit information:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rsp_buff[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_proto_lu_vpd(rp, len, op->do_hex); + } + return 0; + } + break; + case VPD_PROTO_PORT: /* 0x91 */ + if (allow_name) + printf("Protocol-specific port information:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_proto_port_vpd(rp, len, op->do_hex); + } + return 0; + } + break; + case VPD_SCSI_FEATURE_SETS: /* 0x92 */ + if (allow_name) + printf("SCSI Feature sets:\n"); + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_feature_sets_vpd(rp, len, op); + } + return 0; + } + break; + case 0xb0: /* depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Block limits VPD page (SBC):\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("Sequential-access device capabilities VPD page " + "(SSC):\n"); + break; + case PDT_OSD: + printf("OSD information VPD page (OSD):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b0_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb0\n"); + break; + case 0xb1: /* depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Block device characteristics VPD page (SBC):\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("Manufactured-assigned serial number VPD page " + "(SSC):\n"); + break; + case PDT_OSD: + printf("Security token VPD page (OSD):\n"); + break; + case PDT_ADC: + printf("Manufactured-assigned serial number VPD page " + "(ADC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b1_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb1\n"); + break; + case 0xb2: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Logical block provisioning VPD page (SBC):\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("TapeAlert supported flags VPD page (SSC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b2_vpd(rp, len, pdt, op); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb2\n"); + break; + case 0xb3: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Referrals VPD page (SBC):\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("Automation device serial number VPD page " + "(SSC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b3_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb3\n"); + break; + case 0xb4: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Supported block lengths and protection types " + "VPD page (SBC):\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("Data transfer device element address (SSC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b4_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb4\n"); + break; + case 0xb5: /* VPD page depends on pdt */ + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Block device characteristics extension VPD page " + "(SBC):\n"); + break; + case PDT_TAPE: case PDT_MCHANGER: + printf("Logical block protection VPD page (SSC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b5_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb5\n"); + 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) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Zoned block device characteristics VPD page " + "(SBC, ZBC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_zbdc_vpd(rp, len, op->do_hex); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb5\n"); + break; + case 0xb7: + res = vpd_fetch_page(sg_fd, rp, pn, op->maxlen, qt, vb, &len); + if (0 == res) { + pdt = rp[0] & 0x1f; + if (allow_name) { + switch (pdt) { + case PDT_DISK: case PDT_WO: case PDT_OPTICAL: case PDT_ZBC: + printf("Block limits extension VPD page (SBC):\n"); + break; + default: + printf("VPD page=0x%x, pdt=0x%x:\n", pn, pdt); + break; + } + } + if (op->do_raw) + dStrRaw(rp, len); + else { + pdt = rp[0] & 0x1f; + if (vb || long_notquiet) + printf(" [PQual=%d Peripheral device type: %s]\n", + (rp[0] & 0xe0) >> 5, + sg_get_pdt_str(pdt, sizeof(b), b)); + decode_b7_vpd(rp, len, op->do_hex, pdt); + } + return 0; + } else if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 3)) + printf("VPD page=0xb7\n"); + break; + default: + return SG_LIB_CAT_OTHER; + } + return res; +} + +static int +svpd_decode_all(int sg_fd, struct opts_t * op) +{ + int k, res, rlen, n, pn; + int max_pn = 255; + int any_err = 0; + 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) + printf("\n"); + if (op->do_long) + printf("[0x%x] ", pn); + + res = svpd_decode_t10(sg_fd, op, 0, 0); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(sg_fd, op, 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; + 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 (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) + printf("[0x%x] ", pn); + + res = svpd_decode_t10(-1, op, 0, off); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(-1, op, off); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(-1, op, 0, off); + } + } + } + return res; +} + + +int +main(int argc, char * argv[]) +{ + int c, res, matches; + int sg_fd = -1; + int inhex_len = 0; + int ret = 0; + int subvalue = 0; + const char * cp; + struct opts_t * op; + const struct svpd_values_name_t * vnp; + struct opts_t opts; + + op = &opts; + memset(&opts, 0, sizeof(opts)); + 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, "aefhHiI:lm:M:p:qrvV", 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 '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 '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; + } + 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; + break; + case 'q': + op->do_quiet = true; + 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; + } + + 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(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(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) + printf("No matches found for VPD page number 0x%x\n", + op->vpd_pn); + } else { /* enumerate standard then vendor VPD pages */ + printf("Standard VPD pages:\n"); + enumerate_vpds(1, 1); + } + 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 (isalpha(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) { + pr2serr("abbreviation doesn't match a VPD page\n"); + printf("Available standard VPD pages:\n"); + enumerate_vpds(1, 1); + return SG_LIB_SYNTAX_ERROR; + } + } + 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"); + return SG_LIB_SYNTAX_ERROR; + } + 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); + return SG_LIB_SYNTAX_ERROR; + } + if (cp) { + if (isdigit(*(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); + return SG_LIB_SYNTAX_ERROR; + } + subvalue = op->vend_prod_num; + } else if (op->vend_prod) { + if (isdigit(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); + return SG_LIB_SYNTAX_ERROR; + } + subvalue = op->vend_prod_num; + } + } + } else if (op->vend_prod) { + if (isdigit(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); + return SG_LIB_SYNTAX_ERROR; + } + 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); + return sg_convert_errno(ENOMEM); + } + 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 = f2hex_arr(op->inhex_fn, op->do_raw, 0, 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) { + 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) + res = svpd_decode_all(-1, op); + else { + res = svpd_decode_t10(-1, op, subvalue, 0); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(-1, op, 0); + if (SG_LIB_CAT_OTHER == res) + res = svpd_unable_to_decode(-1, op, subvalue, 0); + } + } + ret = res; + goto err_out; + } + + 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->do_all) + ret = svpd_decode_all(sg_fd, op); + else { + memset(rsp_buff, 0, rsp_buff_sz); + + res = svpd_decode_t10(sg_fd, op, subvalue, 0); + if (SG_LIB_CAT_OTHER == res) { + res = svpd_decode_vendor(sg_fd, op, 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"); + } + 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) + return sg_convert_errno(-res); + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} diff --git a/src/sg_vpd_vendor.c b/src/sg_vpd_vendor.c new file mode 100644 index 0000000..3c75177 --- /dev/null +++ b/src/sg_vpd_vendor.c @@ -0,0 +1,1549 @@ +/* + * Copyright (c) 2006-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. + */ + +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifndef SG_LIB_MINGW +#include +#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" + +/* 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 http://www.t10.org . + + Acknowledgments: + - Lars Marowsky-Bree contributed Unit Path Report + VPD page decoding for EMC CLARiiON devices [20041016] + - Hannes Reinecke contributed RDAC vendor + specific VPD pages [20060421] + - Jonathan McDowell 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 + +#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) + +/* These structures are duplicates of those of the same name in + * sg_vpd.c . Take care that both are the same. */ + +struct opts_t { + bool do_all; + bool do_enum; + bool do_force; + bool do_long; + bool do_quiet; + int do_hex; + int vpd_pn; + int do_ident; + int maxlen; + int do_raw; + int vend_prod_num; + int verbose; + const char * device_name; + const char * page_str; + const char * inhex_fn; + const char * vend_prod; +}; + +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; +}; + +int vpd_fetch_page(int sg_fd, uint8_t * rp, int page, int mxlen, + bool qt, int vb, int * rlenp); + +/* sharing large global buffer, defined in sg_vpd.c */ +extern uint8_t * rsp_buff; + +/* end of section copied from sg_vpd.c . Maybe sg_vpd.h is needed */ + +struct svpd_vp_name_t { + int vend_prod_num; /* vendor/product identifier */ + const char * acron; + const char * name; +}; + + +/* Supported vendor specific VPD pages */ +/* Arrange in alphabetical order by acronym */ +static 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 */ +static 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}, +}; + + +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 int +is_like_pdt(int actual_pdt, const struct svpd_values_name_t * vnp) +{ + if (actual_pdt == vnp->pdt) + return 1; + if (PDT_DISK == vnp->pdt) { + switch (actual_pdt) { + case PDT_DISK: + case PDT_RBC: + case PDT_PROCESSOR: + case PDT_SAC: + case PDT_ZBC: + return 1; + default: + return 0; + } + } else if (PDT_TAPE == vnp->pdt) { + switch (actual_pdt) { + case PDT_TAPE: + case PDT_MCHANGER: + case PDT_ADC: + return 1; + default: + return 0; + } + } else + return 0; +} + +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; +} + + +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; +} + + +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; +} + +/* 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); + } + } +} + +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 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)", +}; + +static void +decode_upr_vpd_c0_emc(uint8_t * buff, int len) +{ + int k, ip_mgmt, vpp80, lun_z; + + if (len < 3) { + pr2serr("EMC upr VPD page [0xc0]: length too short=%d\n", len); + return; + } + if (buff[9] != 0x00) { + pr2serr("Unsupported page revision %d, decoding not possible.\n", + buff[9]); + return; + } + printf(" LUN WWN: "); + for (k = 0; k < 16; ++k) + printf("%02x", buff[10 + k]); + printf("\n"); + printf(" Array Serial Number: "); + dStrRaw(&buff[50], buff[49]); + printf("\n"); + + printf(" LUN State: "); + if (buff[4] > 0x02) + printf("Unknown (%x)\n", buff[4]); + else + printf("%s\n", lun_state_arr[buff[4]]); + + printf(" This path connects to: "); + if (buff[8] > 0x01) + printf("Unknown SP (%x)", buff[8]); + else + printf("%s", sp_arr[buff[8]]); + printf(", Port Number: %u\n", buff[7]); + + printf(" Default Owner: "); + if (buff[5] > 0x01) + printf("Unknown (%x)\n", buff[5]); + else + printf("%s\n", sp_arr[buff[5]]); + + printf(" NO_ATF: %s, Access Logix: %s\n", + buff[6] & 0x80 ? "set" : "not set", + buff[6] & 0x40 ? "supported" : "not supported"); + + ip_mgmt = (buff[6] >> 4) & 0x3; + + printf(" SP IP Management Mode: %s\n", ip_mgmt_arr[ip_mgmt]); + if (ip_mgmt == 2) + printf(" SP IPv4 address: %u.%u.%u.%u\n", + buff[44], buff[45], buff[46], buff[47]); + else { + printf(" SP IPv6 address: "); + for (k = 0; k < 16; ++k) + printf("%02x", buff[32 + k]); + printf("\n"); + } + + vpp80 = buff[30] & 0x08; + lun_z = buff[30] & 0x04; + + printf(" System Type: %x, Failover mode: %s\n", + buff[27], failover_mode_arr[buff[28] & 0x0f]); + + printf(" Inquiry VPP 0x80 returns: %s, Arraycommpath: %s\n", + vpp80 ? "array serial#" : "LUN serial#", + lun_z ? "Set to 1" : "Unknown"); + + printf(" Lun operations: %s\n", + buff[48] > 1 ? "undefined" : lun_op_arr[buff[48]]); + + return; +} + +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_c2(uint8_t * buff, int len) +{ + int i, n, v, r, m, p, d, y, num_part; + char part[5]; + + if (len < 3) { + pr2serr("Software Version VPD page length too short=%d\n", len); + 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; + } + printf(" Software Version: %02x.%02x.%02x\n", buff[8], buff[9], buff[10]); + printf(" Software Date: %02d/%02d/%02d\n", buff[11], buff[12], buff[13]); + printf(" Features:"); + if (buff[14] & 0x01) + printf(" Dual Active,"); + if (buff[14] & 0x02) + printf(" Series 3,"); + if (buff[14] & 0x04) + printf(" Multiple Sub-enclosures,"); + if (buff[14] & 0x08) + printf(" DCE/DRM/DSS/DVE,"); + if (buff[14] & 0x10) + printf(" Asymmetric Logical Unit Access,"); + printf("\n"); + printf(" Max. #of LUNS: %d\n", buff[15]); + + 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; +} + +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]); + } +} + +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; + } + 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, int off) +{ + int len, res; + char name[64]; + const struct svpd_values_name_t * vnp; + int alloc_len = op->maxlen; + uint8_t * rp; + + switch (op->vpd_pn) { + 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: + 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, op->vpd_pn, alloc_len, op->do_quiet, + op->verbose, &len); + if (0 == res) { + vnp = svpd_get_v_detail(op->vpd_pn, op->vend_prod_num, 0xf & rp[0]); + if (vnp && vnp->name) + strcpy(name, vnp->name); + else + snprintf(name, sizeof(name) - 1, "Vendor VPD page=0x%x", + op->vpd_pn); + if ((! op->do_raw) && (! op->do_quiet) && (op->do_hex < 2)) + printf("%s VPD Page:\n", name); + if (op->do_raw) + dStrRaw(rp, len); + else if (op->do_hex) + hex2stdout(rp, len, ((1 == op->do_hex) ? 0 : -1)); + else { + switch(op->vpd_pn) { + case 0x3: + if (VPD_VP_WDC_HITACHI == op->vend_prod_num) + decode_vpd_3_hit(rp, len); + else + hex2stdout(rp, len, 0); + break; + case 0xc0: + if (VPD_VP_SEAGATE == op->vend_prod_num) + decode_firm_vpd_c0_sea(rp, len); + else if (VPD_VP_EMC == op->vend_prod_num) + decode_upr_vpd_c0_emc(rp, len); + 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, op->vpd_pn); + else + hex2stdout(rp, len, 0); + break; + case 0xc1: + 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, op->vpd_pn); + else + hex2stdout(rp, len, 0); + break; + case 0xc2: + if (VPD_VP_RDAC == op->vend_prod_num) + decode_rdac_vpd_c2(rp, len); + else if (VPD_VP_HP_LTO == op->vend_prod_num) + decode_hp_lto_vpd_cx(rp, len, op->vpd_pn); + else + hex2stdout(rp, len, 0); + break; + case 0xc3: + 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, op->vpd_pn); + else + hex2stdout(rp, len, 0); + break; + case 0xc4: + 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, op->vpd_pn); + else + hex2stdout(rp, len, 0); + break; + case 0xc5: + if (VPD_VP_HP_LTO == op->vend_prod_num) + decode_hp_lto_vpd_cx(rp, len, op->vpd_pn); + else + hex2stdout(rp, len, 0); + break; + case 0xc8: + if (VPD_VP_RDAC == op->vend_prod_num) + decode_rdac_vpd_c8(rp, len); + else + hex2stdout(rp, len, 0); + break; + case 0xc9: + if (VPD_VP_RDAC == op->vend_prod_num) + decode_rdac_vpd_c9(rp, len); + else + hex2stdout(rp, len, 0); + break; + case 0xca: + if (VPD_VP_RDAC == op->vend_prod_num) + decode_rdac_vpd_ca(rp, len); + else + hex2stdout(rp, len, 0); + break; + case 0xd0: + if (VPD_VP_RDAC == op->vend_prod_num) + decode_rdac_vpd_d0(rp, len); + else + hex2stdout(rp, len, 0); + break; + case 0xd1: + if (VPD_VP_WDC_HITACHI == op->vend_prod_num) + decode_vpd_d1_hit(rp, len); + else + hex2stdout(rp, len, 0); + break; + case 0xd2: + if (VPD_VP_WDC_HITACHI == op->vend_prod_num) + decode_vpd_d2_hit(rp, len); + else + hex2stdout(rp, len, 0); + break; + default: + pr2serr("%s: logic error, should know can't decode " + "pn=0x%x\n", __func__, op->vpd_pn); + return SG_LIB_CAT_OTHER; + } + return 0; + } + } else + pr2serr("Vendor VPD page=0x%x failed to fetch", op->vpd_pn); + return res; +} diff --git a/src/sg_wr_mode.c b/src/sg_wr_mode.c new file mode 100644 index 0000000..ce900ff --- /dev/null +++ b/src/sg_wr_mode.c @@ -0,0 +1,646 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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.26 20180628"; + +#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(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 = 0x1f; + + /* 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 0000000..9c81e93 --- /dev/null +++ b/src/sg_write_buffer.c @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2006-2018 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.28 20180628"; /* spc5r19 */ + +#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=,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 * 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(*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; + } + } + } + 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 (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 0000000..41c6b8b --- /dev/null +++ b/src/sg_write_long.c @@ -0,0 +1,329 @@ +/* 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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 0000000..f589d0e --- /dev/null +++ b/src/sg_write_same.c @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2009-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.26 20180723"; + + +#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'}, + {"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 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] " + "[--grpnum=GN] [--help]\n" + " [--in=IF] [--lba=LBA] [--lbdata] " + "[--ndob] [--num=NUM]\n" + " [--pbdata] [--timeout=TO] [--unmap] " + "[--verbose]\n" + " [--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" + " --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 k, ret, res, sense_cat, cdb_len; + uint64_t llba; + uint8_t ws_cdb[WRITE_SAME32_LEN]; + uint8_t sense_b[SENSE_BUFF_LEN]; + 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; + memset(ws_cdb, 0, sizeof(ws_cdb)); + 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 & 0x1f); + 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 & 0x1f); + break; + case WRITE_SAME32_LEN: + ws_cdb[0] = VARIABLE_LEN_OP; + ws_cdb[6] = (op->grpnum & 0x1f); + 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) { + pr2serr(" Write same(%d) cdb: ", cdb_len); + for (k = 0; k < cdb_len; ++k) + pr2serr("%02x ", ws_cdb[k]); + pr2serr("\n 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, SG_NO_DATA_IN, + sense_b, true /*noisy */, op->verbose, + &sense_cat); + if (-1 == ret) + 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; + + 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, "ag:hi:l:Ln:NPRSt:TUvVw:x:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'a': + op->anchor = 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, vb > 3); + 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->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 0000000..55b715e --- /dev/null +++ b/src/sg_write_verify.c @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2014-2018 Douglas Gilbert + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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.15 20180628"; + + +#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, k, sense_cat, ret; + struct sg_pt_base * ptvp; + uint8_t sense_b[SENSE_BUFF_LEN]; + char b[32]; + + snprintf(b, sizeof(b), "Write and verify(%d)", cdb_len); + if (verbose) { + pr2serr(" %s cdb: ", b); + for (k = 0; k < cdb_len; ++k) + pr2serr("%02x ", cdbp[k]); + pr2serr("\n"); + 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, SG_NO_DATA_IN, sense_b, noisy, + verbose, &sense_cat); + if (-1 == ret) + 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_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 & 0x1f; + 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 & 0x1f; + ret = run_scsi_transaction(sg_fd, wv_cdb, sizeof(wv_cdb), dop, do_len, + timeout, noisy, verbose); + return ret; +} + +/* Returns file descriptor ( >= 0) if successfule. 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 = ""; + 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 0000000..3b8f091 --- /dev/null +++ b/src/sg_write_x.c @@ -0,0 +1,2665 @@ +/* + * Copyright (c) 2017-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* needed for lseek() */ +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 20180628"; + +/* 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 Descrptor" 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 */ + 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(*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 + strcpy(c, lcp); /* ... to end of line, including null */ + 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; + default: + pr2serr("%s: k=%d should not be >= 3\n", __func__, k); + ok = false; + 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; + default: + pr2serr("%s: k=%d should not be >= 3\n", __func__, k); + ok = false; + break; + } + } + return ok ? 0 : SG_LIB_SYNTAX_ERROR; +} + +/* 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 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 = ""; + } 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); + } + } + 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 (fp && (stdin != fp)) + fclose(fp); + return 0; +bad_exit: + if (fp && (stdin != fp)) + fclose(fp); + return SG_LIB_SYNTAX_ERROR; +bad_mem_exit: + if (fp && (stdin != 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]; + 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 & 0x1f); + } else { + x_cdb[0] = VARIABLE_LEN_OP; + x_cdb[6] = (op->grpnum & 0x1f); + 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; + 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) { + pr2serr(" %s cdb: ", op->cdb_name); + for (k = 0; k < cdb_len; ++k) + pr2serr("%02x ", x_cdb[k]); + pr2serr("\n"); + } + 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, SG_NO_DATA_IN, + sense_b, true /*noisy */, vb, &sense_cat); + if (-1 == ret) + 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=\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; + 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) || (j > INT32_MAX)) { + 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; + + op = &opts; + memset(op, 0, sizeof(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 0000000..5facfc8 --- /dev/null +++ b/src/sg_xcopy.c @@ -0,0 +1,1913 @@ +/* A utility program for copying files. Similar to 'dd' but using + * the 'Extended Copy' command. + * + * Copyright (c) 2011-2018 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. + * + * 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 and 4 series. + */ + +#define _XOPEN_SOURCE 600 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#include +#include +#ifndef major +#include +#endif +#include + +#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.68 20180811"; + +#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 /*unlikey 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 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; + + 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); + sg_cmds_close_device(fp->sg_fd); + 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" + " 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 (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(" < %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, u, 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 ((u = 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); + /* Descriptor must be less than 16 bytes */ + if (i_len > 16) + continue; + if (desig == 3) { + best = bp; + best_len = i_len; + break; + } + if (desig == 2) { + if (!best || f_desig < 2) { + best = bp; + best_len = i_len; + f_desig = 2; + } + } else if (desig == 1) { + if (!best || f_desig == 0) { + best = bp; + best_len = i_len; + f_desig = desig; + } + } else if (desig == 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; + memcpy(desc + 4, best, best_len + 4); + desc[4] &= 0x1f; + 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 ((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, "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 + strncpy(ixcf.fname, buf, INOUTF_SZ - 1); + } 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 + strncpy(oxcf.fname, buf, INOUTF_SZ - 1); + } 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_zone.c b/src/sg_zone.c new file mode 100644 index 0000000..9c5cda2 --- /dev/null +++ b/src/sg_zone.c @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2014-2018 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#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 CLOSE ZONE, FINISH ZONE or OPEN ZONE command + * to the given SCSI device. Based on zbc-r04c.pdf . + */ + +static const char * version_str = "1.12 20180628"; + +#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 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'}, + {"finish", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"open", no_argument, 0, 'o'}, + {"reset-all", no_argument, 0, 'R'}, + {"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 */ +static const char * sa_name_arr[] = { + "no SA=0", + "Close zone", + "Finish zone", + "Open zone", + "-", "-", "-", "-", + "-", /* 0x8 */ + "-", "-", "-", "-", + "-", + "-", + "-", + "Sequentialize zone", /* 0x10 */ +}; + + +static void +usage() +{ + pr2serr("Usage: " + "sg_zone [--all] [--close] [--count=ZC] [--finish] [--help]\n" + " [--open] [--sequentialize] [--verbose] " + "[--version]\n" + " [--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" + " --finish|-f issue FINISH ZONE command\n" + " --help|-h print out usage message\n" + " --open|-o issue OPEN ZONE 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 or " + "SEQUENTIALIZE\nZONE command. ID is decimal by default, for hex " + "use a leading '0x'\nor a trailing 'h'. Either --close, " + "--finish, --open or\n--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 k, 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]; + char b[64]; + + zo_cdb[1] = 0x1f & sa; + 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) { + pr2serr(" %s cdb: ", b); + for (k = 0; k < SG_ZONING_OUT_CMDLEN; ++k) + pr2serr("%02x ", zo_cdb[k]); + pr2serr("\n"); + } + + 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, "reset write pointer", res, + SG_NO_DATA_IN, sense_b, noisy, verbose, + &sense_cat); + if (-1 == ret) + 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 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:fhoRSvVz:", 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 'f': + finish = true; + sa = FINISH_ZONE_SA; + break; + case 'h': + case '?': + usage(); + return 0; + case 'o': + open = true; + sa = OPEN_ZONE_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)) { + pr2serr("one from the --close, --finish, --open and --sequentialize " + "options must be given\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; + } + + 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 0000000..bddc964 --- /dev/null +++ b/src/sginfo.c @@ -0,0 +1,3994 @@ +/* * 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 access page number [and subpage ], try to decode + * -u access page number [and subpage ], 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. + * + * 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" as well as "sr" 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.42 [20180811]"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#include + +#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]; + 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 *)malloc(sizeof(unsigned int) * MAX_HEADS); + if (headsp == NULL) { + perror("malloc failed"); + return status; + } + memset(headsp,0,sizeof(unsigned int) * MAX_HEADS); + } + 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("|"); + } + 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("|"); + } + 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("|"); + } + 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 0) { + printf("%3d: %u\n", i, headsp[i]); + } + } + } + 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, "Mimimum 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", 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 Access mode page [subpage ] and decode.\n" + "\t-T Trace commands (for debugging, double for more)\n" + "\t-u Access mode page [subpage ], 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 " + "[,]"); + 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 0000000..5d97589 --- /dev/null +++ b/src/sgm_dd.c @@ -0,0 +1,1437 @@ +/* A utility program for copying files. Specialised for "files" that + * represent devices that understand the SCSI command set. + * + * Copyright (C) 1999 - 2018 D. Gilbert and P. Allworth + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + + This program is a specialisation of the Unix "dd" command in which + either the input or the output file is a scsi generic device or a + raw 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 and 4 series. +*/ + +#define _XOPEN_SOURCE 600 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#include +#include +#ifndef major +#include +#endif +#include /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */ +#include /* 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.60 20180811"; + +#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 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 /*unlikey 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 bool do_time = false; +static bool start_tm_valid = false; +static struct timeval start_tm; +static int blk_sz = 0; + +static const char * proc_allow_dio = "/proc/scsi/sg/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 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 (verbose) + 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 or ). */ +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 + "], 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 + "], 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) +{ + int k, res; + uint8_t rdCmd[MAX_SCSI_CDBSZ]; + uint8_t senseBuff[SENSE_BUFF_LEN]; + 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)from_block; + if (do_mmap) + io_hdr.flags |= SG_FLAG_MMAP_IO; + if (verbose > 2) { + pr2serr(" read cdb: "); + for (k = 0; k < cdbsz; ++k) + pr2serr("%02x ", rdCmd[k]); + pr2serr("\n"); + } + +#if 1 + while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) && + ((EINTR == errno) || (EAGAIN == 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))) + ; + 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))) + ; + 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_ABORTED_COMMAND: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ILLEGAL_REQ: + default: + sg_chk_n_print3("reading", &io_hdr, verbose > 1); + 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) +{ + int k, res; + uint8_t wrCmd[MAX_SCSI_CDBSZ]; + uint8_t senseBuff[SENSE_BUFF_LEN]; + struct sg_io_hdr io_hdr; + + 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; + } + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + 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)to_block; + if (do_mmap) + io_hdr.flags |= SG_FLAG_MMAP_IO; + else if (diop && *diop) + io_hdr.flags |= SG_FLAG_DIRECT_IO; + if (verbose > 2) { + pr2serr(" write cdb: "); + for (k = 0; k < cdbsz; ++k) + pr2serr("%02x ", wrCmd[k]); + pr2serr("\n"); + } + +#if 1 + while (((res = ioctl(sg_fd, SG_IO, &io_hdr)) < 0) && + ((EINTR == errno) || (EAGAIN == 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))) + ; + 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))) + ; + 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_ABORTED_COMMAND: + case SG_LIB_CAT_UNIT_ATTENTION: + case SG_LIB_CAT_ILLEGAL_REQ: + default: + sg_chk_n_print3("writing", &io_hdr, verbose > 1); + 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); + 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 (-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,"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 (-1 == ibs) { + 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 + snprintf(inf, INOUTF_SZ, "%s", buf); + } 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 + snprintf(outf, INOUTF_SZ, "%s", buf); + } 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 (-1 == obs) { + pr2serr(ME "bad argument to 'obs'\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,"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, '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, "--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' (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) { + pr2serr("bpt must be greater than 0\n"); + 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) + 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) + 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) + 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) + 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("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("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(proc_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + pr2serr(">>> %s set to '0' but should be set to '1' for " + "direct IO\n", proc_allow_dio); + } + close(fd); + } + } + + if (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))) + ; + 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))) + ; + 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) + close(infd); + if ((STDOUT_FILENO != outfd) && (FT_DEV_NULL != out_type)) + 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 0000000..7b76092 --- /dev/null +++ b/src/sgp_dd.c @@ -0,0 +1,1778 @@ +/* A utility program for copying files. Specialised for "files" that + * represent devices that understand the SCSI command set. + * + * Copyright (C) 1999 - 2018 D. Gilbert and P. Allworth + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is a specialisation of the Unix "dd" command in which + * one or both of the given files is a scsi generic device or a raw + * device. A block size ('bs') is assumed to be 512 if not given. This + * program complains if 'ibs' or 'obs' are given with some other value + * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If + * 'of' is not given or 'of=-' then stdout assumed. + * + * 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 and 4 series. + */ + +#define _XOPEN_SOURCE 600 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include +#ifndef major +#include +#endif +#include +#include /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */ +#include /* 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 = "5.69 20180811"; + +#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 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 SG_MAX_QUEUE + +#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 + +struct flags_t { + bool append; + bool coe; + bool dio; + bool direct; + bool dpo; + bool dsync; + bool excl; + bool fua; +}; + +typedef struct request_collection +{ /* 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; /* | */ + bool in_stop; /* | */ + pthread_mutex_t in_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; /* | */ + bool out_stop; /* | */ + pthread_mutex_t out_mutex; /* | */ + pthread_cond_t out_sync_cv; /* -/ hold writes until "in order" */ + int bs; + int bpt; + int dio_incomplete_count; /* -\ */ + int sum_of_resids; /* | */ + pthread_mutex_t aux_mutex; /* -/ (also serializes some printf()s */ + int debug; + int dry_run; +} Rq_coll; + +typedef struct request_element +{ /* one instance per worker thread */ + bool wr; + 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 cmd[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; +} Rq_elem; + +static sigset_t signal_set; +static pthread_t sig_listen_thread_id; + +static const char * proc_allow_dio = "/proc/scsi/sg/allow_dio"; + +static void sg_in_operation(Rq_coll * clp, Rq_elem * rep); +static void sg_out_operation(Rq_coll * clp, Rq_elem * rep); +static bool normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks); +static void normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks); +static int sg_start_io(Rq_elem * rep); +static int sg_finish_io(bool wr, Rq_elem * rep, pthread_mutex_t * a_mutp); + +#define STRERR_BUFF_LEN 128 + +static pthread_mutex_t strerr_mut = PTHREAD_MUTEX_INITIALIZER; + +static bool shutting_down = false; +static bool do_sync = false; +static bool do_time = false; +static Rq_coll rcoll; +static struct timeval start_tm; +static int64_t dd_count = -1; +static int num_threads = DEF_NUM_THREADS; +static int exit_status = 0; + +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)rcoll.bs * (dd_count - rcoll.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 != rcoll.out_rem_count) + pr2serr(" remaining block count=%" PRId64 "\n", + rcoll.out_rem_count); + infull = dd_count - rcoll.in_rem_count; + pr2serr("%s%" PRId64 "+%d records in\n", str, + infull - rcoll.in_partial, rcoll.in_partial); + + outfull = dd_count - rcoll.out_rem_count; + pr2serr("%s%" PRId64 "+%d records out\n", str, + outfull - rcoll.out_partial, rcoll.out_partial); +} + +static void +interrupt_handler(int sig) +{ + struct sigaction sigact; + + sigact.sa_handler = SIG_DFL; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(sig, &sigact, NULL); + 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) +{ + char * cp; + + pthread_mutex_lock(&strerr_mut); + cp = safe_strerror(code); + strncpy(ebp, cp, STRERR_BUFF_LEN); + pthread_mutex_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]; \ + 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 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, 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,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" + " thr is number of threads, must be > 0, default 4, " + "max 16\n" + " time 0->no timing(def), 1->time plus calculate " + "throughput\n" + " verbose same as 'deb=VERB': increase verbosity\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 void +guarded_stop_in(Rq_coll * clp) +{ + pthread_mutex_lock(&clp->in_mutex); + clp->in_stop = true; + pthread_mutex_unlock(&clp->in_mutex); +} + +static void +guarded_stop_out(Rq_coll * clp) +{ + pthread_mutex_lock(&clp->out_mutex); + clp->out_stop = true; + pthread_mutex_unlock(&clp->out_mutex); +} + +static void +guarded_stop_both(Rq_coll * clp) +{ + guarded_stop_in(clp); + guarded_stop_out(clp); +} + +/* 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 or ). */ +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) +{ + Rq_coll * clp = (Rq_coll *)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); + guarded_stop_both(clp); + pthread_cond_broadcast(&clp->out_sync_cv); + } + } + return NULL; +} + +static void +cleanup_in(void * v_clp) +{ + Rq_coll * clp = (Rq_coll *)v_clp; + + pr2serr("thread cancelled while in mutex held\n"); + clp->in_stop = true; + pthread_mutex_unlock(&clp->in_mutex); + guarded_stop_out(clp); + pthread_cond_broadcast(&clp->out_sync_cv); +} + +static void +cleanup_out(void * v_clp) +{ + Rq_coll * clp = (Rq_coll *)v_clp; + + pr2serr("thread cancelled while out mutex held\n"); + clp->out_stop = true; + pthread_mutex_unlock(&clp->out_mutex); + guarded_stop_in(clp); + pthread_cond_broadcast(&clp->out_sync_cv); +} + +static void * +read_write_thread(void * v_clp) +{ + Rq_coll * clp; + Rq_elem rel; + Rq_elem * rep = &rel; + int sz; + volatile bool stop_after_write = false; + int64_t seek_skip; + int blocks, status; + + clp = (Rq_coll *)v_clp; + sz = clp->bpt * clp->bs; + seek_skip = clp->seek - clp->skip; + memset(rep, 0, sizeof(Rq_elem)); + 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"); + + /* Following clp members are constant during lifetime of thread */ + rep->bs = clp->bs; + rep->infd = clp->infd; + rep->outfd = clp->outfd; + 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; + + while(1) { + status = pthread_mutex_lock(&clp->in_mutex); + if (0 != status) err_exit(status, "lock in_mutex"); + if (clp->in_stop || (clp->in_count <= 0)) { + /* no more to do, exit loop then thread */ + status = pthread_mutex_unlock(&clp->in_mutex); + if (0 != status) err_exit(status, "unlock in_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; + + pthread_cleanup_push(cleanup_in, (void *)clp); + if (FT_SG == clp->in_type) + sg_in_operation(clp, rep); /* lets go of in_mutex mid operation */ + else { + stop_after_write = normal_in_operation(clp, rep, blocks); + status = pthread_mutex_unlock(&clp->in_mutex); + if (0 != status) err_exit(status, "unlock in_mutex"); + } + pthread_cleanup_pop(0); + + status = pthread_mutex_lock(&clp->out_mutex); + if (0 != status) err_exit(status, "lock out_mutex"); + if (FT_DEV_NULL != clp->out_type) { + while ((! clp->out_stop) && + ((rep->blk + seek_skip) != 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->out_mutex); + if (0 != status) err_exit(status, "cond out_sync_cv"); + pthread_cleanup_pop(0); + } + } + + if (clp->out_stop || (clp->out_count <= 0)) { + if (! clp->out_stop) + clp->out_stop = true; + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + break; + } + if (stop_after_write) + clp->out_stop = true; + rep->wr = true; + rep->blk = clp->out_blk; + clp->out_blk += blocks; + clp->out_count -= blocks; + + if (0 == rep->num_blks) { + clp->out_stop = true; + stop_after_write = true; + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + break; /* read nothing so leave loop */ + } + + pthread_cleanup_push(cleanup_out, (void *)clp); + if (FT_SG == clp->out_type) + sg_out_operation(clp, rep); /* releases out_mutex mid operation */ + else if (FT_DEV_NULL == clp->out_type) { + /* skip actual write operation */ + clp->out_rem_count -= blocks; + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + } + else { + normal_out_operation(clp, rep, blocks); + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + } + pthread_cleanup_pop(0); + + if (stop_after_write) + break; + pthread_cond_broadcast(&clp->out_sync_cv); + } /* end of while loop */ + if (rep->alloc_bp) + free(rep->alloc_bp); + status = pthread_mutex_lock(&clp->in_mutex); + if (0 != status) err_exit(status, "lock in_mutex"); + if (! clp->in_stop) + clp->in_stop = true; /* flag other workers to stop */ + status = pthread_mutex_unlock(&clp->in_mutex); + if (0 != status) err_exit(status, "unlock in_mutex"); + pthread_cond_broadcast(&clp->out_sync_cv); + return stop_after_write ? NULL : clp; +} + +static bool +normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks) +{ + bool stop_after_write = false; + int res; + char strerr_buff[STRERR_BUFF_LEN]; + + /* enters holding in_mutex */ + while (((res = read(clp->infd, rep->buffp, blocks * clp->bs)) < 0) && + ((EINTR == errno) || (EAGAIN == errno))) + ; + if (res < 0) { + if (clp->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 * clp->bs; + } + else { + pr2serr("error in normal read, %s\n", + tsafe_strerror(errno, strerr_buff)); + clp->in_stop = true; + guarded_stop_out(clp); + return 1; + } + } + if (res < blocks * clp->bs) { + int o_blocks = blocks; + stop_after_write = true; + blocks = res / clp->bs; + if ((res % clp->bs) > 0) { + blocks++; + clp->in_partial++; + } + /* Reverse out + re-apply blocks on clp */ + clp->in_blk -= o_blocks; + clp->in_count += o_blocks; + rep->num_blks = blocks; + clp->in_blk += blocks; + clp->in_count -= blocks; + } + clp->in_rem_count -= blocks; + return stop_after_write; +} + +static void +normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks) +{ + int res; + char strerr_buff[STRERR_BUFF_LEN]; + + /* enters holding out_mutex */ + while (((res = write(clp->outfd, rep->buffp, rep->num_blks * clp->bs)) + < 0) && ((EINTR == errno) || (EAGAIN == errno))) + ; + if (res < 0) { + if (clp->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 * clp->bs; + } + else { + pr2serr("error normal write, %s\n", + tsafe_strerror(errno, strerr_buff)); + guarded_stop_in(clp); + clp->out_stop = true; + return; + } + } + if (res < blocks * clp->bs) { + blocks = res / clp->bs; + if ((res % clp->bs) > 0) { + blocks++; + clp->out_partial++; + } + rep->num_blks = blocks; + } + clp->out_rem_count -= blocks; +} + +static int +sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks, + int64_t start_block, bool 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(Rq_coll * clp, Rq_elem * rep) +{ + int res; + int status; + + /* enters holding in_mutex */ + 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); + status = pthread_mutex_unlock(&clp->in_mutex); + if (0 != status) err_exit(status, "unlock in_mutex"); + guarded_stop_both(clp); + return; + } + /* Now release in mutex to let other reads run in parallel */ + status = pthread_mutex_unlock(&clp->in_mutex); + if (0 != status) err_exit(status, "unlock in_mutex"); + + res = sg_finish_io(rep->wr, rep, &clp->aux_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 */ + status = pthread_mutex_lock(&clp->in_mutex); + if (0 != status) err_exit(status, "lock in_mutex"); + break; + case SG_LIB_CAT_MEDIUM_HARD: + if (0 == clp->in_flags.coe) { + pr2serr("error finishing sg in command (medium)\n"); + if (exit_status <= 0) + exit_status = res; + guarded_stop_both(clp); + 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: + if (rep->dio_incomplete_count || rep->resid) { + status = pthread_mutex_lock(&clp->aux_mutex); + if (0 != status) err_exit(status, "lock aux_mutex"); + clp->dio_incomplete_count += rep->dio_incomplete_count; + clp->sum_of_resids += rep->resid; + status = pthread_mutex_unlock(&clp->aux_mutex); + if (0 != status) err_exit(status, "unlock aux_mutex"); + } + status = pthread_mutex_lock(&clp->in_mutex); + if (0 != status) err_exit(status, "lock in_mutex"); + clp->in_rem_count -= rep->num_blks; + status = pthread_mutex_unlock(&clp->in_mutex); + if (0 != status) err_exit(status, "unlock in_mutex"); + return; + default: + pr2serr("error finishing sg in command (%d)\n", res); + if (exit_status <= 0) + exit_status = res; + guarded_stop_both(clp); + return; + } + } +} + +static void +sg_out_operation(Rq_coll * clp, Rq_elem * rep) +{ + int res; + int status; + + /* enters holding out_mutex */ + 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); + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + guarded_stop_both(clp); + return; + } + /* Now release in mutex to let other reads run in parallel */ + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + + res = sg_finish_io(rep->wr, rep, &clp->aux_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 */ + status = pthread_mutex_lock(&clp->out_mutex); + if (0 != status) err_exit(status, "lock out_mutex"); + break; + case SG_LIB_CAT_MEDIUM_HARD: + if (0 == clp->out_flags.coe) { + pr2serr("error finishing sg out command (medium)\n"); + if (exit_status <= 0) + exit_status = res; + guarded_stop_both(clp); + 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: + if (rep->dio_incomplete_count || rep->resid) { + status = pthread_mutex_lock(&clp->aux_mutex); + if (0 != status) err_exit(status, "lock aux_mutex"); + clp->dio_incomplete_count += rep->dio_incomplete_count; + clp->sum_of_resids += rep->resid; + status = pthread_mutex_unlock(&clp->aux_mutex); + if (0 != status) err_exit(status, "unlock aux_mutex"); + } + status = pthread_mutex_lock(&clp->out_mutex); + if (0 != status) err_exit(status, "lock out_mutex"); + clp->out_rem_count -= rep->num_blks; + status = pthread_mutex_unlock(&clp->out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + return; + default: + pr2serr("error finishing sg out command (%d)\n", res); + if (exit_status <= 0) + exit_status = res; + guarded_stop_both(clp); + 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; + int cdbsz = rep->wr ? rep->cdbsz_out : rep->cdbsz_in; + int res; + + if (sg_build_scsi_cdb(rep->cmd, 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->cmd; + hp->dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; + hp->dxfer_len = rep->bs * rep->num_blks; + hp->dxferp = rep->buffp; + hp->mx_sb_len = sizeof(rep->sb); + hp->sbp = rep->sb; + hp->timeout = DEF_TIMEOUT; + hp->usr_ptr = rep; + hp->pack_id = (int)rep->blk; + if (dio) + hp->flags |= SG_FLAG_DIRECT_IO; + if (rep->debug > 8) { + pr2serr("sg_start_io: SCSI %s, blk=%" PRId64 " num_blks=%d\n", + 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))) + ; + 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->blk; + + while (((res = read(wr ? rep->outfd : rep->infd, &io_hdr, + sizeof(struct sg_io_hdr))) < 0) && + ((EINTR == errno) || (EAGAIN == 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 > 8) + sg_chk_n_print3((wr ? "writing": "reading"), hp, false); + return res; + case SG_LIB_CAT_NOT_READY: + default: + { + 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 aux_mutex"); + sg_chk_n_print3(ebuff, hp, false); + status = pthread_mutex_unlock(a_mutp); + if (0 != status) err_exit(status, "unlock aux_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("sg_finish_io: completed %s\n", wr ? "WRITE" : "READ"); + return 0; +} + +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 +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, "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 + +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; + char inf[INOUTF_SZ]; + char outf[INOUTF_SZ]; + int res, k, err, keylen; + int64_t in_num_sect = 0; + int64_t out_num_sect = 0; + pthread_t threads[MAX_NUM_THREADS]; + int in_sect_sz, out_sect_sz, status, n, flags; + void * vp; + 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(&rcoll, 0, sizeof(Rq_coll)); + rcoll.bpt = DEF_BLOCKS_PER_TRANSFER; + rcoll.in_type = FT_OTHER; + rcoll.out_type = FT_OTHER; + rcoll.cdbsz_in = DEF_SCSI_CDBSZ; + rcoll.cdbsz_out = DEF_SCSI_CDBSZ; + inf[0] = '\0'; + outf[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")) { + rcoll.bpt = sg_get_num(buf); + if (-1 == rcoll.bpt) { + pr2serr("%sbad argument to 'bpt='\n", my_name); + return SG_LIB_SYNTAX_ERROR; + } + bpt_given = 1; + } else if (0 == strcmp(key,"bs")) { + rcoll.bs = sg_get_num(buf); + if (-1 == rcoll.bs) { + pr2serr("%sbad argument to 'bs='\n", my_name); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strcmp(key,"cdbsz")) { + rcoll.cdbsz_in = sg_get_num(buf); + rcoll.cdbsz_out = rcoll.cdbsz_in; + cdbsz_given = 1; + } else if (0 == strcmp(key,"coe")) { + rcoll.in_flags.coe = !! sg_get_num(buf); + rcoll.out_flags.coe = rcoll.in_flags.coe; + } else if (0 == strcmp(key,"count")) { + if (0 != strcmp("-1", buf)) { + dd_count = sg_get_llnum(buf); + if (-1LL == dd_count) { + 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))) + rcoll.debug = sg_get_num(buf); + else if (0 == strcmp(key,"dio")) { + rcoll.in_flags.dio = !! sg_get_num(buf); + rcoll.out_flags.dio = rcoll.in_flags.dio; + } else if (0 == strcmp(key,"fua")) { + n = sg_get_num(buf); + if (n & 1) + rcoll.out_flags.fua = true; + if (n & 2) + rcoll.in_flags.fua = true; + } else if (0 == strcmp(key,"ibs")) { + ibs = sg_get_num(buf); + if (-1 == ibs) { + pr2serr("%sbad argument to 'ibs='\n", my_name); + return SG_LIB_SYNTAX_ERROR; + } + } else if (strcmp(key,"if") == 0) { + if ('\0' != inf[0]) { + pr2serr("Second 'if=' argument??\n"); + return SG_LIB_SYNTAX_ERROR; + } else + snprintf(inf, INOUTF_SZ, "%s", buf); + } else if (0 == strcmp(key, "iflag")) { + if (process_flags(buf, &rcoll.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 (-1 == obs) { + pr2serr("%sbad argument to 'obs='\n", my_name); + return SG_LIB_SYNTAX_ERROR; + } + } else if (strcmp(key,"of") == 0) { + if ('\0' != outf[0]) { + pr2serr("Second 'of=' argument??\n"); + return SG_LIB_SYNTAX_ERROR; + } else + snprintf(outf, INOUTF_SZ, "%s", buf); + } else if (0 == strcmp(key, "oflag")) { + if (process_flags(buf, &rcoll.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 (-1LL == 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 (-1LL == skip) { + pr2serr("%sbad argument to 'skip='\n", my_name); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strcmp(key,"sync")) + do_sync = !! sg_get_num(buf); + else if (0 == strcmp(key,"thr")) + num_threads = sg_get_num(buf); + else if (0 == strcmp(key,"time")) + do_time = !! sg_get_num(buf); + else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) { + res = 0; + n = num_chs_in_str(key + 1, keylen - 1, 'd'); + rcoll.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, 'v'); + if (n > 0) + verbose_given = true; + rcoll.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, "--dry-run", 9)) || + (0 == strncmp(key, "--dry_run", 9))) + ++rcoll.dry_run; + else if ((0 == strncmp(key, "--help", 6)) || + (0 == strcmp(key, "-?"))) { + usage(); + return 0; + } else if (0 == strncmp(key, "--verb", 6)) { + verbose_given = true; + ++rcoll.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; + rcoll.debug = 0; + } else if (! verbose_given) { + pr2serr("set '-vv'\n"); + rcoll.debug = 2; + } else + pr2serr("keep verbose=%d\n", rcoll.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 (rcoll.bs <= 0) { + rcoll.bs = DEF_BLOCK_SIZE; + pr2serr("Assume default 'bs' (block size) of %d bytes\n", rcoll.bs); + } + if ((ibs && (ibs != rcoll.bs)) || (obs && (obs != rcoll.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 (rcoll.out_flags.append && (seek > 0)) { + pr2serr("Can't use both append and seek switches\n"); + return SG_LIB_SYNTAX_ERROR; + } + if (rcoll.bpt < 1) { + pr2serr("bpt must be greater than 0\n"); + 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 ((rcoll.bs >= 2048) && (0 == bpt_given)) + rcoll.bpt = DEF_BLOCKS_PER_2048TRANSFER; + if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) { + pr2serr("too few or too many threads requested\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + if (rcoll.debug) + pr2serr("%sif=%s skip=%" PRId64 " of=%s seek=%" PRId64 " count=%" + PRId64 "\n", my_name, inf, skip, outf, seek, dd_count); + + install_handler(SIGINT, interrupt_handler); + install_handler(SIGQUIT, interrupt_handler); + install_handler(SIGPIPE, interrupt_handler); + install_handler(SIGUSR1, siginfo_handler); + + rcoll.infd = STDIN_FILENO; + rcoll.outfd = STDOUT_FILENO; + if (inf[0] && ('-' != inf[0])) { + rcoll.in_type = dd_filetype(inf); + + if (FT_ERROR == rcoll.in_type) { + pr2serr("%sunable to access %s\n", my_name, inf); + return SG_LIB_FILE_ERROR; + } else if (FT_ST == rcoll.in_type) { + pr2serr("%sunable to use scsi tape device %s\n", my_name, inf); + return SG_LIB_FILE_ERROR; + } else if (FT_SG == rcoll.in_type) { + flags = O_RDWR; + if (rcoll.in_flags.direct) + flags |= O_DIRECT; + if (rcoll.in_flags.excl) + flags |= O_EXCL; + if (rcoll.in_flags.dsync) + flags |= O_SYNC; + + if ((rcoll.infd = open(inf, flags)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for sg " + "reading", my_name, inf); + perror(ebuff); + return sg_convert_errno(err);; + } + if (sg_prepare(rcoll.infd, rcoll.bs, rcoll.bpt)) + return SG_LIB_FILE_ERROR; + } + else { + flags = O_RDONLY; + if (rcoll.in_flags.direct) + flags |= O_DIRECT; + if (rcoll.in_flags.excl) + flags |= O_EXCL; + if (rcoll.in_flags.dsync) + flags |= O_SYNC; + + if ((rcoll.infd = open(inf, flags)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for reading", + my_name, inf); + perror(ebuff); + return sg_convert_errno(err); + } + else if (skip > 0) { + off64_t offset = skip; + + offset *= rcoll.bs; /* could exceed 32 here! */ + if (lseek64(rcoll.infd, offset, SEEK_SET) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scouldn't skip to required " + "position on %s", my_name, inf); + perror(ebuff); + return sg_convert_errno(err); + } + } + } + } + if (outf[0] && ('-' != outf[0])) { + rcoll.out_type = dd_filetype(outf); + + if (FT_ST == rcoll.out_type) { + pr2serr("%sunable to use scsi tape device %s\n", my_name, outf); + return SG_LIB_FILE_ERROR; + } + else if (FT_SG == rcoll.out_type) { + flags = O_RDWR; + if (rcoll.out_flags.direct) + flags |= O_DIRECT; + if (rcoll.out_flags.excl) + flags |= O_EXCL; + if (rcoll.out_flags.dsync) + flags |= O_SYNC; + + if ((rcoll.outfd = open(outf, flags)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for sg " + "writing", my_name, outf); + perror(ebuff); + return sg_convert_errno(err); + } + + if (sg_prepare(rcoll.outfd, rcoll.bs, rcoll.bpt)) + return SG_LIB_FILE_ERROR; + } + else if (FT_DEV_NULL == rcoll.out_type) + rcoll.outfd = -1; /* don't bother opening */ + else { + if (FT_RAW != rcoll.out_type) { + flags = O_WRONLY | O_CREAT; + if (rcoll.out_flags.direct) + flags |= O_DIRECT; + if (rcoll.out_flags.excl) + flags |= O_EXCL; + if (rcoll.out_flags.dsync) + flags |= O_SYNC; + if (rcoll.out_flags.append) + flags |= O_APPEND; + + if ((rcoll.outfd = open(outf, flags, 0666)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for " + "writing", my_name, outf); + perror(ebuff); + return sg_convert_errno(err); + } + } + else { /* raw output file */ + if ((rcoll.outfd = open(outf, O_WRONLY)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for raw " + "writing", my_name, outf); + perror(ebuff); + return sg_convert_errno(err); + } + } + if (seek > 0) { + off64_t offset = seek; + + offset *= rcoll.bs; /* could exceed 32 bits here! */ + if (lseek64(rcoll.outfd, offset, SEEK_SET) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scouldn't seek to required " + "position on %s", my_name, outf); + perror(ebuff); + return sg_convert_errno(err); + } + } + } + } + if ((STDIN_FILENO == rcoll.infd) && (STDOUT_FILENO == rcoll.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 == rcoll.in_type) { + res = scsi_read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz); + if (2 == res) { + pr2serr("Unit attention, media changed(in), continuing\n"); + res = scsi_read_capacity(rcoll.infd, &in_num_sect, + &in_sect_sz); + } + if (0 != res) { + if (res == SG_LIB_CAT_INVALID_OP) + pr2serr("read capacity not supported on %s\n", inf); + else if (res == SG_LIB_CAT_NOT_READY) + pr2serr("read capacity failed, %s not ready\n", inf); + else + pr2serr("Unable to read capacity on %s\n", inf); + in_num_sect = -1; + } + } else if (FT_BLOCK == rcoll.in_type) { + if (0 != read_blkdev_capacity(rcoll.infd, &in_num_sect, + &in_sect_sz)) { + pr2serr("Unable to read block capacity on %s\n", inf); + in_num_sect = -1; + } + if (rcoll.bs != in_sect_sz) { + pr2serr("block size on %s confusion; bs=%d, from device=%d\n", + inf, rcoll.bs, in_sect_sz); + in_num_sect = -1; + } + } + if (in_num_sect > skip) + in_num_sect -= skip; + + out_num_sect = -1; + if (FT_SG == rcoll.out_type) { + res = scsi_read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz); + if (2 == res) { + pr2serr("Unit attention, media changed(out), continuing\n"); + res = scsi_read_capacity(rcoll.outfd, &out_num_sect, + &out_sect_sz); + } + if (0 != res) { + if (res == SG_LIB_CAT_INVALID_OP) + pr2serr("read capacity not supported on %s\n", outf); + else if (res == SG_LIB_CAT_NOT_READY) + pr2serr("read capacity failed, %s not ready\n", outf); + else + pr2serr("Unable to read capacity on %s\n", outf); + out_num_sect = -1; + } + } else if (FT_BLOCK == rcoll.out_type) { + if (0 != read_blkdev_capacity(rcoll.outfd, &out_num_sect, + &out_sect_sz)) { + pr2serr("Unable to read block capacity on %s\n", outf); + out_num_sect = -1; + } + if (rcoll.bs != out_sect_sz) { + pr2serr("block size on %s confusion: bs=%d, from device=%d\n", + outf, rcoll.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 (rcoll.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 == rcoll.in_type) && (MAX_SCSI_CDBSZ != rcoll.cdbsz_in) && + (((dd_count + skip) > UINT_MAX) || (rcoll.bpt > USHRT_MAX))) { + pr2serr("Note: SCSI command size increased to 16 bytes (for " + "'if')\n"); + rcoll.cdbsz_in = MAX_SCSI_CDBSZ; + } + if ((FT_SG == rcoll.out_type) && (MAX_SCSI_CDBSZ != rcoll.cdbsz_out) && + (((dd_count + seek) > UINT_MAX) || (rcoll.bpt > USHRT_MAX))) { + pr2serr("Note: SCSI command size increased to 16 bytes (for " + "'of')\n"); + rcoll.cdbsz_out = MAX_SCSI_CDBSZ; + } + } + + rcoll.in_count = dd_count; + rcoll.in_rem_count = dd_count; + rcoll.skip = skip; + rcoll.in_blk = skip; + rcoll.out_count = dd_count; + rcoll.out_rem_count = dd_count; + rcoll.seek = seek; + rcoll.out_blk = seek; + status = pthread_mutex_init(&rcoll.in_mutex, NULL); + if (0 != status) err_exit(status, "init in_mutex"); + status = pthread_mutex_init(&rcoll.out_mutex, NULL); + if (0 != status) err_exit(status, "init out_mutex"); + status = pthread_mutex_init(&rcoll.aux_mutex, NULL); + if (0 != status) err_exit(status, "init aux_mutex"); + status = pthread_cond_init(&rcoll.out_sync_cv, NULL); + if (0 != status) err_exit(status, "init out_sync_cv"); + + if (rcoll.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 *)&rcoll); + 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 ((rcoll.out_rem_count > 0) && (num_threads > 0)) { + /* Run 1 work thread to shake down infant retryable stuff */ + status = pthread_mutex_lock(&rcoll.out_mutex); + if (0 != status) err_exit(status, "lock out_mutex"); + status = pthread_create(&threads[0], NULL, read_write_thread, + (void *)&rcoll); + if (0 != status) err_exit(status, "pthread_create"); + if (rcoll.debug) + pr2serr("Starting worker thread k=0\n"); + + /* wait for any broadcast */ + pthread_cleanup_push(cleanup_out, (void *)&rcoll); + status = pthread_cond_wait(&rcoll.out_sync_cv, &rcoll.out_mutex); + if (0 != status) err_exit(status, "cond out_sync_cv"); + pthread_cleanup_pop(0); + status = pthread_mutex_unlock(&rcoll.out_mutex); + if (0 != status) err_exit(status, "unlock out_mutex"); + + /* now start the rest of the threads */ + for (k = 1; k < num_threads; ++k) { + status = pthread_create(&threads[k], NULL, read_write_thread, + (void *)&rcoll); + if (0 != status) err_exit(status, "pthread_create"); + if (rcoll.debug) + pr2serr("Starting worker thread k=%d\n", k); + } + + /* now wait for worker threads to finish */ + for (k = 0; k < num_threads; ++k) { + status = pthread_join(threads[k], &vp); + if (0 != status) err_exit(status, "pthread_join"); + if (rcoll.debug) + 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 == rcoll.out_type) { + pr2serr(">> Synchronizing cache on %s\n", outf); + res = sg_ll_sync_cache_10(rcoll.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(rcoll.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 != rcoll.infd) + close(rcoll.infd); + if ((STDOUT_FILENO != rcoll.outfd) && (FT_DEV_NULL != rcoll.out_type)) + close(rcoll.outfd); + res = exit_status; + if ((0 != rcoll.out_count) && (0 == rcoll.dry_run)) { + pr2serr(">>>> Some error occurred, remaining blocks=%" PRId64 "\n", + rcoll.out_count); + if (0 == res) + res = SG_LIB_CAT_OTHER; + } + print_stats(""); + if (rcoll.dio_incomplete_count) { + int fd; + char c; + + pr2serr(">> Direct IO requested but incomplete %d times\n", + rcoll.dio_incomplete_count); + if ((fd = open(proc_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + pr2serr(">>> %s set to '0' but should be set to '1' for " + "direct IO\n", proc_allow_dio); + } + close(fd); + } + } + if (rcoll.sum_of_resids) + pr2serr(">> Non-zero sum of residual counts=%d\n", + rcoll.sum_of_resids); + return (res >= 0) ? res : SG_LIB_CAT_OTHER; +} diff --git a/suse/sg3_utils.changes b/suse/sg3_utils.changes new file mode 100644 index 0000000..4279a29 --- /dev/null +++ b/suse/sg3_utils.changes @@ -0,0 +1,393 @@ +------------------------------------------------------------------- +Thu Jan 23 15:00:00 EST 2014 - dgilbert@interlog.com + +- import Suse build files into sg3_utils in the suse directory + * change suse spec file to be patch-less + * henceforth see ChangeLog in main directory + +------------------------------------------------------------------- +Thu Jan 23 08:57:56 CET 2014 - hare@suse.de + +- Update to inofficial release 1.38r546 + * sg_ses: error and warning message cleanup + - fix --data=- problem with large buffers + - new --data=@FN to read hex data from file FN + - add --maxlen= option + * sg_inq: + - add LU_CONG to standard inquiry response + - sync version descriptors dated 20131126 + - fix overflow in encode_whitespaces + * sg_vpd: add LU_CONG to standard inquiry response output + - decode Third Party Copy (tpc) page + * sg_persist: add PROUT: Replace Lost Reservation (spc4r36) + * sg_readcap: for --16 show physical block size if + * sg_xcopy: + - environment variables: XCOPY_TO_SRC and + XCOPY_TO_DST indicate where xcopy command is sent + - change default to send xcopy to dst (was src) + - improve CL handling of short options (e.g. '-vv') + * sg_write_same: repeat if unit attention + * sg_rtpg: fix indexing bug with --extended option + * sg_lib_data: sync asc/ascq codes with T10 dated 20131110 + * sg_cmds_extra: fix sa bug in sg_ll_3party_copy_out() +- Update tarball to 1.38b7r537 +- Add sg3_utils-1.38r546.patch + +------------------------------------------------------------------- +Mon Nov 4 01:59:38 UTC 2013 - jengelh@inai.de + +- Update to new upstream release 1.37 +* sg_compare_and_write: add --quiet option to suppress miscompare + report +* sg_persist: fix core dump on -Q option +* sg_unmap: fix core dump on -g option +* sg_ses: add --nickname and --nickid options +- Remove sg3_utils-Fixup-T10-Vendor-designator-display.patch + (merged upstream) + +------------------------------------------------------------------- +Sun Aug 25 18:45:14 CEST 2013 - ohering@suse.de + +- Fixup T10 Vendor designator display (bnc#805059) + sg3_utils-Fixup-T10-Vendor-designator-display.patch +- In rescan-scsi-bus.sh, check if the HBA driver exports issue_lip + in sysfs before using it (bnc#780946) + sg3_utils-check-if-hba-supports-issue-lip.patch + +------------------------------------------------------------------- +Thu Jun 13 14:15:26 UTC 2013 - jengelh@inai.de + +- Implement shlib packaging guidelines; rename sg3_utils-devel + to libsgutils-devel (upstream recommendation) +- More robust make install call; remove redundant %clean section; + simplify file lists + +------------------------------------------------------------------- +Tue Jun 11 08:56:39 UTC 2013 - rmilasan@suse.com + +- Update to version 1.36 + - sg_vpd: Protocol-specific port information VPD page + for SAS SSP, persistent connection (spl3r2), power + disable (spl3r3) + - block device characteristics: add FUAB bit + - sg_xcopy: handle more descriptor types; handle zero + maximum segment length; allow list IDs to be disabled; + improve skip/seek handling; allow xcopy on destination + - sg_reset: and --no-esc option to stop reset escalation + - clean up cli, add long option names + - sg_luns: add --test=ALUN option for decoding LUNs + - decoded luns output in decimal or hex (if -HH given) + - add '--linux' option to show Linux LUN after T10 + representation, can map one to the other + - sg_inq: add --vendor option to show standard inquiry's + vendor specific fields in ASCII + - take resid into account with response output + - sg_sync: add --16 (for 16 byte command) and --timeout= + - sg_logs: add data compression page (ssc4) + - sg_sat_set_features: increase --lba from 1 to 4 bytes + - sg_write_same: add --ndob option (sbc3r35d) + - sg_map: mark as deprecated + - sginfo: mark as deprecated, especially -l (list) + - sg_lib: improve snprintf handling + - sg_lib_data: sync asc/ascq codes with T10 20130117 + - sg_cmds (lib): if noisy given, give more UA info + - make code more C++ friendly + +------------------------------------------------------------------- +Tue Mar 12 09:13:45 CET 2013 - hare@suse.de + +- Update to version 1.35 + - sg_compare_and_write: new utility + - sg_inq+sg_vpd: block device characteristics VPD page: + add product_type, WABEREQ, WACEREQ and VBULS fields + - sg_inq: more --export option changes for udev + - sg_vpd: add more rdac vendor specific vpd pages + - sg_verify: add --ebytchk option for sbc3r34 changes + - sg_stpg: --offline option: fix 'Invalid state 0xe' + - sg_ses: Door Lock element changed to Door element and + abbreviation changed from 'dl' to 'do' (ses3r05) + - archive/rescan-scsi-bus.sh: upgrade to version 1.53hr + - move rescan-scsi-bus.sh to scripts directory + - sync to sbc3r34 + - sg_lib: sg_ll_verify10+16 expand BYTCHK to 2 bit field + - sg_pt_win32, sg_scan(win32): changes for cygwin 1.7.17 + - clean up man page summary lines + - sg_xcopy: new dd like utility for extended copy command + - sg_copy_results: new utility for receive copy results + - sg_verify: add 16 byte cdb, bytchk (data-out buffer) + and group number support + - sync to spc4r36 and sbc3r32 + - sg_inq: add --export so sg_inq can replace udev's scsi_id + - decode old EMC Symmetrix abuse of VPD page 0x83 + - sg_vpd: decode old EMC Symmetrix abuse of VPD page 0x83 + - sg_ses: increase max dpage response size to 64 KB + - allow ident,locate on enclosure controller + - more sanity for additional element status descriptor + - sg_sanitize: add --ause, --fail and --test= + - sg_luns: add long extended flat space addressing format + - sg_logs: add ATA pass-through results lpage (SAT-2) + - sg_rtpg: add --extended option + - sg_senddiag: list rebuild assist diag page name + - sg_pt_linux: expand DID_ (host_byte) codes + - cope with a transport error plus sense data + - prefer major() over MAJOR() macro + - sg_lib: fix sg_get_command_name() service actions + - report sdat_ovfl bit (if set) in sense data + - decode extended_copy and receive_copy service actions + - decode read_buffer and write_buffer modes + - decode ATA PT fixed format sense (SAT-2) + - sg_cmds_extra: add sg_ll_report_tgt_prt_grp2() + - ./configure options: + - change --enable-no-linux-bsg to --disable-linuxbsg + - add --disable-scsistrings to reduce utility sizes + +------------------------------------------------------------------- +Wed Jul 4 07:01:46 UTC 2012 - cfarrell@suse.com + +- license update: GPL-2.0+ and BSD-3-Clause + Show aggregation and make compatible with Fedora declaration + +------------------------------------------------------------------- +Sun Apr 22 11:50:44 UTC 2012 - puzel@suse.com + +- Update to version 1.33 + - sg_ses: major rework of indexes (again), now two level + - sg_write_buffer: new --specific option for mode specific + field; new mode 13 (spc4r32) + - sg_vpd: add hp3par volume info vendor VPD page + - fix 'scsi ports' [0x88] page problem + - add 'sinq' pseudo page for standard inquiry response + - add power consumption page + - sg_format: add --poll= option for request sense polling + - improve handling of disks > 2 TB and DIF (protection) + - sg_logs: LB provision lpage extra (sbc3r28) + - sg_modes: application tag mpage subcode 0xf0->0x2 + - sg_write_same: no prot fields when wrprotect=0 + - sg_get_lba_status: reflect change in sbc3r25 to Parameter + Data Length response field (offset reduced from 8 to 4) + - sg_inq, sg_vpd: sync with spc4r33 + - win32: change DataBufferOffset type per MSDN; caused + problem with 64 bit machines (with buffered interface) + - sg_luns: tweak documentation for vendor specific reports + - add man pages for scsi_loging_level, scsi_mandat, + scsi_satl and scsi_temperature + +------------------------------------------------------------------- +Mon Jan 16 19:59:42 UTC 2012 - tabraham@novell.com + +- Update to version 1.32 + + sg_sanitize: new utility for command added in sb3r27 + + sg_sat_identify: add '--ident' to output WWN + + sg_ses: major rework of descriptor output + + add --index, --descriptor, --join, --clear, --get, and --set + options + + sg_raw: exit status corrections + + sg_decode_sense: add --nospace and --hex options + + sg_logs: fix bug with large --maxlen + + zero response length when resid implies it is invalid + + add scope field to lb provisioning lpage (sb3r27) + + sg_inq: sync version descriptors with spc4r31 + + sb_lib_data: sync asc/ascq codes with spc4r31 + + sg_vpd: add LBPRZ field in LP provisioning VPD page + + sg_format: allow format of pdt 7 (some MO drives) + + sg_cmd_basic: sg_cmds_process_resp() handle status good + with a sense key other than no_sense (e.g. completed) + + add README.iscsi + +- Updated rescan-scsi-bus.sh to v1.56 + +------------------------------------------------------------------- +Thu Mar 10 08:47:43 UTC 2011 - coolo@novell.com + +- fix file list + +------------------------------------------------------------------- +Fri Feb 18 16:41:32 CET 2011 - hare@suse.de + +- Update to version 1.31: + + sg_decode_sense: new utility to decode sense data + + sg_vpd: LB provisioning + Block limits pages (sbc3r26) + + sync asc/ascq and version descriptors with spc4r28 + + sg_get_config, sg_rmsn, sg_verify: add --readonly option + + sg_lib: implement forwarded sense data descriptor + - decode user data segment referral sense data descriptor + + sg_lib, sg_turs, sg_format: more precision for progress + indication (two places after decimal point) + + sg_lib(win32): add runtime selection of SPT direct or + indirect interface + - sg_read_buffer+sg_write_buffer: set SPT direct + + add examples/forwarded_sense.txt + examples/ref_sense.txt + +- Changes from version 1.30: + + sg_referrals: new utility for REPORT REFERRALS + + sbc3r25 renames 'thin' provisioning' to 'logical block + provisioning': changes in sg_format, sg_inq, sg_logs, + sg_modes, sg_readcap, sg_vpd + + sg_inq: update version descriptor list to spc4r27 + - extended inquiry vpd page add extended self test + completion minutes field + + sg_lib: sync asc/ascq list to spc4r27 + - dStrHex(): trim excess trailing spaces + + sg_read_long: add --readonly option (open() is rw) + + sg_raw: add --readonly option (open() is rw) + - allow bidirectional commands + + sg_vpd: rdac vendor page [0xc8] parse corrections + - extended inquiry vpd page add extended self test + -completion minutes field + + sg_ses: expand --data (in) buffer to 2048 bytes + + sg_opcodes: add extended parameter data for TMFs (spc4r26) + + sg_dd: clean count calculation, document nocache flag + - treat bsg devices as implicit sg_io + + sg_write_same: if READ CAPACITY(16) fails try 10 byte variant + - anticipate approval of proposal to allow UNMAP and ANCHOR + bits to be set on WRITE SAME(10) with '--10' option + + sg3_utils man page: sections added for OS device names + +------------------------------------------------------------------- +Fri Aug 13 11:42:50 CEST 2010 - dimstar@opensuse.org + +- Update to version 1.29: + + sg_rtpg: new logical block dependent state and bit (spc4r23) + + sg_start: add '--readonly' option for ATA disks + + sg_lib: update asc/ascq list to spc4r23 + + sg_inq: update version descriptor list to spc4r23 + + sg_vpd: block device characteristics page: fix form factor + - update Extended Inquiry VPD page to spc4r23 + - update Block Limits VPD page to sbc3r22 + - update Thin Provisioning VPD page to sbc3r22 + - Automation device serial number and Data transfer device + element VPD pages (ssc4r01) + - add Referrals VPD page (sbc3r22) + + sg_logs: add thin provisioning and solid state media log pages + - addition of IBM LTO specific log pages + + sg_modes: new page names from ssc4r01 + + sg_ses: sync with ses3r02 (SAS-2.1 connector types) + + sg_unmap: add '--anchor' option (sbc3r22) + + sg_write_same: add '--anchor' option (sbc3r22) + + sg_pt interface: add set_scsi_pt_flags() to permit passing + through SCSI_PT_FLAGS_QUEUE_AT_TAIL and AT_HEAD flags + + add examples/sg_queue_tst+bsg_queue_tst for SG_FLAG_Q_AT_TAIL + + add AM_MAINTAINER_MODE to configure.ac to lessen build issues + + add BSD_LICENSE file to this and lib directories, refer to + it from source and header files. Some source has GPL license +- Changes from version 1.28: + + sg_unmap: new utility for thin provisioning + - add examples/sg_unmap_example.txt + + sg_get_lba_status: new utility for thin provisioning + + sg_read_block_limits: new utility for tape drives + + sg_logs: add cache memory statistics log (sub)page + + sg_vpd, sg_inq: extend Block limits VPD page (sbc3r19) + + sg_vpd: add Thin provisioning VPD page (sbc3r20) and + TapeAlert supported flags VPD page + + sg_inq: note VPD page support better in sg_vpd + + sg_persist: add transport specific transportID format + - allow transportIDs to be read from named file + + sg_opcodes: allow --opcode= option to take OP and SA + values (comma seperated) + - tweak print format, remove test code + + sg_requests: remove test code in progress calculation + + sg_reset: add target reset option + + sg_luns: reduce default maxlen to 8192 (for FreeBSD) + + sg_raw: extend max cdb length from 16 to 256 bytes + - align heap allocs to page boundaries + + sg_lib: sg_set_binary_mode() needs config.h included + - add progress indication sense data descriptor (0xa) + - change SG3_UTILS_* constants to SG_LIB_* + - decode service actions within persistent reserve in/out + - sync with spc4r21 + + sg_cmds_extra: add sg_ll_unmap() and sg_ll_get_lba_status() + + sg_pt_linux: fix check condition but empty sense buffer; + - major() macro grief, if present include and + use MAJOR() instead + + scripts/sas_disk_blink: moved from this package to sdparm + + utils/hxascdmp: in Windows set binary mode on read files + + examples/sg_persist_tst.sh: add PRIN read full status command + + sg_raw,sg_write_buffer,sg_write_long,sg_write_same: in Windows + set binary mode on read files + + sg_pt_win32: default to non-direct variant of SPT interface + - use './configure --enable-win32-spt-direct' to override + - non-direct data length set to 16 KB, extended if required + + debian: incorporate patch from debian sid + +------------------------------------------------------------------- +Mon Jun 28 06:38:35 UTC 2010 - jengelh@medozas.de + +- use %_smp_mflags + +------------------------------------------------------------------- +Tue Jul 21 14:00:16 CEST 2009 - hare@suse.de + +- Clean up spec file and remove obsolete cruft + +------------------------------------------------------------------- +Fri Apr 17 20:15:58 CEST 2009 - crrodriguez@suse.de + +- remove static libraries and "la" files + +------------------------------------------------------------------- +Mon Jan 26 15:30:31 CET 2009 - hare@suse.de + +- Fixes to rescan-scsi-bus.sh: + * Implement '--forcerescan' to force a rescan of existing devices + * Handle LUN changes correctly + * Check variables before evaluation + +------------------------------------------------------------------- +Wed Oct 29 11:05:47 CET 2008 - garloff@suse.de + +- rescan-scsi-bus.sh 1.29: + * Fix error in script (returning "" does not work) + * Support systems without /proc/scsi +- Don't install INSTALL + +------------------------------------------------------------------- +Tue Sep 30 14:11:15 CEST 2008 - hare@suse.de + +- Add %insserv_prereq (bnc#423204) + +------------------------------------------------------------------- +Fri Sep 12 20:29:08 CEST 2008 - garloff@suse.de + +- Update rescan-scsi-bus.sh script to 1.28: + * Merge fixes from Hannes + * Minor cleanups + * Sort hosts numerically + +------------------------------------------------------------------- +Tue Aug 12 18:25:43 CEST 2008 - garloff@suse.de + +- Update to sg3_utils-1.27: + * Adapted to linux-2.6.26 (sg_map26) + * sg_dd uses flock (rw -- if that fails ro) + * sg_get_config: OSSC feature (mmc6r02) +- Update to sg3_utils-1.26: + * Minor fixes and enhancements to + sg_sat_phy_event, sg_ses, sg_get_config, sg_verify, sg_vpd, + sg_inq, sg_modes, sg_start, sg_request, sg_luns, sg_dd, + sg_opcodes, sg_turs. + * sg_lib: asc/ascq update for spc4r15, osd2r03 service actions, + sense key specific unit attn queue overflow decoding, ... + * Restructuring: sg_lib -> sg_lib_data, sg_inq_data, (u)int64_t, + sg_io_linux -> lib/. + * Documentation enhancements. + +------------------------------------------------------------------- +Wed Jul 16 09:55:33 CEST 2008 - hare@suse.de + +- Use correct length parameter for sg_inq (bnc#363438) + +------------------------------------------------------------------- +Fri May 23 10:22:31 CEST 2008 - hare@suse.de + +- Use 'Provides' to clean update dependency + +------------------------------------------------------------------- +Fri May 9 17:31:33 CEST 2008 - schwab@suse.de + +- Use autoreconf -i. + +------------------------------------------------------------------- +Thu Apr 24 14:14:14 CEST 2008 - hare@suse.de + +- Split off from original scsi package. + diff --git a/suse/sg3_utils.spec b/suse/sg3_utils.spec new file mode 100644 index 0000000..2606139 --- /dev/null +++ b/suse/sg3_utils.spec @@ -0,0 +1,125 @@ +# +# spec file for package sg3_utils +# +# Copyright (c) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# +# +# No patches, this is the maintainer's version for Suse targets. +# Patch lines would appear after the "Source:" line and look like: +# Patch1: sg3_utils-1.38r546.patch +# then under the "%setup -q" line there would be one or more lines: +# %patch1 -p1 + + +Name: sg3_utils +%define lname libsgutils2-2 +Version: 1.41 +Release: 0 +Summary: A collection of tools that send SCSI commands to devices +License: GPL-2.0+ and BSD-3-Clause +Group: Hardware/Other +Url: http://sg.danny.cz/sg/sg3_utils.html + +Source: http://sg.danny.cz/sg/p/%name-%{version}.tar.xz +BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildRequires: xz +Requires(pre): %insserv_prereq +Provides: scsi +Provides: sg_utils +Obsoletes: scsi <= 1.7_2.38_1.25_0.19_1.02_0.93 + +%description +The sg3_utils package contains utilities that send SCSI commands to +devices. As well as devices on transports traditionally associated with +SCSI (e.g. Fibre Channel (FCP), Serial Attached SCSI (SAS) and the SCSI +Parallel Interface(SPI)) many other devices use SCSI command sets. +ATAPI cd/dvd drives and SATA disks that connect via a translation layer +or a bridge device are examples of devices that use SCSI command sets. + +%package -n %lname +Summary: Library to hold functions common to the SCSI utilities +License: BSD-3-Clause +Group: System/Libraries + +%description -n %lname +The sg3_utils package contains utilities that send SCSI commands to +devices. As well as devices on transports traditionally associated with +SCSI (e.g. Fibre Channel (FCP), Serial Attached SCSI (SAS) and the SCSI +Parallel Interface(SPI)) many other devices use SCSI command sets. +ATAPI cd/dvd drives and SATA disks that connect via a translation layer +or a bridge device are examples of devices that use SCSI command sets. + +This subpackage contains the library of common sg_utils code, such as +SCSI error processing. + +%package -n libsgutils-devel +Summary: A collection of tools that send SCSI commands to devices +License: BSD-3-Clause +Group: Development/Libraries/C and C++ +Requires: %lname = %version +# Added for 13.1 +Obsoletes: %name-devel < %version-%release +Provides: %name-devel = %version-%release + +%description -n libsgutils-devel +The sg3_utils package contains utilities that send SCSI commands to +devices. As well as devices on transports traditionally associated with +SCSI (e.g. Fibre Channel (FCP), Serial Attached SCSI (SAS) and the SCSI +Parallel Interface(SPI)) many other devices use SCSI command sets. +ATAPI cd/dvd drives and SATA disks that connect via a translation layer +or a bridge device are examples of devices that use SCSI command sets. + +This subpackage contains libraries and header files for developing +applications that want to make use of libsgutils. + +%prep +%setup -q + +%build +%configure --disable-static --with-pic +make %{?_smp_mflags} + +%install +make install DESTDIR="%buildroot" +install -m 755 scripts/scsi_logging_level $RPM_BUILD_ROOT%{_bindir} +install -m 755 scripts/rescan-scsi-bus.sh $RPM_BUILD_ROOT%{_bindir} +%{__rm} -f %{buildroot}%{_libdir}/*.la + +%post -p /sbin/ldconfig -n %lname + +%postun -p /sbin/ldconfig -n %lname + +%files +%defattr(-,root,root) +%doc README README.sg_start +%doc ChangeLog CREDITS NEWS +%_bindir/sg_* +%_bindir/scsi_* +%_bindir/sginfo +%_bindir/sgp_dd +%_bindir/sgm_dd +%_bindir/scsi_logging_level +%_bindir/rescan-scsi-bus.sh +%_mandir/man8/*.8* + +%files -n %lname +%defattr(-,root,root) +%_libdir/libsgutils2.so.2* + +%files -n libsgutils-devel +%defattr(-,root,root) +%_libdir/libsgutils2.so +%_includedir/scsi/ + +%changelog diff --git a/testing/Makefile.cplus b/testing/Makefile.cplus new file mode 100644 index 0000000..96fcf5c --- /dev/null +++ b/testing/Makefile.cplus @@ -0,0 +1,85 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +CC = g++ +LD = g++ +## CC = clang++ +## LD = clang++ + +EXECS = sg_tst_excl sg_tst_excl2 sg_tst_excl3 sg_tst_context sg_tst_async + +EXTRAS = + +BSG_EXTRAS = + + +MAN_PGS = +MAN_PREF = man8 + +LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 + +CPPFLAGS = -std=c++11 -pthread -g -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) +# CPPFLAGS = -std=c++11 -pthread -g -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) -DDEBUG +## CFLAGS = -g -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) +# CFLAGS = -g -O2 -Wall -iquote ../include -D_REENTRANT -DSG_KERNEL_INCLUDES $(LARGE_FILE_FLAGS) +# CFLAGS = -g -O2 -Wall -pedantic -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) + +LDFLAGS = -std=c++11 -pthread + +LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_io_linux.o +LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_pt_linux.o ../lib/sg_pt_common.o \ + ../lib/sg_pt_linux_nvme.o ../lib/sg_io_linux.o ../lib/sg_cmds_basic.o + +all: $(EXECS) + +extras: $(EXTRAS) + + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) core .depend + +sg_tst_excl: sg_tst_excl.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_tst_excl2: sg_tst_excl2.o $(LIBFILESNEW) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_tst_excl3: sg_tst_excl3.o $(LIBFILESNEW) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_tst_context: sg_tst_context.o $(LIBFILESNEW) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_tst_async: sg_tst_async.o $(LIBFILESNEW) + $(LD) -o $@ $(LDFLAGS) $^ + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/testing/Makefile.cplus_fb b/testing/Makefile.cplus_fb new file mode 100644 index 0000000..a41c234 --- /dev/null +++ b/testing/Makefile.cplus_fb @@ -0,0 +1,79 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +## CC = g++ +## LD = g++ +## CC = clang++ +## LD = clang++ + +## This is just an example FreeBSD Makefile for how to build for clang++ +## All .cpp execs in this directory are Linux specific so there is +## nothing to do for FreeBSD. +## Not optimum but it works around 20180715 [dpg] + +# EXECS = sg_tst_context +EXECS = + +EXTRAS = + +BSG_EXTRAS = + + +MAN_PGS = +MAN_PREF = man8 + +LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 + +CXXFLAGS = -std=c++11 -pthread -g -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) +# CPPFLAGS = -std=c++11 -pthread -g -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) -DDEBUG +## CFLAGS = -g -O2 -W -Wall -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) +# CFLAGS = -g -O2 -Wall -iquote ../include -D_REENTRANT -DSG_KERNEL_INCLUDES $(LARGE_FILE_FLAGS) +# CFLAGS = -g -O2 -Wall -pedantic -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) + +LDFLAGS = -std=c++11 -pthread + +LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o +LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_pt_freebsd.o ../lib/sg_pt_common.o \ + ../lib/sg_cmds_basic.o + +all: $(EXECS) + +extras: $(EXTRAS) + + +depend dep: + for i in $(EXECS).cpp; do $(CXX) $(INCLUDES) $(CXXFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) core .depend + +sg_tst_context: sg_tst_async.o $(LIBFILESNEW) + $(CXX) -o $@ $(LDFLAGS) $^ + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +## ifeq (.depend,$(wildcard .depend)) +## include .depend +## endif diff --git a/testing/Makefile.cyg b/testing/Makefile.cyg new file mode 100644 index 0000000..058f837 --- /dev/null +++ b/testing/Makefile.cyg @@ -0,0 +1,110 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?) +# the default C compiler is clang. Swap the comment marks (lines starting +# with '#') on the next 4 (non-blank) lines. +CC = gcc +# CC = clang + +LD = gcc +# LD = clang + +EXECS = sg_sense_test sg_chk_asc sg_tst_nvme tst_sg_lib + +EXTRAS = + +BSG_EXTRAS = + + +MAN_PGS = +MAN_PREF = man8 + +LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 + +# For C++/clang testing +## CC = gcc +## CC = g++ +## CC = clang +## CC = clang++ + +CPPFLAGS = -iquote ../include -iquote .. -D_REENTRANT $(LARGE_FILE_FLAGS) -DHAVE_CONFIG_H -DHAVE_NVME +CFLAGS = -g -O2 -W -Wall +# CFLAGS = -g -O2 -Wall -DSG_KERNEL_INCLUDES +# CFLAGS = -g -O2 -Wall -pedantic +# CFLAGS = -Wall -W -pedantic -std=c11 --analyze +# CFLAGS = -Wall -W -pedantic -std=c++14 -fPIC + +LDFLAGS = + +LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o +LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o \ + ../lib/sg_pt_win32.o ../lib/sg_pt_common.o ../lib/sg_cmds_basic.o + +all: $(EXECS) + +extras: $(EXTRAS) + +bsg: $(BSG_EXTRAS) + + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) core .depend + +sg_iovec_tst: sg_iovec_tst.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_sense_test: sg_sense_test.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_queue_tst: sg_queue_tst.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +bsg_queue_tst: bsg_queue_tst.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +# building sg_chk_asc depends on a prior successful make in ../lib +sg_chk_asc: sg_chk_asc.o ../lib/sg_lib.o ../lib/sg_lib_data.o + $(LD) -o $@ $(LDFLAGS) $^ + +sg_tst_nvme: sg_tst_nvme.o $(LIBFILESNEW) + $(LD) -o $@ $(LDFLAGS) $^ + +tst_sg_lib: tst_sg_lib.o ../lib/sg_lib.o ../lib/sg_lib_data.o + $(LD) -o $@ $(LDFLAGS) $^ + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +# Linux uses GNU make and FreeBSD uses Berkely make. The following lines +# only work in Linux. Possible solutions in FreeBSD: +# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq' +# c) build with 'make -f Makefile.freebsd' +# In Linux one can install bmake (but that won't help here). +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/testing/Makefile.freebsd b/testing/Makefile.freebsd new file mode 100644 index 0000000..6cfe7d8 --- /dev/null +++ b/testing/Makefile.freebsd @@ -0,0 +1,96 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?) +# the default C compiler is clang. Swap the comment marks (lines starting +# with '#') on the next 4 (non-blank) lines. +# CC = gcc +# CC = clang + +# LD = gcc +# LD = clang + +EXECS = sg_sense_test sg_chk_asc sg_tst_nvme tst_sg_lib + +EXTRAS = + +MAN_PGS = +MAN_PREF = man8 + +OS_FLAGS = -DSG_LIB_FREEBSD -DHAVE_NVME +EXTRA_FLAGS = $(OS_FLAGS) + +# For C++/clang testing +## CC = gcc +## CC = g++ +## CC = clang +## CC = clang++ + +# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) -I ../include +CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) -I ../include +# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) -I ../include + +CFLAGS_PTHREADS = -D_REENTRANT + +# there is no rule to make the following in the parent directory, +# it is assumed they are already built. +D_FILES = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_cmds_basic.o ../lib/sg_pt_common.o ../lib/sg_pt_freebsd.o + +LDFLAGS = -lcam + +all: $(EXECS) + +extras: $(EXTRAS) + + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) core .depend + +sg_sense_test: sg_sense_test.o $(D_FILES) + $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES) + +# building sg_chk_asc depends on a prior successful make in ../lib +sg_chk_asc: sg_chk_asc.o $(D_FILES) + $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES) + +sg_tst_nvme: sg_tst_nvme.o $(D_FILES) + $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES) + +tst_sg_lib: tst_sg_lib.o $(D_FILES) + $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES) + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +# Linux uses GNU make and FreeBSD uses Berkely make. The following lines +# only work in Linux. Possible solutions in FreeBSD: +# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq' +# c) build with 'make -f Makefile.freebsd' +# In Linux one can install bmake (but that won't help here). +# ifeq (.depend,$(wildcard .depend)) +# include .depend +# endif diff --git a/testing/README b/testing/README new file mode 100644 index 0000000..7ae56f6 --- /dev/null +++ b/testing/README @@ -0,0 +1,29 @@ +Building files in this directory depends on several files being already +built in the ../lib directory. So to build files here, the ./configure +needs to be executed in the parent directory followed by changing +directory to the lib directory and calling 'make' there. +Another way is to do a top level 'make' after the ./configure which +will make the libraries followed by all the utilities in the src/ +directory. To make them in FreeBSD use 'make -f Makefile.freebsd' . + +There is an brief explanation of each example in the README file in +the main (i.e. this directory's parent) directory. There are also +some notes at the top of each source file. + +The sg_chk_asc utility decodes the SCSI additional sense code table +found at http://www.t10.org/lists/asc-num.txt and checks it against +the table found in sg_lib_data.c in the lib/ subdirectory. It is +designed to keep the table in sg_lib_data.c in "sync" with the +table at the t10.org web site. + +The tst_sg_lib utility exercises several functions found in sg_lib.c +and related files in the 'lib' sibling directory. Use 'tst_sg_lib -h' +to get more information. + +Those files with the extension "cpp" are C++ examples that use facilities +in C++11. They can be built by calling 'make -f Makefile.cplus'. A +gcc/g++ compiler of 4.7.3 vintage or later (or a recent clang compiler) +will be required. + +Douglas Gilbert +19th January 2018 diff --git a/testing/bsg_queue_tst.c b/testing/bsg_queue_tst.c new file mode 100644 index 0000000..af0c42b --- /dev/null +++ b/testing/bsg_queue_tst.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* If the following fails the Linux kernel is probably too old */ +#include + + +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_linux_inc.h" + +/* This program was used to test SCSI mid level queue ordering. + The default behaviour is to "queue at head" which is useful for + error processing but not for streaming READ and WRITE commands. + +* Copyright (C) 2010-2016 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: bsg_queue_tst [-t] + -t queue at tail + + Version 0.90 (20100324) + +*/ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define SDIAG_CMD_LEN 6 +#define SENSE_BUFFER_LEN 96 + +#define EBUFF_SZ 256 + +#ifndef BSG_FLAG_Q_AT_TAIL +#define BSG_FLAG_Q_AT_TAIL 0x10 +#endif + +#ifndef BSG_FLAG_Q_AT_HEAD +#define BSG_FLAG_Q_AT_HEAD 0x20 +#endif + + +int main(int argc, char * argv[]) +{ + int bsg_fd, k, ok; + uint8_t inq_cdb[INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t sdiag_cdb[SDIAG_CMD_LEN] = + {0x1d, 0, 0, 0, 0, 0}; + uint8_t inqBuff[16][INQ_REPLY_LEN]; + struct sg_io_v4 io_hdr[16]; + struct sg_io_v4 rio_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[16][SENSE_BUFFER_LEN]; + int q_at_tail = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-t", argv[k], 2)) + ++q_at_tail; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'bsg_queue_tst [-t] '\n" + "where:\n -t queue_at_tail (def: q_at_head)\n"); + return 1; + } + + /* An access mode of O_RDWR is required for write()/read() interface */ + if ((bsg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "bsg_queue_tst: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + for (k = 0; k < 16; ++k) { + /* Prepare INQUIRY command */ + memset(&io_hdr[k], 0, sizeof(struct sg_io_v4)); + io_hdr[k].guard = 'Q'; + /* io_hdr[k].iovec_count = 0; */ /* memset takes care of this */ + if (0 == (k % 3)) { + io_hdr[k].request_len = sizeof(sdiag_cdb); + io_hdr[k].request = (uint64_t)(long)sdiag_cdb; + } else { + io_hdr[k].request_len = sizeof(inq_cdb); + io_hdr[k].request = (uint64_t)(long)inq_cdb; + io_hdr[k].din_xfer_len = INQ_REPLY_LEN; + io_hdr[k].din_xferp = (uint64_t)(long)inqBuff[k]; + } + io_hdr[k].response = (uint64_t)(long)sense_buffer[k]; + io_hdr[k].max_response_len = SENSE_BUFFER_LEN; + io_hdr[k].timeout = 20000; /* 20000 millisecs == 20 seconds */ + io_hdr[k].usr_ptr = k; + /* default is to queue at head (in SCSI mid level) */ + if (q_at_tail) + io_hdr[k].flags |= BSG_FLAG_Q_AT_TAIL; + else + io_hdr[k].flags |= BSG_FLAG_Q_AT_HEAD; + + if (write(bsg_fd, &io_hdr[k], sizeof(struct sg_io_v4)) < 0) { + perror("bsg_queue_tst: bsg write error"); + close(bsg_fd); + return 1; + } + } + /* sleep(3); */ + for (k = 0; k < 16; ++k) { + memset(&rio_hdr, 0, sizeof(struct sg_io_v4)); + rio_hdr.guard = 'Q'; + if (read(bsg_fd, &rio_hdr, sizeof(struct sg_io_v4)) < 0) { + perror("bsg_queue_tst: bsg read error"); + close(bsg_fd); + return 1; + } + /* now for the error processing */ + ok = 0; + if (0 == rio_hdr.device_status) + ok = 1; + else { + switch (sg_err_category_sense((uint8_t *)(long) + rio_hdr.response, rio_hdr.response_len)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + fprintf(stderr, "command error:\n"); + sg_print_sense(NULL, (uint8_t *)(long)rio_hdr.response, + rio_hdr.response_len, 1); + break; + } + } + + if (ok) { /* output result if it is available */ + /* if (0 == rio_hdr.pack_id) */ + if (0 == (rio_hdr.usr_ptr % 3)) + printf("SEND DIAGNOSTIC %d duration=%u\n", + (int)rio_hdr.usr_ptr, rio_hdr.duration); + else + printf("INQUIRY %d duration=%u\n", (int)rio_hdr.usr_ptr, + rio_hdr.duration); + } + } + + close(bsg_fd); + return 0; +} diff --git a/testing/sg_chk_asc.c b/testing/sg_chk_asc.c new file mode 100644 index 0000000..ef37729 --- /dev/null +++ b/testing/sg_chk_asc.c @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2006-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sg_lib.h" + +/* A utility program for the Linux OS SCSI subsystem. + * + * This program takes a asc_ascq.txt file from www.t10.org and + * checks it against the additional sense codes held in the + * sg_lib.c file. + * The online version of the asc_ascq codes can be found at: + * http://www.t10.org/lists/asc-num.txt + */ + +static const char * version_str = "1.06 20180119"; + + +#define MAX_LINE_LEN 1024 + + +static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"verbose", 0, 0, 'v'}, + {"version", 0, 0, 'V'}, + {0, 0, 0, 0}, +}; + +static void usage() +{ + fprintf(stderr, "Usage: " + "sg_chk_asc [--help] [--offset=POS] [--verbose] [--version]\n" + " \n" + " where:\n" + " --help|-h print out usage message\n" + " --offset=POS|-o POS line position in file where " + "text starts\n" + " origin 0 (def: 24 (was 25))\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Checks asc/ascq codes in against the sg3_utils " + "library.\nThe additional sense code (asc_ascq) can be found at\n" + "www.t10.org/lists/asc-num.txt .\n" + ); + +} + +int main(int argc, char * argv[]) +{ + int k, j, res, c, num, len; + unsigned int asc, ascq; + FILE * fp; + int offset = 24; + int verbose = 0; + char file_name[256]; + char line[MAX_LINE_LEN]; + char b[MAX_LINE_LEN]; + char bb[MAX_LINE_LEN]; + char * cp; + int ret = 1; + + memset(file_name, 0, sizeof file_name); + memset(line, 0, sizeof file_name); + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "ho:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + usage(); + return 0; + case 'o': + offset = sg_get_num(optarg); + if (offset < 0) { + fprintf(stderr, "bad argument to --offset\n"); + return 1; + } + break; + case 'v': + ++verbose; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + return 0; + default: + fprintf(stderr, "unrecognised switch code 0x%x ??\n", c); + usage(); + return 1; + } + } + if (optind < argc) { + if ('\0' == file_name[0]) { + strncpy(file_name, argv[optind], sizeof(file_name) - 1); + file_name[sizeof(file_name) - 1] = '\0'; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return 1; + } + } + + if (0 == file_name[0]) { + fprintf(stderr, "missing file name!\n"); + usage(); + return 1; + } + fp = fopen(file_name, "r"); + if (NULL == fp) { + fprintf(stderr, "open error: %s: %s\n", file_name, + safe_strerror(errno)); + return 1; + } + for (k = 0; (cp = fgets(line, sizeof(line) - 1, fp)); ++k) { + len = strlen(line); + if (len < 1) + continue; + if (! isdigit(line[0])) + continue; + num = sscanf(line, "%xh/%xh", &asc, &ascq); + if (1 == num) + ascq = 999; + if (num < 1) { + if (verbose) + fprintf(stderr, "Badly formed line number %d (num=%d)\n", + k + 1, num); + continue; + } + if (len < 26) + continue; +#if 0 +strncpy(b , line, sizeof(b) - 1); +b[sizeof(b) - 1] = '\0'; +num = strlen(b); +if (0xd == b[num - 2]) { + b[num - 2] = '\0'; + b[num - 1] = '\0'; +} +printf("\"%s\",\n", b); +#endif + strncpy(b , line + offset, sizeof(b) - 1); + b[sizeof(b) - 1] = '\0'; + num = strlen(b); + if (0xd == b[num - 2]) { + b[num - 2] = '\0'; + b[num - 1] = '\0'; + } + num = strlen(b); + for (j = 0; j < num; ++j) + b[j] = toupper(b[j]); + + bb[0] = '\0'; + if (ascq < 999) { + cp = sg_get_asc_ascq_str(asc, ascq, sizeof(bb) - 1, bb); + if (NULL == cp) { + fprintf(stderr, "no entry for %x,%x : %s\n", asc, ascq, b); + continue; + } + num = strlen(cp); +// fprintf(stderr, "file: asc=%x acsq=%x strlen=%d %s\n", asc, ascq, num, +// cp); +// if (num < 20) +// continue; + if ((num > 6) && + ((0 == memcmp("ASC", cp, 3)) || + (0 == memcmp("vendor", cp, 6)))) { + fprintf(stderr, "%x,%x differ, ref: %s, sg_lib_data: " + "\n", asc, ascq, b); + continue; + } + if (num > 20) { + cp += 18; + num -= 18; + for (j = 0; j < num; ++j) + cp[j] = toupper(cp[j]); + } + if (0 != strcmp(b, cp)) + fprintf(stderr, "%x,%x differ, ref: %s, sg_lib_data: " + "%s\n", asc, ascq, b, cp); + } + } + if (NULL == cp) { + if (feof(fp)) { + if (verbose > 2) + fprintf(stderr, "EOF detected\n"); + } else + fprintf(stderr, "fgets: %s\n", safe_strerror(errno)); + } else + fprintf(stderr, "%s\n", line); + + res = fclose(fp); + if (EOF == res) { + fprintf(stderr, "close error: %s\n", safe_strerror(errno)); + return 1; + } + return ret; +} diff --git a/testing/sg_iovec_tst.c b/testing/sg_iovec_tst.c new file mode 100644 index 0000000..a968e38 --- /dev/null +++ b/testing/sg_iovec_tst.c @@ -0,0 +1,267 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_unaligned.h" + +/* Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg") + device driver. +* Copyright (C) 2003-2018 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + This program will read a certain number of blocks of a given block size + from a given sg device node and write what is retrieved out to a + normal file. The purpose is to test the sg_iovec mechanism within the + sg_io_hdr structure. + + Version 0.16 (20180219) +*/ + + +#define ME "sg_iovec_tst: " + +#define A_PRIME 509 +#define IOVEC_ELEMS 2048 + +#define SENSE_BUFF_LEN 32 +#define DEF_TIMEOUT 40000 /* 40,000 milliseconds */ + +struct sg_iovec iovec[IOVEC_ELEMS]; + + +static void +usage(void) +{ + printf("Usage: sg_iovec_tst [-a] [-b=bs] -c=num [-e=es] [-h]\n" + " \n"); + printf(" where: -a async sg use (def: use ioctl(SGIO) )\n"); + printf(" -b=bs block size (default 512 Bytes)\n"); + printf(" -c=num count of blocks to transfer\n"); + printf(" -e=es iovec element size (def: 509)\n"); + printf(" -h this usage message\n"); + printf(" reads from and sends to " + "\nUses iovec (a scatter list) in linear " + "mode\n"); +} + +/* Returns 0 if everything ok */ +static int sg_read(int sg_fd, uint8_t * buff, int num_blocks, + int from_block, int bs, int elem_size, int async) +{ + uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t senseBuff[SENSE_BUFF_LEN]; + struct sg_io_hdr io_hdr; + struct pollfd a_poll; + int dxfer_len = bs * num_blocks; + int k, pos, rem, res; + + sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2); + sg_put_unaligned_be16((uint16_t)from_block, rdCmd + 7); + + for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) { + iovec[k].iov_base = buff + pos; + iovec[k].iov_len = (rem > elem_size) ? elem_size : rem; + if (rem <= elem_size) + break; + pos += elem_size; + rem -= elem_size; + } + if (k >= IOVEC_ELEMS) { + fprintf(stderr, "Can't fit dxfer_len=%d bytes in iovec\n", dxfer_len); + return -1; + } + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(rdCmd); + io_hdr.cmdp = rdCmd; + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = dxfer_len; + io_hdr.iovec_count = k + 1; + io_hdr.dxferp = iovec; + io_hdr.mx_sb_len = SENSE_BUFF_LEN; + io_hdr.sbp = senseBuff; + io_hdr.timeout = DEF_TIMEOUT; + io_hdr.pack_id = from_block; + + if (async) { + res = write(sg_fd, &io_hdr, sizeof(io_hdr)); + if (res < 0) { + perror("write(), error"); + return -1; + } else if (res < (int)sizeof(io_hdr)) { + fprintf(stderr, "write() returned %d, expected %d\n", + res, (int)sizeof(io_hdr)); + return -1; + } + a_poll.fd = sg_fd; + a_poll.events = POLLIN; + a_poll.revents = 0; + res = poll(&a_poll, 1, 2000 /* millisecs */ ); + if (res < 0) { + perror("poll error on "); + return -1; + } + if (0 == (POLLIN & a_poll.revents)) { + fprintf(stderr, "strange, poll() completed without data to " + "read\n"); + return -1; + } + res = read(sg_fd, &io_hdr, sizeof(io_hdr)); + if (res < 0) { + perror("read(), error"); + return -1; + } else if (res < (int)sizeof(io_hdr)) { + fprintf(stderr, "read() returned %d, expected %d\n", + res, (int)sizeof(io_hdr)); + return -1; + } + } else if (ioctl(sg_fd, SG_IO, &io_hdr)) { + perror("reading (SG_IO) on sg device, error"); + return -1; + } + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: + fprintf(stderr, "Recovered error while reading block=%d, num=%d\n", + from_block, num_blocks); + break; + case SG_LIB_CAT_UNIT_ATTENTION: + fprintf(stderr, "Unit attention\n"); + return -1; + default: + sg_chk_n_print3("reading", &io_hdr, 1); + return -1; + } + return 0; +} + + +int main(int argc, char * argv[]) +{ + int sg_fd, fd, res, j, m, dxfer_len; + unsigned int k, num; + int do_async = 0; + int do_help = 0; + int blk_size = 512; + int elem_size = A_PRIME; + int count = 0; + char * sg_file_name = 0; + char * out_file_name = 0; + uint8_t * buffp; + + for (j = 1; j < argc; ++j) { + if (0 == strcmp("-a", argv[j])) + do_async = 1; + else if (0 == strncmp("-b=", argv[j], 3)) { + m = 3; + num = sscanf(argv[j] + m, "%d", &blk_size); + if ((1 != num) || (blk_size <= 0)) { + printf("Couldn't decode number after '-b' switch\n"); + sg_file_name = 0; + break; + } + } else if (0 == strncmp("-c=", argv[j], 3)) { + m = 3; + num = sscanf(argv[j] + m, "%d", &count); + if (1 != num) { + printf("Couldn't decode number after '-c' switch\n"); + sg_file_name = 0; + break; + } + } else if (0 == strncmp("-e=", argv[j], 3)) { + m = 3; + num = sscanf(argv[j] + m, "%d", &elem_size); + if (1 != num) { + printf("Couldn't decode number after '-e' switch\n"); + sg_file_name = 0; + break; + } + } else if (0 == strcmp("-h", argv[j])) + do_help = 1; + else if (*argv[j] == '-') { + printf("Unrecognized switch: %s\n", argv[j]); + sg_file_name = 0; + break; + } else if (NULL == sg_file_name) + sg_file_name = argv[j]; + else + out_file_name = argv[j]; + } + if (do_help) { + usage(); + return 0; + } + if (NULL == sg_file_name) { + printf(">>> need sg node name (e.g. /dev/sg3)\n\n"); + usage(); + return 1; + } + if (NULL == out_file_name) { + printf(">>> need out filename (to place what is fetched by READ\n\n"); + usage(); + return 1; + } + if (0 == count) { + printf(">>> need count of blocks to READ\n\n"); + usage(); + return 1; + } + + if (do_async) + sg_fd = open(sg_file_name, O_RDWR); + else + sg_fd = open(sg_file_name, O_RDONLY); + if (sg_fd < 0) { + perror(ME "sg device node open error"); + return 1; + } + /* Don't worry, being very careful not to write to a none-sg file ... */ + res = ioctl(sg_fd, SG_GET_VERSION_NUM, &k); + if ((res < 0) || (k < 30000)) { + printf(ME "not a sg device, or driver prior to 3.x\n"); + return 1; + } + fd = open(out_file_name, O_WRONLY | O_CREAT, 0666); + if (fd < 0) { + perror(ME "output file open error"); + return 1; + } + dxfer_len = count * blk_size; + buffp = (uint8_t *)calloc(count, blk_size); + if (buffp) { + if (0 == sg_read(sg_fd, buffp, count, 0, blk_size, elem_size, + do_async)) { + if (write(fd, buffp, dxfer_len) < 0) + perror(ME "output write failed"); + } + free(buffp); + } else + fprintf(stderr, "user space calloc for %d bytes failed\n", + dxfer_len); + res = close(fd); + if (res < 0) { + perror(ME "output file close error"); + close(sg_fd); + return 1; + } + res = close(sg_fd); + if (res < 0) { + perror(ME "sg device close error"); + return 1; + } + return 0; +} diff --git a/testing/sg_queue_tst.c b/testing/sg_queue_tst.c new file mode 100644 index 0000000..cb58c82 --- /dev/null +++ b/testing/sg_queue_tst.c @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_linux_inc.h" + +/* This program was used to test SCSI mid level queue ordering. + The default behaviour is to "queue at head" which is useful for + error processing but not for streaming READ and WRITE commands. + +* Copyright (C) 2010 D. Gilbert +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. + + Invocation: sg_queue_tst [-t] + -t queue at tail + + Version 0.91 (20160528) + +*/ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define SDIAG_CMD_LEN 6 +#define SENSE_BUFFER_LEN 96 + +#define EBUFF_SZ 256 + +#ifndef SG_FLAG_Q_AT_TAIL +#define SG_FLAG_Q_AT_TAIL 0x10 +#endif + +#ifndef SG_FLAG_Q_AT_HEAD +#define SG_FLAG_Q_AT_HEAD 0x20 +#endif + + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t inq_cdb[INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t sdiag_cdb[SDIAG_CMD_LEN] = + {0x1d, 0, 0, 0, 0, 0}; + uint8_t inqBuff[16][INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr[16]; + sg_io_hdr_t rio_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[16][SENSE_BUFFER_LEN]; + int q_at_tail = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-t", argv[k], 2)) + ++q_at_tail; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_queue_tst [-t] '\n" + "where:\n -t queue_at_tail (def: q_at_head)\n"); + return 1; + } + + /* An access mode of O_RDWR is required for write()/read() interface */ + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_queue_tst: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + for (k = 0; k < 16; ++k) { + /* Prepare INQUIRY command */ + memset(&io_hdr[k], 0, sizeof(sg_io_hdr_t)); + io_hdr[k].interface_id = 'S'; + /* io_hdr[k].iovec_count = 0; */ /* memset takes care of this */ + io_hdr[k].mx_sb_len = (uint8_t)sizeof(sense_buffer); + if (0 == (k % 3)) { + io_hdr[k].cmd_len = sizeof(sdiag_cdb); + io_hdr[k].cmdp = sdiag_cdb; + io_hdr[k].dxfer_direction = SG_DXFER_NONE; + } else { + io_hdr[k].cmd_len = sizeof(inq_cdb); + io_hdr[k].cmdp = inq_cdb; + io_hdr[k].dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr[k].dxfer_len = INQ_REPLY_LEN; + io_hdr[k].dxferp = inqBuff[k]; + } + io_hdr[k].sbp = sense_buffer[k]; + io_hdr[k].mx_sb_len = SENSE_BUFFER_LEN; + io_hdr[k].timeout = 20000; /* 20000 millisecs == 20 seconds */ + io_hdr[k].pack_id = k; + /* default is to queue at head (in SCSI mid level) */ + if (q_at_tail) + io_hdr[k].flags |= SG_FLAG_Q_AT_TAIL; + else + io_hdr[k].flags |= SG_FLAG_Q_AT_HEAD; + /* io_hdr[k].usr_ptr = NULL; */ + + if (write(sg_fd, &io_hdr[k], sizeof(sg_io_hdr_t)) < 0) { + perror("sg_queue_tst: sg write error"); + close(sg_fd); + return 1; + } + } + /* sleep(3); */ + for (k = 0; k < 16; ++k) { + memset(&rio_hdr, 0, sizeof(sg_io_hdr_t)); + rio_hdr.interface_id = 'S'; + if (read(sg_fd, &rio_hdr, sizeof(sg_io_hdr_t)) < 0) { + perror("sg_queue_tst: sg read error"); + close(sg_fd); + return 1; + } + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&rio_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("command error", &rio_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + /* if (0 == rio_hdr.pack_id) */ + if (0 == (rio_hdr.pack_id % 3)) + printf("SEND DIAGNOSTIC %d duration=%u\n", rio_hdr.pack_id, + rio_hdr.duration); + else + printf("INQUIRY %d duration=%u\n", rio_hdr.pack_id, + rio_hdr.duration); + } + } + + close(sg_fd); + return 0; +} diff --git a/testing/sg_sense_test.c b/testing/sg_sense_test.c new file mode 100644 index 0000000..ea8bfd5 --- /dev/null +++ b/testing/sg_sense_test.c @@ -0,0 +1,202 @@ +/* + * 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. + */ + +/* This is a simple program that tests the sense data descriptor format + * printout function in sg_lib.c . */ + +#include +#include +#include +#include +#include +#include +#include + +#include "sg_lib.h" + + +#define EBUFF_SZ 256 + +#define ME "sg_sense_test: " + +static const char * version_str = "2.03 20180220"; + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"leadin", required_argument, 0, 'l'}, + {"stdout", no_argument, 0, 's'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, /* sentinel */ +}; + + +static void +usage() +{ + fprintf(stderr, + "Usage: %s [--help] [--leadin=STR] [--stdout] [--verbose] " + "[--version]\n" + " where: --help|-h print out usage message\n" + " --leadin=STR|-l STR every line output by --sense " + "should\n" + " be prefixed by STR\n" + " --stdout|-s send output to stdout (def: " + "stderr)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Test sense data handling of sg_lib. Overlaps somewhat with " + "tst_sg_lib\n", ME + ); + +} + +int +main(int argc, char * argv[]) +{ + bool to_stdout = false; + int c, k, prev_len; + int verbose = 0; + const char * leadin = NULL; + FILE * outfp = stderr; + uint8_t err1[] = {0x72, 0x5, 0x24, 0x0, 0, 0, 0, 32, + 0x2, 0x6, 0, 0, 0xc8, 0x0, 0x3, 0, + 0, 0xa, 0x80, 0, 1, 2, 3, 4, + 0xaa, 0xbb, 0xcc, 0xdd, + 1, 0xa, 0, 0, 1, 2, 3, 4, + 0xaa, 0xbb, 0xee, 0xff}; + uint8_t err2[] = {0x72, SPC_SK_MEDIUM_ERROR, 0x11, 0xb, 0x80, 0, 0, + 32, + 0x2, 0x6, 0, 0, 0xc8, 0x0, 0x3, 0, + 0, 0xa, 0x80, 0, 1, 2, 3, 4, + 0xaa, 0xbb, 0xcc, 0xdd, + 1, 0xa, 0, 0, 1, 2, 3, 4, + 0xaa, 0xbb, 0xee, 0xff}; + /* Set SDAT_OVFL */ + uint8_t err3[] = {0x72, SPC_SK_NO_SENSE, 0x4, 0x4, 0, 0, 0, 8, + 0x2, 0x6, 0, 0, 0xc8, 0x12, 0x34, 0}; + uint8_t err4[] = {0x73, SPC_SK_COPY_ABORTED, 0x8, 0x4, 0, 0, 0, 22, + 0x2, 0x6, 0, 0, 0xc8, 0x0, 0x3, 0, + 0x3, 0x2, 0, 0x55, + 0x5, 0x2, 0, 0x20, + 0x85, 0x4, 0, 0x20, 0x33, 0x44}; + /* Set Filemark, EOM, ILI and SDAT_OVFL */ + uint8_t err5[] = {0xf1, 0, (0xf0 | SPC_SK_ILLEGAL_REQUEST), 0x11, + 0x22, 0x33, 0x44, 0xa, + 0x0, 0x0, 0, 0, 0x4, 0x1, 0, 0xcf, 0, 5,}; + uint8_t err6[] = {0x72, SPC_SK_NO_SENSE, 0x4, 0x1, 0, 0, 0, 14, + 0x9, 0xc, 1, 0, 0x11, 0x22, 0x66, 0x33, + 0x77, 0x44, 0x88, 0x55, 0x1, 0x2}; + uint8_t err7[] = {0xf1, 0, 0xe5, 0x11, 0x22, 0x33, 0x44, 0xa, + 0x0, 0x0, 0x0, 0x0, 0x24, 0x1, 0xbb, + 0xc9, 0x0, 0x2}; + /* Vendor specific, with "valid" bit set */ + uint8_t err8[] = {0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, + 0xd, 0xe, 0xf, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0}; + char b[2048]; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "hl:svV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + usage(); + return 0; + case 'l': + leadin = optarg; + break; + case 's': + to_stdout = true; + break; + case 'v': + ++verbose; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + return 0; + default: + fprintf(stderr, "unrecognised switch code 0x%x ??\n", c); + usage(); + return 1; + } + } + if (optind < argc) { + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return 1; + } + } + if (to_stdout) { + outfp = stdout; + sg_set_warnings_strm(outfp); + } + + fprintf(outfp, "err1 test:\n"); + sg_print_sense(leadin, err1, sizeof(err1), verbose /* raw_info */); + fprintf(outfp, "\n"); + fprintf(outfp, "err2 test:\n"); + sg_print_sense(leadin, err2, sizeof(err2), verbose); + fprintf(outfp, "\n"); + fprintf(outfp, "err3 test:\n"); + sg_print_sense(leadin, err3, sizeof(err3), verbose); + fprintf(outfp, "\n"); + fprintf(outfp, "err4 test:\n"); + sg_print_sense(leadin, err4, sizeof(err4), verbose); + fprintf(outfp, "\n"); + fprintf(outfp, "err5 test: Set Filemark, EOM, ILI and SDAT_OVFL\n"); + sg_print_sense(leadin, err5, sizeof(err5), verbose); + fprintf(outfp, "\n"); + fprintf(outfp, "err6 test:\n"); + sg_print_sense(leadin, err6, sizeof(err6), verbose); + fprintf(outfp, "\n"); + fprintf(outfp, "err7 test:\n"); + sg_print_sense(leadin, err7, sizeof(err7), verbose); + fprintf(outfp, "\n"); + fprintf(outfp, "err8 test (vendor specific):\n"); + sg_print_sense(leadin, err8, sizeof(err8), verbose); + fprintf(outfp, "\n"); + + if (verbose > 1) { + fprintf(outfp, "\n\nTry different output string sizes with " + "sg_get_sense_str(err2):\n"); + for (k = 1, prev_len = -1; k < 512; ++k) { + /* snprintf(leadin, sizeof(leadin), "blen=%d", k); */ + sg_get_sense_str(NULL, err2, sizeof(err2), 0, k, b); + fprintf(outfp, "%s\n", b); + if (prev_len == (int)strlen(b)) + break; + else + prev_len = strlen(b); + } + } + + if (verbose > 2) { + fprintf(outfp, "\n\nTry different output string sizes with " + "sg_get_sense_str(err4):\n"); + for (k = 1, prev_len = -1; k < 512; ++k) { + /* snprintf(leadin, sizeof(leadin), "blen=%d", k); */ + sg_get_sense_str(NULL, err4, sizeof(err4), 0, k, b); + fprintf(outfp, "%s\n", b); + if (prev_len == (int)strlen(b)) + break; + else + prev_len = strlen(b); + } + } + return 0; +} diff --git a/testing/sg_tst_async.cpp b/testing/sg_tst_async.cpp new file mode 100644 index 0000000..a1230a0 --- /dev/null +++ b/testing/sg_tst_async.cpp @@ -0,0 +1,1320 @@ +/* + * Copyright (c) 2014-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include +#include +#include +#include + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_unaligned.h" +#include "sg_pt.h" +#include "sg_cmds.h" + +static const char * version_str = "1.14 20180712"; +static const char * util_name = "sg_tst_async"; + +/* This is a test program for checking the async usage of the Linux sg + * driver. Each thread opens 1 file descriptor to the next sg device (1 + * or more can be given on the command line) and then starts up to 16 + * commands while checking with the poll command (or + * ioctl(SG_GET_NUM_WAITING) ) for the completion of those commands. Each + * command has a unique "pack_id" which is a sequence starting at 1. + * Either TEST UNIT UNIT, READ(16) or WRITE(16) commands are issued. + * + * This is C++ code with some things from C++11 (e.g. threads) and was + * only just able to compile (when some things were reverted) with gcc/g++ + * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support + * was not available until g++ version 4.8.1 . It should build okay on + * recent distributions. + * + * The build uses various object files from the /lib directory + * which is assumed to be a sibling of this examples directory. Those + * object files in the lib directory can be built with: + * cd ; ./configure ; cd lib; make + * cd ../examples + * Then to build sg_tst_async concatenate the next 3 lines: + * g++ -Wall -std=c++11 -pthread -I ../include ../lib/sg_lib.o + * ../lib/sg_lib_data.o ../lib/sg_io_linux.o -o sg_tst_async + * sg_tst_async.cpp + * or use the C++ Makefile in that directory: + * make -f Makefile.cplus sg_tst_async + * + * Currently this utility is Linux only and uses the sg driver. The bsg + * driver is known to be broken (it doesn't match responses to the + * correct file descriptor that requested them) so this utility won't + * be extended to bsg until that is fixed. + * + * BEWARE: >>> This utility will modify a logical block (default LBA 1000) + * on the given device when the '-W' option is given. + * + */ + +using namespace std; +using namespace std::chrono; + +#define DEF_NUM_PER_THREAD 1000 +#define DEF_NUM_THREADS 4 +#define DEF_WAIT_MS 10 /* 0: yield or no wait */ +#define DEF_TIMEOUT_MS 20000 /* 20 seconds */ +#define DEF_LB_SZ 512 +#define DEF_BLOCKING 0 +#define DEF_DIRECT 0 /* 1: direct_io [future maybe 2: mmap IO] */ +#define DEF_NO_XFER 0 +#define DEF_LBA 1000 + +#define MAX_Q_PER_FD 16 /* sg driver per file descriptor limit */ +#define MAX_CONSEC_NOMEMS 16 +#define URANDOM_DEV "/dev/urandom" + +#ifndef SG_FLAG_Q_AT_TAIL +#define SG_FLAG_Q_AT_TAIL 0x10 +#endif +#ifndef SG_FLAG_Q_AT_HEAD +#define SG_FLAG_Q_AT_HEAD 0x20 +#endif + + +#define DEF_PT_TIMEOUT 60 /* 60 seconds */ + +#define EBUFF_SZ 256 + +static mutex console_mutex; +static mutex rand_lba_mutex; +static atomic async_starts(0); +static atomic sync_starts(0); +static atomic async_finishes(0); +static atomic ebusy_count(0); +static atomic start_eagain_count(0); +static atomic fin_eagain_count(0); +static atomic uniq_pack_id(1); +static atomic generic_errs(0); + +static int page_size = 4096; /* rough guess, will ask sysconf() */ + +enum command2execute {SCSI_TUR, SCSI_READ16, SCSI_WRITE16}; +/* Linux Block layer queue disciplines: */ +enum blkLQDiscipline {BLQ_DEFAULT, BLQ_AT_HEAD, BLQ_AT_TAIL}; +/* Queue disciplines of this utility. When both completions and + * queuing a new command are both possible: */ +enum myQDiscipline {MYQD_LOW, /* favour completions over new cmds */ + MYQD_MEDIUM, + MYQD_HIGH}; /* favour new cmds over completions */ + +struct opts_t { + vector dev_names; + bool block; + bool generic_pt; + bool no_xfer; + bool verbose_given; + bool version_given; + int direct; + int maxq_per_thread; + int num_per_thread; + uint64_t lba; + unsigned int hi_lba; /* last one, inclusive range */ + vector hi_lbas; /* only used when hi_lba=-1 */ + int lb_sz; + int stats; + int verbose; + int wait_ms; + command2execute c2e; + blkLQDiscipline blqd; + myQDiscipline myqd; +}; + +#if 0 +class Rand_uint { +public: + Rand_uint(unsigned int lo, unsigned int hi) : p{lo, hi} {} + unsigned int operator()() const { return r(); } +private: + uniform_int_distribution::param_type p; + auto r = bind(uniform_int_distribution{p}, + default_random_engine()); + /* compiler thinks auto should be a static, bs again? */ +}; +#endif + +#if 0 +class Rand_uint { +public: + Rand_uint(unsigned int lo, unsigned int hi, unsigned int my_seed) + : r(bind(uniform_int_distribution{lo, hi}, + default_random_engine())) { r.seed(myseed); } + unsigned int operator()() const { return r(); } +private: + function r; +}; +#endif + +/* Use this class to wrap C++11 features to produce uniform random + * unsigned ints in the range [lo, hi] (inclusive) given a_seed */ +class Rand_uint { +public: + Rand_uint(unsigned int lo, unsigned int hi, unsigned int a_seed) + : uid(lo, hi), dre(a_seed) { } + /* uid ctor takes inclusive range when integral type */ + + unsigned int get() { return uid(dre); } + +private: + uniform_int_distribution uid; + default_random_engine dre; +}; + +static struct option long_options[] = { + {"direct", no_argument, 0, 'd'}, + {"force", no_argument, 0, 'f'}, + {"generic-pt", no_argument, 0, 'g'}, + {"generic_pt", no_argument, 0, 'g'}, + {"help", no_argument, 0, 'h'}, + {"lba", required_argument, 0, 'l'}, + {"maxqpt", required_argument, 0, 'M'}, + {"numpt", required_argument, 0, 'n'}, + {"noxfer", no_argument, 0, 'N'}, + {"qat", required_argument, 0, 'q'}, + {"qfav", required_argument, 0, 'Q'}, + {"read", no_argument, 0, 'R'}, + {"szlb", required_argument, 0, 's'}, + {"stats", no_argument, 0, 'S'}, + {"tnum", required_argument, 0, 't'}, + {"tur", no_argument, 0, 'T'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"wait", required_argument, 0, 'w'}, + {"write", no_argument, 0, 'W'}, + {0, 0, 0, 0}, +}; + + +static void +usage(void) +{ + printf("Usage: %s [--direct] [--force] [--generic-pt] [--help]\n" + " [--lba=LBA+] [--maxqpt=QPT] [--numpt=NPT] " + "[--noxfer]\n" + " [--qat=AT] [-qfav=FAV] [--read] [--szlb=LB] " + "[--stats]\n" + " [--tnum=NT] [--tur] [--verbose] [--version] " + "[--wait=MS]\n" + " [--write] *\n", + util_name); + printf(" where\n"); + printf(" --direct|-d do direct_io (def: indirect)\n"); + printf(" --force|-f force: any sg device (def: only scsi_debug " + "owned)\n"); + printf(" WARNING: written to if '-W' given\n"); + printf(" --generic-pt|-g use generic passthru in sg3_utils " + "instead\n"); + printf(" of Linux sg driver and SG_IO ioctl " + "(def)\n"); + printf(" --help|-h print this usage message then exit\n"); + printf(" --lba=LBA|-l LBA logical block to access (def: %u)\n", + DEF_LBA); + printf(" --lba=LBA,HI_LBA|-l LBA,HI_LBA logical block range " + "(inclusive)\n" + " if hi_lba=-1 assume last block on " + "device\n"); + printf(" --maxqpt=QPT|-M QPT maximum commands queued per thread " + "(def:%d)\n", MAX_Q_PER_FD); + printf(" --numpt=NPT|-n NPT number of commands per thread " + "(def: %d)\n", DEF_NUM_PER_THREAD); + printf(" --noxfer|-N no data xfer (def: xfer on READ and " + "WRITE)\n"); + printf(" --qat=AT|-q AT AT=0: q_at_head; AT=1: q_at_tail\n"); + printf(" --qfav=FAV|-Q FAV FAV=0: favour completions (smaller q),\n" + " FAV=1: medium,\n" + " FAV=2: favour submissions (larger q, " + "default)\n"); + printf(" --read|-R do READs (def: TUR)\n"); + printf(" --szlb=LB|-s LB logical block size (def: 512)\n"); + printf(" --stats|-S show more statistics on completion\n"); + printf(" --tnum=NT|-t NT number of threads (def: %d)\n", + DEF_NUM_THREADS); + printf(" --tur|-T do TEST UNIT READYs (default is TURs)\n"); + printf(" --verbose|-v increase verbosity\n"); + printf(" --version|-V print version number then exit\n"); + printf(" --wait=MS|-w MS >0: poll(); =0: poll(0); (def: " + "%d)\n", DEF_WAIT_MS); + printf(" --write|-W do WRITEs (def: TUR)\n\n"); + printf("Multiple threads send READ(16), WRITE(16) or TEST UNIT READY " + "(TUR) SCSI\ncommands. There can be 1 or more s " + "and each thread takes\nthe next in a round robin fashion. " + "Each thread queues up to 16 commands.\nOne block is transferred " + "by each READ and WRITE; zeros are written. If a\nlogical block " + "range is given, a uniform distribution generates a pseudo\n" + "random sequence of LBAs.\n"); +} + +#ifdef __GNUC__ +static int pr2serr_lk(const char * fmt, ...) + __attribute__ ((format (printf, 1, 2))); +static void pr_errno_lk(int e_no, const char * fmt, ...) + __attribute__ ((format (printf, 2, 3))); +#else +static int pr2serr_lk(const char * fmt, ...); +static void pr_errno_lk(int e_no, const char * fmt, ...); +#endif + + +static int +pr2serr_lk(const char * fmt, ...) +{ + int n; + va_list args; + lock_guard lg(console_mutex); + + va_start(args, fmt); + n = vfprintf(stderr, fmt, args); + va_end(args); + return n; +} + +static void +pr_errno_lk(int e_no, const char * fmt, ...) +{ + char b[160]; + va_list args; + lock_guard lg(console_mutex); + + va_start(args, fmt); + vsnprintf(b, sizeof(b), fmt, args); + fprintf(stderr, "%s: %s\n", b, strerror(e_no)); + va_end(args); +} + +static unsigned int +get_urandom_uint(void) +{ + unsigned int res = 0; + int n; + uint8_t b[sizeof(unsigned int)]; + lock_guard lg(rand_lba_mutex); + + int fd = open(URANDOM_DEV, O_RDONLY); + if (fd >= 0) { + n = read(fd, b, sizeof(unsigned int)); + if (sizeof(unsigned int) == n) + memcpy(&res, b, sizeof(unsigned int)); + close(fd); + } + return res; +} + +#define TUR_CMD_LEN 6 +#define READ16_REPLY_LEN 512 +#define READ16_CMD_LEN 16 +#define WRITE16_REPLY_LEN 512 +#define WRITE16_CMD_LEN 16 + +/* Returns 0 if command injected okay, else -1 */ +static int +start_sg3_cmd(int sg_fd, command2execute cmd2exe, int pack_id, uint64_t lba, + uint8_t * lbp, int xfer_bytes, int flags, + unsigned int & eagains) +{ + struct sg_io_hdr pt; + uint8_t turCmdBlk[TUR_CMD_LEN] = {0, 0, 0, 0, 0, 0}; + uint8_t r16CmdBlk[READ16_CMD_LEN] = + {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + uint8_t w16CmdBlk[WRITE16_CMD_LEN] = + {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + uint8_t sense_buffer[64]; + const char * np = NULL; + + memset(&pt, 0, sizeof(pt)); + switch (cmd2exe) { + case SCSI_TUR: + np = "TEST UNIT READY"; + pt.cmdp = turCmdBlk; + pt.cmd_len = sizeof(turCmdBlk); + pt.dxfer_direction = SG_DXFER_NONE; + break; + case SCSI_READ16: + np = "READ(16)"; + if (lba > 0xffffffff) + sg_put_unaligned_be32(lba >> 32, &r16CmdBlk[2]); + sg_put_unaligned_be32(lba & 0xffffffff, &r16CmdBlk[6]); + pt.cmdp = r16CmdBlk; + pt.cmd_len = sizeof(r16CmdBlk); + pt.dxfer_direction = SG_DXFER_FROM_DEV; + pt.dxferp = lbp; + pt.dxfer_len = xfer_bytes; + break; + case SCSI_WRITE16: + np = "WRITE(16)"; + if (lba > 0xffffffff) + sg_put_unaligned_be32(lba >> 32, &w16CmdBlk[2]); + sg_put_unaligned_be32(lba & 0xffffffff, &w16CmdBlk[6]); + pt.cmdp = w16CmdBlk; + pt.cmd_len = sizeof(w16CmdBlk); + pt.dxfer_direction = SG_DXFER_TO_DEV; + pt.dxferp = lbp; + pt.dxfer_len = xfer_bytes; + break; + } + pt.interface_id = 'S'; + pt.mx_sb_len = sizeof(sense_buffer); + pt.sbp = sense_buffer; /* ignored .... */ + pt.timeout = DEF_TIMEOUT_MS; + pt.pack_id = pack_id; + pt.flags = flags; + + for (int k = 0; write(sg_fd, &pt, sizeof(pt)) < 0; ++k) { + if ((ENOMEM == errno) && (k < MAX_CONSEC_NOMEMS)) { + this_thread::yield(); + continue; + } + if (EAGAIN == errno) { + ++eagains; + this_thread::yield(); + continue; + } + pr_errno_lk(errno, "%s: %s, pack_id=%d", __func__, np, pack_id); + return -1; + } + return 0; +} + +static int +finish_sg3_cmd(int sg_fd, command2execute cmd2exe, int & pack_id, int wait_ms, + unsigned int & eagains) +{ + int ok, res; + struct sg_io_hdr pt; + uint8_t sense_buffer[64]; + const char * np = NULL; + + memset(&pt, 0, sizeof(pt)); + switch (cmd2exe) { + case SCSI_TUR: + np = "TEST UNIT READY"; + break; + case SCSI_READ16: + np = "READ(16)"; + break; + case SCSI_WRITE16: + np = "WRITE(16)"; + break; + } + pt.interface_id = 'S'; + pt.mx_sb_len = sizeof(sense_buffer); + pt.sbp = sense_buffer; + pt.timeout = DEF_TIMEOUT_MS; + pt.pack_id = 0; + + while (((res = read(sg_fd, &pt, sizeof(pt))) < 0) && + (EAGAIN == errno)) { + ++eagains; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (res < 0) { + pr_errno_lk(errno, "%s: %s", __func__, np); + return -1; + } + /* now for the error processing */ + pack_id = pt.pack_id; + ok = 0; + switch (sg_err_category3(&pt)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + pr2serr_lk("%s: Recovered error on %s, continuing\n", __func__, np); + ok = 1; + break; + default: /* won't bother decoding other categories */ + { + lock_guard lg(console_mutex); + sg_chk_n_print3(np, &pt, 1); + } + break; + } + return ok ? 0 : -1; +} + +static void +work_sync_thread(int id, const char * dev_name, unsigned int /* hi_lba */, + struct opts_t * op) +{ + bool is_rw = (SCSI_TUR != op->c2e); + int k, sg_fd, err, rs, n, sense_cat, ret; + int vb = op->verbose; + int num_errs = 0; + int thr_sync_starts = 0; + struct sg_pt_base * ptp = NULL; + uint8_t cdb[6]; + uint8_t sense_b[32]; + char b[120]; + + if (is_rw) { + pr2serr_lk("id=%d: only support TUR here for now\n", id); + goto err_out; + } + if ((sg_fd = sg_cmds_open_device(dev_name, false /* ro */, vb)) < 0) { + pr2serr_lk("id=%d: error opening file: %s: %s\n", id, dev_name, + safe_strerror(-sg_fd)); + goto err_out; + } + + ptp = construct_scsi_pt_obj_with_fd(sg_fd, vb); + err = 0; + if ((NULL == ptp) || ((err = get_scsi_pt_os_err(ptp)))) { + ret = sg_convert_errno(err ? err : ENOMEM); + sg_exit2str(ret, true, sizeof(b), b); + pr2serr_lk("id=%d: construct_scsi_pt_obj_with_fd: %s\n", id, b); + goto err_out; + } + for (k = 0; k < op->num_per_thread; ++k) { + /* Might get Unit Attention on first invocation */ + memset(cdb, 0, sizeof(cdb)); /* TUR's cdb is 6 zeros */ + set_scsi_pt_cdb(ptp, cdb, sizeof(cdb)); + set_scsi_pt_sense(ptp, sense_b, sizeof(sense_b)); + ++thr_sync_starts; + rs = do_scsi_pt(ptp, -1, DEF_PT_TIMEOUT, vb); + n = sg_cmds_process_resp(ptp, "Test unit ready", rs, + SG_NO_DATA_IN, sense_b, + (0 == k), vb, &sense_cat); + if (-1 == n) { + ret = sg_convert_errno(get_scsi_pt_os_err(ptp)); + sg_exit2str(ret, true, sizeof(b), b); + pr2serr_lk("id=%d: do_scsi_pt: %s\n", id, b); + goto err_out; + } else if (-2 == n) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + break; + case SG_LIB_CAT_NOT_READY: + ++num_errs; + if (1 == op->num_per_thread) { + pr2serr_lk("id=%d: device not ready\n", id); + } + break; + case SG_LIB_CAT_UNIT_ATTENTION: + ++num_errs; + if (vb) + pr2serr_lk("Ignoring Unit attention (sense key)\n"); + break; + default: + ++num_errs; + if (1 == op->num_per_thread) { + sg_get_category_sense_str(sense_cat, sizeof(b), b, vb); + pr2serr_lk("%s\n", b); + goto err_out; + } + break; + } + } + clear_scsi_pt_obj(ptp); + } +err_out: + if (ptp) + destruct_scsi_pt_obj(ptp); + if (num_errs > 0) + pr2serr_lk("id=%d: number of errors: %d\n", id, num_errs); + sync_starts += thr_sync_starts; +} + +static void +work_thread(int id, struct opts_t * op) +{ + int thr_async_starts = 0; + int thr_async_finishes = 0; + int vb = op->verbose; + unsigned int thr_start_eagain_count = 0; + unsigned int thr_fin_eagain_count = 0; + unsigned int seed = 0; + unsigned int hi_lba; + int k, n, res, sg_fd, num_outstanding, do_inc, npt, pack_id, sg_flags; + int num_waiting_read, num_to_read; + int open_flags = O_RDWR; + bool is_rw = (SCSI_TUR != op->c2e); + char ebuff[EBUFF_SZ]; + uint64_t lba; + uint8_t * lbp; + uint8_t * free_lbp = NULL; + const char * dev_name; + const char * err = NULL; + Rand_uint * ruip = NULL; + struct pollfd pfd[1]; + list > free_lst; /* of aligned lb buffers */ + map pi_2_buff; /* pack_id -> lb buffer */ + map pi_2_lba; /* pack_id -> LBA */ + + /* device name and hi_lba may depend on id */ + n = op->dev_names.size(); + dev_name = op->dev_names[id % n]; + if ((UINT_MAX == op->hi_lba) && (n == (int)op->hi_lbas.size())) + hi_lba = op->hi_lbas[id % n]; + else + hi_lba = op->hi_lba; + + if (vb) { + if ((vb > 1) && hi_lba) + pr2serr_lk("Enter work_thread id=%d using %s\n" + " LBA range: 0x%x to 0x%x (inclusive)\n", + id, dev_name, (unsigned int)op->lba, hi_lba); + else + pr2serr_lk("Enter work_thread id=%d using %s\n", id, dev_name); + } + if (op->generic_pt) { + work_sync_thread(id, dev_name, hi_lba, op); + return; + } + if (! op->block) + open_flags |= O_NONBLOCK; + + sg_fd = open(dev_name, open_flags); + if (sg_fd < 0) { + pr_errno_lk(errno, "%s: id=%d, error opening file: %s", __func__, id, + dev_name); + return; + } + pfd[0].fd = sg_fd; + pfd[0].events = POLLIN; + if (is_rw && hi_lba) { + seed = get_urandom_uint(); + if (vb > 1) + pr2serr_lk(" id=%d, /dev/urandom seed=0x%x\n", id, seed); + ruip = new Rand_uint((unsigned int)op->lba, hi_lba, seed); + } + + sg_flags = 0; + if (BLQ_AT_TAIL == op->blqd) + sg_flags |= SG_FLAG_Q_AT_TAIL; + else if (BLQ_AT_HEAD == op->blqd) + sg_flags |= SG_FLAG_Q_AT_HEAD; + if (op->direct) + sg_flags |= SG_FLAG_DIRECT_IO; + if (op->no_xfer) + sg_flags |= SG_FLAG_NO_DXFER; + if (vb > 1) + pr2serr_lk(" id=%d, sg_flags=0x%x, %s cmds\n", id, sg_flags, + ((SCSI_TUR == op->c2e) ? "TUR": + ((SCSI_READ16 == op->c2e) ? "READ" : "WRITE"))); + + npt = op->num_per_thread; + /* main loop, continues until num_per_thread exhausted and there are + * no more outstanding responses */ + for (k = 0, num_outstanding = 0; (k < npt) || num_outstanding; + k = do_inc ? k + 1 : k) { + do_inc = 0; + if ((num_outstanding < op->maxq_per_thread) && (k < npt)) { + do_inc = 1; + pack_id = uniq_pack_id.fetch_add(1); + if (is_rw) { /* get new lb buffer or one from free list */ + if (free_lst.empty()) { + lbp = sg_memalign(op->lb_sz, 0, &free_lbp, vb); + if (NULL == lbp) { + err = "out of memory"; + break; + } + } else { + lbp = free_lst.back().first; + free_lst.pop_back(); + } + } else + lbp = NULL; + if (is_rw) { + if (ruip) { + lba = ruip->get(); /* fetch a random LBA */ + if (vb > 3) + pr2serr_lk(" id=%d: start IO at lba=0x%" PRIx64 "\n", + id, lba); + } else + lba = op->lba; + } else + lba = 0; + if (start_sg3_cmd(sg_fd, op->c2e, pack_id, lba, lbp, op->lb_sz, + sg_flags, thr_start_eagain_count)) { + err = "start_sg3_cmd()"; + break; + } + ++thr_async_starts; + ++num_outstanding; + pi_2_buff[pack_id] = lbp; + if (ruip) + pi_2_lba[pack_id] = lba; + } + num_to_read = 0; + if ((num_outstanding >= op->maxq_per_thread) || (k >= npt)) { + /* full queue or finished injecting */ + num_waiting_read = 0; + if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting_read) < 0) { + err = "ioctl(SG_GET_NUM_WAITING) failed"; + break; + } + if (1 == num_waiting_read) + num_to_read = num_waiting_read; + else if (num_waiting_read > 0) { + if (k >= npt) + num_to_read = num_waiting_read; + else { + switch (op->myqd) { + case MYQD_LOW: + num_to_read = num_waiting_read; + break; + case MYQD_MEDIUM: + num_to_read = num_waiting_read / 2; + break; + case MYQD_HIGH: + default: + num_to_read = 1; + break; + } + } + } else { /* nothing waiting to be read */ + n = (op->wait_ms > 0) ? op->wait_ms : 0; + while (0 == (res = poll(pfd, 1, n))) { + if (res < 0) { + err = "poll(wait_ms) failed"; + break; + } + } + if (err) + break; + } + } else { /* not full, not finished injecting */ + if (MYQD_HIGH == op->myqd) + num_to_read = 0; + else { + num_waiting_read = 0; + if (ioctl(sg_fd, SG_GET_NUM_WAITING, &num_waiting_read) < 0) { + err = "ioctl(SG_GET_NUM_WAITING) failed"; + break; + } + if (num_waiting_read > 0) + num_to_read = num_waiting_read / + ((MYQD_LOW == op->myqd) ? 1 : 2); + else + num_to_read = 0; + } + } + + while (num_to_read-- > 0) { + if (finish_sg3_cmd(sg_fd, op->c2e, pack_id, op->wait_ms, + thr_fin_eagain_count)) { + err = "finish_sg3_cmd()"; + if (ruip && (pack_id > 0)) { + auto q = pi_2_lba.find(pack_id); + + if (q != pi_2_lba.end()) { + snprintf(ebuff, sizeof(ebuff), "%s: lba=0x%" PRIx64 , + err, q->second); + err = ebuff; + } + } + break; + } + ++thr_async_finishes; + --num_outstanding; + auto p = pi_2_buff.find(pack_id); + + if (p == pi_2_buff.end()) { + snprintf(ebuff, sizeof(ebuff), "pack_id=%d from " + "finish_sg3_cmd() not found\n", pack_id); + if (! err) + err = ebuff; + } else { + lbp = p->second; + pi_2_buff.erase(p); + if (lbp) + free_lst.push_front(make_pair(lbp, free_lbp)); + } + if (ruip && (pack_id > 0)) { + auto q = pi_2_lba.find(pack_id); + + if (q != pi_2_lba.end()) { + if (vb > 3) + pr2serr_lk(" id=%d: finish IO at lba=0x%" PRIx64 + "\n", id, q->second); + pi_2_lba.erase(q); + } + } + if (err) + break; + } + if (err) + break; + } + close(sg_fd); // sg driver will handle any commands "in flight" + if (ruip) + delete ruip; + + if (err || (k < npt)) { + if (k < npt) + pr2serr_lk("thread id=%d FAILed at iteration %d%s%s\n", id, k, + (err ? ", Reason: " : ""), (err ? err : "")); + else + pr2serr_lk("thread id=%d FAILed on last%s%s\n", id, + (err ? ", Reason: " : ""), (err ? err : "")); + } + n = pi_2_buff.size(); + if (n > 0) + pr2serr_lk("thread id=%d Still %d elements in pi_2_buff map on " + "exit\n", id, n); + for (k = 0; ! free_lst.empty(); ++k) { + lbp = free_lst.back().first; + free_lbp = free_lst.back().second; + free_lst.pop_back(); + if (free_lbp) + free(free_lbp); + } + if ((vb > 2) && (k > 0)) + pr2serr_lk("thread id=%d Maximum number of READ/WRITEs queued: %d\n", + id, k); + async_starts += thr_async_starts; + async_finishes += thr_async_finishes; + start_eagain_count += thr_start_eagain_count; + fin_eagain_count += thr_fin_eagain_count; +} + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 + +/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field + * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success, + * else -1 . */ +static int +do_inquiry_prod_id(const char * dev_name, int block, char * b, int b_mlen) +{ + int sg_fd, ok, ret; + struct sg_io_hdr pt; + uint8_t inqCmdBlk [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t inqBuff[INQ_REPLY_LEN]; + uint8_t sense_buffer[64]; + int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */ + + if (! block) + open_flags |= O_NONBLOCK; + sg_fd = open(dev_name, open_flags); + if (sg_fd < 0) { + pr_errno_lk(errno, "%s: error opening file: %s", __func__, dev_name); + return -1; + } + /* Prepare INQUIRY command */ + memset(&pt, 0, sizeof(pt)); + pt.interface_id = 'S'; + pt.cmd_len = sizeof(inqCmdBlk); + /* pt.iovec_count = 0; */ /* memset takes care of this */ + pt.mx_sb_len = sizeof(sense_buffer); + pt.dxfer_direction = SG_DXFER_FROM_DEV; + pt.dxfer_len = INQ_REPLY_LEN; + pt.dxferp = inqBuff; + pt.cmdp = inqCmdBlk; + pt.sbp = sense_buffer; + pt.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* pt.flags = 0; */ /* take defaults: indirect IO, etc */ + /* pt.pack_id = 0; */ + /* pt.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &pt) < 0) { + pr_errno_lk(errno, "%s: Inquiry SG_IO ioctl error", __func__); + close(sg_fd); + return -1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&pt)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + pr2serr_lk("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + { + lock_guard lg(console_mutex); + sg_chk_n_print3("INQUIRY command error", &pt, 1); + } + break; + } + if (ok) { + /* Good, so fetch Product ID from response, copy to 'b' */ + if (b_mlen > 0) { + if (b_mlen > 16) { + memcpy(b, inqBuff + 16, 16); + b[16] = '\0'; + } else { + memcpy(b, inqBuff + 16, b_mlen - 1); + b[b_mlen - 1] = '\0'; + } + } + ret = 0; + } else + ret = -1; + close(sg_fd); + return ret; +} + +/* Only allow ranges up to 2**32-1 upper limit, so READ CAPACITY(10) + * sufficient. Return of 0 -> success, -1 -> failure, 2 -> try again */ +static int +do_read_capacity(const char * dev_name, int block, unsigned int * last_lba, + unsigned int * blk_sz) +{ + int res, sg_fd; + uint8_t rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t rcBuff[64]; + uint8_t sense_b[64]; + sg_io_hdr_t io_hdr; + int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */ + + if (! block) + open_flags |= O_NONBLOCK; + sg_fd = open(dev_name, open_flags); + if (sg_fd < 0) { + pr_errno_lk(errno, "%s: error opening file: %s", __func__, dev_name); + return -1; + } + /* Prepare READ CAPACITY(10) command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(rcCmdBlk); + io_hdr.mx_sb_len = sizeof(sense_b); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = sizeof(rcBuff); + io_hdr.dxferp = rcBuff; + io_hdr.cmdp = rcCmdBlk; + io_hdr.sbp = sense_b; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */; + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + pr_errno_lk(errno, "%s (SG_IO) error", __func__); + close(sg_fd); + return -1; + } + res = sg_err_category3(&io_hdr); + if (SG_LIB_CAT_UNIT_ATTENTION == res) { + lock_guard lg(console_mutex); + sg_chk_n_print3("read capacity", &io_hdr, 1); + close(sg_fd); + return 2; /* probably have another go ... */ + } else if (SG_LIB_CAT_CLEAN != res) { + lock_guard lg(console_mutex); + sg_chk_n_print3("read capacity", &io_hdr, 1); + close(sg_fd); + return -1; + } + *last_lba = sg_get_unaligned_be32(&rcBuff[0]); + *blk_sz = sg_get_unaligned_be32(&rcBuff[4]); + close(sg_fd); + return 0; +} + + +int +main(int argc, char * argv[]) +{ + int k, n, c, res; + int force = 0; + int64_t ll; + int num_threads = DEF_NUM_THREADS; + char b[128]; + struct timespec start_tm, end_tm; + struct opts_t opts; + struct opts_t * op; + const char * cp; + const char * dev_name; + + op = &opts; + memset(op, 0, sizeof(opts)); + op->direct = DEF_DIRECT; + op->lba = DEF_LBA; + op->hi_lba = 0; + op->lb_sz = DEF_LB_SZ; + op->maxq_per_thread = MAX_Q_PER_FD; + op->num_per_thread = DEF_NUM_PER_THREAD; + op->no_xfer = !! DEF_NO_XFER; + op->verbose = 0; + op->wait_ms = DEF_WAIT_MS; + op->c2e = SCSI_TUR; + op->blqd = BLQ_DEFAULT; + op->block = !! DEF_BLOCKING; + op->myqd = MYQD_HIGH; + page_size = sysconf(_SC_PAGESIZE); + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "dfghl:M:n:Nq:Q:Rs:St:TvVw:W", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'd': + op->direct = 1; + break; + case 'f': + force = true; + break; + case 'g': + op->generic_pt = true; + break; + case 'h': + case '?': + usage(); + return 0; + case 'l': + if (isdigit(*optarg)) { + ll = sg_get_llnum(optarg); + if (-1 == ll) { + pr2serr_lk("could not decode lba\n"); + return 1; + } else + op->lba = (uint64_t)ll; + cp = strchr(optarg, ','); + if (cp) { + if (0 == strcmp("-1", cp + 1)) + op->hi_lba = UINT_MAX; + else { + ll = sg_get_llnum(cp + 1); + if ((-1 == ll) || (ll > UINT_MAX)) { + pr2serr_lk("could not decode hi_lba, or > " + "UINT_MAX\n"); + return 1; + } else + op->hi_lba = (unsigned int)ll; + } + } + } else { + pr2serr_lk("--lba= expects a number\n"); + return 1; + } + break; + case 'M': + if (isdigit(*optarg)) { + n = atoi(optarg); + if ((n < 1) || (n > MAX_Q_PER_FD)) { + pr2serr_lk("-M expects a value from 1 to %d\n", + MAX_Q_PER_FD); + return 1; + } + op->maxq_per_thread = n; + } else { + pr2serr_lk("--maxqpt= expects a number\n"); + return 1; + } + break; + case 'n': + if (isdigit(*optarg)) + op->num_per_thread = sg_get_num(optarg); + else { + pr2serr_lk("--numpt= expects a number\n"); + return 1; + } + break; + case 'N': + op->no_xfer = true; + break; + case 'q': + if (isdigit(*optarg)) { + n = atoi(optarg); + if (0 == n) + op->blqd = BLQ_AT_HEAD; + else if (1 == n) + op->blqd = BLQ_AT_TAIL; + } else { + pr2serr_lk("--qat= expects a number: 0 or 1\n"); + return 1; + } + break; + case 'Q': + if (isdigit(*optarg)) { + n = atoi(optarg); + if (0 == n) + op->myqd = MYQD_LOW; + else if (1 == n) + op->myqd = MYQD_MEDIUM; + else if (2 == n) + op->myqd = MYQD_HIGH; + } else { + pr2serr_lk("--qfav= expects a number: 0, 1 or 2\n"); + return 1; + } + break; + case 'R': + op->c2e = SCSI_READ16; + break; + case 's': + if (isdigit(*optarg)) { + op->lb_sz = atoi(optarg); + if (op->lb_sz < 256) { + cerr << "Strange lb_sz, using 256" << endl; + op->lb_sz = 256; + } + } else { + pr2serr_lk("--szlb= expects a number\n"); + return 1; + } + break; + case 'S': + ++op->stats; + break; + case 't': + if (isdigit(*optarg)) + num_threads = atoi(optarg); + else { + pr2serr_lk("--tnum= expects a number\n"); + return 1; + } + break; + case 'T': + op->c2e = SCSI_TUR; + break; + case 'v': + op->verbose_given = true; + ++op->verbose; + break; + case 'V': + op->version_given = true; + break; + case 'w': + if ((isdigit(*optarg) || ('-' == *optarg))) { + if ('-' == *optarg) + op->wait_ms = - atoi(optarg + 1); + else + op->wait_ms = atoi(optarg); + } else { + pr2serr_lk("--wait= expects a number\n"); + return 1; + } + break; + case 'W': + op->c2e = SCSI_WRITE16; + break; + default: + pr2serr_lk("unrecognised option code 0x%x ??\n", c); + usage(); + return 1; + } + } + if (optind < argc) { + if (optind < argc) { + for (; optind < argc; ++optind) + op->dev_names.push_back(argv[optind]); + } + } +#ifdef DEBUG + pr2serr_lk("In DEBUG mode, "); + if (op->verbose_given && op->version_given) { + pr2serr_lk("but override: '-vV' given, zero verbose and continue\n"); + op->verbose_given = false; + op->version_given = false; + op->verbose = 0; + } else if (! op->verbose_given) { + pr2serr_lk("set '-vv'\n"); + op->verbose = 2; + } else + pr2serr_lk("keep verbose=%d\n", op->verbose); +#else + if (op->verbose_given && op->version_given) + pr2serr_lk("Not in DEBUG mode, so '-vV' has no special action\n"); +#endif + if (op->version_given) { + pr2serr_lk("version: %s\n", version_str); + return 0; + } + + if (0 == op->dev_names.size()) { + fprintf(stderr, "No sg_disk_device-s given\n\n"); + usage(); + return 1; + } + if (op->hi_lba && (op->lba > op->hi_lba)) { + cerr << "lba,hi_lba range is illegal" << endl; + return 1; + } + + try { + struct stat a_stat; + + for (k = 0; k < (int)op->dev_names.size(); ++k) { + dev_name = op->dev_names[k]; + if (stat(dev_name, &a_stat) < 0) { + snprintf(b, sizeof(b), "could not stat() %s", dev_name); + perror(b); + return 1; + } + if (! S_ISCHR(a_stat.st_mode)) { + pr2serr_lk("%s should be a sg device which is a char " + "device. %s\n", dev_name, dev_name); + pr2serr_lk("is not a char device and damage could be done " + "if it is a BLOCK\ndevice, exiting ...\n"); + return 1; + } + if (! force) { + res = do_inquiry_prod_id(dev_name, op->block, b, sizeof(b)); + if (res) { + pr2serr_lk("INQUIRY failed on %s\n", dev_name); + return 1; + } + // For safety, since written to, only permit scsi_debug + // devices. Bypass this with '-f' option. + if (0 != memcmp("scsi_debug", b, 10)) { + pr2serr_lk("Since this utility may write to LBAs, " + "only devices with the\n" + "product ID 'scsi_debug' accepted. Use '-f' " + "to override.\n"); + return 2; + } + } + if (UINT_MAX == op->hi_lba) { + unsigned int last_lba; + unsigned int blk_sz; + + res = do_read_capacity(dev_name, op->block, &last_lba, + &blk_sz); + if (2 == res) + res = do_read_capacity(dev_name, op->block, &last_lba, + &blk_sz); + if (res) { + pr2serr_lk("READ CAPACITY(10) failed on %s\n", dev_name); + return 1; + } + op->hi_lbas.push_back(last_lba); + if (blk_sz != (unsigned int)op->lb_sz) + pr2serr_lk(">>> warning: Logical block size (%d) of %s\n" + " differs from command line option (or " + "default)\n", blk_sz, dev_name); + } + } + + start_tm.tv_sec = 0; + start_tm.tv_nsec = 0; + if (clock_gettime(CLOCK_MONOTONIC, &start_tm) < 0) + perror("clock_gettime failed"); + + vector vt; + + /* start multi-threaded section */ + for (k = 0; k < num_threads; ++k) { + thread * tp = new thread {work_thread, k, op}; + vt.push_back(tp); + } + + // g++ 4.7.3 didn't like range-for loop here + for (k = 0; k < (int)vt.size(); ++k) + vt[k]->join(); + /* end multi-threaded section, just this main thread left */ + + for (k = 0; k < (int)vt.size(); ++k) + delete vt[k]; + + n = uniq_pack_id.load() - 1; + if (((n > 0) || op->generic_pt) && + (0 == clock_gettime(CLOCK_MONOTONIC, &end_tm))) { + struct timespec res_tm; + double a, b; + + if (op->generic_pt) + n = op->num_per_thread * num_threads; + res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec; + res_tm.tv_nsec = end_tm.tv_nsec - start_tm.tv_nsec; + if (res_tm.tv_nsec < 0) { + --res_tm.tv_sec; + res_tm.tv_nsec += 1000000000; + } + a = res_tm.tv_sec; + a += (0.000001 * (res_tm.tv_nsec / 1000)); + b = (double)n; + if (a > 0.000001) { + printf("Time to complete %d commands was %d.%06d seconds\n", + n, (int)res_tm.tv_sec, (int)(res_tm.tv_nsec / 1000)); + printf("Implies %.0f IOPS\n", (b / a)); + } + } + + if (op->verbose || op->stats) { + cout << "Number of sync_starts: " << sync_starts.load() << endl; + cout << "Number of async_starts: " << async_starts.load() << endl; + cout << "Number of async_finishes: " << async_finishes.load() << + endl; + cout << "Last pack_id: " << n << endl; + cout << "Number of EBUSYs: " << ebusy_count.load() << endl; + cout << "Number of start EAGAINs: " << start_eagain_count.load() + << endl; + cout << "Number of finish EAGAINs: " << fin_eagain_count.load() + << endl; + } + } + catch(system_error& e) { + cerr << "got a system_error exception: " << e.what() << '\n'; + auto ec = e.code(); + cerr << "category: " << ec.category().name() << '\n'; + cerr << "value: " << ec.value() << '\n'; + cerr << "message: " << ec.message() << '\n'; + cerr << "\nNote: if g++ may need '-pthread' or similar in " + "compile/link line" << '\n'; + } + catch(...) { + cerr << "got another exception: " << '\n'; + } + return 0; +} diff --git a/testing/sg_tst_context.cpp b/testing/sg_tst_context.cpp new file mode 100644 index 0000000..b811c56 --- /dev/null +++ b/testing/sg_tst_context.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2013-2018 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_pt.h" + +static const char * version_str = "1.03 20180712"; +static const char * util_name = "sg_tst_context"; + +/* This is a test program for checking that file handles keep their + * context properly when sent (synchronous) SCSI pass-through commands. + * A disk device is assumed and even-numbered threads send TEST UNIT + * READY commands while odd-numbered threads send alternating START STOP + * UNIT commands (i.e. start then stop then start, etc). The point is to + * check the results to make sure that they don't get the other command's + * response. For example a START STOP UNIT command should not see a "not + * ready" sense key. + * + * This is C++ code with some things from C++11 (e.g. threads) and was + * only just able to compile (when some things were reverted) with gcc/g++ + * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support + * was not available until g++ version 4.8.1 and that is found in Fedora + * 19 and Ubuntu 13.10 . + * + * The build uses various object files from the /lib directory + * which is assumed to be a sibling of this examples directory. Those + * object files in the lib directory can be built with: + * cd ; ./configure ; cd lib; make + * Then to build sg_tst_context concatenate the next 3 lines: + * g++ -Wall -std=c++11 -pthread -I ../include ../lib/sg_lib.o + * ../lib/sg_lib_data.o ../lib/sg_pt_linux.o -o sg_tst_context + * sg_tst_context.cpp + * Alternatively use 'make -f Makefile.cplus sg_tst_context' + * + */ + +using namespace std; +using namespace std::chrono; + +#define DEF_NUM_PER_THREAD 200 +#define DEF_NUM_THREADS 2 + +#define EBUFF_SZ 256 + + +static mutex count_mutex; +static mutex console_mutex; +static unsigned int even_notreadys; +static unsigned int odd_notreadys; +static unsigned int ebusy_count; +static int verbose; + + +static void +usage(void) +{ + printf("Usage: %s [-e] [-h] [-n ] [-N] [-R] [-s]\n" + " [-t ] [-v] [-V] \n", + util_name); + printf(" where\n"); + printf(" -e use O_EXCL on open (def: don't)\n"); + printf(" -h print this usage message then exit\n"); + printf(" -n number of loops per thread " + "(def: %d)\n", DEF_NUM_PER_THREAD); + printf(" -N use O_NONBLOCK on open (def: don't)\n"); + printf(" -R make sure device in ready (started) " + "state after\n" + " test (do extra iteration if " + "necessary)\n"); + printf(" -s share an open file handle (def: one " + "per thread)\n"); + printf(" -t number of threads (def: %d)\n", + DEF_NUM_THREADS); + printf(" -v increase verbosity\n"); + printf(" -V print version number then exit\n\n"); + printf("Test if file handles keep context through to their responses. " + "Sends\nTEST UNIT READY commands on even threads (origin 0) and " + "START STOP\nUNIT commands on odd threads. Expect NOT READY " + "sense keys only\nfrom the even threads (i.e from TUR)\n"); +} + +static int +pt_err(int res) +{ + if (res < 0) + fprintf(stderr, " pass through OS error: %s\n", safe_strerror(-res)); + else if (SCSI_PT_DO_BAD_PARAMS == res) + fprintf(stderr, " bad pass through setup\n"); + else if (SCSI_PT_DO_TIMEOUT == res) + fprintf(stderr, " pass through timeout\n"); + else + fprintf(stderr, " do_scsi_pt error=%d\n", res); + return (res < 0) ? res : -EPERM /* -1 */; +} + +static int +pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp) +{ + int slen; + char b[256]; + const int bl = (int)sizeof(b); + const char * cp = NULL; + + b[0] = '\0'; + switch (cat) { + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b); + cp = " scsi status: %s\n"; + break; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptp); + sg_get_sense_str("", sbp, slen, 1, bl, b); + cp = "%s\n"; + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + get_scsi_pt_transport_err_str(ptp, bl, b); + cp = " transport: %s\n"; + break; + case SCSI_PT_RESULT_OS_ERR: + get_scsi_pt_os_err_str(ptp, bl, b); + cp = " os: %s\n"; + break; + default: + cp = " unknown pt result category (%d)\n"; + break; + } + if (cp) { + lock_guard lg(console_mutex); + + fprintf(stderr, cp, b); + } + return -EIO /* -5 */; +} + +#define TUR_CMD_LEN 6 +#define SSU_CMD_LEN 6 +#define NOT_READY SG_LIB_CAT_NOT_READY + +/* Returns 0 for good, 1024 for a sense key of NOT_READY, or a negative + * errno */ +static int +do_tur(struct sg_pt_base * ptp, int id) +{ + int slen, res, cat; + unsigned char turCmdBlk [TUR_CMD_LEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + unsigned char sense_buffer[64]; + + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, turCmdBlk, sizeof(turCmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + res = do_scsi_pt(ptp, -1, 20 /* secs timeout */, verbose); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "TEST UNIT READY do_scsi_pt() submission error, " + "id=%d\n", id); + } + res = pt_err(res); + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + slen = get_scsi_pt_sense_len(ptp); + if ((SCSI_PT_RESULT_SENSE == cat) && + (NOT_READY == sg_err_category_sense(sense_buffer, slen))) { + res = 1024; + goto err; + } + { + lock_guard lg(console_mutex); + + fprintf(stderr, "TEST UNIT READY do_scsi_pt() category problem, " + "id=%d\n", id); + } + res = pt_cat_no_good(cat, ptp, sense_buffer); + goto err; + } + res = 0; +err: + return res; +} + +/* Returns 0 for good, 1024 for a sense key of NOT_READY, or a negative + * errno */ +static int +do_ssu(struct sg_pt_base * ptp, int id, bool start) +{ + int slen, res, cat; + unsigned char ssuCmdBlk [SSU_CMD_LEN] = {0x1b, 0x0, 0x0, 0x0, 0x0, 0x0}; + unsigned char sense_buffer[64]; + + if (start) + ssuCmdBlk[4] |= 0x1; + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, ssuCmdBlk, sizeof(ssuCmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + res = do_scsi_pt(ptp, -1, 40 /* secs timeout */, verbose); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "START STOP UNIT do_scsi_pt() submission error, " + "id=%d\n", id); + } + res = pt_err(res); + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + slen = get_scsi_pt_sense_len(ptp); + if ((SCSI_PT_RESULT_SENSE == cat) && + (NOT_READY == sg_err_category_sense(sense_buffer, slen))) { + res = 1024; + goto err; + } + { + lock_guard lg(console_mutex); + + fprintf(stderr, "START STOP UNIT do_scsi_pt() category problem, " + "id=%d\n", id); + } + res = pt_cat_no_good(cat, ptp, sense_buffer); + goto err; + } + res = 0; +err: + return res; +} + +static void +work_thread(const char * dev_name, int id, int num, bool share, + int pt_fd, int nonblock, int oexcl, bool ready_after) +{ + bool started = true; + int k; + int res = 0; + unsigned int thr_even_notreadys = 0; + unsigned int thr_odd_notreadys = 0; + unsigned int thr_ebusy_count = 0; + struct sg_pt_base * ptp = NULL; + char ebuff[EBUFF_SZ]; + + { + lock_guard lg(console_mutex); + + cerr << "Enter work_thread id=" << id << " num=" << num << " share=" + << share << endl; + } + if (! share) { /* ignore passed ptp, make this thread's own */ + int oflags = O_RDWR; + + if (nonblock) + oflags |= O_NONBLOCK; + if (oexcl) + oflags |= O_EXCL; + while (((pt_fd = scsi_pt_open_flags(dev_name, oflags, verbose)) < 0) + && (-EBUSY == pt_fd)) { + ++thr_ebusy_count; + this_thread::yield(); // give other threads a chance + } + if (pt_fd < 0) { + snprintf(ebuff, EBUFF_SZ, "work_thread id=%d: error opening: %s", + id, dev_name); + perror(ebuff); + return; + } + if (thr_ebusy_count) { + lock_guard lg(count_mutex); + + ebusy_count += thr_ebusy_count; + } + } + /* The instance of 'struct sg_pt_base' is local to this thread but the + * pt_fd it contains may be shared, depending on the 'share' boolean. */ + ptp = construct_scsi_pt_obj_with_fd(pt_fd, verbose); + if (NULL == ptp) { + fprintf(stderr, "work_thread id=%d: " + "construct_scsi_pt_obj_with_fd() failed, memory?\n", id); + return; + } + for (k = 0; k < num; ++k) { + if (0 == (id % 2)) { + /* Even thread ids do TEST UNIT READYs */ + res = do_tur(ptp, id); + if (1024 == res) { + ++thr_even_notreadys; + res = 0; + } + } else { + /* Odd thread ids do START STOP UNITs, alternating between + * starts and stops */ + started = (0 == (k % 2)); + res = do_ssu(ptp, id, started); + if (1024 == res) { + ++thr_odd_notreadys; + res = 0; + } + } + if (res) + break; + if (ready_after && (! started)) + do_ssu(ptp, id, true); + } + if (ptp) + destruct_scsi_pt_obj(ptp); + if ((! share) && (pt_fd >= 0)) + close(pt_fd); + + { + lock_guard lg(count_mutex); + + even_notreadys += thr_even_notreadys; + odd_notreadys += thr_odd_notreadys; + } + + { + lock_guard lg(console_mutex); + + if (k < num) + cerr << "thread id=" << id << " FAILed at iteration: " << k + << " [negated errno: " << res << " <" + << safe_strerror(-res) << ">]" << endl; + else + cerr << "thread id=" << id << " normal exit" << '\n'; + } +} + + +int +main(int argc, char * argv[]) +{ + int k; + int pt_fd = -1; + int oexcl = 0; + int nonblock = 0; + int num_per_thread = DEF_NUM_PER_THREAD; + bool ready_after = false; + bool share = false; + int num_threads = DEF_NUM_THREADS; + char * dev_name = NULL; + char ebuff[EBUFF_SZ]; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-e", argv[k], 2)) + ++oexcl; + else if (0 == memcmp("-h", argv[k], 2)) { + usage(); + return 0; + } else if (0 == memcmp("-n", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) { + num_per_thread = sg_get_num(argv[k]); + if (num_per_thread<= 0) { + fprintf(stderr, "want positive integer for number " + "per thread\n"); + return 1; + } + } else + break; + } else if (0 == memcmp("-N", argv[k], 2)) + ++nonblock; + else if (0 == memcmp("-R", argv[k], 2)) + ready_after = true; + else if (0 == memcmp("-s", argv[k], 2)) + share = true; + else if (0 == memcmp("-t", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_threads = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-v", argv[k], 2)) + ++verbose; + else if (0 == memcmp("-V", argv[k], 2)) { + printf("%s version: %s\n", util_name, version_str); + return 0; + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + dev_name = NULL; + break; + } + else if (! dev_name) + dev_name = argv[k]; + else { + printf("too many arguments\n"); + dev_name = 0; + break; + } + } + if (0 == dev_name) { + usage(); + return 1; + } + try { + if (share) { + int oflags = O_RDWR; + + if (nonblock) + oflags |= O_NONBLOCK; + if (oexcl) + oflags |= O_EXCL; + while (((pt_fd = scsi_pt_open_flags(dev_name, oflags, verbose)) + < 0) && (-EBUSY == pt_fd)) { + ++ebusy_count; + sleep(0); // process yield ?? + } + if (pt_fd < 0) { + snprintf(ebuff, EBUFF_SZ, "main: error opening: %s", + dev_name); + perror(ebuff); + return 1; + } + /* Tried calling construct_scsi_pt_obj_with_fd() here but that + * doesn't work since 'struct sg_pt_base' objects aren't + * thread-safe without user space intervention (e.g. mutexes). */ + } + + vector vt; + + for (k = 0; k < num_threads; ++k) { + thread * tp = new thread {work_thread, dev_name, k, + num_per_thread, share, pt_fd, nonblock, + oexcl, ready_after}; + vt.push_back(tp); + } + + for (k = 0; k < (int)vt.size(); ++k) + vt[k]->join(); + + for (k = 0; k < (int)vt.size(); ++k) + delete vt[k]; + + if (share) + scsi_pt_close_device(pt_fd); + + cout << "Expected not_readys on TEST UNIT READY: " << even_notreadys + << endl; + cout << "UNEXPECTED not_readys on START STOP UNIT: " + << odd_notreadys << endl; + if (ebusy_count) + cout << "Number of EBUSYs (on open): " << ebusy_count << endl; + + } + catch(system_error& e) { + cerr << "got a system_error exception: " << e.what() << '\n'; + auto ec = e.code(); + cerr << "category: " << ec.category().name() << '\n'; + cerr << "value: " << ec.value() << '\n'; + cerr << "message: " << ec.message() << '\n'; + cerr << "\nNote: if g++ may need '-pthread' or similar in " + "compile/link line" << '\n'; + } + catch(...) { + cerr << "got another exception: " << '\n'; + } + if (pt_fd >= 0) + close(pt_fd); + return 0; +} diff --git a/testing/sg_tst_excl.cpp b/testing/sg_tst_excl.cpp new file mode 100644 index 0000000..5b4e695 --- /dev/null +++ b/testing/sg_tst_excl.cpp @@ -0,0 +1,678 @@ +/* + * Copyright (c) 2013-2014 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_io_linux.h" + +static const char * version_str = "1.09 20140828"; +static const char * util_name = "sg_tst_excl"; + +/* This is a test program for checking O_EXCL on open() works. It uses + * multiple threads and can be run as multiple processes and attempts + * to "break" O_EXCL. The strategy is to open a device O_EXCL|O_NONBLOCK + * and do a double increment on a LB then close it. Prior to the first + * increment, the value is checked for even or odd. Assuming the count + * starts as an even (typically 0) then it should remain even. Odd instances + * are counted and reported at the end of the program, after all threads + * have completed. + * + * This is C++ code with some things from C++11 (e.g. threads) and was + * only just able to compile (when some things were reverted) with gcc/g++ + * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support + * was not available until g++ version 4.8.1 and that is only currently + * found in Fedora 19 . + * + * The build uses various object files from the /lib directory + * which is assumed to be a sibling of this examples directory. Those + * object files in the lib directory can be built with: + * cd ; ./configure ; cd lib; make + * Then to build sg_tst_excl concatenate the next 3 lines: + * g++ -Wall -std=c++11 -pthread -I ../include ../lib/sg_lib.o + * ../lib/sg_lib_data.o ../lib/sg_io_linux.o -o sg_tst_excl + * sg_tst_excl.cpp + * or use the C++ Makefile in that directory: + * make -f Makefile.cplus sg_tst_excl + * + * Currently this utility is Linux only and assumes the SG_IO v3 interface + * which is supported by sg and block devices (but not bsg devices which + * require the SG_IO v4 interface). This restriction is relaxed in the + * sg_tst_excl2 variant of this utility. + * + * BEWARE: this utility modifies a logical block (default LBA 1000) on the + * given device. + * + */ + +using namespace std; +using namespace std::chrono; + +#define DEF_NUM_PER_THREAD 200 +#define DEF_NUM_THREADS 4 +#define DEF_WAIT_MS 0 /* 0: yield; -1: don't wait; -2: sleep(0) */ + + +#define DEF_LBA 1000 + +#define EBUFF_SZ 256 + +static mutex odd_count_mutex; +static mutex console_mutex; +static unsigned int odd_count; +static unsigned int ebusy_count; +static unsigned int eagain_count; + + +static void +usage(void) +{ + printf("Usage: %s [-b] [-f] [-h] [-l ] [-n ] " + "[-t ]\n" + " [-V] [-w ] [-x] [-xx] " + "\n", util_name); + printf(" where\n"); + printf(" -b block on open (def: O_NONBLOCK)\n"); + printf(" -f force: any SCSI disk (def: only " + "scsi_debug)\n"); + printf(" WARNING: written to\n"); + printf(" -h print this usage message then exit\n"); + printf(" -l logical block to increment (def: %u)\n", + DEF_LBA); + printf(" -n number of loops per thread " + "(def: %d)\n", DEF_NUM_PER_THREAD); + printf(" -t number of threads (def: %d)\n", + DEF_NUM_THREADS); + printf(" -V print version number then exit\n"); + printf(" -w >0: sleep_for(); =0: " + "yield(); -1: no\n" + " wait; -2: sleep(0) (def: %d)\n", + DEF_WAIT_MS); + printf(" -x don't use O_EXCL on first thread " + "(def: use\n" + " O_EXCL on all threads)\n" + " -xx don't use O_EXCL on any thread\n\n"); + printf("Test O_EXCL open flag with Linux sg driver. Each open/close " + "cycle with the\nO_EXCL flag does a double increment on " + "lba (using its first 4 bytes).\nEach increment uses a READ_16, " + "READ_16, increment, WRITE_16 cycle. The two\nREAD_16s are " + "launched asynchronously. Note that '-xx' will run test\n" + "without any O_EXCL flags.\n"); +} + + +#define READ16_REPLY_LEN 512 +#define READ16_CMD_LEN 16 +#define WRITE16_REPLY_LEN 512 +#define WRITE16_CMD_LEN 16 + +/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for + * wait_ms milliseconds if wait_ms is positive. + * Reads lba (twice) and treats the first 4 bytes as an int (SCSI endian), + * increments it and writes it back. Repeats so that happens twice. Then + * closes dev_name. If an error occurs returns -1 else returns 0 if + * first int read from lba is even otherwise returns 1. */ +static int +do_rd_inc_wr_twice(const char * dev_name, unsigned int lba, int block, + int excl, int wait_ms, int id, unsigned int & ebusy, + unsigned int & eagains) +{ + int k, sg_fd, ok, res; + int odd = 0; + unsigned int u = 0; + struct sg_io_hdr pt, pt2; + unsigned char r16CmdBlk [READ16_CMD_LEN] = + {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + unsigned char w16CmdBlk [WRITE16_CMD_LEN] = + {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + unsigned char sense_buffer[64]; + unsigned char lb[READ16_REPLY_LEN]; + char ebuff[EBUFF_SZ]; + int open_flags = O_RDWR; + + r16CmdBlk[6] = w16CmdBlk[6] = (lba >> 24) & 0xff; + r16CmdBlk[7] = w16CmdBlk[7] = (lba >> 16) & 0xff; + r16CmdBlk[8] = w16CmdBlk[8] = (lba >> 8) & 0xff; + r16CmdBlk[9] = w16CmdBlk[9] = lba & 0xff; + if (! block) + open_flags |= O_NONBLOCK; + if (excl) + open_flags |= O_EXCL; + + while (((sg_fd = open(dev_name, open_flags)) < 0) && + (EBUSY == errno)) { + ++ebusy; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (sg_fd < 0) { + snprintf(ebuff, EBUFF_SZ, + "do_rd_inc_wr_twice: error opening file: %s", dev_name); + perror(ebuff); + return -1; + } + + for (k = 0; k < 2; ++k) { + /* Prepare READ_16 command */ + memset(&pt, 0, sizeof(pt)); + pt.interface_id = 'S'; + pt.cmd_len = sizeof(r16CmdBlk); + pt.mx_sb_len = sizeof(sense_buffer); + pt.dxfer_direction = SG_DXFER_FROM_DEV; + pt.dxfer_len = READ16_REPLY_LEN; + pt.dxferp = lb; + pt.cmdp = r16CmdBlk; + pt.sbp = sense_buffer; + pt.timeout = 20000; /* 20000 millisecs == 20 seconds */ + pt.pack_id = id; + + // queue up two READ_16s to same LBA + if (write(sg_fd, &pt, sizeof(pt)) < 0) { + { + lock_guard lg(console_mutex); + + perror("do_rd_inc_wr_twice: write(sg, READ_16)"); + } + close(sg_fd); + return -1; + } + pt2 = pt; + if (write(sg_fd, &pt2, sizeof(pt2)) < 0) { + { + lock_guard lg(console_mutex); + + perror("do_rd_inc_wr_twice: write(sg, READ_16) 2"); + } + close(sg_fd); + return -1; + } + + while (((res = read(sg_fd, &pt, sizeof(pt))) < 0) && + (EAGAIN == errno)) { + ++eagains; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (res < 0) { + { + lock_guard lg(console_mutex); + + perror("do_rd_inc_wr_twice: read(sg, READ_16)"); + } + close(sg_fd); + return -1; + } + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&pt)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + { + lock_guard lg(console_mutex); + + fprintf(stderr, "Recovered error on READ_16, continuing\n"); + } + ok = 1; + break; + default: /* won't bother decoding other categories */ + { + lock_guard lg(console_mutex); + + sg_chk_n_print3("READ_16 command error", &pt, 1); + } + break; + } + if (ok) { + while (((res = read(sg_fd, &pt2, sizeof(pt2))) < 0) && + (EAGAIN == errno)) { + ++eagains; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (res < 0) { + { + lock_guard lg(console_mutex); + + perror("do_rd_inc_wr_twice: read(sg, READ_16) 2"); + } + close(sg_fd); + return -1; + } + pt = pt2; + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&pt)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + { + lock_guard lg(console_mutex); + + fprintf(stderr, "Recovered error on READ_16, continuing " + "2\n"); + } + ok = 1; + break; + default: /* won't bother decoding other categories */ + { + lock_guard lg(console_mutex); + + sg_chk_n_print3("READ_16 command error 2", &pt, 1); + } + break; + } + } + if (! ok) { + close(sg_fd); + return -1; + } + + u = (lb[0] << 24) + (lb[1] << 16) + (lb[2] << 8) + lb[3]; + if (0 == k) + odd = (1 == (u % 2)); + ++u; + lb[0] = (u >> 24) & 0xff; + lb[1] = (u >> 16) & 0xff; + lb[2] = (u >> 8) & 0xff; + lb[3] = u & 0xff; + + if (wait_ms > 0) /* allow daylight for bad things ... */ + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); + else if (-2 == wait_ms) + sleep(0); // process yield ?? + + /* Prepare WRITE_16 command */ + memset(&pt, 0, sizeof(pt)); + pt.interface_id = 'S'; + pt.cmd_len = sizeof(w16CmdBlk); + pt.mx_sb_len = sizeof(sense_buffer); + pt.dxfer_direction = SG_DXFER_TO_DEV; + pt.dxfer_len = WRITE16_REPLY_LEN; + pt.dxferp = lb; + pt.cmdp = w16CmdBlk; + pt.sbp = sense_buffer; + pt.timeout = 20000; /* 20000 millisecs == 20 seconds */ + pt.pack_id = id; + + if (ioctl(sg_fd, SG_IO, &pt) < 0) { + { + lock_guard lg(console_mutex); + + perror("do_rd_inc_wr_twice: WRITE_16 SG_IO ioctl error"); + } + close(sg_fd); + return -1; + } + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&pt)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + { + lock_guard lg(console_mutex); + + fprintf(stderr, "Recovered error on WRITE_16, continuing\n"); + } + ok = 1; + break; + default: /* won't bother decoding other categories */ + { + lock_guard lg(console_mutex); + + sg_chk_n_print3("WRITE_16 command error", &pt, 1); + } + break; + } + if (! ok) { + close(sg_fd); + return -1; + } + } + close(sg_fd); + return odd; +} + + + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 + +/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field + * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success, + * else -1 . */ +static int +do_inquiry_prod_id(const char * dev_name, int block, int wait_ms, + unsigned int & ebusys, char * b, int b_mlen) +{ + int sg_fd, ok, ret; + struct sg_io_hdr pt; + unsigned char inqCmdBlk [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + unsigned char sense_buffer[64]; + char ebuff[EBUFF_SZ]; + int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */ + + if (! block) + open_flags |= O_NONBLOCK; + while (((sg_fd = open(dev_name, open_flags)) < 0) && + (EBUSY == errno)) { + ++ebusys; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (sg_fd < 0) { + snprintf(ebuff, EBUFF_SZ, + "do_inquiry_prod_id: error opening file: %s", dev_name); + perror(ebuff); + return -1; + } + /* Prepare INQUIRY command */ + memset(&pt, 0, sizeof(pt)); + pt.interface_id = 'S'; + pt.cmd_len = sizeof(inqCmdBlk); + /* pt.iovec_count = 0; */ /* memset takes care of this */ + pt.mx_sb_len = sizeof(sense_buffer); + pt.dxfer_direction = SG_DXFER_FROM_DEV; + pt.dxfer_len = INQ_REPLY_LEN; + pt.dxferp = inqBuff; + pt.cmdp = inqCmdBlk; + pt.sbp = sense_buffer; + pt.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* pt.flags = 0; */ /* take defaults: indirect IO, etc */ + /* pt.pack_id = 0; */ + /* pt.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &pt) < 0) { + perror("do_inquiry_prod_id: Inquiry SG_IO ioctl error"); + close(sg_fd); + return -1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&pt)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + fprintf(stderr, "Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &pt, 1); + break; + } + if (ok) { + /* Good, so fetch Product ID from response, copy to 'b' */ + if (b_mlen > 0) { + if (b_mlen > 16) { + memcpy(b, inqBuff + 16, 16); + b[16] = '\0'; + } else { + memcpy(b, inqBuff + 16, b_mlen - 1); + b[b_mlen - 1] = '\0'; + } + } + ret = 0; + } else + ret = -1; + close(sg_fd); + return ret; +} + +static void +work_thread(const char * dev_name, unsigned int lba, int id, int block, + int excl, int num, int wait_ms) +{ + unsigned int thr_odd_count = 0; + unsigned int thr_ebusy_count = 0; + unsigned int thr_eagain_count = 0; + int k, res; + + { + lock_guard lg(console_mutex); + + cerr << "Enter work_thread id=" << id << " excl=" << excl << " block=" + << block << endl; + } + for (k = 0; k < num; ++k) { + res = do_rd_inc_wr_twice(dev_name, lba, block, excl, wait_ms, k, + thr_ebusy_count, thr_eagain_count); + if (res < 0) + break; + if (res) + ++thr_odd_count; + } + { + lock_guard lg(console_mutex); + + if (k < num) + cerr << "thread id=" << id << " FAILed at iteration: " << k << + '\n'; + else + cerr << "thread id=" << id << " normal exit" << '\n'; + } + { + lock_guard lg(odd_count_mutex); + + odd_count += thr_odd_count; + ebusy_count += thr_ebusy_count; + eagain_count += thr_eagain_count; + } +} + + +int +main(int argc, char * argv[]) +{ + int k, res; + int block = 0; + int force = 0; + unsigned int lba = DEF_LBA; + int num_per_thread = DEF_NUM_PER_THREAD; + int num_threads = DEF_NUM_THREADS; + int wait_ms = DEF_WAIT_MS; + int no_o_excl = 0; + char * dev_name = NULL; + char b[64]; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-b", argv[k], 2)) + ++block; + else if (0 == memcmp("-f", argv[k], 2)) + ++force; + else if (0 == memcmp("-h", argv[k], 2)) { + usage(); + return 0; + } else if (0 == memcmp("-l", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + lba = (unsigned int)atoi(argv[k]); + else + break; + } else if (0 == memcmp("-n", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_per_thread = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-t", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_threads = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-V", argv[k], 2)) { + printf("%s version: %s\n", util_name, version_str); + return 0; + } else if (0 == memcmp("-w", argv[k], 2)) { + ++k; + if ((k < argc) && (isdigit(*argv[k]) || ('-' == *argv[k]))) { + if ('-' == *argv[k]) + wait_ms = - atoi(argv[k] + 1); + else + wait_ms = atoi(argv[k]); + } else + break; + } else if (0 == memcmp("-xxx", argv[k], 4)) + no_o_excl += 3; + else if (0 == memcmp("-xx", argv[k], 3)) + no_o_excl += 2; + else if (0 == memcmp("-x", argv[k], 2)) + ++no_o_excl; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + dev_name = NULL; + break; + } + else if (! dev_name) + dev_name = argv[k]; + else { + printf("too many arguments\n"); + dev_name = 0; + break; + } + } + if (0 == dev_name) { + usage(); + return 1; + } + try { + struct stat a_stat; + + if (stat(dev_name, &a_stat) < 0) { + perror("stat() on dev_name failed"); + return 1; + } + if (! S_ISCHR(a_stat.st_mode)) { + fprintf(stderr, "%s should be a sg device which is a char " + "device. %s\n", dev_name, dev_name); + fprintf(stderr, "is not a char device and damage could be done " + "if it is a BLOCK\ndevice, exiting ...\n"); + return 1; + } + if (! force) { + res = do_inquiry_prod_id(dev_name, block, wait_ms, ebusy_count, + b, sizeof(b)); + if (res) { + fprintf(stderr, "INQUIRY failed on %s\n", dev_name); + return 1; + } + // For safety, since written to, only permit scsi_debug + // devices. Bypass this with '-f' option. + if (0 != memcmp("scsi_debug", b, 10)) { + fprintf(stderr, "Since this utility writes to LBA %d, only " + "devices with scsi_debug\nproduct ID accepted.\n", + lba); + return 2; + } + } + + vector vt; + + for (k = 0; k < num_threads; ++k) { + int excl = 1; + + if (no_o_excl > 1) + excl = 0; + else if ((0 == k) && (1 == no_o_excl)) + excl = 0; + + thread * tp = new thread {work_thread, dev_name, lba, k, block, + excl, num_per_thread, wait_ms}; + vt.push_back(tp); + } + + // g++ 4.7.3 didn't like range-for loop here + for (k = 0; k < (int)vt.size(); ++k) + vt[k]->join(); + + for (k = 0; k < (int)vt.size(); ++k) + delete vt[k]; + + if (no_o_excl) + cout << "Odd count: " << odd_count << endl; + else + cout << "Expecting odd count of 0, got " << odd_count << endl; + cout << "Number of EBUSYs: " << ebusy_count << endl; + cout << "Number of EAGAINs: " << eagain_count << endl; + + } + catch(system_error& e) { + cerr << "got a system_error exception: " << e.what() << '\n'; + auto ec = e.code(); + cerr << "category: " << ec.category().name() << '\n'; + cerr << "value: " << ec.value() << '\n'; + cerr << "message: " << ec.message() << '\n'; + cerr << "\nNote: if g++ may need '-pthread' or similar in " + "compile/link line" << '\n'; + } + catch(...) { + cerr << "got another exception: " << '\n'; + } + return 0; +} diff --git a/testing/sg_tst_excl2.cpp b/testing/sg_tst_excl2.cpp new file mode 100644 index 0000000..24d3677 --- /dev/null +++ b/testing/sg_tst_excl2.cpp @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2013-2014 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_pt.h" + +static const char * version_str = "1.07 20140828"; +static const char * util_name = "sg_tst_excl2"; + +/* This is a test program for checking O_EXCL on open() works. It uses + * multiple threads and can be run as multiple processes and attempts + * to "break" O_EXCL. The strategy is to open a device O_EXCL|O_NONBLOCK + * and do a double increment on a LB then close it. Prior to the first + * increment, the value is checked for even or odd. Assuming the count + * starts as an even (typically 0) then it should remain even. Odd instances + * are counted and reported at the end of the program, after all threads + * have completed. + * + * This is C++ code with some things from C++11 (e.g. threads) and was + * only just able to compile (when some things were reverted) with gcc/g++ + * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support + * was not available until g++ version 4.8.1 and that is only currently + * found in Fedora 19 . + * + * The build uses various object files from the /lib directory + * which is assumed to be a sibling of this examples directory. Those + * object files in the lib directory can be built with: + * cd ; ./configure ; cd lib; make + * Then to build sg_tst_excl2 concatenate the next 3 lines: + * g++ -Wall -std=c++11 -pthread -I ../include ../lib/sg_lib.o + * ../lib/sg_lib_data.o ../lib/sg_pt_linux.o -o sg_tst_excl2 + * sg_tst_excl2.cpp + * Alternatively use 'make -f Makefile.cplus sg_tst_excl2' + * + * BEWARE: this utility modifies a logical block (default LBA 1000) on the + * given device. + * + * Test breaks sg driver in lk 3.10.4 but works with proposed fix so should + * work soon thereafter. Works on standard block driver (e.g. /dev/sdc) in + * lk 3.10.4 . Fails on bsg driver in lk 3.10.4 because it ignores the + * O_EXCL flag (and that is unlikely to change). + * + */ + +using namespace std; +using namespace std::chrono; + +#define DEF_NUM_PER_THREAD 200 +#define DEF_NUM_THREADS 4 +#define DEF_WAIT_MS 0 /* 0: yield; -1: don't wait; -2: sleep(0) */ + +#define DEF_LBA 1000 + +#define EBUFF_SZ 256 + + +static mutex odd_count_mutex; +static mutex console_mutex; +static unsigned int odd_count; +static unsigned int ebusy_count; + + +static void +usage(void) +{ + printf("Usage: %s [-b] [-f] [-h] [-l ] [-n ] " + "[-t ]\n" + " [-V] [-w ] [-x] " + "\n", util_name); + printf(" where\n"); + printf(" -b block on open (def: O_NONBLOCK)\n"); + printf(" -f force: any SCSI disk (def: only " + "scsi_debug)\n"); + printf(" WARNING: written to\n"); + printf(" -h print this usage message then exit\n"); + printf(" -l logical block to increment (def: %u)\n", + DEF_LBA); + printf(" -n number of loops per thread " + "(def: %d)\n", DEF_NUM_PER_THREAD); + printf(" -t number of threads (def: %d)\n", + DEF_NUM_THREADS); + printf(" -V print version number then exit\n"); + printf(" -w >0: sleep_for(); =0: " + "yield(); -1: no\n" + " wait; -2: sleep(0) (def: %d)\n", + DEF_WAIT_MS); + printf(" -x don't use O_EXCL on first thread " + "(def: use\n" + " O_EXCL on all threads)\n\n"); + printf("Test O_EXCL open flag with pass-through drivers. Each " + "open/close cycle with\nthe O_EXCL flag does a double increment " + "on lba (using its first 4 bytes).\n"); +} + +/* Assumed a lock (mutex) held when pt_err() is called */ +static int +pt_err(int res) +{ + if (res < 0) + fprintf(stderr, " pass through os error: %s\n", safe_strerror(-res)); + else if (SCSI_PT_DO_BAD_PARAMS == res) + fprintf(stderr, " bad pass through setup\n"); + else if (SCSI_PT_DO_TIMEOUT == res) + fprintf(stderr, " pass through timeout\n"); + else + fprintf(stderr, " do_scsi_pt error=%d\n", res); + return -1; +} + +/* Assumed a lock (mutex) held when pt_cat_no_good() is called */ +static int +pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp) +{ + int slen; + char b[256]; + const int bl = (int)sizeof(b); + + switch (cat) { + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b); + fprintf(stderr, " scsi status: %s\n", b); + break; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptp); + sg_get_sense_str("", sbp, slen, 1, bl, b); + fprintf(stderr, "%s", b); + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + get_scsi_pt_transport_err_str(ptp, bl, b); + fprintf(stderr, " transport: %s", b); + break; + case SCSI_PT_RESULT_OS_ERR: + get_scsi_pt_os_err_str(ptp, bl, b); + fprintf(stderr, " os: %s", b); + break; + default: + fprintf(stderr, " unknown pt result category (%d)\n", cat); + break; + } + return -1; +} + +#define READ16_REPLY_LEN 512 +#define READ16_CMD_LEN 16 +#define WRITE16_REPLY_LEN 512 +#define WRITE16_CMD_LEN 16 + +/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for + * wait_ms milliseconds if wait_ms is positive. Reads lba and treats the + * first 4 bytes as an int (SCSI endian), increments it and writes it back. + * Repeats so that happens twice. Then closes dev_name. If an error occurs + * returns -1 else returns 0 if first int read is even otherwise returns 1. */ +static int +do_rd_inc_wr_twice(const char * dev_name, unsigned int lba, int block, + int excl, int wait_ms, unsigned int & ebusys) +{ + int k, sg_fd, res, cat; + int odd = 0; + unsigned int u = 0; + struct sg_pt_base * ptp = NULL; + unsigned char r16CmdBlk [READ16_CMD_LEN] = + {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + unsigned char w16CmdBlk [WRITE16_CMD_LEN] = + {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + unsigned char sense_buffer[64]; + unsigned char lb[READ16_REPLY_LEN]; + char ebuff[EBUFF_SZ]; + int open_flags = O_RDWR; + + r16CmdBlk[6] = w16CmdBlk[6] = (lba >> 24) & 0xff; + r16CmdBlk[7] = w16CmdBlk[7] = (lba >> 16) & 0xff; + r16CmdBlk[8] = w16CmdBlk[8] = (lba >> 8) & 0xff; + r16CmdBlk[9] = w16CmdBlk[9] = lba & 0xff; + if (! block) + open_flags |= O_NONBLOCK; + if (excl) + open_flags |= O_EXCL; + + while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) && + (-EBUSY == sg_fd)) { + ++ebusys; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); // thread yield + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (sg_fd < 0) { + snprintf(ebuff, EBUFF_SZ, + "do_rd_inc_wr_twice: error opening file: %s", dev_name); + { + lock_guard lg(console_mutex); + + perror(ebuff); + } + return -1; + } + + ptp = construct_scsi_pt_obj(); + for (k = 0; k < 2; ++k) { + /* Prepare READ_16 command */ + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, r16CmdBlk, sizeof(r16CmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + set_scsi_pt_data_in(ptp, lb, READ16_REPLY_LEN); + res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "READ_16 do_scsi_pt() submission error\n"); + res = pt_err(res); + } + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "READ_16 do_scsi_pt() category problem\n"); + res = pt_cat_no_good(cat, ptp, sense_buffer); + } + goto err; + } + + u = (lb[0] << 24) + (lb[1] << 16) + (lb[2] << 8) + lb[3]; + // Assuming u starts test as even (probably 0), expect it to stay even + if (0 == k) + odd = (1 == (u % 2)); + ++u; + lb[0] = (u >> 24) & 0xff; + lb[1] = (u >> 16) & 0xff; + lb[2] = (u >> 8) & 0xff; + lb[3] = u & 0xff; + + if (wait_ms > 0) /* allow daylight for bad things ... */ + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); // thread yield + else if (-2 == wait_ms) + sleep(0); // process yield ?? + + /* Prepare WRITE_16 command */ + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, w16CmdBlk, sizeof(w16CmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + set_scsi_pt_data_out(ptp, lb, WRITE16_REPLY_LEN); + res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "WRITE_16 do_scsi_pt() submission error\n"); + res = pt_err(res); + } + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "WRITE_16 do_scsi_pt() category problem\n"); + res = pt_cat_no_good(cat, ptp, sense_buffer); + } + goto err; + } + } +err: + if (ptp) + destruct_scsi_pt_obj(ptp); + scsi_pt_close_device(sg_fd); + return odd; +} + + + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 + +/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field + * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success, + * else -1 . */ +static int +do_inquiry_prod_id(const char * dev_name, int block, int wait_ms, + unsigned int & ebusys, char * b, int b_mlen) +{ + int sg_fd, res, cat; + struct sg_pt_base * ptp = NULL; + unsigned char inqCmdBlk [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + unsigned char sense_buffer[64]; + char ebuff[EBUFF_SZ]; + int open_flags = O_RDWR; /* since O_EXCL | O_RDONLY gives EPERM */ + + if (! block) + open_flags |= O_NONBLOCK; + while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) && + (-EBUSY == sg_fd)) { + ++ebusys; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); // thread yield + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (sg_fd < 0) { + snprintf(ebuff, EBUFF_SZ, + "do_inquiry_prod_id: error opening file: %s", dev_name); + perror(ebuff); + return -1; + } + /* Prepare INQUIRY command */ + ptp = construct_scsi_pt_obj(); + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, inqCmdBlk, sizeof(inqCmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + set_scsi_pt_data_in(ptp, inqBuff, INQ_REPLY_LEN); + res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "INQUIRY do_scsi_pt() submission error\n"); + res = pt_err(res); + } + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "INQUIRY do_scsi_pt() category problem\n"); + res = pt_cat_no_good(cat, ptp, sense_buffer); + } + goto err; + } + + /* Good, so fetch Product ID from response, copy to 'b' */ + if (b_mlen > 0) { + if (b_mlen > 16) { + memcpy(b, inqBuff + 16, 16); + b[16] = '\0'; + } else { + memcpy(b, inqBuff + 16, b_mlen - 1); + b[b_mlen - 1] = '\0'; + } + } +err: + if (ptp) + destruct_scsi_pt_obj(ptp); + close(sg_fd); + return 0; +} + +static void +work_thread(const char * dev_name, unsigned int lba, int id, int block, + int excl, int num, int wait_ms) +{ + unsigned int thr_odd_count = 0; + unsigned int thr_ebusy_count = 0; + int k, res; + + { + lock_guard lg(console_mutex); + + cerr << "Enter work_thread id=" << id << " excl=" << excl << " block=" + << block << endl; + } + for (k = 0; k < num; ++k) { + res = do_rd_inc_wr_twice(dev_name, lba, block, excl, wait_ms, + thr_ebusy_count); + if (res < 0) + break; + if (res) + ++thr_odd_count; + } + { + lock_guard lg(console_mutex); + + if (k < num) + cerr << "thread id=" << id << " FAILed at iteration: " << k << + '\n'; + else + cerr << "thread id=" << id << " normal exit" << '\n'; + } + + { + lock_guard lg(odd_count_mutex); + + odd_count += thr_odd_count; + ebusy_count += thr_ebusy_count; + } +} + + +int +main(int argc, char * argv[]) +{ + int k, res; + int block = 0; + int force = 0; + unsigned int lba = DEF_LBA; + int num_per_thread = DEF_NUM_PER_THREAD; + int num_threads = DEF_NUM_THREADS; + int wait_ms = DEF_WAIT_MS; + int exclude_o_excl = 0; + char * dev_name = NULL; + char b[64]; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-b", argv[k], 2)) + ++block; + else if (0 == memcmp("-f", argv[k], 2)) + ++force; + else if (0 == memcmp("-h", argv[k], 2)) { + usage(); + return 0; + } else if (0 == memcmp("-l", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + lba = (unsigned int)atoi(argv[k]); + else + break; + } else if (0 == memcmp("-n", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_per_thread = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-t", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_threads = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-V", argv[k], 2)) { + printf("%s version: %s\n", util_name, version_str); + return 0; + } else if (0 == memcmp("-w", argv[k], 2)) { + ++k; + if ((k < argc) && (isdigit(*argv[k]) || ('-' == *argv[k]))) { + if ('-' == *argv[k]) + wait_ms = - atoi(argv[k] + 1); + else + wait_ms = atoi(argv[k]); + } else + break; + } else if (0 == memcmp("-x", argv[k], 2)) + ++exclude_o_excl; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + dev_name = NULL; + break; + } + else if (! dev_name) + dev_name = argv[k]; + else { + printf("too many arguments\n"); + dev_name = 0; + break; + } + } + if (0 == dev_name) { + usage(); + return 1; + } + try { + + if (! force) { + res = do_inquiry_prod_id(dev_name, block, wait_ms, ebusy_count, + b, sizeof(b)); + if (res) { + fprintf(stderr, "INQUIRY failed on %s\n", dev_name); + return 1; + } + // For safety, since written to, only permit scsi_debug + // devices. Bypass this with '-f' option. + if (0 != memcmp("scsi_debug", b, 10)) { + fprintf(stderr, "Since this utility writes to LBA %d, only " + "devices with scsi_debug\nproduct ID accepted.\n", + lba); + return 2; + } + } + + vector vt; + + for (k = 0; k < num_threads; ++k) { + int excl = ((0 == k) && exclude_o_excl) ? 0 : 1; + + thread * tp = new thread {work_thread, dev_name, lba, k, block, + excl, num_per_thread, wait_ms}; + vt.push_back(tp); + } + + for (k = 0; k < (int)vt.size(); ++k) + vt[k]->join(); + + for (k = 0; k < (int)vt.size(); ++k) + delete vt[k]; + + cout << "Expecting odd count of 0, got " << odd_count << endl; + cout << "Number of EBUSYs: " << ebusy_count << endl; + + } + catch(system_error& e) { + cerr << "got a system_error exception: " << e.what() << '\n'; + auto ec = e.code(); + cerr << "category: " << ec.category().name() << '\n'; + cerr << "value: " << ec.value() << '\n'; + cerr << "message: " << ec.message() << '\n'; + cerr << "\nNote: if g++ may need '-pthread' or similar in " + "compile/link line" << '\n'; + } + catch(...) { + cerr << "got another exception: " << '\n'; + } + return 0; +} diff --git a/testing/sg_tst_excl3.cpp b/testing/sg_tst_excl3.cpp new file mode 100644 index 0000000..6b6b880 --- /dev/null +++ b/testing/sg_tst_excl3.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2013-2014 Douglas Gilbert. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sg_lib.h" +#include "sg_pt.h" + +static const char * version_str = "1.05 20140828"; +static const char * util_name = "sg_tst_excl3"; + +/* This is a test program for checking O_EXCL on open() works. It uses + * multiple threads and can be run as multiple processes and attempts + * to "break" O_EXCL. The strategy is to open a device O_EXCL|O_NONBLOCK + * and do a double increment on a LB then close it from a single thread. + * the remaining threads open that device O_NONBLOCK and do a read and + * note of the number is odd. Assuming the count starts as an even + * (typically 0) then it should remain even. Odd instances + * are counted and reported at the end of the program, after all threads + * have completed. + * + * This is C++ code with some things from C++11 (e.g. threads) and was + * only just able to compile (when some things were reverted) with gcc/g++ + * version 4.7.3 found in Ubuntu 13.04 . C++11 "feature complete" support + * was not available until g++ version 4.8.1 and that is found in Fedora + * 19 and Ubuntu 13.10 . + * + * The build uses various object files from the /lib directory + * which is assumed to be a sibling of this examples directory. Those + * object files in the lib directory can be built with: + * cd ; ./configure ; cd lib; make + * Then to build sg_tst_excl3 concatenate the next 3 lines: + * g++ -Wall -std=c++11 -pthread -I ../include ../lib/sg_lib.o + * ../lib/sg_lib_data.o ../lib/sg_pt_linux.o -o sg_tst_excl3 + * sg_tst_excl3.cpp + * Alternatively use 'make -f Makefile.cplus sg_tst_excl3' + * + * BEWARE: this utility modifies a logical block (default LBA 1000) on the + * given device. + * + * Test breaks sg driver in lk 3.10.4 but works with proposed fix so should + * work soon thereafter. It works on standard block driver (e.g. /dev/sdc) + * in lk 3.10.4 (most of the time). It fails on bsg driver in lk 3.10.4 + * because it ignores the O_EXCL flag (and that is unlikely to change in + * the short term). + * + */ + +using namespace std; +using namespace std::chrono; + +#define DEF_NUM_PER_THREAD 200 +#define DEF_NUM_THREADS 4 +#define DEF_WAIT_MS 0 /* 0: yield; -1: don't wait; -2: sleep(0) */ + +#define DEF_LBA 1000 + +#define EBUFF_SZ 256 + + +static mutex odd_count_mutex; +static mutex console_mutex; +static unsigned int odd_count; +static unsigned int ebusy_count; + + +static void +usage(void) +{ + printf("Usage: %s [-b] [-f] [-h] [-l ] [-n ]\n" + " [-R] [-t ] [-V] [-w ] " + "[-x]\n" + " \n", util_name); + printf(" where\n"); + printf(" -b block on open (def: O_NONBLOCK)\n"); + printf(" -f force: any SCSI disk (def: only " + "scsi_debug)\n"); + printf(" WARNING: written to\n"); + printf(" -h print this usage message then exit\n"); + printf(" -l logical block to increment (def: %u)\n", + DEF_LBA); + printf(" -n number of loops per thread " + "(def: %d)\n", DEF_NUM_PER_THREAD); + printf(" -R all readers; so first thread (id=0) " + "just reads\n"); + printf(" -t number of threads (def: %d)\n", + DEF_NUM_THREADS); + printf(" -V print version number then exit\n"); + printf(" -w >0: sleep_for(); =0: " + "yield(); -1: no\n" + " wait; -2: sleep(0) (def: %d)\n", + DEF_WAIT_MS); + printf(" -x don't use O_EXCL on first thread " + "(def: use\n" + " O_EXCL on first thread)\n\n"); + printf("Test O_EXCL open flag with pass-through drivers. First thread " + "(id=0) does\nopen/close cycle with the O_EXCL flag then does a " + "double increment on\nlba (using its first 4 bytes). Remaining " + "theads read (without\nO_EXCL flag on open) and check the " + "value is even.\n"); +} + +/* Assumed a lock (mutex) held when pt_err() is called */ +static int +pt_err(int res) +{ + if (res < 0) + fprintf(stderr, " pass through os error: %s\n", safe_strerror(-res)); + else if (SCSI_PT_DO_BAD_PARAMS == res) + fprintf(stderr, " bad pass through setup\n"); + else if (SCSI_PT_DO_TIMEOUT == res) + fprintf(stderr, " pass through timeout\n"); + else + fprintf(stderr, " do_scsi_pt error=%d\n", res); + return -1; +} + +/* Assumed a lock (mutex) held when pt_cat_no_good() is called */ +static int +pt_cat_no_good(int cat, struct sg_pt_base * ptp, const unsigned char * sbp) +{ + int slen; + char b[256]; + const int bl = (int)sizeof(b); + + switch (cat) { + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + sg_get_scsi_status_str(get_scsi_pt_status_response(ptp), bl, b); + fprintf(stderr, " scsi status: %s\n", b); + break; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptp); + sg_get_sense_str("", sbp, slen, 1, bl, b); + fprintf(stderr, "%s", b); + break; + case SCSI_PT_RESULT_TRANSPORT_ERR: + get_scsi_pt_transport_err_str(ptp, bl, b); + fprintf(stderr, " transport: %s", b); + break; + case SCSI_PT_RESULT_OS_ERR: + get_scsi_pt_os_err_str(ptp, bl, b); + fprintf(stderr, " os: %s", b); + break; + default: + fprintf(stderr, " unknown pt result category (%d)\n", cat); + break; + } + return -1; +} + +#define READ16_REPLY_LEN 512 +#define READ16_CMD_LEN 16 +#define WRITE16_REPLY_LEN 512 +#define WRITE16_CMD_LEN 16 + +/* Opens dev_name and spins if busy (i.e. gets EBUSY), sleeping for + * wait_ms milliseconds if wait_ms is positive. Reads lba and treats the + * first 4 bytes as an int (SCSI endian), increments it and writes it back. + * Repeats so that happens twice. Then closes dev_name. If an error occurs + * returns -1 else returns 0 if first int read is even otherwise returns 1. */ +static int +do_rd_inc_wr_twice(const char * dev_name, int read_only, unsigned int lba, + int block, int excl, int wait_ms, unsigned int & ebusys) +{ + int k, sg_fd, res, cat; + int odd = 0; + unsigned int u = 0; + struct sg_pt_base * ptp = NULL; + unsigned char r16CmdBlk [READ16_CMD_LEN] = + {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + unsigned char w16CmdBlk [WRITE16_CMD_LEN] = + {0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + unsigned char sense_buffer[64]; + unsigned char lb[READ16_REPLY_LEN]; + char ebuff[EBUFF_SZ]; + int open_flags = O_RDWR; + + r16CmdBlk[6] = w16CmdBlk[6] = (lba >> 24) & 0xff; + r16CmdBlk[7] = w16CmdBlk[7] = (lba >> 16) & 0xff; + r16CmdBlk[8] = w16CmdBlk[8] = (lba >> 8) & 0xff; + r16CmdBlk[9] = w16CmdBlk[9] = lba & 0xff; + if (! block) + open_flags |= O_NONBLOCK; + if (excl) + open_flags |= O_EXCL; + + while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) && + (-EBUSY == sg_fd)) { + ++ebusys; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); // thread yield + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (sg_fd < 0) { + snprintf(ebuff, EBUFF_SZ, + "do_rd_inc_wr_twice: error opening file: %s", dev_name); + { + lock_guard lg(console_mutex); + + perror(ebuff); + } + return -1; + } + + ptp = construct_scsi_pt_obj(); + for (k = 0; k < 2; ++k) { + /* Prepare READ_16 command */ + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, r16CmdBlk, sizeof(r16CmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + set_scsi_pt_data_in(ptp, lb, READ16_REPLY_LEN); + res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "READ_16 do_scsi_pt() submission error\n"); + res = pt_err(res); + } + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "READ_16 do_scsi_pt() category problem\n"); + res = pt_cat_no_good(cat, ptp, sense_buffer); + } + goto err; + } + + u = (lb[0] << 24) + (lb[1] << 16) + (lb[2] << 8) + lb[3]; + // Assuming u starts test as even (probably 0), expect it to stay even + if (0 == k) + odd = (1 == (u % 2)); + + if (wait_ms > 0) /* allow daylight for bad things ... */ + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); // thread yield + else if (-2 == wait_ms) + sleep(0); // process yield ?? + + if (read_only) + break; + ++u; + lb[0] = (u >> 24) & 0xff; + lb[1] = (u >> 16) & 0xff; + lb[2] = (u >> 8) & 0xff; + lb[3] = u & 0xff; + + /* Prepare WRITE_16 command */ + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, w16CmdBlk, sizeof(w16CmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + set_scsi_pt_data_out(ptp, lb, WRITE16_REPLY_LEN); + res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1); + if (res) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "WRITE_16 do_scsi_pt() submission error\n"); + res = pt_err(res); + } + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + { + lock_guard lg(console_mutex); + + fprintf(stderr, "WRITE_16 do_scsi_pt() category problem\n"); + res = pt_cat_no_good(cat, ptp, sense_buffer); + } + goto err; + } + } +err: + if (ptp) + destruct_scsi_pt_obj(ptp); + scsi_pt_close_device(sg_fd); + return odd; +} + + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 + +/* Send INQUIRY and fetches response. If okay puts PRODUCT ID field + * in b (up to m_blen bytes). Does not use O_EXCL flag. Returns 0 on success, + * else -1 . */ +static int +do_inquiry_prod_id(const char * dev_name, int block, int wait_ms, + unsigned int & ebusys, char * b, int b_mlen) +{ + int sg_fd, res, cat; + struct sg_pt_base * ptp = NULL; + unsigned char inqCmdBlk [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + unsigned char sense_buffer[64]; + char ebuff[EBUFF_SZ]; + int open_flags = O_RDWR; /* since O_EXCL | O_RDONLY gives EPERM */ + + if (! block) + open_flags |= O_NONBLOCK; + while (((sg_fd = scsi_pt_open_flags(dev_name, open_flags, 0)) < 0) && + (-EBUSY == sg_fd)) { + ++ebusys; + if (wait_ms > 0) + this_thread::sleep_for(milliseconds{wait_ms}); + else if (0 == wait_ms) + this_thread::yield(); // thread yield + else if (-2 == wait_ms) + sleep(0); // process yield ?? + } + if (sg_fd < 0) { + snprintf(ebuff, EBUFF_SZ, + "do_inquiry_prod_id: error opening file: %s", dev_name); + perror(ebuff); + return -1; + } + /* Prepare INQUIRY command */ + ptp = construct_scsi_pt_obj(); + clear_scsi_pt_obj(ptp); + set_scsi_pt_cdb(ptp, inqCmdBlk, sizeof(inqCmdBlk)); + set_scsi_pt_sense(ptp, sense_buffer, sizeof(sense_buffer)); + set_scsi_pt_data_in(ptp, inqBuff, INQ_REPLY_LEN); + res = do_scsi_pt(ptp, sg_fd, 20 /* secs timeout */, 1); + if (res) { + fprintf(stderr, "INQUIRY do_scsi_pt() submission error\n"); + res = pt_err(res); + goto err; + } + cat = get_scsi_pt_result_category(ptp); + if (SCSI_PT_RESULT_GOOD != cat) { + fprintf(stderr, "INQUIRY do_scsi_pt() category problem\n"); + res = pt_cat_no_good(cat, ptp, sense_buffer); + goto err; + } + + /* Good, so fetch Product ID from response, copy to 'b' */ + if (b_mlen > 0) { + if (b_mlen > 16) { + memcpy(b, inqBuff + 16, 16); + b[16] = '\0'; + } else { + memcpy(b, inqBuff + 16, b_mlen - 1); + b[b_mlen - 1] = '\0'; + } + } +err: + if (ptp) + destruct_scsi_pt_obj(ptp); + close(sg_fd); + return res; +} + +static void +work_thread(const char * dev_name, unsigned int lba, int id, int block, + int excl, bool all_readers, int num, int wait_ms) +{ + unsigned int thr_odd_count = 0; + unsigned int thr_ebusy_count = 0; + int k, res; + int reader = ((id > 0) || (all_readers)); + + { + lock_guard lg(console_mutex); + + cerr << "Enter work_thread id=" << id << " excl=" << excl << " block=" + << block << " reader=" << reader << endl; + } + for (k = 0; k < num; ++k) { + res = do_rd_inc_wr_twice(dev_name, reader, lba, block, excl, + wait_ms, thr_ebusy_count); + if (res < 0) + break; + if (res) + ++thr_odd_count; + } + { + lock_guard lg(console_mutex); + + if (k < num) + cerr << "thread id=" << id << " FAILed at iteration: " << k + << '\n'; + else + cerr << "thread id=" << id << " normal exit" << '\n'; + } + + { + lock_guard lg(odd_count_mutex); + + odd_count += thr_odd_count; + ebusy_count += thr_ebusy_count; + } +} + + +int +main(int argc, char * argv[]) +{ + int k, res; + int block = 0; + int force = 0; + unsigned int lba = DEF_LBA; + int num_per_thread = DEF_NUM_PER_THREAD; + bool all_readers = false; + int num_threads = DEF_NUM_THREADS; + int wait_ms = DEF_WAIT_MS; + int exclude_o_excl = 0; + char * dev_name = NULL; + char b[64]; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-b", argv[k], 2)) + ++block; + else if (0 == memcmp("-f", argv[k], 2)) + ++force; + else if (0 == memcmp("-h", argv[k], 2)) { + usage(); + return 0; + } else if (0 == memcmp("-l", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + lba = (unsigned int)atoi(argv[k]); + else + break; + } else if (0 == memcmp("-n", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_per_thread = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-t", argv[k], 2)) { + ++k; + if ((k < argc) && isdigit(*argv[k])) + num_threads = atoi(argv[k]); + else + break; + } else if (0 == memcmp("-R", argv[k], 2)) + all_readers = true; + else if (0 == memcmp("-V", argv[k], 2)) { + printf("%s version: %s\n", util_name, version_str); + return 0; + } else if (0 == memcmp("-w", argv[k], 2)) { + ++k; + if ((k < argc) && (isdigit(*argv[k]) || ('-' == *argv[k]))) { + if ('-' == *argv[k]) + wait_ms = - atoi(argv[k] + 1); + else + wait_ms = atoi(argv[k]); + } else + break; + } else if (0 == memcmp("-x", argv[k], 2)) + ++exclude_o_excl; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + dev_name = NULL; + break; + } + else if (! dev_name) + dev_name = argv[k]; + else { + printf("too many arguments\n"); + dev_name = 0; + break; + } + } + if (0 == dev_name) { + usage(); + return 1; + } + try { + + if (! force) { + res = do_inquiry_prod_id(dev_name, block, wait_ms, ebusy_count, + b, sizeof(b)); + if (res) { + fprintf(stderr, "INQUIRY failed on %s\n", dev_name); + return 1; + } + // For safety, since written to, only permit scsi_debug + // devices. Bypass this with '-f' option. + if (0 != memcmp("scsi_debug", b, 10)) { + fprintf(stderr, "Since this utility writes to LBA %d, only " + "devices with scsi_debug\nproduct ID accepted.\n", + lba); + return 2; + } + } + + vector vt; + + for (k = 0; k < num_threads; ++k) { + int excl = ((0 == k) && (! exclude_o_excl)) ? 1 : 0; + + thread * tp = new thread {work_thread, dev_name, lba, k, block, + excl, all_readers, num_per_thread, + wait_ms}; + vt.push_back(tp); + } + + for (k = 0; k < (int)vt.size(); ++k) + vt[k]->join(); + + for (k = 0; k < (int)vt.size(); ++k) + delete vt[k]; + + cout << "Expecting odd count of 0, got " << odd_count << endl; + cout << "Number of EBUSYs: " << ebusy_count << endl; + + } + catch(system_error& e) { + cerr << "got a system_error exception: " << e.what() << '\n'; + auto ec = e.code(); + cerr << "category: " << ec.category().name() << '\n'; + cerr << "value: " << ec.value() << '\n'; + cerr << "message: " << ec.message() << '\n'; + cerr << "\nNote: if g++ may need '-pthread' or similar in " + "compile/link line" << '\n'; + } + catch(...) { + cerr << "got another exception: " << '\n'; + } + return 0; +} diff --git a/testing/sg_tst_nvme.c b/testing/sg_tst_nvme.c new file mode 100644 index 0000000..bded6b6 --- /dev/null +++ b/testing/sg_tst_nvme.c @@ -0,0 +1,956 @@ +/* + * Copyright (c) 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. + * + * This program issues a NVMe Identify command (controller or namespace) + * or a Device self-test command via the "SCSI" pass-through interface of + * this packages sg_utils library. That interface is primarily shown in + * the ../include/sg_pt.h header file. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_pt.h" +#include "sg_pt_nvme.h" +#include "sg_cmds_basic.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +static const char * version_str = "1.04 20180220"; + + +#define ME "sg_tst_nvme: " + +#define SENSE_BUFF_LEN 32 /* Arbitrary, only need 16 bytes for NVME + * (and SCSI at least 18) currently */ +#define SENSE_BUFF_NVME_LEN 16 /* 4 DWords, little endian, as byte string */ + +#define INQUIRY_CMD 0x12 /* SCSI command to get VPD page 0x83 */ +#define INQUIRY_CMDLEN 6 +#define INQUIRY_MAX_RESP_LEN 252 + +#define VPD_DEVICE_ID 0x83 + +#define NVME_NSID_ALL 0xffffffff + +#define DEF_TIMEOUT_SECS 60 + + +static struct option long_options[] = { + {"ctl", no_argument, 0, 'c'}, + {"dev-id", no_argument, 0, 'd'}, + {"dev_id", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"long", no_argument, 0, 'l'}, + {"maxlen", required_argument, 0, 'm'}, + {"nsid", required_argument, 0, 'n'}, + {"self-test", required_argument, 0, 's'}, + {"self_test", required_argument, 0, 's'}, + {"to-ms", required_argument, 0, 't'}, + {"to_ms", required_argument, 0, 't'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +/* Assume index is less than 16 */ +static const char * sg_ansi_version_arr[16] = +{ + "no conformance claimed", + "SCSI-1", /* obsolete, ANSI X3.131-1986 */ + "SCSI-2", /* obsolete, ANSI X3.131-1994 */ + "SPC", /* withdrawn, ANSI INCITS 301-1997 */ + "SPC-2", /* ANSI INCITS 351-2001, ISO/IEC 14776-452 */ + "SPC-3", /* ANSI INCITS 408-2005, ISO/IEC 14776-453 */ + "SPC-4", /* ANSI INCITS 513-2015 */ + "SPC-5", + "ecma=1, [8h]", + "ecma=1, [9h]", + "ecma=1, [Ah]", + "ecma=1, [Bh]", + "reserved [Ch]", + "reserved [Dh]", + "reserved [Eh]", + "reserved [Fh]", +}; + +#define MAX_DEV_NAMES 8 + +static const char * dev_name_arr[MAX_DEV_NAMES] = { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static int next_dev_name_pos = 0; + + +static void +usage() +{ + pr2serr("Usage: sg_tst_nvme [--ctl] [dev-id] [--help] [--long] " + "[--maxlen=LEN]\n" + " [--nsid=ID] [--self-test=ST] [--to-ms=TO] " + "[--verbose]\n" + " [--version] DEVICE [DEVICE ...]\n" + " where:\n" + " --ctl|-c only do Identify controller command\n" + " --dev-id|-d do SCSI INQUIRY for device " + " identification\n" + " VPD page (0x83) via own SNTL\n" + " --help|-h print out usage message\n" + " --long|-l add more detail to decoded output\n" + " --maxlen=LEN| -m LEN allocation length for SCSI devices\n" + " --nsid=ID| -n ID do Identify namespace with nsid set to " + "ID; if ID\n" + " is 0 then try to get nsid from " + "DEVICE.\n" + " Can also be used with self-test (def: " + "0)\n" + " --self-test=ST|-s ST do (or abort) device self-test, ST " + "can be:\n" + " 0: do nothing\n" + " 1: do short (background) " + "self-test\n" + " 2: do long self-test\n" + " 15: abort self-test in " + "progress\n" + " if nsid is 0 then test controller " + "only\n" + " if nsid is 0xffffffff (-1) then test " + "controller\n" + " and all namespaces\n" + " --to-ms=TO|-t TO command timeout in milliseconds (def: " + "60,000)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string then exit\n\n" + "Performs a NVME Identify or Device self-test Admin command on " + "each DEVICE.\nCan also simulate a SCSI device identification VPD " + "page [0x83] via\na local SNTL. --nsid= accepts '-1' for " + "0xffffffff which means all.\n" + ); +} + +static void +show_nvme_id_ctl(const uint8_t *dinp, const char *dev_name, int do_long, + uint32_t * max_nsid_p) +{ + bool got_fguid; + uint8_t ver_min, ver_ter, mtds; + uint16_t ver_maj, oacs, oncs; + uint32_t k, ver, max_nsid, npss, j, n, m; + uint64_t sz1, sz2; + const uint8_t * up; + + max_nsid = sg_get_unaligned_le32(dinp + 516); /* NN */ + if (max_nsid_p) + *max_nsid_p = max_nsid; + printf("Identify controller for %s:\n", dev_name); + printf(" Model number: %.40s\n", (const char *)(dinp + 24)); + printf(" Serial number: %.20s\n", (const char *)(dinp + 4)); + printf(" Firmware revision: %.8s\n", (const char *)(dinp + 64)); + ver = sg_get_unaligned_le32(dinp + 80); + ver_maj = (ver >> 16); + ver_min = (ver >> 8) & 0xff; + ver_ter = (ver & 0xff); + printf(" Version: %u.%u", ver_maj, ver_min); + if ((ver_maj > 1) || ((1 == ver_maj) && (ver_min > 2)) || + ((1 == ver_maj) && (2 == ver_min) && (ver_ter > 0))) + printf(".%u\n", ver_ter); + else + printf("\n"); + oacs = sg_get_unaligned_le16(dinp + 256); + if (0x1ff & oacs) { + printf(" Optional admin command support:\n"); + if (0x100 & oacs) + printf(" Doorbell buffer config\n"); + if (0x80 & oacs) + printf(" Virtualization management\n"); + if (0x40 & oacs) + printf(" NVMe-MI send and NVMe-MI receive\n"); + if (0x20 & oacs) + printf(" Directive send and directive receive\n"); + if (0x10 & oacs) + printf(" Device self-test\n"); + if (0x8 & oacs) + printf(" Namespace management and attachment\n"); + if (0x4 & oacs) + printf(" Firmware download and commit\n"); + if (0x2 & oacs) + printf(" Format NVM\n"); + if (0x1 & oacs) + printf(" Security send and receive\n"); + } else + printf(" No optional admin command support\n"); + oncs = sg_get_unaligned_le16(dinp + 256); + if (0x7f & oncs) { + printf(" Optional NVM command support:\n"); + if (0x40 & oncs) + printf(" Timestamp feature\n"); + if (0x20 & oncs) + printf(" Reservations\n"); + if (0x10 & oncs) + printf(" Save and Select fields non-zero\n"); + if (0x8 & oncs) + printf(" Write zeroes\n"); + if (0x4 & oncs) + printf(" Dataset management\n"); + if (0x2 & oncs) + printf(" Write uncorrectable\n"); + if (0x1 & oncs) + printf(" Compare\n"); + } else + printf(" No optional NVM command support\n"); + printf(" PCI vendor ID VID/SSVID: 0x%x/0x%x\n", + sg_get_unaligned_le16(dinp + 0), + sg_get_unaligned_le16(dinp + 2)); + printf(" IEEE OUI Identifier: 0x%x\n", + sg_get_unaligned_le24(dinp + 73)); + got_fguid = ! sg_all_zeros(dinp + 112, 16); + if (got_fguid) { + printf(" FGUID: 0x%02x", dinp[112]); + for (k = 1; k < 16; ++k) + printf("%02x", dinp[112 + k]); + printf("\n"); + } else if (do_long) + printf(" FGUID: 0x0\n"); + printf(" Controller ID: 0x%x\n", sg_get_unaligned_le16(dinp + 78)); + if (do_long) { + printf(" Management endpoint capabilities, over a PCIe port: %d\n", + !! (0x2 & dinp[255])); + printf(" Management endpoint capabilities, over a SMBus/I2C port: " + "%d\n", !! (0x1 & dinp[255])); + } + printf(" Number of namespaces: %u\n", max_nsid); + sz1 = sg_get_unaligned_le64(dinp + 280); /* lower 64 bits */ + sz2 = sg_get_unaligned_le64(dinp + 288); /* upper 64 bits */ + if (sz2) + printf(" Total NVM capacity: huge ...\n"); + else if (sz1) + printf(" Total NVM capacity: %" PRIu64 " bytes\n", sz1); + mtds = dinp[77]; + printf(" Maximum data transfer size: "); + if (mtds) + printf("%u pages\n", 1U << mtds); + else + printf("\n"); + + if (do_long) { + const char * const non_op = "does not process I/O"; + const char * const operat = "processes I/O"; + const char * cp; + + printf(" Total NVM capacity: 0 bytes\n"); + npss = dinp[263] + 1; + up = dinp + 2048; + for (k = 0; k < npss; ++k, up += 32) { + n = sg_get_unaligned_le16(up + 0); + n *= (0x1 & up[3]) ? 1 : 100; /* unit: 100 microWatts */ + j = n / 10; /* unit: 1 milliWatts */ + m = j % 1000; + j /= 1000; + cp = (0x2 & up[3]) ? non_op : operat; + printf(" Power state %u: Max power: ", k); + if (0 == j) { + m = n % 10; + n /= 10; + printf("%u.%u milliWatts, %s\n", n, m, cp); + } else + printf("%u.%03u Watts, %s\n", j, m, cp); + n = sg_get_unaligned_le32(up + 4); + if (0 == n) + printf(" [ENLAT], "); + else + printf(" ENLAT=%u, ", n); + n = sg_get_unaligned_le32(up + 8); + if (0 == n) + printf("[EXLAT], "); + else + printf("EXLAT=%u, ", n); + n = 0x1f & up[12]; + printf("RRT=%u, ", n); + n = 0x1f & up[13]; + printf("RRL=%u, ", n); + n = 0x1f & up[14]; + printf("RWT=%u, ", n); + n = 0x1f & up[15]; + printf("RWL=%u\n", n); + } + } +} + +static const char * rperf[] = {"Best", "Better", "Good", "Degraded"}; + +static void +show_nvme_id_ns(const uint8_t * dinp, uint32_t nsid, const char *dev_name, + int do_long) +{ + bool got_eui_128 = false; + uint32_t u, k, off, num_lbaf, flbas, flba_info, md_size, lb_size; + uint64_t ns_sz, eui_64; + + printf("Identify namespace %u for %s:\n", nsid, dev_name); + num_lbaf = dinp[25] + 1; /* spec says this is "0's based value" */ + flbas = dinp[26] & 0xf; /* index of active LBA format (for this ns) */ + ns_sz = sg_get_unaligned_le64(dinp + 0); + eui_64 = sg_get_unaligned_be64(dinp + 120); /* N.B. EUI is big endian */ + if (! sg_all_zeros(dinp + 104, 16)) + got_eui_128 = true; + printf(" Namespace size/capacity: %" PRIu64 "/%" PRIu64 + " blocks\n", ns_sz, sg_get_unaligned_le64(dinp + 8)); + printf(" Namespace utilization: %" PRIu64 " blocks\n", + sg_get_unaligned_le64(dinp + 16)); + if (got_eui_128) { /* N.B. big endian */ + printf(" NGUID: 0x%02x", dinp[104]); + for (k = 1; k < 16; ++k) + printf("%02x", dinp[104 + k]); + printf("\n"); + } else if (do_long) + printf(" NGUID: 0x0\n"); + if (eui_64) + printf(" EUI-64: 0x%" PRIx64 "\n", eui_64); /* N.B. big endian */ + printf(" Number of LBA formats: %u\n", num_lbaf); + printf(" Index LBA size: %u\n", flbas); + for (k = 0, off = 128; k < num_lbaf; ++k, off += 4) { + printf(" LBA format %u support:", k); + if (k == flbas) + printf(" <-- active\n"); + else + printf("\n"); + flba_info = sg_get_unaligned_le32(dinp + off); + md_size = flba_info & 0xffff; + lb_size = flba_info >> 16 & 0xff; + if (lb_size > 31) { + pr2serr("%s: logical block size exponent of %u implies a LB " + "size larger than 4 billion bytes, ignore\n", __func__, + lb_size); + continue; + } + lb_size = 1U << lb_size; + ns_sz *= lb_size; + ns_sz /= 500*1000*1000; + if (ns_sz & 0x1) + ns_sz = (ns_sz / 2) + 1; + else + ns_sz = ns_sz / 2; + u = (flba_info >> 24) & 0x3; + printf(" Logical block size: %u bytes\n", lb_size); + printf(" Approximate namespace size: %" PRIu64 " GB\n", ns_sz); + printf(" Metadata size: %u bytes\n", md_size); + printf(" Relative performance: %s [0x%x]\n", rperf[u], u); + } +} + +/* Invokes a NVMe Admin command via sg_utils library pass-through that will + * potentially fetch data from the device (din). Returns 0 -> success, + * various SG_LIB_* positive values or negated errno values. + * SG_LIB_NVME_STATUS is returned if the NVMe status is non-zero. */ +static int +nvme_din_admin_cmd(struct sg_pt_base * ptvp, const uint8_t *cmdp, + uint32_t cmd_len, const char *cmd_str, uint8_t *dip, + int di_len, int timeout_ms, uint16_t *sct_scp, int vb) +{ + int res, k; + uint16_t sct_sc = 0; + uint32_t result, clen; + uint8_t sense_b[SENSE_BUFF_NVME_LEN]; + uint8_t ucmd[128]; + char b[32]; + + snprintf(b, sizeof(b), "%s", cmd_str); + clen = (cmd_len > sizeof(ucmd)) ? sizeof(ucmd) : cmd_len; + memcpy(ucmd, cmdp, clen); + if (vb > 1) { + pr2serr(" %s cdb:\n", b); + hex2stderr(ucmd, clen, -1); + } + set_scsi_pt_cdb(ptvp, ucmd, clen); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + if (dip && (di_len > 0)) + set_scsi_pt_data_in(ptvp, dip, di_len); + res = do_scsi_pt(ptvp, -1, -timeout_ms, vb); + if (res) { + if (res < 0) { + res = sg_convert_errno(-res); + goto err_out; + } else { + if (SCSI_PT_DO_BAD_PARAMS == res) + pr2serr("%s: bad parameters to do_scsi_pt()\n", __func__); + else if (SCSI_PT_DO_TIMEOUT == res) + pr2serr("%s: timeout in do_scsi_pt()\n", __func__); + else if (SCSI_PT_DO_NVME_STATUS == res) { + sct_sc = get_scsi_pt_status_response(ptvp); + res = SG_LIB_NVME_STATUS; + goto nvme_status_err; + } else + pr2serr("%s: unknown error (%d) from do_scsi_pt()\n", + __func__, res); + } + res = SG_LIB_FILE_ERROR; + goto err_out; + } + + if ((vb > 2) && dip && di_len) { + k = get_scsi_pt_resid(ptvp); + pr2serr(" Data in buffer [%d bytes]:\n", di_len - k); + if (di_len > k) + hex2stderr(dip, di_len - k, -1); + if (vb > 3) + pr2serr(" do_scsi_pt(nvme): res=%d resid=%d\n", res, k); + } + sct_sc = get_scsi_pt_status_response(ptvp); + result = get_pt_result(ptvp); + k = get_scsi_pt_sense_len(ptvp); + if (vb) { + pr2serr("Status: 0x%x [SCT<<8 + SC], Result: 0x%x, Completion Q:\n", + sct_sc, result); + if (k > 0) + hex2stderr(sense_b, k, -1); + } +nvme_status_err: + if (sct_scp) + *sct_scp = sct_sc; +err_out: + return res; +} + +static void +std_inq_decode(const char * prefix, uint8_t * b, int len, int vb) +{ + int pqual, n; + + if (len < 4) + return; + pqual = (b[0] & 0xe0) >> 5; + if (0 == pqual) + printf("%s:\n", prefix); + else if (1 == pqual) + printf("%s: [qualifier indicates no connected LU]\n", prefix); + else if (3 == pqual) + printf("%s: [qualifier indicates not capable of supporting LU]\n", + prefix); + else + printf("%s: [reserved or vendor specific qualifier [%d]]\n", + prefix, pqual); + printf(" PQual=%d Device_type=%d RMB=%d LU_CONG=%d " + "version=0x%02x ", pqual, b[0] & 0x1f, !!(b[1] & 0x80), + !!(b[1] & 0x40), (unsigned int)b[2]); + printf(" [%s]\n", sg_ansi_version_arr[b[2] & 0xf]); + printf(" [AERC=%d] [TrmTsk=%d] NormACA=%d HiSUP=%d " + " Resp_data_format=%d\n", + !!(b[3] & 0x80), !!(b[3] & 0x40), !!(b[3] & 0x20), + !!(b[3] & 0x10), b[3] & 0x0f); + if (len < 5) + return; + n = b[4] + 5; + if (vb) + pr2serr(">> requested %d bytes, %d bytes available\n", len, n); + printf(" SCCS=%d ACC=%d TPGS=%d 3PC=%d Protect=%d ", + !!(b[5] & 0x80), !!(b[5] & 0x40), ((b[5] & 0x30) >> 4), + !!(b[5] & 0x08), !!(b[5] & 0x01)); + printf(" [BQue=%d]\n EncServ=%d ", !!(b[6] & 0x80), + !!(b[6] & 0x40)); + if (b[6] & 0x10) + printf("MultiP=1 (VS=%d) ", !!(b[6] & 0x20)); + else + printf("MultiP=0 "); + printf("[MChngr=%d] [ACKREQQ=%d] Addr16=%d\n [RelAdr=%d] ", + !!(b[6] & 0x08), !!(b[6] & 0x04), !!(b[6] & 0x01), + !!(b[7] & 0x80)); + printf("WBus16=%d Sync=%d [Linked=%d] [TranDis=%d] ", + !!(b[7] & 0x20), !!(b[7] & 0x10), !!(b[7] & 0x08), + !!(b[7] & 0x04)); + printf("CmdQue=%d\n", !!(b[7] & 0x02)); + if (len < 36) + return; + printf(" Vendor_identification: %.8s\n", b + 8); + printf(" Product_identification: %.16s\n", b + 16); + printf(" Product_revision_level: %.4s\n", b + 32); +} + +/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when + * successful, various SG_LIB_CAT_* positive values or -1 -> other errors. + * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so + * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION + * CODES command instead). Adds the ability to set the command abort timeout + * and the ability to report the residual count. If timeout_secs is zero + * the default command abort timeout (60 seconds) is used. + * If residp is non-NULL then the residual value is written where residp + * points. A residual value of 0 implies mx_resp_len bytes have be written + * where resp points. If the residual value equals mx_resp_len then no + * bytes have been written. */ +static int +sg_scsi_inquiry(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp, + int mx_resp_len, int timeout_secs, int * residp, + bool noisy, int vb) +{ + int res, ret, k, sense_cat, resid; + uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0}; + uint8_t sense_b[SENSE_BUFF_LEN]; + uint8_t * up; + + if (evpd) + inq_cdb[1] |= 1; + inq_cdb[2] = (uint8_t)pg_op; + sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3); + if (vb > 1) { + pr2serr(" INQUIRY cdb: "); + for (k = 0; k < INQUIRY_CMDLEN; ++k) + pr2serr("%02x ", inq_cdb[k]); + pr2serr("\n"); + } + if (resp && (mx_resp_len > 0)) { + up = (uint8_t *)resp; + up[0] = 0x7f; /* defensive prefill */ + if (mx_resp_len > 4) + up[4] = 0; + } + if (timeout_secs == 0) + timeout_secs = DEF_TIMEOUT_SECS; + set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len); + res = do_scsi_pt(ptvp, -1, timeout_secs, vb); + ret = sg_cmds_process_resp(ptvp, "inquiry", res, mx_resp_len, sense_b, + noisy, vb, &sense_cat); + resid = get_scsi_pt_resid(ptvp); + if (residp) + *residp = resid; + if (-1 == ret) + ; + else if (-2 == ret) { + switch (sense_cat) { + case SG_LIB_CAT_RECOVERED: + case SG_LIB_CAT_NO_SENSE: + ret = 0; + break; + default: + ret = sense_cat; + break; + } + } else if (ret < 4) { + if (vb) + pr2serr("%s: got too few bytes (%d)\n", __func__, ret); + ret = SG_LIB_CAT_MALFORMED; + } else + ret = 0; + + if (resid > 0) { + if (resid > mx_resp_len) { + pr2serr("INQUIRY resid (%d) should never exceed requested " + "len=%d\n", resid, mx_resp_len); + return ret ? ret : SG_LIB_CAT_MALFORMED; + } + /* zero unfilled section of response buffer */ + memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid); + } + return ret; +} + +int +main(int argc, char * argv[]) +{ + bool do_all = false; + bool do_dev_id_vpd = false; + bool do_id_ctl = false; + bool do_id_ns = false; + bool do_self_test = false; + bool flagged = false; + bool is_nvme = false; + int res, c, n, resid, off, len, ln, k, q, num; + int curr_dev_name_pos = 0; + int do_long = 0; + int maxlen = INQUIRY_MAX_RESP_LEN; + int self_test = 0; + int sg_fd = -1; + int ret = 0; + int timeout_ms = DEF_TIMEOUT_SECS * 1000; + int vb = 0; + uint32_t nsid = 0; + uint32_t dn_nsid, al_size; + uint32_t pg_sz = sg_get_page_size(); + int64_t ll; + uint8_t * al_buff = NULL; + uint8_t * free_al_buff = NULL; + uint8_t * bp; + const char * device_name = NULL; + const char * cp; + struct sg_pt_base * ptvp = NULL; + char cmd_name[32]; + char b[2048]; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "cdhlm:n:s:t:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + strcpy(cmd_name, "Identify(ctl)"); + do_id_ctl = true; + break; + case 'd': + strcpy(cmd_name, "INQUIRY(vpd=0x83)"); + do_dev_id_vpd = true; + break; + case 'h': + case '?': + usage(); + return 0; + case 'l': + ++do_long; + break; + case 'm': + maxlen = sg_get_num(optarg); + if (maxlen < 0) { + pr2serr("bad argument to '--maxlen='\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'n': + if ((2 == strlen(optarg)) && (0 == memcmp("-1", optarg, 2))) { + nsid = NVME_NSID_ALL; /* treat '-1' as (2**32 - 1) */ + break; + } + ll = sg_get_llnum(optarg); + if ((ll < 0) || (ll > UINT32_MAX)) { + pr2serr("bad argument to '--nsid', accept 0 to 0xffffffff\n"); + return SG_LIB_SYNTAX_ERROR; + } + strcpy(cmd_name, "Identify(ns)"); + nsid = (uint32_t)ll; + do_id_ns = true; + break; + case 's': + self_test = sg_get_num(optarg); + if (self_test < 0) { + pr2serr("bad argument to '--self-test=', expect 0 or " + "higher\n"); + return SG_LIB_SYNTAX_ERROR; + } + strcpy(cmd_name, "Device self-test"); + do_self_test = true; + break; + case 't': + timeout_ms = sg_get_num(optarg); + if (timeout_ms < 0) { + pr2serr("bad argument to '--to-ms=', expect 0 or higher\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'v': + ++vb; + break; + case 'V': + pr2serr(ME "version: %s\n", version_str); + return 0; + default: + pr2serr("unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + for (; optind < argc; ++optind) { + if (next_dev_name_pos >= MAX_DEV_NAMES) { + pr2serr("Only accepts %d DEVICE names\n", MAX_DEV_NAMES); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + dev_name_arr[next_dev_name_pos++] = argv[optind]; + } + } + + if (next_dev_name_pos < 1) { + pr2serr("Need at least one DEVICE, can have up to %d\n\n", + MAX_DEV_NAMES); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + + if (do_self_test && do_id_ns) + do_id_ns = false; /* self-test with DW10 set to nsid */ + n = (int)do_id_ctl + (int)do_id_ns + (int)do_dev_id_vpd + + (int)do_self_test; + if (n > 1) { + pr2serr("can only have one of --ctl, --dev-id, --nsid= and " + "--self-test=\n\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } else if (0 == n) { + do_id_ns = true; + strcpy(cmd_name, "Identify(ns)"); + } + + al_size = ((uint32_t)maxlen > pg_sz) ? (uint32_t)maxlen : pg_sz; + al_buff = sg_memalign(al_size, pg_sz, &free_al_buff, vb > 3); + if (NULL == al_buff) { + pr2serr("out of memory allocating page sized buffer (of %u bytes)\n", + al_size); + return SG_LIB_OS_BASE_ERR + ENOMEM; + } + device_name = dev_name_arr[curr_dev_name_pos++]; + sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb); + if (sg_fd < 0) { + pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd)); + ret = SG_LIB_FILE_ERROR; + flagged = true; + goto fini; + } + n = check_pt_file_handle(sg_fd, device_name, vb); + if (n < 0) { + pr2serr("check_pt_file_handle error: %s: %s\n", device_name, + safe_strerror(-n)); + flagged = true; + goto fini; + } + cp = NULL; + switch (n) { + case 0: + cp = "Unidentified device (SATA disk ?)"; + break; + case 1: + cp = "SCSI char device (e.g. in Linux: sg or bsg device)"; + break; + case 2: + cp = "SCSI block device (e.g. in FreeBSD: /dev/da0)"; + break; + case 3: + cp = "NVMe char device (e.g. in Linux: /dev/nvme0)"; + break; + case 4: + cp = "NVMe block device (e.g. in FreeBSD: /dev/nvme0ns1)"; + break; + default: + pr2serr("Strange value from check_pt_file_handle() --> %d\n", n); + break; + } + if (cp && (vb || (do_long > 0))) + pr2serr("%s\n", cp); + + ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb); + if (NULL == ptvp) { + pr2serr("%s: out of memory\n", b); + ret = sg_convert_errno(ENOMEM); + goto fini; + } + k = get_scsi_pt_os_err(ptvp); + if (k) { + pr2serr("OS error from construct_scsi_pt_obj_with_fd(): %s\n", + safe_strerror(k)); + ret = sg_convert_errno(k); + goto fini; + } + + /* Loop over all given DEVICEs */ + for (q = 0; q < MAX_DEV_NAMES; ++q) { + is_nvme = pt_device_is_nvme(ptvp); + if ((curr_dev_name_pos > 1) && vb) + pr2serr("Device %d [%s] seems to be %s\n", q + 1, device_name, + is_nvme ? "NVMe" : "SCSI or ATA"); + resid = 0; + if (do_dev_id_vpd || (! is_nvme)) { + if (do_dev_id_vpd) + ret = sg_scsi_inquiry(ptvp, true /* evpd */, VPD_DEVICE_ID, + al_buff, maxlen, timeout_ms / 1000, + &resid, true, vb); + else /* do a standard INQUIRY */ + ret = sg_scsi_inquiry(ptvp, false /* evpd */, 0, al_buff, + maxlen, timeout_ms / 1000, &resid, true, + vb); + if (ret) { + pr2serr("SCSI INQUIRY(%s) failed\n", + do_dev_id_vpd ? "dev_id" : "standard"); + goto fini; + } + len = maxlen - resid; + if (len < 4) { + pr2serr("Something wrong with data-in, len=%d (resid=%d)\n", + len, resid); + goto fini; + } + if (do_dev_id_vpd) { + printf(" Device %d [%s] identification VPD:\n", q + 1, + device_name); + for (off = -1, bp = al_buff + 4, ln = len - 4; + 0 == sg_vpd_dev_id_iter(bp, ln, &off, -1, -1, -1); ) { + n = sg_get_designation_descriptor_str(" ", bp + off, + bp[off + 3] + 4, do_long, + do_long > 1, sizeof(b), b); + if (n > 0) + printf("%s", b); + } + } else { + snprintf(b, sizeof(b), " Device %d [%s] Standard INQUIRY:", + q + 1, device_name); + std_inq_decode(b, al_buff, len, vb); + } + clear_scsi_pt_obj(ptvp); + } else { /* NVME Identify or Device self-test */ + bool this_ctl = false; + uint16_t sct_sc = 0; + uint32_t max_nsid; + struct sg_nvme_passthru_cmd n_cmd; + + if ((! do_self_test) && (NVME_NSID_ALL == nsid)) + do_all = true; + num = 1; /* preliminary, may alter */ + for (k = 0; k < num; ++k) { + bp = (uint8_t *)&n_cmd; + memset(bp, 0, sizeof(n_cmd)); + if (do_self_test) { + n_cmd.opcode = 0x14; /* Device self-test */ + n_cmd.nsid = nsid; + n_cmd.cdw10 = self_test; + if (0 == k) { + if (0 == nsid) + printf("Starting Device self-test for controller " + "only\n"); + else if (do_all) + printf("Starting Device self-test for controller " + "and all namespaces\n"); + else + printf("Starting Device self-test for controller " + "and namespace %u\n", nsid); + } + } else { /* one or more variants of Identify */ + n_cmd.opcode = 0x6; /* Identify */ + dn_nsid = get_pt_nvme_nsid(ptvp); + if ((0 == k) && (do_id_ctl || (0 == nsid) || do_all)) { + n_cmd.cdw10 = 0x1; /* Controller */ + this_ctl = true; + } else { + n_cmd.cdw10 = 0x0; /* Namespace */ + if (do_all) + n_cmd.nsid = k; + else if (nsid > 0) + n_cmd.nsid = nsid; + else if (dn_nsid > 0) + n_cmd.nsid = dn_nsid; + else + break; + this_ctl = false; + } + sg_put_unaligned_le64((uint64_t)(sg_uintptr_t)al_buff, + bp + SG_NVME_PT_ADDR); + sg_put_unaligned_le32(pg_sz, bp + SG_NVME_PT_DATA_LEN); + } + ret = nvme_din_admin_cmd(ptvp, (const uint8_t *)&n_cmd, + sizeof(n_cmd), cmd_name, al_buff, + pg_sz, timeout_ms, &sct_sc, vb); + if (sct_sc || (SG_LIB_NVME_STATUS == ret)) { + sg_get_nvme_cmd_status_str(sct_sc, sizeof(b), b); + pr2serr("%s: %s\n", cmd_name, b); + flagged = true; + goto fini; + } + if (ret) + goto fini; + if (0x6 == n_cmd.opcode) { + if (this_ctl) { + show_nvme_id_ctl(al_buff, device_name, do_long, + &max_nsid); + num = max_nsid + 1; + } else + show_nvme_id_ns(al_buff, n_cmd.nsid, device_name, + do_long); + } + + clear_scsi_pt_obj(ptvp); + if (do_self_test) + break; + if (do_id_ctl) + break; + } /* end of for loop */ + } + ret = 0; + + if (sg_fd >= 0) { + res = sg_cmds_close_device(sg_fd); + if (res < 0) { + pr2serr("close error: %s\n", safe_strerror(-res)); + ret = sg_convert_errno(-res); + break; + } + sg_fd = -1; + } + if (ret) + break; + if (curr_dev_name_pos < next_dev_name_pos) + device_name = dev_name_arr[curr_dev_name_pos++]; + else + break; + if (NULL == device_name) { + pr2serr("Unexpected NULL device name at pos=%d\n", + curr_dev_name_pos - 1); + ret = sg_convert_errno(EINVAL); + flagged = true; + break; + } + sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb); + if (sg_fd < 0) { + pr2serr(ME "open error: %s: %s\n", device_name, + safe_strerror(-sg_fd)); + ret = sg_convert_errno(-sg_fd); + flagged = true; + break; + } + k = set_pt_file_handle(ptvp, sg_fd, vb); + if (k) { + ret = sg_convert_errno(k); + pr2serr("set_pt_file_handle() failed: %s\n", safe_strerror(k)); + flagged = true; + break; + } + printf("\n"); + } /* end of "q" outer for loop */ +fini: + if (ptvp) { + destruct_scsi_pt_obj(ptvp); + ptvp = NULL; + } + if (free_al_buff) + free(free_al_buff); + if (sg_fd >= 0) { + res = sg_cmds_close_device(sg_fd); + if (res < 0) { + pr2serr("close error: %s\n", safe_strerror(-res)); + if (0 == ret) + return SG_LIB_FILE_ERROR; + } + } + if (ret && (0 == vb) && (! flagged)) { + if (! sg_if_can2stderr("", ret)) + pr2serr("Some error occurred [%d]\n", ret); + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} diff --git a/testing/tst_sg_lib.c b/testing/tst_sg_lib.c new file mode 100644 index 0000000..027e6fb --- /dev/null +++ b/testing/tst_sg_lib.c @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2013-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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __STDC_FORMAT_MACROS 1 +#include + +#include + +#if defined(__GNUC__) && ! defined(SG_LIB_FREEBSD) +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" /* need this to see if HAVE_BYTESWAP_H */ +#endif + +#include "sg_lib.h" +#include "sg_pr2serr.h" + +/* Uncomment the next two undefs to force use of the generic (i.e. shifting) + * unaligned functions (i.e. sg_get_* and sg_put_*). Use "-b 16|32|64 + * -n 100m" to see the differences in timing. */ +/* #undef HAVE_CONFIG_H */ +/* #undef HAVE_BYTESWAP_H */ +#include "sg_unaligned.h" + +/* + * A utility program to test sg_libs string handling, specifically + * related to snprintf(). + */ + +static const char * version_str = "1.11 20180715"; + + +#define MAX_LINE_LEN 1024 + + +static struct option long_options[] = { + {"byteswap", required_argument, 0, 'b'}, + {"exit", no_argument, 0, 'e'}, + {"help", no_argument, 0, 'h'}, + {"hex2", no_argument, 0, 'H'}, + {"leadin", required_argument, 0, 'l'}, + {"num", required_argument, 0, 'n'}, + {"printf", no_argument, 0, 'p'}, + {"sense", no_argument, 0, 's'}, + {"unaligned", no_argument, 0, 'u'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, /* sentinel */ +}; + +static const uint8_t desc_sense_data1[] = { + /* unrec_err, excessive_writes, sdat_ovfl, additional_len=? */ + 0x72, 0x1, 0x3, 0x2, 0x80, 0x0, 0x0, 12+12+8+4+8+4+28, + /* Information: 0x11223344556677bb */ + 0x0, 0xa, 0x80, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0xbb, + /* command specific: 0x3344556677bbccff */ + 0x1, 0xa, 0x0, 0x0, 0x33, 0x44, 0x55, 0x66, 0x77, 0xbb, 0xcc, 0xff, + /* sense key specific: SKSV=1, actual_count=257 (hex: 0x101) */ + 0x2, 0x6, 0x0, 0x0, 0x80, 0x1, 0x1, 0x0, + /* field replaceable code=0x45 */ + 0x3, 0x2, 0x0, 0x45, + /* another progress report indicator */ + 0xa, 0x6, 0x2, 0x1, 0x2, 0x0, 0x32, 0x01, + /* incorrect length indicator (ILI) */ + 0x5, 0x2, 0x0, 0x20, + /* user data segment referral */ + 0xb, 26, 0x1, 0x0, + 0,0,0,1, 0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8, + 0x1,0x2,0x3,0x4,0x55,0x6,0x7,0x8, + 2,0,0x12,0x34, + }; + +static const uint8_t desc_sense_data2[] = { + /* ill_req, inv fld in para list, additional_len=? */ + 0x72, 0x5, 0x26, 0x0, 0x0, 0x0, 0x0, 8+4, + /* sense key specific: SKSV=1, C/D*=0, bitp=7 bytep=34 */ + 0x2, 0x6, 0x0, 0x0, 0x8f, 0x0, 0x34, 0x0, + /* field replaceable code=0x45 */ + 0x3, 0x2, 0x0, 0x45, + }; + +static const uint8_t desc_sense_data3[] = { + /* medium err, vibration induced ..., additional_len=? */ + 0x72, 0x3, 0x9, 0x5, 0x0, 0x0, 0x0, 32+16, + /* 0xd: block dev: sense key specific: SKSV=1, retry_count=257, fru=0x45 + * info=0x1122334455, command_specific=0x1 */ + 0xd, 0x1e, 0xa0, 0x0, 0x80, 0x1, 0x1, 0x45, + 0x0, 0x0, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + /* following sbc3 (standard) and sbc4r10 inconsistency; add padding */ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + /* 0xe: reason: send_to_given+henceforth, lu, naa-5, 0x5333333000001f40 */ + 0xe, 0xe, 0x0, 0x1, 0x1, 0x3, 0x0, 0x8, + 0x53, 0x33, 0x33, 0x30, 0x0, 0x0, 0x1f, 0x40, + }; + +static const uint8_t desc_sense_data4[] = { + /* ill_req, inv fld in para list, additional_len=? */ + 0x72, 0x5, 0x26, 0x0, 0x0, 0x0, 0x0, 24, + /* Forwarded sense data, FSDT=0, sd_src=7, f_status=2 */ + 0xc, 22, 0x7, 0x2, + /* ill_req, inv fld in para list, additional_len=? */ + 0x72, 0x5, 0x26, 0x0, 0x0, 0x0, 0x0, 8+4, + /* sense key specific: SKSV=1, C/D*=0, bitp=7 bytep=34 */ + 0x2, 0x6, 0x0, 0x0, 0x8f, 0x0, 0x34, 0x0, + /* field replaceable code=0x45 */ + 0x3, 0x2, 0x0, 0x45, + }; + +static const uint8_t desc_sense_data5[] = { + /* no_sense, ATA info available */ + 0x72, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 14+14, + /* ATA descriptor extend=1 */ + 0x9, 0xc, 0x1, 0x0, 0x34, 0x12, 0x44, 0x11, + 0x55, 0x22, 0x66, 0x33, 0x1, 0x0, + /* ATA descriptor extend=0 */ + 0x9, 0xc, 0x0, 0x0, 0x34, 0x12, 0x44, 0x11, + 0x55, 0x22, 0x66, 0x33, 0x1, 0x0, + }; + +static const uint8_t desc_sense_data6[] = { + /* UA, req, subsidiary bindinganged */ + 0x72, 0x6, 0x3f, 0x1a, 0x0, 0x0, 0x0, 26+12+12, + /* 0xe: designator, reason: preferred admin lu, uuid */ + 0xe, 0x18, 0x0, 0x4, 0x1, 0xa, 0x0, 0x12, + 0x10, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, + 0xfe, 0xdc, + /* 0x0: Information(valid): lun */ + 0x0, 0xa, 0x80, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + /* 0x1: Command specific: 0x1 */ + 0x1, 0xa, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + }; + +static const char * leadin = NULL; + + +static void +usage() +{ + fprintf(stderr, + "Usage: tst_sg_lib [--exit] [--help] [--hex2] [--leadin=STR] " + "[--printf]\n" + " [--sense] [--unaligned] [--verbose] " + "[--version]\n" + " where:\n" +#if defined(__GNUC__) && ! defined(SG_LIB_FREEBSD) + " --byteswap=B|-b B B is 16, 32 or 64; tests NUM " + "byteswaps\n" + " compared to sg_unaligned " + "equivalent\n" + " --exit|-e test exit status strings\n" +#else + " --exit|-e test exit status strings\n" +#endif + " --help|-h print out usage message\n" + " --hex2|-H test hex2* variants\n" + " --leadin=STR|-l STR every line output by --sense " + "should\n" + " be prefixed by STR\n" + " --num=NUM|-n NUM number of iterations (def=1)\n" + " --printf|-p test library printf variants\n" + " --sense|-s test sense data handling\n" + " --unaligned|-u test unaligned data handling\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Test various parts of sg_lib, see options. Sense data tests " + "overlap\nsomewhat with examples/sg_sense_test .\n" + ); + +} + +static char * +get_exit_status_str(int exit_status, bool longer, int b_len, char * b) +{ + int n; + + n = sg_scnpr(b, b_len, " ES=%d: ", exit_status); + if (n >= (b_len - 1)) + return b; + if (sg_exit2str(exit_status, longer, b_len - n, b + n)) { + n = (int)strlen(b); + if (n < (b_len - 1)) + sg_scnpr(b + n, b_len - n, " [ok=true]"); + return b; + } else + snprintf(b, b_len, " No ES string for %d%s", exit_status, + (longer ? " [ok=false]" : "")); + return b; +} + +static uint8_t arr[64]; + +#define OFF 7 /* in byteswap mode, can test different alignments (def: 8) */ + +int +main(int argc, char * argv[]) +{ + bool do_exit_status = false; + bool ok; + int k, c, n, len; + int byteswap_sz = 0; + int do_hex2 = 0; + int do_num = 1; + int do_printf = 0; + int do_sense = 0; + int do_unaligned = 0; + int did_something = 0; + int vb = 0; + int ret = 0; + char b[2048]; + char bb[256]; + const int b_len = sizeof(b); + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "b:ehHl:n:psuvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'b': + byteswap_sz = sg_get_num(optarg); + if (! ((16 == byteswap_sz) || (32 == byteswap_sz) || + (64 == byteswap_sz))) { + fprintf(stderr, "--byteswap= requires 16, 32 or 64\n"); + return 1; + } + break; + case 'e': + do_exit_status = true; + break; + case 'h': + case '?': + usage(); + return 0; + case 'H': + ++do_hex2; + break; + case 'l': + leadin = optarg; + break; + case 'n': + do_num = sg_get_num(optarg); + if (do_num < 0) { + fprintf(stderr, "--num= unable decode argument as number\n"); + return 1; + } + break; + case 'p': + ++do_printf; + break; + case 's': + ++do_sense; + break; + case 'u': + ++do_unaligned; + break; + case 'v': + ++vb; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + return 0; + default: + fprintf(stderr, "unrecognised switch code 0x%x ??\n", c); + usage(); + return 1; + } + } + if (optind < argc) { + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return 1; + } + } + + if (do_exit_status) { + ++did_something; + + printf("Test Exit Status strings (add -v for long version):\n"); + printf(" No error (es=0): %s\n", + sg_get_category_sense_str(0, b_len, b, vb)); + ok = sg_exit2str(0, true, b_len, b); + printf(" No error (force verbose): %s\n", b); + if (vb) + printf(" for previous line sg_exit2str() returned: %s\n", + (ok ? "true" : "false")); + printf("%s\n", get_exit_status_str(1, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(2, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(3, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(4, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(5, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(6, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(7, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(8, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(25, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(33, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(36, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(48, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(50, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(51, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(96, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(97, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(97, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(255, (vb > 0), b_len, b)); + printf("%s\n", get_exit_status_str(-1, (vb > 0), b_len, b)); + + printf("\n"); + } + + if (do_sense ) { + ++did_something; + printf("desc_sense_data test1:\n"); + sg_print_sense(leadin, desc_sense_data1, + (int)sizeof(desc_sense_data1), vb); + printf("\n"); +#if 1 + printf("sg_get_sense_str(ds_data1):\n"); + sg_get_sense_str(leadin, desc_sense_data1, + sizeof(desc_sense_data1), vb, b_len, b); + printf("sg_get_sense_str: strlen(b)->%u\n", (uint32_t)strlen(b)); + printf("%s", b); + printf("\n"); +#endif + printf("desc_sense_data test2\n"); + sg_print_sense(leadin, desc_sense_data2, + (int)sizeof(desc_sense_data2), vb); + printf("\n"); + printf("desc_sense block dev combo plus designator test3\n"); + sg_print_sense(leadin, desc_sense_data3, + (int)sizeof(desc_sense_data3), vb); + printf("\n"); + printf("desc_sense forwarded sense test4\n"); + sg_print_sense(leadin, desc_sense_data4, + (int)sizeof(desc_sense_data4), vb); + printf("\n"); + printf("desc_sense ATA Info test5\n"); + sg_print_sense(leadin, desc_sense_data5, + (int)sizeof(desc_sense_data5), vb); + printf("\n"); + printf("desc_sense UA subsidiary binfing changed test6\n"); + sg_print_sense(leadin, desc_sense_data6, + (int)sizeof(desc_sense_data6), vb); + printf("\n"); + printf("\n"); + } + + if (do_printf) { + ++did_something; + printf("Testing sg_scnpr():\n"); + b[0] = '\0'; + len = b_len; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = -1; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 0; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 1; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 2; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 3; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 4; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 5; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 6; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + + b[0] = '\0'; + len = 7; + n = sg_scnpr(b, len, "%s", "test"); + printf("sg_scnpr(,%d,,\"test\") -> %d; strlen(b) -> %u\n", + len, n, (uint32_t)strlen(b)); + if (strlen(b) > 0) + printf("Resulting string: %s\n", b); + } + if (do_hex2) { + uint8_t b[] = {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58}; + + ++did_something; + for (k = 0; k < 18; ++k) { + printf("k=%d:\n", k); + hex2stdout(b, k, 0); + hex2str(b, k, "h2str0: ", 0, sizeof(bb), bb); + printf("%s", bb); + hex2stdout(b, k, 1); + hex2str(b, k, "h2str1: ", 1, sizeof(bb), bb); + printf("%s", bb); + hex2stdout(b, k, -1); + printf("\n"); + } + } + if (do_unaligned) { + uint16_t u16 = 0x55aa; + uint16_t u16r; + uint32_t u24 = 0x224488; + uint32_t u24r; + uint32_t u32 = 0x224488aa; + uint32_t u32r; + uint64_t u48 = 0x112233445566ULL; + uint64_t u48r; + uint64_t u64 = 0x1122334455667788ULL; + uint64_t u64r; + uint8_t u8[64]; + + ++did_something; + if (vb) + memset(u8, 0, sizeof(u8)); + printf("u16=0x%" PRIx16 "\n", u16); + sg_put_unaligned_le16(u16, u8); + printf(" le16:\n"); + hex2stdout(u8, vb ? 10 : 2, -1); + u16r = sg_get_unaligned_le16(u8); + printf(" u16r=0x%" PRIx16 "\n", u16r); + sg_put_unaligned_be16(u16, u8); + printf(" be16:\n"); + hex2stdout(u8, vb ? 10 : 2, -1); + u16r = sg_get_unaligned_be16(u8); + printf(" u16r=0x%" PRIx16 "\n\n", u16r); + + printf("u24=0x%" PRIx32 "\n", u24); + sg_put_unaligned_le24(u24, u8); + printf(" le24:\n"); + hex2stdout(u8, vb ? 10 : 3, -1); + u24r = sg_get_unaligned_le24(u8); + printf(" u24r=0x%" PRIx32 "\n", u24r); + sg_put_unaligned_be24(u24, u8); + printf(" be24:\n"); + hex2stdout(u8, vb ? 10 : 3, -1); + u24r = sg_get_unaligned_be24(u8); + printf(" u24r=0x%" PRIx32 "\n\n", u24r); + + printf("u32=0x%" PRIx32 "\n", u32); + sg_put_unaligned_le32(u32, u8); + printf(" le32:\n"); + hex2stdout(u8, vb ? 10 : 4, -1); + u32r = sg_get_unaligned_le32(u8); + printf(" u32r=0x%" PRIx32 "\n", u32r); + sg_put_unaligned_be32(u32, u8); + printf(" be32:\n"); + hex2stdout(u8, vb ? 10 : 4, -1); + u32r = sg_get_unaligned_be32(u8); + printf(" u32r=0x%" PRIx32 "\n\n", u32r); + + printf("u48=0x%" PRIx64 "\n", u48); + sg_put_unaligned_le48(u48, u8); + printf(" le48:\n"); + hex2stdout(u8, vb ? 10 : 6, -1); + u48r = sg_get_unaligned_le48(u8); + printf(" u48r=0x%" PRIx64 "\n", u48r); + sg_put_unaligned_be48(u48, u8); + printf(" be48:\n"); + hex2stdout(u8, vb ? 10 : 6, -1); + u48r = sg_get_unaligned_be48(u8); + printf(" u48r=0x%" PRIx64 "\n\n", u48r); + + printf("u64=0x%" PRIx64 "\n", u64); + sg_put_unaligned_le64(u64, u8); + printf(" le64:\n"); + hex2stdout(u8, vb ? 10 : 8, -1); + u64r = sg_get_unaligned_le64(u8); + printf(" u64r=0x%" PRIx64 "\n", u64r); + sg_put_unaligned_be64(u64, u8); + printf(" be64:\n"); + hex2stdout(u8, vb ? 10 : 8, -1); + u64r = sg_get_unaligned_be64(u8); + printf(" u64r=0x%" PRIx64 "\n\n", u64r); + + printf(" be[v=8 bytes]:\n"); + hex2stdout(u8, vb ? 10 : 8, -1); + u64r = sg_get_unaligned_be(8, u8); + printf(" u64r[v=8 bytes]=0x%" PRIx64 "\n", u64r); + printf(" le[v=8 bytes]:\n"); + hex2stdout(u8, vb ? 10 : 8, -1); + u64r = sg_get_unaligned_le(8, u8); + printf(" u64r[v=8 bytes]=0x%" PRIx64 "\n\n", u64r); + } + +#if defined(__GNUC__) && ! defined(SG_LIB_FREEBSD) + if (byteswap_sz > 0) { + uint32_t elapsed_msecs; + uint16_t count16 = 0; + uint32_t count32 = 0; + uint64_t count64 = 0; + struct timespec start_tm, end_tm; + + ++did_something; + if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) { + perror("clock_gettime(CLOCK_MONOTONIC)\n"); + return 1; + } + for (k = 0; k < do_num; ++k) { + switch (byteswap_sz) { + case 16: + sg_put_unaligned_be16(count16 + 1, arr + OFF); + count16 = sg_get_unaligned_be16(arr + OFF); + break; + case 32: + sg_put_unaligned_be32(count32 + 1, arr + OFF); + count32 = sg_get_unaligned_be32(arr + OFF); + break; + case 64: + sg_put_unaligned_be64(count64 + 1, arr + OFF); + count64 = sg_get_unaligned_be64(arr + OFF); + break; + default: + break; + } + } + if (0 != clock_gettime(CLOCK_MONOTONIC, &end_tm)) { + perror("clock_gettime(CLOCK_MONOTONIC)\n"); + return 1; + } + elapsed_msecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000; + elapsed_msecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000000; + if (16 == byteswap_sz) + printf(" count16=%u\n", count16); + else if (32 == byteswap_sz) + printf(" count32=%u\n", count32); + else + printf(" count64=%" PRIu64 "\n", count64); + printf("Unaligned elapsed milliseconds: %u\n", elapsed_msecs); + count16 = 0; + count32 = 0; + count64 = 0; + + if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) { + perror("clock_gettime(CLOCK_MONOTONIC)\n"); + return 1; + } + for (k = 0; k < do_num; ++k) { + switch (byteswap_sz) { + case 16: + count16 = bswap_16(count16 + 1); + memcpy(arr + OFF, &count16, 2); + memcpy(&count16, arr + OFF, 2); + count16 = bswap_16(count16); + break; + case 32: + count32 = bswap_32(count32 + 1); + memcpy(arr + OFF, &count32, 4); + memcpy(&count32, arr + OFF, 4); + count32 = bswap_32(count32); + break; + case 64: + count64 = bswap_64(count64 + 1); + memcpy(arr + OFF, &count64, 8); + memcpy(&count64, arr + OFF, 8); + count64 = bswap_64(count64); + break; + default: + break; + } + } + if (0 != clock_gettime(CLOCK_MONOTONIC, &end_tm)) { + perror("clock_gettime(CLOCK_MONOTONIC)\n"); + return 1; + } + elapsed_msecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000; + elapsed_msecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000000; + if (16 == byteswap_sz) + printf(" count16=%u\n", count16); + else if (32 == byteswap_sz) + printf(" count32=%u\n", count32); + else + printf(" count64=%" PRIu64 "\n", count64); + printf("Byteswap/memcpy elapsed milliseconds: %u\n", elapsed_msecs); + count16 = 0; + count32 = 0; + count64 = 0; + } +#endif + + if (0 == did_something) + printf("Looks like no tests done, check usage with '-h'\n"); + return ret; +} diff --git a/utils/Makefile.cygwin b/utils/Makefile.cygwin new file mode 100644 index 0000000..86e9fa3 --- /dev/null +++ b/utils/Makefile.cygwin @@ -0,0 +1,33 @@ +# Assumes Makefile is used in a cygwin shell + +SHELL = /bin/sh + +CC = gcc +LD = gcc + +EXECS = hxascdmp + +EXE_S = hxascdmp.exe + +# OS_FLAGS = -DSG_LIB_WIN32 -DSPTD +OS_FLAGS = -DSG_LIB_WIN32 +LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +EXTRA_FLAGS = $(OS_FLAGS) $(LARGE_FILE_FLAGS) + +# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) +CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) +# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) + +LDFLAGS = + +all: $(EXECS) + +clean: + rm *.o $(EXE_S) + +.c.o: + $(CC) $(INCLUDES) $(CFLAGS) $(S_CFLAGS) -c -o $@ $< + +hxascdmp: hxascdmp.o + $(LD) -o $@ $(LDFLAGS) $@.o + diff --git a/utils/Makefile.freebsd b/utils/Makefile.freebsd new file mode 100644 index 0000000..a43a569 --- /dev/null +++ b/utils/Makefile.freebsd @@ -0,0 +1,52 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +CC = clang +LD = clang + +EXECS = hxascdmp + +MAN_PGS = +MAN_PREF = man8 + +CFLAGS = -g -O2 -W +# CFLAGS = -g -O2 -W -pedantic -std=c99 + +LDFLAGS = + +all: $(EXECS) + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) core .depend + +hxascdmp: hxascdmp.o + $(LD) -o $@ $(LDFLAGS) $@.o + + +install: $(EXECS) + install -d $(INSTDIR) + for name in $(EXECS); \ + do install -s -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + diff --git a/utils/Makefile.mingw b/utils/Makefile.mingw new file mode 100644 index 0000000..6fed348 --- /dev/null +++ b/utils/Makefile.mingw @@ -0,0 +1,33 @@ +# Assumes makefile is used in a MSYS shell with a MinGW compiler available. + +SHELL = /bin/sh + +CC = gcc +LD = gcc + +EXECS = hxascdmp + +EXE_S = hxascdmp.exe + +# OS_FLAGS = -DSG_LIB_WIN32 -DSG_LIB_MINGW -DSPTD +OS_FLAGS = -DSG_LIB_WIN32 -DSG_LIB_MINGW +LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +EXTRA_FLAGS = $(OS_FLAGS) $(LARGE_FILE_FLAGS) + +# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) +CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) +# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) + +LDFLAGS = + +all: $(EXECS) + +clean: + rm *.o $(EXE_S) + +.c.o: + $(CC) $(INCLUDES) $(CFLAGS) $(S_CFLAGS) -c -o $@ $< + +hxascdmp: hxascdmp.o + $(LD) -o $@ $(LDFLAGS) $@.o + diff --git a/utils/Makefile.solaris b/utils/Makefile.solaris new file mode 100644 index 0000000..075d004 --- /dev/null +++ b/utils/Makefile.solaris @@ -0,0 +1,51 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +CC = gcc +LD = gcc + +EXECS = hxascdmp + +MAN_PGS = +MAN_PREF = man8 + +CFLAGS = -g -O2 -W +# CFLAGS = -g -O2 -W -pedantic -std=c99 + +LDFLAGS = + +all: $(EXECS) + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) core .depend + +hxascdmp: hxascdmp.o + $(LD) -o $@ $(LDFLAGS) $@.o + + +install: $(EXECS) + install -d $(INSTDIR) + for name in $(EXECS); \ + do install -s -f $(INSTDIR) $$name; \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -m 644 -f $(MANDIR)/$(MAN_PREF) $$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + diff --git a/utils/README b/utils/README new file mode 100644 index 0000000..c4d1dc3 --- /dev/null +++ b/utils/README @@ -0,0 +1,20 @@ +This directory contains these utilities: + - hxascdmp: takes a binary stream and converts it to hexadecimal ASCII + which is sent to stdout. The incoming binary stream can either be + from a file or, in the absence of a file name, from stdin. Similar to + the Unix "od" command. By default, it decodes 16 bytes per line with + an ASCII interpretation to the right of each line. See its + hxascdmp(1) man page. + - sg_chk_asc and tst_sg_lib: are no longer here, they have been moved + to the 'testing' directory (a sibling of this directory). + + +By default, the Makefile only builds the hxascdmp utility. The 'Makefile' +file (i.e. with no suffix) builds for Linux; the 'Makefile.freebsd' file +builds for FreeBSD (e.g. 'make -f Makefile.freebsd'); the +'Makefile.solaris' file builds for Solaris; the 'Makefile.mingw' builds +in the Windows MinGW environment (e.g. msys shell); and 'Makefile.cygwin' +builds in the Windows Cygwin environment. + +Douglas Gilbert +4th November 2017 diff --git a/utils/hxascdmp.1 b/utils/hxascdmp.1 new file mode 100644 index 0000000..16300aa --- /dev/null +++ b/utils/hxascdmp.1 @@ -0,0 +1,106 @@ +.TH HXASCDMP "1" "February 2014" "sg3_utils\-1.38" SG3_UTILS +.SH NAME +hxascdmp \- hexadecimal ASCII dump +.SH SYNOPSIS +.B hxascdmp +[\fI\-b=BPL\fR] [\fI\-h\fR] [\fI\-H\fR] [\fI\-N\fR] [\fI\-V\fR] +[\fIFILE+\fR] +.SH DESCRIPTION +.\" Add any additional description here +.PP +This utility reads one or more \fIFILE\fR names and dumps them in hexadecimal +and ASCII to stdout. If no \fIFILE\fR is given then stdin is read instead; +reading continues (or stalls) until an EOF is received. +.PP +The default format is to start each line with the hexadecimal address (offset +from the start of file) followed by 16 hexadecimal bytes separated by a +single space (apart from the 8th and 9th bytes which are separated by two +spaces). If the \fI\-H\fR is not given, there is then a string of 16 ASCII +characters corresponding to the hexadecimal bytes earlier in the line; only +bytes in the range 0x20 to 0x7e are printed in ASCII, other bytes values are +printed as '.' . If the \fI\-H\fR is not given, each \fIFILE\fR name that +appears on the command line is printed on a separate line prior to that +file's hexadecimal ASCII dump. +.PP +If the \fI\-N\fR option is given then no address is printed out and each +line starts with the next hexadecimal byte. +.PP +This utility is pretty close to the 'hexdump -C' variant of BSD's +.B hexdump(1) +command. +.SH OPTIONS +.TP +\fB\-b\fR=\fIBPL\fR +where \fIBPL\fR specifies the number of bytes per line. The default value is +16. 16 bytes per line is just enough to allow the address, 16 bytes in +hexadecimal followed by 16 bytes as ASCII to fit on a standard 80 column +wide terminal. +.TP +\fB\-h\fR +output the usage message then exit. +.TP +\fB\-H\fR +output hexadecimal only (i.e. don't place an ASCII representation at the +end of each line). +.TP +\fB\-N\fR +no address; so each line starts with the next hexadecimal byte. +.TP +\fB\-V\fR, \fB\-\-version\fR +print the version string and then exit. +.SH NOTES +In Windows the given file (or files) are set to binary mode. +.SH EXIT STATUS +The exit status of hxascdmp is 0 when it is successful. If any of the +given \fIFILE\fR names cannot be opened then the exit status is 1. +.SH EXAMPLES +First we manufacture a short file with a mix of data in it: mostly ASCII with +some control characters and 0xaa (which the echo command only accepts in +octal (0252): +.PP + $ echo -e "three blind mice,\t\r\0252" > 3bm.txt +.PP +Now we use this utility to see exactly what is in the file. To avoid +problems with line wrapping, the bytes per line option is set to 8: +.PP + $ hxascdmp -b=8 3bm.txt +.br +ASCII hex dump of file: 3bm.txt +.br + 00 74 68 72 65 65 20 62 6c three bl +.br + 08 69 6e 64 20 6d 69 63 65 ind mice +.br + 10 2c 09 0d aa 0a ,.... +.PP +Using the same file, use this utility to output only hexadecimal formatted +16 bytes per line. +.PP + $ hxascdmp -H 3bm.txt +.br +hex dump of file: 3bm.txt +.br + 00 74 68 72 65 65 20 62 6c 69 6e 64 20 6d 69 63 65 +.br + 10 2c 09 0d aa 0a +.PP +For comparison the hexdump utility gives similar output: +.PP + $ hexdump -C 3bm.txt +.br +00000000 74 68 72 65 65 20 62 6c 69 6e 64 20 6d 69 63 65 |three blind mice| +.br +00000010 2c 09 0d aa 0a |,....| +.br +00000015 +.SH AUTHORS +Written by Douglas Gilbert. +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2004\-2014 Douglas Gilbert +.br +This software is distributed under a FreeBSD license. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.B hexdump(1) diff --git a/utils/hxascdmp.c b/utils/hxascdmp.c new file mode 100644 index 0000000..077fd93 --- /dev/null +++ b/utils/hxascdmp.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2004-2014 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DEF_BYTES_PER_LINE 16 + +static int bytes_per_line = DEF_BYTES_PER_LINE; + +static const char * version_str = "1.14 20140213"; + +#define CHARS_PER_HEX_BYTE 3 +#define BINARY_START_COL 6 +#define MAX_LINE_LENGTH 257 + + +#ifdef SG_LIB_MINGW +/* Non Unix OSes distinguish between text and binary files. + Set text mode on fd. Does nothing in Unix. Returns negative number on + failure. */ +int +sg_set_text_mode(int fd) +{ + return setmode(fd, O_TEXT); +} + +/* Set binary mode on fd. Does nothing in Unix. Returns negative number on + failure. */ +int +sg_set_binary_mode(int fd) +{ + return setmode(fd, O_BINARY); +} + +#else +/* For Unix the following functions are dummies. */ +int +sg_set_text_mode(int fd) +{ + return fd; /* fd should be >= 0 */ +} + +int +sg_set_binary_mode(int fd) +{ + return fd; +} +#endif + +/* Returns the number of times 'ch' is found in string 's' given the + * string's length. */ +static int +num_chs_in_str(const char * s, int slen, int ch) +{ + int res = 0; + + while (--slen >= 0) { + if (ch == s[slen]) + ++res; + } + return res; +} + +static void +dStrHex(const char* str, int len, long start, int noAddr) +{ + const char* p = str; + unsigned char c; + char buff[MAX_LINE_LENGTH]; + long a = start; + int bpstart, cpstart; + int j, k, line_length, nl, cpos, bpos, midline_space; + + if (noAddr) { + bpstart = 0; + cpstart = ((CHARS_PER_HEX_BYTE * bytes_per_line) + 1) + 5; + } else { + bpstart = BINARY_START_COL; + cpstart = BINARY_START_COL + + ((CHARS_PER_HEX_BYTE * bytes_per_line) + 1) + 5; + } + cpos = cpstart; + bpos = bpstart; + midline_space = ((bytes_per_line + 1) / 2); + + if (len <= 0) + return; + line_length = BINARY_START_COL + + (bytes_per_line * (1 + CHARS_PER_HEX_BYTE)) + 7; + if (line_length >= MAX_LINE_LENGTH) { + fprintf(stderr, "bytes_per_line causes maximum line length of %d " + "to be exceeded\n", MAX_LINE_LENGTH); + return; + } + memset(buff, ' ', line_length); + buff[line_length] = '\0'; + if (0 == noAddr) { + k = sprintf(buff + 1, "%.2lx", a); + buff[k + 1] = ' '; + } + + for(j = 0; j < len; j++) { + nl = (0 == (j % bytes_per_line)); + if ((j > 0) && nl) { + printf("%s\n", buff); + bpos = bpstart; + cpos = cpstart; + a += bytes_per_line; + memset(buff,' ', line_length); + if (0 == noAddr) { + k = sprintf(buff + 1, "%.2lx", a); + buff[k + 1] = ' '; + } + } + c = *p++; + bpos += (nl && noAddr) ? 0 : CHARS_PER_HEX_BYTE; + if ((bytes_per_line > 4) && ((j % bytes_per_line) == midline_space)) + bpos++; + sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c); + buff[bpos + 2] = ' '; + if ((c < ' ') || (c >= 0x7f)) + c='.'; + buff[cpos++] = c; + } + if (cpos > cpstart) + printf("%s\n", buff); +} + +static void +dStrHexOnly(const char* str, int len, long start, int noAddr) +{ + const char* p = str; + unsigned char c; + char buff[MAX_LINE_LENGTH]; + long a = start; + int bpstart, bpos, nl; + int midline_space = ((bytes_per_line + 1) / 2); + int j, k, line_length; + + if (len <= 0) + return; + bpstart = (noAddr ? 0 : BINARY_START_COL); + bpos = bpstart; + line_length = (noAddr ? 0 : BINARY_START_COL) + + (bytes_per_line * CHARS_PER_HEX_BYTE) + 4; + if (line_length >= MAX_LINE_LENGTH) { + fprintf(stderr, "bytes_per_line causes maximum line length of %d " + "to be exceeded\n", MAX_LINE_LENGTH); + return; + } + memset(buff, ' ', line_length); + buff[line_length] = '\0'; + if (0 == noAddr) { + k = sprintf(buff + 1, "%.2lx", a); + buff[k + 1] = ' '; + } + + for(j = 0; j < len; j++) { + nl = (0 == (j % bytes_per_line)); + if ((j > 0) && nl) { + printf("%s\n", buff); + bpos = bpstart; + a += bytes_per_line; + memset(buff,' ', line_length); + if (0 == noAddr) { + k = sprintf(buff + 1, "%.2lx", a); + buff[k + 1] = ' '; + } + } + c = *p++; + bpos += (nl && noAddr) ? 0 : CHARS_PER_HEX_BYTE; + if ((bytes_per_line > 4) && ((j % bytes_per_line) == midline_space)) + bpos++; + sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c); + buff[bpos + 2] = ' '; + } + if (bpos > bpstart) + printf("%s\n", buff); +} + +static void +usage() +{ + fprintf(stderr, "Usage: hxascdmp [-b=] [-h] [-H] [-N] [-V] [-?] " + "[+]\n"); + fprintf(stderr, " where:\n"); + fprintf(stderr, " -b= bytes per line to display " + "(def: 16)\n"); + fprintf(stderr, " -h print this usage message\n"); + fprintf(stderr, " -H print hex only (i.e. no ASCII " + "to right)\n"); + fprintf(stderr, " -N no address, start in first column\n"); + fprintf(stderr, " -V print version string then exits\n"); + fprintf(stderr, " -? print this usage message\n"); + fprintf(stderr, " + reads file(s) and outputs each " + "as hex ASCII\n"); + fprintf(stderr, " if no then reads stdin\n\n"); + fprintf(stderr, "Sends hex ASCII dump of stdin/file to stdout\n"); +} + +int +main(int argc, const char ** argv) +{ + char buff[8192]; + int num = 8192; + long start = 0; + int res, k, u, len, n; + int inFile = STDIN_FILENO; + int doHelp = 0; + int doHex = 0; + int noAddr = 0; + int doVersion = 0; + int hasFilename = 0; + int ret = 0; + const char * cp; + + for (k = 1; k < argc; k++) { + cp = argv[k]; + len = strlen(cp); + if (0 == strncmp("-b=", cp, 3)) { + res = sscanf(cp + 3, "%d", &u); + if ((1 != res) || (u < 1)) { + fprintf(stderr, "Bad value after '-b=' option\n"); + usage(); + return 1; + } + bytes_per_line = u; + } else if ((len > 1) && ('-' == cp[0]) && ('-' != cp[1])) { + res = 0; + n = num_chs_in_str(cp + 1, len - 1, 'h'); + doHelp += n; + res += n; + n = num_chs_in_str(cp + 1, len - 1, 'H'); + doHex += n; + res += n; + n = num_chs_in_str(cp + 1, len - 1, 'N'); + noAddr += n; + res += n; + n = num_chs_in_str(cp + 1, len - 1, 'V'); + doVersion += n; + res += n; + n = num_chs_in_str(cp + 1, len - 1, '?'); + doHelp += n; + res += n; + if (0 == res) { + fprintf(stderr, "No option recognized in str: %s\n", cp); + usage(); + return 1; + } + } else if (0 == strcmp("-?", argv[k])) + ++doHelp; + else if (*argv[k] == '-') { + fprintf(stderr, "unknown switch: %s\n", argv[k]); + usage(); + return 1; + } else { + hasFilename = 1; + break; + } + } + if (doVersion) { + printf("%s\n", version_str); + return 0; + } + if (doHelp) { + usage(); + return 0; + } + + /* Make sure num to fetch is integral multiple of bytes_per_line */ + if (0 != (num % bytes_per_line)) + num = (num / bytes_per_line) * bytes_per_line; + + if (hasFilename) { + for ( ; k < argc; k++) + { + inFile = open(argv[k], O_RDONLY); + if (inFile < 0) { + fprintf(stderr, "Couldn't open file: %s\n", argv[k]); + ret = 1; + } else { + sg_set_binary_mode(inFile); + start = 0; + if (! doHex) + printf("ASCII hex dump of file: %s\n", argv[k]); + while ((res = read(inFile, buff, num)) > 0) { + if (doHex) + dStrHexOnly(buff, res, start, noAddr); + else + dStrHex(buff, res, start, noAddr); + start += (long)res; + } + } + close(inFile); + } + } else { + sg_set_binary_mode(inFile); + while ((res = read(inFile, buff, num)) > 0) { + if (doHex) + dStrHexOnly(buff, res, start, noAddr); + else + dStrHex(buff, res, start, noAddr); + start += (long)res; + } + } + return ret; +}