4089 lines
142 KiB
C
4089 lines
142 KiB
C
|
|
/*
|
||
|
|
* Copyright (c) 1999-2022 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
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* 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 https://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]
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|
||
|
|
#define _POSIX_C_SOURCE 200809L /* for posix_memalign() */
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stdarg.h>
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <ctype.h>
|
||
|
|
#define __STDC_FORMAT_MACROS 1
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <fcntl.h>
|
||
|
|
#include <sys/types.h>
|
||
|
|
#include <sys/stat.h>
|
||
|
|
|
||
|
|
#ifdef HAVE_CONFIG_H
|
||
|
|
#include "config.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include "sg_lib.h"
|
||
|
|
#include "sg_lib_data.h"
|
||
|
|
#include "sg_unaligned.h"
|
||
|
|
#include "sg_pr2serr.h"
|
||
|
|
|
||
|
|
/* sg_lib_version_str (and datestamp) defined in sg_lib_data.c file */
|
||
|
|
|
||
|
|
#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d /* corresponding ASC is 0 */
|
||
|
|
|
||
|
|
typedef unsigned int my_uint; /* convenience to save a few line wraps */
|
||
|
|
|
||
|
|
FILE * sg_warnings_strm = NULL; /* would like to default to stderr */
|
||
|
|
|
||
|
|
|
||
|
|
int
|
||
|
|
pr2ws(const char * fmt, ...)
|
||
|
|
{
|
||
|
|
va_list args;
|
||
|
|
int n;
|
||
|
|
|
||
|
|
va_start(args, fmt);
|
||
|
|
n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args);
|
||
|
|
va_end(args);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Want safe, 'n += snprintf(b + n, blen - n, ...)' style sequence of
|
||
|
|
* functions. Returns 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). Linux kernel has a similar function called scnprintf(). Public
|
||
|
|
* declaration in sg_pr2serr.h header */
|
||
|
|
int
|
||
|
|
sg_scnpr(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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Simple ASCII printable (does not use locale), includes space and excludes
|
||
|
|
* DEL (0x7f). */
|
||
|
|
static inline int
|
||
|
|
my_isprint(int ch)
|
||
|
|
{
|
||
|
|
return ((ch >= ' ') && (ch < 0x7f));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* DSENSE is 'descriptor sense' as opposed to the older 'fixed sense'.
|
||
|
|
* Only (currently) used in SNTL. */
|
||
|
|
bool
|
||
|
|
sg_get_initial_dsense(void)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
const char * cp;
|
||
|
|
|
||
|
|
cp = getenv("SG3_UTILS_DSENSE");
|
||
|
|
if (cp) {
|
||
|
|
if (1 == sscanf(cp, "%d", &k))
|
||
|
|
return k ? true : false;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 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;
|
||
|
|
|
||
|
|
if (peri_type < 0)
|
||
|
|
peri_type = 0;
|
||
|
|
for (; vp->name; ++vp) {
|
||
|
|
if (value == vp->value) {
|
||
|
|
if (sg_pdt_s_eq(peri_type, vp->peri_dev_type))
|
||
|
|
return vp;
|
||
|
|
holdp = vp;
|
||
|
|
while ((vp + 1)->name && (value == (vp + 1)->value)) {
|
||
|
|
++vp;
|
||
|
|
if (sg_pdt_s_eq(peri_type, vp->peri_dev_type))
|
||
|
|
return vp;
|
||
|
|
}
|
||
|
|
return holdp;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* If this function is not called, sg_warnings_strm will be NULL and all users
|
||
|
|
* (mainly fprintf() ) need to check and substitute stderr as required */
|
||
|
|
void
|
||
|
|
sg_set_warnings_strm(FILE * warnings_strm)
|
||
|
|
{
|
||
|
|
sg_warnings_strm = warnings_strm;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Take care to minimize printf() parsing delays when printing commands */
|
||
|
|
static char bin2hexascii[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
||
|
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||
|
|
|
||
|
|
|
||
|
|
/* Given a SCSI command pointed to by cdbp of sz bytes this function forms
|
||
|
|
* a SCSI command in ASCII surrounded by square brackets in 'b'. 'b' is at
|
||
|
|
* least blen bytes long. If cmd_name is true then the command is prefixed
|
||
|
|
* by its SCSI command name (e.g. "VERIFY(10) [2f ...]". The command is
|
||
|
|
* shown as spaced separated pairs of hexadecimal digits (i.e. 0-9, a-f).
|
||
|
|
* Each pair represents byte. The leftmost pair of digits is cdbp[0] . If
|
||
|
|
* sz <= 0 then this function tries to guess the length of the command. */
|
||
|
|
char *
|
||
|
|
sg_get_command_str(const uint8_t * cdbp, int sz, bool cmd_name, int blen,
|
||
|
|
char * b)
|
||
|
|
{
|
||
|
|
int k, j, jj;
|
||
|
|
|
||
|
|
if ((cdbp == NULL) || (b == NULL) || (blen < 1))
|
||
|
|
return b;
|
||
|
|
if (cmd_name && (blen > 16)) {
|
||
|
|
sg_get_command_name(cdbp, 0, blen, b);
|
||
|
|
j = (int)strlen(b);
|
||
|
|
if (j < (blen - 1))
|
||
|
|
b[j++] = ' ';
|
||
|
|
} else
|
||
|
|
j = 0;
|
||
|
|
if (j >= blen)
|
||
|
|
goto fini;
|
||
|
|
b[j++] = '[';
|
||
|
|
if (j >= blen)
|
||
|
|
goto fini;
|
||
|
|
if (sz <= 0) {
|
||
|
|
if (SG_VARIABLE_LENGTH_CMD == cdbp[0])
|
||
|
|
sz = cdbp[7] + 8;
|
||
|
|
else
|
||
|
|
sz = sg_get_command_size(cdbp[0]);
|
||
|
|
}
|
||
|
|
jj = j;
|
||
|
|
for (k = 0; (k < sz) && (j < (blen - 3)); ++k, j += 3, ++cdbp) {
|
||
|
|
b[j] = bin2hexascii[(*cdbp >> 4) & 0xf];
|
||
|
|
b[j + 1] = bin2hexascii[*cdbp & 0xf];
|
||
|
|
b[j + 2] = ' ';
|
||
|
|
}
|
||
|
|
if (j > jj)
|
||
|
|
--j; /* don't want trailing space before ']' */
|
||
|
|
if (j >= blen)
|
||
|
|
goto fini;
|
||
|
|
b[j++] = ']';
|
||
|
|
fini:
|
||
|
|
if (j >= blen)
|
||
|
|
b[blen - 1] = '\0'; /* truncated string */
|
||
|
|
else
|
||
|
|
b[j] = '\0';
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define CMD_NAME_LEN 128
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_print_command_len(const uint8_t * cdbp, int sz)
|
||
|
|
{
|
||
|
|
char buff[CMD_NAME_LEN];
|
||
|
|
|
||
|
|
sg_get_command_str(cdbp, sz, true, sizeof(buff), buff);
|
||
|
|
pr2ws("%s\n", buff);
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_print_command(const uint8_t * cdbp)
|
||
|
|
{
|
||
|
|
sg_print_command_len(cdbp, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool
|
||
|
|
sg_scsi_status_is_good(int sstatus)
|
||
|
|
{
|
||
|
|
sstatus &= 0xfe;
|
||
|
|
switch (sstatus) {
|
||
|
|
case SAM_STAT_GOOD:
|
||
|
|
case SAM_STAT_CONDITION_MET:
|
||
|
|
return true;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool
|
||
|
|
sg_scsi_status_is_bad(int sstatus)
|
||
|
|
{
|
||
|
|
sstatus &= 0xfe;
|
||
|
|
switch (sstatus) {
|
||
|
|
case SAM_STAT_GOOD:
|
||
|
|
case SAM_STAT_CONDITION_MET:
|
||
|
|
return false;
|
||
|
|
default:
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_get_scsi_status_str(int scsi_status, int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
const struct sg_lib_simple_value_name_t * sstatus_p;
|
||
|
|
|
||
|
|
if ((NULL == buff) || (buff_len < 1))
|
||
|
|
return;
|
||
|
|
else if (1 == buff_len) {
|
||
|
|
buff[0] = '\0';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
scsi_status &= 0x7e; /* sanitize as much as possible */
|
||
|
|
for (sstatus_p = sg_lib_sstatus_str_arr; sstatus_p->name; ++sstatus_p) {
|
||
|
|
if (scsi_status == sstatus_p->value)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (sstatus_p->name)
|
||
|
|
sg_scnpr(buff, buff_len, "%s", sstatus_p->name);
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "Unknown status [0x%x]", scsi_status);
|
||
|
|
}
|
||
|
|
|
||
|
|
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';
|
||
|
|
pr2ws("%s ", buff);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Get sense key from sense buffer. If successful returns a sense key value
|
||
|
|
* between 0 and 15. If sense buffer cannot be decode, returns -1 . */
|
||
|
|
int
|
||
|
|
sg_get_sense_key(const uint8_t * sbp, int sb_len)
|
||
|
|
{
|
||
|
|
if ((NULL == sbp) || (sb_len < 2))
|
||
|
|
return -1;
|
||
|
|
switch (sbp[0] & 0x7f) {
|
||
|
|
case 0x70:
|
||
|
|
case 0x71:
|
||
|
|
return (sb_len < 3) ? -1 : (sbp[2] & 0xf);
|
||
|
|
case 0x72:
|
||
|
|
case 0x73:
|
||
|
|
return sbp[1] & 0xf;
|
||
|
|
default:
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Yield string associated with sense_key value. Returns '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))
|
||
|
|
sg_scnpr(buff, buff_len, "%s", sg_lib_sense_key_desc[sense_key]);
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "invalid value: 0x%x", sense_key);
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
|
||
|
|
char *
|
||
|
|
sg_get_additional_sense_str(int asc, int ascq, bool add_sense_leadin,
|
||
|
|
int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
int k, num, rlen;
|
||
|
|
bool found = false;
|
||
|
|
|
||
|
|
if (1 == buff_len) {
|
||
|
|
buff[0] = '\0';
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
for (k = 0; sg_lib_asc_ascq_range[k].text; ++k) {
|
||
|
|
struct sg_lib_asc_ascq_range_t * ei2p = &sg_lib_asc_ascq_range[k];
|
||
|
|
|
||
|
|
if ((ei2p->asc == asc) &&
|
||
|
|
(ascq >= ei2p->ascq_min) &&
|
||
|
|
(ascq <= ei2p->ascq_max)) {
|
||
|
|
found = true;
|
||
|
|
if (add_sense_leadin)
|
||
|
|
num = sg_scnpr(buff, buff_len, "Additional sense: ");
|
||
|
|
else
|
||
|
|
num = 0;
|
||
|
|
rlen = buff_len - num;
|
||
|
|
sg_scnpr(buff + num, ((rlen > 0) ? rlen : 0), ei2p->text, ascq);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (found)
|
||
|
|
return buff;
|
||
|
|
|
||
|
|
for (k = 0; sg_lib_asc_ascq[k].text; ++k) {
|
||
|
|
struct sg_lib_asc_ascq_t * eip = &sg_lib_asc_ascq[k];
|
||
|
|
|
||
|
|
if (eip->asc == asc &&
|
||
|
|
eip->ascq == ascq) {
|
||
|
|
found = true;
|
||
|
|
if (add_sense_leadin)
|
||
|
|
sg_scnpr(buff, buff_len, "Additional sense: %s", eip->text);
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "%s", eip->text);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (! found) {
|
||
|
|
if (asc >= 0x80)
|
||
|
|
sg_scnpr(buff, buff_len, "vendor specific ASC=%02x, ASCQ=%02x "
|
||
|
|
"(hex)", asc, ascq);
|
||
|
|
else if (ascq >= 0x80)
|
||
|
|
sg_scnpr(buff, buff_len, "ASC=%02x, vendor specific qualification "
|
||
|
|
"ASCQ=%02x (hex)", asc, ascq);
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "ASC=%02x, ASCQ=%02x (hex)", asc, ascq);
|
||
|
|
}
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Yield string associated with ASC/ASCQ values. Returns 'buff'. */
|
||
|
|
char *
|
||
|
|
sg_get_asc_ascq_str(int asc, int ascq, int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
return sg_get_additional_sense_str(asc, ascq, true, buff_len, buff);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Attempt to find the first SCSI sense data descriptor that matches the
|
||
|
|
* given 'desc_type'. If found return pointer to start of sense data
|
||
|
|
* descriptor; otherwise (including fixed format sense data) returns NULL. */
|
||
|
|
const uint8_t *
|
||
|
|
sg_scsi_sense_desc_find(const uint8_t * sbp, int sb_len,
|
||
|
|
int desc_type)
|
||
|
|
{
|
||
|
|
int add_sb_len, desc_len, k;
|
||
|
|
const uint8_t * descp;
|
||
|
|
|
||
|
|
if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
|
||
|
|
return NULL;
|
||
|
|
if ((sbp[0] < 0x72) || (sbp[0] > 0x73))
|
||
|
|
return NULL;
|
||
|
|
add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
|
||
|
|
descp = &sbp[8];
|
||
|
|
for (desc_len = 0, k = 0; k < add_sb_len; k += desc_len) {
|
||
|
|
int add_d_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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns true if valid bit set, false if valid bit clear. Irrespective the
|
||
|
|
* information field is written out via 'info_outp' (except when it is
|
||
|
|
* NULL). Handles both fixed and descriptor sense formats. */
|
||
|
|
bool
|
||
|
|
sg_get_sense_info_fld(const uint8_t * sbp, int sb_len,
|
||
|
|
uint64_t * info_outp)
|
||
|
|
{
|
||
|
|
const uint8_t * bp;
|
||
|
|
|
||
|
|
if (info_outp)
|
||
|
|
*info_outp = 0;
|
||
|
|
if (sb_len < 7)
|
||
|
|
return false;
|
||
|
|
switch (sbp[0] & 0x7f) {
|
||
|
|
case 0x70:
|
||
|
|
case 0x71:
|
||
|
|
if (info_outp)
|
||
|
|
*info_outp = sg_get_unaligned_be32(sbp + 3);
|
||
|
|
return !!(sbp[0] & 0x80);
|
||
|
|
case 0x72:
|
||
|
|
case 0x73:
|
||
|
|
bp = sg_scsi_sense_desc_find(sbp, sb_len, 0 /* info desc */);
|
||
|
|
if (bp && (0xa == bp[1])) {
|
||
|
|
uint64_t ull = sg_get_unaligned_be64(bp + 4);
|
||
|
|
|
||
|
|
if (info_outp)
|
||
|
|
*info_outp = ull;
|
||
|
|
return !!(bp[2] & 0x80); /* since spc3r23 should be set */
|
||
|
|
} else
|
||
|
|
return false;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns true if fixed format or command specific information descriptor
|
||
|
|
* is found in the descriptor sense; else false. If available the command
|
||
|
|
* specific information field (4 byte integer in fixed format, 8 byte
|
||
|
|
* integer in descriptor format) is written out via 'cmd_spec_outp'.
|
||
|
|
* Handles both fixed and descriptor sense formats. */
|
||
|
|
bool
|
||
|
|
sg_get_sense_cmd_spec_fld(const uint8_t * sbp, int sb_len,
|
||
|
|
uint64_t * cmd_spec_outp)
|
||
|
|
{
|
||
|
|
const uint8_t * bp;
|
||
|
|
|
||
|
|
if (cmd_spec_outp)
|
||
|
|
*cmd_spec_outp = 0;
|
||
|
|
if (sb_len < 7)
|
||
|
|
return false;
|
||
|
|
switch (sbp[0] & 0x7f) {
|
||
|
|
case 0x70:
|
||
|
|
case 0x71:
|
||
|
|
if (cmd_spec_outp)
|
||
|
|
*cmd_spec_outp = sg_get_unaligned_be32(sbp + 8);
|
||
|
|
return true;
|
||
|
|
case 0x72:
|
||
|
|
case 0x73:
|
||
|
|
bp = sg_scsi_sense_desc_find(sbp, sb_len,
|
||
|
|
1 /* command specific info desc */);
|
||
|
|
if (bp && (0xa == bp[1])) {
|
||
|
|
if (cmd_spec_outp)
|
||
|
|
*cmd_spec_outp = sg_get_unaligned_be64(bp + 4);
|
||
|
|
return true;
|
||
|
|
} else
|
||
|
|
return false;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns true if any of the 3 bits (i.e. FILEMARK, EOM or ILI) are set.
|
||
|
|
* In descriptor format if the stream commands descriptor not found
|
||
|
|
* then returns false. Writes true or false corresponding to these bits to
|
||
|
|
* the last three arguments if they are non-NULL. */
|
||
|
|
bool
|
||
|
|
sg_get_sense_filemark_eom_ili(const uint8_t * sbp, int sb_len,
|
||
|
|
bool * filemark_p, bool * eom_p, bool * ili_p)
|
||
|
|
{
|
||
|
|
const uint8_t * bp;
|
||
|
|
|
||
|
|
if (sb_len < 7)
|
||
|
|
return false;
|
||
|
|
switch (sbp[0] & 0x7f) {
|
||
|
|
case 0x70:
|
||
|
|
case 0x71:
|
||
|
|
if (sbp[2] & 0xe0) {
|
||
|
|
if (filemark_p)
|
||
|
|
*filemark_p = !!(sbp[2] & 0x80);
|
||
|
|
if (eom_p)
|
||
|
|
*eom_p = !!(sbp[2] & 0x40);
|
||
|
|
if (ili_p)
|
||
|
|
*ili_p = !!(sbp[2] & 0x20);
|
||
|
|
return true;
|
||
|
|
} else
|
||
|
|
return false;
|
||
|
|
case 0x72:
|
||
|
|
case 0x73:
|
||
|
|
/* Look for stream commands sense data descriptor */
|
||
|
|
bp = sg_scsi_sense_desc_find(sbp, sb_len, 4);
|
||
|
|
if (bp && (bp[1] >= 2)) {
|
||
|
|
if (bp[3] & 0xe0) {
|
||
|
|
if (filemark_p)
|
||
|
|
*filemark_p = !!(bp[3] & 0x80);
|
||
|
|
if (eom_p)
|
||
|
|
*eom_p = !!(bp[3] & 0x40);
|
||
|
|
if (ili_p)
|
||
|
|
*ili_p = !!(bp[3] & 0x20);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns true if SKSV is set and sense key is NO_SENSE or NOT_READY. Also
|
||
|
|
* returns true if progress indication sense data descriptor found. Places
|
||
|
|
* progress field from sense data where progress_outp points. If progress
|
||
|
|
* field is not available returns false and *progress_outp is unaltered.
|
||
|
|
* Handles both fixed and descriptor sense formats.
|
||
|
|
* Hint: if true is returned *progress_outp may be multiplied by 100 then
|
||
|
|
* divided by 65536 to get the percentage completion. */
|
||
|
|
bool
|
||
|
|
sg_get_sense_progress_fld(const uint8_t * sbp, int sb_len,
|
||
|
|
int * progress_outp)
|
||
|
|
{
|
||
|
|
const uint8_t * bp;
|
||
|
|
int sk, sk_pr;
|
||
|
|
|
||
|
|
if (sb_len < 7)
|
||
|
|
return false;
|
||
|
|
switch (sbp[0] & 0x7f) {
|
||
|
|
case 0x70:
|
||
|
|
case 0x71:
|
||
|
|
sk = (sbp[2] & 0xf);
|
||
|
|
if ((sb_len < 18) ||
|
||
|
|
((SPC_SK_NO_SENSE != sk) && (SPC_SK_NOT_READY != sk)))
|
||
|
|
return false;
|
||
|
|
if (sbp[15] & 0x80) { /* SKSV bit set */
|
||
|
|
if (progress_outp)
|
||
|
|
*progress_outp = sg_get_unaligned_be16(sbp + 16);
|
||
|
|
return true;
|
||
|
|
} else
|
||
|
|
return false;
|
||
|
|
case 0x72:
|
||
|
|
case 0x73:
|
||
|
|
/* sense key specific progress (0x2) or progress descriptor (0xa) */
|
||
|
|
sk = (sbp[1] & 0xf);
|
||
|
|
sk_pr = (SPC_SK_NO_SENSE == sk) || (SPC_SK_NOT_READY == sk);
|
||
|
|
if (sk_pr && ((bp = sg_scsi_sense_desc_find(sbp, sb_len, 2))) &&
|
||
|
|
(0x6 == bp[1]) && (0x80 & bp[4])) {
|
||
|
|
if (progress_outp)
|
||
|
|
*progress_outp = sg_get_unaligned_be16(bp + 5);
|
||
|
|
return true;
|
||
|
|
} else if (((bp = sg_scsi_sense_desc_find(sbp, sb_len, 0xa))) &&
|
||
|
|
((0x6 == bp[1]))) {
|
||
|
|
if (progress_outp)
|
||
|
|
*progress_outp = sg_get_unaligned_be16(bp + 6);
|
||
|
|
return true;
|
||
|
|
} else
|
||
|
|
return false;
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
char *
|
||
|
|
sg_get_pdt_str(int pdt, int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
if ((pdt < 0) || (pdt > PDT_MAX))
|
||
|
|
sg_scnpr(buff, buff_len, "bad pdt");
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "%s", sg_lib_pdt_strs[pdt]);
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns true if left argument is "equal" to the right argument. l_pdt_s
|
||
|
|
* is a compound PDT (SCSI Peripheral Device Type) or a negative number
|
||
|
|
* which represents a wildcard (i.e. match anything). r_pdt_s has a similar
|
||
|
|
* form. PDT values are 5 bits long (0 to 31) and a compound pdt_s is
|
||
|
|
* formed by shifting the second (upper) PDT by eight bits to the left and
|
||
|
|
* OR-ing it with the first PDT. The pdt_s values must be defined so
|
||
|
|
* PDT_DISK (0) is _not_ the upper value in a compound pdt_s. */
|
||
|
|
bool
|
||
|
|
sg_pdt_s_eq(int l_pdt_s, int r_pdt_s)
|
||
|
|
{
|
||
|
|
bool upper_l = !!(l_pdt_s & PDT_UPPER_MASK);
|
||
|
|
bool upper_r = !!(r_pdt_s & PDT_UPPER_MASK);
|
||
|
|
|
||
|
|
if ((l_pdt_s < 0) || (r_pdt_s < 0))
|
||
|
|
return true;
|
||
|
|
if (!upper_l && !upper_r)
|
||
|
|
return l_pdt_s == r_pdt_s;
|
||
|
|
else if (upper_l && upper_r)
|
||
|
|
return (((PDT_UPPER_MASK & l_pdt_s) == (PDT_UPPER_MASK & r_pdt_s)) ||
|
||
|
|
((PDT_LOWER_MASK & l_pdt_s) == (PDT_LOWER_MASK & r_pdt_s)));
|
||
|
|
else if (upper_l)
|
||
|
|
return (((PDT_LOWER_MASK & l_pdt_s) == r_pdt_s) ||
|
||
|
|
((PDT_UPPER_MASK & l_pdt_s) >> 8) == r_pdt_s);
|
||
|
|
return (((PDT_LOWER_MASK & r_pdt_s) == l_pdt_s) ||
|
||
|
|
((PDT_UPPER_MASK & r_pdt_s) >> 8) == l_pdt_s);
|
||
|
|
}
|
||
|
|
|
||
|
|
int
|
||
|
|
sg_lib_pdt_decay(int pdt)
|
||
|
|
{
|
||
|
|
if ((pdt < 0) || (pdt > PDT_MAX))
|
||
|
|
return 0;
|
||
|
|
return sg_lib_pdt_decay_arr[pdt];
|
||
|
|
}
|
||
|
|
|
||
|
|
char *
|
||
|
|
sg_get_trans_proto_str(int tpi, int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
if ((tpi < 0) || (tpi > 15))
|
||
|
|
sg_scnpr(buff, buff_len, "bad tpi");
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "%s", sg_lib_transport_proto_strs[tpi]);
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define TRANSPORT_ID_MIN_LEN 24
|
||
|
|
|
||
|
|
char *
|
||
|
|
sg_decode_transportid_str(const char * lip, uint8_t * bp, int bplen,
|
||
|
|
bool only_one, int blen, char * b)
|
||
|
|
{
|
||
|
|
int num, k, n;
|
||
|
|
uint64_t ull;
|
||
|
|
int bump;
|
||
|
|
|
||
|
|
if ((NULL == b) || (blen < 1))
|
||
|
|
return b;
|
||
|
|
else if (1 == blen) {
|
||
|
|
b[0] = '\0';
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
/* bump = TRANSPORT_ID_MIN_LEN; // some old compilers insisted on this */
|
||
|
|
for (k = 0, n = 0; bplen > 0; ++k, bp += bump, bplen -= bump) {
|
||
|
|
int proto_id, normal_len, tpid_format;
|
||
|
|
|
||
|
|
if ((k > 0) && only_one)
|
||
|
|
break;
|
||
|
|
if ((bplen < 24) || (0 != (bplen % 4)))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%sTransport Id short or not "
|
||
|
|
"multiple of 4 [length=%d]:\n", lip, blen);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%sTransport Id of initiator:\n",
|
||
|
|
lip);
|
||
|
|
tpid_format = ((bp[0] >> 6) & 0x3);
|
||
|
|
proto_id = (bp[0] & 0xf);
|
||
|
|
normal_len = (bplen > TRANSPORT_ID_MIN_LEN) ?
|
||
|
|
TRANSPORT_ID_MIN_LEN : bplen;
|
||
|
|
switch (proto_id) {
|
||
|
|
case TPROTO_FCP: /* Fibre channel */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s FCP-2 World Wide Name:\n",
|
||
|
|
lip);
|
||
|
|
if (0 != tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
|
||
|
|
"%d]\n", lip, tpid_format);
|
||
|
|
n += hex2str(bp + 8, 8, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_SPI: /* Scsi Parallel Interface, obsolete */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Parallel SCSI initiator SCSI "
|
||
|
|
"address: 0x%x\n", lip,
|
||
|
|
sg_get_unaligned_be16(bp + 2));
|
||
|
|
if (0 != tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
|
||
|
|
"%d]\n", lip, tpid_format);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s relative port number (of "
|
||
|
|
"corresponding target): 0x%x\n", lip,
|
||
|
|
sg_get_unaligned_be16(bp + 6));
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_SSA:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s SSA (transport id not "
|
||
|
|
"defined):\n", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
|
||
|
|
tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_1394: /* IEEE 1394 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s IEEE 1394 EUI-64 name:\n",
|
||
|
|
lip);
|
||
|
|
if (0 != tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
|
||
|
|
"%d]\n", lip, tpid_format);
|
||
|
|
n += hex2str(&bp[8], 8, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_SRP: /* SCSI over RDMA */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s RDMA initiator port "
|
||
|
|
"identifier:\n", lip);
|
||
|
|
if (0 != tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
|
||
|
|
"%d]\n", lip, tpid_format);
|
||
|
|
n += hex2str(bp + 8, 16, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_ISCSI:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s iSCSI ", lip);
|
||
|
|
num = sg_get_unaligned_be16(bp + 2);
|
||
|
|
if (0 == tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "name: %.*s\n", num, &bp[4]);
|
||
|
|
else if (1 == tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "world wide unique port id: "
|
||
|
|
"%.*s\n", num, &bp[4]);
|
||
|
|
else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, " [Unexpected TPID format: "
|
||
|
|
"%d]\n", tpid_format);
|
||
|
|
n += hex2str(bp, num + 4, lip, 0, blen - n, b + n);
|
||
|
|
}
|
||
|
|
bump = (((num + 4) < TRANSPORT_ID_MIN_LEN) ?
|
||
|
|
TRANSPORT_ID_MIN_LEN : num + 4);
|
||
|
|
break;
|
||
|
|
case TPROTO_SAS:
|
||
|
|
ull = sg_get_unaligned_be64(bp + 4);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s SAS address: 0x%" PRIx64 "\n",
|
||
|
|
lip, ull);
|
||
|
|
if (0 != tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [Unexpected TPID format: "
|
||
|
|
"%d]\n", lip, tpid_format);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_ADT: /* no TransportID defined by T10 yet */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s ADT:\n", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
|
||
|
|
tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_ATA: /* no TransportID defined by T10 yet */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s ATAPI:\n", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
|
||
|
|
tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_UAS: /* no TransportID defined by T10 yet */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s UAS:\n", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
|
||
|
|
tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_SOP:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s SOP ", lip);
|
||
|
|
num = sg_get_unaligned_be16(bp + 2);
|
||
|
|
if (0 == tpid_format)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Routing ID: 0x%x\n", num);
|
||
|
|
else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, " [Unexpected TPID format: "
|
||
|
|
"%d]\n", tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
}
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_PCIE: /* no TransportID defined by T10 yet */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s PCIE:\n", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s TPID format: %d\n", lip,
|
||
|
|
tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
case TPROTO_NONE: /* no TransportID defined by T10 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s No specified protocol\n",
|
||
|
|
lip);
|
||
|
|
/* n += hex2str(bp, ((bplen > 24) ? 24 : bplen),
|
||
|
|
* lip, 0, blen - n, b + n); */
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s unknown protocol id=0x%x "
|
||
|
|
"TPID format=%d\n", lip, proto_id, tpid_format);
|
||
|
|
n += hex2str(bp, normal_len, lip, 1, blen - n, b + n);
|
||
|
|
bump = TRANSPORT_ID_MIN_LEN;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static const char * desig_code_set_str_arr[] =
|
||
|
|
{
|
||
|
|
"Reserved [0x0]",
|
||
|
|
"Binary",
|
||
|
|
"ASCII",
|
||
|
|
"UTF-8",
|
||
|
|
"Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
|
||
|
|
"Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
|
||
|
|
"Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
|
||
|
|
};
|
||
|
|
|
||
|
|
const char *
|
||
|
|
sg_get_desig_code_set_str(int val)
|
||
|
|
{
|
||
|
|
if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_code_set_str_arr)))
|
||
|
|
return desig_code_set_str_arr[val];
|
||
|
|
else
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char * desig_assoc_str_arr[] =
|
||
|
|
{
|
||
|
|
"Addressed logical unit",
|
||
|
|
"Target port", /* that received request; unless SCSI ports VPD */
|
||
|
|
"Target device that contains addressed lu",
|
||
|
|
"Reserved [0x3]",
|
||
|
|
};
|
||
|
|
|
||
|
|
const char *
|
||
|
|
sg_get_desig_assoc_str(int val)
|
||
|
|
{
|
||
|
|
if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_assoc_str_arr)))
|
||
|
|
return desig_assoc_str_arr[val];
|
||
|
|
else
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char * desig_type_str_arr[] =
|
||
|
|
{
|
||
|
|
"Vendor specific [0x0]",
|
||
|
|
"T10 vendor identification",
|
||
|
|
"EUI-64 based",
|
||
|
|
"NAA",
|
||
|
|
"Relative target port",
|
||
|
|
"Target port group", /* spc4r09: _primary_ target port group */
|
||
|
|
"Logical unit group",
|
||
|
|
"MD5 logical unit identifier",
|
||
|
|
"SCSI name string",
|
||
|
|
"Protocol specific port identifier", /* spc4r36 */
|
||
|
|
"UUID identifier", /* spc5r08 */
|
||
|
|
"Reserved [0xb]",
|
||
|
|
"Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
|
||
|
|
};
|
||
|
|
|
||
|
|
const char *
|
||
|
|
sg_get_desig_type_str(int val)
|
||
|
|
{
|
||
|
|
if ((val >= 0) && (val < (int)SG_ARRAY_SIZE(desig_type_str_arr)))
|
||
|
|
return desig_type_str_arr[val];
|
||
|
|
else
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
char *
|
||
|
|
sg_get_zone_type_str(uint8_t zt, int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
if ((NULL == buff) || (buff_len < 1))
|
||
|
|
return NULL;
|
||
|
|
switch (zt) {
|
||
|
|
case 1:
|
||
|
|
sg_scnpr(buff, buff_len, "conventional");
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
sg_scnpr(buff, buff_len, "sequential write required");
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
sg_scnpr(buff, buff_len, "sequential write preferred");
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
sg_scnpr(buff, buff_len, "sequential or before required");
|
||
|
|
break;
|
||
|
|
case 5:
|
||
|
|
sg_scnpr(buff, buff_len, "gap");
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
sg_scnpr(buff, buff_len, "unknown [0x%x]", zt);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/* Expects a T10 UUID designator (as found in the Device Identification VPD
|
||
|
|
* page) pointed to by 'dp'. To not produce an error string in 'b', c_set
|
||
|
|
* should be 1 (binary) and dlen should be 18. Currently T10 only supports
|
||
|
|
* locally assigned UUIDs. Writes output to string 'b' of no more than blen
|
||
|
|
* bytes and returns the number of bytes actually written to 'b' but doesn't
|
||
|
|
* count the trailing null character it always appends (if blen > 0). 'lip'
|
||
|
|
* is lead-in string (on each line) than may be NULL. skip_prefix avoids
|
||
|
|
* outputting: ' Locally assigned UUID: ' before the UUID. */
|
||
|
|
int
|
||
|
|
sg_t10_uuid_desig2str(const uint8_t *dp, int dlen, int c_set, bool do_long,
|
||
|
|
bool skip_prefix, const char * lip /* lead-in */,
|
||
|
|
int blen, char * b)
|
||
|
|
{
|
||
|
|
int m;
|
||
|
|
int n = 0;
|
||
|
|
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
if (1 != c_set) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected binary "
|
||
|
|
"code_set >>\n", lip);
|
||
|
|
n += hex2str(dp, dlen, lip, 0, blen - n, b + n);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
if ((1 != ((dp[0] >> 4) & 0xf)) || (18 != dlen)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected locally "
|
||
|
|
"assigned UUID, 16 bytes long >>\n", lip);
|
||
|
|
n += hex2str(dp, dlen, lip, 0, blen - n, b + n);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
if (skip_prefix) {
|
||
|
|
if (strlen(lip) > 0)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s", lip);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Locally assigned UUID: ",
|
||
|
|
lip);
|
||
|
|
for (m = 0; m < 16; ++m) {
|
||
|
|
if ((4 == m) || (6 == m) || (8 == m) || (10 == m))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "-");
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)dp[2 + m]);
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
if (do_long) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
|
||
|
|
for (m = 0; m < 16; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)dp[2 + m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "]\n");
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
int
|
||
|
|
sg_get_designation_descriptor_str(const char * lip, const uint8_t * ddp,
|
||
|
|
int dd_len, bool print_assoc, bool do_long,
|
||
|
|
int blen, char * b)
|
||
|
|
{
|
||
|
|
int m, p_id, piv, c_set, assoc, desig_type, ci_off, c_id, d_id, naa;
|
||
|
|
int vsi, k, n, dlen;
|
||
|
|
uint64_t ccc_id, vsei;
|
||
|
|
const uint8_t * ip;
|
||
|
|
char e[64];
|
||
|
|
const char * cp;
|
||
|
|
|
||
|
|
n = 0;
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
if (dd_len < 4) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%sdesignator desc too short: got "
|
||
|
|
"length of %d want 4 or more\n", lip, dd_len);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
dlen = ddp[3];
|
||
|
|
if (dlen > (dd_len - 4)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%sdesignator too long: says it is %d "
|
||
|
|
"bytes, but given %d bytes\n", lip, dlen, dd_len - 4);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
ip = ddp + 4;
|
||
|
|
p_id = ((ddp[0] >> 4) & 0xf);
|
||
|
|
c_set = (ddp[0] & 0xf);
|
||
|
|
piv = ((ddp[1] & 0x80) ? 1 : 0);
|
||
|
|
assoc = ((ddp[1] >> 4) & 0x3);
|
||
|
|
desig_type = (ddp[1] & 0xf);
|
||
|
|
if (print_assoc && ((cp = sg_get_desig_assoc_str(assoc))))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s %s:\n", lip, cp);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s designator type: ", lip);
|
||
|
|
cp = sg_get_desig_type_str(desig_type);
|
||
|
|
if (cp)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s", cp);
|
||
|
|
n += sg_scnpr(b + n, blen - n, ", code set: ");
|
||
|
|
cp = sg_get_desig_code_set_str(c_set);
|
||
|
|
if (cp)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s", cp);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
if (piv && ((1 == assoc) || (2 == assoc)))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s transport: %s\n", lip,
|
||
|
|
sg_get_trans_proto_str(p_id, sizeof(e), e));
|
||
|
|
switch (desig_type) {
|
||
|
|
case 0: /* vendor specific */
|
||
|
|
k = 0;
|
||
|
|
if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */
|
||
|
|
for (k = 0; (k < dlen) && my_isprint(ip[k]); ++k)
|
||
|
|
;
|
||
|
|
if (k >= dlen)
|
||
|
|
k = 1;
|
||
|
|
}
|
||
|
|
if (k)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vendor specific: %.*s\n",
|
||
|
|
lip, dlen, ip);
|
||
|
|
else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vendor specific:\n", lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 1: /* T10 vendor identification */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vendor id: %.8s\n", lip, ip);
|
||
|
|
if (dlen > 8) {
|
||
|
|
if ((2 == c_set) || (3 == c_set)) { /* ASCII or UTF-8 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vendor specific: "
|
||
|
|
"%.*s\n", lip, dlen - 8, ip + 8);
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vendor specific: 0x",
|
||
|
|
lip);
|
||
|
|
for (m = 8; m < dlen; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 2: /* EUI-64 based */
|
||
|
|
if (! do_long) {
|
||
|
|
if ((8 != dlen) && (12 != dlen) && (16 != dlen)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expect 8, 12 and "
|
||
|
|
"16 byte EUI, got %d >>\n", lip, dlen);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
|
||
|
|
for (m = 0; m < dlen; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s EUI-64 based %d byte "
|
||
|
|
"identifier\n", lip, dlen);
|
||
|
|
if (1 != c_set) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected binary "
|
||
|
|
"code_set (1) >>\n", lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
ci_off = 0;
|
||
|
|
if (16 == dlen) { /* first 8 bytes are 'Identifier Extension' */
|
||
|
|
uint64_t id_ext = sg_get_unaligned_be64(ip);
|
||
|
|
|
||
|
|
ci_off = 8;
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Identifier extension: 0x%"
|
||
|
|
PRIx64 "\n", lip, id_ext);
|
||
|
|
} else if ((8 != dlen) && (12 != dlen)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << can only decode 8, 12 "
|
||
|
|
"and 16 byte ids >>\n", lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
ccc_id = sg_get_unaligned_be64(ip + ci_off);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s IEEE identifier: 0x%"
|
||
|
|
PRIx64 "\n", lip, ccc_id);
|
||
|
|
if (12 == dlen) {
|
||
|
|
d_id = sg_get_unaligned_be32(ip + 8);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Directory ID: 0x%x\n",
|
||
|
|
lip, d_id);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 3: /* NAA <n> */
|
||
|
|
if (1 != c_set) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << unexpected code set "
|
||
|
|
"%d for NAA >>\n", lip, c_set);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
naa = (ip[0] >> 4) & 0xff;
|
||
|
|
switch (naa) {
|
||
|
|
case 2: /* NAA 2: IEEE Extended */
|
||
|
|
if (8 != dlen) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 2 "
|
||
|
|
"identifier length: 0x%x >>\n", lip, dlen);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
d_id = (((ip[0] & 0xf) << 8) | ip[1]);
|
||
|
|
c_id = sg_get_unaligned_be24(ip + 2);
|
||
|
|
vsi = sg_get_unaligned_be24(ip + 5);
|
||
|
|
if (do_long) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s NAA 2, vendor "
|
||
|
|
"specific identifier A: 0x%x\n", lip, d_id);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s AOI: 0x%x\n", lip,
|
||
|
|
c_id);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vendor specific "
|
||
|
|
"identifier B: 0x%x\n", lip, vsi);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
|
||
|
|
for (m = 0; m < 8; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "]\n");
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
|
||
|
|
for (m = 0; m < 8; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
break;
|
||
|
|
case 3: /* NAA 3: Locally assigned */
|
||
|
|
if (8 != dlen) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 3 "
|
||
|
|
"identifier length: 0x%x >>\n", lip, dlen);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (do_long)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s NAA 3, Locally "
|
||
|
|
"assigned:\n", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
|
||
|
|
for (m = 0; m < 8; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
break;
|
||
|
|
case 5: /* NAA 5: IEEE Registered */
|
||
|
|
if (8 != dlen) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 5 "
|
||
|
|
"identifier length: 0x%x >>\n", lip, dlen);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) |
|
||
|
|
(ip[2] << 4) | ((ip[3] & 0xf0) >> 4));
|
||
|
|
vsei = ip[3] & 0xf;
|
||
|
|
for (m = 1; m < 5; ++m) {
|
||
|
|
vsei <<= 8;
|
||
|
|
vsei |= ip[3 + m];
|
||
|
|
}
|
||
|
|
if (do_long) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s NAA 5, AOI: 0x%x\n",
|
||
|
|
lip, c_id);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
|
||
|
|
"Identifier: 0x%" PRIx64 "\n", lip, vsei);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
|
||
|
|
for (m = 0; m < 8; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "]\n");
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
|
||
|
|
for (m = 0; m < 8; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 6: /* NAA 6: IEEE Registered extended */
|
||
|
|
if (16 != dlen) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA 6 "
|
||
|
|
"identifier length: 0x%x >>\n", lip, dlen);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) |
|
||
|
|
(ip[2] << 4) | ((ip[3] & 0xf0) >> 4));
|
||
|
|
vsei = ip[3] & 0xf;
|
||
|
|
for (m = 1; m < 5; ++m) {
|
||
|
|
vsei <<= 8;
|
||
|
|
vsei |= ip[3 + m];
|
||
|
|
}
|
||
|
|
if (do_long) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s NAA 6, AOI: 0x%x\n",
|
||
|
|
lip, c_id);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
|
||
|
|
"Identifier: 0x%" PRIx64 "\n", lip, vsei);
|
||
|
|
vsei = sg_get_unaligned_be64(ip + 8);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Vendor Specific "
|
||
|
|
"Identifier Extension: 0x%" PRIx64 "\n", lip,
|
||
|
|
vsei);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [0x", lip);
|
||
|
|
for (m = 0; m < 16; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "]\n");
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s 0x", lip);
|
||
|
|
for (m = 0; m < 16; ++m)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", (my_uint)ip[m]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << unexpected NAA [0x%x] "
|
||
|
|
">>\n", lip, naa);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 4: /* Relative target port */
|
||
|
|
if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected binary "
|
||
|
|
"code_set, target port association, length 4 >>\n",
|
||
|
|
lip);
|
||
|
|
n += hex2str(ip, dlen, "", 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
d_id = sg_get_unaligned_be16(ip + 2);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Relative target port: 0x%x\n",
|
||
|
|
lip, d_id);
|
||
|
|
break;
|
||
|
|
case 5: /* (primary) Target port group */
|
||
|
|
if ((1 != c_set) || (1 != assoc) || (4 != dlen)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected binary "
|
||
|
|
"code_set, target port association, length 4 >>\n",
|
||
|
|
lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
d_id = sg_get_unaligned_be16(ip + 2);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Target port group: 0x%x\n",
|
||
|
|
lip, d_id);
|
||
|
|
break;
|
||
|
|
case 6: /* Logical unit group */
|
||
|
|
if ((1 != c_set) || (0 != assoc) || (4 != dlen)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected binary "
|
||
|
|
"code_set, logical unit association, length 4 >>\n",
|
||
|
|
lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
d_id = sg_get_unaligned_be16(ip + 2);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Logical unit group: 0x%x\n",
|
||
|
|
lip, d_id);
|
||
|
|
break;
|
||
|
|
case 7: /* MD5 logical unit identifier */
|
||
|
|
if ((1 != c_set) || (0 != assoc)) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected binary "
|
||
|
|
"code_set, logical unit association >>\n", lip);
|
||
|
|
n += hex2str(ip, dlen, "", 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s MD5 logical unit "
|
||
|
|
"identifier:\n", lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
case 8: /* SCSI name string */
|
||
|
|
if (3 != c_set) { /* accept ASCII as subset of UTF-8 */
|
||
|
|
if (2 == c_set) {
|
||
|
|
if (do_long)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected "
|
||
|
|
"UTF-8, use ASCII >>\n", lip);
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s << expected UTF-8 "
|
||
|
|
"code_set >>\n", lip);
|
||
|
|
n += hex2str(ip, dlen, lip, 0, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s SCSI name string:\n", lip);
|
||
|
|
/* does %s print out UTF-8 ok??
|
||
|
|
* Seems to depend on the locale. Looks ok here with my
|
||
|
|
* locale setting: en_AU.UTF-8
|
||
|
|
*/
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s %.*s\n", lip, dlen,
|
||
|
|
(const char *)ip);
|
||
|
|
break;
|
||
|
|
case 9: /* Protocol specific port identifier */
|
||
|
|
/* added in spc4r36, PIV must be set, proto_id indicates */
|
||
|
|
/* whether UAS (USB) or SOP (PCIe) or ... */
|
||
|
|
if (! piv)
|
||
|
|
n += sg_scnpr(b + n, blen - n, " %s >>>> Protocol specific "
|
||
|
|
"port identifier expects protocol\n%s "
|
||
|
|
"identifier to be valid and it is not\n", lip, lip);
|
||
|
|
if (TPROTO_UAS == p_id) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s USB device address: "
|
||
|
|
"0x%x\n", lip, 0x7f & ip[0]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s USB interface number: "
|
||
|
|
"0x%x\n", lip, ip[2]);
|
||
|
|
} else if (TPROTO_SOP == p_id) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s PCIe routing ID, bus "
|
||
|
|
"number: 0x%x\n", lip, ip[0]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s function number: "
|
||
|
|
"0x%x\n", lip, ip[1]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [or device number: "
|
||
|
|
"0x%x, function number: 0x%x]\n", lip,
|
||
|
|
(0x1f & (ip[1] >> 3)), 0x7 & ip[1]);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s >>>> unexpected protocol "
|
||
|
|
"identifier: %s\n%s with Protocol "
|
||
|
|
"specific port identifier\n", lip,
|
||
|
|
sg_get_trans_proto_str(p_id, sizeof(e), e), lip);
|
||
|
|
break;
|
||
|
|
case 0xa: /* UUID identifier */
|
||
|
|
n += sg_t10_uuid_desig2str(ip, dlen, c_set, do_long, false, lip,
|
||
|
|
blen - n, b + n);
|
||
|
|
break;
|
||
|
|
default: /* reserved */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s reserved designator=0x%x\n",
|
||
|
|
lip, desig_type);
|
||
|
|
n += hex2str(ip, dlen, lip, 1, blen - n, b + n);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
decode_sks(const char * lip, const uint8_t * descp, int add_d_len,
|
||
|
|
int sense_key, bool * processedp, int blen, char * b)
|
||
|
|
{
|
||
|
|
int progress, pr, rem, n;
|
||
|
|
|
||
|
|
n = 0;
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
switch (sense_key) {
|
||
|
|
case SPC_SK_ILLEGAL_REQUEST:
|
||
|
|
if (add_d_len < 6) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Field pointer: ");
|
||
|
|
goto too_short;
|
||
|
|
}
|
||
|
|
/* abbreviate to fit on one line */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Field pointer:\n");
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Error in %s: byte %d", lip,
|
||
|
|
(descp[4] & 0x40) ? "Command" : "Data parameters",
|
||
|
|
sg_get_unaligned_be16(descp + 5));
|
||
|
|
if (descp[4] & 0x08) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
break;
|
||
|
|
case SPC_SK_HARDWARE_ERROR:
|
||
|
|
case SPC_SK_MEDIUM_ERROR:
|
||
|
|
case SPC_SK_RECOVERED_ERROR:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Actual retry count: ");
|
||
|
|
if (add_d_len < 6)
|
||
|
|
goto too_short;
|
||
|
|
n += sg_scnpr(b + n, blen - n,"%u\n",
|
||
|
|
sg_get_unaligned_be16(descp + 5));
|
||
|
|
break;
|
||
|
|
case SPC_SK_NO_SENSE:
|
||
|
|
case SPC_SK_NOT_READY:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Progress indication: ");
|
||
|
|
if (add_d_len < 6)
|
||
|
|
goto too_short;
|
||
|
|
progress = sg_get_unaligned_be16(descp + 5);
|
||
|
|
pr = (progress * 100) / 65536;
|
||
|
|
rem = ((progress * 100) % 65536) / 656;
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%d.%02d%%\n", pr, rem);
|
||
|
|
break;
|
||
|
|
case SPC_SK_COPY_ABORTED:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Segment pointer:\n");
|
||
|
|
if (add_d_len < 6)
|
||
|
|
goto too_short;
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Relative to start of %s, "
|
||
|
|
"byte %d", lip, (descp[4] & 0x20) ?
|
||
|
|
"segment descriptor" : "parameter list",
|
||
|
|
sg_get_unaligned_be16(descp + 5));
|
||
|
|
if (descp[4] & 0x08)
|
||
|
|
n += sg_scnpr(b + n, blen - n, " bit %d\n", descp[4] & 0x07);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
break;
|
||
|
|
case SPC_SK_UNIT_ATTENTION:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Unit attention condition queue:\n");
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s overflow flag is %d\n", lip,
|
||
|
|
!!(descp[4] & 0x1));
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Sense_key: 0x%x unexpected\n",
|
||
|
|
sense_key);
|
||
|
|
*processedp = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
|
||
|
|
too_short:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", " >> descriptor too short");
|
||
|
|
*processedp = false;
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
#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 sg_scnpr(b, blen, "active/optimized");
|
||
|
|
case TPGS_STATE_NONOPTIMIZED:
|
||
|
|
return sg_scnpr(b, blen, "active/non optimized");
|
||
|
|
case TPGS_STATE_STANDBY:
|
||
|
|
return sg_scnpr(b, blen, "standby");
|
||
|
|
case TPGS_STATE_UNAVAILABLE:
|
||
|
|
return sg_scnpr(b, blen, "unavailable");
|
||
|
|
case TPGS_STATE_OFFLINE:
|
||
|
|
return sg_scnpr(b, blen, "offline");
|
||
|
|
case TPGS_STATE_TRANSITIONING:
|
||
|
|
return sg_scnpr(b, blen, "transitioning between states");
|
||
|
|
default:
|
||
|
|
return sg_scnpr(b, blen, "unknown: 0x%x", st);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
uds_referral_descriptor_str(char * b, int blen, const uint8_t * dp,
|
||
|
|
int alen, const char * lip)
|
||
|
|
{
|
||
|
|
int n = 0;
|
||
|
|
int dlen = alen - 2;
|
||
|
|
int k, j, g, f;
|
||
|
|
const uint8_t * tp;
|
||
|
|
char c[40];
|
||
|
|
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Not all referrals: %d\n", lip,
|
||
|
|
!!(dp[2] & 0x1));
|
||
|
|
dp += 4;
|
||
|
|
for (k = 0, f = 1; (k + 4) < dlen; k += g, dp += g, ++f) {
|
||
|
|
int ntpgd = dp[3];
|
||
|
|
uint64_t ull;
|
||
|
|
|
||
|
|
g = (ntpgd * 4) + 20;
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Descriptor %d\n", lip, f);
|
||
|
|
if ((k + g) > dlen) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s truncated descriptor, "
|
||
|
|
"stop\n", lip);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
ull = sg_get_unaligned_be64(dp + 4);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s first uds LBA: 0x%" PRIx64
|
||
|
|
"\n", lip, ull);
|
||
|
|
ull = sg_get_unaligned_be64(dp + 12);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s last uds LBA: 0x%" PRIx64
|
||
|
|
"\n", lip, ull);
|
||
|
|
for (j = 0; j < ntpgd; ++j) {
|
||
|
|
tp = dp + 20 + (j * 4);
|
||
|
|
decode_tpgs_state(tp[0] & 0xf, c, sizeof(c));
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s tpg: %d state: %s\n",
|
||
|
|
lip, sg_get_unaligned_be16(tp + 2), c);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char * dd_usage_reason_str_arr[] = {
|
||
|
|
"Unknown",
|
||
|
|
"resend this and further commands to:",
|
||
|
|
"resend this command to:",
|
||
|
|
"new subsidiary lu added to this administrative lu:",
|
||
|
|
"administrative lu associated with a preferred binding:",
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/* Decode descriptor format sense descriptors (assumes sense buffer is
|
||
|
|
* in descriptor format). 'leadin' is string prepended to each line written
|
||
|
|
* to 'b', NULL treated as "". Returns the number of bytes written to 'b'
|
||
|
|
* excluding the trailing '\0'. If problem, returns 0. */
|
||
|
|
int
|
||
|
|
sg_get_sense_descriptors_str(const char * lip, const uint8_t * sbp,
|
||
|
|
int sb_len, int blen, char * b)
|
||
|
|
{
|
||
|
|
int add_sb_len, desc_len, k, j, sense_key;
|
||
|
|
int n, progress, pr, rem;
|
||
|
|
uint16_t sct_sc;
|
||
|
|
bool processed;
|
||
|
|
const uint8_t * descp;
|
||
|
|
char z[64];
|
||
|
|
static const char * dtsp = " >> descriptor too short";
|
||
|
|
static const char * eccp = "Extended copy command";
|
||
|
|
static const char * ddp = "destination device";
|
||
|
|
|
||
|
|
if ((NULL == b) || (blen <= 0))
|
||
|
|
return 0;
|
||
|
|
b[0] = '\0';
|
||
|
|
if (lip)
|
||
|
|
sg_scnpr(z, sizeof(z), "%.60s ", lip);
|
||
|
|
else
|
||
|
|
sg_scnpr(z, sizeof(z), " ");
|
||
|
|
if ((sb_len < 8) || (0 == (add_sb_len = sbp[7])))
|
||
|
|
return 0;
|
||
|
|
add_sb_len = (add_sb_len < (sb_len - 8)) ? add_sb_len : (sb_len - 8);
|
||
|
|
sense_key = (sbp[1] & 0xf);
|
||
|
|
|
||
|
|
for (descp = (sbp + 8), k = 0, n = 0;
|
||
|
|
(k < add_sb_len) && (n < blen);
|
||
|
|
k += desc_len, descp += desc_len) {
|
||
|
|
int 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 += sg_scnpr(b + n, blen - n, "%s Descriptor type: ", lip);
|
||
|
|
processed = true;
|
||
|
|
switch (descp[0]) {
|
||
|
|
case 0:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Information: ");
|
||
|
|
if (add_d_len >= 10) {
|
||
|
|
if (! (0x80 & descp[2]))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Valid=0 (-> vendor "
|
||
|
|
"specific) ");
|
||
|
|
n += sg_scnpr(b + n, blen - n, "0x");
|
||
|
|
for (j = 0; j < 8; ++j)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Command specific: ");
|
||
|
|
if (add_d_len >= 10) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "0x");
|
||
|
|
for (j = 0; j < 8; ++j)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", descp[4 + j]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 2: /* Sense Key Specific */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Sense key specific: ");
|
||
|
|
n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
|
||
|
|
blen - n, b + n);
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Field replaceable unit code: ");
|
||
|
|
if (add_d_len >= 2)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "0x%x\n", descp[3]);
|
||
|
|
else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Stream commands: ");
|
||
|
|
if (add_d_len >= 2) {
|
||
|
|
if (descp[3] & 0x80)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "FILEMARK");
|
||
|
|
if (descp[3] & 0x40)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "End Of Medium (EOM)");
|
||
|
|
if (descp[3] & 0x20)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Incorrect Length "
|
||
|
|
"Indicator (ILI)");
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 5:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Block commands: ");
|
||
|
|
if (add_d_len >= 2)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Incorrect Length Indicator "
|
||
|
|
"(ILI) %s\n",
|
||
|
|
(descp[3] & 0x20) ? "set" : "clear");
|
||
|
|
else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 6:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "OSD object identification\n");
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
case 7:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "OSD response integrity check "
|
||
|
|
"value\n");
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
case 8:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "OSD attribute identification\n");
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
case 9: /* this is defined in SAT (SAT-2) */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "ATA Status Return: ");
|
||
|
|
if (add_d_len >= 12) {
|
||
|
|
int extend, count;
|
||
|
|
|
||
|
|
extend = descp[2] & 1;
|
||
|
|
count = descp[5] + (extend ? (descp[4] << 8) : 0);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "extend=%d error=0x%x \n%s"
|
||
|
|
" count=0x%x ", extend, descp[3], lip,
|
||
|
|
count);
|
||
|
|
if (extend)
|
||
|
|
n += sg_scnpr(b + n, blen - n,
|
||
|
|
"lba=0x%02x%02x%02x%02x%02x%02x ",
|
||
|
|
descp[10], descp[8], descp[6], descp[11],
|
||
|
|
descp[9], descp[7]);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "lba=0x%02x%02x%02x ",
|
||
|
|
descp[11], descp[9], descp[7]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "device=0x%x status=0x%x\n",
|
||
|
|
descp[12], descp[13]);
|
||
|
|
} else {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 0xa:
|
||
|
|
/* Added in SPC-4 rev 17, became 'Another ...' in rev 34 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Another progress indication: ");
|
||
|
|
if (add_d_len < 6) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
progress = sg_get_unaligned_be16(descp + 6);
|
||
|
|
pr = (progress * 100) / 65536;
|
||
|
|
rem = ((progress * 100) % 65536) / 656;
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%d.02%d%%\n", pr, rem);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s [sense_key=0x%x "
|
||
|
|
"asc,ascq=0x%x,0x%x]\n", lip, descp[2], descp[3],
|
||
|
|
descp[4]);
|
||
|
|
break;
|
||
|
|
case 0xb: /* Added in SPC-4 rev 23, defined in SBC-3 rev 22 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "User data segment referral: ");
|
||
|
|
if (add_d_len < 2) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
n += uds_referral_descriptor_str(b + n, blen - n, descp,
|
||
|
|
add_d_len, lip);
|
||
|
|
break;
|
||
|
|
case 0xc: /* Added in SPC-4 rev 28 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Forwarded sense data\n");
|
||
|
|
if (add_d_len < 2) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s FSDT: %s\n", lip,
|
||
|
|
(descp[2] & 0x80) ? "set" : "clear");
|
||
|
|
j = descp[2] & 0xf;
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Sense data source: ", lip);
|
||
|
|
switch (j) {
|
||
|
|
case 0:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s source device\n", eccp);
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
case 2:
|
||
|
|
case 3:
|
||
|
|
case 4:
|
||
|
|
case 5:
|
||
|
|
case 6:
|
||
|
|
case 7:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s %s %d\n", eccp, ddp, j - 1);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
n += sg_scnpr(b + n, blen - n, "unknown [%d]\n", j);
|
||
|
|
}
|
||
|
|
{
|
||
|
|
char c[480];
|
||
|
|
|
||
|
|
sg_get_scsi_status_str(descp[3], sizeof(c) - 1, c);
|
||
|
|
c[sizeof(c) - 1] = '\0';
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Forwarded status: %s\n",
|
||
|
|
lip, c);
|
||
|
|
if (add_d_len > 2) {
|
||
|
|
/* recursing; hope not to get carried away */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s vvvvvvvvvvvvvvvv\n",
|
||
|
|
lip);
|
||
|
|
sg_get_sense_str(lip, descp + 4, add_d_len - 2, false,
|
||
|
|
sizeof(c), c);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s", c);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s ^^^^^^^^^^^^^^^^\n",
|
||
|
|
lip);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 0xd: /* Added in SBC-3 rev 36d */
|
||
|
|
/* this descriptor combines descriptors 0, 1, 2 and 3 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Direct-access block device\n");
|
||
|
|
if (add_d_len < 28) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (0x20 & descp[2])
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s ILI (incorrect length "
|
||
|
|
"indication) set\n", lip);
|
||
|
|
if (0x80 & descp[4]) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Sense key specific: ",
|
||
|
|
lip);
|
||
|
|
n += decode_sks(lip, descp, add_d_len, sense_key, &processed,
|
||
|
|
blen - n, b + n);
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Field replaceable unit "
|
||
|
|
"code: 0x%x\n", lip, descp[7]);
|
||
|
|
if (0x80 & descp[2]) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Information: 0x", lip);
|
||
|
|
for (j = 0; j < 8; ++j)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", descp[8 + j]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Command specific: 0x", lip);
|
||
|
|
for (j = 0; j < 8; ++j)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x", descp[16 + j]);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
break;
|
||
|
|
case 0xe: /* Added in SPC-5 rev 6 (for Bind/Unbind) */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Device designation\n");
|
||
|
|
j = (int)SG_ARRAY_SIZE(dd_usage_reason_str_arr);
|
||
|
|
if (descp[3] < j)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Usage reason: %s\n",
|
||
|
|
lip, dd_usage_reason_str_arr[descp[3]]);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s Usage reason: "
|
||
|
|
"reserved[%d]\n", lip, descp[3]);
|
||
|
|
n += sg_get_designation_descriptor_str(z, descp + 4, descp[1] - 2,
|
||
|
|
true, false, blen - n,
|
||
|
|
b + n);
|
||
|
|
break;
|
||
|
|
case 0xf: /* Added in SPC-5 rev 10 (for Write buffer) */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Microcode activation ");
|
||
|
|
if (add_d_len < 6) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
progress = sg_get_unaligned_be16(descp + 6);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "time: ");
|
||
|
|
if (0 == progress)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "unknown\n");
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%d seconds\n", progress);
|
||
|
|
break;
|
||
|
|
case 0xde: /* NVME Status Field; vendor (sg3_utils) specific */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "NVMe Status: ");
|
||
|
|
if (add_d_len < 6) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s\n", dtsp);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "DNR=%d, M=%d, ",
|
||
|
|
(int)!!(0x80 & descp[5]), (int)!!(0x40 & descp[5]));
|
||
|
|
sct_sc = sg_get_unaligned_be16(descp + 6);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "SCT_SC=0x%x\n", sct_sc);
|
||
|
|
if (sct_sc > 0) {
|
||
|
|
char d[80];
|
||
|
|
|
||
|
|
n += sg_scnpr(b + n, blen - n, " %s\n",
|
||
|
|
sg_get_nvme_cmd_status_str(sct_sc, sizeof(d), d));
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
if (descp[0] >= 0x80)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Vendor specific [0x%x]\n",
|
||
|
|
descp[0]);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, blen - n, "Unknown [0x%x]\n", descp[0]);
|
||
|
|
processed = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (! processed) {
|
||
|
|
if (add_d_len > 0) {
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s ", lip);
|
||
|
|
for (j = 0; j < add_d_len; ++j) {
|
||
|
|
if ((j > 0) && (0 == (j % 24)))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n%s ", lip);
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%02x ", descp[j + 2]);
|
||
|
|
}
|
||
|
|
n += sg_scnpr(b + n, blen - n, "\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (add_d_len < 0)
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s short descriptor\n", lip);
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Decode SAT ATA PASS-THROUGH fixed format sense. Shows "+" after 'count'
|
||
|
|
* and/or 'lba' values to indicate that not all data in those fields is shown.
|
||
|
|
* That extra field information may be available in the ATA pass-through
|
||
|
|
* results log page parameter with the corresponding 'log_index'. */
|
||
|
|
static int
|
||
|
|
sg_get_sense_sat_pt_fixed_str(const char * lip, const uint8_t * sp,
|
||
|
|
int slen, int blen, char * b)
|
||
|
|
{
|
||
|
|
int n = 0;
|
||
|
|
bool extend, count_upper_nz, lba_upper_nz;
|
||
|
|
|
||
|
|
if ((blen < 1) || (slen < 12))
|
||
|
|
return n;
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
if (SPC_SK_RECOVERED_ERROR != (0xf & sp[2]))
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s >> expected Sense key: Recovered "
|
||
|
|
"Error ??\n", lip);
|
||
|
|
/* Fixed sense command-specific information field starts at sp + 8 */
|
||
|
|
extend = !!(0x80 & sp[8]);
|
||
|
|
count_upper_nz = !!(0x40 & sp[8]);
|
||
|
|
lba_upper_nz = !!(0x20 & sp[8]);
|
||
|
|
/* Fixed sense information field starts at sp + 3 */
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s error=0x%x, status=0x%x, "
|
||
|
|
"device=0x%x, count(7:0)=0x%x%c\n", lip, sp[3], sp[4],
|
||
|
|
sp[5], sp[6], (count_upper_nz ? '+' : ' '));
|
||
|
|
n += sg_scnpr(b + n, blen - n, "%s extend=%d, log_index=0x%x, "
|
||
|
|
"lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", lip,
|
||
|
|
(int)extend, (0xf & sp[8]), sp[11], sp[10], sp[9],
|
||
|
|
(lba_upper_nz ? '+' : ' '));
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Fetch sense information */
|
||
|
|
int
|
||
|
|
sg_get_sense_str(const char * lip, const uint8_t * sbp, int sb_len,
|
||
|
|
bool raw_sinfo, int cblen, char * cbp)
|
||
|
|
{
|
||
|
|
bool descriptor_format = false;
|
||
|
|
bool sdat_ovfl = false;
|
||
|
|
bool valid_info_fld;
|
||
|
|
int len, progress, n, r, pr, rem, blen;
|
||
|
|
unsigned int info;
|
||
|
|
uint8_t resp_code;
|
||
|
|
const char * ebp = NULL;
|
||
|
|
char ebuff[64];
|
||
|
|
char b[256];
|
||
|
|
struct sg_scsi_sense_hdr ssh;
|
||
|
|
|
||
|
|
if ((NULL == cbp) || (cblen <= 0))
|
||
|
|
return 0;
|
||
|
|
else if (1 == cblen) {
|
||
|
|
cbp[0] = '\0';
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
blen = sizeof(b);
|
||
|
|
n = 0;
|
||
|
|
if (NULL == lip)
|
||
|
|
lip = "";
|
||
|
|
if ((NULL == sbp) || (sb_len < 1)) {
|
||
|
|
n += sg_scnpr(cbp, cblen, "%s >>> sense buffer empty\n", lip);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
resp_code = 0x7f & sbp[0];
|
||
|
|
valid_info_fld = !!(sbp[0] & 0x80);
|
||
|
|
len = sb_len;
|
||
|
|
if (sg_scsi_normalize_sense(sbp, sb_len, &ssh)) {
|
||
|
|
switch (ssh.response_code) {
|
||
|
|
case 0x70: /* fixed, current */
|
||
|
|
ebp = "Fixed format, current";
|
||
|
|
len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
|
||
|
|
len = (len > sb_len) ? sb_len : len;
|
||
|
|
sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
|
||
|
|
break;
|
||
|
|
case 0x71: /* fixed, deferred */
|
||
|
|
/* error related to a previous command */
|
||
|
|
ebp = "Fixed format, <<<deferred>>>";
|
||
|
|
len = (sb_len > 7) ? (sbp[7] + 8) : sb_len;
|
||
|
|
len = (len > sb_len) ? sb_len : len;
|
||
|
|
sdat_ovfl = (len > 2) ? !!(sbp[2] & 0x10) : false;
|
||
|
|
break;
|
||
|
|
case 0x72: /* descriptor, current */
|
||
|
|
descriptor_format = true;
|
||
|
|
ebp = "Descriptor format, current";
|
||
|
|
sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
|
||
|
|
break;
|
||
|
|
case 0x73: /* descriptor, deferred */
|
||
|
|
descriptor_format = true;
|
||
|
|
ebp = "Descriptor format, <<<deferred>>>";
|
||
|
|
sdat_ovfl = (sb_len > 4) ? !!(sbp[4] & 0x80) : false;
|
||
|
|
break;
|
||
|
|
case 0x0:
|
||
|
|
ebp = "Response code: 0x0 (?)";
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
sg_scnpr(ebuff, sizeof(ebuff), "Unknown response code: 0x%x",
|
||
|
|
ssh.response_code);
|
||
|
|
ebp = ebuff;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s%s; Sense key: %s\n", lip, ebp,
|
||
|
|
sg_lib_sense_key_desc[ssh.sense_key]);
|
||
|
|
if (sdat_ovfl)
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s<<<Sense data overflow "
|
||
|
|
"(SDAT_OVFL)>>>\n", lip);
|
||
|
|
if (descriptor_format) {
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip,
|
||
|
|
sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
|
||
|
|
n += sg_get_sense_descriptors_str(lip, sbp, len,
|
||
|
|
cblen - n, cbp + n);
|
||
|
|
} else if ((len > 12) && (0 == ssh.asc) &&
|
||
|
|
(ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
|
||
|
|
/* SAT ATA PASS-THROUGH fixed format */
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip,
|
||
|
|
sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
|
||
|
|
n += sg_get_sense_sat_pt_fixed_str(lip, sbp, len,
|
||
|
|
cblen - n, cbp + n);
|
||
|
|
} else if (len > 2) { /* fixed format */
|
||
|
|
if (len > 12)
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s%s\n", lip,
|
||
|
|
sg_get_asc_ascq_str(ssh.asc, ssh.ascq, blen, b));
|
||
|
|
r = 0;
|
||
|
|
if (strlen(lip) > 0)
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s", lip);
|
||
|
|
if (len > 6) {
|
||
|
|
info = sg_get_unaligned_be32(sbp + 3);
|
||
|
|
if (valid_info_fld)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " Info fld=0x%x [%u] ",
|
||
|
|
info, info);
|
||
|
|
else if (info > 0)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " Valid=0, Info fld=0x%x "
|
||
|
|
"[%u] ", info, info);
|
||
|
|
} else
|
||
|
|
info = 0;
|
||
|
|
if (sbp[2] & 0xe0) {
|
||
|
|
if (sbp[2] & 0x80)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " FMK");
|
||
|
|
/* current command has read a filemark */
|
||
|
|
if (sbp[2] & 0x40)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " EOM");
|
||
|
|
/* end-of-medium condition exists */
|
||
|
|
if (sbp[2] & 0x20)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " ILI");
|
||
|
|
/* incorrect block length requested */
|
||
|
|
r += sg_scnpr(b + r, blen - r, "\n");
|
||
|
|
} else if (valid_info_fld || (info > 0))
|
||
|
|
r += sg_scnpr(b + r, blen - r, "\n");
|
||
|
|
if ((len >= 14) && sbp[14])
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Field replaceable unit "
|
||
|
|
"code: %d\n", lip, sbp[14]);
|
||
|
|
if ((len >= 18) && (sbp[15] & 0x80)) {
|
||
|
|
/* sense key specific decoding */
|
||
|
|
switch (ssh.sense_key) {
|
||
|
|
case SPC_SK_ILLEGAL_REQUEST:
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Sense Key Specific: "
|
||
|
|
"Error in %s: byte %d", lip,
|
||
|
|
((sbp[15] & 0x40) ?
|
||
|
|
"Command" : "Data parameters"),
|
||
|
|
sg_get_unaligned_be16(sbp + 16));
|
||
|
|
if (sbp[15] & 0x08)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " bit %d\n",
|
||
|
|
sbp[15] & 0x07);
|
||
|
|
else
|
||
|
|
r += sg_scnpr(b + r, blen - r, "\n");
|
||
|
|
break;
|
||
|
|
case SPC_SK_NO_SENSE:
|
||
|
|
case SPC_SK_NOT_READY:
|
||
|
|
progress = sg_get_unaligned_be16(sbp + 16);
|
||
|
|
pr = (progress * 100) / 65536;
|
||
|
|
rem = ((progress * 100) % 65536) / 656;
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Progress indication: "
|
||
|
|
"%d.%02d%%\n", lip, pr, rem);
|
||
|
|
break;
|
||
|
|
case SPC_SK_HARDWARE_ERROR:
|
||
|
|
case SPC_SK_MEDIUM_ERROR:
|
||
|
|
case SPC_SK_RECOVERED_ERROR:
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Actual retry count: "
|
||
|
|
"0x%02x%02x\n", lip, sbp[16], sbp[17]);
|
||
|
|
break;
|
||
|
|
case SPC_SK_COPY_ABORTED:
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Segment pointer: ",
|
||
|
|
lip);
|
||
|
|
r += sg_scnpr(b + r, blen - r, "Relative to start of %s, "
|
||
|
|
"byte %d", ((sbp[15] & 0x20) ?
|
||
|
|
"segment descriptor" : "parameter list"),
|
||
|
|
sg_get_unaligned_be16(sbp + 16));
|
||
|
|
if (sbp[15] & 0x08)
|
||
|
|
r += sg_scnpr(b + r, blen - r, " bit %d\n",
|
||
|
|
sbp[15] & 0x07);
|
||
|
|
else
|
||
|
|
r += sg_scnpr(b + r, blen - r, "\n");
|
||
|
|
break;
|
||
|
|
case SPC_SK_UNIT_ATTENTION:
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Unit attention "
|
||
|
|
"condition queue: ", lip);
|
||
|
|
r += sg_scnpr(b + r, blen - r, "overflow flag is %d\n",
|
||
|
|
!!(sbp[15] & 0x1));
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s Sense_key: 0x%x "
|
||
|
|
"unexpected\n", lip, ssh.sense_key);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (r > 0)
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s", b);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s fixed descriptor length "
|
||
|
|
"too short, len=%d\n", lip, len);
|
||
|
|
} else { /* unable to normalise sense buffer, something irregular */
|
||
|
|
if (sb_len < 4) { /* Too short */
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%ssense buffer too short (4 "
|
||
|
|
"byte minimum)\n", lip);
|
||
|
|
goto check_raw;
|
||
|
|
}
|
||
|
|
if (0x7f == resp_code) { /* Vendor specific */
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%sVendor specific sense "
|
||
|
|
"buffer, in hex:\n", lip);
|
||
|
|
n += hex2str(sbp, sb_len, lip, -1, cblen - n, cbp + n);
|
||
|
|
return n; /* no need to check raw, just output in hex */
|
||
|
|
}
|
||
|
|
/* non-extended SCSI-1 sense data ?? */
|
||
|
|
r = 0;
|
||
|
|
if (strlen(lip) > 0)
|
||
|
|
r += sg_scnpr(b + r, blen - r, "%s", lip);
|
||
|
|
r += sg_scnpr(b + r, blen - r, "Probably uninitialized data.\n%s "
|
||
|
|
"Try to view as SCSI-1 non-extended sense:\n", lip);
|
||
|
|
r += sg_scnpr(b + r, blen - r, " AdValid=%d Error class=%d Error "
|
||
|
|
"code=%d\n", valid_info_fld, ((sbp[0] >> 4) & 0x7),
|
||
|
|
(sbp[0] & 0xf));
|
||
|
|
if (valid_info_fld)
|
||
|
|
sg_scnpr(b + r, blen - r, "%s lba=0x%x\n", lip,
|
||
|
|
sg_get_unaligned_be24(sbp + 1) & 0x1fffff);
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s\n", b);
|
||
|
|
}
|
||
|
|
check_raw:
|
||
|
|
if (raw_sinfo) {
|
||
|
|
int calculated_len;
|
||
|
|
char z[64];
|
||
|
|
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "%s Raw sense data (in hex), "
|
||
|
|
"sb_len=%d", lip, sb_len);
|
||
|
|
if (n >= (cblen - 1))
|
||
|
|
return n;
|
||
|
|
if ((sb_len > 7) && (sbp[0] >= 0x70) && (sbp[0] < 0x74)) {
|
||
|
|
calculated_len = sbp[7] + 8;
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, ", calculated_len=%d\n",
|
||
|
|
calculated_len);
|
||
|
|
} else {
|
||
|
|
calculated_len = sb_len;
|
||
|
|
n += sg_scnpr(cbp + n, cblen - n, "\n");
|
||
|
|
}
|
||
|
|
if (n >= (cblen - 1))
|
||
|
|
return n;
|
||
|
|
|
||
|
|
sg_scnpr(z, sizeof(z), "%.50s ", lip);
|
||
|
|
n += hex2str(sbp, calculated_len, z, -1, cblen - n, cbp + n);
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Print sense information */
|
||
|
|
void
|
||
|
|
sg_print_sense(const char * leadin, const uint8_t * sbp, int sb_len,
|
||
|
|
bool raw_sinfo)
|
||
|
|
{
|
||
|
|
uint32_t pg_sz = sg_get_page_size();
|
||
|
|
char *cp;
|
||
|
|
uint8_t *free_cp;
|
||
|
|
|
||
|
|
cp = (char *)sg_memalign(pg_sz, pg_sz, &free_cp, false);
|
||
|
|
if (NULL == cp)
|
||
|
|
return;
|
||
|
|
sg_get_sense_str(leadin, sbp, sb_len, raw_sinfo, pg_sz, cp);
|
||
|
|
pr2ws("%s", cp);
|
||
|
|
free(free_cp);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* This examines exit_status and if an error message is known it is output
|
||
|
|
* as a string to 'b' and true is returned. If 'longer' is true and extra
|
||
|
|
* information is available then it is added to the output. If no error
|
||
|
|
* message is available a null character is output and false is returned.
|
||
|
|
* If exit_status is zero (no error) and 'longer' is true then the string
|
||
|
|
* 'No errors' is output; if 'longer' is false then a null character is
|
||
|
|
* output; in both cases true is returned. If exit_status is negative then
|
||
|
|
* a null character is output and false is returned. All messages are a
|
||
|
|
* single line (less than 80 characters) with no trailing LF. The output
|
||
|
|
* string including the trailing null character is no longer than b_len.
|
||
|
|
* exit_status represents the Unix exit status available after a utility
|
||
|
|
* finishes executing (for whatever reason). */
|
||
|
|
bool sg_exit2str(int exit_status, bool longer, int b_len, char *b)
|
||
|
|
{
|
||
|
|
const struct sg_value_2names_t * ess = sg_exit_str_arr;
|
||
|
|
|
||
|
|
if ((b_len < 1) || (NULL == b))
|
||
|
|
return false;
|
||
|
|
/* if there is a valid buffer, initialize it to a valid empty string */
|
||
|
|
b[0] = '\0';
|
||
|
|
if (exit_status < 0)
|
||
|
|
return false;
|
||
|
|
else if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status)) {
|
||
|
|
if (longer)
|
||
|
|
goto fini;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((exit_status > SG_LIB_OS_BASE_ERR) && /* 51 to 96 inclusive */
|
||
|
|
(exit_status < SG_LIB_CAT_MALFORMED)) {
|
||
|
|
snprintf(b, b_len, "%s%s", (longer ? "OS error: " : ""),
|
||
|
|
safe_strerror(exit_status - SG_LIB_OS_BASE_ERR));
|
||
|
|
return true;
|
||
|
|
} else if ((exit_status > 128) && (exit_status < 255)) {
|
||
|
|
snprintf(b, b_len, "Utility stopped/aborted by signal number: %d",
|
||
|
|
exit_status - 128);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
fini:
|
||
|
|
for ( ; ess->name; ++ess) {
|
||
|
|
if (exit_status == ess->value)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (ess->name) {
|
||
|
|
if (longer && ess->name2)
|
||
|
|
snprintf(b, b_len, "%s, %s", ess->name, ess->name2);
|
||
|
|
else
|
||
|
|
snprintf(b, b_len, "%s", ess->name);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool
|
||
|
|
sg_if_can2fp(const char * leadin, int exit_status, FILE * fp)
|
||
|
|
{
|
||
|
|
char b[256];
|
||
|
|
const char * s = leadin ? leadin : "";
|
||
|
|
|
||
|
|
if ((0 == exit_status) || (SG_LIB_OK_FALSE == exit_status))
|
||
|
|
return true; /* don't print anything */
|
||
|
|
else if (sg_exit2str(exit_status, false, sizeof(b), b)) {
|
||
|
|
fprintf(fp, "%s%s\n", s, b);
|
||
|
|
return true;
|
||
|
|
} else
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* This examines exit_status and if an error message is known it is output
|
||
|
|
* to stdout/stderr and true is returned. If no error message is
|
||
|
|
* available nothing is output and false is returned. If exit_status is
|
||
|
|
* zero (no error) nothing is output and true is returned. If exit_status
|
||
|
|
* is negative then nothing is output and false is returned. If leadin is
|
||
|
|
* non-NULL then it is printed before the error message. All messages are
|
||
|
|
* a single line with a trailing LF. */
|
||
|
|
bool
|
||
|
|
sg_if_can2stdout(const char * leadin, int exit_status)
|
||
|
|
{
|
||
|
|
return sg_if_can2fp(leadin, exit_status, stdout);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* See sg_if_can2stdout() comments */
|
||
|
|
bool
|
||
|
|
sg_if_can2stderr(const char * leadin, int exit_status)
|
||
|
|
{
|
||
|
|
return sg_if_can2fp(leadin, exit_status,
|
||
|
|
sg_warnings_strm ? sg_warnings_strm : stderr);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* If os_err_num is within bounds then the returned value is 'os_err_num +
|
||
|
|
* SG_LIB_OS_BASE_ERR' otherwise SG_LIB_OS_BASE_ERR is returned. If
|
||
|
|
* os_err_num is 0 then 0 is returned. */
|
||
|
|
int
|
||
|
|
sg_convert_errno(int os_err_num)
|
||
|
|
{
|
||
|
|
if (os_err_num <= 0) {
|
||
|
|
if (os_err_num < 0)
|
||
|
|
return SG_LIB_OS_BASE_ERR;
|
||
|
|
return os_err_num; /* os_err_num of 0 maps to 0 */
|
||
|
|
}
|
||
|
|
if (os_err_num < (SG_LIB_CAT_MALFORMED - SG_LIB_OS_BASE_ERR))
|
||
|
|
return SG_LIB_OS_BASE_ERR + os_err_num;
|
||
|
|
return SG_LIB_OS_BASE_ERR;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char * const bad_sense_cat = "Bad sense category";
|
||
|
|
|
||
|
|
/* Yield string associated with sense category. Returns 'b' (or pointer
|
||
|
|
* to "Bad sense category" if 'b' is NULL). If sense_cat unknown then
|
||
|
|
* yield "Sense category: <sense_cat_val>" string. The original 'sense
|
||
|
|
* category' concept has been expanded to most detected errors and is
|
||
|
|
* returned by these utilities as their exit status value (an (unsigned)
|
||
|
|
* 8 bit value where 0 means good (i.e. no errors)). Uses sg_exit2str()
|
||
|
|
* function. */
|
||
|
|
const char *
|
||
|
|
sg_get_category_sense_str(int sense_cat, int b_len, char * b, int verbose)
|
||
|
|
{
|
||
|
|
if (NULL == b)
|
||
|
|
return bad_sense_cat;
|
||
|
|
if (b_len <= 0)
|
||
|
|
return b;
|
||
|
|
if (! sg_exit2str(sense_cat, (verbose > 0), b_len, b)) {
|
||
|
|
int n = sg_scnpr(b, b_len, "Sense category: %d", sense_cat);
|
||
|
|
|
||
|
|
if ((0 == verbose) && (n < (b_len - 1)))
|
||
|
|
sg_scnpr(b + n, b_len - n, ", try '-v' option for more "
|
||
|
|
"information");
|
||
|
|
}
|
||
|
|
return b; /* Note that a valid C string is returned in all cases */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* See description in sg_lib.h header file */
|
||
|
|
bool
|
||
|
|
sg_scsi_normalize_sense(const uint8_t * sbp, int sb_len,
|
||
|
|
struct sg_scsi_sense_hdr * sshp)
|
||
|
|
{
|
||
|
|
uint8_t resp_code;
|
||
|
|
if (sshp)
|
||
|
|
memset(sshp, 0, sizeof(struct sg_scsi_sense_hdr));
|
||
|
|
if ((NULL == sbp) || (sb_len < 1))
|
||
|
|
return false;
|
||
|
|
resp_code = 0x7f & sbp[0];
|
||
|
|
if ((resp_code < 0x70) || (resp_code > 0x73))
|
||
|
|
return false;
|
||
|
|
if (sshp) {
|
||
|
|
sshp->response_code = resp_code;
|
||
|
|
if (sshp->response_code >= 0x72) { /* descriptor format */
|
||
|
|
if (sb_len > 1)
|
||
|
|
sshp->sense_key = (0xf & sbp[1]);
|
||
|
|
if (sb_len > 2)
|
||
|
|
sshp->asc = sbp[2];
|
||
|
|
if (sb_len > 3)
|
||
|
|
sshp->ascq = sbp[3];
|
||
|
|
if (sb_len > 7)
|
||
|
|
sshp->additional_length = sbp[7];
|
||
|
|
sshp->byte4 = sbp[4]; /* bit 7: SDAT_OVFL bit */
|
||
|
|
/* sbp[5] and sbp[6] reserved for descriptor format */
|
||
|
|
} else { /* fixed format */
|
||
|
|
if (sb_len > 2)
|
||
|
|
sshp->sense_key = (0xf & sbp[2]);
|
||
|
|
if (sb_len > 7) {
|
||
|
|
sb_len = (sb_len < (sbp[7] + 8)) ? sb_len : (sbp[7] + 8);
|
||
|
|
if (sb_len > 12)
|
||
|
|
sshp->asc = sbp[12];
|
||
|
|
if (sb_len > 13)
|
||
|
|
sshp->ascq = sbp[13];
|
||
|
|
}
|
||
|
|
if (sb_len > 6) { /* lower 3 bytes of INFO field */
|
||
|
|
sshp->byte4 = sbp[4];
|
||
|
|
sshp->byte5 = sbp[5];
|
||
|
|
sshp->byte6 = sbp[6];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns a SG_LIB_CAT_* value. If cannot decode sense buffer (sbp) or a
|
||
|
|
* less common sense key then return SG_LIB_CAT_SENSE .*/
|
||
|
|
int
|
||
|
|
sg_err_category_sense(const uint8_t * sbp, int sb_len)
|
||
|
|
{
|
||
|
|
struct sg_scsi_sense_hdr ssh;
|
||
|
|
|
||
|
|
if ((sbp && (sb_len > 2)) &&
|
||
|
|
(sg_scsi_normalize_sense(sbp, sb_len, &ssh))) {
|
||
|
|
switch (ssh.sense_key) { /* 0 to 0x1f */
|
||
|
|
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:
|
||
|
|
if ((0x04 == ssh.asc) && (0x0b == ssh.ascq))
|
||
|
|
return SG_LIB_CAT_STANDBY;
|
||
|
|
if ((0x04 == ssh.asc) && (0x0c == ssh.ascq))
|
||
|
|
return SG_LIB_CAT_UNAVAILABLE;
|
||
|
|
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 if ((0x21 == ssh.asc) && (0x0 == ssh.ascq))
|
||
|
|
return SG_LIB_LBA_OUT_OF_RANGE;
|
||
|
|
else
|
||
|
|
return SG_LIB_CAT_ILLEGAL_REQ;
|
||
|
|
break;
|
||
|
|
case SPC_SK_ABORTED_COMMAND:
|
||
|
|
if (0x10 == ssh.asc)
|
||
|
|
return SG_LIB_CAT_PROTECTION;
|
||
|
|
else
|
||
|
|
return SG_LIB_CAT_ABORTED_COMMAND;
|
||
|
|
case SPC_SK_MISCOMPARE:
|
||
|
|
return SG_LIB_CAT_MISCOMPARE;
|
||
|
|
case SPC_SK_DATA_PROTECT:
|
||
|
|
return SG_LIB_CAT_DATA_PROTECT;
|
||
|
|
case SPC_SK_COPY_ABORTED:
|
||
|
|
return SG_LIB_CAT_COPY_ABORTED;
|
||
|
|
case SPC_SK_COMPLETED:
|
||
|
|
case SPC_SK_VOLUME_OVERFLOW:
|
||
|
|
return SG_LIB_CAT_SENSE;
|
||
|
|
default:
|
||
|
|
; /* reserved and vendor specific sense keys fall through */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return SG_LIB_CAT_SENSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Beware: gives wrong answer for variable length command (opcode=0x7f) */
|
||
|
|
int
|
||
|
|
sg_get_command_size(uint8_t opcode)
|
||
|
|
{
|
||
|
|
switch ((opcode >> 5) & 0x7) {
|
||
|
|
case 0:
|
||
|
|
return 6;
|
||
|
|
case 3: case 5:
|
||
|
|
return 12;
|
||
|
|
case 4:
|
||
|
|
return 16;
|
||
|
|
default: /* 1, 2, 6, 7 */
|
||
|
|
return 10;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_get_command_name(const uint8_t * cdbp, 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 == cdbp) {
|
||
|
|
sg_scnpr(buff, buff_len, "%s", "<null> command pointer");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
service_action = (SG_VARIABLE_LENGTH_CMD == cdbp[0]) ?
|
||
|
|
sg_get_unaligned_be16(cdbp + 8) : (cdbp[1] & 0x1f);
|
||
|
|
sg_get_opcode_sa_name(cdbp[0], service_action, peri_type, buff_len, buff);
|
||
|
|
}
|
||
|
|
|
||
|
|
struct op_code2sa_t {
|
||
|
|
int op_code;
|
||
|
|
int pdt_s;
|
||
|
|
struct sg_lib_value_name_t * arr;
|
||
|
|
const char * prefix;
|
||
|
|
};
|
||
|
|
|
||
|
|
static struct op_code2sa_t op_code2sa_arr[] = {
|
||
|
|
{SG_VARIABLE_LENGTH_CMD, PDT_ALL, sg_lib_variable_length_arr, NULL},
|
||
|
|
{SG_MAINTENANCE_IN, PDT_ALL, sg_lib_maint_in_arr, NULL},
|
||
|
|
{SG_MAINTENANCE_OUT, PDT_ALL, sg_lib_maint_out_arr, NULL},
|
||
|
|
{SG_SERVICE_ACTION_IN_12, PDT_ALL, sg_lib_serv_in12_arr, NULL},
|
||
|
|
{SG_SERVICE_ACTION_OUT_12, PDT_ALL, sg_lib_serv_out12_arr, NULL},
|
||
|
|
{SG_SERVICE_ACTION_IN_16, PDT_ALL, sg_lib_serv_in16_arr, NULL},
|
||
|
|
{SG_SERVICE_ACTION_OUT_16, PDT_ALL, sg_lib_serv_out16_arr, NULL},
|
||
|
|
{SG_SERVICE_ACTION_BIDI, PDT_ALL, sg_lib_serv_bidi_arr, NULL},
|
||
|
|
{SG_PERSISTENT_RESERVE_IN, PDT_ALL, sg_lib_pr_in_arr,
|
||
|
|
"Persistent reserve in"},
|
||
|
|
{SG_PERSISTENT_RESERVE_OUT, PDT_ALL, sg_lib_pr_out_arr,
|
||
|
|
"Persistent reserve out"},
|
||
|
|
{SG_3PARTY_COPY_OUT, PDT_ALL, sg_lib_xcopy_sa_arr, NULL},
|
||
|
|
{SG_3PARTY_COPY_IN, PDT_ALL, sg_lib_rec_copy_sa_arr, NULL},
|
||
|
|
{SG_READ_BUFFER, PDT_ALL, sg_lib_read_buff_arr, "Read buffer(10)"},
|
||
|
|
{SG_READ_BUFFER_16, PDT_ALL, sg_lib_read_buff_arr, "Read buffer(16)"},
|
||
|
|
{SG_READ_ATTRIBUTE, PDT_ALL, sg_lib_read_attr_arr, "Read attribute"},
|
||
|
|
{SG_READ_POSITION, PDT_TAPE, sg_lib_read_pos_arr, "Read position"},
|
||
|
|
{SG_SANITIZE, PDT_DISK_ZBC, sg_lib_sanitize_sa_arr, "Sanitize"},
|
||
|
|
{SG_WRITE_BUFFER, PDT_ALL, sg_lib_write_buff_arr, "Write buffer"},
|
||
|
|
{SG_ZONING_IN, PDT_DISK_ZBC, sg_lib_zoning_in_arr, NULL},
|
||
|
|
{SG_ZONING_OUT, PDT_DISK_ZBC, sg_lib_zoning_out_arr, NULL},
|
||
|
|
{0xffff, -1, NULL, NULL},
|
||
|
|
};
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_get_opcode_sa_name(uint8_t cmd_byte0, int service_action,
|
||
|
|
int peri_type, int buff_len, char * buff)
|
||
|
|
{
|
||
|
|
int d_pdt;
|
||
|
|
const struct sg_lib_value_name_t * vnp;
|
||
|
|
const struct op_code2sa_t * osp;
|
||
|
|
char b[80];
|
||
|
|
|
||
|
|
if ((NULL == buff) || (buff_len < 1))
|
||
|
|
return;
|
||
|
|
else if (1 == buff_len) {
|
||
|
|
buff[0] = '\0';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (peri_type < 0)
|
||
|
|
peri_type = 0;
|
||
|
|
d_pdt = sg_lib_pdt_decay(peri_type);
|
||
|
|
for (osp = op_code2sa_arr; osp->arr; ++osp) {
|
||
|
|
if ((int)cmd_byte0 == osp->op_code) {
|
||
|
|
if (sg_pdt_s_eq(osp->pdt_s, d_pdt)) {
|
||
|
|
vnp = get_value_name(osp->arr, service_action, peri_type);
|
||
|
|
if (vnp) {
|
||
|
|
if (osp->prefix)
|
||
|
|
sg_scnpr(buff, buff_len, "%s, %s", osp->prefix,
|
||
|
|
vnp->name);
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
} else {
|
||
|
|
sg_get_opcode_name(cmd_byte0, peri_type, sizeof(b), b);
|
||
|
|
sg_scnpr(buff, buff_len, "%s service action=0x%x", b,
|
||
|
|
service_action);
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sg_get_opcode_name(cmd_byte0, peri_type, buff_len, buff);
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_get_opcode_name(uint8_t 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) {
|
||
|
|
sg_scnpr(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)
|
||
|
|
sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
else
|
||
|
|
sg_scnpr(buff, buff_len, "Opcode=0x%x", (int)cmd_byte0);
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
sg_scnpr(buff, buff_len, "Reserved [0x%x]", (int)cmd_byte0);
|
||
|
|
break;
|
||
|
|
case 6:
|
||
|
|
case 7:
|
||
|
|
sg_scnpr(buff, buff_len, "Vendor specific [0x%x]", (int)cmd_byte0);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Fetch NVMe command name given first byte (byte offset 0 in 64 byte
|
||
|
|
* command) of command. Gets Admin NVMe command name if 'admin' is true
|
||
|
|
* (e.g. opcode=0x6 -> Identify), otherwise gets NVM command set name
|
||
|
|
* (e.g. opcode=0 -> Flush). Returns 'buff'. */
|
||
|
|
char *
|
||
|
|
sg_get_nvme_opcode_name(uint8_t cmd_byte0, bool admin, int buff_len,
|
||
|
|
char * buff)
|
||
|
|
{
|
||
|
|
const struct sg_lib_simple_value_name_t * vnp = admin ?
|
||
|
|
sg_lib_nvme_admin_cmd_arr : sg_lib_nvme_nvm_cmd_arr;
|
||
|
|
|
||
|
|
if ((NULL == buff) || (buff_len < 1))
|
||
|
|
return buff;
|
||
|
|
else if (1 == buff_len) {
|
||
|
|
buff[0] = '\0';
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
for ( ; vnp->name; ++vnp) {
|
||
|
|
if (cmd_byte0 == (uint8_t)vnp->value) {
|
||
|
|
snprintf(buff, buff_len, "%s", vnp->name);
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (admin) {
|
||
|
|
if (cmd_byte0 >= 0xc0)
|
||
|
|
snprintf(buff, buff_len, "Vendor specific opcode: 0x%x",
|
||
|
|
cmd_byte0);
|
||
|
|
else if (cmd_byte0 >= 0x80)
|
||
|
|
snprintf(buff, buff_len, "Command set specific opcode: 0x%x",
|
||
|
|
cmd_byte0);
|
||
|
|
else
|
||
|
|
snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0);
|
||
|
|
} else { /* NVM (non-Admin) command set */
|
||
|
|
if (cmd_byte0 >= 0x80)
|
||
|
|
snprintf(buff, buff_len, "Vendor specific opcode: 0x%x",
|
||
|
|
cmd_byte0);
|
||
|
|
else
|
||
|
|
snprintf(buff, buff_len, "Unknown opcode: 0x%x", cmd_byte0);
|
||
|
|
}
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 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 uint8_t * initial_desig_desc, int page_len,
|
||
|
|
int * off, int m_assoc, int m_desig_type, int m_code_set)
|
||
|
|
{
|
||
|
|
bool fltr = ((m_assoc >= 0) || (m_desig_type >= 0) || (m_code_set >= 0));
|
||
|
|
int k = *off;
|
||
|
|
const uint8_t * bp = initial_desig_desc;
|
||
|
|
|
||
|
|
while ((k + 3) < page_len) {
|
||
|
|
k = (k < 0) ? 0 : (k + bp[k + 3] + 4);
|
||
|
|
if ((k + 4) > page_len)
|
||
|
|
break;
|
||
|
|
if (fltr) {
|
||
|
|
if (m_code_set >= 0) {
|
||
|
|
if ((bp[k] & 0xf) != m_code_set)
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (m_assoc >= 0) {
|
||
|
|
if (((bp[k + 1] >> 4) & 0x3) != m_assoc)
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (m_desig_type >= 0) {
|
||
|
|
if ((bp[k + 1] & 0xf) != m_desig_type)
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
*off = k;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return (k == page_len) ? -1 : -2;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char * sg_sfs_spc_reserved = "SPC Reserved";
|
||
|
|
static const char * sg_sfs_sbc_reserved = "SBC Reserved";
|
||
|
|
static const char * sg_sfs_ssc_reserved = "SSC Reserved";
|
||
|
|
static const char * sg_sfs_zbc_reserved = "ZBC Reserved";
|
||
|
|
static const char * sg_sfs_reserved = "Reserved";
|
||
|
|
|
||
|
|
/* Yield SCSI Feature Set (sfs) string. When 'peri_type' is < -1 (or > 31)
|
||
|
|
* returns pointer to string (same as 'buff') associated with 'sfs_code'.
|
||
|
|
* When 'peri_type' is between -1 (for SPC) and 31 (inclusive) then a match
|
||
|
|
* on both 'sfs_code' and 'peri_type' is required. If 'foundp' is not NULL
|
||
|
|
* then where it points is set to true if a match is found else it is set to
|
||
|
|
* false. If 'buff' is not NULL then in the case of a match a descriptive
|
||
|
|
* string is written to 'buff' while if there is not a not then a string
|
||
|
|
* ending in "Reserved" is written (and may be prefixed with SPC, SBC, SSC
|
||
|
|
* or ZBC). Returns 'buff' (i.e. a pointer value) even if it is NULL.
|
||
|
|
* Example:
|
||
|
|
* char b[64];
|
||
|
|
* ...
|
||
|
|
* printf("%s\n", sg_get_sfs_str(sfs_code, -2, sizeof(b), b, NULL, 0));
|
||
|
|
*/
|
||
|
|
const char *
|
||
|
|
sg_get_sfs_str(uint16_t sfs_code, int peri_type, int buff_len, char * buff,
|
||
|
|
bool * foundp, int verbose)
|
||
|
|
{
|
||
|
|
const struct sg_lib_value_name_t * vnp = NULL;
|
||
|
|
int n = 0;
|
||
|
|
int my_pdt;
|
||
|
|
|
||
|
|
if ((NULL == buff) || (buff_len < 1)) {
|
||
|
|
if (foundp)
|
||
|
|
*foundp = false;
|
||
|
|
return NULL;
|
||
|
|
} else if (1 == buff_len) {
|
||
|
|
buff[0] = '\0';
|
||
|
|
if (foundp)
|
||
|
|
*foundp = false;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
my_pdt = ((peri_type < -1) || (peri_type > PDT_MAX)) ? -2 : peri_type;
|
||
|
|
vnp = get_value_name(sg_lib_scsi_feature_sets, sfs_code, my_pdt);
|
||
|
|
if (vnp && (-2 != my_pdt)) {
|
||
|
|
if (! sg_pdt_s_eq(my_pdt, vnp->peri_dev_type))
|
||
|
|
vnp = NULL; /* shouldn't really happen */
|
||
|
|
}
|
||
|
|
if (foundp)
|
||
|
|
*foundp = vnp ? true : false;
|
||
|
|
if (sfs_code < 0x100) { /* SPC Feature Sets */
|
||
|
|
if (vnp) {
|
||
|
|
if (verbose)
|
||
|
|
n += sg_scnpr(buff, buff_len, "SPC %s", vnp->name);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", sg_sfs_spc_reserved);
|
||
|
|
} else if (sfs_code < 0x200) { /* SBC Feature Sets */
|
||
|
|
if (vnp) {
|
||
|
|
if (verbose)
|
||
|
|
n += sg_scnpr(buff, buff_len, "SBC %s", vnp->name);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", sg_sfs_sbc_reserved);
|
||
|
|
} else if (sfs_code < 0x300) { /* SSC Feature Sets */
|
||
|
|
if (vnp) {
|
||
|
|
if (verbose)
|
||
|
|
n += sg_scnpr(buff, buff_len, "SSC %s", vnp->name);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", sg_sfs_ssc_reserved);
|
||
|
|
} else if (sfs_code < 0x400) { /* ZBC Feature Sets */
|
||
|
|
if (vnp) {
|
||
|
|
if (verbose)
|
||
|
|
n += sg_scnpr(buff, buff_len, "ZBC %s", vnp->name);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", sg_sfs_zbc_reserved);
|
||
|
|
} else { /* Other SCSI Feature Sets */
|
||
|
|
if (vnp) {
|
||
|
|
if (verbose)
|
||
|
|
n += sg_scnpr(buff, buff_len, "[unrecognized PDT] %s",
|
||
|
|
vnp->name);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", vnp->name);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(buff, buff_len, "%s", sg_sfs_reserved);
|
||
|
|
|
||
|
|
}
|
||
|
|
if (verbose > 4)
|
||
|
|
pr2ws("%s: length of returned string (n) %d\n", __func__, n);
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* This is a heuristic that takes into account the command bytes and length
|
||
|
|
* to decide whether the presented unstructured sequence of bytes could be
|
||
|
|
* a SCSI command. If so it returns true otherwise false. Vendor specific
|
||
|
|
* SCSI commands (i.e. opcodes from 0xc0 to 0xff), if presented, are assumed
|
||
|
|
* to follow SCSI conventions (i.e. length of 6, 10, 12 or 16 bytes). The
|
||
|
|
* only SCSI commands considered above 16 bytes of length are the Variable
|
||
|
|
* Length Commands (opcode 0x7f) and the XCDB wrapped commands (opcode 0x7e).
|
||
|
|
* Both have an inbuilt length field which can be cross checked with clen.
|
||
|
|
* No NVMe commands (64 bytes long plus some extra added by some OSes) have
|
||
|
|
* opcodes 0x7e or 0x7f yet. ATA is register based but SATA has FIS
|
||
|
|
* structures that are sent across the wire. The FIS register structure is
|
||
|
|
* used to move a command from a SATA host to device, but the ATA 'command'
|
||
|
|
* is not the first byte. So it is harder to say what will happen if a
|
||
|
|
* FIS structure is presented as a SCSI command, hopefully there is a low
|
||
|
|
* probability this function will yield true in that case. */
|
||
|
|
bool
|
||
|
|
sg_is_scsi_cdb(const uint8_t * cdbp, int clen)
|
||
|
|
{
|
||
|
|
uint8_t opcode;
|
||
|
|
uint8_t top3bits;
|
||
|
|
|
||
|
|
if (clen < 6)
|
||
|
|
return false;
|
||
|
|
opcode = cdbp[0];
|
||
|
|
top3bits = opcode >> 5;
|
||
|
|
if (0x3 == top3bits) {
|
||
|
|
int ilen, sa;
|
||
|
|
|
||
|
|
if ((clen < 12) || (clen % 4))
|
||
|
|
return false; /* must be modulo 4 and 12 or more bytes */
|
||
|
|
switch (opcode) {
|
||
|
|
case 0x7e: /* Extended cdb (XCDB) */
|
||
|
|
ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
|
||
|
|
return (ilen == clen);
|
||
|
|
case 0x7f: /* Variable Length cdb */
|
||
|
|
ilen = 8 + cdbp[7];
|
||
|
|
sa = sg_get_unaligned_be16(cdbp + 8);
|
||
|
|
/* service action (sa) 0x0 is reserved */
|
||
|
|
return ((ilen == clen) && sa);
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} else if (clen <= 16) {
|
||
|
|
switch (clen) {
|
||
|
|
case 6:
|
||
|
|
if (top3bits > 0x5) /* vendor */
|
||
|
|
return true;
|
||
|
|
return (0x0 == top3bits); /* 6 byte cdb */
|
||
|
|
case 10:
|
||
|
|
if (top3bits > 0x5) /* vendor */
|
||
|
|
return true;
|
||
|
|
return ((0x1 == top3bits) || (0x2 == top3bits)); /* 10 byte cdb */
|
||
|
|
case 16:
|
||
|
|
if (top3bits > 0x5) /* vendor */
|
||
|
|
return true;
|
||
|
|
return (0x4 == top3bits); /* 16 byte cdb */
|
||
|
|
case 12:
|
||
|
|
if (top3bits > 0x5) /* vendor */
|
||
|
|
return true;
|
||
|
|
return (0x5 == top3bits); /* 12 byte cdb */
|
||
|
|
default:
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/* NVMe probably falls out here, clen > 16 and (opcode < 0x60 or
|
||
|
|
* opcode > 0x7f). */
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Yield string associated with NVMe command status value in sct_sc. It
|
||
|
|
* expects to decode DW3 bits 27:17 from the completion queue. Bits 27:25
|
||
|
|
* are the Status Code Type (SCT) and bits 24:17 are the Status Code (SC).
|
||
|
|
* Bit 17 in DW3 should be bit 0 in sct_sc. If no status string is found
|
||
|
|
* a string of the form "Reserved [0x<sct_sc_in_hex>]" is generated.
|
||
|
|
* Returns 'buff'. Does nothing if buff_len<=0 or if buff is NULL.*/
|
||
|
|
char *
|
||
|
|
sg_get_nvme_cmd_status_str(uint16_t sct_sc, int b_len, char * b)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
uint16_t s = 0x3ff & sct_sc;
|
||
|
|
const struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
|
||
|
|
|
||
|
|
if ((b_len <= 0) || (NULL == b))
|
||
|
|
return b;
|
||
|
|
else if (1 == b_len) {
|
||
|
|
b[0] = '\0';
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
|
||
|
|
if (s == (uint16_t)vp->value) {
|
||
|
|
strncpy(b, vp->name, b_len);
|
||
|
|
b[b_len - 1] = '\0';
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (k >= 1000)
|
||
|
|
pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
|
||
|
|
__func__);
|
||
|
|
snprintf(b, b_len, "Reserved [0x%x]", sct_sc);
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Attempts to map NVMe status value ((SCT << 8) | SC) to SCSI status,
|
||
|
|
* sense_key, asc and ascq tuple. If successful returns true and writes to
|
||
|
|
* non-NULL pointer arguments; otherwise returns false. */
|
||
|
|
bool
|
||
|
|
sg_nvme_status2scsi(uint16_t sct_sc, uint8_t * status_p, uint8_t * sk_p,
|
||
|
|
uint8_t * asc_p, uint8_t * ascq_p)
|
||
|
|
{
|
||
|
|
int k, ind;
|
||
|
|
uint16_t s = 0x3ff & sct_sc;
|
||
|
|
struct sg_lib_value_name_t * vp = sg_lib_nvme_cmd_status_arr;
|
||
|
|
struct sg_lib_4tuple_u8 * mp = sg_lib_scsi_status_sense_arr;
|
||
|
|
|
||
|
|
for (k = 0; (vp->name && (k < 1000)); ++k, ++vp) {
|
||
|
|
if (s == (uint16_t)vp->value)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (k >= 1000) {
|
||
|
|
pr2ws("%s: where is sentinel for sg_lib_nvme_cmd_status_arr ??\n",
|
||
|
|
__func__);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (NULL == vp->name)
|
||
|
|
return false;
|
||
|
|
ind = vp->peri_dev_type;
|
||
|
|
|
||
|
|
|
||
|
|
for (k = 0; (0xff != mp->t2) && k < 1000; ++k, ++mp)
|
||
|
|
; /* count entries for valid index range */
|
||
|
|
if (k >= 1000) {
|
||
|
|
pr2ws("%s: where is sentinel for sg_lib_scsi_status_sense_arr ??\n",
|
||
|
|
__func__);
|
||
|
|
return false;
|
||
|
|
} else if (ind >= k)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
mp = sg_lib_scsi_status_sense_arr + ind;
|
||
|
|
if (status_p)
|
||
|
|
*status_p = mp->t1;
|
||
|
|
if (sk_p)
|
||
|
|
*sk_p = mp->t2;
|
||
|
|
if (asc_p)
|
||
|
|
*asc_p = mp->t3;
|
||
|
|
if (ascq_p)
|
||
|
|
*ascq_p = mp->t4;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Add vendor (sg3_utils) specific sense descriptor for the NVMe Status
|
||
|
|
* field. Assumes descriptor (i.e. not fixed) sense. Assumes sbp has room. */
|
||
|
|
void
|
||
|
|
sg_nvme_desc2sense(uint8_t * sbp, bool dnr, bool more, uint16_t sct_sc)
|
||
|
|
{
|
||
|
|
int len = sbp[7] + 8;
|
||
|
|
|
||
|
|
sbp[len] = 0xde; /* vendor specific descriptor type */
|
||
|
|
sbp[len + 1] = 6; /* descriptor is 8 bytes long */
|
||
|
|
memset(sbp + len + 2, 0, 6);
|
||
|
|
if (dnr)
|
||
|
|
sbp[len + 5] = 0x80;
|
||
|
|
if (more)
|
||
|
|
sbp[len + 5] |= 0x40;
|
||
|
|
sg_put_unaligned_be16(sct_sc, sbp + len + 6);
|
||
|
|
sbp[7] += 8;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Build minimum sense buffer, either descriptor type (desc=true) or fixed
|
||
|
|
* type (desc=false). Assume sbp has enough room (8 or 14 bytes
|
||
|
|
* respectively). sbp should have room for 32 or 18 bytes respectively */
|
||
|
|
void
|
||
|
|
sg_build_sense_buffer(bool desc, uint8_t *sbp, uint8_t skey, uint8_t asc,
|
||
|
|
uint8_t ascq)
|
||
|
|
{
|
||
|
|
if (desc) {
|
||
|
|
sbp[0] = 0x72; /* descriptor, current */
|
||
|
|
sbp[1] = skey;
|
||
|
|
sbp[2] = asc;
|
||
|
|
sbp[3] = ascq;
|
||
|
|
sbp[7] = 0;
|
||
|
|
} else {
|
||
|
|
sbp[0] = 0x70; /* fixed, current */
|
||
|
|
sbp[2] = skey;
|
||
|
|
sbp[7] = 0xa; /* Assumes length is 18 bytes */
|
||
|
|
sbp[12] = asc;
|
||
|
|
sbp[13] = ascq;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 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)
|
||
|
|
{
|
||
|
|
char * errstr;
|
||
|
|
|
||
|
|
if (errnum < 0)
|
||
|
|
errnum = -errnum;
|
||
|
|
errstr = strerror(errnum);
|
||
|
|
if (NULL == errstr) {
|
||
|
|
size_t len = strlen(safe_errbuf);
|
||
|
|
|
||
|
|
sg_scnpr(safe_errbuf + len, sizeof(safe_errbuf) - len, "%i", errnum);
|
||
|
|
return safe_errbuf;
|
||
|
|
}
|
||
|
|
return errstr;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
trimTrailingSpaces(char * b)
|
||
|
|
{
|
||
|
|
int n = strlen(b);
|
||
|
|
|
||
|
|
while ((n > 0) && (' ' == b[n - 1]))
|
||
|
|
b[--n] = '\0';
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Read binary starting at 'str' for 'len' bytes and output as ASCII
|
||
|
|
* hexadecinal into file pointer (fp). 16 bytes per line are output with an
|
||
|
|
* additional space between 8th and 9th byte on each line (for readability).
|
||
|
|
* 'no_ascii' selects one of 3 output format 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
|
||
|
|
dStrHexFp(const char* str, int len, int no_ascii, FILE * fp)
|
||
|
|
{
|
||
|
|
const char * p = str;
|
||
|
|
const char * formatstr;
|
||
|
|
uint8_t c;
|
||
|
|
char buff[82];
|
||
|
|
int a = 0;
|
||
|
|
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);
|
||
|
|
if (0 == no_ascii) /* address at left and ASCII at right */
|
||
|
|
formatstr = "%.76s\n";
|
||
|
|
else /* previously when > 0 str was "%.58s\n" */
|
||
|
|
formatstr = "%s\n"; /* when < 0 str was: "%.48s\n" */
|
||
|
|
memset(buff, ' ', 80);
|
||
|
|
buff[80] = '\0';
|
||
|
|
if (no_ascii < 0) {
|
||
|
|
bpstart = 0;
|
||
|
|
bpos = bpstart;
|
||
|
|
for (k = 0; k < len; k++) {
|
||
|
|
c = *p++;
|
||
|
|
if (bpos == (bpstart + (8 * 3)))
|
||
|
|
bpos++;
|
||
|
|
sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
|
||
|
|
buff[bpos + 2] = ' ';
|
||
|
|
if ((k > 0) && (0 == ((k + 1) % 16))) {
|
||
|
|
trimTrailingSpaces(buff);
|
||
|
|
fprintf(fp, formatstr, buff);
|
||
|
|
bpos = bpstart;
|
||
|
|
memset(buff, ' ', 80);
|
||
|
|
} else
|
||
|
|
bpos += 3;
|
||
|
|
}
|
||
|
|
if (bpos > bpstart) {
|
||
|
|
buff[bpos + 2] = '\0';
|
||
|
|
trimTrailingSpaces(buff);
|
||
|
|
fprintf(fp, "%s\n", buff);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
/* no_ascii>=0, start each line with address (offset) */
|
||
|
|
k = sg_scnpr(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++;
|
||
|
|
sg_scnpr(&buff[bpos], blen - bpos, "%.2x", (int)(uint8_t)c);
|
||
|
|
buff[bpos + 2] = ' ';
|
||
|
|
if (no_ascii)
|
||
|
|
buff[cpos++] = ' ';
|
||
|
|
else {
|
||
|
|
if (! my_isprint(c))
|
||
|
|
c = '.';
|
||
|
|
buff[cpos++] = c;
|
||
|
|
}
|
||
|
|
if (cpos > (cpstart + 15)) {
|
||
|
|
if (no_ascii)
|
||
|
|
trimTrailingSpaces(buff);
|
||
|
|
fprintf(fp, formatstr, buff);
|
||
|
|
bpos = bpstart;
|
||
|
|
cpos = cpstart;
|
||
|
|
a += 16;
|
||
|
|
memset(buff, ' ', 80);
|
||
|
|
k = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
|
||
|
|
buff[k + 1] = ' ';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (cpos > cpstart) {
|
||
|
|
buff[cpos] = '\0';
|
||
|
|
if (no_ascii)
|
||
|
|
trimTrailingSpaces(buff);
|
||
|
|
fprintf(fp, "%s\n", buff);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
dStrHex(const char* str, int len, int no_ascii)
|
||
|
|
{
|
||
|
|
dStrHexFp(str, len, no_ascii, stdout);
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
dStrHexErr(const char* str, int len, int no_ascii)
|
||
|
|
{
|
||
|
|
dStrHexFp(str, len, no_ascii,
|
||
|
|
(sg_warnings_strm ? sg_warnings_strm : stderr));
|
||
|
|
}
|
||
|
|
|
||
|
|
#define DSHS_LINE_BLEN 160 /* maximum characters per line */
|
||
|
|
#define DSHS_BPL 16 /* bytes per line */
|
||
|
|
|
||
|
|
/* Read 'len' bytes from 'str' and output as ASCII-Hex bytes (space separated)
|
||
|
|
* to 'b' not to exceed 'b_len' characters. Each line starts with 'leadin'
|
||
|
|
* (NULL for no leadin) and there are 16 bytes per line with an extra space
|
||
|
|
* between the 8th and 9th bytes. 'oformat' is 0 for repeat in printable ASCII
|
||
|
|
* ('.' for non printable chars) to right of each line; 1 don't (so just
|
||
|
|
* output ASCII hex). If 'oformat' is 2 output same as 1 but any LFs are
|
||
|
|
* replaced by space (and trailing spaces are trimmed). Note that an address
|
||
|
|
* is not printed on each line preceding the hex data. Returns number of bytes
|
||
|
|
* written to 'b' excluding the trailing '\0'. The only difference between
|
||
|
|
* dStrHexStr() and hex2str() is the type of the first argument. */
|
||
|
|
int
|
||
|
|
dStrHexStr(const char * str, int len, const char * leadin, int oformat,
|
||
|
|
int b_len, char * b)
|
||
|
|
{
|
||
|
|
bool want_ascii = (0 == oformat);
|
||
|
|
int bpstart, bpos, k, n, prior_ascii_len;
|
||
|
|
char buff[DSHS_LINE_BLEN + 2]; /* allow for trailing null */
|
||
|
|
char a[DSHS_BPL + 1]; /* printable ASCII bytes or '.' */
|
||
|
|
const char * p = str;
|
||
|
|
const char * lf_or = (oformat > 1) ? " " : "\n";
|
||
|
|
|
||
|
|
if (len <= 0) {
|
||
|
|
if (b_len > 0)
|
||
|
|
b[0] = '\0';
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if (b_len <= 0)
|
||
|
|
return 0;
|
||
|
|
if (want_ascii) {
|
||
|
|
memset(a, ' ', DSHS_BPL);
|
||
|
|
a[DSHS_BPL] = '\0';
|
||
|
|
}
|
||
|
|
n = 0;
|
||
|
|
bpstart = 0;
|
||
|
|
if (leadin) {
|
||
|
|
if (oformat > 1)
|
||
|
|
n += sg_scnpr(b + n, b_len - n, "%s", leadin);
|
||
|
|
else {
|
||
|
|
bpstart = strlen(leadin);
|
||
|
|
/* Cap leadin at (DSHS_LINE_BLEN - 70) characters */
|
||
|
|
if (bpstart > (DSHS_LINE_BLEN - 70))
|
||
|
|
bpstart = DSHS_LINE_BLEN - 70;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bpos = bpstart;
|
||
|
|
prior_ascii_len = bpstart + (DSHS_BPL * 3) + 1;
|
||
|
|
memset(buff, ' ', DSHS_LINE_BLEN);
|
||
|
|
buff[DSHS_LINE_BLEN] = '\0';
|
||
|
|
if (bpstart > 0)
|
||
|
|
memcpy(buff, leadin, bpstart);
|
||
|
|
for (k = 0; k < len; k++) {
|
||
|
|
uint8_t c = *p++;
|
||
|
|
|
||
|
|
if (bpos == (bpstart + ((DSHS_BPL / 2) * 3)))
|
||
|
|
bpos++; /* for extra space in middle of each line's hex */
|
||
|
|
sg_scnpr(buff + bpos, (int)sizeof(buff) - bpos, "%.2x",
|
||
|
|
(int)(uint8_t)c);
|
||
|
|
buff[bpos + 2] = ' ';
|
||
|
|
if (want_ascii)
|
||
|
|
a[k % DSHS_BPL] = my_isprint(c) ? c : '.';
|
||
|
|
if ((k > 0) && (0 == ((k + 1) % DSHS_BPL))) {
|
||
|
|
trimTrailingSpaces(buff);
|
||
|
|
if (want_ascii) {
|
||
|
|
n += sg_scnpr(b + n, b_len - n, "%-*s %s\n",
|
||
|
|
prior_ascii_len, buff, a);
|
||
|
|
memset(a, ' ', DSHS_BPL);
|
||
|
|
} else
|
||
|
|
n += sg_scnpr(b + n, b_len - n, "%s%s", buff, lf_or);
|
||
|
|
if (n >= (b_len - 1))
|
||
|
|
goto fini;
|
||
|
|
memset(buff, ' ', DSHS_LINE_BLEN);
|
||
|
|
bpos = bpstart;
|
||
|
|
if (bpstart > 0)
|
||
|
|
memcpy(buff, leadin, bpstart);
|
||
|
|
} else
|
||
|
|
bpos += 3;
|
||
|
|
}
|
||
|
|
if (bpos > bpstart) {
|
||
|
|
trimTrailingSpaces(buff);
|
||
|
|
if (want_ascii)
|
||
|
|
n += sg_scnpr(b + n, b_len - n, "%-*s %s\n", prior_ascii_len,
|
||
|
|
buff, a);
|
||
|
|
else
|
||
|
|
n += sg_scnpr(b + n, b_len - n, "%s%s", buff, lf_or);
|
||
|
|
}
|
||
|
|
fini:
|
||
|
|
if (oformat > 1)
|
||
|
|
n = trimTrailingSpaces(b);
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
hex2stdout(const uint8_t * b_str, int len, int no_ascii)
|
||
|
|
{
|
||
|
|
dStrHex((const char *)b_str, len, no_ascii);
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
hex2stderr(const uint8_t * b_str, int len, int no_ascii)
|
||
|
|
{
|
||
|
|
dStrHexErr((const char *)b_str, len, no_ascii);
|
||
|
|
}
|
||
|
|
|
||
|
|
int
|
||
|
|
hex2str(const uint8_t * b_str, int len, const char * leadin, int oformat,
|
||
|
|
int b_len, char * b)
|
||
|
|
{
|
||
|
|
return dStrHexStr((const char *)b_str, len, leadin, oformat, b_len, b);
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
hex2fp(const uint8_t * b_str, int len, const char * leadin, int oformat,
|
||
|
|
FILE * fp)
|
||
|
|
{
|
||
|
|
int k, num;
|
||
|
|
char b[800]; /* allow for 4 lines of 16 bytes (in hex) each */
|
||
|
|
|
||
|
|
if (leadin && (strlen(leadin) > 118)) {
|
||
|
|
fprintf(fp, ">>> leadin parameter is too large\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for (k = 0; k < len; k += num) {
|
||
|
|
num = ((k + 64) < len) ? 64 : (len - k);
|
||
|
|
hex2str(b_str + k, num, leadin, oformat, sizeof(b), b);
|
||
|
|
fprintf(fp, "%s", b);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns true when executed on big endian machine; else returns false.
|
||
|
|
* Useful for displaying ATA identify words (which need swapping on a
|
||
|
|
* big endian machine). */
|
||
|
|
bool
|
||
|
|
sg_is_big_endian()
|
||
|
|
{
|
||
|
|
union u_t {
|
||
|
|
uint16_t s;
|
||
|
|
uint8_t c[sizeof(uint16_t)];
|
||
|
|
} u;
|
||
|
|
|
||
|
|
u.s = 0x0102;
|
||
|
|
return (u.c[0] == 0x01); /* The lowest address contains
|
||
|
|
the most significant byte */
|
||
|
|
}
|
||
|
|
|
||
|
|
bool
|
||
|
|
sg_all_zeros(const uint8_t * bp, int b_len)
|
||
|
|
{
|
||
|
|
if ((NULL == bp) || (b_len <= 0))
|
||
|
|
return false;
|
||
|
|
for (--b_len; b_len >= 0; --b_len) {
|
||
|
|
if (0x0 != bp[b_len])
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool
|
||
|
|
sg_all_ffs(const uint8_t * bp, int b_len)
|
||
|
|
{
|
||
|
|
if ((NULL == bp) || (b_len <= 0))
|
||
|
|
return false;
|
||
|
|
for (--b_len; b_len >= 0; --b_len) {
|
||
|
|
if (0xff != bp[b_len])
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint16_t
|
||
|
|
swapb_uint16(uint16_t u)
|
||
|
|
{
|
||
|
|
uint16_t 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' is true then bytes in each word swapped. Needs to be set
|
||
|
|
* for ATA IDENTIFY DEVICE response on big-endian machines. */
|
||
|
|
void
|
||
|
|
dWordHex(const uint16_t* words, int num, int no_ascii, bool swapb)
|
||
|
|
{
|
||
|
|
const uint16_t * p = words;
|
||
|
|
uint16_t c;
|
||
|
|
char buff[82];
|
||
|
|
uint8_t 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_uint16(c);
|
||
|
|
bpos += 5;
|
||
|
|
sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)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 = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
|
||
|
|
buff[k + 1] = ' ';
|
||
|
|
|
||
|
|
for (i = 0; i < num; i++) {
|
||
|
|
c = *p++;
|
||
|
|
if (swapb)
|
||
|
|
c = swapb_uint16(c);
|
||
|
|
bpos += 5;
|
||
|
|
sg_scnpr(buff + bpos, blen - bpos, "%.4x", (my_uint)c);
|
||
|
|
buff[bpos + 4] = ' ';
|
||
|
|
if (no_ascii) {
|
||
|
|
buff[cpos++] = ' ';
|
||
|
|
buff[cpos++] = ' ';
|
||
|
|
buff[cpos++] = ' ';
|
||
|
|
} else {
|
||
|
|
upp = (c >> 8) & 0xff;
|
||
|
|
low = c & 0xff;
|
||
|
|
if (! my_isprint(upp))
|
||
|
|
upp = '.';
|
||
|
|
buff[cpos++] = upp;
|
||
|
|
if (! my_isprint(low))
|
||
|
|
low = '.';
|
||
|
|
buff[cpos++] = low;
|
||
|
|
buff[cpos++] = ' ';
|
||
|
|
}
|
||
|
|
if (cpos > (cpstart + 23)) {
|
||
|
|
printf("%.76s\n", buff);
|
||
|
|
bpos = bpstart;
|
||
|
|
cpos = cpstart;
|
||
|
|
a += 8;
|
||
|
|
memset(buff, ' ', 80);
|
||
|
|
k = sg_scnpr(buff + 1, blen - 1, "%.2x", a);
|
||
|
|
buff[k + 1] = ' ';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (cpos > cpstart)
|
||
|
|
printf("%.76s\n", buff);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* If the number in 'buf' can not 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. Ignore leading spaces and
|
||
|
|
* tabs; accept comma, hyphen, space, tab and hash as terminator.
|
||
|
|
* Handles zero and positive values up to 2**31-1 .
|
||
|
|
* Experimental: left argument (must in with hexadecimal digit) added
|
||
|
|
* to, or multiplied, by right argument. No embedded spaces.
|
||
|
|
* Examples: '3+1k' (evaluates to 1027) and '0x34+1m'. */
|
||
|
|
int
|
||
|
|
sg_get_num(const char * buf)
|
||
|
|
{
|
||
|
|
bool is_hex = false;
|
||
|
|
int res, num, n, len;
|
||
|
|
unsigned int unum;
|
||
|
|
char * cp;
|
||
|
|
const char * b;
|
||
|
|
const char * b2p;
|
||
|
|
char c = 'c';
|
||
|
|
char c2 = '\0'; /* keep static checker happy */
|
||
|
|
char c3 = '\0'; /* keep static checker happy */
|
||
|
|
char lb[16];
|
||
|
|
|
||
|
|
if ((NULL == buf) || ('\0' == buf[0]))
|
||
|
|
return -1;
|
||
|
|
len = strlen(buf);
|
||
|
|
n = strspn(buf, " \t");
|
||
|
|
if (n > 0) {
|
||
|
|
if (n == len)
|
||
|
|
return -1;
|
||
|
|
buf += n;
|
||
|
|
len -= n;
|
||
|
|
}
|
||
|
|
/* following hack to keep C++ happy */
|
||
|
|
cp = strpbrk((char *)buf, " \t,#-");
|
||
|
|
if (cp) {
|
||
|
|
len = cp - buf;
|
||
|
|
n = (int)sizeof(lb) - 1;
|
||
|
|
len = (len < n) ? len : n;
|
||
|
|
memcpy(lb, buf, len);
|
||
|
|
lb[len] = '\0';
|
||
|
|
b = lb;
|
||
|
|
} else
|
||
|
|
b = buf;
|
||
|
|
|
||
|
|
b2p = b;
|
||
|
|
if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
|
||
|
|
res = sscanf(b + 2, "%x%c", &unum, &c);
|
||
|
|
num = unum;
|
||
|
|
is_hex = true;
|
||
|
|
b2p = b + 2;
|
||
|
|
} else if ('H' == toupper((int)b[len - 1])) {
|
||
|
|
res = sscanf(b, "%x", &unum);
|
||
|
|
num = unum;
|
||
|
|
} else
|
||
|
|
res = sscanf(b, "%d%c%c%c", &num, &c, &c2, &c3);
|
||
|
|
|
||
|
|
if (res < 1)
|
||
|
|
return -1;
|
||
|
|
else if (1 == res)
|
||
|
|
return num;
|
||
|
|
else {
|
||
|
|
c = toupper((int)c);
|
||
|
|
if (is_hex) {
|
||
|
|
if (! ((c == '+') || (c == 'X')))
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
if (res > 2)
|
||
|
|
c2 = toupper((int)c2);
|
||
|
|
if (res > 3)
|
||
|
|
c3 = toupper((int)c3);
|
||
|
|
|
||
|
|
switch (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': /* experimental: multiplication */
|
||
|
|
/* left argument must end with hexadecimal digit */
|
||
|
|
cp = (char *)strchr(b2p, 'x');
|
||
|
|
if (NULL == cp)
|
||
|
|
cp = (char *)strchr(b2p, 'X');
|
||
|
|
if (cp) {
|
||
|
|
n = sg_get_num(cp + 1);
|
||
|
|
if (-1 != n)
|
||
|
|
return num * n;
|
||
|
|
}
|
||
|
|
return -1;
|
||
|
|
case '+': /* experimental: addition */
|
||
|
|
/* left argument must end with hexadecimal digit */
|
||
|
|
cp = (char *)strchr(b2p, '+');
|
||
|
|
if (cp) {
|
||
|
|
n = sg_get_num(cp + 1);
|
||
|
|
if (-1 != n)
|
||
|
|
return num + n;
|
||
|
|
}
|
||
|
|
return -1;
|
||
|
|
default:
|
||
|
|
pr2ws("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 (","), hyphen ("-"),
|
||
|
|
* 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 not be decoded or the multiplier is unknown
|
||
|
|
* then -1LL is returned. Accepts a hex prefix (0x or 0X), hex suffix
|
||
|
|
* (h or H), 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
|
||
|
|
* and E. Ignore leading spaces and tabs; accept comma, hyphen, space, tab
|
||
|
|
* and hash as terminator. Handles zero and positive values up to 2**63-1 .
|
||
|
|
* Experimental: left argument (must in with hexadecimal digit) added
|
||
|
|
* to, or multiplied by right argument. No embedded spaces.
|
||
|
|
* Examples: '3+1k' (evaluates to 1027) and '0x34+1m'. */
|
||
|
|
int64_t
|
||
|
|
sg_get_llnum(const char * buf)
|
||
|
|
{
|
||
|
|
bool is_hex = false;
|
||
|
|
int res, len, n;
|
||
|
|
int64_t num, ll;
|
||
|
|
uint64_t unum;
|
||
|
|
char * cp;
|
||
|
|
const char * b;
|
||
|
|
const char * b2p;
|
||
|
|
char c = 'c';
|
||
|
|
char c2 = '\0'; /* keep static checker happy */
|
||
|
|
char c3 = '\0'; /* keep static checker happy */
|
||
|
|
char lb[32];
|
||
|
|
|
||
|
|
if ((NULL == buf) || ('\0' == buf[0]))
|
||
|
|
return -1LL;
|
||
|
|
len = strlen(buf);
|
||
|
|
n = strspn(buf, " \t");
|
||
|
|
if (n > 0) {
|
||
|
|
if (n == len)
|
||
|
|
return -1LL;
|
||
|
|
buf += n;
|
||
|
|
len -= n;
|
||
|
|
}
|
||
|
|
/* following cast hack to keep C++ happy */
|
||
|
|
cp = strpbrk((char *)buf, " \t,#-");
|
||
|
|
if (cp) {
|
||
|
|
len = cp - buf;
|
||
|
|
n = (int)sizeof(lb) - 1;
|
||
|
|
len = (len < n) ? len : n;
|
||
|
|
memcpy(lb, buf, len);
|
||
|
|
lb[len] = '\0';
|
||
|
|
b = lb;
|
||
|
|
} else
|
||
|
|
b = buf;
|
||
|
|
|
||
|
|
b2p = b;
|
||
|
|
if (('0' == b[0]) && (('x' == b[1]) || ('X' == b[1]))) {
|
||
|
|
res = sscanf(b + 2, "%" SCNx64 "%c", &unum, &c);
|
||
|
|
num = unum;
|
||
|
|
is_hex = true;
|
||
|
|
b2p = b + 2;
|
||
|
|
} else if ('H' == toupper((int)b[len - 1])) {
|
||
|
|
res = sscanf(b, "%" SCNx64 , &unum);
|
||
|
|
num = unum;
|
||
|
|
} else
|
||
|
|
res = sscanf(b, "%" SCNd64 "%c%c%c", &num, &c, &c2, &c3);
|
||
|
|
|
||
|
|
if (res < 1)
|
||
|
|
return -1LL;
|
||
|
|
else if (1 == res)
|
||
|
|
return num;
|
||
|
|
else {
|
||
|
|
c = toupper((int)c);
|
||
|
|
if (is_hex) {
|
||
|
|
if (! ((c == '+') || (c == 'X')))
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
if (res > 2)
|
||
|
|
c2 = toupper((int)c2);
|
||
|
|
if (res > 3)
|
||
|
|
c3 = toupper((int)c3);
|
||
|
|
|
||
|
|
switch (c) {
|
||
|
|
case 'C':
|
||
|
|
return num;
|
||
|
|
case 'W':
|
||
|
|
return num * 2;
|
||
|
|
case 'B':
|
||
|
|
return num * 512;
|
||
|
|
case 'K': /* kilo or kibi */
|
||
|
|
if (2 == res)
|
||
|
|
return num * 1024;
|
||
|
|
if (('B' == c2) || ('D' == c2))
|
||
|
|
return num * 1000;
|
||
|
|
if (('I' == c2) && (4 == res) && ('B' == c3))
|
||
|
|
return num * 1024; /* KiB */
|
||
|
|
return -1LL;
|
||
|
|
case 'M': /* mega or mebi */
|
||
|
|
if (2 == res)
|
||
|
|
return num * 1048576; /* M */
|
||
|
|
if (('B' == c2) || ('D' == c2))
|
||
|
|
return num * 1000000; /* MB */
|
||
|
|
if (('I' == c2) && (4 == res) && ('B' == c3))
|
||
|
|
return num * 1048576; /* MiB */
|
||
|
|
return -1LL;
|
||
|
|
case 'G': /* giga or gibi */
|
||
|
|
if (2 == res)
|
||
|
|
return num * 1073741824; /* G */
|
||
|
|
if (('B' == c2) || ('D' == c2))
|
||
|
|
return num * 1000000000; /* GB */
|
||
|
|
if (('I' == c2) && (4 == res) && ('B' == c3))
|
||
|
|
return num * 1073741824; /* GiB */
|
||
|
|
return -1LL;
|
||
|
|
case 'T': /* tera or tebi */
|
||
|
|
if (2 == res)
|
||
|
|
return num * 1099511627776LL; /* T */
|
||
|
|
if (('B' == c2) || ('D' == c2))
|
||
|
|
return num * 1000000000000LL; /* TB */
|
||
|
|
if (('I' == c2) && (4 == res) && ('B' == c3))
|
||
|
|
return num * 1099511627776LL; /* TiB */
|
||
|
|
return -1LL;
|
||
|
|
case 'P': /* peta or pebi */
|
||
|
|
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 'E': /* exa or exbi */
|
||
|
|
if (2 == res)
|
||
|
|
return num * 1099511627776LL * 1024 * 1024;
|
||
|
|
if (('B' == c2) || ('D' == c2))
|
||
|
|
return num * 1000000000000LL * 1000 * 1000;
|
||
|
|
if (('I' == c2) && (4 == res) && ('B' == c3))
|
||
|
|
return num * 1099511627776LL * 1024 * 1024;
|
||
|
|
return -1LL;
|
||
|
|
case 'X': /* experimental: decimal (left arg) multiplication */
|
||
|
|
cp = (char *)strchr(b2p, 'x');
|
||
|
|
if (NULL == cp)
|
||
|
|
cp = (char *)strchr(b2p, 'X');
|
||
|
|
if (cp) {
|
||
|
|
ll = sg_get_llnum(cp + 1);
|
||
|
|
if (-1LL != ll)
|
||
|
|
return num * ll;
|
||
|
|
}
|
||
|
|
return -1LL;
|
||
|
|
case '+': /* experimental: decimal (left arg) addition */
|
||
|
|
cp = (char *)strchr(b2p, '+');
|
||
|
|
if (cp) {
|
||
|
|
ll = sg_get_llnum(cp + 1);
|
||
|
|
if (-1LL != ll)
|
||
|
|
return num + ll;
|
||
|
|
}
|
||
|
|
return -1LL;
|
||
|
|
default:
|
||
|
|
pr2ws("unrecognized multiplier\n");
|
||
|
|
return -1LL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 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 (","), hyphen ("-"),
|
||
|
|
* a whitespace or newline as terminator. Only decimal numbers can represent
|
||
|
|
* negative numbers and '-1' must be treated separately. */
|
||
|
|
int64_t
|
||
|
|
sg_get_llnum_nomult(const char * buf)
|
||
|
|
{
|
||
|
|
int res, len;
|
||
|
|
int64_t num;
|
||
|
|
uint64_t unum;
|
||
|
|
|
||
|
|
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, "%" SCNx64 "", &unum);
|
||
|
|
num = unum;
|
||
|
|
} else if ('H' == toupper(buf[len - 1])) {
|
||
|
|
res = sscanf(buf, "%" SCNx64 "", &unum);
|
||
|
|
num = unum;
|
||
|
|
} else
|
||
|
|
res = sscanf(buf, "%" SCNd64 "", &num);
|
||
|
|
return (1 == res) ? num : -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define MAX_NUM_ASCII_LINES 1048576
|
||
|
|
|
||
|
|
/* Read ASCII hex bytes or binary from fname (a file named '-' taken as
|
||
|
|
* stdin). If reading ASCII hex then there should be either one entry per
|
||
|
|
* line or a comma, space, hyphen or tab separated list of bytes. If no_space
|
||
|
|
* is set then a string of ACSII hex digits is expected, 2 perbyte.
|
||
|
|
* Everything from and including a '#' on a line is ignored. Returns 0 if ok,
|
||
|
|
* or an error code. If the error code is SG_LIB_LBA_OUT_OF_RANGE then mp_arr
|
||
|
|
* would be exceeded and both mp_arr and mp_arr_len are written to.
|
||
|
|
* The max_arr_len_and argument may carry extra information: when it
|
||
|
|
* is negative its absolute value is used for the maximum number of bytes to
|
||
|
|
* write to mp_arr _and_ the first hexadecimal value on each line is skipped.
|
||
|
|
* Many hexadecimal output programs place a running address (index) as the
|
||
|
|
* first field on each line. When as_binary and/or no_space are true, the
|
||
|
|
* absolute value of max_arr_len_and is used. */
|
||
|
|
int
|
||
|
|
sg_f2hex_arr(const char * fname, bool as_binary, bool no_space,
|
||
|
|
uint8_t * mp_arr, int * mp_arr_len, int max_arr_len_and)
|
||
|
|
{
|
||
|
|
bool has_stdin, split_line, skip_first, redo_first;
|
||
|
|
int fn_len, in_len, k, j, m, fd, err, max_arr_len;
|
||
|
|
int off = 0;
|
||
|
|
int ret = 0;
|
||
|
|
unsigned int h;
|
||
|
|
const char * lcp;
|
||
|
|
FILE * fp = NULL;
|
||
|
|
struct stat a_stat;
|
||
|
|
char line[512];
|
||
|
|
char carry_over[4];
|
||
|
|
|
||
|
|
if ((NULL == fname) || (NULL == mp_arr) || (NULL == mp_arr_len)) {
|
||
|
|
pr2ws("%s: bad arguments\n", __func__);
|
||
|
|
return SG_LIB_LOGIC_ERROR;
|
||
|
|
}
|
||
|
|
if (max_arr_len_and < 0) {
|
||
|
|
skip_first = true;
|
||
|
|
max_arr_len = -max_arr_len_and;
|
||
|
|
} else {
|
||
|
|
skip_first = false;
|
||
|
|
max_arr_len = max_arr_len_and;
|
||
|
|
}
|
||
|
|
fn_len = strlen(fname);
|
||
|
|
if (0 == fn_len)
|
||
|
|
return SG_LIB_SYNTAX_ERROR;
|
||
|
|
has_stdin = ((1 == fn_len) && ('-' == fname[0])); /* read from stdin */
|
||
|
|
if (as_binary) {
|
||
|
|
if (has_stdin)
|
||
|
|
fd = STDIN_FILENO;
|
||
|
|
else {
|
||
|
|
fd = open(fname, O_RDONLY);
|
||
|
|
if (fd < 0) {
|
||
|
|
err = errno;
|
||
|
|
pr2ws("unable to open binary file %s: %s\n", fname,
|
||
|
|
safe_strerror(err));
|
||
|
|
return sg_convert_errno(err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
k = read(fd, mp_arr, max_arr_len);
|
||
|
|
if (k <= 0) {
|
||
|
|
if (0 == k) {
|
||
|
|
ret = SG_LIB_FILE_ERROR;
|
||
|
|
pr2ws("read 0 bytes from binary file %s\n", fname);
|
||
|
|
} else {
|
||
|
|
ret = sg_convert_errno(errno);
|
||
|
|
pr2ws("read from binary file %s: %s\n", fname,
|
||
|
|
safe_strerror(errno));
|
||
|
|
}
|
||
|
|
} else if ((k < max_arr_len) && (0 == fstat(fd, &a_stat)) &&
|
||
|
|
S_ISFIFO(a_stat.st_mode)) {
|
||
|
|
/* pipe; keep reading till error or 0 read */
|
||
|
|
while (k < max_arr_len) {
|
||
|
|
m = read(fd, mp_arr + k, max_arr_len - k);
|
||
|
|
if (0 == m)
|
||
|
|
break;
|
||
|
|
if (m < 0) {
|
||
|
|
err = errno;
|
||
|
|
pr2ws("read from binary pipe %s: %s\n", fname,
|
||
|
|
safe_strerror(err));
|
||
|
|
ret = sg_convert_errno(err);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
k += m;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (k >= 0)
|
||
|
|
*mp_arr_len = k;
|
||
|
|
if ((fd >= 0) && (! has_stdin))
|
||
|
|
close(fd);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* So read the file as ASCII hex */
|
||
|
|
if (has_stdin)
|
||
|
|
fp = stdin;
|
||
|
|
else {
|
||
|
|
fp = fopen(fname, "r");
|
||
|
|
if (NULL == fp) {
|
||
|
|
err = errno;
|
||
|
|
pr2ws("Unable to open %s for reading: %s\n", fname,
|
||
|
|
safe_strerror(err));
|
||
|
|
ret = sg_convert_errno(err);
|
||
|
|
goto fini;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
carry_over[0] = 0;
|
||
|
|
for (j = 0; j < MAX_NUM_ASCII_LINES; ++j) {
|
||
|
|
if (NULL == fgets(line, sizeof(line), fp))
|
||
|
|
break;
|
||
|
|
in_len = strlen(line);
|
||
|
|
if (in_len > 0) {
|
||
|
|
if ('\n' == line[in_len - 1]) {
|
||
|
|
--in_len;
|
||
|
|
line[in_len] = '\0';
|
||
|
|
split_line = false;
|
||
|
|
} else
|
||
|
|
split_line = true;
|
||
|
|
}
|
||
|
|
if (in_len < 1) {
|
||
|
|
carry_over[0] = 0;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (carry_over[0]) {
|
||
|
|
if (isxdigit(line[0])) {
|
||
|
|
carry_over[1] = line[0];
|
||
|
|
carry_over[2] = '\0';
|
||
|
|
if (1 == sscanf(carry_over, "%4x", &h)) {
|
||
|
|
if (off > 0) {
|
||
|
|
if (off > max_arr_len) {
|
||
|
|
pr2ws("%s: array length exceeded\n", __func__);
|
||
|
|
ret = SG_LIB_LBA_OUT_OF_RANGE;
|
||
|
|
*mp_arr_len = max_arr_len;
|
||
|
|
goto fini;
|
||
|
|
} else
|
||
|
|
mp_arr[off - 1] = h; /* back up and overwrite */
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
pr2ws("%s: carry_over error ['%s'] around line %d\n",
|
||
|
|
__func__, carry_over, j + 1);
|
||
|
|
ret = SG_LIB_SYNTAX_ERROR;
|
||
|
|
goto fini;
|
||
|
|
}
|
||
|
|
lcp = line + 1;
|
||
|
|
--in_len;
|
||
|
|
} else
|
||
|
|
lcp = line;
|
||
|
|
carry_over[0] = 0;
|
||
|
|
} else
|
||
|
|
lcp = line;
|
||
|
|
|
||
|
|
m = strspn(lcp, " \t");
|
||
|
|
if (m == in_len)
|
||
|
|
continue;
|
||
|
|
lcp += m;
|
||
|
|
in_len -= m;
|
||
|
|
if ('#' == *lcp)
|
||
|
|
continue;
|
||
|
|
k = strspn(lcp, "0123456789aAbBcCdDeEfF ,-\t");
|
||
|
|
if ((k < in_len) && ('#' != lcp[k]) && ('\r' != lcp[k])) {
|
||
|
|
pr2ws("%s: syntax error at line %d, pos %d\n", __func__,
|
||
|
|
j + 1, m + k + 1);
|
||
|
|
ret = SG_LIB_SYNTAX_ERROR;
|
||
|
|
goto fini;
|
||
|
|
}
|
||
|
|
if (no_space) {
|
||
|
|
for (k = 0; isxdigit(*lcp) && isxdigit(*(lcp + 1));
|
||
|
|
++k, lcp += 2) {
|
||
|
|
if (1 != sscanf(lcp, "%2x", &h)) {
|
||
|
|
pr2ws("%s: bad hex number in line %d, pos %d\n",
|
||
|
|
__func__, j + 1, (int)(lcp - line + 1));
|
||
|
|
ret = SG_LIB_SYNTAX_ERROR;
|
||
|
|
goto fini;
|
||
|
|
}
|
||
|
|
if ((off + k) >= max_arr_len) {
|
||
|
|
pr2ws("%s: array length exceeded\n", __func__);
|
||
|
|
*mp_arr_len = max_arr_len;
|
||
|
|
ret = SG_LIB_LBA_OUT_OF_RANGE;
|
||
|
|
goto fini;
|
||
|
|
} else
|
||
|
|
mp_arr[off + k] = h;
|
||
|
|
}
|
||
|
|
if (isxdigit(*lcp) && (! isxdigit(*(lcp + 1))))
|
||
|
|
carry_over[0] = *lcp;
|
||
|
|
off += k;
|
||
|
|
} else { /* (white)space separated ASCII hexadecimal bytes */
|
||
|
|
for (redo_first = false, k = 0; k < 1024;
|
||
|
|
k = (redo_first ? k : k + 1)) {
|
||
|
|
if (1 == sscanf(lcp, "%10x", &h)) {
|
||
|
|
if (h > 0xff) {
|
||
|
|
pr2ws("%s: hex number larger than 0xff in line "
|
||
|
|
"%d, pos %d\n", __func__, j + 1,
|
||
|
|
(int)(lcp - line + 1));
|
||
|
|
ret = SG_LIB_SYNTAX_ERROR;
|
||
|
|
goto fini;
|
||
|
|
}
|
||
|
|
if (split_line && (1 == strlen(lcp))) {
|
||
|
|
/* single trailing hex digit might be a split pair */
|
||
|
|
carry_over[0] = *lcp;
|
||
|
|
}
|
||
|
|
if ((off + k) >= max_arr_len) {
|
||
|
|
pr2ws("%s: array length exceeded\n", __func__);
|
||
|
|
ret = SG_LIB_LBA_OUT_OF_RANGE;
|
||
|
|
*mp_arr_len = max_arr_len;
|
||
|
|
goto fini;
|
||
|
|
} else if ((0 == k) && skip_first && (! redo_first))
|
||
|
|
redo_first = true;
|
||
|
|
else {
|
||
|
|
redo_first = false;
|
||
|
|
mp_arr[off + k] = h;
|
||
|
|
}
|
||
|
|
lcp = strpbrk(lcp, " ,-\t");
|
||
|
|
if (NULL == lcp)
|
||
|
|
break;
|
||
|
|
lcp += strspn(lcp, " ,-\t");
|
||
|
|
if ('\0' == *lcp)
|
||
|
|
break;
|
||
|
|
} else {
|
||
|
|
if (('#' == *lcp) || ('\r' == *lcp)) {
|
||
|
|
--k;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
pr2ws("%s: error in line %d, at pos %d\n", __func__,
|
||
|
|
j + 1, (int)(lcp - line + 1));
|
||
|
|
ret = SG_LIB_SYNTAX_ERROR;
|
||
|
|
goto fini;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
off += (k + 1);
|
||
|
|
}
|
||
|
|
} /* end of per line loop */
|
||
|
|
if (j >= MAX_NUM_ASCII_LINES) {
|
||
|
|
pr2ws("%s: wow, more than %d lines of ASCII, give up\n", __func__,
|
||
|
|
SG_LIB_LBA_OUT_OF_RANGE);
|
||
|
|
return SG_LIB_LBA_OUT_OF_RANGE;
|
||
|
|
}
|
||
|
|
*mp_arr_len = off;
|
||
|
|
if (fp && (! has_stdin))
|
||
|
|
fclose(fp);
|
||
|
|
return 0;
|
||
|
|
fini:
|
||
|
|
if (fp && (! has_stdin))
|
||
|
|
fclose(fp);
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 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 uint16_t * word_arr, int start_word,
|
||
|
|
int num_words, bool is_big_endian, char * ochars)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
char * op = ochars;
|
||
|
|
|
||
|
|
for (k = start_word; k < (start_word + num_words); ++k) {
|
||
|
|
char a, b;
|
||
|
|
uint16_t 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;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef SG_LIB_FREEBSD
|
||
|
|
#include <sys/param.h>
|
||
|
|
#elif defined(SG_LIB_WIN32)
|
||
|
|
#include <windows.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
uint32_t
|
||
|
|
sg_get_page_size(void)
|
||
|
|
{
|
||
|
|
#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
|
||
|
|
{
|
||
|
|
long res = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
|
||
|
|
|
||
|
|
return (res <= 0) ? 4096 : res;
|
||
|
|
}
|
||
|
|
#elif defined(SG_LIB_WIN32)
|
||
|
|
static bool got_page_size = false;
|
||
|
|
static uint32_t win_page_size;
|
||
|
|
|
||
|
|
if (! got_page_size) {
|
||
|
|
SYSTEM_INFO si;
|
||
|
|
|
||
|
|
GetSystemInfo(&si);
|
||
|
|
win_page_size = si.dwPageSize;
|
||
|
|
got_page_size = true;
|
||
|
|
}
|
||
|
|
return win_page_size;
|
||
|
|
#elif defined(SG_LIB_FREEBSD)
|
||
|
|
return PAGE_SIZE;
|
||
|
|
#else
|
||
|
|
return 4096; /* give up, pick likely figure */
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
#if defined(SG_LIB_WIN32)
|
||
|
|
#if defined(MSC_VER) || defined(__MINGW32__)
|
||
|
|
/* windows.h already included above */
|
||
|
|
#define sg_sleep_for(seconds) Sleep( (seconds) * 1000)
|
||
|
|
#else
|
||
|
|
#define sg_sleep_for(seconds) sleep(seconds)
|
||
|
|
#endif
|
||
|
|
#else
|
||
|
|
#define sg_sleep_for(seconds) sleep(seconds)
|
||
|
|
#endif
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_sleep_secs(int num_secs)
|
||
|
|
{
|
||
|
|
sg_sleep_for(num_secs);
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
sg_warn_and_wait(const char * cmd_name, const char * dev_name,
|
||
|
|
bool stress_all)
|
||
|
|
{
|
||
|
|
int k, j;
|
||
|
|
const char * stressp = stress_all ? "ALL d" : "D";
|
||
|
|
const char * will_mayp = stress_all ? "will" : "may";
|
||
|
|
|
||
|
|
for (k = 0, j = 15; k < 3; ++k, j -= 5) {
|
||
|
|
printf("\nA %s command will commence in %d seconds\n", cmd_name, j);
|
||
|
|
printf(" %sata on %s %s be DESTROYED%s\n", stressp, dev_name,
|
||
|
|
will_mayp, (stress_all ? "" : " or modified"));
|
||
|
|
printf(" Press control-C to abort\n");
|
||
|
|
sg_sleep_secs(5);
|
||
|
|
}
|
||
|
|
sg_sleep_secs(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Returns pointer to heap (or NULL) that is aligned to a align_to byte
|
||
|
|
* boundary. Sends back *buff_to_free pointer in third argument that may be
|
||
|
|
* different from the return value. If it is different then the *buff_to_free
|
||
|
|
* pointer should be freed (rather than the returned value) when the heap is
|
||
|
|
* no longer needed. If align_to is 0 then aligns to OS's page size. Sets all
|
||
|
|
* returned heap to zeros. If num_bytes is 0 then set to page size. */
|
||
|
|
uint8_t *
|
||
|
|
sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free,
|
||
|
|
bool vb)
|
||
|
|
{
|
||
|
|
size_t psz;
|
||
|
|
|
||
|
|
if (buff_to_free) /* make sure buff_to_free is NULL if alloc fails */
|
||
|
|
*buff_to_free = NULL;
|
||
|
|
psz = (align_to > 0) ? align_to : sg_get_page_size();
|
||
|
|
if (0 == num_bytes)
|
||
|
|
num_bytes = psz; /* ugly to handle otherwise */
|
||
|
|
|
||
|
|
#ifdef HAVE_POSIX_MEMALIGN
|
||
|
|
{
|
||
|
|
int err;
|
||
|
|
uint8_t * res;
|
||
|
|
void * wp = NULL;
|
||
|
|
|
||
|
|
err = posix_memalign(&wp, psz, num_bytes);
|
||
|
|
if (err || (NULL == wp)) {
|
||
|
|
pr2ws("%s: posix_memalign: error [%d], out of memory?\n",
|
||
|
|
__func__, err);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
memset(wp, 0, num_bytes);
|
||
|
|
if (buff_to_free)
|
||
|
|
*buff_to_free = (uint8_t *)wp;
|
||
|
|
res = (uint8_t *)wp;
|
||
|
|
if (vb) {
|
||
|
|
pr2ws("%s: posix_ma, len=%d, ", __func__, num_bytes);
|
||
|
|
if (buff_to_free)
|
||
|
|
pr2ws("wrkBuffp=%p, ", (void *)res);
|
||
|
|
pr2ws("psz=%u, rp=%p\n", (unsigned int)psz, (void *)res);
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
{
|
||
|
|
void * wrkBuff;
|
||
|
|
uint8_t * res;
|
||
|
|
sg_uintptr_t align_1 = psz - 1;
|
||
|
|
|
||
|
|
wrkBuff = (uint8_t *)calloc(num_bytes + psz, 1);
|
||
|
|
if (NULL == wrkBuff) {
|
||
|
|
if (buff_to_free)
|
||
|
|
*buff_to_free = NULL;
|
||
|
|
return NULL;
|
||
|
|
} else if (buff_to_free)
|
||
|
|
*buff_to_free = (uint8_t *)wrkBuff;
|
||
|
|
res = (uint8_t *)(void *)
|
||
|
|
(((sg_uintptr_t)wrkBuff + align_1) & (~align_1));
|
||
|
|
if (vb) {
|
||
|
|
pr2ws("%s: hack, len=%d, ", __func__, num_bytes);
|
||
|
|
if (buff_to_free)
|
||
|
|
pr2ws("buff_to_free=%p, ", wrkBuff);
|
||
|
|
pr2ws("align_1=%" PRIuPTR "u, rp=%p\n", align_1, (void *)res);
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
/* If byte_count is 0 or less then the OS page size is used as denominator.
|
||
|
|
* Returns true if the remainder of ((unsigned)pointer % byte_count) is 0,
|
||
|
|
* else returns false. */
|
||
|
|
bool
|
||
|
|
sg_is_aligned(const void * pointer, int byte_count)
|
||
|
|
{
|
||
|
|
return 0 == ((sg_uintptr_t)pointer %
|
||
|
|
((byte_count > 0) ? (uint32_t)byte_count :
|
||
|
|
sg_get_page_size()));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Does similar job to sg_get_unaligned_be*() but this function starts at
|
||
|
|
* a given start_bit (i.e. within byte, so 7 is MSbit of byte and 0 is LSbit)
|
||
|
|
* offset. Maximum number of num_bits is 64. For example, these two
|
||
|
|
* invocations are equivalent (and should yield the same result);
|
||
|
|
* sg_get_big_endian(from_bp, 7, 16)
|
||
|
|
* sg_get_unaligned_be16(from_bp) */
|
||
|
|
uint64_t
|
||
|
|
sg_get_big_endian(const uint8_t * from_bp, int start_bit /* 0 to 7 */,
|
||
|
|
int num_bits /* 1 to 64 */)
|
||
|
|
{
|
||
|
|
uint64_t res;
|
||
|
|
int sbit_o1 = start_bit + 1;
|
||
|
|
|
||
|
|
res = (*from_bp++ & ((1 << sbit_o1) - 1));
|
||
|
|
num_bits -= sbit_o1;
|
||
|
|
while (num_bits > 0) {
|
||
|
|
res <<= 8;
|
||
|
|
res |= *from_bp++;
|
||
|
|
num_bits -= 8;
|
||
|
|
}
|
||
|
|
if (num_bits < 0)
|
||
|
|
res >>= (-num_bits);
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Does similar job to sg_put_unaligned_be*() but this function starts at
|
||
|
|
* a given start_bit offset. Maximum number of num_bits is 64. Preserves
|
||
|
|
* residual bits in partially written bytes. start_bit 7 is MSb. */
|
||
|
|
void
|
||
|
|
sg_set_big_endian(uint64_t val, uint8_t * to,
|
||
|
|
int start_bit /* 0 to 7 */, int num_bits /* 1 to 64 */)
|
||
|
|
{
|
||
|
|
int sbit_o1 = start_bit + 1;
|
||
|
|
int mask, num, k, x;
|
||
|
|
|
||
|
|
if ((NULL == to) || (start_bit > 7) || (num_bits > 64)) {
|
||
|
|
pr2ws("%s: bad args: start_bit=%d, num_bits=%d\n", __func__,
|
||
|
|
start_bit, num_bits);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
mask = (8 != sbit_o1) ? ((1 << sbit_o1) - 1) : 0xff;
|
||
|
|
k = start_bit - ((num_bits - 1) % 8);
|
||
|
|
if (0 != k)
|
||
|
|
val <<= ((k > 0) ? k : (8 + k));
|
||
|
|
num = (num_bits + 15 - sbit_o1) / 8;
|
||
|
|
for (k = 0; k < num; ++k) {
|
||
|
|
if ((sbit_o1 - num_bits) > 0)
|
||
|
|
mask &= ~((1 << (sbit_o1 - num_bits)) - 1);
|
||
|
|
if (k < (num - 1))
|
||
|
|
x = (val >> ((num - k - 1) * 8)) & 0xff;
|
||
|
|
else
|
||
|
|
x = val & 0xff;
|
||
|
|
to[k] = (to[k] & ~mask) | (x & mask);
|
||
|
|
mask = 0xff;
|
||
|
|
num_bits -= sbit_o1;
|
||
|
|
sbit_o1 = 8;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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 <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
|