| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include "sg_include.h" |
| #include "sg_lib.h" |
| #include "sg_cmds.h" |
| |
| /* A utility program for the Linux OS SCSI generic ("sg") device driver. |
| * Copyright (C) 2003-2005 D. Gilbert |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| |
| This program issues the SCSI SEND DIAGNOSTIC command and in one case |
| the SCSI RECEIVE DIAGNOSTIC command to list supported diagnostic pages. |
| */ |
| |
| static char * version_str = "0.24 20050323"; |
| |
| #define ME "sg_senddiag: " |
| |
| #define MX_ALLOC_LEN (1024 * 4) |
| #define EBUFF_SZ 256 |
| |
| |
| /* Return of 0 -> success, SG_LIB_CAT_INVALID_OP -> Send diagnostic not |
| * supported, SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, -1 -> other |
| * failure */ |
| static int do_senddiag(int sg_fd, int sf_code, int pf_bit, int sf_bit, |
| int devofl_bit, int unitofl_bit, void * outgoing_pg, |
| int outgoing_len, int noisy, int verbose) |
| { |
| int long_duration = 0; |
| |
| if ((0 == sf_bit) && ((5 == sf_code) || (6 == sf_code))) |
| long_duration = 1; /* foreground self tests */ |
| return sg_ll_send_diag(sg_fd, sf_code, pf_bit, sf_bit, devofl_bit, |
| unitofl_bit, long_duration, outgoing_pg, |
| outgoing_len, noisy, verbose); |
| } |
| |
| /* Get expected extended self-test time from mode page 0xa (for '-e' option) */ |
| static int do_modes_0a(int sg_fd, void * resp, int mx_resp_len, int noisy, |
| int mode6, int verbose) |
| { |
| int res; |
| |
| if (mode6) |
| res = sg_ll_mode_sense6(sg_fd, 1 /* dbd */, 0 /* pc */, 0xa /* page */, |
| 0, resp, mx_resp_len, noisy, verbose); |
| else |
| res = sg_ll_mode_sense10(sg_fd, 0 /* llbaa */, 1 /* dbd */, 0, 0xa, 0, |
| resp, mx_resp_len, noisy, verbose); |
| if (SG_LIB_CAT_INVALID_OP == res) |
| fprintf(stderr, "Mode sense (%s) command not supported\n", |
| (mode6 ? "6" : "10")); |
| else if (SG_LIB_CAT_ILLEGAL_REQ == res) |
| fprintf(stderr, "bad field in Mode sense (%s) command\n", |
| (mode6 ? "6" : "10")); |
| return res; |
| } |
| |
| /* Read hex numbers from command line (comma separated list) or from */ |
| /* stdin (one per line, comma separated list or space separated list). */ |
| /* Returns 0 if ok, or 1 if error. */ |
| static int build_diag_page(const char * inp, unsigned char * mp_arr, |
| int * mp_arr_len, int max_arr_len) |
| { |
| int in_len, k, j, m; |
| unsigned int h; |
| const char * lcp; |
| char * cp; |
| |
| if ((NULL == inp) || (NULL == mp_arr) || |
| (NULL == mp_arr_len)) |
| return 1; |
| lcp = inp; |
| in_len = strlen(inp); |
| if (0 == in_len) |
| *mp_arr_len = 0; |
| if ('-' == inp[0]) { /* read from stdin */ |
| char line[512]; |
| int off = 0; |
| |
| for (j = 0; j < 512; ++j) { |
| if (NULL == fgets(line, sizeof(line), stdin)) |
| break; |
| in_len = strlen(line); |
| if (in_len > 0) { |
| if ('\n' == line[in_len - 1]) { |
| --in_len; |
| line[in_len] = '\0'; |
| } |
| } |
| if (0 == in_len) |
| continue; |
| lcp = line; |
| m = strspn(lcp, " \t"); |
| if (m == in_len) |
| continue; |
| lcp += m; |
| in_len -= m; |
| if ('#' == *lcp) |
| continue; |
| k = strspn(lcp, "0123456789aAbBcCdDeEfF ,\t"); |
| if ((k < in_len) && ('#' != lcp[k])) { |
| fprintf(stderr, "build_diag_page: syntax error at " |
| "line %d, pos %d\n", j + 1, m + k + 1); |
| return 1; |
| } |
| for (k = 0; k < 1024; ++k) { |
| if (1 == sscanf(lcp, "%x", &h)) { |
| if (h > 0xff) { |
| fprintf(stderr, "build_diag_page: hex number " |
| "larger than 0xff in line %d, pos %d\n", |
| j + 1, (int)(lcp - line + 1)); |
| return 1; |
| } |
| if ((off + k) >= max_arr_len) { |
| fprintf(stderr, "build_diag_page: array length " |
| "exceeded\n"); |
| return 1; |
| } |
| mp_arr[off + k] = h; |
| lcp = strpbrk(lcp, " ,\t"); |
| if (NULL == lcp) |
| break; |
| lcp += strspn(lcp, " ,\t"); |
| if ('\0' == *lcp) |
| break; |
| } else { |
| if ('#' == *lcp) { |
| --k; |
| break; |
| } |
| fprintf(stderr, "build_diag_page: error in " |
| "line %d, at pos %d\n", j + 1, |
| (int)(lcp - line + 1)); |
| return 1; |
| } |
| } |
| off += (k + 1); |
| } |
| *mp_arr_len = off; |
| } else { /* hex string on command line */ |
| k = strspn(inp, "0123456789aAbBcCdDeEfF,"); |
| if (in_len != k) { |
| fprintf(stderr, "build_diag_page: error at pos %d\n", k + 1); |
| return 1; |
| } |
| for (k = 0; k < max_arr_len; ++k) { |
| if (1 == sscanf(lcp, "%x", &h)) { |
| if (h > 0xff) { |
| fprintf(stderr, "build_diag_page: hex number larger " |
| "than 0xff at pos %d\n", (int)(lcp - inp + 1)); |
| return 1; |
| } |
| mp_arr[k] = h; |
| cp = strchr(lcp, ','); |
| if (NULL == cp) |
| break; |
| lcp = cp + 1; |
| } else { |
| fprintf(stderr, "build_diag_page: error at pos %d\n", |
| (int)(lcp - inp + 1)); |
| return 1; |
| } |
| } |
| *mp_arr_len = k + 1; |
| if (k == max_arr_len) { |
| fprintf(stderr, "build_diag_page: array length exceeded\n"); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| |
| struct page_code_desc { |
| int page_code; |
| const char * desc; |
| }; |
| static struct page_code_desc pc_desc_arr[] = { |
| {0x0, "Supported diagnostic pages"}, |
| {0x1, "Configuration (SES)"}, |
| {0x2, "Enclosure status/control (SES)"}, |
| {0x3, "Help text (SES)"}, |
| {0x4, "String In/Out (SES)"}, |
| {0x5, "Threshold In/Out (SES)"}, |
| {0x6, "Array Status/Control (SES, obsolete)"}, |
| {0x7, "Element descriptor (SES)"}, |
| {0x8, "Short enclosure status (SES)"}, |
| {0x9, "Enclosure busy (SES-2)"}, |
| {0xa, "Additional (device) element status (SES-2)"}, |
| {0xb, "Subenclosure help text (SES-2)"}, |
| {0xc, "Subenclosure string In/Out (SES-2)"}, |
| {0xd, "Supported SES diagnostic pages (SES-2)"}, |
| {0xe, "Download microcode diagnostic pages (SES-2)"}, |
| {0xf, "Subenclosure nickname diagnostic pages (SES-2)"}, |
| {0x3f, "Protocol specific SAS (SAS-1)"}, |
| {0x40, "Translate address (direct access)"}, |
| {0x41, "Device status (direct access)"}, |
| }; |
| |
| static const char * find_page_code_desc(int page_num) |
| { |
| int k; |
| int num = sizeof(pc_desc_arr) / sizeof(pc_desc_arr[0]); |
| const struct page_code_desc * pcdp = &pc_desc_arr[0]; |
| |
| for (k = 0; k < num; ++k, ++pcdp) { |
| if (page_num == pcdp->page_code) |
| return pcdp->desc; |
| else if (page_num < pcdp->page_code) |
| return NULL; |
| } |
| return NULL; |
| } |
| |
| static void list_page_codes() |
| { |
| int k; |
| int num = sizeof(pc_desc_arr) / sizeof(pc_desc_arr[0]); |
| const struct page_code_desc * pcdp = &pc_desc_arr[0]; |
| |
| printf("Page_Code Description\n"); |
| for (k = 0; k < num; ++k, ++pcdp) |
| printf(" 0x%02x %s\n", pcdp->page_code, |
| (pcdp->desc ? pcdp->desc : "<unknown>")); |
| } |
| |
| static void usage() |
| { |
| printf("Usage: 'sg_senddiag [-doff] [-e] [-h] [-l] [-pf]" |
| " [-raw=<h>,<h>...]\n" |
| " [-s=<self_test_code>] [-t] [-uoff] [-v] " |
| "[-V]\n" |
| " [<scsi_device>]'\n" |
| " where -doff device online (def: 0, only with '-t')\n" |
| " -e duration of an extended test (from mode page 0xa)\n" |
| " -h output in hex\n" |
| " -l list supported page codes\n" |
| " -pf set PF bit (def: 0)\n" |
| " -raw=<h>,<h>... sequence of bytes to form diag page to " |
| "send\n" |
| " -raw=- read stdin for sequence of bytes to send\n" |
| " -s=<self_test_code> (def: 0)\n" |
| " 1->background short, 2->background extended," |
| " 4->abort test\n" |
| " 5->foreground short, 6->foreground extended\n" |
| " -t default self test\n" |
| " -uoff unit online (def: 0, only with '-t')\n" |
| " -v increase verbosity (print issued SCSI cmds)\n" |
| " -V output version string\n" |
| " -? output this usage message\n"); |
| } |
| |
| |
| int main(int argc, char * argv[]) |
| { |
| int sg_fd, k, num, rsp_len, read_in_len; |
| char * file_name = 0; |
| char ebuff[EBUFF_SZ]; |
| unsigned char rsp_buff[MX_ALLOC_LEN]; |
| int rsp_buff_size = MX_ALLOC_LEN; |
| unsigned int u; |
| int self_test_code = 0; |
| int do_pf = 0; |
| int do_doff = 0; |
| int do_hex = 0; |
| int do_list = 0; |
| int do_def_test = 0; |
| int do_uoff = 0; |
| int do_ext_time = 0; |
| int do_raw = 0; |
| int verbose = 0; |
| int oflags = O_RDWR | O_NONBLOCK; |
| const char * cp; |
| unsigned char read_in[MX_ALLOC_LEN]; |
| int ret = 1; |
| |
| for (k = 1; k < argc; ++k) { |
| if (0 == strncmp("-s=", argv[k], 3)) { |
| num = sscanf(argv[k] + 3, "%x", &u); |
| if ((1 != num) || (u > 7)) { |
| printf("Bad page code after '-s' switch\n"); |
| file_name = 0; |
| break; |
| } |
| self_test_code = u; |
| } |
| else if (0 == strncmp("-raw=", argv[k], 5)) { |
| if (build_diag_page(argv[k] + 5, read_in, &read_in_len, sizeof(read_in))) { |
| printf("Bad sequence after '-raw'\n"); |
| file_name = 0; |
| break; |
| } |
| do_raw = 1; |
| } else if (0 == strcmp("-pf", argv[k])) |
| do_pf = 1; |
| else if (0 == strcmp("-doff", argv[k])) |
| do_doff = 1; |
| else if (0 == strcmp("-h", argv[k])) |
| do_hex = 1; |
| else if (0 == strcmp("-l", argv[k])) |
| do_list = 1; |
| else if (0 == strcmp("-t", argv[k])) |
| do_def_test = 1; |
| else if (0 == strcmp("-uoff", argv[k])) |
| do_uoff = 1; |
| else if (0 == strcmp("-e", argv[k])) |
| do_ext_time = 1; |
| else if (0 == strcmp("-?", argv[k])) { |
| usage(); |
| return 0; |
| } else if (0 == strcmp("-v", argv[k])) |
| ++verbose; |
| else if (0 == strcmp("-V", argv[k])) { |
| printf("Version string: %s\n", version_str); |
| exit(0); |
| } else if (*argv[k] == '-') { |
| printf("Unrecognized switch: %s\n", argv[k]); |
| file_name = 0; |
| break; |
| } else if (0 == file_name) |
| file_name = argv[k]; |
| else { |
| printf("too many arguments\n"); |
| file_name = 0; |
| break; |
| } |
| } |
| if ((do_doff || do_uoff) && (! do_def_test)) { |
| printf("setting -doff or -uoff only useful when -t is set\n"); |
| usage(); |
| return 1; |
| } |
| if ((self_test_code > 0) && do_def_test) { |
| printf("either set -s=<num> or -t (not both)\n"); |
| usage(); |
| return 1; |
| } |
| if (do_raw) { |
| if ((self_test_code > 0) || do_def_test || do_ext_time || do_list) { |
| printf("'--raw=' cannot be used with self tests, '-e' or " |
| "'-l'\n"); |
| usage(); |
| return 1; |
| } |
| if (! do_pf) |
| printf(">>> warning, '-pf' probably should be used with " |
| "'--raw='\n"); |
| } |
| if (0 == file_name) { |
| if (do_list) { |
| list_page_codes(); |
| return 0; |
| } |
| usage(); |
| return 1; |
| } |
| |
| if ((sg_fd = open(file_name, oflags)) < 0) { |
| snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name); |
| perror(ebuff); |
| return 1; |
| } |
| if (do_ext_time) { |
| if (0 == do_modes_0a(sg_fd, rsp_buff, 32, 1, 0, verbose)) { |
| /* Assume mode sense(10) response without block descriptors */ |
| num = (rsp_buff[0] << 8) + rsp_buff[1] - 6; |
| if (num >= 0xc) { |
| int secs; |
| |
| secs = (rsp_buff[18] << 8) + rsp_buff[19]; |
| printf("Expected extended self-test duration=%d seconds " |
| "(%.2f minutes)\n", secs, secs / 60.0); |
| } else |
| printf("Extended self-test duration not available\n"); |
| } else { |
| printf("Extended self-test duration (mode page 0xa) failed\n"); |
| goto err_out; |
| } |
| } else if (do_list) { |
| memset(rsp_buff, 0, sizeof(rsp_buff)); |
| if (0 == do_senddiag(sg_fd, 0, do_pf, 0, 0, 0, rsp_buff, 4, 1, |
| verbose)) { |
| if (0 == sg_ll_receive_diag(sg_fd, 0, 0, rsp_buff, |
| rsp_buff_size, 1, verbose)) { |
| printf("Supported diagnostic pages response:\n"); |
| rsp_len = (rsp_buff[2] << 8) + rsp_buff[3] + 4; |
| if (do_hex) |
| dStrHex((const char *)rsp_buff, rsp_len, 1); |
| else { |
| for (k = 0; k < (rsp_len - 4); ++k) { |
| cp = find_page_code_desc(rsp_buff[k + 4]); |
| printf(" %s\n", (cp ? cp : "<unknown>")); |
| } |
| } |
| } |
| } |
| } else if (do_raw) { |
| if (do_senddiag(sg_fd, 0, do_pf, 0, 0, 0, read_in, read_in_len, 1, |
| verbose)) |
| goto err_out; |
| } else if (0 == do_senddiag(sg_fd, self_test_code, do_pf, do_def_test, |
| do_doff, do_uoff, NULL, 0, 1, verbose)) { |
| if ((5 == self_test_code) || (6 == self_test_code)) |
| printf("Foreground self test returned GOOD status\n"); |
| else if (do_def_test && (! do_doff) && (! do_uoff)) |
| printf("Default self test returned GOOD status\n"); |
| } else |
| goto err_out; |
| ret = 0; |
| |
| err_out: |
| close(sg_fd); |
| return ret; |
| } |