/* * 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 #include #include #include #include #include #include #include #include #include #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; }