1 /*
2 * Copyright (C) 2003-2021 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 * Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg")
11 * device driver.
12 * This C++ program will read a certain number of blocks of a given block
13 * size from a given sg device node using struct sg_iovec and write what is
14 * retrieved out to a normal file. The purpose is to test the sg_iovec
15 * mechanism within the sg_io_hdr and sg_io_v4 structures.
16 *
17 * struct sg_iovec and struct iovec [in include/uapi/uio.h] are basically
18 * the same thing: a pointer followed by a length (of type size_t). If
19 * applied to a disk then the pointer will hold a LBA and 'length' will
20 * be a number of logical blocks (which usually cannot exceed 2**32-1 .
21 *
22 */
23
24 #include <unistd.h>
25 #include <signal.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <getopt.h>
31 #include <errno.h>
32 #include <poll.h>
33 #include <limits.h>
34 #include <time.h>
35 #define __STDC_FORMAT_MACROS 1
36 #include <inttypes.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40
41 #include <linux/bsg.h>
42
43 #ifndef HAVE_LINUX_SG_V4_HDR
44
45 /* Kernel uapi header contain __user decorations on user space pointers
46 * to indicate they are unsafe in the kernel space. However glibc takes
47 * all those __user decorations out from headers in /usr/include/linux .
48 * So to stop compile errors when directly importing include/uapi/scsi/sg.h
49 * undef __user before doing that include. */
50 #define __user
51
52 /* Want to block the original sg.h header from also being included. That
53 * causes lots of multiple definition errors. This will only work if this
54 * header is included _before_ the original sg.h header. */
55 #define _SCSI_GENERIC_H /* original kernel header guard */
56 #define _SCSI_SG_H /* glibc header guard */
57
58 #include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
59
60 #else
61 #define __user
62 #endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
63
64 #include "sg_lib.h"
65 #include "sg_io_linux.h"
66 #include "sg_unaligned.h"
67
68 // C++ local header
69 #include "sg_scat_gath.h"
70
71 static const char * version_str = "1.08 20210214";
72
73 #define ME "sg_iovec_tst: "
74
75 #define IOVEC_ELEMS 1024 /* match current UIO_MAXIOV in <linux/uio.h> */
76
77 #define DEF_BLK_SZ 512
78 #define SENSE_BUFF_LEN 32
79 #define DEF_TIMEOUT 40000 /* 40,000 milliseconds */
80
81 static struct sg_iovec iovec[IOVEC_ELEMS];
82
83 static int verbose;
84
85 static struct option long_options[] = {
86 {"async", no_argument, 0, 'a'},
87 {"bs", required_argument, 0, 'b'},
88 {"elem_size", required_argument, 0, 'e'},
89 {"elem-size", required_argument, 0, 'e'},
90 {"elemsz", required_argument, 0, 'e'},
91 {"fill", required_argument, 0, 'f'},
92 {"from_skip", no_argument, 0, 'F'},
93 {"from-skip", no_argument, 0, 'F'},
94 {"help", no_argument, 0, 'h'},
95 {"num", required_argument, 0, 'n'},
96 {"num_blks", required_argument, 0, 'n'},
97 {"num-blks", required_argument, 0, 'n'},
98 {"sgl", required_argument, 0, 'S'},
99 {"sgv4", no_argument, 0, '4'},
100 {"skip", required_argument, 0, 's'},
101 {"verbose", no_argument, 0, 'v'},
102 {"version", no_argument, 0, 'V'},
103 {0, 0, 0, 0},
104 };
105
106
107 static void
usage(void)108 usage(void)
109 {
110 printf("Usage: sg_iovec_tst [--async] [--bs=BS] [--elem_sz=ES] "
111 "[--fill=F_ELEMS]\n"
112 " [from_skip] [--help] --num=NUM [--sgl=SFN] "
113 "[--sgv4]\n"
114 " [--skip=SKIP] [--verbose] [--version] "
115 "SG_DEV OUT_F\n");
116 printf("where:\n"
117 " --async|-a async sg usage (def: use ioctl(SG_IO) )\n");
118 printf(" --bs=BS|-b BS logical block size of SG_DEV (def: 512 "
119 "bytes)\n");
120 printf(" --elem_sz=ES|-e ES iovec element size (def: BS bytes)\n");
121 printf(" --fill=F_ELEMS|-f F_ELEMS append F_ELEMS*ES zero bytes "
122 "onto OUT_F\n"
123 " after each iovec element (def: "
124 "0)\n");
125 printf(" --from_skip|-F sgl output starts from SKIP (def: 0)\n");
126 printf(" --help|-h this usage message\n");
127 printf(" --num=NUM|-n NUM number of blocks to read from SG_DEV\n");
128 printf(" --sgl=SFN|-S SFN Sgl FileName (SFN) that is written to, "
129 "with\n"
130 " addresses and lengths having ES as "
131 "their unit\n");
132 printf(" --sgv4|-4 use the sg v4 interface (def: v3 "
133 "interface)\n");
134 printf(" --skip=SKIP|-s SKIP SKIP blocks before reading S_DEV "
135 "(def: 0)\n");
136 printf(" --verbose|-v increase verbosity\n");
137 printf(" --version|-V print version and exit\n\n");
138 printf("Reads from SG_DEV and writes that data to OUT_F in binary. Uses "
139 "iovec\n(a scatter gather list) in linear mode (i.e. it cuts up "
140 "a contiguous\nbuffer). Example:\n"
141 " sg_iovec_tst -n 8k -e 4k /dev/sg3 out.bin\n");
142 }
143
144 /* Returns 0 if everything ok */
145 static int
sg_read(int sg_fd,uint8_t * buff,int num_blocks,int from_block,int bs,int elem_size,int async)146 sg_read(int sg_fd, uint8_t * buff, int num_blocks, int from_block, int bs,
147 int elem_size, int async)
148 {
149 uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0};
150 uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
151 struct sg_io_hdr io_hdr;
152 struct pollfd a_poll;
153 int dxfer_len = bs * num_blocks;
154 int k, pos, rem;
155
156 sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2);
157 sg_put_unaligned_be16((uint16_t)num_blocks, rdCmd + 7);
158
159 for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) {
160 iovec[k].iov_base = buff + pos;
161 iovec[k].iov_len = (rem > elem_size) ? elem_size : rem;
162 if (rem <= elem_size)
163 break;
164 pos += elem_size;
165 rem -= elem_size;
166 }
167 if (k >= IOVEC_ELEMS) {
168 fprintf(stderr, "Can't fit dxfer_len=%d bytes in %d iovec elements "
169 "(would need %d)\n", dxfer_len, IOVEC_ELEMS,
170 dxfer_len / elem_size);
171 fprintf(stderr, "Try expanding elem_size which is currently %d "
172 "bytes\n", elem_size);
173 return -1;
174 }
175 memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
176 io_hdr.interface_id = 'S';
177 io_hdr.cmd_len = sizeof(rdCmd);
178 io_hdr.cmdp = rdCmd;
179 io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
180 io_hdr.dxfer_len = dxfer_len;
181 io_hdr.iovec_count = k + 1;
182 io_hdr.dxferp = iovec;
183 io_hdr.mx_sb_len = SENSE_BUFF_LEN;
184 io_hdr.sbp = senseBuff;
185 io_hdr.timeout = DEF_TIMEOUT;
186 io_hdr.pack_id = from_block;
187 if (verbose) {
188 char b[128];
189
190 fprintf(stderr, "cdb: %s\n", sg_get_command_str(rdCmd, 10, true,
191 sizeof(b), b));
192 }
193
194 if (async) {
195 int res = write(sg_fd, &io_hdr, sizeof(io_hdr));
196
197 if (res < 0) {
198 perror("write(<sg_device>), error");
199 return -1;
200 } else if (res < (int)sizeof(io_hdr)) {
201 fprintf(stderr, "write(<sg_device>) returned %d, expected %d\n",
202 res, (int)sizeof(io_hdr));
203 return -1;
204 }
205 a_poll.fd = sg_fd;
206 a_poll.events = POLLIN;
207 a_poll.revents = 0;
208 res = poll(&a_poll, 1, 2000 /* millisecs */ );
209 if (res < 0) {
210 perror("poll error on <sg_device>");
211 return -1;
212 }
213 if (0 == (POLLIN & a_poll.revents)) {
214 fprintf(stderr, "strange, poll() completed without data to "
215 "read\n");
216 return -1;
217 }
218 res = read(sg_fd, &io_hdr, sizeof(io_hdr));
219 if (res < 0) {
220 perror("read(<sg_device>), error");
221 return -1;
222 } else if (res < (int)sizeof(io_hdr)) {
223 fprintf(stderr, "read(<sg_device>) returned %d, expected %d\n",
224 res, (int)sizeof(io_hdr));
225 return -1;
226 }
227 } else if (ioctl(sg_fd, SG_IO, &io_hdr)) {
228 perror("reading (SG_IO) on sg device, error");
229 return -1;
230 }
231 switch (sg_err_category3(&io_hdr)) {
232 case SG_LIB_CAT_CLEAN:
233 break;
234 case SG_LIB_CAT_RECOVERED:
235 fprintf(stderr, "Recovered error while reading block=%d, num=%d\n",
236 from_block, num_blocks);
237 break;
238 case SG_LIB_CAT_UNIT_ATTENTION:
239 fprintf(stderr, "Unit attention\n");
240 return -1;
241 default:
242 sg_chk_n_print3("reading", &io_hdr, 1);
243 return -1;
244 }
245 return 0;
246 }
247
248 /* Returns 0 if everything ok */
249 static int
sg_read_v4(int sg_fd,uint8_t * buff,int num_blocks,int from_block,int bs,int elem_size,int async)250 sg_read_v4(int sg_fd, uint8_t * buff, int num_blocks, int from_block, int bs,
251 int elem_size, int async)
252 {
253 uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0};
254 uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
255 struct sg_io_v4 io_hdr;
256 struct pollfd a_poll;
257 int dxfer_len = bs * num_blocks;
258 int k, pos, rem, res;
259
260 sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2);
261 sg_put_unaligned_be16((uint16_t)num_blocks, rdCmd + 7);
262
263 for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) {
264 iovec[k].iov_base = buff + pos;
265 iovec[k].iov_len = (rem > elem_size) ? elem_size : rem;
266 if (rem <= elem_size)
267 break;
268 pos += elem_size;
269 rem -= elem_size;
270 }
271 if (k >= IOVEC_ELEMS) {
272 fprintf(stderr, "Can't fit dxfer_len=%d bytes in %d iovec elements "
273 "(would need %d)\n", dxfer_len, IOVEC_ELEMS,
274 dxfer_len / elem_size);
275 fprintf(stderr, "Try expanding elem_size which is currently %d "
276 "bytes\n", elem_size);
277 return -1;
278 }
279 memset(&io_hdr, 0, sizeof(struct sg_io_v4));
280 io_hdr.guard = 'Q';
281 io_hdr.request_len = sizeof(rdCmd);
282 io_hdr.request = (uint64_t)(uintptr_t)rdCmd;
283 io_hdr.din_xfer_len = dxfer_len;
284 io_hdr.din_xferp = (uint64_t)(uintptr_t)iovec;
285 io_hdr.din_iovec_count = k + 1;
286 io_hdr.max_response_len = SG_DXFER_FROM_DEV;
287 io_hdr.response = (uint64_t)(uintptr_t)senseBuff;
288 io_hdr.timeout = DEF_TIMEOUT;
289 io_hdr.request_extra = from_block; /* pack_id */
290 if (verbose) {
291 char b[128];
292
293 fprintf(stderr, "cdb: %s\n", sg_get_command_str(rdCmd, 10, true,
294 sizeof(b), b));
295 }
296 if (async) {
297 res = ioctl(sg_fd, SG_IOSUBMIT, &io_hdr);
298 if (res < 0) {
299 perror("ioctl(SG_IOSUBMIT <sg_device>), error");
300 return -1;
301 }
302 a_poll.fd = sg_fd;
303 a_poll.events = POLLIN;
304 a_poll.revents = 0;
305 res = poll(&a_poll, 1, 2000 /* millisecs */ );
306 if (res < 0) {
307 perror("poll error on <sg_device>");
308 return -1;
309 }
310 if (0 == (POLLIN & a_poll.revents)) {
311 fprintf(stderr, "strange, poll() completed without data to "
312 "read\n");
313 return -1;
314 }
315 res = ioctl(sg_fd, SG_IORECEIVE, &io_hdr);
316 if (res < 0) {
317 perror("ioctl(SG_IORECEIVE <sg_device>), error");
318 return -1;
319 }
320 } else if (ioctl(sg_fd, SG_IO, &io_hdr)) {
321 perror("ioctl(SG_IO) on sg device, error");
322 return -1;
323 }
324
325 res = sg_err_category_new(io_hdr.device_status, io_hdr.transport_status,
326 io_hdr.driver_status,
327 (const uint8_t *)(unsigned long)io_hdr.response,
328 io_hdr.response_len);
329 switch (res) {
330 case SG_LIB_CAT_CLEAN:
331 break;
332 case SG_LIB_CAT_RECOVERED:
333 fprintf(stderr, "Recovered error while reading block=%d, num=%d\n",
334 from_block, num_blocks);
335 break;
336 case SG_LIB_CAT_UNIT_ATTENTION:
337 fprintf(stderr, "Unit attention\n");
338 return -1;
339 default:
340 sg_linux_sense_print("reading", io_hdr.device_status,
341 io_hdr.transport_status, io_hdr.driver_status,
342 senseBuff, io_hdr.response_len, true);
343 return -1;
344 }
345 return 0;
346 }
347
348
349 int
main(int argc,char * argv[])350 main(int argc, char * argv[])
351 {
352 bool do_sgv4 = false;
353 bool do_async = false;
354 bool do_help = false;
355 bool from_skip = false;
356 bool blk_size_given = false;
357 bool elem_size_given = false;
358 int sg_fd, fd, c, res, res2, err, dxfer_len;
359 unsigned int k;
360 int blk_size = DEF_BLK_SZ;
361 int elem_size = blk_size;
362 int num_blks = 0;
363 int f_elems = 0;
364 int64_t start_blk = 0;
365 char * sg_dev_name = 0;
366 char * out_file_name = 0;
367 char * sgl_fn = 0;
368 uint8_t * buffp;
369 uint8_t * fillp = NULL;
370 FILE * fp = NULL;
371
372 while (1) {
373 int option_index = 0;
374
375 c = getopt_long(argc, argv, "4ab:e:f:Fhn:s:S:vV",
376 long_options, &option_index);
377 if (c == -1)
378 break;
379
380 switch (c) {
381 case '4':
382 do_sgv4 = true;
383 break;
384 case 'a':
385 do_async = true;
386 break;
387 case 'b':
388 blk_size = sg_get_num(optarg);
389 if (blk_size < 1) {
390 printf("Couldn't decode positive number after '--bs=' "
391 "option\n");
392 sg_dev_name = 0;
393 } else
394 blk_size_given = true;
395 break;
396 case 'e':
397 elem_size = sg_get_num(optarg);
398 if (elem_size < 1) {
399 printf("Couldn't decode positive number after '--elem_size=' "
400 "option\n");
401 sg_dev_name = 0;
402 } else
403 elem_size_given = true;
404 break;
405 case 'f':
406 f_elems = sg_get_num(optarg);
407 if (f_elems < 0) {
408 printf("Couldn't decode number after '--fill=' option\n");
409 sg_dev_name = 0;
410 }
411 break;
412 case 'F':
413 from_skip = true;
414 break;
415 case 'h':
416 do_help = true;
417 break;
418 case 'n':
419 num_blks = sg_get_num(optarg);
420 if (num_blks < 1) {
421 printf("Couldn't decode positive number after '--num=' "
422 "option\n");
423 sg_dev_name = 0;
424 }
425 break;
426 case 's':
427 start_blk = sg_get_llnum(optarg);
428 if ((start_blk < 0) || (start_blk > INT_MAX)) {
429 printf("Couldn't decode number after '--skip=' option\n");
430 sg_dev_name = 0;
431 }
432 break;
433 case 'S':
434 if (sgl_fn) {
435 printf("Looks like --sgl=SFN has been given twice\n");
436 sg_dev_name = 0;
437 } else
438 sgl_fn = optarg;
439 break;
440 case 'v':
441 ++verbose;
442 break;
443 case 'V':
444 printf("Version: %s\n", version_str);
445 return 0;
446 default:
447 fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
448 usage();
449 return SG_LIB_SYNTAX_ERROR;
450 }
451 }
452 if (optind < argc) {
453 if (NULL == sg_dev_name) {
454 sg_dev_name = argv[optind];
455 ++optind;
456 }
457 if (optind < argc) {
458 if (sg_dev_name) {
459 out_file_name = argv[optind];
460 ++optind;
461 }
462 if (optind < argc) {
463 for (; optind < argc; ++optind)
464 fprintf(stderr, "Unexpected extra argument: %s\n",
465 argv[optind]);
466 usage();
467 return SG_LIB_SYNTAX_ERROR;
468 }
469 }
470 }
471 if (do_help) {
472 usage();
473 return 0;
474 }
475 if (NULL == sg_dev_name) {
476 printf(">>> need sg node name (e.g. /dev/sg3)\n\n");
477 usage();
478 return 1;
479 }
480 if (NULL == out_file_name) {
481 printf(">>> need out filename (to place what is fetched by READ\n\n");
482 usage();
483 return 1;
484 }
485 if (0 == num_blks) {
486 printf(">>> need number of blocks to READ\n\n");
487 usage();
488 return 1;
489 }
490
491 if ((! elem_size_given) && blk_size_given)
492 elem_size = blk_size;
493
494 if (do_async)
495 sg_fd = open(sg_dev_name, O_RDWR);
496 else
497 sg_fd = open(sg_dev_name, O_RDONLY);
498 if (sg_fd < 0) {
499 perror(ME "sg device node open error");
500 return 1;
501 }
502 /* Don't worry, being very careful not to write to a none-sg file ... */
503 res = ioctl(sg_fd, SG_GET_VERSION_NUM, &k);
504 if ((res < 0) || (k < 30000)) {
505 printf(ME "not a sg device, or driver prior to 3.x\n");
506 return 1;
507 }
508 fd = open(out_file_name, O_WRONLY | O_CREAT, 0666);
509 if (fd < 0) {
510 perror(ME "output file open error");
511 return 1;
512 }
513 if (f_elems > 0) {
514 fillp = (uint8_t *)calloc(f_elems, elem_size);
515 if (NULL == fillp) {
516 fprintf(stderr, "fill calloc for %d bytes failed\n",
517 f_elems * elem_size);
518 goto fini;
519 }
520 }
521 if (sgl_fn) {
522 time_t t = time(NULL);
523 struct tm *tm = localtime(&t);
524 char s[128];
525
526 fp = fopen(sgl_fn, "w");
527 if (NULL == fp) {
528 err = errno;
529 fprintf(stderr, "Unable to open %s, error: %s\n", sgl_fn,
530 strerror(err));
531 res = sg_convert_errno(err);
532 goto fini;
533 }
534 strftime(s, sizeof(s), "%c", tm);
535 fprintf(fp, "# Scatter gather list generated by sg_iovec_tst "
536 "%s\n#\n", s);
537 }
538
539 dxfer_len = num_blks * blk_size;
540 buffp = (uint8_t *)calloc(num_blks, blk_size);
541 if (buffp) {
542 int dx_len;
543 int64_t curr_blk = from_skip ? start_blk : 0;
544
545 if (do_sgv4) {
546 if (sg_read(sg_fd, buffp, num_blks, (int)start_blk, blk_size,
547 elem_size, do_async))
548 goto free_buff;
549 } else {
550 if (sg_read_v4(sg_fd, buffp, num_blks, (int)start_blk, blk_size,
551 elem_size, do_async))
552 goto free_buff;
553 }
554 if (f_elems > 0) {
555 int fill_len = f_elems * elem_size;
556
557 for (dx_len = 0; dx_len < dxfer_len; dx_len += elem_size) {
558 if (write(fd, buffp + dx_len, elem_size) < 0) {
559 perror(ME "partial dxfer output write failed");
560 break;
561 }
562 if (sgl_fn) {
563 fprintf(fp, "%" PRId64 ",1\n", curr_blk);
564 curr_blk += f_elems + 1;
565 }
566 if (write(fd, fillp, fill_len) < 0) {
567 perror(ME "partial fill output write failed");
568 break;
569 }
570 }
571 } else if (write(fd, buffp, dxfer_len) < 0)
572 perror(ME "full output write failed");
573 else if (sgl_fn) {
574 for (dx_len = 0; dx_len < dxfer_len; dx_len += elem_size)
575 fprintf(fp, "%" PRId64 ",1\n", curr_blk++);
576 }
577 free_buff:
578 free(buffp);
579 } else
580 fprintf(stderr, "user space calloc for %d bytes failed\n",
581 dxfer_len);
582 res = close(fd);
583 if (res < 0) {
584 perror(ME "output file close error");
585 close(sg_fd);
586 return 1;
587 }
588 fini:
589 res2 = close(sg_fd);
590 if (res2 < 0) {
591 err = errno;
592 perror(ME "sg device close error");
593 if (0 == res)
594 res = sg_convert_errno(err);
595 }
596 if (fp)
597 fclose(fp);
598 return res;
599 }
600