blob: 04766485ffae920229da1198868952409520622d [file] [log] [blame]
/*
* Copyright (c) 2004-2016 Douglas Gilbert.
* All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the BSD_LICENSE file.
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.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.16 20160208";
#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", 1, 0, 'c'},
{"dbd", 0, 0, 'd'},
{"force", 0, 0, 'f'},
{"help", 0, 0, 'h'},
{"len", 1, 0, 'l'},
{"mask", 1, 0, 'm'},
{"page", 1, 0, 'p'},
{"save", 0, 0, 's'},
{"verbose", 0, 0, 'v'},
{"version", 0, 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"
" [--save] [--verbose] [--version] 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"
" --save | -s set 'save page' (SP) bit; default "
"don't so\n"
" only 'current' values changed\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 1 if error. */
static int build_mode_page(const char * inp, unsigned char * mp_arr,
int * mp_arr_len, int max_arr_len)
{
int in_len, k, j, m;
unsigned int h;
const char * lcp;
char * cp;
char * c2p;
if ((NULL == inp) || (NULL == mp_arr) ||
(NULL == mp_arr_len))
return 1;
lcp = inp;
in_len = strlen(inp);
if (0 == in_len)
*mp_arr_len = 0;
if ('-' == inp[0]) { /* read from stdin */
char line[512];
char carry_over[4];
int off = 0;
int split_line;
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 = 0;
} else
split_line = 1;
}
if (in_len < 1) {
carry_over[0] = 0;
continue;
}
if (carry_over[0]) {
if (isxdigit(line[0])) {
carry_over[1] = line[0];
carry_over[2] = '\0';
if (1 == sscanf(carry_over, "%x", &h))
mp_arr[off - 1] = h; /* back up and overwrite */
else {
pr2serr("build_mode_page: carry_over error ['%s'] "
"around line %d\n", carry_over, j + 1);
return 1;
}
lcp = line + 1;
--in_len;
} else
lcp = line;
carry_over[0] = 0;
} else
lcp = line;
m = strspn(lcp, " \t");
if (m == in_len)
continue;
lcp += m;
in_len -= m;
if ('#' == *lcp)
continue;
k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
if ((k < in_len) && ('#' != lcp[k])) {
pr2serr("build_mode_page: syntax error at "
"line %d, pos %d\n", j + 1, m + k + 1);
return 1;
}
for (k = 0; k < 1024; ++k) {
if (1 == sscanf(lcp, "%x", &h)) {
if (h > 0xff) {
pr2serr("%s: hex number larger than 0xff in line %d, "
"pos %d\n", __func__, j + 1,
(int)(lcp - line + 1));
return 1;
}
if (split_line && (1 == strlen(lcp))) {
/* single trailing hex digit might be a split pair */
carry_over[0] = *lcp;
}
if ((off + k) >= max_arr_len) {
pr2serr("%s: array length exceeded\n", __func__);
return 1;
}
mp_arr[off + k] = h;
lcp = strpbrk(lcp, " ,\t");
if (NULL == lcp)
break;
lcp += strspn(lcp, " ,\t");
if ('\0' == *lcp)
break;
} else {
if ('#' == *lcp) {
--k;
break;
}
pr2serr("%s: error in line %d, at pos %d\n", __func__,
j + 1, (int)(lcp - line + 1));
return 1;
}
}
off += (k + 1);
}
*mp_arr_len = off;
} else { /* hex string on command line */
k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
if (in_len != k) {
pr2serr("%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;
}
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 1;
}
}
*mp_arr_len = k + 1;
if (k == max_arr_len) {
pr2serr("%s: array length exceeded\n", __func__);
return 1;
}
}
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, unsigned char * 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[])
{
int sg_fd, res, c, num, alloc_len, off, pdt;
int k, md_len, hdr_len, bd_len, mask_in_len;
unsigned u, uu;
int dbd = 0;
int got_contents = 0;
int force = 0;
int got_mask = 0;
int mode_6 = 0;
int pg_code = -1;
int sub_pg_code = 0;
int save = 0;
int verbose = 0;
int read_in_len = 0;
const char * device_name = NULL;
unsigned char read_in[MX_ALLOC_LEN];
unsigned char mask_in[MX_ALLOC_LEN];
unsigned char ref_md[MX_ALLOC_LEN];
char ebuff[EBUFF_SZ];
char b[80];
struct sg_simple_inquiry_resp inq_data;
int ret = 0;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "c:dfhl:m:p:svV", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'c':
memset(read_in, 0, sizeof(read_in));
if (0 != build_mode_page(optarg, read_in, &read_in_len,
sizeof(read_in))) {
pr2serr("bad argument to '--contents'\n");
return SG_LIB_SYNTAX_ERROR;
}
got_contents = 1;
break;
case 'd':
dbd = 1;
break;
case 'f':
force = 1;
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) ? 1 : 0;
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 = 1;
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 's':
save = 1;
break;
case 'v':
++verbose;
break;
case 'V':
pr2serr(ME "version: %s\n", version_str);
return 0;
default:
pr2serr("unrecognised option code 0x%x ??\n", c);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (optind < argc) {
if (NULL == device_name) {
device_name = argv[optind];
++optind;
}
if (optind < argc) {
for (; optind < argc; ++optind)
pr2serr("Unexpected extra argument: %s\n", argv[optind]);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (NULL == device_name) {
pr2serr("missing device name!\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if (pg_code < 0) {
pr2serr("need page code (see '--page=')\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if (got_mask && force) {
pr2serr("cannot use both '--force' and '--mask'\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
sg_fd = sg_cmds_open_device(device_name, 0 /* rw */, verbose);
if (sg_fd < 0) {
pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
return SG_LIB_FILE_ERROR;
}
if (0 == sg_simple_inquiry(sg_fd, &inq_data, 0, verbose))
pdt = inq_data.peripheral_type;
else
pdt = 0x1f;
/* do MODE SENSE to fetch current values */
memset(ref_md, 0, MX_ALLOC_LEN);
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, 1, verbose);
else
res = sg_ll_mode_sense10(sg_fd, 0 /* llbaa */, dbd, 0 /* current */,
pg_code, sub_pg_code, ref_md, alloc_len, 1,
verbose);
ret = res;
if (res) {
if (SG_LIB_CAT_INVALID_OP == res)
pr2serr("MODE SENSE (%d) not supported, try '--len=%d'\n",
(mode_6 ? 6 : 10), (mode_6 ? 10 : 6));
else {
sg_get_category_sense_str(res, sizeof(b), b, verbose);
pr2serr("MODE SENSE (%d): %s\n", (mode_6 ? 6 : 10), b);
}
goto err_out;
}
off = sg_mode_page_offset(ref_md, alloc_len, mode_6, ebuff, EBUFF_SZ);
if (off < 0) {
pr2serr("MODE SENSE (%d): %s\n", (mode_6 ? 6 : 10), ebuff);
goto err_out;
}
if (mode_6) {
hdr_len = 4;
md_len = ref_md[0] + 1;
bd_len = ref_md[3];
} else {
hdr_len = 8;
md_len = sg_get_unaligned_be16(ref_md + 0) + 2;
bd_len = sg_get_unaligned_be16(ref_md + 6);
}
if (got_contents) {
if (read_in_len < 2) {
pr2serr("contents length=%d too short\n", read_in_len);
goto err_out;
}
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 err_out;
}
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 err_out;
}
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 err_out;
}
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 err_out;
}
if ((read_in[0] & 0x40) != (ref_md[off] & 0x40)) {
pr2serr("contents flags subpage but reference page does not "
"(or vice versa)\n");
goto err_out;
}
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 err_out;
}
} 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(sg_fd, 1 /* PF */, save, ref_md, md_len,
1, verbose);
else
res = sg_ll_mode_select10(sg_fd, 1 /* PF */, save, ref_md,
md_len, 1, verbose);
ret = res;
if (res) {
sg_get_category_sense_str(res, sizeof(b), b, verbose);
pr2serr("MODE SELECT (%d): %s\n", (mode_6 ? 6 : 10), b);
goto err_out;
}
} else {
printf(">>> No contents given, so show current mode page data:\n");
printf(" header:\n");
dStrHex((const char *)ref_md, hdr_len, -1);
if (bd_len) {
printf(" block descriptor(s):\n");
dStrHex((const char *)(ref_md + hdr_len), bd_len, -1);
} else
printf(" << no block descriptors >>\n");
printf(" mode page:\n");
dStrHex((const char *)(ref_md + off), md_len - off, -1);
}
err_out:
res = sg_cmds_close_device(sg_fd);
if (res < 0) {
pr2serr("close error: %s\n", safe_strerror(-res));
if (0 == ret)
return SG_LIB_FILE_ERROR;
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}