1 /* A utility program originally written for the Linux OS SCSI subsystem.
2 * Copyright (C) 1999-2022 D. Gilbert
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2, or (at your option)
6 * any later version.
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 *
10 * This program uses the SCSI command READ BUFFER on the given
11 * device, first to find out how big it is and then to read that
12 * buffer (data mode, buffer id 0).
13 */
14
15
16 #define _XOPEN_SOURCE 600
17 #ifndef _GNU_SOURCE
18 #define _GNU_SOURCE 1
19 #endif
20
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <stdbool.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <getopt.h>
30 #define __STDC_FORMAT_MACROS 1
31 #include <inttypes.h>
32 #include <sys/ioctl.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/mman.h>
36 #include <sys/time.h>
37
38 #ifdef HAVE_CONFIG_H
39 #include "config.h"
40 #endif
41 #include "sg_lib.h"
42 #include "sg_io_linux.h"
43 #include "sg_unaligned.h"
44 #include "sg_pr2serr.h"
45
46 #define RB_MODE_DESC 3
47 #define RB_MODE_DATA 2
48 #define RB_MODE_ECHO_DESC 0xb
49 #define RB_MODE_ECHO_DATA 0xa
50 #define RB_DESC_LEN 4
51 #define RB_DEF_SIZE (200*1024*1024)
52 #define RB_OPCODE 0x3C
53 #define RB_CMD_LEN 10
54
55 #ifndef SG_FLAG_MMAP_IO
56 #define SG_FLAG_MMAP_IO 4
57 #endif
58
59
60 static const char * version_str = "5.09 20220425";
61
62 static struct option long_options[] = {
63 {"buffer", required_argument, 0, 'b'},
64 {"dio", no_argument, 0, 'd'},
65 {"echo", no_argument, 0, 'e'},
66 {"help", no_argument, 0, 'h'},
67 {"mmap", no_argument, 0, 'm'},
68 {"new", no_argument, 0, 'N'},
69 {"old", no_argument, 0, 'O'},
70 {"quick", no_argument, 0, 'q'},
71 {"size", required_argument, 0, 's'},
72 {"time", no_argument, 0, 't'},
73 {"verbose", no_argument, 0, 'v'},
74 {"version", no_argument, 0, 'V'},
75 {0, 0, 0, 0},
76 };
77
78 struct opts_t {
79 bool do_dio;
80 bool do_echo;
81 bool do_mmap;
82 bool do_quick;
83 bool do_time;
84 bool verbose_given;
85 bool version_given;
86 bool opt_new;
87 int do_buffer;
88 int do_help;
89 int verbose;
90 int64_t do_size;
91 const char * device_name;
92 };
93
94
95 static void
usage()96 usage()
97 {
98 pr2serr("Usage: sg_rbuf [--buffer=EACH] [--dio] [--echo] "
99 "[--help] [--mmap]\n"
100 " [--quick] [--size=OVERALL] [--time] [--verbose] "
101 "[--version]\n"
102 " SG_DEVICE\n");
103 pr2serr(" where:\n"
104 " --buffer=EACH|-b EACH buffer size to use (in bytes)\n"
105 " --dio|-d requests dio ('-q' overrides it)\n"
106 " --echo|-e use echo buffer (def: use data mode)\n"
107 " --help|-h print usage message then exit\n"
108 " --mmap|-m requests mmap-ed IO (overrides -q, -d)\n"
109 " --quick|-q quick, don't xfer to user space\n");
110 pr2serr(" --size=OVERALL|-s OVERALL total size to read (in bytes)\n"
111 " default: 200 MiB\n"
112 " --time|-t time the data transfer\n"
113 " --verbose|-v increase verbosity (more debug)\n"
114 " --old|-O use old interface (use as first option)\n"
115 " --version|-V print version string then exit\n\n"
116 "Use SCSI READ BUFFER command (data or echo buffer mode, buffer "
117 "id 0)\nrepeatedly. This utility only works with Linux sg "
118 "devices.\n");
119 }
120
121 static void
usage_old()122 usage_old()
123 {
124 printf("Usage: sg_rbuf [-b=EACH_KIB] [-d] [-m] [-q] [-s=OVERALL_MIB] "
125 "[-t] [-v] [-V]\n SG_DEVICE\n");
126 printf(" where:\n");
127 printf(" -b=EACH_KIB num is buffer size to use (in KiB)\n");
128 printf(" -d requests dio ('-q' overrides it)\n");
129 printf(" -e use echo buffer (def: use data mode)\n");
130 printf(" -m requests mmap-ed IO (overrides -q, -d)\n");
131 printf(" -q quick, don't xfer to user space\n");
132 printf(" -s=OVERALL_MIB num is total size to read (in MiB) "
133 "(default: 200 MiB)\n");
134 printf(" maximum total size is 4000 MiB\n");
135 printf(" -t time the data transfer\n");
136 printf(" -v increase verbosity (more debug)\n");
137 printf(" -N|--new use new interface\n");
138 printf(" -V print version string then exit\n\n");
139 printf("Use SCSI READ BUFFER command (data or echo buffer mode, buffer "
140 "id 0)\nrepeatedly. This utility only works with Linux sg "
141 "devices.\n");
142 }
143
144 static void
usage_for(const struct opts_t * op)145 usage_for(const struct opts_t * op)
146 {
147 if (op->opt_new)
148 usage();
149 else
150 usage_old();
151 }
152
153 static int
new_parse_cmd_line(struct opts_t * op,int argc,char * argv[])154 new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
155 {
156 int c, n;
157 int64_t nn;
158
159 while (1) {
160 int option_index = 0;
161
162 c = getopt_long(argc, argv, "b:dehmNOqs:tvV", long_options,
163 &option_index);
164 if (c == -1)
165 break;
166
167 switch (c) {
168 case 'b':
169 n = sg_get_num(optarg);
170 if (n < 0) {
171 pr2serr("bad argument to '--buffer'\n");
172 usage_for(op);
173 return SG_LIB_SYNTAX_ERROR;
174 }
175 op->do_buffer = n;
176 break;
177 case 'd':
178 op->do_dio = true;
179 break;
180 case 'e':
181 op->do_echo = true;
182 break;
183 case 'h':
184 case '?':
185 ++op->do_help;
186 break;
187 case 'm':
188 op->do_mmap = true;
189 break;
190 case 'N':
191 break; /* ignore */
192 case 'O':
193 op->opt_new = false;
194 return 0;
195 case 'q':
196 op->do_quick = true;
197 break;
198 case 's':
199 nn = sg_get_llnum(optarg);
200 if (nn < 0) {
201 pr2serr("bad argument to '--size'\n");
202 usage_for(op);
203 return SG_LIB_SYNTAX_ERROR;
204 }
205 op->do_size = nn;
206 break;
207 case 't':
208 op->do_time = true;
209 break;
210 case 'v':
211 op->verbose_given = true;
212 ++op->verbose;
213 break;
214 case 'V':
215 op->version_given = true;
216 break;
217 default:
218 pr2serr("unrecognised option code %c [0x%x]\n", c, c);
219 if (op->do_help)
220 break;
221 usage_for(op);
222 return SG_LIB_SYNTAX_ERROR;
223 }
224 }
225 if (optind < argc) {
226 if (NULL == op->device_name) {
227 op->device_name = argv[optind];
228 ++optind;
229 }
230 if (optind < argc) {
231 for (; optind < argc; ++optind)
232 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
233 usage_for(op);
234 return SG_LIB_SYNTAX_ERROR;
235 }
236 }
237 return 0;
238 }
239
240 static int
old_parse_cmd_line(struct opts_t * op,int argc,char * argv[])241 old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
242 {
243 bool jmp_out;
244 int k, plen, num;
245 int64_t nn;
246 const char * cp;
247
248 for (k = 1; k < argc; ++k) {
249 cp = argv[k];
250 plen = strlen(cp);
251 if (plen <= 0)
252 continue;
253 if ('-' == *cp) {
254 for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
255 switch (*cp) {
256 case 'd':
257 op->do_dio = true;
258 break;
259 case 'e':
260 op->do_echo = true;
261 break;
262 case 'h':
263 case '?':
264 ++op->do_help;
265 break;
266 case 'm':
267 op->do_mmap = true;
268 break;
269 case 'N':
270 op->opt_new = true;
271 return 0;
272 case 'O':
273 break;
274 case 'q':
275 op->do_quick = true;
276 break;
277 case 't':
278 op->do_time = true;
279 break;
280 case 'v':
281 op->verbose_given = true;
282 ++op->verbose;
283 break;
284 case 'V':
285 op->version_given = true;
286 break;
287 default:
288 jmp_out = true;
289 break;
290 }
291 if (jmp_out)
292 break;
293 }
294 if (plen <= 0)
295 continue;
296 if (0 == strncmp("b=", cp, 2)) {
297 num = sscanf(cp + 2, "%d", &op->do_buffer);
298 if ((1 != num) || (op->do_buffer <= 0)) {
299 printf("Couldn't decode number after 'b=' option\n");
300 usage_for(op);
301 return SG_LIB_SYNTAX_ERROR;
302 }
303 op->do_buffer *= 1024;
304 }
305 else if (0 == strncmp("s=", cp, 2)) {
306 nn = sg_get_llnum(optarg);
307 if (nn < 0) {
308 printf("Couldn't decode number after 's=' option\n");
309 usage_for(op);
310 return SG_LIB_SYNTAX_ERROR;
311 }
312 op->do_size = nn;
313 op->do_size *= 1024 * 1024;
314 } else if (0 == strncmp("-old", cp, 4))
315 ;
316 else if (jmp_out) {
317 pr2serr("Unrecognized option: %s\n", cp);
318 usage_for(op);
319 return SG_LIB_SYNTAX_ERROR;
320 }
321 } else if (0 == op->device_name)
322 op->device_name = cp;
323 else {
324 pr2serr("too many arguments, got: %s, not expecting: %s\n",
325 op->device_name, cp);
326 usage_for(op);
327 return SG_LIB_SYNTAX_ERROR;
328 }
329 }
330 return 0;
331 }
332
333 static int
parse_cmd_line(struct opts_t * op,int argc,char * argv[])334 parse_cmd_line(struct opts_t * op, int argc, char * argv[])
335 {
336 int res;
337 char * cp;
338
339 cp = getenv("SG3_UTILS_OLD_OPTS");
340 if (cp) {
341 op->opt_new = false;
342 res = old_parse_cmd_line(op, argc, argv);
343 if ((0 == res) && op->opt_new)
344 res = new_parse_cmd_line(op, argc, argv);
345 } else {
346 op->opt_new = true;
347 res = new_parse_cmd_line(op, argc, argv);
348 if ((0 == res) && (! op->opt_new))
349 res = old_parse_cmd_line(op, argc, argv);
350 }
351 return res;
352 }
353
354
355 int
main(int argc,char * argv[])356 main(int argc, char * argv[])
357 {
358 #ifdef DEBUG
359 bool clear = true;
360 #endif
361 bool dio_incomplete = false;
362 int sg_fd, res, err;
363 int buf_capacity = 0;
364 int buf_size = 0;
365 size_t psz;
366 unsigned int k, num;
367 int64_t total_size = RB_DEF_SIZE;
368 struct opts_t * op;
369 uint8_t * rbBuff = NULL;
370 void * rawp = NULL;
371 uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
372 uint8_t rb_cdb [RB_CMD_LEN] SG_C_CPP_ZERO_INIT;
373 struct sg_io_hdr io_hdr;
374 struct timeval start_tm, end_tm;
375 struct opts_t opts;
376
377 #if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
378 psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
379 #else
380 psz = 4096; /* give up, pick likely figure */
381 #endif
382 op = &opts;
383 memset(op, 0, sizeof(opts));
384 res = parse_cmd_line(op, argc, argv);
385 if (res)
386 return SG_LIB_SYNTAX_ERROR;
387 if (op->do_help) {
388 usage_for(op);
389 return 0;
390 }
391 #ifdef DEBUG
392 pr2serr("In DEBUG mode, ");
393 if (op->verbose_given && op->version_given) {
394 pr2serr("but override: '-vV' given, zero verbose and continue\n");
395 op->verbose_given = false;
396 op->version_given = false;
397 op->verbose = 0;
398 } else if (! op->verbose_given) {
399 pr2serr("set '-vv'\n");
400 op->verbose = 2;
401 } else
402 pr2serr("keep verbose=%d\n", op->verbose);
403 #else
404 if (op->verbose_given && op->version_given)
405 pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
406 #endif
407 if (op->version_given) {
408 pr2serr("Version string: %s\n", version_str);
409 return 0;
410 }
411
412 if (NULL == op->device_name) {
413 pr2serr("No DEVICE argument given\n\n");
414 usage_for(op);
415 return SG_LIB_SYNTAX_ERROR;
416 }
417
418 if (op->do_buffer > 0)
419 buf_size = op->do_buffer;
420 if (op->do_size > 0)
421 total_size = op->do_size;
422
423 sg_fd = open(op->device_name, O_RDONLY | O_NONBLOCK);
424 if (sg_fd < 0) {
425 err = errno;
426 perror("device open error");
427 return sg_convert_errno(err);
428 }
429 if (op->do_mmap) {
430 op->do_dio = false;
431 op->do_quick = false;
432 }
433 if (NULL == (rawp = malloc(512))) {
434 printf("out of memory (query)\n");
435 return SG_LIB_CAT_OTHER;
436 }
437 rbBuff = (uint8_t *)rawp;
438
439 rb_cdb[0] = RB_OPCODE;
440 rb_cdb[1] = op->do_echo ? RB_MODE_ECHO_DESC : RB_MODE_DESC;
441 rb_cdb[8] = RB_DESC_LEN;
442 memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
443 io_hdr.interface_id = 'S';
444 io_hdr.cmd_len = sizeof(rb_cdb);
445 io_hdr.mx_sb_len = sizeof(sense_buffer);
446 io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
447 io_hdr.dxfer_len = RB_DESC_LEN;
448 io_hdr.dxferp = rbBuff;
449 io_hdr.cmdp = rb_cdb;
450 io_hdr.sbp = sense_buffer;
451 io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
452 if (op->verbose) {
453 char b[128];
454
455 pr2serr(" Read buffer (%sdescriptor) cdb: %s\n",
456 (op->do_echo ? "echo " : ""),
457 sg_get_command_str(rb_cdb, RB_CMD_LEN, false, sizeof(b), b));
458 }
459
460 /* do normal IO to find RB size (not dio or mmap-ed at this stage) */
461 if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
462 perror("SG_IO READ BUFFER descriptor error");
463 if (rawp)
464 free(rawp);
465 return SG_LIB_CAT_OTHER;
466 }
467
468 if (op->verbose > 2)
469 pr2serr(" duration=%u ms\n", io_hdr.duration);
470 /* now for the error processing */
471 res = sg_err_category3(&io_hdr);
472 switch (res) {
473 case SG_LIB_CAT_RECOVERED:
474 sg_chk_n_print3("READ BUFFER descriptor, continuing", &io_hdr,
475 op->verbose > 1);
476 #if defined(__GNUC__)
477 #if (__GNUC__ >= 7)
478 __attribute__((fallthrough));
479 /* FALL THROUGH */
480 #endif
481 #endif
482 case SG_LIB_CAT_CLEAN:
483 break;
484 default: /* won't bother decoding other categories */
485 sg_chk_n_print3("READ BUFFER descriptor error", &io_hdr,
486 op->verbose > 1);
487 if (rawp) free(rawp);
488 return (res >= 0) ? res : SG_LIB_CAT_OTHER;
489 }
490
491 if (op->do_echo) {
492 buf_capacity = 0x1fff & sg_get_unaligned_be16(rbBuff + 2);
493 printf("READ BUFFER reports: echo buffer capacity=%d\n",
494 buf_capacity);
495 } else {
496 buf_capacity = sg_get_unaligned_be24(rbBuff + 1);
497 printf("READ BUFFER reports: buffer capacity=%d, offset "
498 "boundary=%d\n", buf_capacity, (int)rbBuff[0]);
499 }
500
501 if (0 == buf_size)
502 buf_size = buf_capacity;
503 else if (buf_size > buf_capacity) {
504 printf("Requested buffer size=%d exceeds reported capacity=%d\n",
505 buf_size, buf_capacity);
506 if (rawp) free(rawp);
507 return SG_LIB_CAT_MALFORMED;
508 }
509 if (rawp) {
510 free(rawp);
511 rawp = NULL;
512 }
513
514 if (! op->do_dio) {
515 k = buf_size;
516 if (op->do_mmap && (0 != (k % psz)))
517 k = ((k / psz) + 1) * psz; /* round up to page size */
518 res = ioctl(sg_fd, SG_SET_RESERVED_SIZE, &k);
519 if (res < 0)
520 perror("SG_SET_RESERVED_SIZE error");
521 }
522
523 if (op->do_mmap) {
524 rbBuff = (uint8_t *)mmap(NULL, buf_size, PROT_READ, MAP_SHARED,
525 sg_fd, 0);
526 if (MAP_FAILED == rbBuff) {
527 if (ENOMEM == errno) {
528 pr2serr("mmap() out of memory, try a smaller buffer size "
529 "than %d bytes\n", buf_size);
530 if (op->opt_new)
531 pr2serr(" [with '--buffer=EACH' where EACH is in "
532 "bytes]\n");
533 else
534 pr2serr(" [with '-b=EACH' where EACH is in KiB]\n");
535 } else
536 perror("error using mmap()");
537 return SG_LIB_CAT_OTHER;
538 }
539 }
540 else { /* non mmap-ed IO */
541 rawp = (uint8_t *)malloc(buf_size + (op->do_dio ? psz : 0));
542 if (NULL == rawp) {
543 printf("out of memory (data)\n");
544 return SG_LIB_CAT_OTHER;
545 }
546 /* perhaps use posix_memalign() instead */
547 if (op->do_dio) /* align to page boundary */
548 rbBuff= (uint8_t *)(((sg_uintptr_t)rawp + psz - 1) &
549 (~(psz - 1)));
550 else
551 rbBuff = (uint8_t *)rawp;
552 }
553
554 num = total_size / buf_size;
555 if (op->do_time) {
556 start_tm.tv_sec = 0;
557 start_tm.tv_usec = 0;
558 gettimeofday(&start_tm, NULL);
559 }
560 /* main data reading loop */
561 for (k = 0; k < num; ++k) {
562 memset(rb_cdb, 0, RB_CMD_LEN);
563 rb_cdb[0] = RB_OPCODE;
564 rb_cdb[1] = op->do_echo ? RB_MODE_ECHO_DATA : RB_MODE_DATA;
565 sg_put_unaligned_be24((uint32_t)buf_size, rb_cdb + 6);
566 #ifdef DEBUG
567 memset(rbBuff, 0, buf_size);
568 #endif
569
570 memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
571 io_hdr.interface_id = 'S';
572 io_hdr.cmd_len = sizeof(rb_cdb);
573 io_hdr.mx_sb_len = sizeof(sense_buffer);
574 io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
575 io_hdr.dxfer_len = buf_size;
576 if (! op->do_mmap)
577 io_hdr.dxferp = rbBuff;
578 io_hdr.cmdp = rb_cdb;
579 io_hdr.sbp = sense_buffer;
580 io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
581 io_hdr.pack_id = k;
582 if (op->do_mmap)
583 io_hdr.flags |= SG_FLAG_MMAP_IO;
584 else if (op->do_dio)
585 io_hdr.flags |= SG_FLAG_DIRECT_IO;
586 else if (op->do_quick)
587 io_hdr.flags |= SG_FLAG_NO_DXFER;
588 if (op->verbose > 1) {
589 char b[128];
590
591 pr2serr(" Read buffer (%sdata) cdb: %s\n",
592 (op->do_echo ? "echo " : ""),
593 sg_get_command_str(rb_cdb, RB_CMD_LEN, false,
594 sizeof(b), b));
595 }
596 if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
597 if (ENOMEM == errno) {
598 pr2serr("SG_IO data: out of memory, try a smaller buffer "
599 "size than %d bytes\n", buf_size);
600 if (op->opt_new)
601 pr2serr(" [with '--buffer=EACH' where EACH is in "
602 "bytes]\n");
603 else
604 pr2serr(" [with '-b=EACH' where EACH is in KiB]\n");
605 } else
606 perror("SG_IO READ BUFFER data error");
607 if (rawp) free(rawp);
608 return SG_LIB_CAT_OTHER;
609 }
610
611 if (op->verbose > 2)
612 pr2serr(" duration=%u ms\n", io_hdr.duration);
613 /* now for the error processing */
614 res = sg_err_category3(&io_hdr);
615 switch (res) {
616 case SG_LIB_CAT_CLEAN:
617 break;
618 case SG_LIB_CAT_RECOVERED:
619 sg_chk_n_print3("READ BUFFER data, continuing", &io_hdr,
620 op->verbose > 1);
621 break;
622 default: /* won't bother decoding other categories */
623 sg_chk_n_print3("READ BUFFER data error", &io_hdr,
624 op->verbose > 1);
625 if (rawp) free(rawp);
626 return (res >= 0) ? res : SG_LIB_CAT_OTHER;
627 }
628 if (op->do_dio &&
629 ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
630 dio_incomplete = true; /* flag that dio not done (completely) */
631
632 #ifdef DEBUG
633 if (clear) {
634 int j;
635
636 for (j = 0; j < buf_size; ++j) {
637 if (rbBuff[j] != 0) {
638 clear = false;
639 break;
640 }
641 }
642 }
643 #endif
644 }
645 if (op->do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
646 struct timeval res_tm;
647 double a, b;
648
649 gettimeofday(&end_tm, NULL);
650 res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
651 res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
652 if (res_tm.tv_usec < 0) {
653 --res_tm.tv_sec;
654 res_tm.tv_usec += 1000000;
655 }
656 a = res_tm.tv_sec;
657 a += (0.000001 * res_tm.tv_usec);
658 b = (double)buf_size * num;
659 printf("time to read data from buffer was %d.%06d secs",
660 (int)res_tm.tv_sec, (int)res_tm.tv_usec);
661 if (a > 0.00001) {
662 if (b > 511)
663 printf(", %.2f MB/sec", b / (a * 1000000.0));
664 printf(", %.2f IOPS", num / a);
665 }
666 printf("\n");
667 }
668 if (dio_incomplete)
669 printf(">> direct IO requested but not done\n");
670 printf("Read %" PRId64 " MiB (actual: %" PRId64 " bytes), buffer "
671 "size=%d KiB (%d bytes)\n", (total_size / (1024 * 1024)),
672 (int64_t)num * buf_size, buf_size / 1024, buf_size);
673
674 if (rawp) free(rawp);
675 res = close(sg_fd);
676 if (res < 0) {
677 err = errno;
678 perror("close error");
679 return sg_convert_errno(err);
680 }
681 #ifdef DEBUG
682 if (clear)
683 printf("read buffer always zero\n");
684 else
685 printf("read buffer non-zero\n");
686 #endif
687 return res;
688 }
689