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