xref: /aosp_15_r20/external/sg3_utils/src/sg_seek.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2018-2020 Douglas Gilbert.
3  * All rights reserved.
4  * Use of this source code is governed by a BSD-style
5  * license that can be found in the BSD_LICENSE file.
6  *
7  * SPDX-License-Identifier: BSD-2-Clause
8  */
9 
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdbool.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <errno.h>
18 #include <getopt.h>
19 #define __STDC_FORMAT_MACROS 1
20 #include <inttypes.h>
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
27 #include <time.h>
28 #elif defined(HAVE_GETTIMEOFDAY)
29 #include <time.h>
30 #include <sys/time.h>
31 #endif
32 
33 #include "sg_lib.h"
34 #include "sg_lib_data.h"
35 #include "sg_cmds_basic.h"
36 #include "sg_cmds_extra.h"
37 #include "sg_unaligned.h"
38 #include "sg_pr2serr.h"
39 
40 /*
41  * This program issues one or more SCSI SEEK(10), PRE-FETCH(10) or
42  * PRE-FETCH(16) commands. Both PRE-FETCH commands are current and appear
43  * in the most recent SBC-4 draft (sbc4r15.pdf at time of writing) while
44  * SEEK(10) has been obsolete since SBC-2 (2004). Currently more hard disks
45  * and SSDs support SEEK(10) than PRE-FETCH. It is even unclear what
46  * SEEK(10) means (defined in SBC-1 as moving the hard disk heads to the
47  * track containing the given LBA) for a SSD. But if the manufacturers'
48  * support it, then it must have a use, presumably to speed the next access
49  * to that LBA ...
50  */
51 
52 static const char * version_str = "1.08 20200115";
53 
54 #define BACKGROUND_CONTROL_SA 0x15
55 
56 #define CMD_ABORT_TIMEOUT  60      /* 60 seconds */
57 
58 
59 static struct option long_options[] = {
60         {"10", no_argument, 0, 'T'},
61         {"count", required_argument, 0, 'c'},
62         {"grpnum", required_argument, 0, 'g'},
63         {"help", no_argument, 0, 'h'},
64         {"immed", no_argument, 0, 'i'},
65         {"lba", required_argument, 0, 'l'},
66         {"num-blocks", required_argument, 0, 'n'},
67         {"num_blocks", required_argument, 0, 'n'},
68         {"pre-fetch", no_argument, 0, 'p'},
69         {"pre_fetch", no_argument, 0, 'p'},
70         {"readonly", no_argument, 0, 'r'},
71         {"skip", required_argument, 0, 's'},
72         {"time", required_argument, 0, 't'},
73         {"verbose", no_argument, 0, 'v'},
74         {"version", no_argument, 0, 'V'},
75         {"wrap-offset", required_argument, 0, 'w'},
76         {"wrap_offset", required_argument, 0, 'w'},
77         {0, 0, 0, 0},
78 };
79 
80 
81 static void
usage()82 usage()
83 {
84     pr2serr("Usage: "
85             "sg_seek  [--10] [--count=NC] [--grpnum=GN] [--help] [--immed]\n"
86             "                [--lba=LBA] [--num-blocks=NUM] [--pre-fetch] "
87             "[--readonly]\n"
88             "                [--skip=SB] [--time] [--verbose] [--version]\n"
89             "                [--wrap-offset=WO] DEVICE\n");
90     pr2serr("  where:\n"
91             "    --10|-T             do PRE-FETCH(10) command (def: "
92             "SEEK(10), or\n"
93             "                        PRE-FETCH(16) if --pre-fetch also "
94             "given)\n"
95             "    --count=NC|-c NC    NC is number of commands to execute "
96             "(def: 1)\n"
97             "    --grpnum=GN|-g GN    GN is group number to place in "
98             "PRE-FETCH\n"
99             "                         cdb; 0 to 63 (def: 0)\n"
100             "    --help|-h           print out usage message\n"
101             "    --immed|-i          set IMMED bit in PRE-FETCH command\n"
102             "    --lba=LBA|-l LBA    starting Logical Block Address (LBA) "
103             "(def: 0)\n"
104             "    --num-blocks=NUM|-n NUM    number of blocks to cache (for "
105             "PRE-FETCH)\n"
106             "                               (def: 1). Ignored by "
107             "SEEK(10)\n");
108     pr2serr("    --pre-fetch|-p     do PRE-FETCH command, 16 byte variant if "
109             "--10 not\n"
110             "                       given (def: do SEEK(10))\n"
111             "    --readonly|-r      open DEVICE read-only (if supported)\n"
112             "    --skip=SB|-s SB    when NC>1 skip SB blocks to next LBA "
113             "(def: 1)\n"
114             "    --time|-t          time the command(s) and if NC>1 show "
115             "usecs/command\n"
116             "                       (def: don't time)\n"
117             "    --verbose|-v       increase verbosity\n"
118             "    --version|-V       print version string and exit\n"
119             "    --wrap-offset=WO|-w WO    if SB>0 and WO>0 then if "
120             "LBAn>LBA+WO\n"
121             "                       then reset LBAn back to LBA (def: 0)\n\n"
122             "Performs SCSI SEEK(10), PRE-FETCH(10) or PRE-FETCH(16) "
123             "command(s).If no\noptions are given does one SEEK(10) command "
124             "with an LBA of 0 . If NC>1\nthen a tally is kept of successes, "
125             "'condition-met's and errors that is\nprinted on completion. "
126             "'condition-met' is from PRE-FETCH when NUM blocks\nfit in "
127             "the DEVICE's cache.\n"
128            );
129 }
130 
131 
132 int
main(int argc,char * argv[])133 main(int argc, char * argv[])
134 {
135     bool cdb10 = false;
136     bool count_given = false;
137     bool do_time = false;
138     bool immed = false;
139     bool prefetch = false;
140     bool readonly = false;
141     bool start_tm_valid = false;
142     bool verbose_given = false;
143     bool version_given = false;
144     int res, c;
145     int sg_fd = -1;
146     int first_err = 0;
147     int last_err = 0;
148     int ret = 0;
149     int verbose = 0;
150     uint32_t count = 1;
151     int32_t l;
152     uint32_t grpnum = 0;
153     uint32_t k;
154     uint32_t num_cond_met = 0;
155     uint32_t num_err = 0;
156     uint32_t num_good = 0;
157     uint32_t numblocks = 1;
158     uint32_t skip = 1;
159     uint32_t wrap_offs = 0;
160     int64_t ll;
161     int64_t elapsed_usecs = 0;
162     uint64_t lba = 0;
163     uint64_t lba_n;
164     const char * device_name = NULL;
165     const char * cdb_name = NULL;
166     char b[64];
167 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
168     struct timespec start_tm, end_tm;
169 #elif defined(HAVE_GETTIMEOFDAY)
170     struct timeval start_tm, end_tm;
171 #endif
172 
173     while (1) {
174         int option_index = 0;
175 
176         c = getopt_long(argc, argv, "c:g:hil:n:prs:tTvVw:", long_options,
177                         &option_index);
178         if (c == -1)
179             break;
180 
181         switch (c) {
182         case 'c':
183             l = sg_get_num(optarg);
184             if (l < 0) {
185                 pr2serr("--count= unable to decode argument, want 0 or "
186                         "higher\n");
187                 return SG_LIB_SYNTAX_ERROR;
188             }
189             count = (uint32_t)l;
190             count_given = true;
191             break;
192         case 'g':
193             l = sg_get_num(optarg);
194             if ((l > 63) || (l < 0)) {
195                 pr2serr("--grpnum= expect argument in range 0 to 63\n");
196                 return SG_LIB_SYNTAX_ERROR;
197             }
198             grpnum = (uint32_t)l;
199             break;
200         case 'h':
201         case '?':
202             usage();
203             return 0;
204         case 'i':
205             immed = true;
206             break;
207         case 'l':
208             ll = sg_get_llnum(optarg);
209             if (-1 == ll) {
210                 pr2serr("--lba= unable to decode argument\n");
211                 return SG_LIB_SYNTAX_ERROR;
212             }
213             lba = (uint64_t)ll;
214             break;
215         case 'n':
216             l = sg_get_num(optarg);
217             if (-1 == l) {
218                 pr2serr("--num= unable to decode argument\n");
219                 return SG_LIB_SYNTAX_ERROR;
220             }
221             numblocks = (uint32_t)l;
222             break;
223         case 'p':
224             prefetch = true;
225             break;
226         case 'r':
227             readonly = true;
228             break;
229         case 's':
230             l = sg_get_num(optarg);
231             if (-1 == l) {
232                 pr2serr("--skip= unable to decode argument\n");
233                 return SG_LIB_SYNTAX_ERROR;
234             }
235             skip = (uint32_t)l;
236             break;
237         case 't':
238             do_time = true;
239             break;
240         case 'T':
241             cdb10 = true;
242             break;
243         case 'v':
244             verbose_given = true;
245             ++verbose;
246             break;
247         case 'V':
248             version_given = true;
249             break;
250         case 'w':
251             l = sg_get_num(optarg);
252             if (-1 == l) {
253                 pr2serr("--wrap-offset= unable to decode argument\n");
254                 return SG_LIB_SYNTAX_ERROR;
255             }
256             wrap_offs = (uint32_t)l;
257             break;
258         default:
259             pr2serr("unrecognised option code 0x%x ??\n", c);
260             usage();
261             return SG_LIB_SYNTAX_ERROR;
262         }
263     }
264     if (optind < argc) {
265         if (NULL == device_name) {
266             device_name = argv[optind];
267             ++optind;
268         }
269         if (optind < argc) {
270             for (; optind < argc; ++optind)
271                 pr2serr("Unexpected extra argument: %s\n",
272                         argv[optind]);
273             usage();
274             return SG_LIB_SYNTAX_ERROR;
275         }
276     }
277 
278 #ifdef DEBUG
279     pr2serr("In DEBUG mode, ");
280     if (verbose_given && version_given) {
281         pr2serr("but override: '-vV' given, zero verbose and continue\n");
282         verbose_given = false;
283         version_given = false;
284         verbose = 0;
285     } else if (! verbose_given) {
286         pr2serr("set '-vv'\n");
287         verbose = 2;
288     } else
289         pr2serr("keep verbose=%d\n", verbose);
290 #else
291     if (verbose_given && version_given)
292         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
293 #endif
294     if (version_given) {
295         pr2serr("version: %s\n", version_str);
296         return 0;
297     }
298 
299     if (NULL == device_name) {
300         pr2serr("Missing device name!\n\n");
301         usage();
302         return SG_LIB_SYNTAX_ERROR;
303     }
304 
305     if (prefetch) {
306         if (cdb10)
307             cdb_name = "Pre-fetch(10)";
308         else
309             cdb_name = "Pre-fetch(16)";
310     } else
311         cdb_name = "Seek(10)";
312 
313     sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
314     if (sg_fd < 0) {
315         if (verbose)
316             pr2serr("open error: %s: %s %s\n", device_name, cdb_name,
317                     safe_strerror(-sg_fd));
318         ret = sg_convert_errno(-sg_fd);
319         goto fini;
320     }
321 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
322     if (do_time) {
323         start_tm.tv_sec = 0;
324         start_tm.tv_nsec = 0;
325         if (0 == clock_gettime(CLOCK_MONOTONIC, &start_tm))
326             start_tm_valid = true;
327         else
328             perror("clock_gettime(CLOCK_MONOTONIC)\n");
329     }
330 #elif defined(HAVE_GETTIMEOFDAY)
331     if (do_time) {
332         start_tm.tv_sec = 0;
333         start_tm.tv_usec = 0;
334         gettimeofday(&start_tm, NULL);
335         start_tm_valid = true;
336     }
337 #else
338     start_tm_valid = false;
339 #endif
340 
341     for (k = 0, lba_n = lba; k < count; ++k, lba_n += skip) {
342         if (wrap_offs && (lba_n > lba) && ((lba_n - lba) > wrap_offs))
343             lba_n = lba;
344         res = sg_ll_pre_fetch_x(sg_fd, ! prefetch, ! cdb10, immed, lba_n,
345                                 numblocks, grpnum, 0, (verbose > 0), verbose);
346         ret = res;      /* last command executed sets exit status */
347         if (SG_LIB_CAT_CONDITION_MET == res)
348             ++num_cond_met;
349         else if (res) {
350             ++num_err;
351             if (0 == first_err)
352                 first_err = res;
353             last_err = res;
354         } else
355             ++num_good;
356     }
357 
358 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
359     if ((count > 0) && start_tm_valid &&
360         (start_tm.tv_sec || start_tm.tv_nsec)) {
361         int err;
362 
363         res = clock_gettime(CLOCK_MONOTONIC, &end_tm);
364         if (res < 0) {
365             err = errno;
366             perror("clock_gettime");
367             if (EINVAL == err)
368                 pr2serr("clock_gettime(CLOCK_MONOTONIC) not supported\n");
369         }
370         elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
371         /* Note that (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
372         elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
373     }
374 #elif defined(HAVE_GETTIMEOFDAY)
375     if ((count > 0) && start_tm_valid &&
376         (start_tm.tv_sec || start_tm.tv_usec)) {
377         gettimeofday(&end_tm, NULL);
378         elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
379         elapsed_usecs += (end_tm.tv_usec - start_tm.tv_usec);
380     }
381 #endif
382 
383     if (elapsed_usecs > 0) {
384         if (elapsed_usecs > 1000000)
385             snprintf(b, sizeof(b), " (over %d seconds)",
386                     (int)elapsed_usecs / 1000000);
387         else
388             b[0] = '\0';
389         printf("Elapsed time: %" PRId64 " microseconds%s, per command time: "
390                "%" PRId64 "\n", elapsed_usecs, b, elapsed_usecs / count);
391     }
392 
393     if (count_given && verbose_given)
394         printf("Command count=%u, number of condition_mets=%u, number of "
395                "goods=%u\n", count, num_cond_met, num_good);
396     if (first_err) {
397         bool printed;
398 
399         printf(" number of errors=%d\n", num_err);
400         printf("    first error");
401         printed = sg_if_can2stdout(": ", first_err);
402         if (! printed)
403             printf(" code: %d\n", first_err);
404         if (num_err > 1) {
405             printf("    last error");
406             printed = sg_if_can2stdout(": ", last_err);
407             if (! printed)
408                 printf(" code: %d\n", last_err);
409         }
410     }
411 fini:
412     if (sg_fd >= 0) {
413         res = sg_cmds_close_device(sg_fd);
414         if (res < 0) {
415             pr2serr("close error: %s\n", safe_strerror(-res));
416             if (0 == ret)
417                 ret = sg_convert_errno(-res);
418         }
419     }
420     if (0 == verbose) {
421         const char * e_str = (SG_LIB_CAT_CONDITION_MET == ret) ?
422                              "sg_seek: " : "sg_seek: failed";
423 
424         if (! sg_if_can2stderr(e_str, ret))
425             pr2serr("Some error occurred, try again with '-v' "
426                     "or '-vv' for more information\n");
427     }
428     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
429 }
430