1 /*
2 * Copyright (C) 2000-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
11 /*
12 * This program sends a user specified number of TEST UNIT READY ("tur")
13 * commands to the given sg device. Since TUR is a simple command involing
14 * no data transfer (and no REQUEST SENSE command iff the unit is ready)
15 * then this can be used for timing per SCSI command overheads.
16 */
17
18 #include <unistd.h>
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdarg.h>
23 #include <stdbool.h>
24 #include <string.h>
25 #include <time.h>
26 #include <errno.h>
27 #include <getopt.h>
28 #define __STDC_FORMAT_MACROS 1
29 #include <inttypes.h>
30
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34
35 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
36 #include <time.h>
37 #elif defined(HAVE_GETTIMEOFDAY)
38 #include <time.h>
39 #include <sys/time.h>
40 #endif
41
42 #include "sg_lib.h"
43 #include "sg_cmds_basic.h"
44 #include "sg_pt.h"
45 #include "sg_pr2serr.h"
46
47
48 static const char * version_str = "3.51 20220425";
49
50 #define DEF_PT_TIMEOUT 60 /* 60 seconds */
51
52
53 static struct option long_options[] = {
54 {"delay", required_argument, 0, 'd'},
55 {"help", no_argument, 0, 'h'},
56 {"low", no_argument, 0, 'l'},
57 {"new", no_argument, 0, 'N'},
58 {"number", required_argument, 0, 'n'},
59 {"num", required_argument, 0, 'n'}, /* added in v3.32 (sg3_utils
60 * v1.43) for sg_requests compatibility */
61 {"old", no_argument, 0, 'O'},
62 {"progress", no_argument, 0, 'p'},
63 {"time", no_argument, 0, 't'},
64 {"verbose", no_argument, 0, 'v'},
65 {"version", no_argument, 0, 'V'},
66 {0, 0, 0, 0},
67 };
68
69 struct opts_t {
70 bool delay_given;
71 bool do_low;
72 bool do_progress;
73 bool do_time;
74 bool opts_new;
75 bool verbose_given;
76 bool version_given;
77 int delay;
78 int do_help;
79 int do_number;
80 int verbose;
81 const char * device_name;
82 };
83
84 struct loop_res_t {
85 bool reported;
86 int num_errs;
87 int ret;
88 };
89
90
91 static void
usage()92 usage()
93 {
94 printf("Usage: sg_turs [--delay=MS] [--help] [--low] [--number=NUM] "
95 "[--num=NUM]\n"
96 " [--progress] [--time] [--verbose] [--version] "
97 "DEVICE\n"
98 " where:\n"
99 " --delay=MS|-d MS delay MS miiliseconds before sending "
100 "each tur\n"
101 " --help|-h print usage message then exit\n"
102 " --low|-l use low level (sg_pt) interface for "
103 "speed\n"
104 " --number=NUM|-n NUM number of test_unit_ready commands "
105 "(def: 1)\n"
106 " --num=NUM|-n NUM same action as '--number=NUM'\n"
107 " --old|-O use old interface (use as first option)\n"
108 " --progress|-p outputs progress indication (percentage) "
109 "if available\n"
110 " waits 30 seconds before TUR unless "
111 "--delay=MS given\n"
112 " --time|-t outputs total duration and commands per "
113 "second\n"
114 " --verbose|-v increase verbosity\n"
115 " --version|-V print version string then exit\n\n"
116 "Performs a SCSI TEST UNIT READY command (or many of them).\n"
117 "This SCSI command is often known by its abbreviation: TUR .\n");
118 }
119
120 static void
usage_old()121 usage_old()
122 {
123 printf("Usage: sg_turs [-d=MS] [-l] [-n=NUM] [-p] [-t] [-v] [-V] "
124 "DEVICE\n"
125 " where:\n"
126 " -d=MS same as --delay=MS in new interface\n"
127 " -l use low level interface (sg_pt) for speed\n"
128 " -n=NUM number of test_unit_ready commands "
129 "(def: 1)\n"
130 " -p outputs progress indication (percentage) "
131 "if available\n"
132 " -t outputs total duration and commands per "
133 "second\n"
134 " -v increase verbosity\n"
135 " -N|--new use new interface\n"
136 " -V print version string then exit\n\n"
137 "Performs a SCSI TEST UNIT READY command (or many of them).\n");
138 }
139
140 static void
usage_for(const struct opts_t * op)141 usage_for(const struct opts_t * op)
142 {
143 if (op->opts_new)
144 usage();
145 else
146 usage_old();
147 }
148
149 static int
new_parse_cmd_line(struct opts_t * op,int argc,char * argv[])150 new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
151 {
152 int c, n;
153
154 while (1) {
155 int option_index = 0;
156
157 c = getopt_long(argc, argv, "d:hln:NOptvV", long_options,
158 &option_index);
159 if (c == -1)
160 break;
161
162 switch (c) {
163 case 'd':
164 n = sg_get_num(optarg);
165 if (n < 0) {
166 pr2serr("bad argument to '--delay='\n");
167 usage();
168 return SG_LIB_SYNTAX_ERROR;
169 }
170 op->delay = n;
171 op->delay_given = true;
172 break;
173 case 'h':
174 case '?':
175 ++op->do_help;
176 break;
177 case 'l':
178 op->do_low = true;
179 break;
180 case 'n':
181 n = sg_get_num(optarg);
182 if (n < 0) {
183 pr2serr("bad argument to '--number='\n");
184 usage();
185 return SG_LIB_SYNTAX_ERROR;
186 }
187 op->do_number = n;
188 break;
189 case 'N':
190 break; /* ignore */
191 case 'O':
192 op->opts_new = false;
193 return 0;
194 case 'p':
195 op->do_progress = true;
196 break;
197 case 't':
198 op->do_time = true;
199 break;
200 case 'v':
201 op->verbose_given = true;
202 ++op->verbose;
203 break;
204 case 'V':
205 op->version_given = true;
206 break;
207 default:
208 pr2serr("unrecognised option code %c [0x%x]\n", c, c);
209 if (op->do_help)
210 break;
211 usage();
212 return SG_LIB_SYNTAX_ERROR;
213 }
214 }
215 if (optind < argc) {
216 if (NULL == op->device_name) {
217 op->device_name = argv[optind];
218 ++optind;
219 }
220 if (optind < argc) {
221 for (; optind < argc; ++optind)
222 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
223 usage();
224 return SG_LIB_SYNTAX_ERROR;
225 }
226 }
227 return 0;
228 }
229
230 static int
old_parse_cmd_line(struct opts_t * op,int argc,char * argv[])231 old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
232 {
233 bool jmp_out;
234 int k, plen;
235 const char * cp;
236
237 for (k = 1; k < argc; ++k) {
238 cp = argv[k];
239 plen = strlen(cp);
240 if (plen <= 0)
241 continue;
242 if ('-' == *cp) {
243 for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
244 switch (*cp) {
245 case 'l':
246 op->do_low = true;
247 return 0;
248 case 'N':
249 op->opts_new = true;
250 return 0;
251 case 'O':
252 break;
253 case 'p':
254 op->do_progress = true;
255 break;
256 case 't':
257 op->do_time = true;
258 break;
259 case 'v':
260 op->verbose_given = true;
261 ++op->verbose;
262 break;
263 case 'V':
264 op->version_given = true;
265 break;
266 case '?':
267 ++op->do_help;
268 return 0;
269 default:
270 jmp_out = true;
271 break;
272 }
273 if (jmp_out)
274 break;
275 }
276 if (plen <= 0)
277 continue;
278 if (0 == strncmp("d=", cp, 2)) {
279 op->delay = sg_get_num(cp + 2);
280 if (op->delay < 0) {
281 printf("Couldn't decode number after 'd=' option\n");
282 usage_old();
283 return SG_LIB_SYNTAX_ERROR;
284 }
285 op->delay_given = true;
286 } else if (0 == strncmp("n=", cp, 2)) {
287 op->do_number = sg_get_num(cp + 2);
288 if (op->do_number <= 0) {
289 printf("Couldn't decode number after 'n=' option\n");
290 usage_old();
291 return SG_LIB_SYNTAX_ERROR;
292 }
293 } else if (0 == strncmp("-old", cp, 4))
294 ;
295 else if (jmp_out) {
296 pr2serr("Unrecognized option: %s\n", cp);
297 usage_old();
298 return SG_LIB_SYNTAX_ERROR;
299 }
300 } else if (0 == op->device_name)
301 op->device_name = cp;
302 else {
303 pr2serr("too many arguments, got: %s, not expecting: %s\n",
304 op->device_name, cp);
305 usage_old();
306 return SG_LIB_SYNTAX_ERROR;
307 }
308 }
309 return 0;
310 }
311
312 static int
parse_cmd_line(struct opts_t * op,int argc,char * argv[])313 parse_cmd_line(struct opts_t * op, int argc, char * argv[])
314 {
315 int res;
316 char * cp;
317
318 cp = getenv("SG3_UTILS_OLD_OPTS");
319 if (cp) {
320 op->opts_new = false;
321 res = old_parse_cmd_line(op, argc, argv);
322 if ((0 == res) && op->opts_new)
323 res = new_parse_cmd_line(op, argc, argv);
324 } else {
325 op->opts_new = true;
326 res = new_parse_cmd_line(op, argc, argv);
327 if ((0 == res) && (0 == op->opts_new))
328 res = old_parse_cmd_line(op, argc, argv);
329 }
330 return res;
331 }
332
333 #ifdef SG_LIB_MINGW
334
335 #include <windows.h>
336
337 static void
wait_millisecs(int millisecs)338 wait_millisecs(int millisecs)
339 {
340 /* MinGW requires pthreads library for nanosleep, use Sleep() instead */
341 Sleep(millisecs);
342 }
343
344 #else
345
346 static void
wait_millisecs(int millisecs)347 wait_millisecs(int millisecs)
348 {
349 struct timespec wait_period, rem;
350
351 wait_period.tv_sec = millisecs / 1000;
352 wait_period.tv_nsec = (millisecs % 1000) * 1000000;
353 while ((nanosleep(&wait_period, &rem) < 0) && (EINTR == errno))
354 wait_period = rem;
355 }
356
357 #endif
358
359 /* Returns true if prints estimate of duration to ready */
360 bool
check_for_lu_becoming(struct sg_pt_base * ptvp)361 check_for_lu_becoming(struct sg_pt_base * ptvp)
362 {
363 int s_len = get_scsi_pt_sense_len(ptvp);
364 uint64_t info;
365 uint8_t * sense_b = get_scsi_pt_sense_buf(ptvp);
366 struct sg_scsi_sense_hdr ssh;
367
368 /* Check for "LU is in process of becoming ready" with a non-zero INFO
369 * field that isn't too big. As per 20-061r2 it means the following: */
370 if (sg_scsi_normalize_sense(sense_b, s_len, &ssh) && (ssh.asc == 0x4) &&
371 (ssh.ascq == 0x1) && sg_get_sense_info_fld(sense_b, s_len, &info) &&
372 (info > 0x0) && (info < 0x1000000)) {
373 printf("device not ready, estimated to be ready in %" PRIu64
374 " milliseconds\n", info);
375 return true;
376 }
377 return false;
378 }
379
380 /* Returns number of TURs performed */
381 static int
loop_turs(struct sg_pt_base * ptvp,struct loop_res_t * resp,struct opts_t * op)382 loop_turs(struct sg_pt_base * ptvp, struct loop_res_t * resp,
383 struct opts_t * op)
384 {
385 int k, res;
386 int packet_id = 0;
387 int vb = op->verbose;
388 char b[80];
389 uint8_t sense_b[32] SG_C_CPP_ZERO_INIT;
390
391 if (op->do_low) {
392 int rs, n, sense_cat;
393 uint8_t cdb[6];
394
395 for (k = 0; k < op->do_number; ++k) {
396 if (op->delay > 0)
397 wait_millisecs(op->delay);
398 /* Might get Unit Attention on first invocation */
399 memset(cdb, 0, sizeof(cdb)); /* TUR's cdb is 6 zeros */
400 set_scsi_pt_cdb(ptvp, cdb, sizeof(cdb));
401 set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
402 set_scsi_pt_packet_id(ptvp, ++packet_id);
403 rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, vb);
404 n = sg_cmds_process_resp(ptvp, "Test unit ready", rs, (0 == k),
405 vb, &sense_cat);
406 if (-1 == n) {
407 if (get_scsi_pt_transport_err(ptvp))
408 resp->ret = SG_LIB_TRANSPORT_ERROR;
409 else
410 resp->ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
411 return k;
412 } else if (-2 == n) {
413 switch (sense_cat) {
414 case SG_LIB_CAT_RECOVERED:
415 case SG_LIB_CAT_NO_SENSE:
416 break;
417 case SG_LIB_CAT_NOT_READY:
418 ++resp->num_errs;
419 if ((1 == op->do_number) || (op->delay > 0)) {
420 if (! check_for_lu_becoming(ptvp))
421 printf("device not ready\n");
422 resp->ret = sense_cat;
423 resp->reported = true;
424 }
425 break;
426 case SG_LIB_CAT_UNIT_ATTENTION:
427 ++resp->num_errs;
428 if (vb) {
429 pr2serr("Ignoring Unit attention (sense key)\n");
430 resp->reported = true;
431 }
432 break;
433 case SG_LIB_CAT_STANDBY:
434 ++resp->num_errs;
435 if (vb) {
436 pr2serr("Ignoring standby device (sense key)\n");
437 resp->reported = true;
438 }
439 break;
440 case SG_LIB_CAT_UNAVAILABLE:
441 ++resp->num_errs;
442 if (vb) {
443 pr2serr("Ignoring unavailable device (sense key)\n");
444 resp->reported = true;
445 }
446 break;
447 default:
448 ++resp->num_errs;
449 if (1 == op->do_number) {
450 resp->ret = sense_cat;
451 sg_get_category_sense_str(sense_cat, sizeof(b), b, vb);
452 printf("%s\n", b);
453 resp->reported = true;
454 return k;
455 }
456 break;
457 }
458 }
459 partial_clear_scsi_pt_obj(ptvp);
460 }
461 return k;
462 } else {
463 for (k = 0; k < op->do_number; ++k) {
464 if (op->delay > 0)
465 wait_millisecs(op->delay);
466 set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
467 /* Might get Unit Attention on first invocation */
468 res = sg_ll_test_unit_ready_pt(ptvp, k, (0 == k), vb);
469 if (res) {
470 ++resp->num_errs;
471 resp->ret = res;
472 if ((1 == op->do_number) || (op->delay > 0)) {
473 if (SG_LIB_CAT_NOT_READY == res) {
474 if (! check_for_lu_becoming(ptvp))
475 printf("device not ready\n");
476 continue;
477 } else {
478 sg_get_category_sense_str(res, sizeof(b), b, vb);
479 printf("%s\n", b);
480 }
481 resp->reported = true;
482 break;
483 }
484 }
485 }
486 return k;
487 }
488 }
489
490
491 int
main(int argc,char * argv[])492 main(int argc, char * argv[])
493 {
494 bool start_tm_valid = false;
495 int k, res, progress, pr, rem, num_done;
496 int err = 0;
497 int ret = 0;
498 int sg_fd = -1;
499 int64_t elapsed_usecs = 0;
500 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
501 struct timespec start_tm, end_tm;
502 #elif defined(HAVE_GETTIMEOFDAY)
503 struct timeval start_tm, end_tm;
504 #endif
505 struct loop_res_t loop_res;
506 struct loop_res_t * resp = &loop_res;
507 struct sg_pt_base * ptvp = NULL;
508 struct opts_t opts;
509 struct opts_t * op = &opts;
510
511
512 memset(op, 0, sizeof(opts));
513 memset(resp, 0, sizeof(loop_res));
514 op->do_number = 1;
515 res = parse_cmd_line(op, argc, argv);
516 if (res)
517 return res;
518 if (op->do_help) {
519 usage_for(op);
520 return 0;
521 }
522 #ifdef DEBUG
523 pr2serr("In DEBUG mode, ");
524 if (op->verbose_given && op->version_given) {
525 pr2serr("but override: '-vV' given, zero verbose and continue\n");
526 op->verbose_given = false;
527 op->version_given = false;
528 op->verbose = 0;
529 } else if (! op->verbose_given) {
530 pr2serr("set '-vv'\n");
531 op->verbose = 2;
532 } else
533 pr2serr("keep verbose=%d\n", op->verbose);
534 #else
535 if (op->verbose_given && op->version_given)
536 pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
537 #endif
538 if (op->version_given) {
539 pr2serr("Version string: %s\n", version_str);
540 return 0;
541 }
542 if (op->do_progress && (! op->delay_given))
543 op->delay = 30 * 1000; /* progress has 30 second default delay */
544
545 if (NULL == op->device_name) {
546 pr2serr("No DEVICE argument given\n");
547 usage_for(op);
548 return SG_LIB_SYNTAX_ERROR;
549 }
550
551 if ((sg_fd = sg_cmds_open_device(op->device_name, true /* ro */,
552 op->verbose)) < 0) {
553 pr2serr("%s: error opening file: %s: %s\n", __func__,
554 op->device_name, safe_strerror(-sg_fd));
555 ret = sg_convert_errno(-sg_fd);
556 goto fini;
557 }
558 ptvp = construct_scsi_pt_obj_with_fd(sg_fd, op->verbose);
559 if ((NULL == ptvp) || ((err = get_scsi_pt_os_err(ptvp)))) {
560 pr2serr("%s: unable to construct pt object\n", __func__);
561 ret = sg_convert_errno(err ? err : ENOMEM);
562 goto fini;
563 }
564 if (op->do_progress) {
565 for (k = 0; k < op->do_number; ++k) {
566 if (op->delay > 0) {
567 if (op->delay_given)
568 wait_millisecs(op->delay);
569 else if (k > 0)
570 wait_millisecs(op->delay);
571 }
572 progress = -1;
573 res = sg_ll_test_unit_ready_progress_pt(ptvp, k, &progress,
574 (1 == op->do_number), op->verbose);
575 if (progress < 0) {
576 ret = res;
577 break;
578 } else {
579 pr = (progress * 100) / 65536;
580 rem = ((progress * 100) % 65536) / 656;
581 printf("Progress indication: %d.%02d%% done\n", pr, rem);
582 }
583 }
584 if (op->do_number > 1)
585 printf("Completed %d Test Unit Ready commands\n",
586 ((k < op->do_number) ? k + 1 : k));
587 } else { /* --progress not given */
588 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
589 if (op->do_time) {
590 start_tm.tv_sec = 0;
591 start_tm.tv_nsec = 0;
592 if (0 == clock_gettime(CLOCK_MONOTONIC, &start_tm))
593 start_tm_valid = true;
594 else
595 perror("clock_gettime(CLOCK_MONOTONIC)\n");
596 }
597 #elif defined(HAVE_GETTIMEOFDAY)
598 if (op->do_time) {
599 start_tm.tv_sec = 0;
600 start_tm.tv_usec = 0;
601 gettimeofday(&start_tm, NULL);
602 start_tm_valid = true;
603 }
604 #else
605 start_tm_valid = false;
606 #endif
607
608 num_done = loop_turs(ptvp, resp, op);
609
610 if (op->do_time && start_tm_valid) {
611 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
612 if (start_tm.tv_sec || start_tm.tv_nsec) {
613
614 res = clock_gettime(CLOCK_MONOTONIC, &end_tm);
615 if (res < 0) {
616 err = errno;
617 perror("clock_gettime");
618 if (EINVAL == err)
619 pr2serr("clock_gettime(CLOCK_MONOTONIC) not "
620 "supported\n");
621 }
622 elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
623 /* Note: (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
624 elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
625 }
626 #elif defined(HAVE_GETTIMEOFDAY)
627 if (start_tm.tv_sec || start_tm.tv_usec) {
628 gettimeofday(&end_tm, NULL);
629 elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
630 elapsed_usecs += (end_tm.tv_usec - start_tm.tv_usec);
631 }
632 #endif
633 if (elapsed_usecs > 0) {
634 int64_t nom = num_done;
635
636 printf("time to perform commands was %u.%06u secs",
637 (unsigned)(elapsed_usecs / 1000000),
638 (unsigned)(elapsed_usecs % 1000000));
639 nom *= 1000000; /* scale for integer division */
640 printf("; %d operations/sec\n", (int)(nom / elapsed_usecs));
641 } else
642 printf("Recorded 0 or less elapsed microseconds ??\n");
643 }
644 if (((op->do_number > 1) || (resp->num_errs > 0)) &&
645 (! resp->reported))
646 printf("Completed %d Test Unit Ready commands with %d errors\n",
647 op->do_number, resp->num_errs);
648 if (1 == op->do_number)
649 ret = resp->ret;
650 }
651 fini:
652 if (ptvp)
653 destruct_scsi_pt_obj(ptvp);
654 if (sg_fd >= 0)
655 sg_cmds_close_device(sg_fd);
656 return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
657 }
658