aboutsummaryrefslogtreecommitdiff
path: root/src/sg_seek.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_seek.c')
-rw-r--r--src/sg_seek.c429
1 files changed, 429 insertions, 0 deletions
diff --git a/src/sg_seek.c b/src/sg_seek.c
new file mode 100644
index 00000000..fb647639
--- /dev/null
+++ b/src/sg_seek.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 2018-2020 Douglas Gilbert.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+#include <time.h>
+#elif defined(HAVE_GETTIMEOFDAY)
+#include <time.h>
+#include <sys/time.h>
+#endif
+
+#include "sg_lib.h"
+#include "sg_lib_data.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/*
+ * This program issues one or more SCSI SEEK(10), PRE-FETCH(10) or
+ * PRE-FETCH(16) commands. Both PRE-FETCH commands are current and appear
+ * in the most recent SBC-4 draft (sbc4r15.pdf at time of writing) while
+ * SEEK(10) has been obsolete since SBC-2 (2004). Currently more hard disks
+ * and SSDs support SEEK(10) than PRE-FETCH. It is even unclear what
+ * SEEK(10) means (defined in SBC-1 as moving the hard disk heads to the
+ * track containing the given LBA) for a SSD. But if the manufacturers'
+ * support it, then it must have a use, presumably to speed the next access
+ * to that LBA ...
+ */
+
+static const char * version_str = "1.08 20200115";
+
+#define BACKGROUND_CONTROL_SA 0x15
+
+#define CMD_ABORT_TIMEOUT 60 /* 60 seconds */
+
+
+static struct option long_options[] = {
+ {"10", no_argument, 0, 'T'},
+ {"count", required_argument, 0, 'c'},
+ {"grpnum", required_argument, 0, 'g'},
+ {"help", no_argument, 0, 'h'},
+ {"immed", no_argument, 0, 'i'},
+ {"lba", required_argument, 0, 'l'},
+ {"num-blocks", required_argument, 0, 'n'},
+ {"num_blocks", required_argument, 0, 'n'},
+ {"pre-fetch", no_argument, 0, 'p'},
+ {"pre_fetch", no_argument, 0, 'p'},
+ {"readonly", no_argument, 0, 'r'},
+ {"skip", required_argument, 0, 's'},
+ {"time", required_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"wrap-offset", required_argument, 0, 'w'},
+ {"wrap_offset", required_argument, 0, 'w'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: "
+ "sg_seek [--10] [--count=NC] [--grpnum=GN] [--help] [--immed]\n"
+ " [--lba=LBA] [--num-blocks=NUM] [--pre-fetch] "
+ "[--readonly]\n"
+ " [--skip=SB] [--time] [--verbose] [--version]\n"
+ " [--wrap-offset=WO] DEVICE\n");
+ pr2serr(" where:\n"
+ " --10|-T do PRE-FETCH(10) command (def: "
+ "SEEK(10), or\n"
+ " PRE-FETCH(16) if --pre-fetch also "
+ "given)\n"
+ " --count=NC|-c NC NC is number of commands to execute "
+ "(def: 1)\n"
+ " --grpnum=GN|-g GN GN is group number to place in "
+ "PRE-FETCH\n"
+ " cdb; 0 to 63 (def: 0)\n"
+ " --help|-h print out usage message\n"
+ " --immed|-i set IMMED bit in PRE-FETCH command\n"
+ " --lba=LBA|-l LBA starting Logical Block Address (LBA) "
+ "(def: 0)\n"
+ " --num-blocks=NUM|-n NUM number of blocks to cache (for "
+ "PRE-FETCH)\n"
+ " (def: 1). Ignored by "
+ "SEEK(10)\n");
+ pr2serr(" --pre-fetch|-p do PRE-FETCH command, 16 byte variant if "
+ "--10 not\n"
+ " given (def: do SEEK(10))\n"
+ " --readonly|-r open DEVICE read-only (if supported)\n"
+ " --skip=SB|-s SB when NC>1 skip SB blocks to next LBA "
+ "(def: 1)\n"
+ " --time|-t time the command(s) and if NC>1 show "
+ "usecs/command\n"
+ " (def: don't time)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --wrap-offset=WO|-w WO if SB>0 and WO>0 then if "
+ "LBAn>LBA+WO\n"
+ " then reset LBAn back to LBA (def: 0)\n\n"
+ "Performs SCSI SEEK(10), PRE-FETCH(10) or PRE-FETCH(16) "
+ "command(s).If no\noptions are given does one SEEK(10) command "
+ "with an LBA of 0 . If NC>1\nthen a tally is kept of successes, "
+ "'condition-met's and errors that is\nprinted on completion. "
+ "'condition-met' is from PRE-FETCH when NUM blocks\nfit in "
+ "the DEVICE's cache.\n"
+ );
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool cdb10 = false;
+ bool count_given = false;
+ bool do_time = false;
+ bool immed = false;
+ bool prefetch = false;
+ bool readonly = false;
+ bool start_tm_valid = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ int res, c;
+ int sg_fd = -1;
+ int first_err = 0;
+ int last_err = 0;
+ int ret = 0;
+ int verbose = 0;
+ uint32_t count = 1;
+ int32_t l;
+ uint32_t grpnum = 0;
+ uint32_t k;
+ uint32_t num_cond_met = 0;
+ uint32_t num_err = 0;
+ uint32_t num_good = 0;
+ uint32_t numblocks = 1;
+ uint32_t skip = 1;
+ uint32_t wrap_offs = 0;
+ int64_t ll;
+ int64_t elapsed_usecs = 0;
+ uint64_t lba = 0;
+ uint64_t lba_n;
+ const char * device_name = NULL;
+ const char * cdb_name = NULL;
+ char b[64];
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ struct timespec start_tm, end_tm;
+#elif defined(HAVE_GETTIMEOFDAY)
+ struct timeval start_tm, end_tm;
+#endif
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:g:hil:n:prs:tTvVw:", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ l = sg_get_num(optarg);
+ if (l < 0) {
+ pr2serr("--count= unable to decode argument, want 0 or "
+ "higher\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ count = (uint32_t)l;
+ count_given = true;
+ break;
+ case 'g':
+ l = sg_get_num(optarg);
+ if ((l > 63) || (l < 0)) {
+ pr2serr("--grpnum= expect argument in range 0 to 63\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ grpnum = (uint32_t)l;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'i':
+ immed = true;
+ break;
+ case 'l':
+ ll = sg_get_llnum(optarg);
+ if (-1 == ll) {
+ pr2serr("--lba= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ lba = (uint64_t)ll;
+ break;
+ case 'n':
+ l = sg_get_num(optarg);
+ if (-1 == l) {
+ pr2serr("--num= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ numblocks = (uint32_t)l;
+ break;
+ case 'p':
+ prefetch = true;
+ break;
+ case 'r':
+ readonly = true;
+ break;
+ case 's':
+ l = sg_get_num(optarg);
+ if (-1 == l) {
+ pr2serr("--skip= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ skip = (uint32_t)l;
+ break;
+ case 't':
+ do_time = true;
+ break;
+ case 'T':
+ cdb10 = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ case 'w':
+ l = sg_get_num(optarg);
+ if (-1 == l) {
+ pr2serr("--wrap-offset= unable to decode argument\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ wrap_offs = (uint32_t)l;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("version: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (prefetch) {
+ if (cdb10)
+ cdb_name = "Pre-fetch(10)";
+ else
+ cdb_name = "Pre-fetch(16)";
+ } else
+ cdb_name = "Seek(10)";
+
+ sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
+ if (sg_fd < 0) {
+ if (verbose)
+ pr2serr("open error: %s: %s %s\n", device_name, cdb_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_nsec = 0;
+ if (0 == clock_gettime(CLOCK_MONOTONIC, &start_tm))
+ start_tm_valid = true;
+ else
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
+ }
+#else
+ start_tm_valid = false;
+#endif
+
+ for (k = 0, lba_n = lba; k < count; ++k, lba_n += skip) {
+ if (wrap_offs && (lba_n > lba) && ((lba_n - lba) > wrap_offs))
+ lba_n = lba;
+ res = sg_ll_pre_fetch_x(sg_fd, ! prefetch, ! cdb10, immed, lba_n,
+ numblocks, grpnum, 0, (verbose > 0), verbose);
+ ret = res; /* last command executed sets exit status */
+ if (SG_LIB_CAT_CONDITION_MET == res)
+ ++num_cond_met;
+ else if (res) {
+ ++num_err;
+ if (0 == first_err)
+ first_err = res;
+ last_err = res;
+ } else
+ ++num_good;
+ }
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if ((count > 0) && start_tm_valid &&
+ (start_tm.tv_sec || start_tm.tv_nsec)) {
+ int err;
+
+ res = clock_gettime(CLOCK_MONOTONIC, &end_tm);
+ if (res < 0) {
+ err = errno;
+ perror("clock_gettime");
+ if (EINVAL == err)
+ pr2serr("clock_gettime(CLOCK_MONOTONIC) not supported\n");
+ }
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ /* Note that (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
+ elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
+ if ((count > 0) && start_tm_valid &&
+ (start_tm.tv_sec || start_tm.tv_usec)) {
+ gettimeofday(&end_tm, NULL);
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ elapsed_usecs += (end_tm.tv_usec - start_tm.tv_usec);
+ }
+#endif
+
+ if (elapsed_usecs > 0) {
+ if (elapsed_usecs > 1000000)
+ snprintf(b, sizeof(b), " (over %d seconds)",
+ (int)elapsed_usecs / 1000000);
+ else
+ b[0] = '\0';
+ printf("Elapsed time: %" PRId64 " microseconds%s, per command time: "
+ "%" PRId64 "\n", elapsed_usecs, b, elapsed_usecs / count);
+ }
+
+ if (count_given && verbose_given)
+ printf("Command count=%u, number of condition_mets=%u, number of "
+ "goods=%u\n", count, num_cond_met, num_good);
+ if (first_err) {
+ bool printed;
+
+ printf(" number of errors=%d\n", num_err);
+ printf(" first error");
+ printed = sg_if_can2stdout(": ", first_err);
+ if (! printed)
+ printf(" code: %d\n", first_err);
+ if (num_err > 1) {
+ printf(" last error");
+ printed = sg_if_can2stdout(": ", last_err);
+ if (! printed)
+ printf(" code: %d\n", last_err);
+ }
+ }
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ const char * e_str = (SG_LIB_CAT_CONDITION_MET == ret) ?
+ "sg_seek: " : "sg_seek: failed";
+
+ if (! sg_if_can2stderr(e_str, ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}