blob: 748d9831efe21f5721f32f79931088d0a305d216 [file] [log] [blame]
/*
* Copyright (c) 1999-2013 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.
*/
/* NOTICE:
* On 5th October 2004 (v1.00) this file name was changed from sg_err.c
* to sg_lib.c and the previous GPL was changed to a FreeBSD license.
* The intention is to maintain this file and the related sg_lib.h file
* as open source and encourage their unencumbered use.
*
* CONTRIBUTIONS:
* This file started out as a copy of SCSI opcodes, sense keys and
* additional sense codes (ASC/ASCQ) kept in the Linux SCSI subsystem
* in the kernel source file: drivers/scsi/constant.c . That file
* bore this notice: "Copyright (C) 1993, 1994, 1995 Eric Youngdale"
* and a GPL notice.
*
* Much of the data in this file is derived from SCSI draft standards
* found at http://www.t10.org with the "SCSI Primary Commands-4" (SPC-4)
* being the central point of reference.
*
* Contributions:
* sense key specific field decoding [Trent Piepho 20031116]
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#define __STDC_FORMAT_MACROS 1
#include <inttypes.h>
#include "sg_lib.h"
#include "sg_lib_data.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */
#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */
FILE * sg_warnings_strm = NULL; /* would like to default to stderr */
static void dStrHexErr(const char* str, int len, int b_len, char * b);
/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
* functions. Returns number number of chars placed in cp excluding the
* trailing null char. So for cp_max_len > 0 the return value is always
* < cp_max_len; for cp_max_len <= 1 the return value is 0 and no chars
* are written to cp. Note this means that when cp_max_len = 1, this
* function assumes that cp[0] is the null character and does nothing
* (and returns 0). */
static int
my_snprintf(char * cp, int cp_max_len, const char * fmt, ...)
{
va_list args;
int n;
if (cp_max_len < 2)
return 0;
va_start(args, fmt);
n = vsnprintf(cp, cp_max_len, fmt, args);
va_end(args);
return (n < cp_max_len) ? n : (cp_max_len - 1);
}
/* Searches 'arr' for match on 'value' then 'peri_type'. If matches
'value' but not 'peri_type' then yields first 'value' match entry.
Last element of 'arr' has NULL 'name'. If no match returns NULL. */
static const struct sg_lib_value_name_t *
get_value_name(const struct sg_lib_value_name_t * arr, int value,
int peri_type)
{
const struct sg_lib_value_name_t * vp = arr;
const struct sg_lib_value_name_t * holdp;
for (; vp->name; ++vp) {
if (value == vp->value) {
if (peri_type == vp->peri_dev_type)
return vp;
holdp = vp;
while ((vp + 1)->name && (value == (vp + 1)->value)) {
++vp;
if (peri_type == vp->peri_dev_type)
return vp;
}
return holdp;
}
}
return NULL;
}
void
sg_set_warnings_strm(FILE * warnings_strm)
{
sg_warnings_strm = warnings_strm;
}
#define CMD_NAME_LEN 128
void
sg_print_command(const unsigned char * command)
{
int k, sz;
char buff[CMD_NAME_LEN];
sg_get_command_name(command, 0, CMD_NAME_LEN, buff);
buff[CMD_NAME_LEN - 1] = '\0';
if (NULL == sg_warnings_strm)
sg_warnings_strm = stderr;
fprintf(sg_warnings_strm, "%s [", buff);
if (SG_VARIABLE_LENGTH_CMD == command[0])
sz = command[7] + 8;
else
sz = sg_get_command_size(command[0]);
for (k = 0; k < sz; ++k)
fprintf(sg_warnings_strm, "%02x ", command[k]);
fprintf(sg_warnings_strm, "]\n");
}
void
sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff)
{
const char * ccp;
if ((NULL == buff) || (buff_len < 1))
return;
else if (1 == buff_len) {
buff[0] = '\0';
return;
}
scsi_status &= 0x7e; /* sanitize as much as possible */
switch (scsi_status) {
case 0: ccp = "Good"; break;
case 0x2: ccp = "Check Condition"; break;
case 0x4: ccp = "Condition Met"; break;
case 0x8: ccp = "Busy"; break;
case 0x10: ccp = "Intermediate (obsolete)"; break;
case 0x14: ccp = "Intermediate-Condition Met (obs)"; break;
case 0x18: ccp = "Reservation Conflict"; break;
case 0x22: ccp = "Command Terminated (obsolete)"; break;
case 0x28: ccp = "Task set Full"; break;
case 0x30: ccp = "ACA Active"; break;
case 0x40: ccp = "Task Aborted"; break;
default: ccp = "Unknown status"; break;
}
my_snprintf(buff, buff_len, "%s", ccp);
}
void
sg_print_scsi_status(int scsi_status)
{
char buff[128];
sg_get_scsi_status_str(scsi_status, sizeof(buff) - 1, buff);
buff[sizeof(buff) - 1] = '\0';
if (NULL == sg_warnings_strm)
sg_warnings_strm = stderr;
fprintf(sg_warnings_strm, "%s ", buff);
}
char *
sg_get_sense_key_str(int sense_key, int buff_len, char * buff)
{
if (1 == buff_len) {
buff[0] = '\0';
return buff;
}
if ((sense_key >= 0) && (sense_key < 16))
my_snprintf(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]);
else
my_snprintf(buff, buff_len, "invalid value: 0x%x", sense_key);
return buff;
}
char *
sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff)
{
int k, num, rlen;
int found = 0;
struct sg_lib_asc_ascq_t * eip;
struct sg_lib_asc_ascq_range_t * ei2p;
if (1 == buff_len) {
buff[0] = '\0';
return buff;
}
for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) {
ei2p = &sg_lib_asc_ascq_range[k];
if ((ei2p->asc == asc) &&
(ascq >= ei2p->ascq_min) &&
(ascq <= ei2p->ascq_max)) {
found = 1;
num = my_snprintf(buff, buff_len, "Additional sense: ");
rlen = buff_len - num;
num += my_snprintf(buff + num, ((rlen > 0) ? rlen : 0),
ei2p->text, ascq);
}
}
if (found)
return buff;
for (k = 0; sg_lib_asc_ascq[k].text; ++k) {
eip = &sg_lib_asc_ascq[k];
if (eip->asc == asc &&
eip->ascq == ascq) {
found = 1;
my_snprintf(buff, buff_len, "Additional sense: %s", eip->text);
}
}
if (! found) {
if (asc >= 0x80)
my_snprintf(buff, buff_len, "vendor specific ASC=%02x, "
"ASCQ=%02x (hex)", asc, ascq);
else if (ascq >= 0x80)
my_snprintf(buff, buff_len, "ASC=%02x, vendor specific "
"qualification ASCQ=%02x (hex)", asc, ascq);
else
my_snprintf(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc,
ascq);
}
return buff;
}
const unsigned char *
sg_scsi_sense_desc_find(const unsigned char * sensep, int sense_len,
int desc_type)
{
int add_sb_len, add_d_len, desc_len, k;
const unsigned char * descp;
if ((sense_len < 8) || (0 == (add_sb_len = sensep[7])))
return NULL;
if ((sensep[0] < 0x72) || (sensep[0] > 0x73))
return NULL;
add_sb_len = (add_sb_len < (sense_len - 8)) ?
add_sb_len : (sense_len - 8);
descp = &sensep[8];
for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) {
descp += desc_len;
add_d_len = (k < (add_sb_len - 1)) ? descp[1]: -1;
desc_len = add_d_len + 2;
if (descp[0] == desc_type)
return descp;
if (add_d_len < 0) /* short descriptor ?? */
break;
}
return NULL;
}
int
sg_get_sense_info_fld(const unsigned char * sensep, int sb_len,
uint64_t * info_outp)
{
int j;
const unsigned char * ucp;
uint64_t ull;
if (info_outp)
*info_outp = 0;
if (sb_len < 7)
return 0;
switch (sensep[0] & 0x7f) {
case 0x70:
case 0x71:
if (info_outp)
*info_outp = ((unsigned int)sensep[3] << 24) + (sensep[4] << 16) +
(sensep[5] << 8) + sensep[6];
return (sensep[0] & 0x80) ? 1 : 0;
case 0x72:
case 0x73:
ucp = sg_scsi_sense_desc_find(sensep, sb_len, 0 /* info desc */);
if (ucp && (0xa == ucp[1])) {
ull = 0;
for (j = 0; j < 8; ++j) {
if (j > 0)
ull <<= 8;
ull |= ucp[4 + j];
}
if (info_outp)
*info_outp = ull;
return !!(ucp[2] & 0x80); /* since spc3r23 should be set */
} else
return 0;
default:
return 0;
}
}
int
sg_get_sense_filemark_eom_ili(const unsigned char * sensep, int sb_len,
int * filemark_p, int * eom_p, int * ili_p)
{
const unsigned char * ucp;
if (sb_len < 7)
return 0;
switch (sensep[0] & 0x7f) {
case 0x70:
case 0x71:
if (sensep[2] & 0xe0) {
if (filemark_p)
*filemark_p = !!(sensep[2] & 0x80);
if (eom_p)
*eom_p = !!(sensep[2] & 0x40);
if (ili_p)
*ili_p = !!(sensep[2] & 0x20);
return 1;
} else
return 0;
case 0x72:
case 0x73:
/* Look for stream commands sense data descriptor */
ucp = sg_scsi_sense_desc_find(sensep, sb_len, 4);
if (ucp && (ucp[1] >= 2)) {
if (ucp[3] & 0xe0) {
if (filemark_p)
*filemark_p = !!(ucp[3] & 0x80);
if (eom_p)
*eom_p = !!(ucp[3] & 0x40);
if (ili_p)
*ili_p = !!(ucp[3] & 0x20);
return 1;
}
}
return 0;
default:
return 0;
}
}
/* Returns 1 if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
* returns 1 if progress indication sense data descriptor found. Places
* progress field from sense data where progress_outp points. If progress
* field is not available returns 0. Handles both fixed and descriptor
* sense formats. N.B. App should multiply by 100 and divide by 65536
* to get percentage completion from given value. */
int
sg_get_sense_progress_fld(const unsigned char * sensep, int sb_len,
int * progress_outp)
{
const unsigned char * ucp;
int sk, sk_pr;
if (sb_len < 7)
return 0;
switch (sensep[0] & 0x7f) {
case 0x70:
case 0x71:
sk = (sensep[2] & 0xf);
if ((sb_len < 18) ||
((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk)))
return 0;
if (sensep[15] & 0x80) { /* SKSV bit set */
if (progress_outp)
*progress_outp = (sensep[16] << 8) + sensep[17];
return 1;
} else
return 0;
case 0x72:
case 0x73:
/* sense key specific progress (0x2) or progress descriptor (0xa) */
sk = (sensep[1] & 0xf);
sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk);
if (sk_pr && ((ucp = sg_scsi_sense_desc_find(sensep, sb_len, 2))) &&
(0x6 == ucp[1]) && (0x80 & ucp[4])) {
if (progress_outp)
*progress_outp = (ucp[5] << 8) + ucp[6];
return 1;
} else if (((ucp = sg_scsi_sense_desc_find(sensep, sb_len, 0xa))) &&
((0x6 == ucp[1]))) {
if (progress_outp)
*progress_outp = (ucp[6] << 8) + ucp[7];
return 1;
} else
return 0;
default:
return 0;
}
}
char *
sg_get_pdt_str(int pdt, int buff_len, char * buff)
{
if ((pdt < 0) || (pdt > 31))
my_snprintf(buff, buff_len, "bad pdt");
else
my_snprintf(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]);
return buff;
}
char *
sg_get_trans_proto_str(int tpi, int buff_len, char * buff)
{
if ((tpi < 0) || (tpi > 15))
my_snprintf(buff, buff_len, "bad tpi");
else
my_snprintf(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]);
return buff;
}
#define TPGS_STATE_OPTIMIZED 0x0
#define TPGS_STATE_NONOPTIMIZED 0x1
#define TPGS_STATE_STANDBY 0x2
#define TPGS_STATE_UNAVAILABLE 0x3
#define TPGS_STATE_OFFLINE 0xe
#define TPGS_STATE_TRANSITIONING 0xf
static int
decode_tpgs_state(int st, char * b, int blen)
{
switch (st) {
case TPGS_STATE_OPTIMIZED:
return my_snprintf(b, blen, "active/optimized");
case TPGS_STATE_NONOPTIMIZED:
return my_snprintf(b, blen, "active/non optimized");
case TPGS_STATE_STANDBY:
return my_snprintf(b, blen, "standby");
case TPGS_STATE_UNAVAILABLE:
return my_snprintf(b, blen, "unavailable");
case TPGS_STATE_OFFLINE:
return my_snprintf(b, blen, "offline");
case TPGS_STATE_TRANSITIONING:
return my_snprintf(b, blen, "transitioning between states");
default:
return my_snprintf(b, blen, "unknown: 0x%x", st);
}
}
static int
uds_referral_descriptor_str(char * b, int blen, const unsigned char * dp,
int alen)
{
int n = 0;
int dlen = alen - 2;
int k, j, g, f, tpgd;
const unsigned char * tp;
uint64_t ull;
char c[40];
n += my_snprintf(b + n, blen - n, " Not all referrals: %d\n",
!!(dp[2] & 0x1));
dp += 4;
for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
tpgd = dp[3];
g = (tpgd * 4) + 20;
n += my_snprintf(b + n, blen - n, " Descriptor %d\n", f);
if ((k + g) > dlen) {
n += my_snprintf(b + n, blen - n, " truncated descriptor, "
"stop\n");
return n;
}
ull = 0;
for (j = 0; j < 8; ++j) {
if (j > 0)
ull <<= 8;
ull |= dp[4 + j];
}
n += my_snprintf(b + n, blen - n, " first uds LBA: 0x%"PRIx64
"\n", ull);
ull = 0;
for (j = 0; j < 8; ++j) {
if (j > 0)
ull <<= 8;
ull |= dp[12 + j];
}
n += my_snprintf(b + n, blen - n, " last uds LBA: 0x%"PRIx64
"\n", ull);
for (j = 0; j < tpgd; ++j) {
tp = dp + 20 + (j * 4);
decode_tpgs_state(tp[0] & 0xf, c, sizeof(c));
n += my_snprintf(b + n, blen - n, " tpg: %d state: %s\n",
(tp[2] << 8) + tp[3], c);
}
}
return n;
}
static const char * sdata_src[] = {
"unknown",
"Extended Copy command source device",
"Extended Copy command destination device",
};
/* Decode descriptor format sense descriptors (assumes sense buffer is
in descriptor format) */
static void
sg_get_sense_descriptors_str(const unsigned char * sense_buffer, int sb_len,
int blen, char * b)
{
int add_sb_len, add_d_len, desc_len, k, j, sense_key, processed;
int n, progress, pr, rem;
const unsigned char * descp;
const char * dtsp = " >> descriptor too short";
if ((NULL == b) || (blen <= 0))
return;
b[0] = '\0';
if ((sb_len < 8) || (0 == (add_sb_len = sense_buffer[7])))
return;
add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
sense_key = (sense_buffer[1] & 0xf);
for (descp = (sense_buffer + 8), k = 0, n = 0;
(k < add_sb_len) && (n < blen);
k += desc_len, descp += desc_len) {
add_d_len = (k < (add_sb_len - 1)) ? descp[1] : -1;
if ((k + add_d_len + 2) > add_sb_len)
add_d_len = add_sb_len - k - 2;
desc_len = add_d_len + 2;
n += my_snprintf(b + n, blen - n, " Descriptor type: ");
processed = 1;
switch (descp[0]) {
case 0:
n += my_snprintf(b + n, blen - n, "Information\n");
if ((add_d_len >= 10) && (0x80 & descp[2])) {
n += my_snprintf(b + n, blen - n, " 0x");
for (j = 0; j < 8; ++j)
n += my_snprintf(b + n, blen - n, "%02x", descp[4 + j]);
n += my_snprintf(b + n, blen - n, "\n");
} else {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
}
break;
case 1:
n += my_snprintf(b + n, blen - n, "Command specific\n");
if (add_d_len >= 10) {
n += my_snprintf(b + n, blen - n, " 0x");
for (j = 0; j < 8; ++j)
n += my_snprintf(b + n, blen - n, "%02x", descp[4 + j]);
n += my_snprintf(b + n, blen - n, "\n");
} else {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
}
break;
case 2:
n += my_snprintf(b + n, blen - n, "Sense key specific:");
switch (sense_key) {
case SPC_SK_ILLEGAL_REQUEST:
n += my_snprintf(b + n, blen - n, " Field pointer\n");
if (add_d_len < 6) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
n += my_snprintf(b + n, blen - n, " Error in %s byte %d",
(descp[4] & 0x40) ? "Command" : "Data",
(descp[5] << 8) | descp[6]);
if (descp[4] & 0x08) {
n += my_snprintf(b + n, blen - n, " bit %d\n",
descp[4] & 0x07);
} else
n += my_snprintf(b + n, blen - n, "\n");
break;
case SPC_SK_HARDWARE_ERROR:
case SPC_SK_MEDIUM_ERROR:
case SPC_SK_RECOVERED_ERROR:
n += my_snprintf(b + n, blen - n, " Actual retry count\n");
if (add_d_len < 6) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
n += my_snprintf(b + n, blen - n," 0x%02x%02x\n", descp[5],
descp[6]);
break;
case SPC_SK_NO_SENSE:
case SPC_SK_NOT_READY:
n += my_snprintf(b + n, blen - n, " Progress indication: ");
if (add_d_len < 6) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
progress = (descp[5] << 8) + descp[6];
pr = (progress * 100) / 65536;
rem = ((progress * 100) % 65536) / 655;
n += my_snprintf(b + n, blen - n, "%d.%02d%%\n", pr, rem);
break;
case SPC_SK_COPY_ABORTED:
n += my_snprintf(b + n, blen - n, " Segment pointer\n");
if (add_d_len < 6) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
n += my_snprintf(b + n, blen - n, " Relative to start of %s, "
"byte %d",
(descp[4] & 0x20) ? "segment descriptor" :
"parameter list",
(descp[5] << 8) | descp[6]);
if (descp[4] & 0x08)
n += my_snprintf(b + n, blen - n, " bit %d\n",
descp[4] & 0x07);
else
n += my_snprintf(b + n, blen - n, "\n");
break;
case SPC_SK_UNIT_ATTENTION:
n += my_snprintf(b + n, blen - n, " Unit attention condition "
"queue: ");
n += my_snprintf(b + n, blen - n, "overflow flag is %d\n",
!!(descp[4] & 0x1));
break;
default:
n += my_snprintf(b + n, blen - n, " Sense_key: 0x%x "
"unexpected\n", sense_key);
processed = 0;
break;
}
break;
case 3:
n += my_snprintf(b + n, blen - n, "Field replaceable unit\n");
if (add_d_len >= 2)
n += my_snprintf(b + n, blen - n, " code=0x%x\n",
descp[3]);
else {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
}
break;
case 4:
n += my_snprintf(b + n, blen - n, "Stream commands\n");
if (add_d_len >= 2) {
if (descp[3] & 0x80)
n += my_snprintf(b + n, blen - n, " FILEMARK");
if (descp[3] & 0x40)
n += my_snprintf(b + n, blen - n, " End Of Medium "
"(EOM)");
if (descp[3] & 0x20)
n += my_snprintf(b + n, blen - n, " Incorrect Length "
"Indicator (ILI)");
n += my_snprintf(b + n, blen - n, "\n");
} else {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
}
break;
case 5:
n += my_snprintf(b + n, blen - n, "Block commands\n");
if (add_d_len >= 2)
n += my_snprintf(b + n, blen - n, " Incorrect Length "
"Indicator (ILI) %s\n",
(descp[3] & 0x20) ? "set" : "clear");
else {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
}
break;
case 6:
n += my_snprintf(b + n, blen - n, "OSD object identification\n");
processed = 0;
break;
case 7:
n += my_snprintf(b + n, blen - n, "OSD response integrity check "
"value\n");
processed = 0;
break;
case 8:
n += my_snprintf(b + n, blen - n, "OSD attribute "
"identification\n");
processed = 0;
break;
case 9: /* this is defined in SAT (and SAT-2) */
n += my_snprintf(b + n, blen - n, "ATA Status Return\n");
if (add_d_len >= 12) {
int extend, sector_count;
extend = descp[2] & 1;
sector_count = descp[5] + (extend ? (descp[4] << 8) : 0);
n += my_snprintf(b + n, blen - n, " extend=%d error=0x%x "
" sector_count=0x%x\n", extend, descp[3],
sector_count);
if (extend)
n += my_snprintf(b + n, blen - n, " "
"lba=0x%02x%02x%02x%02x%02x%02x\n",
descp[10], descp[8], descp[6],
descp[11], descp[9], descp[7]);
else
n += my_snprintf(b + n, blen - n, " "
"lba=0x%02x%02x%02x\n",
descp[11], descp[9], descp[7]);
n += my_snprintf(b + n, blen - n, " device=0x%x "
"status=0x%x\n", descp[12], descp[13]);
} else {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
}
break;
case 0xa:
/* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
n += my_snprintf(b + n, blen - n, "Another progress "
"indication\n");
if (add_d_len < 6) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
progress = (descp[6] << 8) + descp[7];
pr = (progress * 100) / 65536;
rem = ((progress * 100) % 65536) / 655;
n += my_snprintf(b + n, blen - n, " %d.02%d%%", pr, rem);
n += my_snprintf(b + n, blen - n, " [sense_key=0x%x "
"asc,ascq=0x%x,0x%x]\n",
descp[2], descp[3], descp[4]);
break;
case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
n += my_snprintf(b + n, blen - n, "User data segment referral\n");
if (add_d_len < 2) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
n += uds_referral_descriptor_str(b + n, blen - n, descp,
add_d_len);
break;
case 0xc: /* Added in SPC-4 rev 28 */
n += my_snprintf(b + n, blen - n, "Forwarded sense data\n");
if (add_d_len < 2) {
n += my_snprintf(b + n, blen - n, "%s\n", dtsp);
processed = 0;
break;
}
n += my_snprintf(b + n, blen - n, " FSDT: %s\n",
(descp[2] & 0x80) ? "set" : "clear");
j = descp[2] & 0xf;
if (j < 3)
n += my_snprintf(b + n, blen - n, " Sense data source: "
"%s\n", sdata_src[j]);
else
n += my_snprintf(b + n, blen - n, " Sense data source: "
"reserved [%d]\n", j);
{
char c[200];
sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c);
c[sizeof(c) - 1] = '\0';
n += my_snprintf(b + n, blen - n, " Forwarded status: "
"%s\n", c);
if (add_d_len > 2) {
/* recursing; hope not to get carried away */
n += my_snprintf(b + n, blen - n, " vvvvvvvvvvvvvvvv\n");
sg_get_sense_str(NULL, descp + 4, add_d_len - 2, 0,
sizeof(c), c);
n += my_snprintf(b + n, blen - n, "%s", c);
n += my_snprintf(b + n, blen - n, " ^^^^^^^^^^^^^^^^\n");
}
}
break;
default:
if (descp[0] >= 0x80)
n += my_snprintf(b + n, blen - n, "Vendor specific [0x%x]\n",
descp[0]);
else
n += my_snprintf(b + n, blen - n, "Unknown [0x%x]\n",
descp[0]);
processed = 0;
break;
}
if (! processed) {
if (add_d_len > 0) {
n += my_snprintf(b + n, blen - n, " ");
for (j = 0; j < add_d_len; ++j) {
if ((j > 0) && (0 == (j % 24)))
n += my_snprintf(b + n, blen - n, "\n ");
n += my_snprintf(b + n, blen - n, "%02x ", descp[j + 2]);
}
n += my_snprintf(b + n, blen - n, "\n");
}
}
if (add_d_len < 0)
n += my_snprintf(b + n, blen - n, " short descriptor\n");
}
}
/* Decode SAT ATA PASS-THROUGH fixed format sense */
static void
sg_get_sense_sat_pt_fixed_str(const unsigned char * sp, int slen, int blen,
char * b)
{
int n = 0;
slen = slen; /* suppress warning */
if (blen < 1)
return;
if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2]))
n += my_snprintf(b + n, blen - n, " >> expected Sense key: "
"Recovered Error ??\n");
n += my_snprintf(b + n, blen - n, " error=0x%x, status=0x%x, "
"device=0x%x, sector_count(7:0)=0x%x%c\n", sp[3], sp[4],
sp[5], sp[6], ((0x40 & sp[8]) ? '+' : ' '));
n += my_snprintf(b + n, blen - n, " extend=%d, log_index=0x%x, "
"lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n",
(!!(0x80 & sp[8])), (0xf & sp[8]), sp[9], sp[10], sp[11],
((0x20 & sp[8]) ? '+' : ' '));
}
/* Fetch sense information */
void
sg_get_sense_str(const char * leadin, const unsigned char * sense_buffer,
int sb_len, int raw_sinfo, int buff_len, char * buff)
{
int len, valid, progress, n, r, pr, rem, blen;
unsigned int info;
int descriptor_format = 0;
int sdat_ovfl = 0;
const char * ebp = NULL;
char error_buff[64];
char b[256];
struct sg_scsi_sense_hdr ssh;
if ((NULL == buff) || (buff_len <= 0))
return;
else if (1 == buff_len) {
buff[0] = '\0';
return;
}
blen = sizeof(b);
n = 0;
if (sb_len < 1) {
my_snprintf(buff, buff_len, "sense buffer empty\n");
return;
}
if (leadin)
n += my_snprintf(buff + n, buff_len - n, "%s: ", leadin);
len = sb_len;
if (sg_scsi_normalize_sense(sense_buffer, sb_len, &ssh)) {
switch (ssh.response_code) {
case 0x70: /* fixed, current */
ebp = "Fixed format, current";
len = (sb_len > 7) ? (sense_buffer[7] + 8) : sb_len;
len = (len > sb_len) ? sb_len : len;
sdat_ovfl = (len > 2) ? !!(sense_buffer[2] & 0x10) : 0;
break;
case 0x71: /* fixed, deferred */
/* error related to a previous command */
ebp = "Fixed format, <<<deferred>>>";
len = (sb_len > 7) ? (sense_buffer[7] + 8) : sb_len;
len = (len > sb_len) ? sb_len : len;
sdat_ovfl = (len > 2) ? !!(sense_buffer[2] & 0x10) : 0;
break;
case 0x72: /* descriptor, current */
descriptor_format = 1;
ebp = "Descriptor format, current";
sdat_ovfl = (sb_len > 4) ? !!(sense_buffer[4] & 0x80) : 0;
break;
case 0x73: /* descriptor, deferred */
descriptor_format = 1;
ebp = "Descriptor format, <<<deferred>>>";
sdat_ovfl = (sb_len > 4) ? !!(sense_buffer[4] & 0x80) : 0;
break;
case 0x0:
ebp = "Response code: 0x0 (?)";
break;
default:
my_snprintf(error_buff, sizeof(error_buff),
"Unknown response code: 0x%x", ssh.response_code);
ebp = error_buff;
break;
}
n += my_snprintf(buff + n, buff_len - n, " %s; Sense key: %s\n ",
ebp, sg_lib_sense_key_desc[ssh.sense_key]);
if (sdat_ovfl)
n += my_snprintf(buff + n, buff_len - n, "<<<Sense data "
"overflow>>>\n");
if (descriptor_format) {
n += my_snprintf(buff + n, buff_len - n, "%s\n",
sg_get_asc_ascq_str(ssh.asc, ssh.ascq,
sizeof(b), b));
sg_get_sense_descriptors_str(sense_buffer, len, buff_len - n,
buff + n);
n = strlen(buff);
} else if ((len > 12) && (0 == ssh.asc) &&
(ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
/* SAT ATA PASS-THROUGH fixed format */
n += my_snprintf(buff + n, buff_len - n, "%s\n",
sg_get_asc_ascq_str(ssh.asc, ssh.ascq,
sizeof(b), b));
sg_get_sense_sat_pt_fixed_str(sense_buffer, len, buff_len - n,
buff + n);
n = strlen(buff);
} else if (len > 2) { /* fixed format */
if (len > 12)
n += my_snprintf(buff + n, buff_len - n, "%s\n",
sg_get_asc_ascq_str(ssh.asc, ssh.ascq,
sizeof(b), b));
r = 0;
valid = sense_buffer[0] & 0x80;
if (len > 6) {
info = (unsigned int)((sense_buffer[3] << 24) |
(sense_buffer[4] << 16) | (sense_buffer[5] << 8) |
sense_buffer[6]);
if (valid)
r += my_snprintf(b + r, blen - r, " Info fld=0x%x [%u] ",
info, info);
else if (info > 0)
r += my_snprintf(b + r, blen - r, " Valid=0, Info "
"fld=0x%x [%u] ", info, info);
} else
info = 0;
if (sense_buffer[2] & 0xe0) {
if (sense_buffer[2] & 0x80)
r += my_snprintf(b + r, blen - r, " FMK");
/* current command has read a filemark */
if (sense_buffer[2] & 0x40)
r += my_snprintf(b + r, blen - r, " EOM");
/* end-of-medium condition exists */
if (sense_buffer[2] & 0x20)
r += my_snprintf(b + r, blen - r, " ILI");
/* incorrect block length requested */
r += my_snprintf(b + r, blen - r, "\n");
} else if (valid || (info > 0))
r += my_snprintf(b + r, blen - r, "\n");
if ((len >= 14) && sense_buffer[14])
r += my_snprintf(b + r, blen - r, " Field replaceable unit "
"code: %d\n", sense_buffer[14]);
if ((len >= 18) && (sense_buffer[15] & 0x80)) {
/* sense key specific decoding */
switch (ssh.sense_key) {
case SPC_SK_ILLEGAL_REQUEST:
r += my_snprintf(b + r, blen - r, " Sense Key Specific: "
"Error in %s byte %d",
((sense_buffer[15] & 0x40) ? "Command" : "Data"),
(sense_buffer[16] << 8) | sense_buffer[17]);
if (sense_buffer[15] & 0x08)
r += my_snprintf(b + r, blen - r, " bit %d\n",
sense_buffer[15] & 0x07);
else
r += my_snprintf(b + r, blen - r, "\n");
break;
case SPC_SK_NO_SENSE:
case SPC_SK_NOT_READY:
progress = (sense_buffer[16] << 8) + sense_buffer[17];
pr = (progress * 100) / 65536;
rem = ((progress * 100) % 65536) / 655;
r += my_snprintf(b + r, blen - r, " Progress "
"indication: %d.%02d%%\n", pr, rem);
break;
case SPC_SK_HARDWARE_ERROR:
case SPC_SK_MEDIUM_ERROR:
case SPC_SK_RECOVERED_ERROR:
r += my_snprintf(b + r, blen - r, " Actual retry count: "
"0x%02x%02x\n", sense_buffer[16],
sense_buffer[17]);
break;
case SPC_SK_COPY_ABORTED:
r += my_snprintf(b + r, blen - r, " Segment pointer: ");
r += my_snprintf(b + r, blen - r, "Relative to start of "
"%s, byte %d",
((sense_buffer[15] & 0x20) ?
"segment descriptor" : "parameter list"),
((sense_buffer[16] << 8) +
sense_buffer[17]));
if (sense_buffer[15] & 0x08)
r += my_snprintf(b + r, blen - r, " bit %d\n",
sense_buffer[15] & 0x07);
else
r += my_snprintf(b + r, blen - r, "\n");
break;
case SPC_SK_UNIT_ATTENTION:
r += my_snprintf(b + r, blen - r, " Unit attention "
"condition queue: ");
r += my_snprintf(b + r, blen - r, "overflow flag is %d\n",
!!(sense_buffer[15] & 0x1));
break;
default:
r += my_snprintf(b + r, blen - r, " Sense_key: 0x%x "
"unexpected\n", ssh.sense_key);
break;
}
}
if (r > 0)
n += my_snprintf(buff + n, buff_len - n, "%s", b);
} else
n += my_snprintf(buff + n, buff_len - n, " fixed descriptor "
"length too short, len=%d\n", len);
} else { /* non-extended SCSI-1 sense data ?? */
if (sb_len < 4) {
n += my_snprintf(buff + n, buff_len - n, "sense buffer too short "
"(4 byte minimum)\n");
return;
}
r = 0;
r += my_snprintf(b + r, blen - r, "Probably uninitialized data.\n "
"Try to view as SCSI-1 non-extended sense:\n");
r += my_snprintf(b + r, blen - r, " AdValid=%d Error class=%d "
"Error code=%d\n", !!(sense_buffer[0] & 0x80),
((sense_buffer[0] >> 4) & 0x7),
(sense_buffer[0] & 0xf));
if (sense_buffer[0] & 0x80)
r += my_snprintf(b + r, blen - r, " lba=0x%x\n",
((sense_buffer[1] & 0x1f) << 16) +
(sense_buffer[2] << 8) + sense_buffer[3]);
n += my_snprintf(buff + n, buff_len - n, "%s\n", b);
len = sb_len;
if (len > 32)
len = 32; /* trim in case there is a lot of rubbish */
}
if (raw_sinfo) {
n += my_snprintf(buff + n, buff_len - n, " Raw sense data (in hex):"
"\n");
if (n >= (buff_len - 1))
return;
dStrHexErr((const char *)sense_buffer, len, buff_len - n, buff + n);
}
}
/* Print sense information */
void
sg_print_sense(const char * leadin, const unsigned char * sense_buffer,
int sb_len, int raw_sinfo)
{
char b[2048];
sg_get_sense_str(leadin, sense_buffer, sb_len, raw_sinfo, sizeof(b), b);
if (NULL == sg_warnings_strm)
sg_warnings_strm = stderr;
fprintf(sg_warnings_strm, "%s", b);
}
/* See description in sg_lib.h header file */
int
sg_scsi_normalize_sense(const unsigned char * sensep, int sb_len,
struct sg_scsi_sense_hdr * sshp)
{
if (sshp)
memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
if ((NULL == sensep) || (0 == sb_len) || (0x70 != (0x70 & sensep[0])))
return 0;
if (sshp) {
sshp->response_code = (0x7f & sensep[0]);
if (sshp->response_code >= 0x72) { /* descriptor format */
if (sb_len > 1)
sshp->sense_key = (0xf & sensep[1]);
if (sb_len > 2)
sshp->asc = sensep[2];
if (sb_len > 3)
sshp->ascq = sensep[3];
if (sb_len > 7)
sshp->additional_length = sensep[7];
} else { /* fixed format */
if (sb_len > 2)
sshp->sense_key = (0xf & sensep[2]);
if (sb_len > 7) {
sb_len = (sb_len < (sensep[7] + 8)) ? sb_len :
(sensep[7] + 8);
if (sb_len > 12)
sshp->asc = sensep[12];
if (sb_len > 13)
sshp->ascq = sensep[13];
}
}
}
return 1;
}
/* Returns a SG_LIB_CAT_* value. If cannot decode sense_buffer or a less
* common sense key then return SG_LIB_CAT_SENSE .*/
int
sg_err_category_sense(const unsigned char * sense_buffer, int sb_len)
{
struct sg_scsi_sense_hdr ssh;
if ((sense_buffer && (sb_len > 2)) &&
(sg_scsi_normalize_sense(sense_buffer, sb_len, &ssh))) {
switch (ssh.sense_key) {
case SPC_SK_NO_SENSE:
return SG_LIB_CAT_NO_SENSE;
case SPC_SK_RECOVERED_ERROR:
return SG_LIB_CAT_RECOVERED;
case SPC_SK_NOT_READY:
return SG_LIB_CAT_NOT_READY;
case SPC_SK_MEDIUM_ERROR:
case SPC_SK_HARDWARE_ERROR:
case SPC_SK_BLANK_CHECK:
return SG_LIB_CAT_MEDIUM_HARD;
case SPC_SK_UNIT_ATTENTION:
return SG_LIB_CAT_UNIT_ATTENTION;
/* used to return SG_LIB_CAT_MEDIA_CHANGED when ssh.asc==0x28 */
case SPC_SK_ILLEGAL_REQUEST:
if ((0x20 == ssh.asc) && (0x0 == ssh.ascq))
return SG_LIB_CAT_INVALID_OP;
else
return SG_LIB_CAT_ILLEGAL_REQ;
break;
case SPC_SK_ABORTED_COMMAND:
return SG_LIB_CAT_ABORTED_COMMAND;
default:
; /* drop through (SPC_SK_COMPLETED amongst others) */
}
}
return SG_LIB_CAT_SENSE;
}
/* gives wrong answer for variable length command (opcode=0x7f) */
int
sg_get_command_size(unsigned char opcode)
{
switch ((opcode >> 5) & 0x7) {
case 0:
return 6;
case 1: case 2: case 6: case 7:
return 10;
case 3: case 5:
return 12;
break;
case 4:
return 16;
default:
return 10;
}
}
void
sg_get_command_name(const unsigned char * cmdp, int peri_type, int buff_len,
char * buff)
{
int service_action;
if ((NULL == buff) || (buff_len < 1))
return;
else if (1 == buff_len) {
buff[0] = '\0';
return;
}
if (NULL == cmdp) {
my_snprintf(buff, buff_len, "%s", "<null> command pointer");
return;
}
service_action = (SG_VARIABLE_LENGTH_CMD == cmdp[0]) ?
((cmdp[8] << 8) | cmdp[9]) : (cmdp[1] & 0x1f);
sg_get_opcode_sa_name(cmdp[0], service_action, peri_type, buff_len, buff);
}
void
sg_get_opcode_sa_name(unsigned char cmd_byte0, int service_action,
int peri_type, int buff_len, char * buff)
{
const struct sg_lib_value_name_t * vnp;
if ((NULL == buff) || (buff_len < 1))
return;
else if (1 == buff_len) {
buff[0] = '\0';
return;
}
switch ((int)cmd_byte0) {
case SG_VARIABLE_LENGTH_CMD:
vnp = get_value_name(sg_lib_variable_length_arr, service_action,
peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Variable length service action=0x%x",
service_action);
break;
case SG_MAINTENANCE_IN:
vnp = get_value_name(sg_lib_maint_in_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Maintenance in service action=0x%x",
service_action);
break;
case SG_MAINTENANCE_OUT:
vnp = get_value_name(sg_lib_maint_out_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Maintenance out service action=0x%x",
service_action);
break;
case SG_SERVICE_ACTION_IN_12:
vnp = get_value_name(sg_lib_serv_in12_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Service action in(12)=0x%x",
service_action);
break;
case SG_SERVICE_ACTION_OUT_12:
vnp = get_value_name(sg_lib_serv_out12_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Service action out(12)=0x%x",
service_action);
break;
case SG_SERVICE_ACTION_IN_16:
vnp = get_value_name(sg_lib_serv_in16_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Service action in(16)=0x%x",
service_action);
break;
case SG_SERVICE_ACTION_OUT_16:
vnp = get_value_name(sg_lib_serv_out16_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Service action out(16)=0x%x",
service_action);
break;
case SG_PERSISTENT_RESERVE_IN:
vnp = get_value_name(sg_lib_pr_in_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Persistent reserve in, service "
"action=0x%x", service_action);
break;
case SG_PERSISTENT_RESERVE_OUT:
vnp = get_value_name(sg_lib_pr_out_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Persistent reserve out, service "
"action=0x%x", service_action);
break;
case SG_EXTENDED_COPY:
vnp = get_value_name(sg_lib_xcopy_sa_arr, service_action, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Extended copy, service action=0x%x",
service_action);
break;
case SG_RECEIVE_COPY:
vnp = get_value_name(sg_lib_rec_copy_sa_arr, service_action,
peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Receive copy, service action=0x%x",
service_action);
break;
case SG_READ_BUFFER:
/* spc4r34 requested treating mode as service action */
vnp = get_value_name(sg_lib_read_buff_arr, service_action,
peri_type);
if (vnp)
my_snprintf(buff, buff_len, "Read buffer (%s)\n", vnp->name);
else
my_snprintf(buff, buff_len, "Read buffer, mode=0x%x",
service_action);
break;
case SG_WRITE_BUFFER:
/* spc4r34 requested treating mode as service action */
vnp = get_value_name(sg_lib_write_buff_arr, service_action,
peri_type);
if (vnp)
my_snprintf(buff, buff_len, "Write buffer (%s)\n", vnp->name);
else
my_snprintf(buff, buff_len, "Write buffer, mode=0x%x",
service_action);
break;
default:
sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
break;
}
}
void
sg_get_opcode_name(unsigned char cmd_byte0, int peri_type, int buff_len,
char * buff)
{
const struct sg_lib_value_name_t * vnp;
int grp;
if ((NULL == buff) || (buff_len < 1))
return;
else if (1 == buff_len) {
buff[0] = '\0';
return;
}
if (SG_VARIABLE_LENGTH_CMD == cmd_byte0) {
my_snprintf(buff, buff_len, "%s", "Variable length");
return;
}
grp = (cmd_byte0 >> 5) & 0x7;
switch (grp) {
case 0:
case 1:
case 2:
case 4:
case 5:
vnp = get_value_name(sg_lib_normal_opcodes, cmd_byte0, peri_type);
if (vnp)
my_snprintf(buff, buff_len, "%s", vnp->name);
else
my_snprintf(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
break;
case 3:
my_snprintf(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0);
break;
case 6:
case 7:
my_snprintf(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0);
break;
default:
my_snprintf(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
break;
}
}
/* Iterates to next designation descriptor in the device identification
* VPD page. The 'initial_desig_desc' should point to start of first
* descriptor with 'page_len' being the number of valid bytes in that
* and following descriptors. To start, 'off' should point to a negative
* value, thereafter it should point to the value yielded by the previous
* call. If 0 returned then 'initial_desig_desc + *off' should be a valid
* descriptor; returns -1 if normal end condition and -2 for an abnormal
* termination. Matches association, designator_type and/or code_set when
* any of those values are greater than or equal to zero. */
int
sg_vpd_dev_id_iter(const unsigned char * initial_desig_desc, int page_len,
int * off, int m_assoc, int m_desig_type, int m_code_set)
{
const unsigned char * ucp;
int k, c_set, assoc, desig_type;
for (k = *off, ucp = initial_desig_desc ; (k + 3) < page_len; ) {
k = (k < 0) ? 0 : (k + ucp[k + 3] + 4);
if ((k + 4) > page_len)
break;
c_set = (ucp[k] & 0xf);
if ((m_code_set >= 0) && (m_code_set != c_set))
continue;
assoc = ((ucp[k + 1] >> 4) & 0x3);
if ((m_assoc >= 0) && (m_assoc != assoc))
continue;
desig_type = (ucp[k + 1] & 0xf);
if ((m_desig_type >= 0) && (m_desig_type != desig_type))
continue;
*off = k;
return 0;
}
return (k == page_len) ? -1 : -2;
}
/* safe_strerror() contributed by Clayton Weaver <cgweav at email dot com>
Allows for situation in which strerror() is given a wild value (or the
C library is incomplete) and returns NULL. Still not thread safe.
*/
static char safe_errbuf[64] = {'u', 'n', 'k', 'n', 'o', 'w', 'n', ' ',
'e', 'r', 'r', 'n', 'o', ':', ' ', 0};
char *
safe_strerror(int errnum)
{
size_t len;
char * errstr;
if (errnum < 0)
errnum = -errnum;
errstr = strerror(errnum);
if (NULL == errstr) {
len = strlen(safe_errbuf);
my_snprintf(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i",
errnum);
return safe_errbuf;
}
return errstr;
}
/* Note the ASCII-hex output goes to stdout. [Most other output from functions
in this file go to sg_warnings_strm (default stderr).]
'no_ascii' allows for 3 output types:
> 0 each line has address then up to 16 ASCII-hex bytes
= 0 in addition, the bytes are listed in ASCII to the right
< 0 only the ASCII-hex bytes are listed (i.e. without address) */
void
dStrHex(const char* str, int len, int no_ascii)
{
const char * p = str;
const char * formatstr;
unsigned char c;
char buff[82];
int a = 0;
const int bpstart = 5;
const int cpstart = 60;
int cpos = cpstart;
int bpos = bpstart;
int i, k, blen;
if (len <= 0)
return;
blen = (int)sizeof(buff);
formatstr = (0 == no_ascii) ? "%.76s\n" : "%.56s\n";
memset(buff, ' ', 80);
buff[80] = '\0';
if (no_ascii < 0) {
for (k = 0; k < len; k++) {
c = *p++;
bpos += 3;
if (bpos == (bpstart + (9 * 3)))
bpos++;
my_snprintf(&buff[bpos], blen - bpos, "%.2x",
(int)(unsigned char)c);
buff[bpos + 2] = ' ';
if ((k > 0) && (0 == ((k + 1) % 16))) {
printf(formatstr, buff);
bpos = bpstart;
memset(buff, ' ', 80);
}
}
if (bpos > bpstart) {
buff[bpos + 2] = '\0';
printf("%s\n", buff);
}
return;
}
/* no_ascii>=0, start each line with address (offset) */
k = my_snprintf(buff + 1, blen - 1, "%.2x", a);
buff[k + 1] = ' ';
for (i = 0; i < len; i++) {
c = *p++;
bpos += 3;
if (bpos == (bpstart + (9 * 3)))
bpos++;
my_snprintf(&buff[bpos], blen - bpos, "%.2x", (int)(unsigned char)c);
buff[bpos + 2] = ' ';
if (no_ascii)
buff[cpos++] = ' ';
else {
if ((c < ' ') || (c >= 0x7f))
c = '.';
buff[cpos++] = c;
}
if (cpos > (cpstart + 15)) {
printf(formatstr, buff);
bpos = bpstart;
cpos = cpstart;
a += 16;
memset(buff, ' ', 80);
k = my_snprintf(buff + 1, blen - 1, "%.2x", a);
buff[k + 1] = ' ';
}
}
if (cpos > cpstart) {
buff[cpos] = '\0';
printf("%s\n", buff);
}
}
/* Output to ASCII-Hex bytes to 'b' not to exceed 'b_len' characters.
* 16 bytes per line with an extra space between the 8th and 9th bytes */
static void
dStrHexErr(const char* str, int len, int b_len, char * b)
{
const char * p = str;
unsigned char c;
char buff[82];
const int bpstart = 5;
int bpos = bpstart;
int k, n;
if (len <= 0)
return;
n = 0;
memset(buff, ' ', 80);
buff[80] = '\0';
for (k = 0; k < len; k++) {
c = *p++;
bpos += 3;
if (bpos == (bpstart + (9 * 3)))
bpos++;
my_snprintf(&buff[bpos], (int)sizeof(buff) - bpos, "%.2x",
(int)(unsigned char)c);
buff[bpos + 2] = ' ';
if ((k > 0) && (0 == ((k + 1) % 16))) {
n += my_snprintf(b + n, b_len - n, "%.60s\n", buff);
if (n >= (b_len - 1))
return;
bpos = bpstart;
memset(buff, ' ', 80);
}
}
if (bpos > bpstart)
n += my_snprintf(b + n, b_len - n, "%.60s\n", buff);
return;
}
/* Returns 1 when executed on big endian machine; else returns 0.
Useful for displaying ATA identify words (which need swapping on a
big endian machine). */
int
sg_is_big_endian()
{
union u_t {
unsigned short s;
unsigned char c[sizeof(unsigned short)];
} u;
u.s = 0x0102;
return (u.c[0] == 0x01); /* The lowest address contains
the most significant byte */
}
static unsigned short
swapb_ushort(unsigned short u)
{
unsigned short r;
r = (u >> 8) & 0xff;
r |= ((u & 0xff) << 8);
return r;
}
/* Note the ASCII-hex output goes to stdout. [Most other output from functions
in this file go to sg_warnings_strm (default stderr).]
'no_ascii' allows for 3 output types:
> 0 each line has address then up to 8 ASCII-hex 16 bit words
= 0 in addition, the ASCI bytes pairs are listed to the right
= -1 only the ASCII-hex words are listed (i.e. without address)
= -2 only the ASCII-hex words, formatted for "hdparm --Istdin"
< -2 same as -1
If 'swapb' non-zero then bytes in each word swapped. Needs to be set
for ATA IDENTIFY DEVICE response on big-endian machines. */
void
dWordHex(const unsigned short* words, int num, int no_ascii, int swapb)
{
const unsigned short * p = words;
unsigned short c;
char buff[82];
unsigned char upp, low;
int a = 0;
const int bpstart = 3;
const int cpstart = 52;
int cpos = cpstart;
int bpos = bpstart;
int i, k, blen;
if (num <= 0)
return;
blen = (int)sizeof(buff);
memset(buff, ' ', 80);
buff[80] = '\0';
if (no_ascii < 0) {
for (k = 0; k < num; k++) {
c = *p++;
if (swapb)
c = swapb_ushort(c);
bpos += 5;
my_snprintf(&buff[bpos], blen - bpos, "%.4x", (unsigned int)c);
buff[bpos + 4] = ' ';
if ((k > 0) && (0 == ((k + 1) % 8))) {
if (-2 == no_ascii)
printf("%.39s\n", buff +8);
else
printf("%.47s\n", buff);
bpos = bpstart;
memset(buff, ' ', 80);
}
}
if (bpos > bpstart) {
if (-2 == no_ascii)
printf("%.39s\n", buff +8);
else
printf("%.47s\n", buff);
}
return;
}
/* no_ascii>=0, start each line with address (offset) */
k = my_snprintf(buff + 1, blen - 1, "%.2x", a);
buff[k + 1] = ' ';
for (i = 0; i < num; i++) {
c = *p++;
if (swapb)
c = swapb_ushort(c);
bpos += 5;
my_snprintf(&buff[bpos], blen - bpos, "%.4x", (unsigned int)c);
buff[bpos + 4] = ' ';
if (no_ascii) {
buff[cpos++] = ' ';
buff[cpos++] = ' ';
buff[cpos++] = ' ';
} else {
upp = (c >> 8) & 0xff;
low = c & 0xff;
if ((upp < 0x20) || (upp >= 0x7f))
upp = '.';
buff[cpos++] = upp;
if ((low < 0x20) || (low >= 0x7f))
low = '.';
buff[cpos++] = low;
buff[cpos++] = ' ';
}
if (cpos > (cpstart + 23)) {
printf("%.76s\n", buff);
bpos = bpstart;
cpos = cpstart;
a += 8;
memset(buff, ' ', 80);
k = my_snprintf(buff + 1, blen - 1, "%.2x", a);
buff[k + 1] = ' ';
}
}
if (cpos > cpstart)
printf("%.76s\n", buff);
}
/* If the number in 'buf' can be decoded or the multiplier is unknown
then -1 is returned. Accepts a hex prefix (0x or 0X) or a decimal
multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
Main (SI) multipliers supported: K, M, G. */
int
sg_get_num(const char * buf)
{
int res, num, n, len;
unsigned int unum;
char * cp;
char c = 'c';
char c2, c3;
if ((NULL == buf) || ('\0' == buf[0]))
return -1;
len = strlen(buf);
if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
res = sscanf(buf + 2, "%x", &unum);
num = unum;
} else if ('H' == toupper((int)buf[len - 1])) {
res = sscanf(buf, "%x", &unum);
num = unum;
} else
res = sscanf(buf, "%d%c%c%c", &num, &c, &c2, &c3);
if (res < 1)
return -1LL;
else if (1 == res)
return num;
else {
if (res > 2)
c2 = toupper((int)c2);
if (res > 3)
c3 = toupper((int)c3);
switch (toupper((int)c)) {
case 'C':
return num;
case 'W':
return num * 2;
case 'B':
return num * 512;
case 'K':
if (2 == res)
return num * 1024;
if (('B' == c2) || ('D' == c2))
return num * 1000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1024;
return -1;
case 'M':
if (2 == res)
return num * 1048576;
if (('B' == c2) || ('D' == c2))
return num * 1000000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1048576;
return -1;
case 'G':
if (2 == res)
return num * 1073741824;
if (('B' == c2) || ('D' == c2))
return num * 1000000000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1073741824;
return -1;
case 'X':
cp = (char *)strchr(buf, 'x');
if (NULL == cp)
cp = (char *)strchr(buf, 'X');
if (cp) {
n = sg_get_num(cp + 1);
if (-1 != n)
return num * n;
}
return -1;
default:
if (NULL == sg_warnings_strm)
sg_warnings_strm = stderr;
fprintf(sg_warnings_strm, "unrecognized multiplier\n");
return -1;
}
}
}
/* If the number in 'buf' can not be decoded then -1 is returned. Accepts a
hex prefix (0x or 0X) or a 'h' (or 'H') suffix; otherwise decimal is
assumed. Does not accept multipliers. Accept a comma (","), a whitespace
or newline as terminator. */
int
sg_get_num_nomult(const char * buf)
{
int res, len, num;
unsigned int unum;
char * commap;
if ((NULL == buf) || ('\0' == buf[0]))
return -1;
len = strlen(buf);
commap = (char *)strchr(buf + 1, ',');
if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
res = sscanf(buf + 2, "%x", &unum);
num = unum;
} else if (commap && ('H' == toupper((int)*(commap - 1)))) {
res = sscanf(buf, "%x", &unum);
num = unum;
} else if ((NULL == commap) && ('H' == toupper((int)buf[len - 1]))) {
res = sscanf(buf, "%x", &unum);
num = unum;
} else
res = sscanf(buf, "%d", &num);
if (1 == res)
return num;
else
return -1;
}
/* If the number in 'buf' can be decoded or the multiplier is unknown
then -1LL is returned. Accepts a hex prefix (0x or 0X) or a decimal
multiplier suffix (as per GNU's dd (since 2002: SI and IEC 60027-2)).
Main (SI) multipliers supported: K, M, G, T, P. */
int64_t
sg_get_llnum(const char * buf)
{
int res, len;
int64_t num, ll;
uint64_t unum;
char * cp;
char c = 'c';
char c2, c3;
if ((NULL == buf) || ('\0' == buf[0]))
return -1LL;
len = strlen(buf);
if (('0' == buf[0]) && (('x' == buf[1]) || ('X' == buf[1]))) {
res = sscanf(buf + 2, "%" SCNx64 "", &unum);
num = unum;
} else if ('H' == toupper((int)buf[len - 1])) {
res = sscanf(buf, "%" SCNx64 "", &unum);
num = unum;
} else
res = sscanf(buf, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3);
if (res < 1)
return -1LL;
else if (1 == res)
return num;
else {
if (res > 2)
c2 = toupper((int)c2);
if (res > 3)
c3 = toupper((int)c3);
switch (toupper((int)c)) {
case 'C':
return num;
case 'W':
return num * 2;
case 'B':
return num * 512;
case 'K':
if (2 == res)
return num * 1024;
if (('B' == c2) || ('D' == c2))
return num * 1000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1024;
return -1LL;
case 'M':
if (2 == res)
return num * 1048576;
if (('B' == c2) || ('D' == c2))
return num * 1000000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1048576;
return -1LL;
case 'G':
if (2 == res)
return num * 1073741824;
if (('B' == c2) || ('D' == c2))
return num * 1000000000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1073741824;
return -1LL;
case 'T':
if (2 == res)
return num * 1099511627776LL;
if (('B' == c2) || ('D' == c2))
return num * 1000000000000LL;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1099511627776LL;
return -1LL;
case 'P':
if (2 == res)
return num * 1099511627776LL * 1024;
if (('B' == c2) || ('D' == c2))
return num * 1000000000000LL * 1000;
if (('I' == c2) && (4 == res) && ('B' == c3))
return num * 1099511627776LL * 1024;
return -1LL;
case 'X':
cp = (char *)strchr(buf, 'x');
if (NULL == cp)
cp = (char *)strchr(buf, 'X');
if (cp) {
ll = sg_get_llnum(cp + 1);
if (-1LL != ll)
return num * ll;
}
return -1LL;
default:
if (NULL == sg_warnings_strm)
sg_warnings_strm = stderr;
fprintf(sg_warnings_strm, "unrecognized multiplier\n");
return -1LL;
}
}
}
/* Extract character sequence from ATA words as in the model string
in a IDENTIFY DEVICE response. Returns number of characters
written to 'ochars' before 0 character is found or 'num' words
are processed. */
int
sg_ata_get_chars(const unsigned short * word_arr, int start_word,
int num_words, int is_big_endian, char * ochars)
{
int k;
unsigned short s;
char a, b;
char * op = ochars;
for (k = start_word; k < (start_word + num_words); ++k) {
s = word_arr[k];
if (is_big_endian) {
a = s & 0xff;
b = (s >> 8) & 0xff;
} else {
a = (s >> 8) & 0xff;
b = s & 0xff;
}
if (a == 0)
break;
*op++ = a;
if (b == 0)
break;
*op++ = b;
}
return op - ochars;
}
const char *
sg_lib_version()
{
return sg_lib_version_str;
}
#ifdef SG_LIB_MINGW
/* Non Unix OSes distinguish between text and binary files.
Set text mode on fd. Does nothing in Unix. Returns negative number on
failure. */
#include <unistd.h>
#include <fcntl.h>
int
sg_set_text_mode(int fd)
{
return setmode(fd, O_TEXT);
}
/* Set binary mode on fd. Does nothing in Unix. Returns negative number on
failure. */
int
sg_set_binary_mode(int fd)
{
return setmode(fd, O_BINARY);
}
#else
/* For Unix the following functions are dummies. */
int
sg_set_text_mode(int fd)
{
return fd; /* fd should be >= 0 */
}
int
sg_set_binary_mode(int fd)
{
return fd;
}
#endif