aboutsummaryrefslogtreecommitdiff
path: root/lib/sg_cmds_basic.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sg_cmds_basic.c')
-rw-r--r--lib/sg_cmds_basic.c927
1 files changed, 927 insertions, 0 deletions
diff --git a/lib/sg_cmds_basic.c b/lib/sg_cmds_basic.c
new file mode 100644
index 00000000..01ca55c1
--- /dev/null
+++ b/lib/sg_cmds_basic.c
@@ -0,0 +1,927 @@
+/*
+ * 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
+ */
+
+/*
+ * 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 <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_pt.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* Needs to be after config.h */
+#ifdef SG_LIB_LINUX
+#include <errno.h>
+#endif
+
+
+static const char * const version_str = "2.00 20220118";
+
+
+#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 INQUIRY_CMD 0x12
+#define INQUIRY_CMDLEN 6
+#define REQUEST_SENSE_CMD 0x3
+#define REQUEST_SENSE_CMDLEN 6
+#define REPORT_LUNS_CMD 0xa0
+#define REPORT_LUNS_CMDLEN 12
+#define TUR_CMD 0x0
+#define TUR_CMDLEN 6
+
+#define SAFE_STD_INQ_RESP_LEN 36 /* other lengths lock up some devices */
+
+
+const char *
+sg_cmds_version()
+{
+ return version_str;
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+ negated errno. */
+int
+sg_cmds_open_device(const char * device_name, bool read_only, int verbose)
+{
+ return scsi_pt_open_device(device_name, read_only, verbose);
+}
+
+/* Returns file descriptor >= 0 if successful. If error in Unix returns
+ negated errno. */
+int
+sg_cmds_open_flags(const char * device_name, int flags, int verbose)
+{
+ return scsi_pt_open_flags(device_name, flags, verbose);
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+sg_cmds_close_device(int device_fd)
+{
+ return scsi_pt_close_device(device_fd);
+}
+
+static const char * const pass_through_s = "pass-through";
+
+static void
+sg_cmds_resid_print(const char * leadin, bool is_din, int num_req,
+ int num_got)
+{
+ pr2ws(" %s: %s requested %d bytes (data-%s got %d "
+ "bytes%s\n", leadin, pass_through_s,num_req,
+ (is_din ? "in), got" : "out) but reported"), num_got,
+ (is_din ? "" : " sent"));
+}
+
+static int
+sg_cmds_process_helper(const char * leadin, int req_din_x, int act_din_x,
+ int req_dout_x, int act_dout_x, const uint8_t * sbp,
+ int slen, bool noisy, int verbose, int * o_sense_cat)
+{
+ int scat;
+ bool n = false;
+ bool check_data_in = false;
+
+ scat = sg_err_category_sense(sbp, slen);
+ switch (scat) {
+ case SG_LIB_CAT_NOT_READY:
+ case SG_LIB_CAT_INVALID_OP:
+ case SG_LIB_CAT_ILLEGAL_REQ:
+ case SG_LIB_LBA_OUT_OF_RANGE:
+ case SG_LIB_CAT_ABORTED_COMMAND:
+ case SG_LIB_CAT_COPY_ABORTED:
+ case SG_LIB_CAT_DATA_PROTECT:
+ case SG_LIB_CAT_PROTECTION:
+ case SG_LIB_CAT_NO_SENSE:
+ case SG_LIB_CAT_MISCOMPARE:
+ case SG_LIB_CAT_STANDBY:
+ case SG_LIB_CAT_UNAVAILABLE:
+ n = false;
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_MEDIUM_HARD:
+ check_data_in = true;
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ case SG_LIB_CAT_SENSE:
+ default:
+ n = noisy;
+ break;
+ }
+ if (verbose || n) {
+ char b[512];
+
+ if (leadin && (strlen(leadin) > 0))
+ pr2ws("%s:\n", leadin);
+ sg_get_sense_str(NULL, sbp, slen, (verbose > 1),
+ sizeof(b), b);
+ pr2ws("%s", b);
+ if (req_din_x > 0) {
+ if (act_din_x != req_din_x) {
+ if ((verbose > 2) || check_data_in || (act_din_x > 0))
+ sg_cmds_resid_print(leadin, true, req_din_x, act_din_x);
+ if (act_din_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't get negative bytes, say it "
+ "got none\n", leadin, pass_through_s);
+ }
+ }
+ }
+ if (req_dout_x > 0) {
+ if (act_dout_x != req_dout_x) {
+ if ((verbose > 1) && (act_dout_x > 0))
+ sg_cmds_resid_print(leadin, false, req_dout_x, act_dout_x);
+ if (act_dout_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't send negative bytes, say it "
+ "sent none\n", leadin, pass_through_s);
+ }
+ }
+ }
+ }
+ if (o_sense_cat)
+ *o_sense_cat = scat;
+ return -2;
+}
+
+/* This is a helper function used by sg_cmds_* implementations after the
+ * call to the pass-through. pt_res is returned from do_scsi_pt(). If valid
+ * sense data is found it is decoded and output to sg_warnings_strm (def:
+ * stderr); depending on the 'noisy' and 'verbose' settings. Returns -2 for
+ * o_sense_cat (sense category) written which may not be fatal. Returns
+ * -1 for other types of failure. Returns 0, or a positive number. If data-in
+ * type command (or bidi) then returns actual number of bytes read
+ * (din_len - resid); otherwise returns 0. Note that several sense categories
+ * also have data in bytes received; -2 is still returned. */
+int
+sg_cmds_process_resp(struct sg_pt_base * ptvp, const char * leadin,
+ int pt_res, bool noisy, int verbose, int * o_sense_cat)
+{
+ int cat, slen, sstat, req_din_x, req_dout_x;
+ int act_din_x, act_dout_x;
+ const uint8_t * sbp;
+ char b[1024];
+
+ if (NULL == leadin)
+ leadin = "";
+ if (pt_res < 0) {
+#ifdef SG_LIB_LINUX
+ if (verbose)
+ pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+ safe_strerror(-pt_res));
+ if ((-ENXIO == pt_res) && o_sense_cat) {
+ if (verbose > 2)
+ pr2ws("map ENXIO to SG_LIB_CAT_NOT_READY\n");
+ *o_sense_cat = SG_LIB_CAT_NOT_READY;
+ return -2;
+ } else if (noisy && (0 == verbose))
+ pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+ safe_strerror(-pt_res));
+#else
+ if (noisy || verbose)
+ pr2ws("%s: %s os error: %s\n", leadin, pass_through_s,
+ safe_strerror(-pt_res));
+#endif
+ return -1;
+ } else if (SCSI_PT_DO_BAD_PARAMS == pt_res) {
+ pr2ws("%s: bad %s setup\n", leadin, pass_through_s);
+ return -1;
+ } else if (SCSI_PT_DO_TIMEOUT == pt_res) {
+ pr2ws("%s: %s timeout\n", leadin, pass_through_s);
+ return -1;
+ }
+ if (verbose > 2) {
+ uint64_t duration = get_pt_duration_ns(ptvp);
+
+ if (duration > 0)
+ pr2ws(" duration=%" PRIu64 " ns\n", duration);
+ else {
+ int d = get_scsi_pt_duration_ms(ptvp);
+
+ if (d != -1)
+ pr2ws(" duration=%u ms\n", (uint32_t)d);
+ }
+ }
+ get_pt_req_lengths(ptvp, &req_din_x, &req_dout_x);
+ get_pt_actual_lengths(ptvp, &act_din_x, &act_dout_x);
+ slen = get_scsi_pt_sense_len(ptvp);
+ sbp = get_scsi_pt_sense_buf(ptvp);
+ switch ((cat = get_scsi_pt_result_category(ptvp))) {
+ case SCSI_PT_RESULT_GOOD:
+ if (sbp && (slen > 7)) {
+ int resp_code = sbp[0] & 0x7f;
+
+ /* SBC referrals can have status=GOOD and sense_key=COMPLETED */
+ if (resp_code >= 0x70) {
+ if (resp_code < 0x72) {
+ if (SPC_SK_NO_SENSE != (0xf & sbp[2]))
+ sg_err_category_sense(sbp, slen);
+ } else if (resp_code < 0x74) {
+ if (SPC_SK_NO_SENSE != (0xf & sbp[1]))
+ sg_err_category_sense(sbp, slen);
+ }
+ }
+ }
+ if (req_din_x > 0) {
+ if (act_din_x != req_din_x) {
+ if ((verbose > 1) && (act_din_x >= 0))
+ sg_cmds_resid_print(leadin, true, req_din_x, act_din_x);
+ if (act_din_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't get negative bytes, say it "
+ "got none\n", leadin, pass_through_s);
+ act_din_x = 0;
+ }
+ }
+ }
+ if (req_dout_x > 0) {
+ if (act_dout_x != req_dout_x) {
+ if ((verbose > 1) && (act_dout_x >= 0))
+ sg_cmds_resid_print(leadin, false, req_dout_x, act_dout_x);
+ if (act_dout_x < 0) {
+ if (verbose)
+ pr2ws(" %s: %s can't send negative bytes, say it "
+ "sent none\n", leadin, pass_through_s);
+ act_dout_x = 0;
+ }
+ }
+ }
+ return act_din_x;
+ case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */
+ sstat = get_scsi_pt_status_response(ptvp);
+ if (o_sense_cat) {
+ switch (sstat) {
+ case SAM_STAT_RESERVATION_CONFLICT:
+ *o_sense_cat = SG_LIB_CAT_RES_CONFLICT;
+ return -2;
+ case SAM_STAT_CONDITION_MET:
+ *o_sense_cat = SG_LIB_CAT_CONDITION_MET;
+ return -2;
+ case SAM_STAT_BUSY:
+ *o_sense_cat = SG_LIB_CAT_BUSY;
+ return -2;
+ case SAM_STAT_TASK_SET_FULL:
+ *o_sense_cat = SG_LIB_CAT_TS_FULL;
+ return -2;
+ case SAM_STAT_ACA_ACTIVE:
+ *o_sense_cat = SG_LIB_CAT_ACA_ACTIVE;
+ return -2;
+ case SAM_STAT_TASK_ABORTED:
+ *o_sense_cat = SG_LIB_CAT_TASK_ABORTED;
+ return -2;
+ default:
+ break;
+ }
+ }
+ if (verbose || noisy) {
+ sg_get_scsi_status_str(sstat, sizeof(b), b);
+ pr2ws("%s: scsi status: %s\n", leadin, b);
+ }
+ return -1;
+ case SCSI_PT_RESULT_SENSE:
+ return sg_cmds_process_helper(leadin, req_din_x, act_din_x,
+ req_dout_x, act_dout_x, sbp, slen,
+ noisy, verbose, o_sense_cat);
+ case SCSI_PT_RESULT_TRANSPORT_ERR:
+ if (verbose || noisy) {
+ get_scsi_pt_transport_err_str(ptvp, sizeof(b), b);
+ pr2ws("%s: transport: %s\n", leadin, b);
+ }
+#ifdef SG_LIB_LINUX
+ return -1; /* DRIVER_SENSE is not passed through */
+#else
+ /* Shall we favour sense data over a transport error (given both) */
+ {
+ bool favour_sense = ((SAM_STAT_CHECK_CONDITION ==
+ get_scsi_pt_status_response(ptvp)) && (slen > 0));
+
+ if (favour_sense)
+ return sg_cmds_process_helper(leadin, req_din_x, act_din_x,
+ req_dout_x, act_dout_x, sbp,
+ slen, noisy, verbose,
+ o_sense_cat);
+ else
+ return -1;
+ }
+#endif
+ case SCSI_PT_RESULT_OS_ERR:
+ if (verbose || noisy) {
+ get_scsi_pt_os_err_str(ptvp, sizeof(b), b);
+ pr2ws("%s: os: %s\n", leadin, b);
+ }
+ return -1;
+ default:
+ pr2ws("%s: unknown %s result category (%d)\n", leadin, pass_through_s,
+ cat);
+ return -1;
+ }
+}
+
+bool
+sg_cmds_is_nvme(const struct sg_pt_base * ptvp)
+{
+ return pt_device_is_nvme(ptvp);
+}
+
+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;
+}
+
+static const char * const inquiry_s = "inquiry";
+
+
+/* Returns 0 on success, while positive values are SG_LIB_CAT_* errors
+ * (e.g. SG_LIB_CAT_MALFORMED). If OS error, returns negated errno or -1. */
+static int
+sg_ll_inquiry_com(struct sg_pt_base * ptvp, int sg_fd, bool cmddt, bool evpd,
+ int pg_op, void * resp, int mx_resp_len, int timeout_secs,
+ int * residp, bool noisy, int verbose)
+{
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int res, ret, sense_cat, resid;
+ uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ uint8_t * up;
+
+ if (resp == NULL) {
+ if (verbose)
+ pr2ws("Got NULL `resp` pointer");
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (cmddt)
+ inq_cdb[1] |= 0x2;
+ if (evpd)
+ inq_cdb[1] |= 0x1;
+ inq_cdb[2] = (uint8_t)pg_op;
+ /* 16 bit allocation length (was 8, increased in spc3r09, 200209) */
+ sg_put_unaligned_be16((uint16_t)mx_resp_len, inq_cdb + 3);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", inquiry_s,
+ sg_get_command_str(inq_cdb, INQUIRY_CMDLEN, false, sizeof(b),
+ b));
+ }
+ if (mx_resp_len > 0) {
+ up = (uint8_t *)resp;
+ up[0] = 0x7f; /* defensive prefill */
+ if (mx_resp_len > 4)
+ up[4] = 0;
+ }
+ if (timeout_secs <= 0)
+ timeout_secs = DEF_PT_TIMEOUT;
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, timeout_secs, verbose);
+ ret = sg_cmds_process_resp(ptvp, inquiry_s, res, noisy, verbose,
+ &sense_cat);
+ resid = get_scsi_pt_resid(ptvp);
+ if (residp)
+ *residp = resid;
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } 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 (ret < 4) {
+ if (verbose)
+ pr2ws("%s: got too few bytes (%d)\n", __func__, ret);
+ ret = SG_LIB_CAT_MALFORMED;
+ } else
+ ret = 0;
+
+ if (resid > 0) {
+ if (resid > mx_resp_len) {
+ pr2ws("%s resid (%d) should never exceed requested "
+ "len=%d\n", inquiry_s, resid, mx_resp_len);
+ if (0 == ret)
+ ret = SG_LIB_CAT_MALFORMED;
+ goto fini;
+ }
+ /* zero unfilled section of response buffer, based on resid */
+ memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
+ }
+fini:
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values, negated errno or
+ * -1 -> other errors. The CMDDT field is obsolete in the INQUIRY cdb. */
+int
+sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_inquiry_com(NULL, sg_fd, cmddt, evpd, pg_op, resp,
+ mx_resp_len, 0 /* timeout_sec */, NULL, noisy,
+ verbose);
+}
+
+/* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
+ * successful, various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * The CMDDT field is obsolete in the INQUIRY cdb (since spc3r16 in 2003) so
+ * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION
+ * CODES command instead). 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_inquiry_v2(int sg_fd, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp,
+ bool noisy, int verbose)
+{
+ return sg_ll_inquiry_com(NULL, sg_fd, false, evpd, pg_op, resp,
+ mx_resp_len, timeout_secs, residp, noisy,
+ verbose);
+}
+
+/* Similar to _v2 but takes a pointer to an object (derived from) sg_pt_base.
+ * That object is assumed to be constructed and have a device file descriptor
+ * associated with it. Caller is responsible for lifetime of ptp. */
+int
+sg_ll_inquiry_pt(struct sg_pt_base * ptvp, bool evpd, int pg_op, void * resp,
+ int mx_resp_len, int timeout_secs, int * residp, bool noisy,
+ int verbose)
+{
+ return sg_ll_inquiry_com(ptvp, -1, false, evpd, pg_op, resp, mx_resp_len,
+ timeout_secs, residp, noisy, verbose);
+}
+
+/* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values, negated
+ * errno or -1 -> other errors */
+int
+sg_simple_inquiry(int sg_fd, struct sg_simple_inquiry_resp * inq_data,
+ bool noisy, int verbose)
+{
+ int ret;
+ uint8_t * inq_resp = NULL;
+ uint8_t * free_irp = NULL;
+
+ if (inq_data) {
+ memset(inq_data, 0, sizeof(* inq_data));
+ inq_data->peripheral_qualifier = 0x3;
+ inq_data->peripheral_type = PDT_UNKNOWN;
+ }
+ inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false);
+ if (NULL == inq_resp) {
+ pr2ws("%s: out of memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ ret = sg_ll_inquiry_com(NULL, sg_fd, false, false, 0, inq_resp,
+ SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose);
+
+ if (inq_data && (0 == ret)) {
+ inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
+ inq_data->peripheral_type = inq_resp[0] & PDT_MASK;
+ inq_data->byte_1 = inq_resp[1];
+ inq_data->version = inq_resp[2];
+ inq_data->byte_3 = inq_resp[3];
+ inq_data->byte_5 = inq_resp[5];
+ inq_data->byte_6 = inq_resp[6];
+ inq_data->byte_7 = inq_resp[7];
+ memcpy(inq_data->vendor, inq_resp + 8, 8);
+ memcpy(inq_data->product, inq_resp + 16, 16);
+ memcpy(inq_data->revision, inq_resp + 32, 4);
+ }
+ if (free_irp)
+ free(free_irp);
+ return ret;
+}
+
+/* Similar to sg_simple_inquiry() but takes pointer to pt object rather
+ * than device file descriptor. */
+int
+sg_simple_inquiry_pt(struct sg_pt_base * ptvp,
+ struct sg_simple_inquiry_resp * inq_data,
+ bool noisy, int verbose)
+{
+ int ret;
+ uint8_t * inq_resp = NULL;
+ uint8_t * free_irp = NULL;
+
+ if (inq_data) {
+ memset(inq_data, 0, sizeof(* inq_data));
+ inq_data->peripheral_qualifier = 0x3;
+ inq_data->peripheral_type = PDT_MASK;
+ }
+ inq_resp = sg_memalign(SAFE_STD_INQ_RESP_LEN, 0, &free_irp, false);
+ if (NULL == inq_resp) {
+ pr2ws("%s: out of memory\n", __func__);
+ return sg_convert_errno(ENOMEM);
+ }
+ ret = sg_ll_inquiry_com(ptvp, -1, false, false, 0, inq_resp,
+ SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose);
+
+ if (inq_data && (0 == ret)) {
+ inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
+ inq_data->peripheral_type = inq_resp[0] & PDT_MASK;
+ inq_data->byte_1 = inq_resp[1];
+ inq_data->version = inq_resp[2];
+ inq_data->byte_3 = inq_resp[3];
+ inq_data->byte_5 = inq_resp[5];
+ inq_data->byte_6 = inq_resp[6];
+ inq_data->byte_7 = inq_resp[7];
+ memcpy(inq_data->vendor, inq_resp + 8, 8);
+ memcpy(inq_data->product, inq_resp + 16, 16);
+ memcpy(inq_data->revision, inq_resp + 32, 4);
+ }
+ if (free_irp)
+ free(free_irp);
+ return ret;
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * N.B. To access the sense buffer outside this routine then one be
+ * provided by the caller.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Looks for progress indicator if 'progress' non-NULL;
+ * if found writes value [0..65535] else write -1.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+static int
+sg_ll_test_unit_ready_com(struct sg_pt_base * ptvp, int sg_fd, int pack_id,
+ int * progress, bool noisy, int verbose)
+{
+ static const char * const tur_s = "test unit ready";
+ bool ptvp_given = false;
+ bool local_sense = true;
+ bool local_cdb = true;
+ int res, ret, sense_cat;
+ uint8_t tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", tur_s,
+ sg_get_command_str(tur_cdb, TUR_CMDLEN, false, sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false; /* N.B. Ignores locally built cdb */
+ else
+ set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_packet_id(ptvp, pack_id);
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, tur_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } else if (-2 == ret) {
+ if (progress) {
+ int slen = get_scsi_pt_sense_len(ptvp);
+
+ if (! sg_get_sense_progress_fld(sense_b, slen, progress))
+ *progress = -1;
+ }
+ 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;
+ if (ptvp_given) {
+ if (local_sense) /* stop caller trying to access local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+int
+sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id,
+ int * progress, bool noisy, int verbose)
+{
+ return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, progress, noisy,
+ verbose);
+}
+
+int
+sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
+ bool noisy, int verbose)
+{
+ return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, progress, noisy,
+ verbose);
+}
+
+/* Invokes a SCSI TEST UNIT READY command.
+ * 'pack_id' is just for diagnostics, safe to set to 0.
+ * Returns 0 when successful, various SG_LIB_CAT_* positive values or
+ * -1 -> other errors */
+int
+sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose)
+{
+ return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, NULL, noisy,
+ verbose);
+}
+
+int
+sg_ll_test_unit_ready_pt(struct sg_pt_base * ptvp, int pack_id, bool noisy,
+ int verbose)
+{
+ return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, NULL, noisy, verbose);
+}
+
+/* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various
+ * SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_request_sense_com(struct sg_pt_base * ptvp, int sg_fd, bool desc,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ bool ptvp_given = false;
+ bool local_cdb = true;
+ bool local_sense = true;
+ int ret, res, sense_cat;
+ static const char * const rq_s = "request sense";
+ uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] =
+ {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ if (desc)
+ rs_cdb[1] |= 0x1;
+ if (mx_resp_len > 0xff) {
+ pr2ws("mx_resp_len cannot exceed 255\n");
+ return -1;
+ }
+ rs_cdb[4] = mx_resp_len & 0xff;
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", rq_s,
+ sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false;
+ else
+ set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+ if (NULL == ptvp)
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, rq_s, res, noisy, verbose, &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } 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 >= 8) && (ret < 8)) {
+ if (verbose)
+ pr2ws(" %s: got %d bytes in response, too short\n", rq_s,
+ ret);
+ ret = -1;
+ } else
+ ret = 0;
+ }
+ if (ptvp_given) {
+ if (local_sense) /* stop caller accessing local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb) /* stop caller accessing local sense */
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ return ret;
+}
+
+int
+sg_ll_request_sense(int sg_fd, bool desc, void * resp, int mx_resp_len,
+ bool noisy, int verbose)
+{
+ return sg_ll_request_sense_com(NULL, sg_fd, desc, resp, mx_resp_len,
+ noisy, verbose);
+}
+
+int
+sg_ll_request_sense_pt(struct sg_pt_base * ptvp, bool desc, void * resp,
+ int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_request_sense_com(ptvp, -1, desc, resp, mx_resp_len,
+ noisy, verbose);
+}
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors */
+static int
+sg_ll_report_luns_com(struct sg_pt_base * ptvp, int sg_fd, int select_report,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ static const char * const report_luns_s = "report luns";
+ bool ptvp_given = false;
+ bool local_cdb = true;
+ bool local_sense = true;
+ int ret, res, sense_cat;
+ uint8_t rl_cdb[REPORT_LUNS_CMDLEN] =
+ {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+
+ rl_cdb[2] = select_report & 0xff;
+ sg_put_unaligned_be32((uint32_t)mx_resp_len, rl_cdb + 6);
+ if (verbose) {
+ char b[128];
+
+ pr2ws(" %s cdb: %s\n", report_luns_s,
+ sg_get_command_str(rl_cdb, REPORT_LUNS_CMDLEN, false,
+ sizeof(b), b));
+ }
+ if (ptvp) {
+ ptvp_given = true;
+ partial_clear_scsi_pt_obj(ptvp);
+ if (get_scsi_pt_cdb_buf(ptvp))
+ local_cdb = false;
+ else
+ set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+ if (get_scsi_pt_sense_buf(ptvp))
+ local_sense = false;
+ else
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ } else {
+ if (NULL == ((ptvp = create_pt_obj(report_luns_s))))
+ return sg_convert_errno(ENOMEM);
+ set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+ set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+ }
+ set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
+ res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+ ret = sg_cmds_process_resp(ptvp, report_luns_s, res, noisy, verbose,
+ &sense_cat);
+ if (-1 == ret) {
+ if (get_scsi_pt_transport_err(ptvp))
+ ret = SG_LIB_TRANSPORT_ERROR;
+ else
+ ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
+ } 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;
+ if (ptvp_given) {
+ if (local_sense) /* stop caller accessing local sense */
+ set_scsi_pt_sense(ptvp, NULL, 0);
+ if (local_cdb)
+ set_scsi_pt_cdb(ptvp, NULL, 0);
+ } else {
+ if (ptvp)
+ destruct_scsi_pt_obj(ptvp);
+ }
+ return ret;
+}
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors,
+ * Expects sg_fd to be >= 0 representing an open device fd. */
+int
+sg_ll_report_luns(int sg_fd, int select_report, void * resp, int mx_resp_len,
+ bool noisy, int verbose)
+{
+ return sg_ll_report_luns_com(NULL, sg_fd, select_report, resp,
+ mx_resp_len, noisy, verbose);
+}
+
+
+/* Invokes a SCSI REPORT LUNS command. Return of 0 -> success,
+ * various SG_LIB_CAT_* positive values or -1 -> other errors.
+ * Expects a non-NULL ptvp containing an open device fd. */
+int
+sg_ll_report_luns_pt(struct sg_pt_base * ptvp, int select_report,
+ void * resp, int mx_resp_len, bool noisy, int verbose)
+{
+ return sg_ll_report_luns_com(ptvp, -1, select_report, resp,
+ mx_resp_len, noisy, verbose);
+}