blob: e05f791ea612d403d5dcf065c8bd179fe9318caa [file] [log] [blame]
/* This code is does a SCSI READ CAPACITY command on the given device
and outputs the result.
* Copyright (C) 1999 - 2015 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.
This program was originally written with Linux 2.4 kernel series.
It now builds for the Linux 2.6 and 3 kernel series and various other
operating systems.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.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 = "3.95 20151219";
#define ME "sg_readcap: "
#define RCAP_REPLY_LEN 8
#define RCAP16_REPLY_LEN 32
static struct option long_options[] = {
{"brief", 0, 0, 'b'},
{"help", 0, 0, 'h'},
{"hex", 0, 0, 'H'},
{"lba", 1, 0, 'L'},
{"long", 0, 0, 'l'},
{"16", 0, 0, 'l'},
{"new", 0, 0, 'N'},
{"old", 0, 0, 'O'},
{"pmi", 0, 0, 'p'},
{"raw", 0, 0, 'r'},
{"readonly", 0, 0, 'R'},
{"verbose", 0, 0, 'v'},
{"version", 0, 0, 'V'},
{"zbc", 0, 0, 'z'},
{0, 0, 0, 0},
};
struct opts_t {
int do_brief;
int do_help;
int do_hex;
int do_lba;
int do_long;
int do_pmi;
int do_raw;
int o_readonly;
int do_verbose;
int do_version;
int do_zbc;
uint64_t llba;
const char * device_name;
int opt_new;
};
static void
usage()
{
pr2serr("Usage: sg_readcap [--brief] [--help] [--hex] [--lba=LBA] "
"[--long] [--16]\n"
" [--pmi] [--raw] [--readonly] [--verbose] "
"[--version]\n"
" [--zbc] DEVICE\n"
" where:\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"
" --16 use READ CAPACITY (16) cdb (same as "
"--long)\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"
" --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"
" -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
process_cl_new(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;
break;
case 'b':
++op->do_brief;
break;
case 'h':
case '?':
++op->do_help;
break;
case 'H':
++op->do_hex;
break;
case 'l':
++op->do_long;
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;
++op->do_lba;
break;
case 'N':
break; /* ignore */
case 'O':
op->opt_new = 0;
return 0;
case 'p':
++op->do_pmi;
break;
case 'r':
++op->do_raw;
break;
case 'R':
++op->o_readonly;
break;
case 'v':
++op->do_verbose;
break;
case 'V':
++op->do_version;
break;
case 'z':
++op->do_zbc;
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
process_cl_old(struct opts_t * op, int argc, char * argv[])
{
int k, jmp_out, 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 = 0; plen > 0; --plen, ++cp) {
switch (*cp) {
case '1':
if ('6' == *(cp + 1)) {
++op->do_long;
++cp;
--plen;
} else
jmp_out = 1;
break;
case 'b':
++op->do_brief;
break;
case 'h':
case '?':
++op->do_help;
break;
case 'H':
++op->do_hex;
break;
case 'N':
op->opt_new = 1;
return 0;
case 'O':
break;
case 'p':
if (0 == strncmp("pmi", cp, 3)) {
++op->do_pmi;
cp += 2;
plen -= 2;
} else
jmp_out = 1;
break;
case 'r':
++op->do_raw;
break;
case 'R':
++op->o_readonly;
break;
case 'v':
++op->do_verbose;
break;
case 'V':
++op->do_version;
break;
case 'z':
++op->do_zbc;
break;
default:
jmp_out = 1;
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;
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
process_cl(struct opts_t * op, int argc, char * argv[])
{
int res;
char * cp;
cp = getenv("SG3_UTILS_OLD_OPTS");
if (cp) {
op->opt_new = 0;
res = process_cl_old(op, argc, argv);
if ((0 == res) && op->opt_new)
res = process_cl_new(op, argc, argv);
} else {
op->opt_new = 1;
res = process_cl_new(op, argc, argv);
if ((0 == res) && (0 == op->opt_new))
res = process_cl_old(op, argc, argv);
}
return res;
}
static void
dStrRaw(const char* 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[])
{
int sg_fd, res, prot_en, p_type, lbppbe, rw_0_flag;
uint64_t llast_blk_addr;
int ret = 0;
uint32_t last_blk_addr, block_size;
unsigned char resp_buff[RCAP16_REPLY_LEN];
char b[80];
struct opts_t opts;
struct opts_t * op;
op = &opts;
memset(op, 0, sizeof(opts));
res = process_cl(op, argc, argv);
if (res)
return SG_LIB_SYNTAX_ERROR;
if (op->do_help) {
usage_for(op);
return 0;
}
if (op->do_version) {
pr2serr("Version string: %s\n", version_str);
return 0;
}
if (NULL == op->device_name) {
pr2serr("No DEVICE argument given\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;
}
memset(resp_buff, 0, sizeof(resp_buff));
if ((0 == op->do_pmi) && (op->llba > 0)) {
pr2serr(ME "lba can only be non-zero when '--pmi' is set\n");
usage_for(op);
return SG_LIB_SYNTAX_ERROR;
}
if (op->do_long)
rw_0_flag = op->o_readonly;
else
rw_0_flag = 1; /* RCAP(10) has opened RO in past, so leave */
if ((sg_fd = sg_cmds_open_device(op->device_name, rw_0_flag,
op->do_verbose)) < 0) {
pr2serr(ME "error opening file: %s: %s\n", op->device_name,
safe_strerror(-sg_fd));
return SG_LIB_FILE_ERROR;
}
if (! op->do_long) {
res = sg_ll_readcap_10(sg_fd, op->do_pmi, (unsigned int)op->llba,
resp_buff, RCAP_REPLY_LEN, 1, op->do_verbose);
ret = res;
if (0 == res) {
if (op->do_hex || op->do_raw) {
if (op->do_raw)
dStrRaw((const char *)resp_buff, RCAP_REPLY_LEN);
else if (op->do_hex > 2)
dStrHex((const char *)resp_buff, RCAP_REPLY_LEN, -1);
else
dStrHex((const char *)resp_buff, RCAP_REPLY_LEN, 1);
goto good;
}
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 good;
}
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 logical block address=%" PRIu32 " (0x%"
PRIx32 "), Number of 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\n", total_sz, sz_mb, sz_gb);
#else
printf(" Device size: %" PRIu64 " bytes, %.1f MiB, "
"%.2f GB\n", total_sz, sz_mb, sz_gb);
#endif
}
goto good;
} else {
printf("READ CAPACITY (10) indicates device capacity too "
"large\n now trying 16 byte cdb variant\n");
op->do_long = 1;
}
} else if (SG_LIB_CAT_INVALID_OP == res) {
op->do_long = 1;
sg_cmds_close_device(sg_fd);
if ((sg_fd = sg_cmds_open_device(op->device_name, op->o_readonly,
op->do_verbose)) < 0) {
pr2serr(ME "error re-opening file: %s (rw): %s\n",
op->device_name, safe_strerror(-sg_fd));
return SG_LIB_FILE_ERROR;
}
if (op->do_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->do_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, 1, op->do_verbose);
ret = res;
if (0 == res) {
if (op->do_hex || op->do_raw) {
if (op->do_raw)
dStrRaw((const char *)resp_buff, RCAP16_REPLY_LEN);
else if (op->do_hex > 2)
dStrHex((const char *)resp_buff, RCAP16_REPLY_LEN, -1);
else
dStrHex((const char *)resp_buff, RCAP16_REPLY_LEN, 1);
goto good;
}
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 good;
}
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 logical block address=%" 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 logical block address=%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\n",
total_sz, sz_mb, sz_gb);
#else
printf(" Device size: %" PRIu64 " bytes, %.1f MiB, %.2f "
"GB\n", total_sz, sz_mb, sz_gb);
#endif
}
goto good;
} 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->do_verbose);
pr2serr("READ CAPACITY (16) failed: %s\n", b);
}
}
if (op->do_brief)
printf("0x0 0x0\n");
good:
res = sg_cmds_close_device(sg_fd);
if (res < 0) {
pr2serr("close error: %s\n", safe_strerror(-res));
if (0 == ret)
return SG_LIB_FILE_ERROR;
}
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}