1070 lines
37 KiB
C
1070 lines
37 KiB
C
/*
|
|
* Copyright (c) 1999-2018 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.
|
|
*/
|
|
|
|
/*
|
|
* CONTENTS
|
|
* Some SCSI commands are executed in many contexts and hence began
|
|
* to appear in several sg3_utils utilities. This files centralizes
|
|
* some of the low level command execution code. In most cases the
|
|
* interpretation of the command response is left to the each
|
|
* utility.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "sg_lib.h"
|
|
#include "sg_cmds_basic.h"
|
|
#include "sg_pt.h"
|
|
#include "sg_unaligned.h"
|
|
|
|
|
|
|
|
#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
|
|
#define EBUFF_SZ 256
|
|
|
|
#define DEF_PT_TIMEOUT 60 /* 60 seconds */
|
|
#define START_PT_TIMEOUT 120 /* 120 seconds == 2 minutes */
|
|
#define LONG_PT_TIMEOUT 7200 /* 7,200 seconds == 120 minutes */
|
|
|
|
#define SYNCHRONIZE_CACHE_CMD 0x35
|
|
#define SYNCHRONIZE_CACHE_CMDLEN 10
|
|
#define SERVICE_ACTION_IN_16_CMD 0x9e
|
|
#define SERVICE_ACTION_IN_16_CMDLEN 16
|
|
#define READ_CAPACITY_16_SA 0x10
|
|
#define READ_CAPACITY_10_CMD 0x25
|
|
#define READ_CAPACITY_10_CMDLEN 10
|
|
#define MODE_SENSE6_CMD 0x1a
|
|
#define MODE_SENSE6_CMDLEN 6
|
|
#define MODE_SENSE10_CMD 0x5a
|
|
#define MODE_SENSE10_CMDLEN 10
|
|
#define MODE_SELECT6_CMD 0x15
|
|
#define MODE_SELECT6_CMDLEN 6
|
|
#define MODE_SELECT10_CMD 0x55
|
|
#define MODE_SELECT10_CMDLEN 10
|
|
#define LOG_SENSE_CMD 0x4d
|
|
#define LOG_SENSE_CMDLEN 10
|
|
#define LOG_SELECT_CMD 0x4c
|
|
#define LOG_SELECT_CMDLEN 10
|
|
#define START_STOP_CMD 0x1b
|
|
#define START_STOP_CMDLEN 6
|
|
#define PREVENT_ALLOW_CMD 0x1e
|
|
#define PREVENT_ALLOW_CMDLEN 6
|
|
|
|
#define MODE6_RESP_HDR_LEN 4
|
|
#define MODE10_RESP_HDR_LEN 8
|
|
#define MODE_RESP_ARB_LEN 1024
|
|
|
|
#define INQUIRY_RESP_INITIAL_LEN 36
|
|
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
static int pr2ws(const char * fmt, ...)
|
|
__attribute__ ((format (printf, 1, 2)));
|
|
#else
|
|
static int pr2ws(const char * fmt, ...);
|
|
#endif
|
|
|
|
|
|
static 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;
|
|
}
|
|
|
|
static struct sg_pt_base *
|
|
create_pt_obj(const char * cname)
|
|
{
|
|
struct sg_pt_base * ptvp = construct_scsi_pt_obj();
|
|
if (NULL == ptvp)
|
|
pr2ws("%s: out of memory\n", cname);
|
|
return ptvp;
|
|
}
|
|
|
|
/* Invokes a SCSI SYNCHRONIZE CACHE (10) command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_sync_cache_10(int sg_fd, bool sync_nv, bool immed, int group,
|
|
unsigned int lba, unsigned int count, bool noisy,
|
|
int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "synchronize cache(10)";
|
|
int res, ret, k, sense_cat;
|
|
unsigned char sc_cdb[SYNCHRONIZE_CACHE_CMDLEN] =
|
|
{SYNCHRONIZE_CACHE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
if (sync_nv)
|
|
sc_cdb[1] |= 4;
|
|
if (immed)
|
|
sc_cdb[1] |= 2;
|
|
sg_put_unaligned_be32((uint32_t)lba, sc_cdb + 2);
|
|
sc_cdb[6] = group & 0x1f;
|
|
if (count > 0xffff) {
|
|
pr2ws("count too big\n");
|
|
return -1;
|
|
}
|
|
sg_put_unaligned_be16((int16_t)count, sc_cdb + 7);
|
|
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < SYNCHRONIZE_CACHE_CMDLEN; ++k)
|
|
pr2ws("%02x ", sc_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, sc_cdb, sizeof(sc_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI READ CAPACITY (16) command. Returns 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_readcap_16(int sg_fd, bool pmi, uint64_t llba, void * resp,
|
|
int mx_resp_len, bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "read capacity(16)";
|
|
int k, ret, res, sense_cat;
|
|
unsigned char rc_cdb[SERVICE_ACTION_IN_16_CMDLEN] =
|
|
{SERVICE_ACTION_IN_16_CMD, READ_CAPACITY_16_SA,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
if (pmi) { /* lbs only valid when pmi set */
|
|
rc_cdb[14] |= 1;
|
|
sg_put_unaligned_be64(llba, rc_cdb + 2);
|
|
}
|
|
/* Allocation length, no guidance in SBC-2 rev 15b */
|
|
sg_put_unaligned_be32((uint32_t)mx_resp_len, rc_cdb + 10);
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < SERVICE_ACTION_IN_16_CMDLEN; ++k)
|
|
pr2ws("%02x ", rc_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI READ CAPACITY (10) command. Returns 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_readcap_10(int sg_fd, bool pmi, unsigned int lba, void * resp,
|
|
int mx_resp_len, bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "read capacity(10)";
|
|
int k, ret, res, sense_cat;
|
|
unsigned char rc_cdb[READ_CAPACITY_10_CMDLEN] =
|
|
{READ_CAPACITY_10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
if (pmi) { /* lbs only valid when pmi set */
|
|
rc_cdb[8] |= 1;
|
|
sg_put_unaligned_be32((uint32_t)lba, rc_cdb + 2);
|
|
}
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < READ_CAPACITY_10_CMDLEN; ++k)
|
|
pr2ws("%02x ", rc_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, rc_cdb, sizeof(rc_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI MODE SENSE (6) command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_mode_sense6(int sg_fd, bool dbd, int pc, int pg_code, int sub_pg_code,
|
|
void * resp, int mx_resp_len, bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "mode sense(6)";
|
|
int res, ret, k, sense_cat, resid;
|
|
unsigned char modes_cdb[MODE_SENSE6_CMDLEN] =
|
|
{MODE_SENSE6_CMD, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
modes_cdb[1] = (unsigned char)(dbd ? 0x8 : 0);
|
|
modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
|
|
modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff);
|
|
modes_cdb[4] = (unsigned char)(mx_resp_len & 0xff);
|
|
if (mx_resp_len > 0xff) {
|
|
pr2ws("mx_resp_len too big\n");
|
|
return -1;
|
|
}
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < MODE_SENSE6_CMDLEN; ++k)
|
|
pr2ws("%02x ", modes_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
resid = get_scsi_pt_resid(ptvp);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else {
|
|
if ((verbose > 2) && (ret > 0)) {
|
|
pr2ws(" %s: response", cdb_name_s);
|
|
if (3 == verbose) {
|
|
pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
|
|
hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
|
|
-1);
|
|
} else {
|
|
pr2ws(":\n");
|
|
hex2stderr((const uint8_t *)resp, ret, 0);
|
|
}
|
|
}
|
|
ret = 0;
|
|
}
|
|
destruct_scsi_pt_obj(ptvp);
|
|
|
|
if (resid > 0) {
|
|
if (resid > mx_resp_len) {
|
|
pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
|
|
cdb_name_s, resid, mx_resp_len);
|
|
return ret ? ret : SG_LIB_CAT_MALFORMED;
|
|
}
|
|
/* zero unfilled section of response buffer */
|
|
memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_mode_sense10(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
|
|
int sub_pg_code, void * resp, int mx_resp_len,
|
|
bool noisy, int verbose)
|
|
{
|
|
return sg_ll_mode_sense10_v2(sg_fd, llbaa, dbd, pc, pg_code, sub_pg_code,
|
|
resp, mx_resp_len, 0, NULL, noisy, verbose);
|
|
}
|
|
|
|
/* Invokes a SCSI MODE SENSE (10) command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors.
|
|
* Adds the ability to set the command abort timeout
|
|
* and the ability to report the residual count. If timeout_secs is zero
|
|
* or less the default command abort timeout (60 seconds) is used.
|
|
* If residp is non-NULL then the residual value is written where residp
|
|
* points. A residual value of 0 implies mx_resp_len bytes have be written
|
|
* where resp points. If the residual value equals mx_resp_len then no
|
|
* bytes have been written. */
|
|
int
|
|
sg_ll_mode_sense10_v2(int sg_fd, bool llbaa, bool dbd, int pc, int pg_code,
|
|
int sub_pg_code, void * resp, int mx_resp_len,
|
|
int timeout_secs, int * residp, bool noisy, int verbose)
|
|
{
|
|
int res, ret, k, sense_cat, resid;
|
|
static const char * const cdb_name_s = "mode sense(10)";
|
|
struct sg_pt_base * ptvp;
|
|
unsigned char modes_cdb[MODE_SENSE10_CMDLEN] =
|
|
{MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
|
|
modes_cdb[1] = (unsigned char)((dbd ? 0x8 : 0) | (llbaa ? 0x10 : 0));
|
|
modes_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
|
|
modes_cdb[3] = (unsigned char)(sub_pg_code & 0xff);
|
|
sg_put_unaligned_be16((int16_t)mx_resp_len, modes_cdb + 7);
|
|
if (mx_resp_len > 0xffff) {
|
|
pr2ws("mx_resp_len too big\n");
|
|
goto gen_err;
|
|
}
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < MODE_SENSE10_CMDLEN; ++k)
|
|
pr2ws("%02x ", modes_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (timeout_secs <= 0)
|
|
timeout_secs = DEF_PT_TIMEOUT;
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
goto gen_err;
|
|
set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
resid = get_scsi_pt_resid(ptvp);
|
|
if (residp)
|
|
*residp = resid;
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else {
|
|
if ((verbose > 2) && (ret > 0)) {
|
|
pr2ws(" %s: response", cdb_name_s);
|
|
if (3 == verbose) {
|
|
pr2ws("%s:\n", (ret > 256 ? ", first 256 bytes" : ""));
|
|
hex2stderr((const uint8_t *)resp, (ret > 256 ? 256 : ret),
|
|
-1);
|
|
} else {
|
|
pr2ws(":\n");
|
|
hex2stderr((const uint8_t *)resp, ret, 0);
|
|
}
|
|
}
|
|
ret = 0;
|
|
}
|
|
destruct_scsi_pt_obj(ptvp);
|
|
|
|
if (resid > 0) {
|
|
if (resid > mx_resp_len) {
|
|
pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
|
|
cdb_name_s, resid, mx_resp_len);
|
|
return ret ? ret : SG_LIB_CAT_MALFORMED;
|
|
}
|
|
/* zero unfilled section of response buffer */
|
|
memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
|
|
}
|
|
return ret;
|
|
gen_err:
|
|
if (residp)
|
|
*residp = 0;
|
|
return -1;
|
|
}
|
|
|
|
/* Invokes a SCSI MODE SELECT (6) command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_mode_select6(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
|
|
bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "mode select(6)";
|
|
int res, ret, k, sense_cat;
|
|
unsigned char modes_cdb[MODE_SELECT6_CMDLEN] =
|
|
{MODE_SELECT6_CMD, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
|
|
modes_cdb[4] = (unsigned char)(param_len & 0xff);
|
|
if (param_len > 0xff) {
|
|
pr2ws("%s: param_len too big\n", cdb_name_s);
|
|
return -1;
|
|
}
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < MODE_SELECT6_CMDLEN; ++k)
|
|
pr2ws("%02x ", modes_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (verbose > 1) {
|
|
pr2ws(" %s parameter list\n", cdb_name_s);
|
|
hex2stderr((const uint8_t *)paramp, param_len, -1);
|
|
}
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI MODE SELECT (10) command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_mode_select10(int sg_fd, bool pf, bool sp, void * paramp, int param_len,
|
|
bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "mode select(10)";
|
|
int res, ret, k, sense_cat;
|
|
unsigned char modes_cdb[MODE_SELECT10_CMDLEN] =
|
|
{MODE_SELECT10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
modes_cdb[1] = (unsigned char)((pf ? 0x10 : 0x0) | (sp ? 0x1 : 0x0));
|
|
sg_put_unaligned_be16((int16_t)param_len, modes_cdb + 7);
|
|
if (param_len > 0xffff) {
|
|
pr2ws("%s: param_len too big\n", cdb_name_s);
|
|
return -1;
|
|
}
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < MODE_SELECT10_CMDLEN; ++k)
|
|
pr2ws("%02x ", modes_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (verbose > 1) {
|
|
pr2ws(" %s parameter list\n", cdb_name_s);
|
|
hex2stderr((const uint8_t *)paramp, param_len, -1);
|
|
}
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, modes_cdb, sizeof(modes_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_out(ptvp, (unsigned char *)paramp, param_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* MODE SENSE commands yield a response that has header then zero or more
|
|
* block descriptors followed by mode pages. In most cases users are
|
|
* interested in the first mode page. This function returns the (byte)
|
|
* offset of the start of the first mode page. Set mode_sense_6 to true for
|
|
* MODE SENSE (6) and false for MODE SENSE (10). Returns >= 0 is successful
|
|
* or -1 if failure. If there is a failure a message is written to err_buff
|
|
* if it is non-NULL and err_buff_len > 0. */
|
|
int
|
|
sg_mode_page_offset(const unsigned char * resp, int resp_len,
|
|
bool mode_sense_6, char * err_buff, int err_buff_len)
|
|
{
|
|
int bd_len, calc_len, offset;
|
|
bool err_buff_ok = ((err_buff_len > 0) && err_buff);
|
|
|
|
if ((NULL == resp) || (resp_len < 4))
|
|
goto too_short;
|
|
if (mode_sense_6) {
|
|
calc_len = resp[0] + 1;
|
|
bd_len = resp[3];
|
|
offset = bd_len + MODE6_RESP_HDR_LEN;
|
|
} else { /* Mode sense(10) */
|
|
if (resp_len < 8)
|
|
goto too_short;
|
|
calc_len = sg_get_unaligned_be16(resp) + 2;
|
|
bd_len = sg_get_unaligned_be16(resp + 6);
|
|
/* LongLBA doesn't change this calculation */
|
|
offset = bd_len + MODE10_RESP_HDR_LEN;
|
|
}
|
|
if ((offset + 2) > calc_len) {
|
|
if (err_buff_ok)
|
|
snprintf(err_buff, err_buff_len, "calculated response "
|
|
"length too small, offset=%d calc_len=%d bd_len=%d\n",
|
|
offset, calc_len, bd_len);
|
|
offset = -1;
|
|
}
|
|
return offset;
|
|
too_short:
|
|
if (err_buff_ok)
|
|
snprintf(err_buff, err_buff_len, "given MS(%d) response length (%d) "
|
|
"too short\n", (mode_sense_6 ? 6 : 10), resp_len);
|
|
return -1;
|
|
}
|
|
|
|
/* MODE SENSE commands yield a response that has header then zero or more
|
|
* block descriptors followed by mode pages. This functions returns the
|
|
* length (in bytes) of those three components. Note that the return value
|
|
* can exceed resp_len in which case the MODE SENSE command should be
|
|
* re-issued with a larger response buffer. If bd_lenp is non-NULL and if
|
|
* successful the block descriptor length (in bytes) is written to *bd_lenp.
|
|
* Set mode_sense_6 to true for MODE SENSE (6) and false for MODE SENSE (10)
|
|
* responses. Returns -1 if there is an error (e.g. response too short). */
|
|
int
|
|
sg_msense_calc_length(const unsigned char * resp, int resp_len,
|
|
bool mode_sense_6, int * bd_lenp)
|
|
{
|
|
int calc_len;
|
|
|
|
if (NULL == resp)
|
|
goto an_err;
|
|
if (mode_sense_6) {
|
|
if (resp_len < 4)
|
|
goto an_err;
|
|
calc_len = resp[0] + 1;
|
|
} else {
|
|
if (resp_len < 8)
|
|
goto an_err;
|
|
calc_len = sg_get_unaligned_be16(resp + 0) + 2;
|
|
}
|
|
if (bd_lenp)
|
|
*bd_lenp = mode_sense_6 ? resp[3] : sg_get_unaligned_be16(resp + 6);
|
|
return calc_len;
|
|
an_err:
|
|
if (bd_lenp)
|
|
*bd_lenp = 0;
|
|
return -1;
|
|
}
|
|
|
|
/* Fetches current, changeable, default and/or saveable modes pages as
|
|
* indicated by pcontrol_arr for given pg_code and sub_pg_code. If
|
|
* mode6==false then use MODE SENSE (10) else use MODE SENSE (6). If
|
|
* flexible set and mode data length seems wrong then try and
|
|
* fix (compensating hack for bad device or driver). pcontrol_arr
|
|
* should have 4 elements for output of current, changeable, default
|
|
* and saved values respectively. Each element should be NULL or
|
|
* at least mx_mpage_len bytes long.
|
|
* Return of 0 -> overall success, various SG_LIB_CAT_* positive values or
|
|
* -1 -> other errors.
|
|
* If success_mask pointer is not NULL then first zeros it. Then set bits
|
|
* 0, 1, 2 and/or 3 if the current, changeable, default and saved values
|
|
* respectively have been fetched. If error on current page
|
|
* then stops and returns that error; otherwise continues if an error is
|
|
* detected but returns the first error encountered. */
|
|
int
|
|
sg_get_mode_page_controls(int sg_fd, bool mode6, int pg_code, int sub_pg_code,
|
|
bool dbd, bool flexible, int mx_mpage_len,
|
|
int * success_mask, void * pcontrol_arr[],
|
|
int * reported_lenp, int verbose)
|
|
{
|
|
bool resp_mode6;
|
|
int k, n, res, offset, calc_len, xfer_len;
|
|
int resid = 0;
|
|
const int msense10_hlen = MODE10_RESP_HDR_LEN;
|
|
unsigned char buff[MODE_RESP_ARB_LEN];
|
|
char ebuff[EBUFF_SZ];
|
|
int first_err = 0;
|
|
|
|
if (success_mask)
|
|
*success_mask = 0;
|
|
if (reported_lenp)
|
|
*reported_lenp = 0;
|
|
if (mx_mpage_len < 4)
|
|
return 0;
|
|
memset(ebuff, 0, sizeof(ebuff));
|
|
/* first try to find length of current page response */
|
|
memset(buff, 0, msense10_hlen);
|
|
if (mode6) /* want first 8 bytes just in case */
|
|
res = sg_ll_mode_sense6(sg_fd, dbd, 0 /* pc */, pg_code,
|
|
sub_pg_code, buff, msense10_hlen, true,
|
|
verbose);
|
|
else /* MODE SENSE(10) obviously */
|
|
res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
|
|
0 /* pc */, pg_code, sub_pg_code, buff,
|
|
msense10_hlen, 0, &resid, true, verbose);
|
|
if (0 != res)
|
|
return res;
|
|
n = buff[0];
|
|
if (reported_lenp) {
|
|
int m;
|
|
|
|
m = sg_msense_calc_length(buff, msense10_hlen, mode6, NULL) - resid;
|
|
if (m < 0) /* Grrr, this should not happen */
|
|
m = 0;
|
|
*reported_lenp = m;
|
|
}
|
|
resp_mode6 = mode6;
|
|
if (flexible) {
|
|
if (mode6 && (n < 3)) {
|
|
resp_mode6 = false;
|
|
if (verbose)
|
|
pr2ws(">>> msense(6) but resp[0]=%d so try msense(10) "
|
|
"response processing\n", n);
|
|
}
|
|
if ((! mode6) && (n > 5)) {
|
|
if ((n > 11) && (0 == (n % 2)) && (0 == buff[4]) &&
|
|
(0 == buff[5]) && (0 == buff[6])) {
|
|
buff[1] = n;
|
|
buff[0] = 0;
|
|
if (verbose)
|
|
pr2ws(">>> msense(10) but resp[0]=%d and not msense(6) "
|
|
"response so fix length\n", n);
|
|
} else
|
|
resp_mode6 = true;
|
|
}
|
|
}
|
|
if (verbose && (resp_mode6 != mode6))
|
|
pr2ws(">>> msense(%d) but resp[0]=%d so switch response "
|
|
"processing\n", (mode6 ? 6 : 10), buff[0]);
|
|
calc_len = sg_msense_calc_length(buff, msense10_hlen, resp_mode6, NULL);
|
|
if (calc_len > MODE_RESP_ARB_LEN)
|
|
calc_len = MODE_RESP_ARB_LEN;
|
|
offset = sg_mode_page_offset(buff, calc_len, resp_mode6, ebuff, EBUFF_SZ);
|
|
if (offset < 0) {
|
|
if (('\0' != ebuff[0]) && (verbose > 0))
|
|
pr2ws("%s: %s\n", __func__, ebuff);
|
|
return SG_LIB_CAT_MALFORMED;
|
|
}
|
|
xfer_len = calc_len - offset;
|
|
if (xfer_len > mx_mpage_len)
|
|
xfer_len = mx_mpage_len;
|
|
|
|
for (k = 0; k < 4; ++k) {
|
|
if (NULL == pcontrol_arr[k])
|
|
continue;
|
|
memset(pcontrol_arr[k], 0, mx_mpage_len);
|
|
resid = 0;
|
|
if (mode6)
|
|
res = sg_ll_mode_sense6(sg_fd, dbd, k /* pc */,
|
|
pg_code, sub_pg_code, buff,
|
|
calc_len, true, verbose);
|
|
else
|
|
res = sg_ll_mode_sense10_v2(sg_fd, false /* llbaa */, dbd,
|
|
k /* pc */, pg_code, sub_pg_code,
|
|
buff, calc_len, 0, &resid, true,
|
|
verbose);
|
|
if (res || resid) {
|
|
if (0 == first_err) {
|
|
if (res)
|
|
first_err = res;
|
|
else {
|
|
first_err = -49; /* unexpected resid != 0 */
|
|
if (verbose)
|
|
pr2ws("%s: unexpected resid=%d, page=0x%x, "
|
|
"pcontrol=%d\n", __func__, resid, pg_code, k);
|
|
}
|
|
}
|
|
if (0 == k)
|
|
break; /* if problem on current page, it won't improve */
|
|
else
|
|
continue;
|
|
}
|
|
if (xfer_len > 0)
|
|
memcpy(pcontrol_arr[k], buff + offset, xfer_len);
|
|
if (success_mask)
|
|
*success_mask |= (1 << k);
|
|
}
|
|
return first_err;
|
|
}
|
|
|
|
/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors. */
|
|
int
|
|
sg_ll_log_sense(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
|
|
int subpg_code, int paramp, unsigned char * resp,
|
|
int mx_resp_len, bool noisy, int verbose)
|
|
{
|
|
return sg_ll_log_sense_v2(sg_fd, ppc, sp, pc, pg_code, subpg_code,
|
|
paramp, resp, mx_resp_len, 0, NULL, noisy,
|
|
verbose);
|
|
}
|
|
|
|
/* Invokes a SCSI LOG SENSE command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors.
|
|
* Adds the ability to set the command abort timeout
|
|
* and the ability to report the residual count. If timeout_secs is zero
|
|
* or less the default command abort timeout (60 seconds) is used.
|
|
* If residp is non-NULL then the residual value is written where residp
|
|
* points. A residual value of 0 implies mx_resp_len bytes have be written
|
|
* where resp points. If the residual value equals mx_resp_len then no
|
|
* bytes have been written. */
|
|
int
|
|
sg_ll_log_sense_v2(int sg_fd, bool ppc, bool sp, int pc, int pg_code,
|
|
int subpg_code, int paramp, unsigned char * resp,
|
|
int mx_resp_len, int timeout_secs, int * residp,
|
|
bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "log sense";
|
|
int res, ret, k, sense_cat, resid;
|
|
unsigned char logs_cdb[LOG_SENSE_CMDLEN] =
|
|
{LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
if (mx_resp_len > 0xffff) {
|
|
pr2ws("mx_resp_len too big\n");
|
|
goto gen_err;
|
|
}
|
|
logs_cdb[1] = (unsigned char)((ppc ? 2 : 0) | (sp ? 1 : 0));
|
|
logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
|
|
logs_cdb[3] = (unsigned char)(subpg_code & 0xff);
|
|
sg_put_unaligned_be16((int16_t)paramp, logs_cdb + 5);
|
|
sg_put_unaligned_be16((int16_t)mx_resp_len, logs_cdb + 7);
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < LOG_SENSE_CMDLEN; ++k)
|
|
pr2ws("%02x ", logs_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if (timeout_secs <= 0)
|
|
timeout_secs = DEF_PT_TIMEOUT;
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
goto gen_err;
|
|
set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_in(ptvp, resp, mx_resp_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, timeout_secs, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, mx_resp_len,
|
|
sense_b, noisy, verbose, &sense_cat);
|
|
resid = get_scsi_pt_resid(ptvp);
|
|
if (residp)
|
|
*residp = resid;
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else {
|
|
if ((mx_resp_len > 3) && (ret < 4)) {
|
|
/* resid indicates LOG SENSE response length bad, so zero it */
|
|
resp[2] = 0;
|
|
resp[3] = 0;
|
|
}
|
|
ret = 0;
|
|
}
|
|
destruct_scsi_pt_obj(ptvp);
|
|
|
|
if (resid > 0) {
|
|
if (resid > mx_resp_len) {
|
|
pr2ws("%s: resid (%d) should never exceed requested len=%d\n",
|
|
cdb_name_s, resid, mx_resp_len);
|
|
return ret ? ret : SG_LIB_CAT_MALFORMED;
|
|
}
|
|
/* zero unfilled section of response buffer */
|
|
memset((unsigned char *)resp + (mx_resp_len - resid), 0, resid);
|
|
}
|
|
return ret;
|
|
gen_err:
|
|
if (residp)
|
|
*residp = 0;
|
|
return -1;
|
|
}
|
|
|
|
/* Invokes a SCSI LOG SELECT command. Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_log_select(int sg_fd, bool pcr, bool sp, int pc, int pg_code,
|
|
int subpg_code, unsigned char * paramp, int param_len,
|
|
bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "log select";
|
|
int res, ret, k, sense_cat;
|
|
unsigned char logs_cdb[LOG_SELECT_CMDLEN] =
|
|
{LOG_SELECT_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
if (param_len > 0xffff) {
|
|
pr2ws("%s: param_len too big\n", cdb_name_s);
|
|
return -1;
|
|
}
|
|
logs_cdb[1] = (unsigned char)((pcr ? 2 : 0) | (sp ? 1 : 0));
|
|
logs_cdb[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
|
|
logs_cdb[3] = (unsigned char)(subpg_code & 0xff);
|
|
sg_put_unaligned_be16((int16_t)param_len, logs_cdb + 7);
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < LOG_SELECT_CMDLEN; ++k)
|
|
pr2ws("%02x ", logs_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
if ((verbose > 1) && (param_len > 0)) {
|
|
pr2ws(" %s parameter list\n", cdb_name_s);
|
|
hex2stderr(paramp, param_len, -1);
|
|
}
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, logs_cdb, sizeof(logs_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
set_scsi_pt_data_out(ptvp, paramp, param_len);
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI START STOP UNIT command (SBC + MMC).
|
|
* Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors.
|
|
* SBC-3 and MMC partially overlap on the power_condition_modifier(sbc) and
|
|
* format_layer_number(mmc) fields. They also overlap on the noflush(sbc)
|
|
* and fl(mmc) one bit field. This is the cause of the awkardly named
|
|
* pc_mod__fl_num and noflush__fl arguments to this function.
|
|
* */
|
|
int
|
|
sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
|
|
int power_cond, bool noflush__fl, bool loej, bool start,
|
|
bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "start stop unit";
|
|
int k, res, ret, sense_cat;
|
|
struct sg_pt_base * ptvp;
|
|
unsigned char ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
|
|
if (immed)
|
|
ssuBlk[1] = 0x1;
|
|
ssuBlk[3] = pc_mod__fl_num & 0xf; /* bits 2 and 3 are reserved in MMC */
|
|
ssuBlk[4] = ((power_cond & 0xf) << 4);
|
|
if (noflush__fl)
|
|
ssuBlk[4] |= 0x4;
|
|
if (loej)
|
|
ssuBlk[4] |= 0x2;
|
|
if (start)
|
|
ssuBlk[4] |= 0x1;
|
|
if (verbose) {
|
|
pr2ws(" %s command:", cdb_name_s);
|
|
for (k = 0; k < (int)sizeof(ssuBlk); ++k)
|
|
pr2ws(" %02x", ssuBlk[k]);
|
|
pr2ws("\n");
|
|
}
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
res = do_scsi_pt(ptvp, sg_fd, START_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|
|
|
|
/* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command
|
|
* [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3]
|
|
* prevent==0 allows removal, prevent==1 prevents removal ...
|
|
* Return of 0 -> success,
|
|
* various SG_LIB_CAT_* positive values or -1 -> other errors */
|
|
int
|
|
sg_ll_prevent_allow(int sg_fd, int prevent, bool noisy, int verbose)
|
|
{
|
|
static const char * const cdb_name_s = "prevent allow medium removal";
|
|
int k, res, ret, sense_cat;
|
|
unsigned char p_cdb[PREVENT_ALLOW_CMDLEN] =
|
|
{PREVENT_ALLOW_CMD, 0, 0, 0, 0, 0};
|
|
unsigned char sense_b[SENSE_BUFF_LEN];
|
|
struct sg_pt_base * ptvp;
|
|
|
|
if ((prevent < 0) || (prevent > 3)) {
|
|
pr2ws("prevent argument should be 0, 1, 2 or 3\n");
|
|
return -1;
|
|
}
|
|
p_cdb[4] |= (prevent & 0x3);
|
|
if (verbose) {
|
|
pr2ws(" %s cdb: ", cdb_name_s);
|
|
for (k = 0; k < PREVENT_ALLOW_CMDLEN; ++k)
|
|
pr2ws("%02x ", p_cdb[k]);
|
|
pr2ws("\n");
|
|
}
|
|
|
|
if (NULL == ((ptvp = create_pt_obj(cdb_name_s))))
|
|
return -1;
|
|
set_scsi_pt_cdb(ptvp, p_cdb, sizeof(p_cdb));
|
|
set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
|
|
res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
|
|
ret = sg_cmds_process_resp(ptvp, cdb_name_s, res, SG_NO_DATA_IN, sense_b,
|
|
noisy, verbose, &sense_cat);
|
|
if (-1 == ret) {
|
|
int os_err = get_scsi_pt_os_err(ptvp);
|
|
|
|
if ((os_err > 0) && (os_err < 47))
|
|
ret = SG_LIB_OS_BASE_ERR + os_err;
|
|
} else if (-2 == ret) {
|
|
switch (sense_cat) {
|
|
case SG_LIB_CAT_RECOVERED:
|
|
case SG_LIB_CAT_NO_SENSE:
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
ret = sense_cat;
|
|
break;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
destruct_scsi_pt_obj(ptvp);
|
|
return ret;
|
|
}
|