/* * 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 #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "sg_lib.h" #include "sg_cmds_basic.h" #include "sg_unaligned.h" #include "sg_pr2serr.h" /* A utility program originally written for the Linux OS SCSI subsystem. * * This program writes the given mode page contents to the corresponding * mode page on the given device. */ static const char * version_str = "1.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; }