/* * Copyright (c) 2004-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 */ #include #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_pr2serr.h" #include "sg_pt.h" /* A utility program for the Linux OS SCSI subsystem. * * * This program issues the SCSI command REQUEST SENSE to the given SCSI device. */ static const char * version_str = "1.40 20220607"; #define MAX_REQS_RESP_LEN 255 #define DEF_REQS_RESP_LEN 252 #define SENSE_BUFF_LEN 96 /* Arbitrary, could be larger */ #define DEF_PT_TIMEOUT 60 /* 60 seconds */ #define REQUEST_SENSE_CMD 0x3 #define REQUEST_SENSE_CMDLEN 6 #define ME "sg_requests: " static struct option long_options[] = { {"desc", no_argument, 0, 'd'}, {"error", no_argument, 0, 'e'}, {"help", no_argument, 0, 'h'}, {"hex", no_argument, 0, 'H'}, {"maxlen", required_argument, 0, 'm'}, {"num", required_argument, 0, 'n'}, {"number", required_argument, 0, 'n'}, {"progress", no_argument, 0, 'p'}, {"raw", no_argument, 0, 'r'}, {"status", no_argument, 0, 's'}, {"time", no_argument, 0, 't'}, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0}, }; static void usage() { pr2serr("Usage: sg_requests [--desc] [--error] [--help] [--hex] " "[--maxlen=LEN]\n" " [--num=NUM] [--number=NUM] [--progress] " "[--raw]\n" " [--status] [--time] [--verbose] " "[--version] DEVICE\n" " where:\n" " --desc|-d set flag for descriptor sense " "format\n" " --error|-e change opcode to 0xff; to measure " "overhead\n" " twice: skip ioctl call\n" " --help|-h print out usage message\n" " --hex|-H output in hexadecimal\n" " --maxlen=LEN|-m LEN max response length (allocation " "length in cdb)\n" " (def: 0 -> 252 bytes)\n" " --num=NUM|-n NUM number of REQUEST SENSE commands " "to send (def: 1)\n" " --number=NUM same action as '--num=NUM'\n" " --progress|-p output a progress indication (percentage) " "if available\n" " --raw|-r output in binary (to stdout)\n" " --status|-s set exit status from parameter data " "(def: only set\n" " exit status from autosense)\n" " --time|-t time the transfer, calculate commands " "per second\n" " --verbose|-v increase verbosity\n" " --version|-V print version string and exit\n\n" "Performs a SCSI REQUEST SENSE command\n" ); } static void dStrRaw(const uint8_t * str, int len) { int k; for (k = 0; k < len; ++k) printf("%c", str[k]); } int main(int argc, char * argv[]) { int c, n, k, progress, rs, sense_cat, act_din_len; int do_error = 0; int err = 0; int num_errs = 0; int num_din_errs = 0; int most_recent_skey = 0; int sg_fd = -1; int res = 0; uint8_t rsBuff[MAX_REQS_RESP_LEN + 1]; bool desc = false; bool do_progress = false; bool do_raw = false; bool do_status = false; bool verbose_given = false; bool version_given = false; bool not_raw_hex; int num_rs = 1; int do_hex = 0; int maxlen = 0; int verbose = 0; const char * device_name = NULL; int ret = 0; struct sg_pt_base * ptvp = NULL; char b[256]; 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; #ifndef SG_LIB_MINGW bool do_time = false; struct timeval start_tm, end_tm; #endif while (1) { int option_index = 0; c = getopt_long(argc, argv, "dehHm:n:prstvV", long_options, &option_index); if (c == -1) break; switch (c) { case 'd': desc = true; break; case 'e': ++do_error; break; case 'h': case '?': usage(); return 0; case 'H': ++do_hex; break; case 'm': maxlen = sg_get_num(optarg); if ((maxlen < 0) || (maxlen > MAX_REQS_RESP_LEN)) { pr2serr("argument to '--maxlen' should be %d or less\n", MAX_REQS_RESP_LEN); return SG_LIB_SYNTAX_ERROR; } break; case 'n': num_rs = sg_get_num(optarg); if (num_rs < 1) { pr2serr("bad argument to '--num'\n"); return SG_LIB_SYNTAX_ERROR; } break; case 'p': do_progress = true; break; case 'r': do_raw = true; break; case 's': do_status = true; break; case 't': #ifndef SG_LIB_MINGW do_time = true; #endif 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(ME "version: %s\n", version_str); return 0; } if (0 == maxlen) maxlen = DEF_REQS_RESP_LEN; 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 (do_raw || do_hex) { not_raw_hex = false; #ifdef SG_LIB_MINGW bool prog_time = do_progress; #else bool prog_time = do_progress || do_time; #endif if (prog_time) { pr2serr("With either --raw or --hex, --progress and --time " "contradict\n"); ret = SG_LIB_CONTRADICT; goto finish; } } else not_raw_hex = true; sg_fd = sg_cmds_open_device(device_name, true /* ro */, verbose); if (sg_fd < 0) { if (not_raw_hex && verbose) pr2serr(ME "open error: %s: %s\n", device_name, safe_strerror(-sg_fd)); ret = sg_convert_errno(-sg_fd); goto finish; } ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose); if ((NULL == ptvp) || ((err = get_scsi_pt_os_err(ptvp)))) { if (not_raw_hex) pr2serr("%s: unable to construct pt object\n", __func__); ret = sg_convert_errno(err ? err : ENOMEM); goto finish; } if (do_error) rs_cdb[0] = 0xff; if (desc) rs_cdb[1] |= 0x1; rs_cdb[4] = maxlen; if (do_progress) { for (k = 0; k < num_rs; ++k) { act_din_len = 0; if (k > 0) sg_sleep_secs(30); set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); memset(rsBuff, 0x0, sizeof(rsBuff)); set_scsi_pt_data_in(ptvp, rsBuff, sizeof(rsBuff)); set_scsi_pt_packet_id(ptvp, k + 1); if (do_error > 1) { ++num_errs; n = 0; } else { if (verbose && (0 == k)) { char bb[128]; pr2serr(" cdb: %s\n", sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, true, sizeof(bb), bb)); } rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); n = sg_cmds_process_resp(ptvp, "Request sense", rs, (0 == k), verbose, &sense_cat); } if (-1 == n) { if (get_scsi_pt_transport_err(ptvp)) ret = SG_LIB_TRANSPORT_ERROR; else ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); goto finish; } else if (-2 == n) { switch (sense_cat) { case SG_LIB_CAT_RECOVERED: case SG_LIB_CAT_NO_SENSE: break; case SG_LIB_CAT_NOT_READY: ++num_errs; if (1 == num_rs) { ret = sense_cat; printf("device not ready\n"); } break; case SG_LIB_CAT_UNIT_ATTENTION: ++num_errs; if (verbose) { pr2serr("Ignoring Unit attention (sense key)\n"); } break; default: ++num_errs; if (1 == num_rs) { ret = sense_cat; sg_get_category_sense_str(sense_cat, sizeof(b), b, verbose); printf("%s\n", b); break; // return k; } break; } } if (n >= 0) act_din_len = n; if (ret) goto finish; if (verbose > 1) { pr2serr("Parameter data in hex\n"); hex2stderr(rsBuff, act_din_len, 1); } progress = -1; sg_get_sense_progress_fld(rsBuff, act_din_len, &progress); if (progress < 0) { ret = res; if (verbose > 1) pr2serr("No progress indication found, iteration %d\n", k + 1); /* N.B. exits first time there isn't a progress indication */ break; } else printf("Progress indication: %d.%02d%% done\n", (progress * 100) / 65536, ((progress * 100) % 65536) / 656); partial_clear_scsi_pt_obj(ptvp); } /* >>>>> end of for(num_rs) loop */ goto finish; } #ifndef SG_LIB_MINGW if (not_raw_hex && do_time) { start_tm.tv_sec = 0; start_tm.tv_usec = 0; gettimeofday(&start_tm, NULL); } #endif rsBuff[0] = '\0'; rsBuff[7] = '\0'; for (k = 0; k < num_rs; ++k) { act_din_len = 0; ret = 0; set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb)); set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); memset(rsBuff, 0x0, sizeof(rsBuff)); set_scsi_pt_data_in(ptvp, rsBuff, sizeof(rsBuff)); set_scsi_pt_packet_id(ptvp, k + 1); if (do_error > 1) { ++num_errs; n = 0; } else { if (verbose && (0 == k)) { char bb[128]; pr2serr(" cdb: %s\n", sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, true, sizeof(bb), bb)); } rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose); n = sg_cmds_process_resp(ptvp, "Request sense", rs, (0 == k), verbose, &sense_cat); } if (-1 == n) { if (get_scsi_pt_transport_err(ptvp)) ret = SG_LIB_TRANSPORT_ERROR; else ret = sg_convert_errno(get_scsi_pt_os_err(ptvp)); goto finish; } else if (-2 == n) { switch (sense_cat) { case SG_LIB_CAT_RECOVERED: case SG_LIB_CAT_NO_SENSE: break; case SG_LIB_CAT_NOT_READY: ++num_errs; if (1 == num_rs) { ret = sense_cat; printf("device not ready\n"); } break; case SG_LIB_CAT_UNIT_ATTENTION: ++num_errs; if (verbose) { pr2serr("Ignoring Unit attention (sense key)\n"); } break; default: ++num_errs; if (1 == num_rs) { ret = sense_cat; sg_get_category_sense_str(sense_cat, sizeof(b), b, verbose); printf("%s\n", b); break; // return k; } break; } } if (n >= 0) act_din_len = n; if (act_din_len > 7) { struct sg_scsi_sense_hdr ssh; if (sg_scsi_normalize_sense(rsBuff, act_din_len, &ssh)) { if (ssh.sense_key > 0) { ++num_din_errs; most_recent_skey = ssh.sense_key; } if (not_raw_hex && ((1 == num_rs) || verbose)) { char bb[144]; sg_get_sense_str(NULL, rsBuff, act_din_len, false, sizeof(bb), bb); pr2serr("data-in decoded as sense:\n%s\n", bb); } } } partial_clear_scsi_pt_obj(ptvp); if (ret) goto finish; if (act_din_len > 0) { if (do_raw) dStrRaw(rsBuff, act_din_len); else if (do_hex) hex2stdout(rsBuff, act_din_len, 1); } } /* <<<<< end of for(num_rs) loop */ if ((0 == ret) && do_status) { ret = sg_err_category_sense(rsBuff, act_din_len); if (SG_LIB_CAT_NO_SENSE == ret) { struct sg_scsi_sense_hdr ssh; if (sg_scsi_normalize_sense(rsBuff, act_din_len, &ssh)) { if ((0 == ssh.asc) && (0 == ssh.ascq)) ret = 0; } } } #ifndef SG_LIB_MINGW if (not_raw_hex && do_time && (start_tm.tv_sec || start_tm.tv_usec)) { struct timeval res_tm; double den, num; gettimeofday(&end_tm, NULL); res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec; res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec; if (res_tm.tv_usec < 0) { --res_tm.tv_sec; res_tm.tv_usec += 1000000; } den = res_tm.tv_sec; den += (0.000001 * res_tm.tv_usec); num = (double)num_rs; printf("time to perform commands was %d.%06d secs", (int)res_tm.tv_sec, (int)res_tm.tv_usec); if (den > 0.00001) printf("; %.2f operations/sec\n", num / den); else printf("\n"); } #endif finish: if (not_raw_hex) { if (num_errs > 0) printf("Number of command errors detected: %d\n", num_errs); if (num_din_errs > 0) printf("Number of data-in errors detected: %d, most recent " "sense_key=%d\n", num_din_errs, most_recent_skey); } if (sg_fd >= 0) { res = sg_cmds_close_device(sg_fd); if (res < 0) { if (not_raw_hex) pr2serr("close error: %s\n", safe_strerror(-res)); if (0 == ret) ret = sg_convert_errno(-res); } } if (not_raw_hex && (0 == verbose)) { if (! sg_if_can2stderr("sg_requests failed: ", ret)) pr2serr("Some error occurred, try again with '-v' " "or '-vv' for more information\n"); } return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; }