diff options
Diffstat (limited to 'src/sg_wr_mode.c')
-rw-r--r-- | src/sg_wr_mode.c | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/src/sg_wr_mode.c b/src/sg_wr_mode.c new file mode 100644 index 00000000..b2dff407 --- /dev/null +++ b/src/sg_wr_mode.c @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2004-2021 Douglas Gilbert. + * All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the BSD_LICENSE file. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <getopt.h> +#include <ctype.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + +/* A utility program originally written for the Linux OS SCSI subsystem. + * + * This program writes the given mode page contents to the corresponding + * mode page on the given device. + */ + +static const char * version_str = "1.27 20210610"; + +#define ME "sg_wr_mode: " + +#define MX_ALLOC_LEN 2048 +#define SHORT_ALLOC_LEN 252 + +#define EBUFF_SZ 256 + + +static struct option long_options[] = { + {"contents", required_argument, 0, 'c'}, + {"dbd", no_argument, 0, 'd'}, + {"force", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"len", required_argument, 0, 'l'}, + {"mask", required_argument, 0, 'm'}, + {"page", required_argument, 0, 'p'}, + {"rtd", no_argument, 0, 'R'}, + {"save", no_argument, 0, 's'}, + {"six", no_argument, 0, '6'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + + +static void +usage() +{ + pr2serr("Usage: sg_wr_mode [--contents=H,H...] [--dbd] [--force] " + "[--help]\n" + " [--len=10|6] [--mask=M,M...] " + "[--page=PG_H[,SPG_H]]\n" + " [--rtd] [--save] [--six] [--verbose] " + "[--version]\n" + " DEVICE\n" + " where:\n" + " --contents=H,H... | -c H,H... comma separated string " + "of hex numbers\n" + " that is mode page contents " + "to write\n" + " --contents=- | -c - read stdin for mode page contents" + " to write\n" + " --dbd | -d disable block descriptors (DBD bit" + " in cdb)\n" + " --force | -f force the contents to be written\n" + " --help | -h print out usage message\n" + " --len=10|6 | -l 10|6 use 10 byte (def) or 6 byte " + "variants of\n" + " SCSI MODE SENSE/SELECT commands\n" + " --mask=M,M... | -m M,M... comma separated " + "string of hex\n" + " numbers that mask contents" + " to write\n" + " --page=PG_H | -p PG_H page_code to be written (in hex)\n" + " --page=PG_H,SPG_H | -p PG_H,SPG_H page and subpage code " + "to be\n" + " written (in hex)\n" + " --rtd | -R set RTD bit (revert to defaults) in " + "cdb\n" + " --save | -s set 'save page' (SP) bit; default " + "don't so\n" + " only 'current' values changed\n" + " --six | -6 do SCSI MODE SENSE/SELECT(6) " + "commands\n" + " --verbose | -v increase verbosity\n" + " --version | -V print version string and exit\n\n" + "writes given mode page with SCSI MODE SELECT (10 or 6) " + "command\n"); +} + + +/* Read hex numbers from command line or stdin. On the command line can + * either be comma or space separated list. Space separated list need to be + * quoted. For stdin (indicated by *inp=='-') there should be either + * one entry per line, a comma separated list or space separated list. + * Returns 0 if ok, or sg3_utils error code if error. */ +static int +build_mode_page(const char * inp, uint8_t * mp_arr, int * mp_arr_len, + int max_arr_len) +{ + int in_len, k, j, m; + unsigned int h; + const char * lcp; + char * cp; + char * c2p; + + if ((NULL == inp) || (NULL == mp_arr) || + (NULL == mp_arr_len)) + return SG_LIB_LOGIC_ERROR; + lcp = inp; + in_len = strlen(inp); + if (0 == in_len) + *mp_arr_len = 0; + if ('-' == inp[0]) { /* read from stdin */ + bool split_line; + int off = 0; + char carry_over[4]; + char line[512]; + + carry_over[0] = 0; + for (j = 0; j < 512; ++j) { + if (NULL == fgets(line, sizeof(line), stdin)) + break; + in_len = strlen(line); + if (in_len > 0) { + if ('\n' == line[in_len - 1]) { + --in_len; + line[in_len] = '\0'; + split_line = false; + } else + split_line = true; + } + if (in_len < 1) { + carry_over[0] = 0; + continue; + } + if (carry_over[0]) { + if (isxdigit((uint8_t)line[0])) { + carry_over[1] = line[0]; + carry_over[2] = '\0'; + if (1 == sscanf(carry_over, "%x", &h)) + mp_arr[off - 1] = h; /* back up and overwrite */ + else { + pr2serr("%s: carry_over error ['%s'] around line " + "%d\n", __func__, carry_over, j + 1); + return SG_LIB_SYNTAX_ERROR; + } + lcp = line + 1; + --in_len; + } else + lcp = line; + carry_over[0] = 0; + } else + lcp = line; + m = strspn(lcp, " \t"); + if (m == in_len) + continue; + lcp += m; + in_len -= m; + if ('#' == *lcp) + continue; + k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); + if ((k < in_len) && ('#' != lcp[k])) { + pr2serr("%s: syntax error at line %d, pos %d\n", __func__, + j + 1, m + k + 1); + return SG_LIB_SYNTAX_ERROR; + } + for (k = 0; k < 1024; ++k) { + if (1 == sscanf(lcp, "%x", &h)) { + if (h > 0xff) { + pr2serr("%s: hex number larger than 0xff in line %d, " + "pos %d\n", __func__, j + 1, + (int)(lcp - line + 1)); + return SG_LIB_SYNTAX_ERROR; + } + if (split_line && (1 == strlen(lcp))) { + /* single trailing hex digit might be a split pair */ + carry_over[0] = *lcp; + } + if ((off + k) >= max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + return SG_LIB_SYNTAX_ERROR; + } + mp_arr[off + k] = h; + lcp = strpbrk(lcp, " ,\t"); + if (NULL == lcp) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } else { + if ('#' == *lcp) { + --k; + break; + } + pr2serr("%s: error in line %d, at pos %d\n", __func__, + j + 1, (int)(lcp - line + 1)); + return SG_LIB_SYNTAX_ERROR; + } + } + off += (k + 1); + } + *mp_arr_len = off; + } else { /* hex string on command line */ + k = strspn(inp, "0123456789aAbBcCdDeEfF, "); + if (in_len != k) { + pr2serr("%s: error at pos %d\n", __func__, k + 1); + return SG_LIB_SYNTAX_ERROR; + } + for (k = 0; k < max_arr_len; ++k) { + if (1 == sscanf(lcp, "%x", &h)) { + if (h > 0xff) { + pr2serr("%s: hex number larger than 0xff at pos %d\n", + __func__, (int)(lcp - inp + 1)); + return SG_LIB_SYNTAX_ERROR; + } + mp_arr[k] = h; + cp = (char *)strchr(lcp, ','); + c2p = (char *)strchr(lcp, ' '); + if (NULL == cp) + cp = c2p; + if (NULL == cp) + break; + if (c2p && (c2p < cp)) + cp = c2p; + lcp = cp + 1; + } else { + pr2serr("%s: error at pos %d\n", __func__, + (int)(lcp - inp + 1)); + return SG_LIB_SYNTAX_ERROR; + } + } + *mp_arr_len = k + 1; + if (k == max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + return SG_LIB_SYNTAX_ERROR; + } + } + return 0; +} + +/* Read hex numbers from command line (comma separated list). + * Can also be (single) space separated list but needs to be quoted on the + * command line. Returns 0 if ok, or 1 if error. */ +static int +build_mask(const char * inp, uint8_t * mask_arr, int * mask_arr_len, + int max_arr_len) +{ + int in_len, k; + unsigned int h; + const char * lcp; + char * cp; + char * c2p; + + if ((NULL == inp) || (NULL == mask_arr) || + (NULL == mask_arr_len)) + return 1; + lcp = inp; + in_len = strlen(inp); + if (0 == in_len) + *mask_arr_len = 0; + if ('-' == inp[0]) { /* read from stdin */ + pr2serr("'--mask' does not accept input from stdin\n"); + return 1; + } else { /* hex string on command line */ + k = strspn(inp, "0123456789aAbBcCdDeEfF, "); + if (in_len != k) { + pr2serr("%s: error at pos %d\n", __func__, k + 1); + return 1; + } + for (k = 0; k < max_arr_len; ++k) { + if (1 == sscanf(lcp, "%x", &h)) { + if (h > 0xff) { + pr2serr("%s: hex number larger than 0xff at pos %d\n", + __func__, (int)(lcp - inp + 1)); + return 1; + } + mask_arr[k] = h; + cp = (char *)strchr(lcp, ','); + c2p = (char *)strchr(lcp, ' '); + if (NULL == cp) + cp = c2p; + if (NULL == cp) + break; + if (c2p && (c2p < cp)) + cp = c2p; + lcp = cp + 1; + } else { + pr2serr("%s: error at pos %d\n", __func__, + (int)(lcp - inp + 1)); + return 1; + } + } + *mask_arr_len = k + 1; + if (k == max_arr_len) { + pr2serr("%s: array length exceeded\n", __func__); + return 1; + } + } + return 0; +} + + +int +main(int argc, char * argv[]) +{ + bool dbd = false; + bool force = false; + bool got_contents = false; + bool got_mask = false; + bool mode_6 = false; /* so default is mode_10 */ + bool rtd = false; /* added in spc5r11 */ + bool save = false; + bool verbose_given = false; + bool version_given = false; + int res, c, num, alloc_len, off, pdt, k, md_len, hdr_len, bd_len; + int mask_in_len; + int sg_fd = -1; + int pg_code = -1; + int sub_pg_code = 0; + int verbose = 0; + int read_in_len = 0; + int ret = 0; + unsigned u, uu; + const char * device_name = NULL; + uint8_t read_in[MX_ALLOC_LEN]; + uint8_t mask_in[MX_ALLOC_LEN]; + uint8_t ref_md[MX_ALLOC_LEN]; + char ebuff[EBUFF_SZ]; + char errStr[128]; + char b[80]; + struct sg_simple_inquiry_resp inq_data; + + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "6c:dfhl:m:p:RsvV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case '6': + mode_6 = true; + break; + case 'c': + memset(read_in, 0, sizeof(read_in)); + if ((ret = build_mode_page(optarg, read_in, &read_in_len, + sizeof(read_in)))) { + pr2serr("bad argument to '--contents='\n"); + return ret; + } + got_contents = true; + break; + case 'd': + dbd = true; + break; + case 'f': + force = true; + break; + case 'h': + case '?': + usage(); + return 0; + case 'l': + num = sscanf(optarg, "%d", &res); + if ((1 == num) && ((6 == res) || (10 == res))) + mode_6 = (6 == res); + else { + pr2serr("length (of cdb) must be 6 or 10\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'm': + memset(mask_in, 0xff, sizeof(mask_in)); + if (0 != build_mask(optarg, mask_in, &mask_in_len, + sizeof(mask_in))) { + pr2serr("bad argument to '--mask'\n"); + return SG_LIB_SYNTAX_ERROR; + } + got_mask = true; + break; + case 'p': + if (NULL == strchr(optarg, ',')) { + num = sscanf(optarg, "%x", &u); + if ((1 != num) || (u > 62)) { + pr2serr("Bad hex page code value after '--page' " + "switch\n"); + return SG_LIB_SYNTAX_ERROR; + } + pg_code = u; + } else if (2 == sscanf(optarg, "%x,%x", &u, &uu)) { + if (uu > 254) { + pr2serr("Bad hex sub page code value after '--page' " + "switch\n"); + return SG_LIB_SYNTAX_ERROR; + } + pg_code = u; + sub_pg_code = uu; + } else { + pr2serr("Bad hex page code, subpage code sequence after " + "'--page' switch\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'R': + rtd = true; + break; + case 's': + save = true; + break; + case 'v': + verbose_given = true; + ++verbose; + break; + case 'V': + version_given = true; + break; + default: + pr2serr("unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if (NULL == device_name) { + device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + pr2serr("Unexpected extra argument: %s\n", argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + +#ifdef DEBUG + pr2serr("In DEBUG mode, "); + if (verbose_given && version_given) { + pr2serr("but override: '-vV' given, zero verbose and continue\n"); + verbose_given = false; + version_given = false; + verbose = 0; + } else if (! verbose_given) { + pr2serr("set '-vv'\n"); + verbose = 2; + } else + pr2serr("keep verbose=%d\n", verbose); +#else + if (verbose_given && version_given) + pr2serr("Not in DEBUG mode, so '-vV' has no special action\n"); +#endif + if (version_given) { + pr2serr(ME "version: %s\n", version_str); + return 0; + } + + if (NULL == device_name) { + pr2serr("missing device name!\n\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + if ((pg_code < 0) && (! rtd)) { + pr2serr("need page code (see '--page=')\n\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + if (got_mask && force) { + pr2serr("cannot use both '--force' and '--mask'\n\n"); + usage(); + return SG_LIB_CONTRADICT; + } + + sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose); + if (sg_fd < 0) { + if (verbose) + pr2serr(ME "open error: %s: %s\n", device_name, + safe_strerror(-sg_fd)); + ret = sg_convert_errno(-sg_fd); + goto fini; + } + if (rtd) + goto revert_to_defaults; + + if (0 == sg_simple_inquiry(sg_fd, &inq_data, false, verbose)) + pdt = inq_data.peripheral_type; + else + pdt = PDT_UNKNOWN; + + /* do MODE SENSE to fetch current values */ + memset(ref_md, 0, MX_ALLOC_LEN); + snprintf(errStr, sizeof(errStr), "MODE SENSE (%d): ", mode_6 ? 6 : 10); + alloc_len = mode_6 ? SHORT_ALLOC_LEN : MX_ALLOC_LEN; + if (mode_6) + res = sg_ll_mode_sense6(sg_fd, dbd, 0 /*current */, pg_code, + sub_pg_code, ref_md, alloc_len, true, + verbose); + else + res = sg_ll_mode_sense10(sg_fd, false /* llbaa */, dbd, + 0 /* current */, pg_code, sub_pg_code, + ref_md, alloc_len, true, verbose); + ret = res; + if (res) { + if (SG_LIB_CAT_INVALID_OP == res) + pr2serr("%snot supported, try '--len=%d'\n", errStr, + (mode_6 ? 10 : 6)); + else { + sg_get_category_sense_str(res, sizeof(b), b, verbose); + pr2serr("%s%s\n", errStr, b); + } + goto fini; + } + off = sg_mode_page_offset(ref_md, alloc_len, mode_6, ebuff, EBUFF_SZ); + if (off < 0) { + pr2serr("%s%s\n", errStr, ebuff); + goto fini; + } + md_len = sg_msense_calc_length(ref_md, alloc_len, mode_6, &bd_len); + if (md_len < 0) { + pr2serr("%ssg_msense_calc_length() failed\n", errStr); + goto fini; + } + hdr_len = mode_6 ? 4 : 8; + if (got_contents) { + if (read_in_len < 2) { + pr2serr("contents length=%d too short\n", read_in_len); + goto fini; + } + ref_md[0] = 0; /* mode data length reserved for mode select */ + if (! mode_6) + ref_md[1] = 0; /* mode data length reserved for mode select */ + if (0 == pdt) /* for disks mask out DPOFUA bit */ + ref_md[mode_6 ? 2 : 3] &= 0xef; + if (md_len > alloc_len) { + pr2serr("mode data length=%d exceeds allocation length=%d\n", + md_len, alloc_len); + goto fini; + } + if (got_mask) { + for (k = 0; k < (md_len - off); ++k) { + if ((0x0 == mask_in[k]) || (k > read_in_len)) + read_in[k] = ref_md[off + k]; + else if (mask_in[k] < 0xff) { + c = (ref_md[off + k] & (0xff & ~mask_in[k])); + read_in[k] = (c | (read_in[k] & mask_in[k])); + } + } + read_in_len = md_len - off; + } + if (! force) { + if ((! (ref_md[off] & 0x80)) && save) { + pr2serr("PS bit in existing mode page indicates that it is " + "not saveable\n but '--save' option given\n"); + goto fini; + } + read_in[0] &= 0x7f; /* mask out PS bit, reserved in mode select */ + if ((md_len - off) != read_in_len) { + pr2serr("contents length=%d but reference mode page " + "length=%d\n", read_in_len, md_len - off); + goto fini; + } + if (pg_code != (read_in[0] & 0x3f)) { + pr2serr("contents page_code=0x%x but reference " + "page_code=0x%x\n", (read_in[0] & 0x3f), pg_code); + goto fini; + } + if ((read_in[0] & 0x40) != (ref_md[off] & 0x40)) { + pr2serr("contents flags subpage but reference page does not " + "(or vice versa)\n"); + goto fini; + } + if ((read_in[0] & 0x40) && (read_in[1] != sub_pg_code)) { + pr2serr("contents subpage_code=0x%x but reference " + "sub_page_code=0x%x\n", read_in[1], sub_pg_code); + goto fini; + } + } else + md_len = off + read_in_len; /* force length */ + + memcpy(ref_md + off, read_in, read_in_len); + if (mode_6) + res = sg_ll_mode_select6_v2(sg_fd, true /* PF */, rtd, save, + ref_md, md_len, true, verbose); + else + res = sg_ll_mode_select10_v2(sg_fd, true /* PF */, rtd, save, + ref_md, md_len, true, verbose); + ret = res; + if (res) + goto fini; + } else { + printf(">>> No contents given, so show current mode page data:\n"); + printf(" header:\n"); + hex2stdout(ref_md, hdr_len, -1); + if (bd_len) { + printf(" block descriptor(s):\n"); + hex2stdout(ref_md + hdr_len, bd_len, -1); + } else + printf(" << no block descriptors >>\n"); + printf(" mode page:\n"); + hex2stdout(ref_md + off, md_len - off, -1); + } + ret = 0; + goto fini; + +revert_to_defaults: + if (verbose) + pr2serr("Doing MODE SELECT(%d) with revert to defaults (RTD) set " + "and SP=%d\n", mode_6 ? 6 : 10, !! save); + if (mode_6) + res = sg_ll_mode_select6_v2(sg_fd, false /* PF */, true /* rtd */, + save, NULL, 0, true, verbose); + else + res = sg_ll_mode_select10_v2(sg_fd, false /* PF */, true /* rtd */, + save, NULL, 0, true, verbose); + ret = res; +fini: + if (sg_fd >= 0) { + res = sg_cmds_close_device(sg_fd); + if (res < 0) { + pr2serr("close error: %s\n", safe_strerror(-res)); + if (0 == ret) + ret = sg_convert_errno(-res); + } + } + if (0 == verbose) { + if (! sg_if_can2stderr("sg_wr_mode failed: ", ret)) + pr2serr("Some error occurred, try again with '-v' or '-vv' for " + "more information\n"); + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} |