xref: /aosp_15_r20/external/sg3_utils/src/sg_write_buffer.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2006-2021 Luben Tuikov and 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 <stdint.h>
16 #include <stdbool.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <string.h>
20 #include <getopt.h>
21 #define __STDC_FORMAT_MACROS 1
22 #include <inttypes.h>
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 #include "sg_lib.h"
28 #include "sg_cmds_basic.h"
29 #include "sg_cmds_extra.h"
30 #include "sg_unaligned.h"
31 #include "sg_pr2serr.h"
32 
33 #ifdef SG_LIB_WIN32
34 #ifdef SG_LIB_WIN32_DIRECT
35 #include "sg_pt.h"      /* needed for scsi_pt_win32_direct() */
36 #endif
37 #endif
38 
39 /*
40  * This utility issues the SCSI WRITE BUFFER command to the given device.
41  */
42 
43 static const char * version_str = "1.30 20210610";    /* spc6r05 */
44 
45 #define ME "sg_write_buffer: "
46 #define DEF_XFER_LEN (8 * 1024 * 1024)
47 #define EBUFF_SZ 256
48 
49 #define WRITE_BUFFER_CMD 0x3b
50 #define WRITE_BUFFER_CMDLEN 10
51 #define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
52 #define DEF_PT_TIMEOUT 300      /* 300 seconds, 5 minutes */
53 
54 static struct option long_options[] = {
55         {"bpw", required_argument, 0, 'b'},
56         {"dry-run", no_argument, 0, 'd'},
57         {"dry_run", no_argument, 0, 'd'},
58         {"help", no_argument, 0, 'h'},
59         {"id", required_argument, 0, 'i'},
60         {"in", required_argument, 0, 'I'},
61         {"length", required_argument, 0, 'l'},
62         {"mode", required_argument, 0, 'm'},
63         {"offset", required_argument, 0, 'o'},
64         {"read-stdin", no_argument, 0, 'r'},
65         {"read_stdin", no_argument, 0, 'r'},
66         {"raw", no_argument, 0, 'r'},
67         {"skip", required_argument, 0, 's'},
68         {"specific", required_argument, 0, 'S'},
69         {"timeout", required_argument, 0, 't' },
70         {"verbose", no_argument, 0, 'v'},
71         {"version", no_argument, 0, 'V'},
72         {0, 0, 0, 0},
73 };
74 
75 
76 static void
usage()77 usage()
78 {
79     pr2serr("Usage: "
80             "sg_write_buffer [--bpw=CS] [--dry-run] [--help] [--id=ID] "
81             "[--in=FILE]\n"
82             "                       [--length=LEN] [--mode=MO] "
83             "[--offset=OFF]\n"
84             "                       [--read-stdin] [--skip=SKIP] "
85             "[--specific=MS]\n"
86             "                       [--timeout=TO] [--verbose] [--version] "
87             "DEVICE\n"
88             "  where:\n"
89             "    --bpw=CS|-b CS         CS is chunk size: bytes per write "
90             "buffer\n"
91             "                           command (def: 0 -> as many as "
92             "possible)\n"
93             "    --dry-run|-d           skip WRITE BUFFER commands, do "
94             "everything else\n"
95             "    --help|-h              print out usage message then exit\n"
96             "    --id=ID|-i ID          buffer identifier (0 (default) to "
97             "255)\n"
98             "    --in=FILE|-I FILE      read from FILE ('-I -' read "
99             "from stdin)\n"
100             "    --length=LEN|-l LEN    length in bytes to write; may be "
101             "deduced from\n"
102             "                           FILE\n"
103             "    --mode=MO|-m MO        write buffer mode, MO is number or "
104             "acronym\n"
105             "                           (def: 0 -> 'combined header and "
106             "data' (obs))\n"
107             "    --offset=OFF|-o OFF    buffer offset (unit: bytes, def: 0)\n"
108             "    --read-stdin|-r        read from stdin (same as '-I -')\n"
109             "    --skip=SKIP|-s SKIP    bytes in file FILE to skip before "
110             "reading\n"
111             "    --specific=MS|-S MS    mode specific value; 3 bit field "
112             "(0 to 7)\n"
113             "    --timeout=TO|-t TO     command timeout in seconds (def: "
114             "300)\n"
115             "    --verbose|-v           increase verbosity\n"
116             "    --version|-V           print version string and exit\n\n"
117             "Performs one or more SCSI WRITE BUFFER commands. Use '-m xxx' "
118             "to list\navailable modes. A chunk size of 4 KB ('--bpw=4k') "
119             "seems to work well.\nExample: sg_write_buffer -b 4k -I xxx.lod "
120             "-m 7 /dev/sg3\n"
121           );
122 
123 }
124 
125 #define MODE_HEADER_DATA        0
126 #define MODE_VENDOR             1
127 #define MODE_DATA               2
128 #define MODE_DNLD_MC            4
129 #define MODE_DNLD_MC_SAVE       5
130 #define MODE_DNLD_MC_OFFS       6
131 #define MODE_DNLD_MC_OFFS_SAVE  7
132 #define MODE_ECHO_BUFFER        0x0A
133 #define MODE_DNLD_MC_EV_OFFS_DEFER 0x0D
134 #define MODE_DNLD_MC_OFFS_DEFER 0x0E
135 #define MODE_ACTIVATE_MC        0x0F
136 #define MODE_EN_EX_ECHO         0x1A
137 #define MODE_DIS_EX             0x1B
138 #define MODE_DNLD_ERR_HISTORY   0x1C
139 
140 
141 struct mode_s {
142         const char *mode_string;
143         int   mode;
144         const char *comment;
145 };
146 
147 static struct mode_s mode_arr[] = {
148         {"hd",         MODE_HEADER_DATA, "combined header and data "
149                 "(obsolete)"},
150         {"vendor",     MODE_VENDOR,    "vendor specific"},
151         {"data",       MODE_DATA,      "data"},
152         {"dmc",        MODE_DNLD_MC,   "download microcode and activate"},
153         {"dmc_save",   MODE_DNLD_MC_SAVE, "download microcode, save and "
154                 "activate"},
155         {"dmc_offs",   MODE_DNLD_MC_OFFS, "download microcode with offsets "
156                 "and activate"},
157         {"dmc_offs_save", MODE_DNLD_MC_OFFS_SAVE, "download microcode with "
158                 "offsets, save and\n\t\t\t\tactivate"},
159         {"echo",       MODE_ECHO_BUFFER, "write data to echo buffer"},
160         {"dmc_offs_ev_defer", MODE_DNLD_MC_EV_OFFS_DEFER, "download "
161                 "microcode with offsets, select\n\t\t\t\tactivation event, "
162                 "save and defer activation"},
163         {"dmc_offs_defer", MODE_DNLD_MC_OFFS_DEFER, "download microcode "
164                 "with offsets, save and\n\t\t\t\tdefer activation"},
165         {"activate_mc", MODE_ACTIVATE_MC, "activate deferred microcode"},
166         {"en_ex",      MODE_EN_EX_ECHO, "enable expander communications "
167                 "protocol and\n\t\t\t\techo buffer (obsolete)"},
168         {"dis_ex",     MODE_DIS_EX, "disable expander communications "
169                 "protocol\n\t\t\t\t(obsolete)"},
170         {"deh",        MODE_DNLD_ERR_HISTORY, "download application client "
171                 "error history "},
172         {NULL, 0, NULL},
173 };
174 
175 static void
print_modes(void)176 print_modes(void)
177 {
178     const struct mode_s * mp;
179 
180     pr2serr("The modes parameter argument can be numeric (hex or decimal)\n"
181             "or symbolic:\n");
182     for (mp = mode_arr; mp->mode_string; ++mp) {
183         pr2serr(" %2d (0x%02x)  %-18s%s\n", mp->mode, mp->mode,
184                 mp->mode_string, mp->comment);
185     }
186     pr2serr("\nAdditionally '--bpw=<val>,act' does a activate deferred "
187             "microcode after\nsuccessful dmc_offs_defer and "
188             "dmc_offs_ev_defer mode downloads.\n");
189 }
190 
191 
192 int
main(int argc,char * argv[])193 main(int argc, char * argv[])
194 {
195     bool bpw_then_activate = false;
196     bool dry_run = false;
197     bool got_stdin = false;
198     bool verbose_given = false;
199     bool version_given = false;
200     bool wb_len_given = false;
201     int infd, res, c, len, k, n;
202     int sg_fd = -1;
203     int bpw = 0;
204     int do_help = 0;
205     int ret = 0;
206     int verbose = 0;
207     int wb_id = 0;
208     int wb_len = 0;
209     int wb_mode = 0;
210     int wb_offset = 0;
211     int wb_skip = 0;
212     int wb_timeout = DEF_PT_TIMEOUT;
213     int wb_mspec = 0;
214     const char * device_name = NULL;
215     const char * file_name = NULL;
216     uint8_t * dop = NULL;
217     uint8_t * read_buf = NULL;
218     uint8_t * free_dop = NULL;
219     char * cp;
220     const struct mode_s * mp;
221     char ebuff[EBUFF_SZ];
222 
223     while (1) {
224         int option_index = 0;
225 
226         c = getopt_long(argc, argv, "b:dhi:I:l:m:o:rs:S:t:vV", long_options,
227                         &option_index);
228         if (c == -1)
229             break;
230 
231         switch (c) {
232         case 'b':
233             bpw = sg_get_num(optarg);
234             if (bpw < 0) {
235                 pr2serr("argument to '--bpw' should be in a positive "
236                         "number\n");
237                 return SG_LIB_SYNTAX_ERROR;
238             }
239             if ((cp = strchr(optarg, ','))) {
240                 if (0 == strncmp("act", cp + 1, 3))
241                     bpw_then_activate = true;
242             }
243             break;
244         case 'd':
245             dry_run = true;
246             break;
247         case 'h':
248         case '?':
249             ++do_help;
250             break;
251         case 'i':
252             wb_id = sg_get_num(optarg);
253             if ((wb_id < 0) || (wb_id > 255)) {
254                 pr2serr("argument to '--id' should be in the range 0 to "
255                         "255\n");
256                 return SG_LIB_SYNTAX_ERROR;
257             }
258             break;
259         case 'I':
260             file_name = optarg;
261             break;
262         case 'l':
263             wb_len = sg_get_num(optarg);
264             if (wb_len < 0) {
265                 pr2serr("bad argument to '--length'\n");
266                 return SG_LIB_SYNTAX_ERROR;
267              }
268              wb_len_given = true;
269              break;
270         case 'm':
271             if (isdigit((uint8_t)*optarg)) {
272                 wb_mode = sg_get_num(optarg);
273                 if ((wb_mode < 0) || (wb_mode > 31)) {
274                     pr2serr("argument to '--mode' should be in the range 0 "
275                             "to 31\n");
276                     return SG_LIB_SYNTAX_ERROR;
277                 }
278             } else {
279                 len = strlen(optarg);
280                 for (mp = mode_arr; mp->mode_string; ++mp) {
281                     if (0 == strncmp(mp->mode_string, optarg, len)) {
282                         wb_mode = mp->mode;
283                         break;
284                     }
285                 }
286                 if (! mp->mode_string) {
287                     print_modes();
288                     return SG_LIB_SYNTAX_ERROR;
289                 }
290             }
291             break;
292         case 'o':
293            wb_offset = sg_get_num(optarg);
294            if (wb_offset < 0) {
295                 pr2serr("bad argument to '--offset'\n");
296                 return SG_LIB_SYNTAX_ERROR;
297             }
298             break;
299         case 'r':       /* --read-stdin and --raw (previous name) */
300             file_name = "-";
301             break;
302         case 's':
303            wb_skip = sg_get_num(optarg);
304            if (wb_skip < 0) {
305                 pr2serr("bad argument to '--skip'\n");
306                 return SG_LIB_SYNTAX_ERROR;
307             }
308             break;
309         case 'S':
310             wb_mspec = sg_get_num(optarg);
311             if ((wb_mspec < 0) || (wb_mspec > 7)) {
312                 pr2serr("expected argument to '--specific' to be 0 to 7\n");
313                 return SG_LIB_SYNTAX_ERROR;
314             }
315             break;
316         case 't':
317             wb_timeout = sg_get_num(optarg);
318             if (wb_timeout < 0) {
319                 pr2serr("Invalid argument to '--timeout'\n");
320                 return SG_LIB_SYNTAX_ERROR;
321             }
322             break;
323         case 'v':
324             verbose_given = true;
325             ++verbose;
326             break;
327         case 'V':
328             version_given = true;
329             break;
330         default:
331             pr2serr("unrecognised option code 0x%x ??\n", c);
332             usage();
333             return SG_LIB_SYNTAX_ERROR;
334         }
335     }
336     if (do_help) {
337         if (do_help > 1) {
338             usage();
339             pr2serr("\n");
340             print_modes();
341         } else
342             usage();
343         return 0;
344     }
345     if (optind < argc) {
346         if (NULL == device_name) {
347             device_name = argv[optind];
348             ++optind;
349         }
350         if (optind < argc) {
351             for (; optind < argc; ++optind)
352                 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
353             usage();
354             return SG_LIB_SYNTAX_ERROR;
355         }
356     }
357 
358 #ifdef DEBUG
359     pr2serr("In DEBUG mode, ");
360     if (verbose_given && version_given) {
361         pr2serr("but override: '-vV' given, zero verbose and continue\n");
362         verbose_given = false;
363         version_given = false;
364         verbose = 0;
365     } else if (! verbose_given) {
366         pr2serr("set '-vv'\n");
367         verbose = 2;
368     } else
369         pr2serr("keep verbose=%d\n", verbose);
370 #else
371     if (verbose_given && version_given)
372         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
373 #endif
374     if (version_given) {
375         pr2serr("version: %s\n", version_str);
376         return 0;
377     }
378 
379     if (NULL == device_name) {
380         pr2serr("Missing device name!\n\n");
381         usage();
382         return SG_LIB_SYNTAX_ERROR;
383     }
384 
385     if ((wb_len > 0) && (bpw > wb_len)) {
386         pr2serr("trim chunk size (CS) to be the same as LEN\n");
387         bpw = wb_len;
388     }
389 
390 #ifdef SG_LIB_WIN32
391 #ifdef SG_LIB_WIN32_DIRECT
392     if (verbose > 4)
393         pr2serr("Initial win32 SPT interface state: %s\n",
394                 scsi_pt_win32_spt_state() ? "direct" : "indirect");
395     scsi_pt_win32_direct(SG_LIB_WIN32_DIRECT /* SPT pt interface */);
396 #endif
397 #endif
398 
399     sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
400     if (sg_fd < 0) {
401         if (verbose)
402             pr2serr(ME "open error: %s: %s\n", device_name,
403                     safe_strerror(-sg_fd));
404         ret = sg_convert_errno(-sg_fd);
405         goto err_out;
406     }
407     if (file_name || (wb_len > 0)) {
408         if (0 == wb_len)
409             wb_len = DEF_XFER_LEN;
410         dop = sg_memalign(wb_len, 0, &free_dop, false);
411         if (NULL == dop) {
412             pr2serr(ME "out of memory\n");
413             ret = sg_convert_errno(ENOMEM);
414             goto err_out;
415         }
416         memset(dop, 0xff, wb_len);
417         if (file_name) {
418             got_stdin = (0 == strcmp(file_name, "-"));
419             if (got_stdin) {
420                 if (wb_skip > 0) {
421                     pr2serr("Can't skip on stdin\n");
422                     ret = SG_LIB_FILE_ERROR;
423                     goto err_out;
424                 }
425                 infd = STDIN_FILENO;
426             } else {
427                 if ((infd = open(file_name, O_RDONLY)) < 0) {
428                     ret = sg_convert_errno(errno);
429                     snprintf(ebuff, EBUFF_SZ,
430                              ME "could not open %s for reading", file_name);
431                     perror(ebuff);
432                     goto err_out;
433                 } else if (sg_set_binary_mode(infd) < 0)
434                     perror("sg_set_binary_mode");
435                 if (wb_skip > 0) {
436                     if (lseek(infd, wb_skip, SEEK_SET) < 0) {
437                         ret = sg_convert_errno(errno);
438                         snprintf(ebuff,  EBUFF_SZ, ME "couldn't skip to "
439                                  "required position on %s", file_name);
440                         perror(ebuff);
441                         close(infd);
442                         goto err_out;
443                     }
444                 }
445             }
446             if (infd == STDIN_FILENO) {
447                 if (NULL == (read_buf = (uint8_t *)malloc(DEF_XFER_LEN))) {
448                     pr2serr(ME "out of memory\n");
449                     ret = SG_LIB_SYNTAX_ERROR;
450                     goto err_out;
451                 }
452                 res = read(infd, read_buf, DEF_XFER_LEN);
453                 if (res < 0) {
454                     snprintf(ebuff, EBUFF_SZ, ME "couldn't read from STDIN");
455                     perror(ebuff);
456                     ret = SG_LIB_FILE_ERROR;
457                     goto err_out;
458                 }
459                 char * pch;
460                 int val = 0;
461                 res = 0;
462                 pch = strtok((char*)read_buf, ",. \n\t");
463                 while (pch != NULL) {
464                     val = sg_get_num_nomult(pch);
465                     if (val >= 0 && val < 255) {
466                         dop[res] = val;
467                         res++;
468                     } else {
469                         pr2serr("Data read from STDIO is wrong.\nPlease "
470                                 "input the data a byte at a time, the bytes "
471                                 "should be separated\nby either space, or "
472                                 "',' ( or by '.'), and the value per byte "
473                                 "should\nbe between 0~255. Hexadecimal "
474                                 "numbers should be preceded by either '0x' "
475                                 "or\n'OX' (or have a trailing 'h' or "
476                                 "'H').\n");
477                         ret = SG_LIB_SYNTAX_ERROR;
478                         goto err_out;
479                     }
480                     pch = strtok(NULL, ",. \n\t");
481                 }
482             } else {
483                 res = read(infd, dop, wb_len);
484                 if (res < 0) {
485                     ret = sg_convert_errno(errno);
486                     snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
487                              file_name);
488                     perror(ebuff);
489                     if (! got_stdin)
490                         close(infd);
491                     goto err_out;
492                 }
493             }
494             if (res < wb_len) {
495                 if (wb_len_given) {
496                     pr2serr("tried to read %d bytes from %s, got %d bytes\n",
497                             wb_len, file_name, res);
498                     pr2serr("pad with 0xff bytes and continue\n");
499                 } else {
500                     if (verbose) {
501                         pr2serr("tried to read %d bytes from %s, got %d "
502                                 "bytes\n", wb_len, file_name, res);
503                         pr2serr("will write %d bytes", res);
504                         if ((bpw > 0) && (bpw < wb_len))
505                             pr2serr(", %d bytes per WRITE BUFFER command\n",
506                                     bpw);
507                         else
508                             pr2serr("\n");
509                     }
510                     wb_len = res;
511                 }
512             }
513             if (! got_stdin)
514                 close(infd);
515         }
516     }
517 
518     res = 0;
519     if (bpw > 0) {
520         for (k = 0; k < wb_len; k += n) {
521             n = wb_len - k;
522             if (n > bpw)
523                 n = bpw;
524             if (verbose)
525                 pr2serr("sending write buffer, mode=0x%x, mspec=%d, id=%d, "
526                         " offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
527                         wb_offset + k, n);
528             if (dry_run) {
529                 if (verbose)
530                     pr2serr("skipping WRITE BUFFER command due to "
531                             "--dry-run\n");
532                 res = 0;
533             } else
534                 res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
535                                             wb_offset + k, dop + k, n,
536                                             wb_timeout, true, verbose);
537             if (res)
538                 break;
539         }
540         if (bpw_then_activate) {
541             if (verbose)
542                 pr2serr("sending Activate deferred microcode [0xf]\n");
543             if (dry_run) {
544                 if (verbose)
545                     pr2serr("skipping WRITE BUFFER(ACTIVATE) command due to "
546                             "--dry-run\n");
547                 res = 0;
548             } else
549                 res = sg_ll_write_buffer_v2(sg_fd, MODE_ACTIVATE_MC,
550                                             0 /* buffer_id */,
551                                             0 /* buffer_offset */, 0,
552                                             NULL, 0, wb_timeout, true,
553                                             verbose);
554         }
555     } else {
556         if (verbose)
557             pr2serr("sending single write buffer, mode=0x%x, mpsec=%d, "
558                     "id=%d, offset=%d, len=%d\n", wb_mode, wb_mspec, wb_id,
559                     wb_offset, wb_len);
560         if (dry_run) {
561             if (verbose)
562                 pr2serr("skipping WRITE BUFFER(all in one) command due to "
563                         "--dry-run\n");
564             res = 0;
565         } else
566             res = sg_ll_write_buffer_v2(sg_fd, wb_mode, wb_mspec, wb_id,
567                                         wb_offset, dop, wb_len, wb_timeout,
568                                         true, verbose);
569     }
570     if (0 != res) {
571         char b[80];
572 
573         ret = res;
574         sg_get_category_sense_str(res, sizeof(b), b, verbose);
575         pr2serr("Write buffer failed: %s\n", b);
576     }
577 
578 err_out:
579     if (free_dop)
580         free(free_dop);
581     if (read_buf)
582         free(read_buf);
583     if (sg_fd >= 0) {
584         res = sg_cmds_close_device(sg_fd);
585         if (res < 0) {
586             pr2serr("close error: %s\n", safe_strerror(-res));
587             if (0 == ret)
588                 ret = sg_convert_errno(-res);
589         }
590     }
591     if (0 == verbose) {
592         if (! sg_if_can2stderr("sg_write_buffer failed: ", ret))
593             pr2serr("Some error occurred, try again with '-v' "
594                     "or '-vv' for more information\n");
595     }
596     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
597 }
598