| /* |
| * Copyright (c) 2017-2022 Douglas Gilbert. |
| * All rights reserved. |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the BSD_LICENSE file. |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * The utility can send six variants of the SCSI WRITE command: (normal) |
| * WRITE(16 or 32), WRITE ATOMIC(16 or 32), ORWRITE(16 or 32), |
| * WRITE SAME(16 or 32), WRITE SCATTERED (16 or 32) or WRITE |
| * STREAM(16 or 32). |
| */ |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <ctype.h> |
| #include <sys/types.h> /* needed for lseek() */ |
| #include <sys/stat.h> |
| #include <getopt.h> |
| #define __STDC_FORMAT_MACROS 1 |
| #include <inttypes.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include "sg_lib.h" |
| #include "sg_pt.h" |
| #include "sg_cmds_basic.h" |
| #include "sg_cmds_extra.h" |
| #include "sg_unaligned.h" |
| #include "sg_pr2serr.h" |
| |
| static const char * version_str = "1.30 20220127"; |
| |
| /* Protection Information refers to 8 bytes of extra information usually |
| * associated with each logical block and is often abbreviated to PI while |
| * its fields: reference-tag (4 bytes), application-tag (2 bytes) and |
| * tag-mask (2 bytes) are often abbreviated to RT, AT and TM respectively. |
| * And the LBA Range Descriptor associated with the WRITE SCATTERED command |
| * is abbreviated to RD. A degenerate RD is one where length components, |
| ( and perhaps the LBA, are zero; it is not illegal according to T10 but are |
| * a little tricky to handle when scanning and little extra information |
| * is provided. */ |
| |
| #define ORWRITE16_OP 0x8b |
| #define WRITE_16_OP 0x8a |
| #define WRITE_ATOMIC16_OP 0x9c |
| #define WRITE_SAME16_OP 0x93 |
| #define SERVICE_ACTION_OUT_16_OP 0x9f /* WRITE SCATTERED (16) uses this */ |
| #define WRITE_SCATTERED16_SA 0x12 |
| #define WRITE_STREAM16_OP 0x9a |
| #define VARIABLE_LEN_OP 0x7f |
| #define ORWRITE32_SA 0xe |
| #define WRITE_32_SA 0xb |
| #define WRITE_ATOMIC32_SA 0xf |
| #define WRITE_SAME_SA 0xd |
| #define WRITE_SCATTERED32_SA 0x11 |
| #define WRITE_STREAM32_SA 0x10 |
| #define WRITE_X_16_LEN 16 |
| #define WRITE_X_32_LEN 32 |
| #define WRITE_X_32_ADD 0x18 |
| #define RCAP10_RESP_LEN 8 |
| #define RCAP16_RESP_LEN 32 |
| #define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ |
| #define DEF_TIMEOUT_SECS 120 /* might need more for large NUM */ |
| #define DEF_WR_NUMBLOCKS 0 /* do nothing; for safety */ |
| #define DEF_RT 0xffffffff |
| #define DEF_AT 0xffff |
| #define DEF_TM 0xffff |
| #define EBUFF_SZ 256 |
| |
| #define MAX_NUM_ADDR 128 |
| |
| #ifndef UINT32_MAX |
| #define UINT32_MAX ((uint32_t)-1) |
| #endif |
| #ifndef UINT16_MAX |
| #define UINT16_MAX ((uint16_t)-1) |
| #endif |
| |
| static struct option long_options[] = { |
| {"32", no_argument, 0, '3'}, |
| {"16", no_argument, 0, '6'}, |
| {"app-tag", required_argument, 0, 'a'}, |
| {"app_tag", required_argument, 0, 'a'}, |
| {"atomic", required_argument, 0, 'A'}, |
| {"bmop", required_argument, 0, 'B'}, |
| {"bs", required_argument, 0, 'b'}, |
| {"combined", required_argument, 0, 'c'}, |
| {"dld", required_argument, 0, 'D'}, |
| {"dpo", no_argument, 0, 'd'}, |
| {"dry-run", no_argument, 0, 'x'}, |
| {"dry_run", no_argument, 0, 'x'}, |
| {"fua", no_argument, 0, 'f'}, |
| {"grpnum", required_argument, 0, 'g'}, |
| {"generation", required_argument, 0, 'G'}, |
| {"help", no_argument, 0, 'h'}, |
| {"in", required_argument, 0, 'i'}, |
| {"lba", required_argument, 0, 'l'}, |
| {"normal", no_argument, 0, 'N'}, |
| {"num", required_argument, 0, 'n'}, |
| {"offset", required_argument, 0, 'o'}, |
| {"or", no_argument, 0, 'O'}, |
| {"quiet", no_argument, 0, 'Q'}, |
| {"ref-tag", required_argument, 0, 'r'}, |
| {"ref_tag", required_argument, 0, 'r'}, |
| {"same", required_argument, 0, 'M'}, |
| {"scat-file", required_argument, 0, 'q'}, |
| {"scat_file", required_argument, 0, 'q'}, |
| {"scat-raw", no_argument, 0, 'R'}, |
| {"scat_raw", no_argument, 0, 'R'}, |
| {"scattered", required_argument, 0, 'S'}, |
| {"stream", required_argument, 0, 'T'}, |
| {"strict", no_argument, 0, 's'}, |
| {"tag-mask", required_argument, 0, 't'}, |
| {"tag_mask", required_argument, 0, 't'}, |
| {"timeout", required_argument, 0, 'I'}, |
| {"unmap", required_argument, 0, 'u'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"version", no_argument, 0, 'V'}, |
| {"wrprotect", required_argument, 0, 'w'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| struct opts_t { |
| bool do_16; /* default when --32 not given */ |
| bool do_32; |
| bool do_anchor; /* from --unmap=U_A , bit 1; WRITE SAME */ |
| bool do_atomic; /* selects WRITE ATOMIC(16 or 32) */ |
| /* --atomic=AB AB --> .atomic_boundary */ |
| bool do_combined; /* -c DOF --> .scat_lbdof */ |
| bool do_or; /* -O ORWRITE(16 or 32) */ |
| bool do_quiet; /* -Q suppress some messages */ |
| bool do_scat_raw; |
| bool do_same; /* -M WRITE SAME(16 or 32) */ |
| /* --same=NDOB NDOB --> .ndob */ |
| bool do_scattered; /* -S WRITE SCATTERED(16 or 32) */ |
| /* --scattered=RD RD --> .scat_num_lbard */ |
| bool do_stream; /* -T WRITE STREAM(16 or 32) */ |
| /* --stream=ID ID --> .str_id */ |
| bool do_unmap; /* from --unmap=U_A , bit 0; WRITE SAME */ |
| bool do_write_normal; /* -N WRITE (16 or 32) */ |
| bool expect_pi_do; /* expect protection information (PI) which |
| * is 8 bytes long following each logical |
| * block in the data out buffer. */ |
| bool dpo; /* "Disable Page Out" bit field */ |
| bool fua; /* "Force Unit Access" bit field */ |
| bool ndob; /* "No Data-Out Buffer" from --same=NDOB */ |
| bool verbose_given; |
| bool version_given; |
| int dld; /* "Duration Limit Descriptor" bit mask; bit 0 --> |
| * DLD0, bit 1 --> DLD1, bit 2 --> DLD2 |
| * only WRITE(16) and WRITE SCATTERED(16) */ |
| int dry_run; /* temporary write when used more than once */ |
| int grpnum; /* "Group Number", 0 to 0x3f (GRPNUM_MASK) */ |
| int help; |
| int pi_type; /* -1: unknown: 0: type 0 (none): 1: type 1 */ |
| int strict; /* > 0, report then exit on questionable meta data */ |
| int timeout; /* timeout (in seconds) to abort SCSI commands */ |
| int verbose; /* incremented for each -v */ |
| int wrprotect; /* is ORPROTECT field for ORWRITE */ |
| uint8_t bmop; /* bit mask operators for ORWRITE(32) */ |
| uint8_t pgp; /* previous generation processing for ORWRITE(32) */ |
| uint16_t app_tag; /* part of protection information (def: 0xffff) */ |
| uint16_t atomic_boundary; /* when 0 atomic write spans given length */ |
| uint16_t scat_lbdof; /* by construction this must be >= 1 */ |
| uint16_t scat_num_lbard; /* RD from --scattered=RD, number of LBA |
| * Range Descriptors */ |
| uint16_t str_id; /* (stream ID) is for WRITE STREAM */ |
| uint16_t tag_mask; /* part of protection information (def: 0xffff) */ |
| uint32_t bs; /* logical block size (def: 0). 0 implies use READ |
| * CAPACITY(10 or 16) to determine */ |
| uint32_t bs_pi_do; /* logical block size plus PI, if any. This value is |
| * used as the actual block size */ |
| uint32_t if_dlen; /* bytes to read after .if_offset from .if_name, |
| * if 0 given, read rest of .if_name */ |
| uint32_t numblocks; /* defaults to 0, number of blocks (of user data) to |
| * write */ |
| uint32_t orw_eog; /* from --generation=EOG,NOG (first argument) */ |
| uint32_t orw_nog; /* from --generation=EOG,NOG (for ORWRITE) */ |
| uint32_t ref_tag; /* part of protection information (def: 0xffffffff) */ |
| uint64_t lba; /* "Logical Block Address", for non-scattered use */ |
| uint64_t if_offset; /* byte offset in .if_name to start reading */ |
| uint64_t tot_lbs; /* from READ CAPACITY */ |
| ssize_t xfer_bytes; /* derived value: bs_pi_do * numblocks */ |
| /* for WRITE SCATTERED .xfer_bytes < do_len */ |
| const char * device_name; |
| const char * if_name; /* from --in=IF */ |
| const char * scat_filename; /* from --scat-file=SF */ |
| const char * cmd_name; /* e.g. 'Write atomic' */ |
| char cdb_name[24]; /* e.g. 'Write atomic(16)' */ |
| }; |
| |
| static const char * xx_wr_fname = "sg_write_x.bin"; |
| static const uint32_t lbard_sz = 32; |
| static const char * lbard_str = "LBA range descriptor"; |
| |
| |
| static void |
| usage(int do_help) |
| { |
| if (do_help < 2) { |
| pr2serr("Usage:\n" |
| "sg_write_x [--16] [--32] [--app-tag=AT] [--atomic=AB] " |
| "[--bmop=OP,PGP]\n" |
| " [--bs=BS] [--combined=DOF] [--dld=DLD] [--dpo] " |
| "[--dry-run]\n" |
| " [--fua] [--generation=EOG,NOG] [--grpnum=GN] " |
| "[--help] --in=IF\n" |
| " [--lba=LBA,LBA...] [--normal] [--num=NUM,NUM...]\n" |
| " [--offset=OFF[,DLEN]] [--or] [--quiet] " |
| "[--ref-tag=RT]\n" |
| " [--same=NDOB] [--scat-file=SF] [--scat-raw] " |
| "[--scattered=RD]\n" |
| " [--stream=ID] [--strict] [--tag-mask=TM] " |
| "[--timeout=TO]\n" |
| " [--unmap=U_A] [--verbose] [--version] " |
| "[--wrprotect=WRP]\n" |
| " DEVICE\n"); |
| if (1 != do_help) { |
| pr2serr("\nOr the corresponding short option usage:\n" |
| "sg_write_x [-6] [-3] [-a AT] [-A AB] [-B OP,PGP] [-b BS] " |
| "[-c DOF] [-D DLD]\n" |
| " [-d] [-x] [-f] [-G EOG,NOG] [-g GN] [-h] -i IF " |
| "[-l LBA,LBA...]\n" |
| " [-N] [-n NUM,NUM...] [-o OFF[,DLEN]] [-O] [-Q] " |
| "[-r RT] [-M NDOB]\n" |
| " [-q SF] [-R] [-S RD] [-T ID] [-s] [-t TM] [-I TO] " |
| "[-u U_A] [-v]\n" |
| " [-V] [-w WPR] DEVICE\n" |
| ); |
| pr2serr("\nUse '-h' or '--help' for more help\n"); |
| return; |
| } |
| pr2serr(" where:\n" |
| " --16|-6 send 16 byte cdb variant (this is " |
| "default action)\n" |
| " --32|-3 send 32 byte cdb variant of command " |
| "(def: 16 byte)\n" |
| " --app-tag=AT|-a AT expected application tag field " |
| "(def: 0xffff)\n" |
| " --atomic=AB|-A AB send WRITE ATOMIC command with AB " |
| "being its\n" |
| " Atomic Boundary field (0 to 0xffff)\n" |
| " --bmop=OP,PGP|-p OP,PGP set BMOP field to OP and " |
| " Previous\n" |
| " Generation Processing field " |
| "to PGP\n" |
| " --bs=BS|-b BS block size (def: use READ CAPACITY), " |
| "if power of\n" |
| " 2: logical block size, otherwise: " |
| "actual block size\n" |
| " --combined=DOF|-c DOF scatter list and data combined " |
| "for WRITE\n" |
| " SCATTERED, data starting at " |
| "offset DOF which\n" |
| " has units of sizeof(LB+PI); " |
| "sizeof(PI)=8n or 0\n" |
| " --dld=DLD|-D DLD set duration limit descriptor (dld) " |
| "bits (def: 0)\n" |
| " --dpo|-d set DPO (disable page out) field " |
| "(def: clear)\n" |
| " --dry-run|-x exit just before sending SCSI write " |
| "command\n" |
| " --fua|-f set FUA (force unit access) field " |
| "(def: clear)\n" |
| " --generation=EOG,NOG set Expected ORWgeneration field " |
| "to EOG\n" |
| " |-G EOG,NOG and New ORWgeneration field to " |
| "NOG\n" |
| ); |
| pr2serr( |
| " --grpnum=GN|-g GN GN is group number field (def: 0, " |
| "range: 0 to 31)\n" |
| " --help|-h use multiple times for different " |
| "usage messages\n" |
| " --in=IF|-i IF IF is file to fetch NUM blocks of " |
| "data from.\n" |
| " Blocks written to DEVICE. 1 or no " |
| "blocks read\n" |
| " in the case of WRITE SAME\n" |
| " --lba=LBA,LBA... list of LBAs (Logical Block Addresses) " |
| "to start\n" |
| " |-l LBA,LBA... writes (def: --lba=0). Alternative is " |
| "--scat-file=SF\n" |
| " --normal|-N send 'normal' WRITE command (default " |
| "when no other\n" |
| " command option given)\n" |
| " --num=NUM,NUM... NUM is number of logical blocks to " |
| "write (def:\n" |
| " |-n NUM,NUM... --num=0). Number of block sent is " |
| "sum of NUMs\n" |
| " --offset=OFF[,DLEN] OFF is byte offset in IF to start " |
| "reading from\n" |
| " |-o OFF[,DLEN] (def: 0), then read DLEN bytes(def: " |
| "rest of IF)\n" |
| " --or|-O send ORWRITE command\n" |
| " --quiet|-Q suppress some informational messages\n" |
| " --ref-tag=RT|-r RT expected reference tag field (def: " |
| "0xffffffff)\n" |
| " --same=NDOB|-M NDOB send WRITE SAME command. NDOB (no " |
| "data out buffer)\n" |
| " can be either 0 (do send buffer) or " |
| "1 (don't)\n" |
| " --scat-file=SF|-q SF file containing LBA, NUM pairs, " |
| "see manpage\n" |
| " --scat-raw|-R read --scat_file=SF as binary (def: " |
| "ASCII hex)\n" |
| " --scattered=RD|-S RD send WRITE SCATTERED command with " |
| "RD range\n" |
| " descriptors (RD can be 0 when " |
| "--combined= given)\n" |
| " --stream=ID|-T ID send WRITE STREAM command with its " |
| "STR_ID\n" |
| " field set to ID\n" |
| " --strict|-s exit if read less than requested from " |
| "IF ;\n" |
| " require variety of WRITE to be given " |
| "as option\n" |
| " --tag-mask=TM|-t TM tag mask field (def: 0xffff)\n" |
| " --timeout=TO|-I TO command timeout (unit: seconds) " |
| "(def: 120)\n" |
| " --unmap=U_A|-u U_A 0 clears both UNMAP and ANCHOR bits " |
| "(default),\n" |
| " 1 sets UNMAP, 2 sets ANCHOR, 3 sets " |
| "both\n" |
| " --verbose|-v increase verbosity\n" |
| " --version|-V print version string then exit\n" |
| " --wrprotect=WPR|-w WPR WPR is the WRPROTECT field " |
| "value (def: 0)\n\n" |
| "Performs a SCSI WRITE (normal), ORWRITE, WRITE ATOMIC, WRITE " |
| "SAME, WRITE\nSCATTERED, or WRITE STREAM command. A 16 or 32 " |
| "byte cdb variant can be\nselected. The --in=IF option (data to " |
| "be written) is required apart from\nwhen --same=1 (i.e. when " |
| "NDOB is set). If no WRITE variant option is given\nthen, in " |
| "the absence of --strict, a (normal) WRITE is performed. Only " |
| "WRITE\nSCATTERED uses multiple LBAs and NUMs, or a SF file " |
| "with multiple pairs.\nThe --num=NUM field defaults to 0 (do " |
| "nothing) for safety. Using '-h'\nmultiple times shows the " |
| "applicable options for each command variant.\n" |
| ); |
| } else if (2 == do_help) { |
| printf("WRITE ATOMIC (16 or 32) applicable options:\n" |
| " sg_write_x --atomic=AB --in=IF [--16] [--32] [--app-tag=AT] " |
| "[--bs=BS]\n" |
| " [--dpo] [--fua] [--grpnum=GN] [--lba=LBA] " |
| "[--num=NUM]\n" |
| " [--offset=OFF[,DLEN]] [--ref-tag=RT] [--strict] " |
| "[--tag-mask=TM]\n" |
| " [--timeout=TO] [--wrprotect=WRP] DEVICE\n" |
| "\n" |
| "normal WRITE (32) applicable options:\n" |
| " sg_write_x --normal --in=IF --32 [--app-tag=AT] [--bs=BS] " |
| "[--dpo] [--fua]\n" |
| " [--grpnum=GN] [--lba=LBA] [--num=NUM] " |
| "[--offset=OFF[,DLEN]]\n" |
| " [--ref-tag=RT] [--strict] [--tag-mask=TM] " |
| "[--timeout=TO]\n" |
| " [--wrprotect=WRP] DEVICE\n" |
| "\n" |
| "normal WRITE (16) applicable options:\n" |
| " sg_write_x --normal --in=IF [--16] [--bs=BS] [--dld=DLD] " |
| "[--dpo] [--fua]\n" |
| " [--grpnum=GN] [--lba=LBA] [--num=NUM] " |
| "[--offset=OFF[,DLEN]]\n" |
| " [--strict] [--timeout=TO] [--verbose] " |
| "[--wrprotect=WRP] DEVICE\n" |
| "\n" |
| "ORWRITE (32) applicable options:\n" |
| " sg_write_x --or --in=IF --32 [--bmop=OP,PGP] [--bs=BS] " |
| "[--dpo] [--fua]\n" |
| " [--generation=EOG,NOG] [--grpnum=GN] [--lba=LBA] " |
| "[--num=NUM]\n" |
| " [--offset=OFF{,DLEN]] [--strict] [--timeout=TO]\n" |
| " [--wrprotect=ORP] DEVICE\n" |
| "\n" |
| "ORWRITE (16) applicable options:\n" |
| " sg_write_x --or --in=IF [--16] [--bs=BS] [--dpo] [--fua] " |
| "[--grpnum=GN]\n" |
| " [--lba=LBA] [--num=NUM] [--offset=OFF[,DLEN]] " |
| "[--strict]\n" |
| " [--timeout=TO] [--wrprotect=ORP] DEVICE\n" |
| "\n" |
| ); |
| } else if (3 == do_help) { |
| printf("WRITE SAME (32) applicable options:\n" |
| " sg_write_x --same=NDOB --32 [--app-tag=AT] [--bs=BS] " |
| "[--grpnum=GN]\n" |
| " [--in=IF] [--lba=LBA] [--num=NUM] " |
| "[--offset=OFF[,DLEN]]\n" |
| " [--ref-tag=RT] [--strict] [--tag-mask=TM] " |
| "[--timeout=TO]\n" |
| " [--unmap=U_A] [--wrprotect=WRP] DEVICE\n" |
| "\n" |
| "WRITE SCATTERED (32) applicable options:\n" |
| " sg_write_x --scattered --in=IF --32 [--app-tag=AT] " |
| "[--bs=BS]\n" |
| " [--combined=DOF] [--dpo] [--fua] [--grpnum=GN]\n" |
| " [--lba=LBA,LBA...] [--num=NUM,NUM...] " |
| "[--offset=OFF[,DLEN]]\n" |
| " [--ref-tag=RT] [--scat-file=SF] [--scat-raw] " |
| "[--strict]\n" |
| " [--tag-mask=TM] [--timeout=TO] [--wrprotect=WRP] " |
| "DEVICE\n" |
| "\n" |
| "WRITE SCATTERED (16) applicable options:\n" |
| " sg_write_x --scattered --in=IF [--bs=BS] [--combined=DOF] " |
| "[--dld=DLD]\n" |
| " [--dpo] [--fua] [--grpnum=GN] [--lba=LBA,LBA...]\n" |
| " [--num=NUM,NUM...] [--offset=OFF[,DLEN]] " |
| "[--scat-raw]\n" |
| " [--scat-file=SF] [--strict] [--timeout=TO] " |
| "[--wrprotect=WRP]\n" |
| " DEVICE\n" |
| "\n" |
| "WRITE STREAM (32) applicable options:\n" |
| " sg_write_x --stream=ID --in=IF --32 [--app-tag=AT] " |
| "[--bs=BS] [--dpo]\n" |
| " [--fua] [--grpnum=GN] [--lba=LBA] [--num=NUM]\n" |
| " [--offset=OFF[,DLEN]] [--ref-tag=RT] [--strict] " |
| "[--tag-mask=TM]\n" |
| " [--timeout=TO] [--verbose] [--wrprotect=WRP] " |
| "DEVICE\n" |
| "\n" |
| "WRITE STREAM (16) applicable options:\n" |
| " sg_write_x --stream=ID --in=IF [--16] [--bs=BS] [--dpo] " |
| "[--fua]\n" |
| " [--grpnum=GN] [--lba=LBA] [--num=NUM] " |
| "[--offset=OFF[,DLEN]]\n" |
| " [--strict] [--timeout=TO] [--wrprotect=WRP] " |
| "DEVICE\n" |
| "\n" |
| ); |
| } else { |
| printf("Notes:\n" |
| " - all 32 byte cdb variants, apart from ORWRITE(32), need type " |
| "1, 2, or 3\n" |
| " protection information active on the DEVICE\n" |
| " - all commands can take one or more --verbose (-v) options " |
| "and/or the\n" |
| " --dry-run option\n" |
| " - all WRITE X commands will accept --scat-file=SF and " |
| "optionally --scat-raw\n" |
| " options but only the first lba,num pair is used (any " |
| "more are ignored)\n" |
| " - when '--rscat-aw --scat-file=SF' are used then the binary " |
| "format expected in\n" |
| " SF is as defined for the WRITE SCATTERED commands. " |
| "That is 32 bytes\n" |
| " of zeros followed by the first LBA range descriptor " |
| "followed by the\n" |
| " second LBA range descriptor, etc. Each LBA range " |
| "descriptor is 32 bytes\n" |
| " long with an 8 byte LBA at offset 0 and a 4 byte " |
| "number_of_logical_\n" |
| " blocks at offset 8 (both big endian). The 'pad' following " |
| "the last LBA\n" |
| " range descriptor does not need to be given\n" |
| " - WRITE SCATTERED(32) additionally has expected initial " |
| "LB reference tag,\n" |
| " application tag and LB application tag mask fields in the " |
| "LBA range\n" |
| " descriptor. If --strict is given then all reserved fields " |
| "are checked\n" |
| " for zeros, an error is generated for non zero bytes.\n" |
| " - when '--lba=LBA,LBA...' is used on commands other than " |
| "WRITE SCATTERED\n" |
| " then only the first LBA value is used.\n" |
| " - when '--num=NUM,NUM...' is used on commands other than " |
| "WRITE SCATTERED\n" |
| " then only the first NUM value is used.\n" |
| " - whenever '--lba=LBA,LBA...' is used then " |
| "'--num=NUM,NUM...' should\n" |
| " also be used. Also they should have the same number of " |
| "elements.\n" |
| ); |
| } |
| } |
| |
| /* Returns 0 if successful, else sg3_utils error code. */ |
| static int |
| bin_read(int fd, uint8_t * up, uint32_t len, const char * fname) |
| { |
| int res, err; |
| |
| res = read(fd, up, len); |
| if (res < 0) { |
| err = errno; |
| pr2serr("Error doing read of %s file: %s\n", fname, |
| safe_strerror(err)); |
| return sg_convert_errno(err); |
| } |
| if ((uint32_t)res < len) { |
| pr2serr("Short (%u) read of %s file, wanted %u\n", (unsigned int)res, |
| fname, len); |
| return SG_LIB_FILE_ERROR; |
| } |
| return 0; |
| } |
| |
| /* Returns true if num_of_f_chars of ASCII 'f' or 'F' characters are found |
| * in sequence. Any leading "0x" or "0X" is ignored; otherwise false is |
| * returned (and the comparison stops when the first mismatch is found). |
| * For example a sequence of 'f' characters in a null terminated C string |
| * that is two characters shorter than the requested num_of_f_chars will |
| * compare the null character in the string with 'f', find them unequal, |
| * stop comparing and return false. */ |
| static bool |
| all_ascii_f_s(const char * cp, int num_of_f_chars) |
| { |
| if ((NULL == cp) || (num_of_f_chars < 1)) |
| return false; /* define degenerate cases */ |
| if (('0' == cp[0]) && (('x' == cp[1]) || ('X' == cp[1]))) |
| cp += 2; |
| for ( ; num_of_f_chars >= 0 ; --num_of_f_chars, ++cp) { |
| if ('F' != toupper((uint8_t)*cp)) |
| return false; |
| } |
| return true; |
| } |
| |
| /* Read numbers (up to 64 bits in size) from command line (comma (or |
| * (single) space) separated list). Assumed decimal unless prefixed |
| * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex). |
| * Returns 0 if ok, or 1 if error. */ |
| static int |
| build_lba_arr(const char * inp, uint64_t * lba_arr, uint32_t * lba_arr_len, |
| int max_arr_len) |
| { |
| int in_len, k; |
| int64_t ll; |
| const char * lcp; |
| char * cp; |
| char * c2p; |
| |
| if ((NULL == inp) || (NULL == lba_arr) || |
| (NULL == lba_arr_len)) |
| return 1; |
| lcp = inp; |
| in_len = strlen(inp); |
| if (0 == in_len) |
| *lba_arr_len = 0; |
| if ('-' == inp[0]) { /* read from stdin */ |
| pr2serr("'--lba' cannot be read from stdin\n"); |
| return 1; |
| } else { /* list of numbers (default decimal) on command line */ |
| k = strspn(inp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, "); |
| if (in_len != k) { |
| pr2serr("build_lba_arr: error at pos %d\n", k + 1); |
| return 1; |
| } |
| for (k = 0; k < max_arr_len; ++k) { |
| ll = sg_get_llnum(lcp); |
| if (-1 != ll) { |
| lba_arr[k] = (uint64_t)ll; |
| cp = (char *)strchr(lcp, ','); |
| c2p = (char *)strchr(lcp, ' '); |
| if (NULL == cp) |
| cp = c2p; |
| if (NULL == cp) |
| break; |
| if (c2p && (c2p < cp)) |
| cp = c2p; |
| lcp = cp + 1; |
| } else { |
| pr2serr("build_lba_arr: error at pos %d\n", |
| (int)(lcp - inp + 1)); |
| return 1; |
| } |
| } |
| *lba_arr_len = (uint32_t)(k + 1); |
| if (k == max_arr_len) { |
| pr2serr("build_lba_arr: array length exceeded\n"); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| /* Read numbers (up to 32 bits in size) from command line (comma (or |
| * (single) space) separated list). Assumed decimal unless prefixed |
| * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex). |
| * Returns 0 if ok, else a sg3_utils error code is returned. */ |
| static int |
| build_num_arr(const char * inp, uint32_t * num_arr, uint32_t * num_arr_len, |
| int max_arr_len) |
| { |
| int in_len, k; |
| const char * lcp; |
| int64_t ll; |
| char * cp; |
| char * c2p; |
| |
| if ((NULL == inp) || (NULL == num_arr) || |
| (NULL == num_arr_len)) |
| return SG_LIB_LOGIC_ERROR; |
| lcp = inp; |
| in_len = strlen(inp); |
| if (0 == in_len) |
| *num_arr_len = 0; |
| if ('-' == inp[0]) { /* read from stdin */ |
| pr2serr("'--len' cannot be read from stdin\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } else { /* list of numbers (default decimal) on command line */ |
| k = strspn(inp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, "); |
| if (in_len != k) { |
| pr2serr("%s: error at pos %d\n", __func__, k + 1); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| for (k = 0; k < max_arr_len; ++k) { |
| ll = sg_get_llnum(lcp); |
| if (-1 != ll) { |
| if (ll > UINT32_MAX) { |
| pr2serr("%s: number exceeds 32 bits at pos %d\n", |
| __func__, (int)(lcp - inp + 1)); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| num_arr[k] = (uint32_t)ll; |
| cp = (char *)strchr(lcp, ','); |
| c2p = (char *)strchr(lcp, ' '); |
| if (NULL == cp) |
| cp = c2p; |
| if (NULL == cp) |
| break; |
| if (c2p && (c2p < cp)) |
| cp = c2p; |
| lcp = cp + 1; |
| } else { |
| pr2serr("%s: error at pos %d\n", __func__, |
| (int)(lcp - inp + 1)); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| *num_arr_len = (uint32_t)(k + 1); |
| if (k == max_arr_len) { |
| pr2serr("%s: array length exceeded\n", __func__); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| return 0; |
| } |
| |
| /* Tries to parse LBA,NUM[,RT,AP,TM] on one line, comma separated. Returns |
| * 0 if parsed ok, else 999 if nothing parsed, else error (currently always |
| * SG_LIB_SYNTAX_ERROR). If protection information fields not given, then |
| * default values are given (i.e. all 0xff bytes). Ignores all spaces and |
| * tabs and everything after '#' on lcp (assumed to be an ASCII line that |
| * is null terminated). If successful and 'up' is non NULL then writes a |
| * LBA range descriptor starting at 'up'. */ |
| static int |
| parse_scat_pi_line(const char * lcp, uint8_t * up, uint32_t * sum_num) |
| { |
| bool ok; |
| int k; |
| int64_t ll; |
| const char * cp; |
| const char * bp; |
| char c[1024]; |
| |
| bp = c; |
| cp = strchr(lcp, '#'); |
| lcp += strspn(lcp, " \t"); |
| if (('\0' == *lcp) || (cp && (lcp >= cp))) |
| return 999; /* blank line or blank prior to first '#' */ |
| if (cp) { /* copy from first non whitespace ... */ |
| memcpy(c, lcp, cp - lcp); /* ... to just prior to first '#' */ |
| c[cp - lcp] = '\0'; |
| } else { |
| /* ... to end of line, including null */ |
| snprintf(c, sizeof(c), "%s", lcp); |
| } |
| ll = sg_get_llnum(bp); |
| ok = ((-1 != ll) || all_ascii_f_s(bp, 16)); |
| if (! ok) { |
| pr2serr("%s: error reading LBA (first) item on ", __func__); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (up) |
| sg_put_unaligned_be64((uint64_t)ll, up + 0); |
| ok = false; |
| cp = strchr(bp, ','); |
| if (cp) { |
| bp = cp + 1; |
| if (*bp) { |
| ll = sg_get_llnum(bp); |
| if (-1 != ll) |
| ok = true; |
| } |
| } |
| if ((! ok) || (ll > UINT32_MAX)) { |
| pr2serr("%s: error reading NUM (second) item on ", __func__); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (up) |
| sg_put_unaligned_be32((uint32_t)ll, up + 8); |
| if (sum_num) |
| *sum_num += (uint32_t)ll; |
| /* now for 3 PI items */ |
| for (k = 0; k < 3; ++k) { |
| ok = true; |
| cp = strchr(bp, ','); |
| if (NULL == cp) |
| break; |
| bp = cp + 1; |
| if (*bp) { |
| cp += strspn(bp, " \t"); |
| if ('\0' == *cp) |
| break; |
| else if (',' == *cp) { |
| if (0 == k) |
| ll = DEF_RT; |
| else |
| ll = DEF_AT; /* DEF_AT and DEF_TM have same value */ |
| } else { |
| ll = sg_get_llnum(bp); |
| if (-1 == ll) |
| ok = false; |
| } |
| } |
| if (! ok) { |
| pr2serr("%s: error reading item %d NUM item on ", __func__, |
| k + 3); |
| break; |
| } |
| switch (k) { |
| case 0: |
| if (ll > UINT32_MAX) { |
| pr2serr("%s: error with item 3, >0xffffffff; on ", __func__); |
| ok = false; |
| } else if (up) |
| sg_put_unaligned_be32((uint32_t)ll, up + 12); |
| break; |
| case 1: |
| if (ll > UINT16_MAX) { |
| pr2serr("%s: error with item 4, >0xffff; on ", __func__); |
| ok = false; |
| } else if (up) |
| sg_put_unaligned_be16((uint16_t)ll, up + 16); |
| break; |
| case 2: |
| if (ll > UINT16_MAX) { |
| pr2serr("%s: error with item 5, >0xffff; on ", __func__); |
| ok = false; |
| } else if (up) |
| sg_put_unaligned_be16((uint16_t)ll, up + 18); |
| break; |
| } |
| if (! ok) |
| break; |
| } |
| if (! ok) |
| return SG_LIB_SYNTAX_ERROR; |
| for ( ; k < 3; ++k) { |
| switch (k) { |
| case 0: |
| if (up) |
| sg_put_unaligned_be32((uint32_t)DEF_RT, up + 12); |
| break; |
| case 1: |
| if (up) |
| sg_put_unaligned_be16((uint16_t)DEF_AT, up + 16); |
| break; |
| case 2: |
| if (up) |
| sg_put_unaligned_be16((uint16_t)DEF_TM, up + 18); |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| /* Read pairs or quintets from a scat_file and places them in a T10 scatter |
| * list array is built starting at at t10_scat_list_out (i.e. as per T10 the |
| * first 32 bytes are zeros followed by the first LBA range descriptor (also |
| * 32 bytes long) then the second LBA range descriptor, etc. The pointer |
| * t10_scat_list_out may be NULL in which case the T10 list array is not |
| * built but all other operations take place; this can be useful for sizing |
| * how large the area holding that list needs to be. The max_list_blen may |
| * also be 0. If do_16 is true then only LBA,NUM pairs are expected, |
| * loosely formatted with numbers found alternating between LBA and NUM, with |
| * an even number of elements required overall. If do_16 is false then a |
| * stricter format for quintets is expected: each non comment line should |
| * contain: LBA,NUM[,RT,AT,TM] . If RT,AT,TM are not given then they assume |
| * their defaults (i.e. 0xffffffff, 0xffff, 0xffff). Each number (64 bits for |
| * the LBA, 32 bits for NUM and RT, 16 bit for AT and TM) may be a comma, |
| * space or tab separated list. Assumed decimal unless prefixed by '0x', '0X' |
| * or contains trailing 'h' or 'H' (which indicate hex). Returns 0 if ok, |
| * else error number. If ok also yields the number of LBA range descriptors |
| * written in num_scat_elems and the sum of NUM elements found. Note that |
| * sum_num is not initialized to 0. If parse_one is true then exits |
| * after one LBA range descriptor is decoded. */ |
| static int |
| build_t10_scat(const char * scat_fname, bool do_16, bool parse_one, |
| uint8_t * t10_scat_list_out, uint16_t * num_scat_elems, |
| uint32_t * sum_num, uint32_t max_list_blen) |
| { |
| bool have_stdin = false; |
| bool del_fp = false; |
| bool bit0, ok; |
| int off = 0; |
| int in_len, k, j, m, n, res, err; |
| int64_t ll; |
| char * lcp; |
| uint8_t * up = t10_scat_list_out; |
| FILE * fp = NULL; |
| char line[1024]; |
| |
| if (up) { |
| if (max_list_blen < 64) { |
| pr2serr("%s: t10_scat_list_out is too short\n", __func__); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| memset(up, 0, max_list_blen); |
| } |
| n = lbard_sz; |
| |
| have_stdin = ((1 == strlen(scat_fname)) && ('-' == scat_fname[0])); |
| if (have_stdin) { |
| fp = stdin; |
| scat_fname = "<stdin>"; |
| } else { |
| fp = fopen(scat_fname, "r"); |
| if (NULL == fp) { |
| err = errno; |
| pr2serr("%s: unable to open %s: %s\n", __func__, scat_fname, |
| safe_strerror(err)); |
| return sg_convert_errno(err); |
| } |
| del_fp = true; |
| } |
| for (j = 0; j < 1024; ++j) {/* loop over lines in file */ |
| if ((max_list_blen > 0) && ((n + lbard_sz) > max_list_blen)) |
| goto fini; |
| if (NULL == fgets(line, sizeof(line), fp)) |
| break; |
| // could improve with carry_over logic if sizeof(line) too small |
| in_len = strlen(line); |
| if (in_len > 0) { |
| if ('\n' == line[in_len - 1]) { |
| --in_len; |
| line[in_len] = '\0'; |
| } |
| } |
| if (in_len < 1) |
| continue; |
| lcp = line; |
| m = strspn(lcp, " \t"); |
| if (m == in_len) |
| continue; |
| lcp += m; |
| in_len -= m; |
| if ('#' == *lcp) /* Comment? If so skip rest of line */ |
| continue; |
| k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP ,\t"); |
| if ((k < in_len) && ('#' != lcp[k])) { |
| pr2serr("%s: syntax error in %s at line %d, pos %d\n", |
| __func__, scat_fname, j + 1, m + k + 1); |
| goto bad_exit; |
| } |
| if (! do_16) { |
| res = parse_scat_pi_line(lcp, up ? (up + n) : up, sum_num); |
| if (999 == res) |
| ; |
| else if (0 == res) { |
| n += lbard_sz; |
| if (parse_one) |
| goto fini; |
| } else { |
| if (SG_LIB_CAT_NOT_READY == res) |
| goto bad_mem_exit; |
| pr2serr("line %d in %s\n", j + 1, scat_fname); |
| goto bad_exit; |
| } |
| continue; |
| } |
| for (k = 0; k < 1024; ++k) { |
| ll = sg_get_llnum(lcp); |
| ok = ((-1 != ll) || all_ascii_f_s(lcp, 16)); |
| if (ok) { |
| bit0 = !! (0x1 & (off + k)); |
| if (bit0) { |
| if (ll > UINT32_MAX) { |
| pr2serr("%s: number exceeds 32 bits in line %d, at " |
| "pos %d of %s\n", __func__, j + 1, |
| (int)(lcp - line + 1), scat_fname); |
| goto bad_exit; |
| } |
| if (up) |
| sg_put_unaligned_be32((uint32_t)ll, up + n + 8); |
| if (sum_num) |
| *sum_num += (uint32_t)ll; |
| n += lbard_sz; /* skip to next LBA range descriptor */ |
| if (parse_one) |
| goto fini; |
| } else { |
| if (up) |
| sg_put_unaligned_be64((uint64_t)ll, up + n + 0); |
| } |
| lcp = strpbrk(lcp, " ,\t"); |
| if (NULL == lcp) |
| break; |
| lcp += strspn(lcp, " ,\t"); |
| if ('\0' == *lcp) |
| break; |
| } else { /* no valid number found */ |
| if ('#' == *lcp) { |
| --k; |
| break; |
| } |
| pr2serr("%s: error on line %d, at pos %d\n", __func__, j + 1, |
| (int)(lcp - line + 1)); |
| goto bad_exit; |
| } |
| } /* inner for loop(k) over line elements */ |
| off += (k + 1); |
| } /* outer for loop(j) over lines */ |
| if (do_16 && (0x1 & off)) { |
| pr2serr("%s: expect LBA,NUM pairs but decoded odd number\n from " |
| "%s\n", __func__, scat_fname); |
| goto bad_exit; |
| } |
| fini: |
| *num_scat_elems = (n / lbard_sz) - 1; |
| if (del_fp) |
| fclose(fp); |
| return 0; |
| bad_exit: |
| if (del_fp) |
| fclose(fp); |
| return SG_LIB_SYNTAX_ERROR; |
| bad_mem_exit: |
| if (del_fp) |
| fclose(fp); |
| return SG_LIB_CAT_NOT_READY; /* flag output buffer too small */ |
| } |
| |
| static bool |
| is_pi_default(const struct opts_t * op) |
| { |
| return ((DEF_AT == op->app_tag) && (DEF_RT == op->ref_tag) && |
| (DEF_TM == op->tag_mask)); |
| } |
| |
| /* Given a t10 parameter list header (32 zero bytes) for WRITE SCATTERED |
| * (16 or 32) followed by n RDs with a total length of at least |
| * max_lbrds_blen bytes, find "n" and increment where num_lbard points |
| * n times. Further get the LBA length component from each RD and add each |
| * length into where sum_num points. Note: the caller probably wants to zero |
| * where num_lbard and sum_num point before invoking this function. If all |
| * goes well return true, else false. If a degenerate RD is detected then |
| * if 'RD' (from --scattered=RD) is 0 then stop looking for further RDs; |
| * otherwise keep going. Currently overlapping LBA range descriptors are no |
| * checked for. If op->strict > 0 then the first 32 bytes are checked for |
| * zeros; any non-zero bytes will report to stderr, stop the check and |
| * return false. If op->strict > 0 then the trailing 20 or 12 bytes (only |
| * 12 if RT, AT and TM fields (for PI) are present) are checked for zeros; |
| * any non-zero bytes cause the same action as the previous check. If |
| * the number of RDs (when 'RD' from --scattered=RD > 0) is greater than |
| * the number of RDs found then a report is sent to stderr and if op->strict |
| * > 0 then returns false, else returns true. */ |
| static bool |
| check_lbrds(const uint8_t * up, uint32_t max_lbrds_blen, |
| const struct opts_t * op, uint16_t * num_lbard, |
| uint32_t * sum_num) |
| { |
| bool ok; |
| int k, j, n; |
| const int max_lbrd_start = max_lbrds_blen - lbard_sz; |
| int vb = op->verbose; |
| |
| if (op->strict) { |
| if (max_lbrds_blen < lbard_sz) { |
| pr2serr("%s: %ss too short (%d < 32)\n", __func__, lbard_str, |
| max_lbrds_blen); |
| return false; |
| } |
| if (! sg_all_zeros(up, lbard_sz)) { |
| pr2serr("%s: first 32 bytes of WRITE SCATTERED data-out buffer " |
| "should be zero.\nFound non-zero byte.\n", __func__); |
| return false; |
| } |
| } |
| if (max_lbrds_blen < (2 * lbard_sz)) { |
| *num_lbard = 0; |
| return true; |
| } |
| n = op->scat_num_lbard ? (int)op->scat_num_lbard : -1; |
| for (k = lbard_sz, j = 0; k < max_lbrd_start; k += lbard_sz, ++j) { |
| if ((n < 0) && sg_all_zeros(up + k + 0, 12)) { /* degenerate LBA */ |
| if (vb) /* ... range descriptor terminator if --scattered=0 */ |
| pr2serr("%s: degenerate %s stops scan at k=%d (num_rds=%d)\n", |
| __func__, lbard_str, k, j); |
| break; |
| } |
| *sum_num += sg_get_unaligned_be32(up + k + 8); |
| *num_lbard += 1; |
| if (op->strict) { |
| ok = true; |
| if (op->wrprotect) { |
| if (! sg_all_zeros(up + k + 20, 12)) |
| ok = false; |
| } else if (! sg_all_zeros(up + k + 12, 20)) |
| ok = false; |
| if (! ok) { |
| pr2serr("%s: %s %d non zero in reserved fields\n", __func__, |
| lbard_str, (k / lbard_sz) - 1); |
| return false; |
| } |
| } |
| if (n >= 0) { |
| if (--n <= 0) |
| break; |
| } |
| } |
| if ((k < max_lbrd_start) && op->strict) { /* check pad all zeros */ |
| k += lbard_sz; |
| j = max_lbrds_blen - k; |
| if (! sg_all_zeros(up + k, j)) { |
| pr2serr("%s: pad (%d bytes) following %ss is non zero\n", |
| __func__, j, lbard_str); |
| return false; |
| } |
| } |
| if (vb > 2) |
| pr2serr("%s: about to return true, num_lbard=%u, sum_num=%u " |
| "[k=%d, n=%d]\n", __func__, *num_lbard, *sum_num, k, n); |
| return true; |
| } |
| |
| static int |
| sum_num_lbards(const uint8_t * up, int num_lbards) |
| { |
| int sum = 0; |
| int k, n; |
| |
| for (k = 0, n = lbard_sz; k < num_lbards; ++k, n += lbard_sz) |
| sum += sg_get_unaligned_be32(up + n + 8); |
| return sum; |
| } |
| |
| /* Returns 0 if successful, else sg3_utils error code. */ |
| static int |
| do_write_x(int sg_fd, const void * dataoutp, int dout_len, |
| const struct opts_t * op) |
| { |
| int k, ret, res, sense_cat, cdb_len, vb, err; |
| uint8_t x_cdb[WRITE_X_32_LEN]; /* use for both lengths */ |
| uint8_t sense_b[SENSE_BUFF_LEN] = {0}; |
| struct sg_pt_base * ptvp; |
| |
| memset(x_cdb, 0, sizeof(x_cdb)); |
| vb = op->verbose; |
| cdb_len = op->do_16 ? WRITE_X_16_LEN : WRITE_X_32_LEN; |
| if (16 == cdb_len) { |
| if (! op->do_scattered) |
| sg_put_unaligned_be64(op->lba, x_cdb + 2); |
| x_cdb[14] = (op->grpnum & GRPNUM_MASK); |
| } else { |
| x_cdb[0] = VARIABLE_LEN_OP; |
| x_cdb[6] = (op->grpnum & GRPNUM_MASK); |
| x_cdb[7] = WRITE_X_32_ADD; |
| if (! op->do_scattered) |
| sg_put_unaligned_be64(op->lba, x_cdb + 12); |
| } |
| if (op->do_write_normal) { |
| if (16 == cdb_len) { |
| x_cdb[0] = WRITE_16_OP; |
| x_cdb[1] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[1] |= 0x10; |
| if (op->fua) |
| x_cdb[1] |= 0x8; |
| if (op->dld) { |
| if (op->dld & 1) |
| x_cdb[14] |= 0x40; |
| if (op->dld & 2) |
| x_cdb[14] |= 0x80; |
| if (op->dld & 4) |
| x_cdb[1] |= 0x1; |
| } |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 10); |
| } else { /* 32 byte WRITE */ |
| sg_put_unaligned_be16((uint16_t)WRITE_32_SA, x_cdb + 8); |
| x_cdb[10] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[10] |= 0x10; |
| if (op->fua) |
| x_cdb[10] |= 0x8; |
| if (op->dld) { /* added in sbc4r19 */ |
| if (op->dld & 1) |
| x_cdb[11] |= 0x1; |
| if (op->dld & 2) |
| x_cdb[11] |= 0x2; |
| if (op->dld & 4) |
| x_cdb[11] |= 0x4; |
| } |
| sg_put_unaligned_be32(op->ref_tag, x_cdb + 20); |
| sg_put_unaligned_be16(op->app_tag, x_cdb + 24); |
| sg_put_unaligned_be16(op->tag_mask, x_cdb + 26); |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 28); |
| } |
| } else if (op->do_atomic) { |
| if (16 == cdb_len) { |
| if (op->numblocks > UINT16_MAX) { |
| pr2serr("Need WRITE ATOMIC(32) since blocks exceed 65535\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| x_cdb[0] = WRITE_ATOMIC16_OP; |
| x_cdb[1] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[1] |= 0x10; |
| if (op->fua) |
| x_cdb[1] |= 0x8; |
| sg_put_unaligned_be16(op->atomic_boundary, x_cdb + 10); |
| sg_put_unaligned_be16((uint16_t)op->numblocks, x_cdb + 12); |
| } else { /* 32 byte WRITE ATOMIC */ |
| sg_put_unaligned_be16(op->atomic_boundary, x_cdb + 4); |
| sg_put_unaligned_be16((uint16_t)WRITE_ATOMIC32_SA, x_cdb + 8); |
| x_cdb[10] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[10] |= 0x10; |
| if (op->fua) |
| x_cdb[10] |= 0x8; |
| sg_put_unaligned_be32(op->ref_tag, x_cdb + 20); |
| sg_put_unaligned_be16(op->app_tag, x_cdb + 24); |
| sg_put_unaligned_be16(op->tag_mask, x_cdb + 26); |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 28); |
| } |
| } else if (op->do_or) { /* ORWRITE(16 or 32) */ |
| if (16 == cdb_len) { |
| x_cdb[0] = ORWRITE16_OP; |
| x_cdb[1] = ((op->wrprotect & 0x7) << 5); /* actually ORPROTECT */ |
| if (op->dpo) |
| x_cdb[1] |= 0x10; |
| if (op->fua) |
| x_cdb[1] |= 0x8; |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 10); |
| } else { |
| x_cdb[2] = op->bmop; |
| x_cdb[3] = op->pgp; |
| sg_put_unaligned_be16((uint16_t)ORWRITE32_SA, x_cdb + 8); |
| x_cdb[10] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[10] |= 0x10; |
| if (op->fua) |
| x_cdb[10] |= 0x8; |
| sg_put_unaligned_be32(op->orw_eog, x_cdb + 20); |
| sg_put_unaligned_be32(op->orw_nog, x_cdb + 24); |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 28); |
| } |
| } else if (op->do_same) { |
| if (16 == cdb_len) { |
| x_cdb[0] = WRITE_SAME16_OP; |
| x_cdb[1] = ((op->wrprotect & 0x7) << 5); |
| if (op->do_anchor) |
| x_cdb[1] |= 0x10; |
| if (op->do_unmap) |
| x_cdb[1] |= 0x8; |
| if (op->ndob) |
| x_cdb[1] |= 0x1; |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 10); |
| } else { |
| sg_put_unaligned_be16((uint16_t)WRITE_SAME_SA, x_cdb + 8); |
| x_cdb[10] = ((op->wrprotect & 0x7) << 5); |
| if (op->do_anchor) |
| x_cdb[10] |= 0x10; |
| if (op->do_unmap) |
| x_cdb[10] |= 0x8; |
| if (op->ndob) |
| x_cdb[10] |= 0x1; |
| /* Expected initial logical block reference tag */ |
| sg_put_unaligned_be32(op->ref_tag, x_cdb + 20); |
| sg_put_unaligned_be16(op->app_tag, x_cdb + 24); |
| sg_put_unaligned_be16(op->tag_mask, x_cdb + 26); |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 28); |
| } |
| } else if (op->do_scattered) { |
| if (16 == cdb_len) { |
| x_cdb[0] = SERVICE_ACTION_OUT_16_OP; |
| x_cdb[1] = WRITE_SCATTERED16_SA; |
| x_cdb[2] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[2] |= 0x10; |
| if (op->fua) |
| x_cdb[2] |= 0x8; |
| if (op->dld) { |
| if (op->dld & 1) |
| x_cdb[14] |= 0x40; |
| if (op->dld & 2) |
| x_cdb[14] |= 0x80; |
| if (op->dld & 4) |
| x_cdb[2] |= 0x1; |
| } |
| sg_put_unaligned_be16(op->scat_lbdof, x_cdb + 4); |
| sg_put_unaligned_be16(op->scat_num_lbard, x_cdb + 8); |
| /* Spec says Buffer Transfer Length field (BTL) is the number |
| * of (user) Logical Blocks in the data-out buffer and that BTL |
| * may be 0. So the total data-out buffer length in bytes is: |
| * (scat_lbdof + numblocks) * actual_block_size */ |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 10); |
| } else { |
| sg_put_unaligned_be16((uint16_t)WRITE_SCATTERED32_SA, x_cdb + 8); |
| x_cdb[10] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[10] |= 0x10; |
| if (op->fua) |
| x_cdb[10] |= 0x8; |
| sg_put_unaligned_be16(op->scat_lbdof, x_cdb + 12); |
| sg_put_unaligned_be16(op->scat_num_lbard, x_cdb + 16); |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 28); |
| /* ref_tag, app_tag and tag_mask placed in scatter list */ |
| } |
| } else if (op->do_stream) { |
| if (16 == cdb_len) { |
| x_cdb[0] = WRITE_STREAM16_OP; |
| x_cdb[1] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[1] |= 0x10; |
| if (op->fua) |
| x_cdb[1] |= 0x8; |
| sg_put_unaligned_be16(op->str_id, x_cdb + 10); |
| sg_put_unaligned_be16((uint16_t)op->numblocks, x_cdb + 12); |
| } else { |
| sg_put_unaligned_be16(op->str_id, x_cdb + 4); |
| sg_put_unaligned_be16((uint16_t)WRITE_STREAM32_SA, x_cdb + 8); |
| x_cdb[10] = ((op->wrprotect & 0x7) << 5); |
| if (op->dpo) |
| x_cdb[10] |= 0x10; |
| if (op->fua) |
| x_cdb[10] |= 0x8; |
| sg_put_unaligned_be32(op->ref_tag, x_cdb + 20); |
| sg_put_unaligned_be16(op->app_tag, x_cdb + 24); |
| sg_put_unaligned_be16(op->tag_mask, x_cdb + 26); |
| sg_put_unaligned_be32(op->numblocks, x_cdb + 28); |
| } |
| } else { |
| pr2serr("%s: bad cdb name or length (%d)\n", __func__, cdb_len); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| |
| if (vb > 1) { |
| char b[128]; |
| |
| pr2serr(" %s cdb: %s\n", op->cdb_name, |
| sg_get_command_str(x_cdb, cdb_len, false, sizeof(b), b)); |
| } |
| if (op->do_scattered && (vb > 2) && (dout_len > 31)) { |
| uint32_t sod_off = op->bs_pi_do * op->scat_lbdof; |
| const uint8_t * up = (const uint8_t *)dataoutp; |
| |
| pr2serr(" %s scatter list, number of %ss: %u\n", op->cdb_name, |
| lbard_str, op->scat_num_lbard); |
| pr2serr(" byte offset of data_to_write: %u, dout_len: %d\n", |
| sod_off, dout_len); |
| up += lbard_sz; /* step over parameter list header */ |
| for (k = 0; k < (int)op->scat_num_lbard; ++k, up += lbard_sz) { |
| pr2serr(" desc %d: LBA=0x%" PRIx64 " numblocks=%" PRIu32 |
| "%s", k, sg_get_unaligned_be64(up + 0), |
| sg_get_unaligned_be32(up + 8), (op->do_16 ? "\n" : " ")); |
| if (op->do_32) |
| pr2serr("rt=0x%x at=0x%x tm=0x%x\n", |
| sg_get_unaligned_be32(up + 12), |
| sg_get_unaligned_be16(up + 16), |
| sg_get_unaligned_be16(up + 18)); |
| if ((uint32_t)(((k + 2) * lbard_sz) + 20) > sod_off) { |
| pr2serr("Warning: possible clash of descriptor %u with " |
| "data_to_write\n", k); |
| if (op->strict > 1) |
| return SG_LIB_FILE_ERROR; |
| } |
| } |
| } |
| if ((vb > 3) && (dout_len > 0)) { |
| if ((dout_len > 1024) && (vb < 7)) { |
| pr2serr(" Data-out buffer contents (first 1024 of %u " |
| "bytes):\n", dout_len); |
| hex2stdout((const uint8_t *)dataoutp, 1024, 1); |
| pr2serr(" Above: dout's first 1024 of %u bytes [%s]\n", |
| dout_len, op->cdb_name); |
| } else { |
| pr2serr(" Data-out buffer contents (length=%u):\n", dout_len); |
| hex2stderr((const uint8_t *)dataoutp, (int)dout_len, 1); |
| } |
| } |
| if (op->dry_run) { |
| if (vb) |
| pr2serr("Exit just before sending %s due to --dry-run\n", |
| op->cdb_name); |
| if (op->dry_run > 1) { |
| int w_fd; |
| |
| w_fd = open(xx_wr_fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| if (w_fd < 0) { |
| err = errno; |
| perror(xx_wr_fname); |
| return sg_convert_errno(err); |
| } |
| res = write(w_fd, dataoutp, dout_len); |
| if (res < 0) { |
| err = errno; |
| perror(xx_wr_fname); |
| close(w_fd); |
| return sg_convert_errno(err); |
| } |
| close(w_fd); |
| printf("Wrote %u bytes to %s", dout_len, xx_wr_fname); |
| if (op->do_scattered) |
| printf(", LB data offset: %u\nNumber of %ss: %u\n", |
| op->scat_lbdof, lbard_str, op->scat_num_lbard); |
| else |
| printf("\n"); |
| } |
| return 0; |
| } |
| ptvp = construct_scsi_pt_obj(); |
| if (NULL == ptvp) { |
| pr2serr("%s: out of memory\n", op->cdb_name); |
| return sg_convert_errno(ENOMEM); |
| } |
| set_scsi_pt_cdb(ptvp, x_cdb, cdb_len); |
| set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); |
| if (dout_len > 0) |
| set_scsi_pt_data_out(ptvp, (uint8_t *)dataoutp, dout_len); |
| else if (vb && (! op->ndob)) |
| pr2serr("%s: dout_len==0, so empty dout buffer\n", |
| op->cdb_name); |
| res = do_scsi_pt(ptvp, sg_fd, op->timeout, vb); |
| ret = sg_cmds_process_resp(ptvp, op->cdb_name, res, true /*noisy */, vb, |
| &sense_cat); |
| if (-1 == ret) { |
| if (get_scsi_pt_transport_err(ptvp)) |
| ret = SG_LIB_TRANSPORT_ERROR; |
| else |
| ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); |
| } else if (-2 == ret) { |
| switch (sense_cat) { |
| case SG_LIB_CAT_RECOVERED: |
| case SG_LIB_CAT_NO_SENSE: |
| ret = 0; |
| break; |
| case SG_LIB_CAT_MEDIUM_HARD: |
| { |
| bool valid; |
| int slen; |
| uint64_t ull = 0; |
| |
| slen = get_scsi_pt_sense_len(ptvp); |
| valid = sg_get_sense_info_fld(sense_b, slen, &ull); |
| if (valid) { |
| pr2serr("Medium or hardware error starting at "); |
| if (op->do_scattered) { |
| if (0 == ull) |
| pr2serr("%s=<not reported>\n", lbard_str); |
| else |
| pr2serr("%s=%" PRIu64 " (origin 0)\n", lbard_str, |
| ull - 1); |
| if (sg_get_sense_cmd_spec_fld(sense_b, slen, &ull)) { |
| if (0 == ull) |
| pr2serr(" Number of successfully written " |
| "%ss is 0 or not reported\n", |
| lbard_str); |
| else |
| pr2serr(" Number of successfully written " |
| "%ss is %u\n", lbard_str, |
| (uint32_t)ull); |
| } |
| } else |
| pr2serr("lba=%" PRIu64 " [0x%" PRIx64 "]\n", ull, |
| ull); |
| } |
| } |
| ret = sense_cat; |
| break; |
| case SG_LIB_CAT_ILLEGAL_REQ: |
| if (vb) |
| sg_print_command_len(x_cdb, cdb_len); |
| ret = sense_cat; |
| break; |
| default: |
| ret = sense_cat; |
| break; |
| } |
| } else |
| ret = 0; |
| |
| destruct_scsi_pt_obj(ptvp); |
| return ret; |
| } |
| |
| /* Returns 0 if successful, else sg3_utils error code. */ |
| static int |
| do_read_capacity(int sg_fd, struct opts_t *op) |
| { |
| bool prot_en = false; |
| int res; |
| int vb = op->verbose; |
| char b[80]; |
| uint8_t resp_buff[RCAP16_RESP_LEN]; |
| |
| res = sg_ll_readcap_16(sg_fd, false /* pmi */, 0 /* llba */, resp_buff, |
| RCAP16_RESP_LEN, true, (vb ? (vb - 1): 0)); |
| if (SG_LIB_CAT_UNIT_ATTENTION == res) { |
| pr2serr("Read capacity(16) unit attention, try again\n"); |
| res = sg_ll_readcap_16(sg_fd, false, 0, resp_buff, RCAP16_RESP_LEN, |
| true, (vb ? (vb - 1): 0)); |
| } |
| if (0 == res) { |
| uint32_t pi_len = 0; |
| |
| if (vb > 3) { |
| pr2serr("Read capacity(16) response:\n"); |
| hex2stderr(resp_buff, RCAP16_RESP_LEN, 1); |
| } |
| op->bs = sg_get_unaligned_be32(resp_buff + 8); |
| op->tot_lbs = sg_get_unaligned_be64(resp_buff + 0) + 1; |
| prot_en = !!(resp_buff[12] & 0x1); |
| if (prot_en) { |
| uint32_t pi_exp; |
| |
| op->pi_type = ((resp_buff[12] >> 1) & 0x7) + 1; |
| pi_exp = 0xf & (resp_buff[13] >> 4); |
| pi_len = 8 * (1 << pi_exp); |
| if (op->wrprotect > 0) { |
| op->bs_pi_do = op->bs + pi_len; |
| if (vb > 1) |
| pr2serr(" For data out buffer purposes the effective " |
| "block size is %u (lb size\n is %u) because " |
| "PROT_EN=1, PI_EXP=%u and WRPROTECT>0\n", op->bs, |
| pi_exp, op->bs_pi_do); |
| } |
| } else { /* device formatted to PI type 0 (i.e. none) */ |
| op->pi_type = 0; |
| if (op->wrprotect > 0) { |
| if (vb) |
| pr2serr("--wrprotect (%d) expects PI but %s says it " |
| "has none\n", op->wrprotect, op->device_name); |
| if (op->strict) |
| return SG_LIB_FILE_ERROR; |
| else if (vb) |
| pr2serr(" ... continue but could be dangerous\n"); |
| } |
| } |
| if (vb) { |
| uint8_t d[2]; |
| |
| pr2serr("Read capacity(16) response fields:\n"); |
| pr2serr(" Last_LBA=0x%" PRIx64 " LB size: %u (with PI: " |
| "%u) bytes p_type=%u\n", op->tot_lbs - 1, |
| op->bs, op->bs + (prot_en ? pi_len : 0), |
| ((resp_buff[12] >> 1) & 0x7)); |
| pr2serr(" prot_en=%u [PI type=%u] p_i_exp=%u lbppb_exp=%u " |
| "lbpme,rz=%u,", prot_en, op->pi_type, |
| ((resp_buff[13] >> 4) & 0xf), (resp_buff[13] & 0xf), |
| !!(resp_buff[14] & 0x80)); |
| memcpy(d, resp_buff + 14, 2); |
| d[0] &= 0x3f; |
| pr2serr("%u low_ali_lba=%u\n", !!(resp_buff[14] & 0x40), |
| sg_get_unaligned_be16(d)); |
| } |
| } else if ((SG_LIB_CAT_INVALID_OP == res) || |
| (SG_LIB_CAT_ILLEGAL_REQ == res)) { |
| if (vb) |
| pr2serr("Read capacity(16) not supported, try Read " |
| "capacity(10)\n"); |
| res = sg_ll_readcap_10(sg_fd, false /* pmi */, 0 /* lba */, |
| resp_buff, RCAP10_RESP_LEN, true, |
| (vb ? (vb - 1): 0)); |
| if (0 == res) { |
| if (vb > 3) { |
| pr2serr("Read capacity(10) response:\n"); |
| hex2stderr(resp_buff, RCAP10_RESP_LEN, 1); |
| } |
| op->tot_lbs = sg_get_unaligned_be32(resp_buff + 0) + 1; |
| op->bs = sg_get_unaligned_be32(resp_buff + 4); |
| } else { |
| strcpy(b,"OS error"); |
| if (res > 0) |
| sg_get_category_sense_str(res, sizeof(b), b, vb); |
| else |
| snprintf(b, sizeof(b), "error: %d", res); |
| pr2serr("Read capacity(10): %s\n", b); |
| pr2serr("Unable to calculate block size\n"); |
| return (res > 0) ? res : SG_LIB_FILE_ERROR; |
| } |
| } else { |
| if (vb) { |
| strcpy(b,"OS error"); |
| if (res > 0) |
| sg_get_category_sense_str(res, sizeof(b), b, vb); |
| pr2serr("Read capacity(16): %s\n", b); |
| pr2serr("Unable to calculate block size\n"); |
| } |
| return (res > 0) ? res : SG_LIB_FILE_ERROR; |
| } |
| op->bs_pi_do = op->expect_pi_do ? (op->bs + 8) : op->bs; |
| return 0; |
| } |
| |
| #define WANT_ZERO_EXIT 9999 |
| static const char * const opt_long_ctl_str = |
| "36a:A:b:B:c:dD:Efg:G:hi:I:l:M:n:No:Oq:Qr:RsS:t:T:u:vVw:x"; |
| |
| /* command line processing, options and arguments. Returns 0 if ok, |
| * returns WANT_ZERO_EXIT so upper level yields an exist status of zero. |
| * Other return values (mainly SG_LIB_SYNTAX_ERROR) indicate errors. */ |
| static int |
| parse_cmd_line(struct opts_t *op, int argc, char *argv[], |
| const char ** lba_opp, const char ** num_opp) |
| { |
| bool fail_if_strict = false; |
| int c, j; |
| int64_t ll; |
| const char * cp; |
| |
| while (1) { |
| int opt_ind = 0; |
| |
| c = getopt_long(argc, argv, opt_long_ctl_str, long_options, &opt_ind); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case '3': /* same as --32 */ |
| op->do_32 = true; |
| break; |
| case '6': /* same as --16 */ |
| op->do_16 = true; |
| break; |
| case 'a': |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > (int)UINT16_MAX)) { |
| pr2serr("bad argument to '--app-tag='. Expect 0 to 0xffff " |
| "inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->app_tag = (uint16_t)j; |
| break; |
| case 'A': |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > (int)UINT16_MAX)) { |
| pr2serr("bad argument to '--atomic='. Expect 0 to 0xffff " |
| "inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->atomic_boundary = (uint16_t)j; |
| op->do_atomic = true; |
| op->cmd_name = "Write atomic"; |
| break; |
| case 'b': /* logical block size in bytes */ |
| j = sg_get_num(optarg); /* 0 -> look up with READ CAPACITY */ |
| if ((j < 0) || (j > (1 << 28))) { |
| pr2serr("bad argument to '--bs='. Expect 0 or greater\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (j > 0) { |
| int k; |
| int m = j; |
| int highest_ind; |
| |
| if (j < 512) { |
| pr2serr("warning: --bs=BS value is < 512 which seems too " |
| "small, continue\n"); |
| fail_if_strict = true; |
| } |
| if (0 != (j % 8)) { |
| pr2serr("warning: --bs=BS value is not a multiple of 8, " |
| "unexpected, continue\n"); |
| fail_if_strict = true; |
| } |
| for (k = 0, highest_ind = 0; k < 28; ++ k, m >>= 1) { |
| if (1 & m) |
| highest_ind = k; |
| } /* loop should get log_base2(j) */ |
| k = 1 << highest_ind; |
| if (j == k) { /* j is a power of two; actual and logical |
| * block size is assumed to be the same */ |
| op->bs = (uint32_t)j; |
| op->bs_pi_do = op->bs; |
| } else { /* j is not power_of_two, use as actual LB size */ |
| op->bs = (uint32_t)k; /* power_of_two less than j */ |
| op->bs_pi_do = (uint32_t)j; |
| } |
| } else { /* j==0, let READCAP sort this out */ |
| op->bs = 0; |
| op->bs_pi_do = 0; |
| } |
| break; |
| case 'B': /* --bmop=OP,PGP (for ORWRITE(32)) */ |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > 7)) { |
| pr2serr("bad first argument to '--bmop='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->bmop = (uint8_t)j; |
| if ((cp = strchr(optarg, ','))) { |
| j = sg_get_num(cp + 1); |
| if ((j < 0) || (j > 15)) { |
| pr2serr("bad second argument to '--bmop='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->pgp = (uint8_t)j; |
| } |
| break; |
| case 'c': /* --combined=DOF for W SCATTERED, DOF: data offset */ |
| j = sg_get_num(optarg); |
| if (j < 0) { |
| pr2serr("bad argument to '--combined='. Expect 0 to " |
| "0x7fffffff\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->scat_lbdof = (uint16_t)j; |
| op->do_combined = true; |
| break; |
| case 'd': |
| op->dpo = true; |
| break; |
| case 'D': |
| op->dld = sg_get_num(optarg); |
| if ((op->dld < 0) || (op->dld > 7)) { |
| pr2serr("bad argument to '--dld=', expect 0 to 7 " |
| "inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'f': |
| op->fua = true; |
| break; |
| case 'g': |
| op->grpnum = sg_get_num(optarg); |
| if ((op->grpnum < 0) || (op->grpnum > 63)) { |
| pr2serr("bad argument to '--grpnum'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'G': /* --generation=EOG,NOG */ |
| ll = sg_get_llnum(optarg); |
| if ((ll < 0) || (ll > UINT32_MAX)) { |
| pr2serr("bad first argument to '--generation='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->orw_eog = (uint32_t)ll; |
| if ((cp = strchr(optarg, ','))) { |
| ll = sg_get_llnum(cp + 1); |
| if ((ll < 0) || (ll > UINT32_MAX)) { |
| pr2serr("bad second argument to '--generation='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->orw_nog = (uint32_t)ll; |
| } else { |
| pr2serr("need two arguments with --generation=EOG,NOG and " |
| "they must be comma separated\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'h': |
| ++op->help; |
| break; |
| case '?': |
| pr2serr("\n"); |
| usage((op->help > 0) ? op->help : 0); |
| return SG_LIB_SYNTAX_ERROR; |
| case 'i': |
| op->if_name = optarg; |
| break; |
| case 'I': |
| op->timeout = sg_get_num(optarg); |
| if (op->timeout < 0) { |
| pr2serr("bad argument to '--timeout='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'l': |
| if (*lba_opp) { |
| pr2serr("only expect '--lba=' option once\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| *lba_opp = optarg; |
| break; |
| case 'M': /* WRITE SAME */ |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > 1)) { |
| pr2serr("bad argument to '--same', expect 0 or 1\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->ndob = (bool)j; |
| op->do_same = true; |
| op->cmd_name = "Write same"; |
| break; |
| case 'n': |
| if (*num_opp) { |
| pr2serr("only expect '--num=' option once\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| *num_opp = optarg; |
| break; |
| case 'N': |
| op->do_write_normal = true; |
| op->cmd_name = "Write"; |
| break; |
| case 'o': |
| ll = sg_get_llnum(optarg); |
| if (-1 == ll) { |
| pr2serr("bad first argument to '--offset='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->if_offset = (uint64_t)ll; |
| if ((cp = strchr(optarg, ','))) { |
| ll = sg_get_llnum(cp + 1); |
| if (-1 == ll) { |
| pr2serr("bad second argument to '--offset='\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (ll > UINT32_MAX) { |
| pr2serr("bad second argument to '--offset=', cannot " |
| "exceed 32 bits\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->if_dlen = (uint32_t)ll; |
| } |
| break; |
| case 'O': |
| op->do_or = true; |
| op->cmd_name = "Orwrite"; |
| break; |
| case 'q': |
| op->scat_filename = optarg; |
| break; |
| case 'Q': |
| op->do_quiet = true; |
| break; |
| case 'R': |
| op->do_scat_raw = true; |
| break; |
| case 'r': /* same as --ref-tag= */ |
| ll = sg_get_llnum(optarg); |
| if ((ll < 0) || (ll > UINT32_MAX)) { |
| pr2serr("bad argument to '--ref-tag='. Expect 0 to " |
| "0xffffffff inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->ref_tag = (uint32_t)ll; |
| break; |
| case 's': |
| ++op->strict; |
| break; |
| case 'S': |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > (int)UINT16_MAX)) { |
| pr2serr("bad argument to '--scattered='. Expect 0 to 0xffff " |
| "inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->scat_num_lbard = (uint16_t)j; |
| op->do_scattered = true; |
| op->cmd_name = "Write scattered"; |
| break; |
| case 't': /* same as --tag-mask= */ |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > (int)UINT16_MAX)) { |
| pr2serr("bad argument to '--tag-mask='. Expect 0 to 0xffff " |
| "inclusive\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->tag_mask = (uint16_t)j; |
| break; |
| case 'T': /* WRITE STREAM */ |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > (int)UINT16_MAX)) { |
| pr2serr("bad argument to '--stream=', expect 0 to 65535\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->str_id = (uint16_t)j; |
| op->do_stream = true; |
| op->cmd_name = "Write stream"; |
| break; |
| case 'u': /* WRITE SAME, UNMAP and ANCHOR bit */ |
| j = sg_get_num(optarg); |
| if ((j < 0) || (j > 3)) { |
| pr2serr("bad argument to '--unmap=', expect 0 to " |
| "3\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->do_unmap = !!(1 & j); |
| op->do_anchor = !!(2 & j); |
| break; |
| case 'v': |
| op->verbose_given = true; |
| ++op->verbose; |
| break; |
| case 'V': |
| op->version_given = true; |
| break; |
| case 'w': /* WRPROTECT field (or ORPROTECT for ORWRITE) */ |
| op->wrprotect = sg_get_num(optarg); |
| if ((op->wrprotect < 0) || (op->wrprotect > 7)) { |
| pr2serr("bad argument to '--wrprotect'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| op->expect_pi_do = (op->wrprotect > 0); |
| break; |
| case 'x': |
| ++op->dry_run; |
| break; |
| default: |
| pr2serr("unrecognised option code 0x%x ??\n", c); |
| usage((op->help > 0) ? op->help : 0); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (optind < argc) { |
| if (NULL == op->device_name) { |
| op->device_name = argv[optind]; |
| ++optind; |
| } |
| if (optind < argc) { |
| for (; optind < argc; ++optind) |
| pr2serr("Unexpected extra argument: %s\n", argv[optind]); |
| usage((op->help > 0) ? op->help : 0); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (op->strict && fail_if_strict) |
| return SG_LIB_SYNTAX_ERROR; |
| return 0; |
| } |
| |
| static int |
| process_scattered(int sg_fd, int infd, uint32_t if_len, uint32_t if_rlen, |
| int sfr_fd, uint32_t sf_len, uint64_t * addr_arr, |
| uint32_t addr_arr_len, uint32_t * num_arr, |
| uint16_t num_lbard, uint32_t sum_num, struct opts_t * op) |
| { |
| int k, n, ret; |
| int vb = op->verbose; |
| uint32_t d, dd, nn, do_len; |
| uint8_t * up = NULL; |
| uint8_t * free_up = NULL; |
| char b[80]; |
| |
| if (op->do_combined) { /* --combined=DOF (.scat_lbdof) */ |
| if (op->scat_lbdof > 0) |
| d = op->scat_lbdof * op->bs_pi_do; |
| else if (op->scat_num_lbard > 0) { |
| d = lbard_sz * (1 + op->scat_num_lbard); |
| if (0 != (d % op->bs_pi_do)) |
| d = ((d / op->bs_pi_do) + 1) * op->bs_pi_do; |
| } else if (if_len > 0) { |
| d = if_len; |
| if (0 != (d % op->bs_pi_do)) |
| d = ((d / op->bs_pi_do) + 1) * op->bs_pi_do; |
| } else { |
| pr2serr("With --combined= if DOF, RD are 0 and IF has an " |
| "unknown length\nthen give up\n"); |
| return SG_LIB_CONTRADICT; |
| } |
| up = sg_memalign(d, 0, &free_up, false); |
| if (NULL == up) { |
| pr2serr("unable to allocate aligned memory for " |
| "scatterlist+data\n"); |
| return sg_convert_errno(ENOMEM); |
| } |
| ret = bin_read(infd, up, ((if_len < d) ? if_len : d), "IF c1"); |
| if (ret) |
| goto finii; |
| if (! check_lbrds(up, d, op, &num_lbard, &sum_num)) |
| goto file_err_outt; |
| if ((op->scat_num_lbard > 0) && (op->scat_num_lbard != num_lbard)) { |
| bool rd_gt = (op->scat_num_lbard > num_lbard); |
| |
| if (rd_gt || op->strict || vb) { |
| pr2serr("RD (%u) %s number of %ss (%u) found in IF\n", |
| op->scat_num_lbard, (rd_gt ? ">" : "<"), lbard_str, |
| num_lbard); |
| if (rd_gt) |
| goto file_err_outt; |
| else if (op->strict) |
| goto file_err_outt; |
| } |
| num_lbard = op->scat_num_lbard; |
| sum_num = sum_num_lbards(up, op->scat_num_lbard); |
| } else |
| op->scat_num_lbard = num_lbard; |
| dd = lbard_sz * (num_lbard + 1); |
| if (0 != (dd % op->bs_pi_do)) |
| dd = ((dd / op->bs_pi_do) + 1) * op->bs_pi_do; /* round up */ |
| nn = op->scat_lbdof * op->bs_pi_do; |
| if (dd != nn) { |
| bool dd_gt = (dd > nn); |
| |
| if (dd_gt) { |
| pr2serr("%s: Cannot fit %ss (%u) in given LB data offset " |
| "(%u)\n", __func__, lbard_str, num_lbard, |
| op->scat_lbdof); |
| goto file_err_outt; |
| } |
| if (vb || op->strict) |
| pr2serr("%s: empty blocks before LB data offset (%u), could " |
| "be okay\n", __func__, op->scat_lbdof); |
| if (op->strict) { |
| pr2serr("Exiting due to --strict; perhaps try again with " |
| "--combined=%u\n", dd / op->bs_pi_do); |
| goto file_err_outt; |
| } |
| dd = nn; |
| } |
| dd += (sum_num * op->bs_pi_do); |
| if (dd > d) { |
| uint8_t * u2p; |
| uint8_t * free_u2p; |
| |
| if (dd != if_len) { |
| bool dd_gt = (dd > if_len); |
| |
| if (dd_gt || op->strict || vb) { |
| pr2serr("Calculated dout length (%u) %s bytes available " |
| "in IF (%u)\n", dd, (dd_gt ? ">" : "<"), if_len); |
| if (dd_gt) |
| goto file_err_outt; |
| else if (op->strict) |
| goto file_err_outt; |
| } |
| } |
| u2p = sg_memalign(dd, 0, &free_u2p, false); |
| if (NULL == u2p) { |
| pr2serr("unable to allocate memory for final " |
| "scatterlist+data\n"); |
| ret = sg_convert_errno(ENOMEM); |
| goto finii; |
| } |
| memcpy(u2p, up, d); |
| free(free_up); |
| up = u2p; |
| free_up = free_u2p; |
| ret = bin_read(infd, up + d, dd - d, "IF c2"); |
| if (ret) |
| goto finii; |
| } |
| do_len = dd; |
| op->numblocks = sum_num; |
| op->xfer_bytes = sum_num * op->bs_pi_do; |
| goto do_io; |
| } |
| |
| /* other than do_combined, so --scat-file= or --lba= */ |
| if (addr_arr_len > 0) |
| num_lbard = addr_arr_len; |
| |
| if (op->scat_filename && (! op->do_scat_raw)) { |
| d = lbard_sz * (num_lbard + 1); |
| nn = d; |
| op->scat_lbdof = d / op->bs_pi_do; |
| if (0 != (d % op->bs_pi_do)) /* if not multiple, round up */ |
| op->scat_lbdof += 1; |
| dd = op->scat_lbdof * op->bs_pi_do; |
| d = sum_num * op->bs_pi_do; |
| do_len = dd + d; |
| /* zeroed data-out buffer for SL+DATA */ |
| up = sg_memalign(do_len, 0, &free_up, false); |
| if (NULL == up) { |
| pr2serr("unable to allocate aligned memory for " |
| "scatterlist+data\n"); |
| return sg_convert_errno(ENOMEM); |
| } |
| num_lbard = 0; |
| sum_num = 0; |
| nn = (nn > lbard_sz) ? nn : (op->scat_lbdof * op->bs_pi_do); |
| ret = build_t10_scat(op->scat_filename, op->do_16, ! op->do_scattered, |
| up, &num_lbard, &sum_num, nn); |
| if (ret) |
| goto finii; |
| /* Calculate number of bytes to read from IF (place in 'd') */ |
| d = sum_num * op->bs_pi_do; |
| if (op->if_dlen > d) { |
| if (op->strict || vb) { |
| pr2serr("DLEN > than bytes implied by sum of scatter " |
| "list NUMs (%u)\n", d); |
| if (vb > 1) |
| pr2serr(" num_lbard=%u, sum_num=%u actual_bs=%u", |
| num_lbard, sum_num, op->bs_pi_do); |
| if (op->strict) |
| goto file_err_outt; |
| } |
| } else if ((op->if_dlen > 0) && (op->if_dlen < d)) |
| d = op->if_dlen; |
| if ((if_rlen > 0) && (if_rlen != d)) { |
| bool readable_lt = (if_rlen < d); |
| |
| if (vb) |
| pr2serr("readable length (%u) of IF %s bytes implied by " |
| "sum of\nscatter list NUMs (%u) and DLEN\n", |
| (uint32_t)if_rlen, |
| readable_lt ? "<" : ">", d); |
| if (op->strict) { |
| if ((op->strict > 1) || (! readable_lt)) |
| goto file_err_outt; |
| } |
| if (readable_lt) |
| d = if_rlen; |
| } |
| if (0 != (d % op->bs_pi_do)) { |
| if (vb || (op->strict > 1)) { |
| pr2serr("Calculated data-out length (0x%x) not a " |
| "multiple of BS (%u", d, op->bs); |
| if (op->bs != op->bs_pi_do) |
| pr2serr(" + %d(PI)", (int)op->bs_pi_do - (int)op->bs); |
| if (op->strict > 1) { |
| pr2serr(")\nexiting ...\n"); |
| goto file_err_outt; |
| } else |
| pr2serr(")\nzero pad and continue ...\n"); |
| } |
| } |
| ret = bin_read(infd, up + (op->scat_lbdof * op->bs_pi_do), d, |
| "IF 3"); |
| if (ret) |
| goto finii; |
| do_len = ((op->scat_lbdof + sum_num) * op->bs_pi_do); |
| op->numblocks = sum_num; |
| op->xfer_bytes = sum_num * op->bs_pi_do; |
| /* dout for scattered write with ASCII scat_file ready */ |
| } else if (op->do_scat_raw) { |
| bool if_len_gt = false; |
| |
| /* guessing game for length of buffer */ |
| if (op->scat_num_lbard > 0) { |
| dd = (op->scat_num_lbard + 1) * lbard_sz; |
| if (sf_len < dd) { |
| pr2serr("SF not long enough (%u bytes) to provide RD " |
| "(%u) %ss\n", sf_len, dd, lbard_str); |
| goto file_err_outt; |
| } |
| nn = dd / op->bs_pi_do; |
| if (0 != (dd % op->bs_pi_do)) |
| nn +=1; |
| dd = nn * op->bs_pi_do; |
| } else |
| dd = op->bs_pi_do; /* guess */ |
| if (if_len > 0) { |
| nn = if_len / op->bs_pi_do; |
| if (0 != (if_len % op->bs_pi_do)) |
| nn += 1; |
| d = nn * op->bs_pi_do; |
| } else |
| d = op->bs_pi_do; /* guess one LB */ |
| /* zero data-out buffer for SL+DATA */ |
| nn = dd + d; |
| up = sg_memalign(nn, 0, &free_up, false); |
| if (NULL == up) { |
| pr2serr("unable to allocate aligned memory for " |
| "scatterlist+data\n"); |
| ret = sg_convert_errno(ENOMEM); |
| goto finii; |
| } |
| ret = bin_read(sfr_fd, up, sf_len, "SF"); |
| if (ret) |
| goto finii; |
| if (! check_lbrds(up, dd, op, &num_lbard, &sum_num)) |
| goto file_err_outt; |
| if (num_lbard != op->scat_num_lbard) { |
| pr2serr("Try again with --scattered=%u\n", num_lbard); |
| goto file_err_outt; |
| } |
| if ((sum_num * op->bs_pi_do) > d) { |
| uint8_t * u2p; |
| uint8_t * free_u2p; |
| |
| d = sum_num * op->bs_pi_do; |
| nn = dd + d; |
| u2p = sg_memalign(nn, 0, &free_u2p, false); |
| if (NULL == u2p) { |
| pr2serr("unable to allocate memory for final " |
| "scatterlist+data\n"); |
| ret = sg_convert_errno(ENOMEM); |
| goto finii; |
| } |
| memcpy(u2p, up, dd); |
| free(free_up); |
| up = u2p; |
| free_up = free_u2p; |
| } |
| if ((if_len != (nn - d)) && (op->strict || vb)) { |
| if_len_gt = (if_len > (nn - d)); |
| pr2serr("IF length (%u) %s 'sum_num' bytes (%u), ", if_len, |
| (if_len_gt ? ">" : "<"), nn - d); |
| if (op->strict > 1) { |
| pr2serr("exiting (strict=%d)\n", op->strict); |
| goto file_err_outt; |
| } else |
| pr2serr("continuing ...\n"); |
| } |
| ret = bin_read(infd, up + d, (if_len_gt ? nn - d : if_len), "IF 4"); |
| if (ret) |
| goto finii; |
| do_len = (num_lbard + sum_num) * op->bs_pi_do; |
| op->numblocks = sum_num; |
| op->xfer_bytes = sum_num * op->bs_pi_do; |
| } else if (addr_arr_len > 0) { /* build RDs for --lba= --num= */ |
| if ((op->scat_num_lbard > 0) && (op->scat_num_lbard > addr_arr_len)) { |
| pr2serr("%s: number given to --scattered= (%u) exceeds number of " |
| "--lba= elements (%u)\n", __func__, op->scat_num_lbard, |
| addr_arr_len); |
| return SG_LIB_CONTRADICT; |
| } |
| d = lbard_sz * (num_lbard + 1); |
| op->scat_lbdof = d / op->bs_pi_do; |
| if (0 != (d % op->bs_pi_do)) /* if not multiple, round up */ |
| op->scat_lbdof += 1; |
| for (sum_num = 0, k = 0; k < (int)addr_arr_len; ++k) |
| sum_num += num_arr[k]; |
| do_len = ((op->scat_lbdof + sum_num) * op->bs_pi_do); |
| up = sg_memalign(do_len, 0, &free_up, false); |
| if (NULL == up) { |
| pr2serr("unable to allocate aligned memory for " |
| "scatterlist+data\n"); |
| ret = sg_convert_errno(ENOMEM); |
| goto finii; |
| } |
| for (n = lbard_sz, k = 0; k < (int)addr_arr_len; ++k, |
| n += lbard_sz) { |
| sg_put_unaligned_be64(addr_arr[k], up + n + 0); |
| sg_put_unaligned_be32(num_arr[k], up + n + 8); |
| if (op->do_32) { |
| if (0 == k) { |
| sg_put_unaligned_be32(op->ref_tag, up + n + 12); |
| sg_put_unaligned_be16(op->app_tag, up + n + 16); |
| sg_put_unaligned_be16(op->tag_mask, up + n + 18); |
| } else { |
| sg_put_unaligned_be32((uint32_t)DEF_RT, up + n + 12); |
| sg_put_unaligned_be16((uint16_t)DEF_AT, up + n + 16); |
| sg_put_unaligned_be16((uint16_t)DEF_TM, up + n + 18); |
| } |
| } |
| } |
| op->numblocks = sum_num; |
| } else { |
| pr2serr("How did we get here??\n"); |
| goto syntax_err_outt; |
| } |
| do_io: |
| ret = do_write_x(sg_fd, up, do_len, op); |
| if (ret) { |
| strcpy(b,"OS error"); |
| if (ret > 0) |
| sg_get_category_sense_str(ret, sizeof(b), b, vb); |
| pr2serr("%s: %s\n", op->cdb_name, b); |
| } |
| goto finii; |
| |
| syntax_err_outt: |
| ret = SG_LIB_SYNTAX_ERROR; |
| goto finii; |
| file_err_outt: |
| ret = SG_LIB_FILE_ERROR; |
| finii: |
| if (free_up) |
| free(free_up); |
| return ret; |
| } |
| |
| |
| int |
| main(int argc, char * argv[]) |
| { |
| bool got_stdin = false; |
| bool got_stat = false; |
| bool if_reg_file = false; |
| int n, err, vb; |
| int infd = -1; |
| int sg_fd = -1; |
| int sfr_fd = -1; |
| int ret = -1; |
| uint32_t nn, addr_arr_len, num_arr_len; /* --lba= */ |
| uint32_t do_len = 0; |
| uint16_t num_lbard = 0; |
| uint32_t if_len = 0; /* after accounting for OFF,DLEN and moving file |
| * file pointer to OFF, is bytes available in IF */ |
| uint32_t sf_len = 0; |
| uint32_t sum_num = 0; |
| ssize_t res; |
| off_t if_readable_len = 0; /* similar to if_len but doesn't take DLEN |
| * into account */ |
| struct opts_t * op; |
| const char * lba_op = NULL; |
| const char * num_op = NULL; |
| uint8_t * up = NULL; |
| uint8_t * free_up = NULL; |
| char ebuff[EBUFF_SZ]; |
| char b[80]; |
| uint64_t addr_arr[MAX_NUM_ADDR]; |
| uint32_t num_arr[MAX_NUM_ADDR]; |
| struct stat if_stat, sf_stat; |
| struct opts_t opts; |
| |
| 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; |
| } |