xref: /aosp_15_r20/external/sg3_utils/src/sg_wr_mode.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
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