1 /*
2 * Copyright (c) 2004-2021 Douglas Gilbert.
3 * All rights reserved.
4 * Use of this source code is governed by a BSD-style
5 * license that can be found in the BSD_LICENSE file.
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <string.h>
17 #include <getopt.h>
18 #include <ctype.h>
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "sg_lib.h"
25 #include "sg_cmds_basic.h"
26 #include "sg_unaligned.h"
27 #include "sg_pr2serr.h"
28
29 /* A utility program originally written for the Linux OS SCSI subsystem.
30 *
31 * This program writes the given mode page contents to the corresponding
32 * mode page on the given device.
33 */
34
35 static const char * version_str = "1.27 20210610";
36
37 #define ME "sg_wr_mode: "
38
39 #define MX_ALLOC_LEN 2048
40 #define SHORT_ALLOC_LEN 252
41
42 #define EBUFF_SZ 256
43
44
45 static struct option long_options[] = {
46 {"contents", required_argument, 0, 'c'},
47 {"dbd", no_argument, 0, 'd'},
48 {"force", no_argument, 0, 'f'},
49 {"help", no_argument, 0, 'h'},
50 {"len", required_argument, 0, 'l'},
51 {"mask", required_argument, 0, 'm'},
52 {"page", required_argument, 0, 'p'},
53 {"rtd", no_argument, 0, 'R'},
54 {"save", no_argument, 0, 's'},
55 {"six", no_argument, 0, '6'},
56 {"verbose", no_argument, 0, 'v'},
57 {"version", no_argument, 0, 'V'},
58 {0, 0, 0, 0},
59 };
60
61
62 static void
usage()63 usage()
64 {
65 pr2serr("Usage: sg_wr_mode [--contents=H,H...] [--dbd] [--force] "
66 "[--help]\n"
67 " [--len=10|6] [--mask=M,M...] "
68 "[--page=PG_H[,SPG_H]]\n"
69 " [--rtd] [--save] [--six] [--verbose] "
70 "[--version]\n"
71 " DEVICE\n"
72 " where:\n"
73 " --contents=H,H... | -c H,H... comma separated string "
74 "of hex numbers\n"
75 " that is mode page contents "
76 "to write\n"
77 " --contents=- | -c - read stdin for mode page contents"
78 " to write\n"
79 " --dbd | -d disable block descriptors (DBD bit"
80 " in cdb)\n"
81 " --force | -f force the contents to be written\n"
82 " --help | -h print out usage message\n"
83 " --len=10|6 | -l 10|6 use 10 byte (def) or 6 byte "
84 "variants of\n"
85 " SCSI MODE SENSE/SELECT commands\n"
86 " --mask=M,M... | -m M,M... comma separated "
87 "string of hex\n"
88 " numbers that mask contents"
89 " to write\n"
90 " --page=PG_H | -p PG_H page_code to be written (in hex)\n"
91 " --page=PG_H,SPG_H | -p PG_H,SPG_H page and subpage code "
92 "to be\n"
93 " written (in hex)\n"
94 " --rtd | -R set RTD bit (revert to defaults) in "
95 "cdb\n"
96 " --save | -s set 'save page' (SP) bit; default "
97 "don't so\n"
98 " only 'current' values changed\n"
99 " --six | -6 do SCSI MODE SENSE/SELECT(6) "
100 "commands\n"
101 " --verbose | -v increase verbosity\n"
102 " --version | -V print version string and exit\n\n"
103 "writes given mode page with SCSI MODE SELECT (10 or 6) "
104 "command\n");
105 }
106
107
108 /* Read hex numbers from command line or stdin. On the command line can
109 * either be comma or space separated list. Space separated list need to be
110 * quoted. For stdin (indicated by *inp=='-') there should be either
111 * one entry per line, a comma separated list or space separated list.
112 * Returns 0 if ok, or sg3_utils error code if error. */
113 static int
build_mode_page(const char * inp,uint8_t * mp_arr,int * mp_arr_len,int max_arr_len)114 build_mode_page(const char * inp, uint8_t * mp_arr, int * mp_arr_len,
115 int max_arr_len)
116 {
117 int in_len, k, j, m;
118 unsigned int h;
119 const char * lcp;
120 char * cp;
121 char * c2p;
122
123 if ((NULL == inp) || (NULL == mp_arr) ||
124 (NULL == mp_arr_len))
125 return SG_LIB_LOGIC_ERROR;
126 lcp = inp;
127 in_len = strlen(inp);
128 if (0 == in_len)
129 *mp_arr_len = 0;
130 if ('-' == inp[0]) { /* read from stdin */
131 bool split_line;
132 int off = 0;
133 char carry_over[4];
134 char line[512];
135
136 carry_over[0] = 0;
137 for (j = 0; j < 512; ++j) {
138 if (NULL == fgets(line, sizeof(line), stdin))
139 break;
140 in_len = strlen(line);
141 if (in_len > 0) {
142 if ('\n' == line[in_len - 1]) {
143 --in_len;
144 line[in_len] = '\0';
145 split_line = false;
146 } else
147 split_line = true;
148 }
149 if (in_len < 1) {
150 carry_over[0] = 0;
151 continue;
152 }
153 if (carry_over[0]) {
154 if (isxdigit((uint8_t)line[0])) {
155 carry_over[1] = line[0];
156 carry_over[2] = '\0';
157 if (1 == sscanf(carry_over, "%x", &h))
158 mp_arr[off - 1] = h; /* back up and overwrite */
159 else {
160 pr2serr("%s: carry_over error ['%s'] around line "
161 "%d\n", __func__, carry_over, j + 1);
162 return SG_LIB_SYNTAX_ERROR;
163 }
164 lcp = line + 1;
165 --in_len;
166 } else
167 lcp = line;
168 carry_over[0] = 0;
169 } else
170 lcp = line;
171 m = strspn(lcp, " \t");
172 if (m == in_len)
173 continue;
174 lcp += m;
175 in_len -= m;
176 if ('#' == *lcp)
177 continue;
178 k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t");
179 if ((k < in_len) && ('#' != lcp[k])) {
180 pr2serr("%s: syntax error at line %d, pos %d\n", __func__,
181 j + 1, m + k + 1);
182 return SG_LIB_SYNTAX_ERROR;
183 }
184 for (k = 0; k < 1024; ++k) {
185 if (1 == sscanf(lcp, "%x", &h)) {
186 if (h > 0xff) {
187 pr2serr("%s: hex number larger than 0xff in line %d, "
188 "pos %d\n", __func__, j + 1,
189 (int)(lcp - line + 1));
190 return SG_LIB_SYNTAX_ERROR;
191 }
192 if (split_line && (1 == strlen(lcp))) {
193 /* single trailing hex digit might be a split pair */
194 carry_over[0] = *lcp;
195 }
196 if ((off + k) >= max_arr_len) {
197 pr2serr("%s: array length exceeded\n", __func__);
198 return SG_LIB_SYNTAX_ERROR;
199 }
200 mp_arr[off + k] = h;
201 lcp = strpbrk(lcp, " ,\t");
202 if (NULL == lcp)
203 break;
204 lcp += strspn(lcp, " ,\t");
205 if ('\0' == *lcp)
206 break;
207 } else {
208 if ('#' == *lcp) {
209 --k;
210 break;
211 }
212 pr2serr("%s: error in line %d, at pos %d\n", __func__,
213 j + 1, (int)(lcp - line + 1));
214 return SG_LIB_SYNTAX_ERROR;
215 }
216 }
217 off += (k + 1);
218 }
219 *mp_arr_len = off;
220 } else { /* hex string on command line */
221 k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
222 if (in_len != k) {
223 pr2serr("%s: error at pos %d\n", __func__, k + 1);
224 return SG_LIB_SYNTAX_ERROR;
225 }
226 for (k = 0; k < max_arr_len; ++k) {
227 if (1 == sscanf(lcp, "%x", &h)) {
228 if (h > 0xff) {
229 pr2serr("%s: hex number larger than 0xff at pos %d\n",
230 __func__, (int)(lcp - inp + 1));
231 return SG_LIB_SYNTAX_ERROR;
232 }
233 mp_arr[k] = h;
234 cp = (char *)strchr(lcp, ',');
235 c2p = (char *)strchr(lcp, ' ');
236 if (NULL == cp)
237 cp = c2p;
238 if (NULL == cp)
239 break;
240 if (c2p && (c2p < cp))
241 cp = c2p;
242 lcp = cp + 1;
243 } else {
244 pr2serr("%s: error at pos %d\n", __func__,
245 (int)(lcp - inp + 1));
246 return SG_LIB_SYNTAX_ERROR;
247 }
248 }
249 *mp_arr_len = k + 1;
250 if (k == max_arr_len) {
251 pr2serr("%s: array length exceeded\n", __func__);
252 return SG_LIB_SYNTAX_ERROR;
253 }
254 }
255 return 0;
256 }
257
258 /* Read hex numbers from command line (comma separated list).
259 * Can also be (single) space separated list but needs to be quoted on the
260 * command line. Returns 0 if ok, or 1 if error. */
261 static int
build_mask(const char * inp,uint8_t * mask_arr,int * mask_arr_len,int max_arr_len)262 build_mask(const char * inp, uint8_t * mask_arr, int * mask_arr_len,
263 int max_arr_len)
264 {
265 int in_len, k;
266 unsigned int h;
267 const char * lcp;
268 char * cp;
269 char * c2p;
270
271 if ((NULL == inp) || (NULL == mask_arr) ||
272 (NULL == mask_arr_len))
273 return 1;
274 lcp = inp;
275 in_len = strlen(inp);
276 if (0 == in_len)
277 *mask_arr_len = 0;
278 if ('-' == inp[0]) { /* read from stdin */
279 pr2serr("'--mask' does not accept input from stdin\n");
280 return 1;
281 } else { /* hex string on command line */
282 k = strspn(inp, "0123456789aAbBcCdDeEfF, ");
283 if (in_len != k) {
284 pr2serr("%s: error at pos %d\n", __func__, k + 1);
285 return 1;
286 }
287 for (k = 0; k < max_arr_len; ++k) {
288 if (1 == sscanf(lcp, "%x", &h)) {
289 if (h > 0xff) {
290 pr2serr("%s: hex number larger than 0xff at pos %d\n",
291 __func__, (int)(lcp - inp + 1));
292 return 1;
293 }
294 mask_arr[k] = h;
295 cp = (char *)strchr(lcp, ',');
296 c2p = (char *)strchr(lcp, ' ');
297 if (NULL == cp)
298 cp = c2p;
299 if (NULL == cp)
300 break;
301 if (c2p && (c2p < cp))
302 cp = c2p;
303 lcp = cp + 1;
304 } else {
305 pr2serr("%s: error at pos %d\n", __func__,
306 (int)(lcp - inp + 1));
307 return 1;
308 }
309 }
310 *mask_arr_len = k + 1;
311 if (k == max_arr_len) {
312 pr2serr("%s: array length exceeded\n", __func__);
313 return 1;
314 }
315 }
316 return 0;
317 }
318
319
320 int
main(int argc,char * argv[])321 main(int argc, char * argv[])
322 {
323 bool dbd = false;
324 bool force = false;
325 bool got_contents = false;
326 bool got_mask = false;
327 bool mode_6 = false; /* so default is mode_10 */
328 bool rtd = false; /* added in spc5r11 */
329 bool save = false;
330 bool verbose_given = false;
331 bool version_given = false;
332 int res, c, num, alloc_len, off, pdt, k, md_len, hdr_len, bd_len;
333 int mask_in_len;
334 int sg_fd = -1;
335 int pg_code = -1;
336 int sub_pg_code = 0;
337 int verbose = 0;
338 int read_in_len = 0;
339 int ret = 0;
340 unsigned u, uu;
341 const char * device_name = NULL;
342 uint8_t read_in[MX_ALLOC_LEN];
343 uint8_t mask_in[MX_ALLOC_LEN];
344 uint8_t ref_md[MX_ALLOC_LEN];
345 char ebuff[EBUFF_SZ];
346 char errStr[128];
347 char b[80];
348 struct sg_simple_inquiry_resp inq_data;
349
350 while (1) {
351 int option_index = 0;
352
353 c = getopt_long(argc, argv, "6c:dfhl:m:p:RsvV", long_options,
354 &option_index);
355 if (c == -1)
356 break;
357
358 switch (c) {
359 case '6':
360 mode_6 = true;
361 break;
362 case 'c':
363 memset(read_in, 0, sizeof(read_in));
364 if ((ret = build_mode_page(optarg, read_in, &read_in_len,
365 sizeof(read_in)))) {
366 pr2serr("bad argument to '--contents='\n");
367 return ret;
368 }
369 got_contents = true;
370 break;
371 case 'd':
372 dbd = true;
373 break;
374 case 'f':
375 force = true;
376 break;
377 case 'h':
378 case '?':
379 usage();
380 return 0;
381 case 'l':
382 num = sscanf(optarg, "%d", &res);
383 if ((1 == num) && ((6 == res) || (10 == res)))
384 mode_6 = (6 == res);
385 else {
386 pr2serr("length (of cdb) must be 6 or 10\n");
387 return SG_LIB_SYNTAX_ERROR;
388 }
389 break;
390 case 'm':
391 memset(mask_in, 0xff, sizeof(mask_in));
392 if (0 != build_mask(optarg, mask_in, &mask_in_len,
393 sizeof(mask_in))) {
394 pr2serr("bad argument to '--mask'\n");
395 return SG_LIB_SYNTAX_ERROR;
396 }
397 got_mask = true;
398 break;
399 case 'p':
400 if (NULL == strchr(optarg, ',')) {
401 num = sscanf(optarg, "%x", &u);
402 if ((1 != num) || (u > 62)) {
403 pr2serr("Bad hex page code value after '--page' "
404 "switch\n");
405 return SG_LIB_SYNTAX_ERROR;
406 }
407 pg_code = u;
408 } else if (2 == sscanf(optarg, "%x,%x", &u, &uu)) {
409 if (uu > 254) {
410 pr2serr("Bad hex sub page code value after '--page' "
411 "switch\n");
412 return SG_LIB_SYNTAX_ERROR;
413 }
414 pg_code = u;
415 sub_pg_code = uu;
416 } else {
417 pr2serr("Bad hex page code, subpage code sequence after "
418 "'--page' switch\n");
419 return SG_LIB_SYNTAX_ERROR;
420 }
421 break;
422 case 'R':
423 rtd = true;
424 break;
425 case 's':
426 save = true;
427 break;
428 case 'v':
429 verbose_given = true;
430 ++verbose;
431 break;
432 case 'V':
433 version_given = true;
434 break;
435 default:
436 pr2serr("unrecognised option code 0x%x ??\n", c);
437 usage();
438 return SG_LIB_SYNTAX_ERROR;
439 }
440 }
441 if (optind < argc) {
442 if (NULL == device_name) {
443 device_name = argv[optind];
444 ++optind;
445 }
446 if (optind < argc) {
447 for (; optind < argc; ++optind)
448 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
449 usage();
450 return SG_LIB_SYNTAX_ERROR;
451 }
452 }
453
454 #ifdef DEBUG
455 pr2serr("In DEBUG mode, ");
456 if (verbose_given && version_given) {
457 pr2serr("but override: '-vV' given, zero verbose and continue\n");
458 verbose_given = false;
459 version_given = false;
460 verbose = 0;
461 } else if (! verbose_given) {
462 pr2serr("set '-vv'\n");
463 verbose = 2;
464 } else
465 pr2serr("keep verbose=%d\n", verbose);
466 #else
467 if (verbose_given && version_given)
468 pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
469 #endif
470 if (version_given) {
471 pr2serr(ME "version: %s\n", version_str);
472 return 0;
473 }
474
475 if (NULL == device_name) {
476 pr2serr("missing device name!\n\n");
477 usage();
478 return SG_LIB_SYNTAX_ERROR;
479 }
480 if ((pg_code < 0) && (! rtd)) {
481 pr2serr("need page code (see '--page=')\n\n");
482 usage();
483 return SG_LIB_SYNTAX_ERROR;
484 }
485 if (got_mask && force) {
486 pr2serr("cannot use both '--force' and '--mask'\n\n");
487 usage();
488 return SG_LIB_CONTRADICT;
489 }
490
491 sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
492 if (sg_fd < 0) {
493 if (verbose)
494 pr2serr(ME "open error: %s: %s\n", device_name,
495 safe_strerror(-sg_fd));
496 ret = sg_convert_errno(-sg_fd);
497 goto fini;
498 }
499 if (rtd)
500 goto revert_to_defaults;
501
502 if (0 == sg_simple_inquiry(sg_fd, &inq_data, false, verbose))
503 pdt = inq_data.peripheral_type;
504 else
505 pdt = PDT_UNKNOWN;
506
507 /* do MODE SENSE to fetch current values */
508 memset(ref_md, 0, MX_ALLOC_LEN);
509 snprintf(errStr, sizeof(errStr), "MODE SENSE (%d): ", mode_6 ? 6 : 10);
510 alloc_len = mode_6 ? SHORT_ALLOC_LEN : MX_ALLOC_LEN;
511 if (mode_6)
512 res = sg_ll_mode_sense6(sg_fd, dbd, 0 /*current */, pg_code,
513 sub_pg_code, ref_md, alloc_len, true,
514 verbose);
515 else
516 res = sg_ll_mode_sense10(sg_fd, false /* llbaa */, dbd,
517 0 /* current */, pg_code, sub_pg_code,
518 ref_md, alloc_len, true, verbose);
519 ret = res;
520 if (res) {
521 if (SG_LIB_CAT_INVALID_OP == res)
522 pr2serr("%snot supported, try '--len=%d'\n", errStr,
523 (mode_6 ? 10 : 6));
524 else {
525 sg_get_category_sense_str(res, sizeof(b), b, verbose);
526 pr2serr("%s%s\n", errStr, b);
527 }
528 goto fini;
529 }
530 off = sg_mode_page_offset(ref_md, alloc_len, mode_6, ebuff, EBUFF_SZ);
531 if (off < 0) {
532 pr2serr("%s%s\n", errStr, ebuff);
533 goto fini;
534 }
535 md_len = sg_msense_calc_length(ref_md, alloc_len, mode_6, &bd_len);
536 if (md_len < 0) {
537 pr2serr("%ssg_msense_calc_length() failed\n", errStr);
538 goto fini;
539 }
540 hdr_len = mode_6 ? 4 : 8;
541 if (got_contents) {
542 if (read_in_len < 2) {
543 pr2serr("contents length=%d too short\n", read_in_len);
544 goto fini;
545 }
546 ref_md[0] = 0; /* mode data length reserved for mode select */
547 if (! mode_6)
548 ref_md[1] = 0; /* mode data length reserved for mode select */
549 if (0 == pdt) /* for disks mask out DPOFUA bit */
550 ref_md[mode_6 ? 2 : 3] &= 0xef;
551 if (md_len > alloc_len) {
552 pr2serr("mode data length=%d exceeds allocation length=%d\n",
553 md_len, alloc_len);
554 goto fini;
555 }
556 if (got_mask) {
557 for (k = 0; k < (md_len - off); ++k) {
558 if ((0x0 == mask_in[k]) || (k > read_in_len))
559 read_in[k] = ref_md[off + k];
560 else if (mask_in[k] < 0xff) {
561 c = (ref_md[off + k] & (0xff & ~mask_in[k]));
562 read_in[k] = (c | (read_in[k] & mask_in[k]));
563 }
564 }
565 read_in_len = md_len - off;
566 }
567 if (! force) {
568 if ((! (ref_md[off] & 0x80)) && save) {
569 pr2serr("PS bit in existing mode page indicates that it is "
570 "not saveable\n but '--save' option given\n");
571 goto fini;
572 }
573 read_in[0] &= 0x7f; /* mask out PS bit, reserved in mode select */
574 if ((md_len - off) != read_in_len) {
575 pr2serr("contents length=%d but reference mode page "
576 "length=%d\n", read_in_len, md_len - off);
577 goto fini;
578 }
579 if (pg_code != (read_in[0] & 0x3f)) {
580 pr2serr("contents page_code=0x%x but reference "
581 "page_code=0x%x\n", (read_in[0] & 0x3f), pg_code);
582 goto fini;
583 }
584 if ((read_in[0] & 0x40) != (ref_md[off] & 0x40)) {
585 pr2serr("contents flags subpage but reference page does not "
586 "(or vice versa)\n");
587 goto fini;
588 }
589 if ((read_in[0] & 0x40) && (read_in[1] != sub_pg_code)) {
590 pr2serr("contents subpage_code=0x%x but reference "
591 "sub_page_code=0x%x\n", read_in[1], sub_pg_code);
592 goto fini;
593 }
594 } else
595 md_len = off + read_in_len; /* force length */
596
597 memcpy(ref_md + off, read_in, read_in_len);
598 if (mode_6)
599 res = sg_ll_mode_select6_v2(sg_fd, true /* PF */, rtd, save,
600 ref_md, md_len, true, verbose);
601 else
602 res = sg_ll_mode_select10_v2(sg_fd, true /* PF */, rtd, save,
603 ref_md, md_len, true, verbose);
604 ret = res;
605 if (res)
606 goto fini;
607 } else {
608 printf(">>> No contents given, so show current mode page data:\n");
609 printf(" header:\n");
610 hex2stdout(ref_md, hdr_len, -1);
611 if (bd_len) {
612 printf(" block descriptor(s):\n");
613 hex2stdout(ref_md + hdr_len, bd_len, -1);
614 } else
615 printf(" << no block descriptors >>\n");
616 printf(" mode page:\n");
617 hex2stdout(ref_md + off, md_len - off, -1);
618 }
619 ret = 0;
620 goto fini;
621
622 revert_to_defaults:
623 if (verbose)
624 pr2serr("Doing MODE SELECT(%d) with revert to defaults (RTD) set "
625 "and SP=%d\n", mode_6 ? 6 : 10, !! save);
626 if (mode_6)
627 res = sg_ll_mode_select6_v2(sg_fd, false /* PF */, true /* rtd */,
628 save, NULL, 0, true, verbose);
629 else
630 res = sg_ll_mode_select10_v2(sg_fd, false /* PF */, true /* rtd */,
631 save, NULL, 0, true, verbose);
632 ret = res;
633 fini:
634 if (sg_fd >= 0) {
635 res = sg_cmds_close_device(sg_fd);
636 if (res < 0) {
637 pr2serr("close error: %s\n", safe_strerror(-res));
638 if (0 == ret)
639 ret = sg_convert_errno(-res);
640 }
641 }
642 if (0 == verbose) {
643 if (! sg_if_can2stderr("sg_wr_mode failed: ", ret))
644 pr2serr("Some error occurred, try again with '-v' or '-vv' for "
645 "more information\n");
646 }
647 return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
648 }
649