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