blob: 9ff3436e07d3373d0b78fee4e06cc61751cfaf38 [file] [log] [blame]
/*
* Copyright (c) 2004-2007 Douglas Gilbert.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "sg_lib.h"
#include "sg_cmds_basic.h"
/* A utility program for the Linux OS SCSI subsystem.
*
* This program writes the given mode page contents to the corresponding
* mode page on the given device.
*/
static char * version_str = "1.08 20070419";
#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()
{
fprintf(stderr, "Usage: "
"sg_wr_mode [--contents=H,H...] [--dbd] [--force] [--help]\n"
" [--len=10|6] [--mask=M,M...] "
"[--page=PG[,SPG]] [--save]\n"
" [--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 | -p PG page_code to be written (in hex)\n"
" --page=PG,SPG | -p PG,SPG 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 (comma separated list) or from */
/* stdin (one per line, comma separated list or space separated list). */
/* Returns 0 if ok, or 1 if error. */
static int build_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;
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];
int off = 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';
}
}
if (0 == in_len)
continue;
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])) {
fprintf(stderr, "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) {
fprintf(stderr, "build_mode_page: hex number "
"larger than 0xff in line %d, pos %d\n",
j + 1, (int)(lcp - line + 1));
return 1;
}
if ((off + k) >= max_arr_len) {
fprintf(stderr, "build_mode_page: array length "
"exceeded\n");
return 1;
}
mp_arr[off + k] = h;
lcp = strpbrk(lcp, " ,\t");
if (NULL == lcp)
break;
lcp += strspn(lcp, " ,\t");
if ('\0' == *lcp)
break;
} else {
if ('#' == *lcp) {
--k;
break;
}
fprintf(stderr, "build_mode_page: error in "
"line %d, at pos %d\n", j + 1,
(int)(lcp - line + 1));
return 1;
}
}
off += (k + 1);
}
*mp_arr_len = off;
} else { /* hex string on command line */
k = strspn(inp, "0123456789aAbBcCdDeEfF,");
if (in_len != k) {
fprintf(stderr, "build_mode_page: error at pos %d\n", k + 1);
return 1;
}
for (k = 0; k < max_arr_len; ++k) {
if (1 == sscanf(lcp, "%x", &h)) {
if (h > 0xff) {
fprintf(stderr, "build_mode_page: hex number larger "
"than 0xff at pos %d\n", (int)(lcp - inp + 1));
return 1;
}
mp_arr[k] = h;
cp = strchr(lcp, ',');
if (NULL == cp)
break;
lcp = cp + 1;
} else {
fprintf(stderr, "build_mode_page: error at pos %d\n",
(int)(lcp - inp + 1));
return 1;
}
}
*mp_arr_len = k + 1;
if (k == max_arr_len) {
fprintf(stderr, "build_mode_page: array length exceeded\n");
return 1;
}
}
return 0;
}
/* Read hex numbers from command line (comma separated list). */
/* 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;
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 */
fprintf(stderr, "'--mask' does not accept input from stdin\n");
return 1;
} else { /* hex string on command line */
k = strspn(inp, "0123456789aAbBcCdDeEfF,");
if (in_len != k) {
fprintf(stderr, "build_mode_page: error at pos %d\n", k + 1);
return 1;
}
for (k = 0; k < max_arr_len; ++k) {
if (1 == sscanf(lcp, "%x", &h)) {
if (h > 0xff) {
fprintf(stderr, "build_mode_page: hex number larger "
"than 0xff at pos %d\n", (int)(lcp - inp + 1));
return 1;
}
mask_arr[k] = h;
cp = strchr(lcp, ',');
if (NULL == cp)
break;
lcp = cp + 1;
} else {
fprintf(stderr, "build_mode_page: error at pos %d\n",
(int)(lcp - inp + 1));
return 1;
}
}
*mask_arr_len = k + 1;
if (k == max_arr_len) {
fprintf(stderr, "build_mode_page: array length exceeded\n");
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;
char device_name[256];
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];
struct sg_simple_inquiry_resp inq_data;
int ret = 0;
memset(device_name, 0, sizeof device_name);
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))) {
fprintf(stderr, "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 {
fprintf(stderr, "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))) {
fprintf(stderr, "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)) {
fprintf(stderr, "Bad 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) {
fprintf(stderr, "Bad sub page code value after '--page'"
" switch\n");
return SG_LIB_SYNTAX_ERROR;
}
pg_code = u;
sub_pg_code = uu;
} else {
fprintf(stderr, "Bad 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':
fprintf(stderr, ME "version: %s\n", version_str);
return 0;
default:
fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (optind < argc) {
if ('\0' == device_name[0]) {
strncpy(device_name, argv[optind], sizeof(device_name) - 1);
device_name[sizeof(device_name) - 1] = '\0';
++optind;
}
if (optind < argc) {
for (; optind < argc; ++optind)
fprintf(stderr, "Unexpected extra argument: %s\n",
argv[optind]);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (0 == device_name[0]) {
fprintf(stderr, "missing device name!\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if (pg_code < 0) {
fprintf(stderr, "need page code (see '--page=')\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
if (got_mask && force) {
fprintf(stderr, "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) {
fprintf(stderr, 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 (SG_LIB_CAT_INVALID_OP == res) {
fprintf(stderr, "MODE SENSE (%d) not supported, try '--len=%d'\n",
(mode_6 ? 6 : 10), (mode_6 ? 10 : 6));
goto err_out;
} else if (SG_LIB_CAT_NOT_READY == res) {
fprintf(stderr, "MODE SENSE (%d) failed, device not ready\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_UNIT_ATTENTION == res) {
fprintf(stderr, "MODE SENSE (%d) failed, unit attention\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
fprintf(stderr, "MODE SENSE (%d) failed, aborted command\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_ILLEGAL_REQ == res) {
fprintf(stderr, "bad field in MODE SENSE (%d) command\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (0 != res) {
fprintf(stderr, "MODE SENSE (%d) failed\n", (mode_6 ? 6 : 10));
goto err_out;
}
off = sg_mode_page_offset(ref_md, alloc_len, mode_6, ebuff, EBUFF_SZ);
if (off < 0) {
fprintf(stderr, "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 = (ref_md[0] << 8) + ref_md[1] + 2;
bd_len = (ref_md[6] << 8) + ref_md[7];
}
if (got_contents) {
if (read_in_len < 2) {
fprintf(stderr, "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) {
fprintf(stderr, "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) {
fprintf(stderr, "PS bit in existing mode page indicates that "
"it is not savable\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) {
fprintf(stderr, "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)) {
fprintf(stderr, "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)) {
fprintf(stderr, "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)) {
fprintf(stderr, "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, save, ref_md, md_len, 1,
verbose);
else
res = sg_ll_mode_select10(sg_fd, 1, save, ref_md, md_len, 1,
verbose);
ret = res;
if (SG_LIB_CAT_INVALID_OP == res) {
fprintf(stderr, "MODE SELECT (%d) not supported\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_NOT_READY == res) {
fprintf(stderr, "MODE SELECT (%d) failed, device not ready\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_UNIT_ATTENTION == res) {
fprintf(stderr, "MODE SELECT (%d) failed, unit attention\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
fprintf(stderr, "MODE SELECT (%d) failed, aborted command\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (SG_LIB_CAT_ILLEGAL_REQ == res) {
fprintf(stderr, "bad field in MODE SELECT (%d) command\n",
(mode_6 ? 6 : 10));
goto err_out;
} else if (0 != res) {
fprintf(stderr, "MODE SELECT (%d) failed\n", (mode_6 ? 6 : 10));
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) {
fprintf(stderr, "close error: %s\n", safe_strerror(-res));
if (0 == ret)
return SG_LIB_FILE_ERROR;
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}