aboutsummaryrefslogtreecommitdiff
path: root/src/sg_safte.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_safte.c')
-rw-r--r--src/sg_safte.c776
1 files changed, 776 insertions, 0 deletions
diff --git a/src/sg_safte.c b/src/sg_safte.c
new file mode 100644
index 00000000..588d47f8
--- /dev/null
+++ b/src/sg_safte.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2004-2018 Hannes Reinecke and 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
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+ *
+ * This program accesses a processor device which operates according
+ * to the 'SCSI Accessed Fault-Tolerant Enclosures' (SAF-TE) spec.
+ */
+
+static const char * version_str = "0.33 20180628";
+
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+#define EBUFF_SZ 256
+
+#define RB_MODE_DESC 3
+#define RWB_MODE_DATA 2
+#define RWB_MODE_VENDOR 1
+#define RB_DESC_LEN 4
+
+#define SAFTE_CFG_FLAG_DOORLOCK 1
+#define SAFTE_CFG_FLAG_ALARM 2
+#define SAFTE_CFG_FLAG_CELSIUS 3
+
+struct safte_cfg_t {
+ int fans;
+ int psupplies;
+ int slots;
+ int temps;
+ int thermostats;
+ int vendor_specific;
+ int flags;
+};
+
+struct safte_cfg_t safte_cfg;
+
+static unsigned int buf_capacity = 64;
+
+static void
+dStrRaw(const uint8_t * str, int len)
+{
+ int k;
+
+ for (k = 0; k < len; ++k)
+ printf("%c", str[k]);
+}
+
+/* Buffer ID 0x0: Read Enclosure Configuration (mandatory) */
+static int
+read_safte_configuration(int sg_fd, uint8_t *rb_buff,
+ unsigned int rb_len, int verbose)
+{
+ int res;
+
+ if (rb_len < buf_capacity) {
+ pr2serr("SCSI BUFFER size too small (%d/%d bytes)\n", rb_len,
+ buf_capacity);
+ return SG_LIB_CAT_ILLEGAL_REQ;
+ }
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=0 to fetch "
+ "configuration\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 0, 0,
+ rb_buff, rb_len, true, verbose);
+ if (res && res != SG_LIB_CAT_RECOVERED)
+ return res;
+
+ safte_cfg.fans = rb_buff[0];
+ safte_cfg.psupplies = rb_buff[1];
+ safte_cfg.slots = rb_buff[2];
+ safte_cfg.temps = rb_buff[4];
+ if (rb_buff[3])
+ safte_cfg.flags |= SAFTE_CFG_FLAG_DOORLOCK;
+ if (rb_buff[5])
+ safte_cfg.flags |= SAFTE_CFG_FLAG_ALARM;
+ if (rb_buff[6] & 0x80)
+ safte_cfg.flags |= SAFTE_CFG_FLAG_CELSIUS;
+
+ safte_cfg.thermostats = rb_buff[6] & 0x0f;
+ safte_cfg.vendor_specific = rb_buff[63];
+
+ return 0;
+}
+
+static int
+print_safte_configuration(void)
+{
+ printf("Enclosure Configuration:\n");
+ printf("\tNumber of Fans: %d\n", safte_cfg.fans);
+ printf("\tNumber of Power Supplies: %d\n", safte_cfg.psupplies);
+ printf("\tNumber of Device Slots: %d\n", safte_cfg.slots);
+ printf("\tNumber of Temperature Sensors: %d\n", safte_cfg.temps);
+ printf("\tNumber of Thermostats: %d\n", safte_cfg.thermostats);
+ printf("\tVendor unique bytes: %d\n", safte_cfg.vendor_specific);
+
+ return 0;
+}
+
+/* Buffer ID 0x01: Read Enclosure Status (mandatory) */
+static int
+do_safte_encl_status(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res, i, offset;
+ unsigned int rb_len;
+ uint8_t *rb_buff;
+
+ rb_len = safte_cfg.fans + safte_cfg.psupplies + safte_cfg.slots +
+ safte_cfg.temps + 5 + safte_cfg.vendor_specific;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=1 to read "
+ "enclosure status\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 1, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res && res != SG_LIB_CAT_RECOVERED)
+ return res;
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Enclosure Status:\n");
+ offset = 0;
+ for (i = 0; i < safte_cfg.fans; i++) {
+ printf("\tFan %d status: ", i);
+ switch(rb_buff[i]) {
+ case 0:
+ printf("operational\n");
+ break;
+ case 1:
+ printf("malfunctioning\n");
+ break;
+ case 2:
+ printf("not installed\n");
+ break;
+ case 80:
+ printf("not reportable\n");
+ break;
+ default:
+ printf("unknown\n");
+ break;
+ }
+ }
+
+ offset += safte_cfg.fans;
+ for (i = 0; i < safte_cfg.psupplies; i++) {
+ printf("\tPower supply %d status: ", i);
+ switch(rb_buff[i + offset]) {
+ case 0:
+ printf("operational / on\n");
+ break;
+ case 1:
+ printf("operational / off\n");
+ break;
+ case 0x10:
+ printf("malfunctioning / on\n");
+ break;
+ case 0x11:
+ printf("malfunctioning / off\n");
+ break;
+ case 0x20:
+ printf("not present\n");
+ break;
+ case 0x21:
+ printf("present\n");
+ break;
+ case 0x80:
+ printf("not reportable\n");
+ break;
+ default:
+ printf("unknown\n");
+ break;
+ }
+ }
+
+ offset += safte_cfg.psupplies;
+ for (i = 0; i < safte_cfg.slots; i++) {
+ printf("\tDevice Slot %d: SCSI ID %d\n", i, rb_buff[i + offset]);
+ }
+
+ offset += safte_cfg.slots;
+ if (safte_cfg.flags & SAFTE_CFG_FLAG_DOORLOCK) {
+ switch(rb_buff[offset]) {
+ case 0x0:
+ printf("\tDoor lock status: locked\n");
+ break;
+ case 0x01:
+ printf("\tDoor lock status: unlocked\n");
+ break;
+ case 0x80:
+ printf("\tDoor lock status: not reportable\n");
+ break;
+ }
+ } else {
+ printf("\tDoor lock status: not installed\n");
+ }
+
+ offset++;
+ if (!(safte_cfg.flags & SAFTE_CFG_FLAG_ALARM)) {
+ printf("\tSpeaker status: not installed\n");
+ } else {
+ switch(rb_buff[offset]) {
+ case 0x0:
+ printf("\tSpeaker status: off\n");
+ break;
+ case 0x01:
+ printf("\tSpeaker status: on\n");
+ break;
+ }
+ }
+
+ offset++;
+ for (i = 0; i < safte_cfg.temps; i++) {
+ int temp = rb_buff[i + offset];
+ int is_celsius = !!(safte_cfg.flags & SAFTE_CFG_FLAG_CELSIUS);
+
+ if (! is_celsius)
+ temp -= 10;
+
+ printf("\tTemperature sensor %d: %d deg %c\n", i, temp,
+ is_celsius ? 'C' : 'F');
+ }
+
+ offset += safte_cfg.temps;
+ if (safte_cfg.thermostats) {
+ if (rb_buff[offset] & 0x80) {
+ printf("\tEnclosure Temperature alert status: abnormal\n");
+ } else {
+ printf("\tEnclosure Temperature alert status: normal\n");
+ }
+ }
+ return 0;
+}
+
+/* Buffer ID 0x02: Read Usage Statistics (optional) */
+static int
+do_safte_usage_statistics(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res;
+ unsigned int rb_len;
+ uint8_t *rb_buff;
+ unsigned int minutes;
+
+ rb_len = 16 + safte_cfg.vendor_specific;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=2 to read "
+ "usage statistics\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 2, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res) {
+ if (res == SG_LIB_CAT_ILLEGAL_REQ) {
+ printf("Usage Statistics:\n\tNot implemented\n");
+ return 0;
+ }
+ if (res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Usage Statistics:\n");
+ minutes = sg_get_unaligned_be32(rb_buff + 0);
+ printf("\tPower on Minutes: %u\n", minutes);
+ minutes = sg_get_unaligned_be32(rb_buff + 4);
+ printf("\tPower on Cycles: %u\n", minutes);
+
+ free(rb_buff);
+ return 0;
+}
+
+/* Buffer ID 0x03: Read Device Insertions (optional) */
+static int
+do_safte_slot_insertions(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res, i;
+ unsigned int rb_len;
+ uint8_t *rb_buff, slot_status;
+
+ rb_len = safte_cfg.slots * 2;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=3 to read "
+ "device insertions\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 3, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res ) {
+ if (res == SG_LIB_CAT_ILLEGAL_REQ) {
+ printf("Slot insertions:\n\tNot implemented\n");
+ return 0;
+ }
+ if (res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Slot insertions:\n");
+ for (i = 0; i < safte_cfg.slots; i++) {
+ slot_status = sg_get_unaligned_be16(rb_buff + (i * 2));
+ printf("\tSlot %d: %d insertions", i, slot_status);
+ }
+ free(rb_buff);
+ return 0;
+}
+
+/* Buffer ID 0x04: Read Device Slot Status (mandatory) */
+static int
+do_safte_slot_status(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res, i;
+ unsigned int rb_len;
+ uint8_t *rb_buff, slot_status;
+
+ rb_len = safte_cfg.slots * 4;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=4 to read "
+ "device slot status\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 4, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res && res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Slot status:\n");
+ for (i = 0; i < safte_cfg.slots; i++) {
+ slot_status = rb_buff[i * 4 + 3];
+ printf("\tSlot %d: ", i);
+ if (slot_status & 0x7) {
+ if (slot_status & 0x1)
+ printf("inserted ");
+ if (slot_status & 0x2)
+ printf("ready ");
+ if (slot_status & 0x4)
+ printf("activated ");
+ printf("\n");
+ } else {
+ printf("empty\n");
+ }
+ }
+ free(rb_buff);
+ return 0;
+}
+
+/* Buffer ID 0x05: Read Global Flags (optional) */
+static int
+do_safte_global_flags(int sg_fd, int do_hex, int do_raw, int verbose)
+{
+ int res;
+ unsigned int rb_len;
+ uint8_t *rb_buff;
+
+ rb_len = 16;
+ rb_buff = (uint8_t *)malloc(rb_len);
+
+ if (verbose > 1)
+ pr2serr("Use READ BUFFER,mode=vendor_specific,buff_id=5 to read "
+ "global flags\n");
+ res = sg_ll_read_buffer(sg_fd, RWB_MODE_VENDOR, 5, 0,
+ rb_buff, rb_len, false, verbose);
+ if (res ) {
+ if (res == SG_LIB_CAT_ILLEGAL_REQ) {
+ printf("Global Flags:\n\tNot implemented\n");
+ return 0;
+ }
+ if (res != SG_LIB_CAT_RECOVERED) {
+ free(rb_buff);
+ return res;
+ }
+ }
+
+ if (do_raw > 1) {
+ dStrRaw(rb_buff, buf_capacity);
+ return 0;
+ }
+ if (do_hex > 1) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ return 0;
+ }
+ printf("Global Flags:\n");
+ printf("\tAudible Alarm Control: %s\n",
+ rb_buff[0] & 0x1?"on":"off");
+ printf("\tGlobal Failure Indicator: %s\n",
+ rb_buff[0] & 0x2?"on":"off");
+ printf("\tGlobal Warning Indicator: %s\n",
+ rb_buff[0] & 0x4?"on":"off");
+ printf("\tEnclosure Power: %s\n",
+ rb_buff[0] & 0x8?"on":"off");
+ printf("\tCooling Failure: %s\n",
+ rb_buff[0] & 0x10?"yes":"no");
+ printf("\tPower Failure: %s\n",
+ rb_buff[0] & 0x20?"yes":"no");
+ printf("\tDrive Failure: %s\n",
+ rb_buff[0] & 0x40?"yes":"no");
+ printf("\tDrive Warning: %s\n",
+ rb_buff[0] & 0x80?"yes":"no");
+ printf("\tArray Failure: %s\n",
+ rb_buff[1] & 0x1?"yes":"no");
+ printf("\tArray Warning: %s\n",
+ rb_buff[0] & 0x2?"yes":"no");
+ printf("\tEnclosure Lock: %s\n",
+ rb_buff[0] & 0x4?"on":"off");
+ printf("\tEnclosure Identify: %s\n",
+ rb_buff[0] & 0x8?"on":"off");
+
+ free(rb_buff);
+ return 0;
+}
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_safte [--config] [--devstatus] [--encstatus] "
+ "[--flags] [--help]\n"
+ " [--hex] [--insertions] [--raw] [--usage] "
+ "[--verbose]\n"
+ " [--version] DEVICE\n"
+ " where:\n"
+ " --config|-c output enclosure configuration\n"
+ " --devstatus|-d output device slot status\n"
+ " --encstatus|-s output enclosure status\n"
+ " --flags|-f output global flags\n"
+ " --help|-h output command usage message then "
+ "exit\n"
+ " --hex|-H output enclosure config in hex\n"
+ " --insertions|-i output insertion statistics\n"
+ " --raw|-r output enclosure config in binary "
+ "to stdout\n"
+ " --usage|-u output usage statistics\n"
+ " --verbose|-v increase verbosity\n"
+ " --version|-v output version then exit\n\n"
+ "Queries a SAF-TE processor device\n");
+}
+
+static struct option long_options[] = {
+ {"config", 0, 0, 'c'},
+ {"devstatus", 0, 0, 'd'},
+ {"encstatus", 0, 0, 's'},
+ {"flags", 0, 0, 'f'},
+ {"help", 0, 0, 'h'},
+ {"hex", 0, 0, 'H'},
+ {"insertions", 0, 0, 'i'},
+ {"raw", 0, 0, 'r'},
+ {"usage", 0, 0, 'u'},
+ {"verbose", 0, 0, 'v'},
+ {"version", 0, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+int
+main(int argc, char * argv[])
+{
+ bool do_insertions = false;
+ bool no_hex_raw;
+ bool verbose_given = false;
+ bool version_given = false;
+ int c, ret, peri_type;
+ int sg_fd = -1;
+ int res = SG_LIB_CAT_OTHER;
+ const char * device_name = NULL;
+ char ebuff[EBUFF_SZ];
+ uint8_t *rb_buff;
+ bool do_config = false;
+ bool do_status = false;
+ bool do_slots = false;
+ bool do_flags = false;
+ bool do_usage = false;
+ int do_hex = 0;
+ int do_raw = 0;
+ int verbose = 0;
+ const char * cp;
+ char buff[48];
+ char b[80];
+ struct sg_simple_inquiry_resp inq_resp;
+ const char op_name[] = "READ BUFFER";
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "cdfhHirsuvV?", long_options,
+ &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'c':
+ do_config = true;
+ break;
+ case 'd':
+ do_slots = true;
+ break;
+ case 'f':
+ do_flags = true;
+ break;
+ case 'h':
+ case '?':
+ usage();
+ return 0;
+ case 'H':
+ ++do_hex;
+ break;
+ case 'i':
+ do_insertions = true;
+ break;
+ case 'r':
+ ++do_raw;
+ break;
+ case 's':
+ do_status = true;
+ break;
+ case 'u':
+ do_usage = true;
+ break;
+ case 'v':
+ verbose_given = true;
+ ++verbose;
+ break;
+ case 'V':
+ version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == device_name) {
+ device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == device_name) {
+ pr2serr("Missing device name!\n\n");
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (do_raw) {
+ if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
+ perror("sg_set_binary_mode");
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ if ((sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose))
+ < 0) {
+ if (verbose) {
+ snprintf(ebuff, EBUFF_SZ, "sg_safte: error opening file: %s (rw)",
+ device_name);
+ perror(ebuff);
+ }
+ ret = sg_convert_errno(-sg_fd);
+ goto fini;
+ }
+ no_hex_raw = ((0 == do_hex) && (0 == do_raw));
+
+ if (no_hex_raw) {
+ if (0 == sg_simple_inquiry(sg_fd, &inq_resp, true, verbose)) {
+ printf(" %.8s %.16s %.4s\n", inq_resp.vendor,
+ inq_resp.product, inq_resp.revision);
+ peri_type = inq_resp.peripheral_type;
+ cp = sg_get_pdt_str(peri_type, sizeof(buff), buff);
+ if (strlen(cp) > 0)
+ printf(" Peripheral device type: %s\n", cp);
+ else
+ printf(" Peripheral device type: 0x%x\n", peri_type);
+ } else {
+ pr2serr("sg_safte: %s doesn't respond to a SCSI INQUIRY\n",
+ device_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+
+ rb_buff = (uint8_t *)malloc(buf_capacity);
+ if (!rb_buff)
+ goto err_out;
+
+ memset(rb_buff, 0, buf_capacity);
+
+ res = read_safte_configuration(sg_fd, rb_buff, buf_capacity, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ if (1 == do_raw) {
+ dStrRaw(rb_buff, buf_capacity);
+ goto finish;
+ }
+ if (1 == do_hex) {
+ hex2stdout(rb_buff, buf_capacity, 1);
+ goto finish;
+ }
+
+ if (do_config && no_hex_raw)
+ print_safte_configuration();
+
+ if (do_status) {
+ res = do_safte_encl_status(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_usage) {
+ res = do_safte_usage_statistics(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_insertions) {
+ res = do_safte_slot_insertions(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_slots) {
+ res = do_safte_slot_status(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+
+ if (do_flags) {
+ res = do_safte_global_flags(sg_fd, do_hex, do_raw, verbose);
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ goto err_out;
+ }
+ }
+finish:
+ res = 0;
+
+err_out:
+ switch (res) {
+ case 0:
+ case SG_LIB_CAT_RECOVERED:
+ break;
+ default:
+ sg_get_category_sense_str(res, sizeof(b), b, verbose);
+ pr2serr("%s failed: %s\n", op_name, b);
+ break;
+ }
+ ret = res;
+fini:
+ if (sg_fd >= 0) {
+ res = sg_cmds_close_device(sg_fd);
+ if (res < 0) {
+ pr2serr("close error: %s\n", safe_strerror(-res));
+ if (0 == ret)
+ ret = sg_convert_errno(-res);
+ }
+ }
+ if (0 == verbose) {
+ if (! sg_if_can2stderr("sg_safte failed: ", ret))
+ pr2serr("Some error occurred, try again with '-v' "
+ "or '-vv' for more information\n");
+ }
+ return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}