aboutsummaryrefslogtreecommitdiff
path: root/src/sg_readcap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_readcap.c')
-rw-r--r--src/sg_readcap.c682
1 files changed, 682 insertions, 0 deletions
diff --git a/src/sg_readcap.c b/src/sg_readcap.c
new file mode 100644
index 00000000..571f92e3
--- /dev/null
+++ b/src/sg_readcap.c
@@ -0,0 +1,682 @@
+/* This code is does a SCSI READ CAPACITY command on the given device
+ * and outputs the result.
+ *
+ * Copyright (C) 1999 - 2020 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program was originally written with Linux 2.4 kernel series.
+ * It now builds for the Linux 2.6, 3 and 4 kernel series and various other
+ * operating systems.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "4.05 20200122";
+
+#define ME "sg_readcap: "
+
+#define RCAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+static struct option long_options[] = {
+ {"brief", no_argument, 0, 'b'},
+ {"help", no_argument, 0, 'h'},
+ {"hex", no_argument, 0, 'H'},
+ {"lba", required_argument, 0, 'L'},
+ {"long", no_argument, 0, 'l'},
+ {"16", no_argument, 0, 'l'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"pmi", no_argument, 0, 'p'},
+ {"raw", no_argument, 0, 'r'},
+ {"readonly", no_argument, 0, 'R'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {"zbc", no_argument, 0, 'z'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_brief;
+ bool do_long;
+ bool do_pmi;
+ bool do_raw;
+ bool o_readonly;
+ bool do_zbc;
+ bool opt_new;
+ bool verbose_given;
+ bool version_given;
+ int do_help;
+ int do_hex;
+ int do_lba;
+ int verbose;
+ uint64_t llba;
+ const char * device_name;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_readcap [--16] [--brief] [--help] [--hex] "
+ "[--lba=LBA] [--long]\n"
+ " [--pmi] [--raw] [--readonly] [--verbose] "
+ "[--version]\n"
+ " [--zbc] DEVICE\n"
+ " where:\n"
+ " --16 use READ CAPACITY (16) cdb (same as "
+ "--long)\n"
+ " --brief|-b brief, two hex numbers: number of blocks "
+ "and block size\n"
+ " --help|-h print this usage message and exit\n"
+ " --hex|-H output response in hexadecimal to stdout\n"
+ " --lba=LBA|-L LBA yields the last block prior to (head "
+ "movement) delay\n"
+ " after LBA [in decimal (def: 0) "
+ "valid with '--pmi']\n"
+ " --long|-l use READ CAPACITY (16) cdb (def: use "
+ "10 byte cdb)\n"
+ " --pmi|-p partial medium indicator (without this "
+ "option shows\n"
+ " total disk capacity) [made obsolete in "
+ "sbc3r26]\n"
+ " --raw|-r output response in binary to stdout\n"
+ " --readonly|-R open DEVICE read-only (def: RCAP(16) "
+ "read-write)\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-V print version string and exit\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --zbc|-z show rc_basis ZBC field (implies --16)\n\n"
+ "Perform a SCSI READ CAPACITY (10 or 16) command\n");
+}
+
+static void
+usage_old()
+{
+ pr2serr("Usage: sg_readcap [-16] [-b] [-h] [-H] [-lba=LBA] "
+ "[-pmi] [-r] [-R]\n"
+ " [-v] [-V] [-z] DEVICE\n"
+ " where:\n"
+ " -16 use READ CAPACITY (16) cdb (def: use "
+ "10 byte cdb)\n"
+ " -b brief, two hex numbers: number of blocks "
+ "and block size\n"
+ " -h print this usage message and exit\n"
+ " -H output response in hexadecimal to stdout\n"
+ " -lba=LBA yields the last block prior to (head "
+ "movement) delay\n"
+ " after LBA [in hex (def: 0) "
+ "valid with -pmi]\n"
+ " -pmi partial medium indicator (without this option "
+ "shows total\n"
+ " disk capacity)\n"
+ " -r output response in binary to stdout\n"
+ " -R open DEVICE read-only (def: RCAP(16) read-write)\n"
+ " -v increase verbosity\n"
+ " -V print version string and exit\n"
+ " -N|--new use new interface\n"
+ " -z show rc_basis ZBC field (implies -16)\n\n"
+ "Perform a SCSI READ CAPACITY (10 or 16) command\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c;
+ int a_one = 0;
+ int64_t nn;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "16bhHlL:NOprRvVz", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '1':
+ ++a_one;
+ break;
+ case '6':
+ if (a_one)
+ op->do_long = true;
+ break;
+ case 'b':
+ op->do_brief = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'l':
+ op->do_long = true;
+ break;
+ case 'L':
+ nn = sg_get_llnum(optarg);
+ if (-1 == nn) {
+ pr2serr("bad argument to '--lba='\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->llba = nn;
+ /* force READ_CAPACITY16 for large lbas */
+ if (op->llba > 0xfffffffeULL)
+ op->do_long = true;
+ ++op->do_lba;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'p':
+ op->do_pmi = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'z':
+ op->do_zbc = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->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;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num;
+ const char * cp;
+ uint64_t uu;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case '1':
+ if ('6' == *(cp + 1)) {
+ op->do_long = true;
+ ++cp;
+ --plen;
+ } else
+ jmp_out = true;
+ break;
+ case 'b':
+ op->do_brief = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'H':
+ ++op->do_hex;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'p':
+ if (0 == strncmp("pmi", cp, 3)) {
+ op->do_pmi = true;
+ cp += 2;
+ plen -= 2;
+ } else
+ jmp_out = true;
+ break;
+ case 'r':
+ op->do_raw = true;
+ break;
+ case 'R':
+ op->o_readonly = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ case 'z':
+ op->do_zbc = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("lba=", cp, 4)) {
+ num = sscanf(cp + 4, "%" SCNx64 "", &uu);
+ if (1 != num) {
+ printf("Bad value after 'lba=' option\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ /* force READ_CAPACITY16 for large lbas */
+ if (uu > 0xfffffffeULL)
+ op->do_long = true;
+ op->llba = uu;
+ ++op->do_lba;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+static const char *
+rc_basis_str(int rc_basis, char * b, int blen)
+{
+ switch (rc_basis) {
+ case 0:
+ snprintf(b, blen, "last contiguous that's not seq write required");
+ break;
+ case 1:
+ snprintf(b, blen, "last LBA on logical unit");
+ break;
+ default:
+ snprintf(b, blen, "reserved (0x%x)", rc_basis);
+ break;
+ }
+ return b;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool rw_0_flag;
+ int res, prot_en, p_type, lbppbe;
+ int sg_fd = -1;
+ int ret = 0;
+ uint32_t last_blk_addr, block_size;
+ uint64_t llast_blk_addr;
+ uint8_t * resp_buff;
+ uint8_t * free_resp_buff;
+ const int resp_buff_sz = RCAP16_REPLY_LEN;
+ char b[80];
+ struct opts_t opts;
+ struct opts_t * op;
+
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return res;
+ if (op->do_help) {
+ usage_for(op);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (op->do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ if (op->do_zbc) {
+ if (! op->do_long)
+ op->do_long = true;
+ }
+
+ resp_buff = sg_memalign(resp_buff_sz, 0, &free_resp_buff, false);
+ if (NULL == resp_buff) {
+ pr2serr("Unable to allocate %d bytes on heap\n", resp_buff_sz);
+ return sg_convert_errno(ENOMEM);
+ }
+ if ((! op->do_pmi) && (op->llba > 0)) {
+ pr2serr(ME "lba can only be non-zero when '--pmi' is set\n");
+ usage_for(op);
+ ret = SG_LIB_CONTRADICT;
+ goto fini;
+ }
+ if (op->do_long)
+ rw_0_flag = op->o_readonly;
+ else
+ rw_0_flag = true; /* RCAP(10) has opened RO in past, so leave */
+ if ((sg_fd = sg_cmds_open_device(op->device_name, rw_0_flag,
+ op->verbose)) < 0) {
+ pr2serr(ME "error opening file: %s: %s\n", op->device_name,
+ safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+
+ if (! op->do_long) {
+ res = sg_ll_readcap_10(sg_fd, op->do_pmi, (unsigned int)op->llba,
+ resp_buff, RCAP_REPLY_LEN, true,
+ op->verbose);
+ ret = res;
+ if (0 == res) {
+ if (op->do_hex || op->do_raw) {
+ if (op->do_raw)
+ dStrRaw(resp_buff, RCAP_REPLY_LEN);
+ else if (op->do_hex > 2)
+ hex2stdout(resp_buff, RCAP_REPLY_LEN, -1);
+ else
+ hex2stdout(resp_buff, RCAP_REPLY_LEN, 1);
+ goto fini;
+ }
+ last_blk_addr = sg_get_unaligned_be32(resp_buff + 0);
+ if (0xffffffff != last_blk_addr) {
+ block_size = sg_get_unaligned_be32(resp_buff + 4);
+ if (op->do_brief) {
+ printf("0x%" PRIx32 " 0x%" PRIx32 "\n",
+ last_blk_addr + 1, block_size);
+ goto fini;
+ }
+ printf("Read Capacity results:\n");
+ if (op->do_pmi)
+ printf(" PMI mode: given lba=0x%" PRIx64 ", last lba "
+ "before delay=0x%" PRIx32 "\n", op->llba,
+ last_blk_addr);
+ else
+ printf(" Last LBA=%" PRIu32 " (0x%" PRIx32 "), Number "
+ "of logical blocks=%" PRIu32 "\n", last_blk_addr,
+ last_blk_addr, last_blk_addr + 1);
+ printf(" Logical block length=%u bytes\n", block_size);
+ if (! op->do_pmi) {
+ uint64_t total_sz = last_blk_addr + 1;
+ double sz_mb, sz_gb;
+
+ total_sz *= block_size;
+ sz_mb = ((double)(last_blk_addr + 1) * block_size) /
+ (double)(1048576);
+ sz_gb = ((double)(last_blk_addr + 1) * block_size) /
+ (double)(1000000000L);
+ printf("Hence:\n");
+#ifdef SG_LIB_MINGW
+ printf(" Device size: %" PRIu64 " bytes, %g MiB, %g "
+ "GB", total_sz, sz_mb, sz_gb);
+#else
+ printf(" Device size: %" PRIu64 " bytes, %.1f MiB, "
+ "%.2f GB", total_sz, sz_mb, sz_gb);
+#endif
+ if (sz_gb > 2000) {
+#ifdef SG_LIB_MINGW
+ printf(", %g TB", sz_gb / 1000);
+#else
+ printf(", %.2f TB", sz_gb / 1000);
+#endif
+ }
+ printf("\n");
+ }
+ goto fini;
+ } else {
+ printf("READ CAPACITY (10) indicates device capacity too "
+ "large\n now trying 16 byte cdb variant\n");
+ op->do_long = true;
+ }
+ } else if (SG_LIB_CAT_INVALID_OP == res) {
+ op->do_long = true;
+ sg_cmds_close_device(sg_fd);
+ if ((sg_fd = sg_cmds_open_device(op->device_name, op->o_readonly,
+ op->verbose)) < 0) {
+ pr2serr(ME "error re-opening file: %s (rw): %s\n",
+ op->device_name, safe_strerror(-sg_fd));
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ if (op->verbose)
+ pr2serr("READ CAPACITY (10) not supported, trying READ "
+ "CAPACITY (16)\n");
+ } else if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("READ CAPACITY (10) failed: %s\n", b);
+ }
+ }
+ if (op->do_long) {
+ res = sg_ll_readcap_16(sg_fd, op->do_pmi, op->llba, resp_buff,
+ RCAP16_REPLY_LEN, true, op->verbose);
+ ret = res;
+ if (0 == res) {
+ if (op->do_hex || op->do_raw) {
+ if (op->do_raw)
+ dStrRaw(resp_buff, RCAP16_REPLY_LEN);
+ else if (op->do_hex > 2)
+ hex2stdout(resp_buff, RCAP16_REPLY_LEN, -1);
+ else
+ hex2stdout(resp_buff, RCAP16_REPLY_LEN, 1);
+ goto fini;
+ }
+ llast_blk_addr = sg_get_unaligned_be64(resp_buff + 0);
+ block_size = sg_get_unaligned_be32(resp_buff + 8);
+ if (op->do_brief) {
+ printf("0x%" PRIx64 " 0x%" PRIx32 "\n", llast_blk_addr + 1,
+ block_size);
+ goto fini;
+ }
+ prot_en = !!(resp_buff[12] & 0x1);
+ p_type = ((resp_buff[12] >> 1) & 0x7);
+ printf("Read Capacity results:\n");
+ printf(" Protection: prot_en=%d, p_type=%d, p_i_exponent=%d",
+ prot_en, p_type, ((resp_buff[13] >> 4) & 0xf));
+ if (prot_en)
+ printf(" [type %d protection]\n", p_type + 1);
+ else
+ printf("\n");
+ if (op->do_zbc) {
+ int rc_basis = (resp_buff[12] >> 4) & 0x3;
+
+ printf(" ZBC's rc_basis=%d [%s]\n", rc_basis,
+ rc_basis_str(rc_basis, b, sizeof(b)));
+ }
+ printf(" Logical block provisioning: lbpme=%d, lbprz=%d\n",
+ !!(resp_buff[14] & 0x80), !!(resp_buff[14] & 0x40));
+ if (op->do_pmi)
+ printf(" PMI mode: given lba=0x%" PRIx64 ", last lba "
+ "before delay=0x%" PRIx64 "\n", op->llba,
+ llast_blk_addr);
+ else
+ printf(" Last LBA=%" PRIu64 " (0x%" PRIx64 "), Number of "
+ "logical blocks=%" PRIu64 "\n", llast_blk_addr,
+ llast_blk_addr, llast_blk_addr + 1);
+ printf(" Logical block length=%" PRIu32 " bytes\n", block_size);
+ lbppbe = resp_buff[13] & 0xf;
+ printf(" Logical blocks per physical block exponent=%d",
+ lbppbe);
+ if (lbppbe > 0)
+ printf(" [so physical block length=%u bytes]\n",
+ block_size * (1 << lbppbe));
+ else
+ printf("\n");
+ printf(" Lowest aligned LBA=%d\n",
+ ((resp_buff[14] & 0x3f) << 8) + resp_buff[15]);
+ if (! op->do_pmi) {
+ uint64_t total_sz = llast_blk_addr + 1;
+ double sz_mb, sz_gb;
+
+ total_sz *= block_size;
+ sz_mb = ((double)(llast_blk_addr + 1) * block_size) /
+ (double)(1048576);
+ sz_gb = ((double)(llast_blk_addr + 1) * block_size) /
+ (double)(1000000000L);
+ printf("Hence:\n");
+#ifdef SG_LIB_MINGW
+ printf(" Device size: %" PRIu64 " bytes, %g MiB, %g GB",
+ total_sz, sz_mb, sz_gb);
+#else
+ printf(" Device size: %" PRIu64 " bytes, %.1f MiB, %.2f "
+ "GB", total_sz, sz_mb, sz_gb);
+#endif
+ if (sz_gb > 2000) {
+#ifdef SG_LIB_MINGW
+ printf(", %g TB", sz_gb / 1000);
+#else
+ printf(", %.2f TB", sz_gb / 1000);
+#endif
+ }
+ printf("\n");
+ }
+ goto fini;
+ } else if (SG_LIB_CAT_ILLEGAL_REQ == res)
+ pr2serr("bad field in READ CAPACITY (16) cdb including "
+ "unsupported service action\n");
+ else if (res) {
+ sg_get_category_sense_str(res, sizeof(b), b, op->verbose);
+ pr2serr("READ CAPACITY (16) failed: %s\n", b);
+ }
+ }
+ if (op->do_brief)
+ printf("0x0 0x0\n");
+fini:
+ if (free_resp_buff)
+ free(free_resp_buff);
+ 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 == op->verbose) {
+ if (! sg_if_can2stderr("sg_readcap failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}