blob: a5f0f2af15789566025ccd2c1943484393a4feb0 [file] [log] [blame]
#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 <sys/stat.h>
#include "sg_include.h"
#include "sg_err.h"
/* A utility program for the Linux OS SCSI generic ("sg") device driver.
* Copyright (C) 2000-2003 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 outputs information provided by a SCSI LOG SENSE command.
*/
static char * version_str = "0.21 20030513";
#define ME "sg_logs: "
/* #define SG_DEBUG */
#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */
#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
#define LOG_SENSE_CMD 0x4d
#define LOG_SENSE_CMDLEN 10
#define MX_ALLOC_LEN (1024 * 17)
#define PG_CODE_ALL 0x00
#define EBUFF_SZ 256
static int do_logs(int sg_fd, int ppc, int sp, int pc, int pg_code,
int paramp, void * resp, int mx_resp_len, int noisy)
{
int res;
unsigned char logsCmdBlk[LOG_SENSE_CMDLEN] =
{LOG_SENSE_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
sg_io_hdr_t io_hdr;
logsCmdBlk[1] = (unsigned char)((ppc ? 2 : 0) | (sp ? 1 : 0));
logsCmdBlk[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
logsCmdBlk[5] = (unsigned char)((paramp >> 8) & 0xff);
logsCmdBlk[6] = (unsigned char)(paramp & 0xff);
if (mx_resp_len > 0xffff) {
printf( ME "mx_resp_len too big\n");
return -1;
}
logsCmdBlk[7] = (unsigned char)((mx_resp_len >> 8) & 0xff);
logsCmdBlk[8] = (unsigned char)(mx_resp_len & 0xff);
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = sizeof(logsCmdBlk);
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = mx_resp_len;
io_hdr.dxferp = resp;
io_hdr.cmdp = logsCmdBlk;
io_hdr.sbp = sense_b;
io_hdr.timeout = DEF_TIMEOUT;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror("SG_IO (log sense) error");
return -1;
}
#if 0
printf("SG_IO ioctl: status=%d, info=%d, sb_len_wr=%d\n",
io_hdr.status, io_hdr.info, io_hdr.sb_len_wr);
#endif
res = sg_err_category3(&io_hdr);
switch (res) {
case SG_ERR_CAT_CLEAN:
case SG_ERR_CAT_RECOVERED:
return 0;
default:
if (noisy) {
char ebuff[EBUFF_SZ];
snprintf(ebuff, EBUFF_SZ, ME "ppc=%d, sp=%d, "
"pc=%d, page_code=%x, paramp=%x\n ", ppc, sp, pc,
pg_code, paramp);
sg_chk_n_print3(ebuff, &io_hdr);
}
return -1;
}
}
static void usage()
{
printf("Usage: 'sg_logs [-a] [-c=<page_control] [-h] [-l] "
"[-p=<page_number>]\n [-p=<page_number>] "
" [-paramp=<parameter_pointer> [-ppc] [-sp]\n"
" [-t] [-V] <sg_device>'\n"
" where -a output all log pages\n"
" -c=<page_control> page control(PC) (default: 1)\n"
" (0 [current threshhold], 1 [current cumulative]\n"
" 2 [default threshhold], 3 [default cumulative])\n"
" -h output in hex\n"
" -l list supported log page names\n"
" -p=<page_code> page code (in hex)\n"
" -paramp=<parameter_pointer> (in hex) (def: 0)\n"
" -ppc set the Parameter Pointer Control (PPC) bit (def: 0)\n"
" -sp set the Saving Parameters (SP) bit (def: 0)\n"
" -t outputs temperature log page (0xd)\n"
" -V output version string\n"
" -? output this usage message\n");
}
static void dStrHex(const char* str, int len, int no_ascii)
{
const char* p = str;
unsigned char c;
char buff[82];
int a = 0;
const int bpstart = 5;
const int cpstart = 60;
int cpos = cpstart;
int bpos = bpstart;
int i, k;
if (len <= 0) return;
memset(buff,' ',80);
buff[80]='\0';
k = sprintf(buff + 1, "%.2x", a);
buff[k + 1] = ' ';
if (bpos >= ((bpstart + (9 * 3))))
bpos++;
for(i = 0; i < len; i++)
{
c = *p++;
bpos += 3;
if (bpos == (bpstart + (9 * 3)))
bpos++;
sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c);
buff[bpos + 2] = ' ';
if (no_ascii)
buff[cpos++] = ' ';
else {
if ((c < ' ') || (c >= 0x7f))
c='.';
buff[cpos++] = c;
}
if (cpos > (cpstart+15))
{
printf("%s\n", buff);
bpos = bpstart;
cpos = cpstart;
a += 16;
memset(buff,' ',80);
k = sprintf(buff + 1, "%.2x", a);
buff[k + 1] = ' ';
}
}
if (cpos > cpstart)
{
printf("%s\n", buff);
}
}
static void show_page_name(int page_no)
{
switch (page_no) {
case 0x0 : printf(" 0x00 Supported log pages\n"); break;
case 0x1 : printf(" 0x01 Buffer over-run/under-run\n"); break;
case 0x2 : printf(" 0x02 Error counters (write)\n"); break;
case 0x3 : printf(" 0x03 Error counters (read)\n"); break;
case 0x4 : printf(" 0x04 Error counters (read reverse)\n"); break;
case 0x5 : printf(" 0x05 Error counters (verify)\n"); break;
case 0x6 : printf(" 0x06 Non-medium errors\n"); break;
case 0x7 : printf(" 0x07 Last n error events\n"); break;
case 0x8 : printf(" 0x08 Format status (sbc2)\n"); break;
case 0xb : printf(" 0x0b Last n deferred errors of "
"asynchronous events\n"); break;
case 0xc : printf(" 0x0c Sequential Access (ssc-2)\n"); break;
case 0xd : printf(" 0x0d Temperature\n"); break;
case 0xe : printf(" 0x0e Start-stop cycle counter\n"); break;
case 0xf : printf(" 0x0f Application client\n"); break;
case 0x10 : printf(" 0x10 Self-test results\n"); break;
case 0x18 : printf(" 0x18 Protocol specific port\n"); break;
case 0x2e : printf(" 0x2e Tape alerts (ssc-2)\n"); break;
case 0x2f : printf(" 0x2f Informational exceptions (SMART)\n"); break;
default: printf(" 0x%.2x\n", page_no); break;
}
}
static void show_buffer_under_overrun_page(unsigned char * resp, int len)
{
int k, j, num, pl, count_basis, cause;
unsigned char * ucp;
unsigned char * xp;
unsigned long long ull;
printf("Buffer over-run/under-run page\n");
num = len - 4;
ucp = &resp[0] + 4;
while (num > 3) {
pl = ucp[3] + 4;
count_basis = (ucp[1] >> 5) & 0x7;
printf(" Count basis: ");
switch (count_basis) {
case 0 : printf("undefined"); break;
case 1 : printf("per command"); break;
case 2 : printf("per failed reconnect"); break;
case 3 : printf("per unit of time"); break;
default: printf("reserved [0x%x]", count_basis); break;
}
cause = (ucp[1] >> 1) & 0xf;
printf(", Cause: ");
switch (cause) {
case 0 : printf("bus busy"); break;
case 1 : printf("transfer rate too slow"); break;
default: printf("reserved [0x%x]", cause); break;
}
printf(", Type: ");
if (ucp[1] & 1)
printf("over-run");
else
printf("under-run");
printf(", count");
k = pl - 4;
xp = ucp + 4;
if (k > sizeof(ull)) {
xp += (k - sizeof(ull));
k = sizeof(ull);
}
ull = 0;
for (j = 0; j < k; ++j) {
if (j > 0)
ull <<= 8;
ull |= xp[j];
}
printf(" = %llu\n", ull);
num -= pl;
ucp += pl;
}
}
static void show_error_counter_page(unsigned char * resp, int len)
{
int k, j, num, pl, pc;
unsigned char * ucp;
unsigned char * xp;
unsigned long long ull;
switch(resp[0]) {
case 2:
printf("Write error counter page\n");
break;
case 3:
printf("Read error counter page\n");
break;
case 4:
printf("Read Reverse error counter page\n");
break;
case 5:
printf("Verify error counter page\n");
break;
default:
printf("expecting error counter page, got page=0x%x\n", resp[0]);
return;
}
num = len - 4;
ucp = &resp[0] + 4;
while (num > 3) {
pc = (ucp[0] << 8) | ucp[1];
pl = ucp[3] + 4;
switch (pc) {
case 0: printf(" Errors corrected without substantion delay"); break;
case 1: printf(" Errors corrected with possible delays"); break;
case 2: printf(" Total operations"); break;
case 3: printf(" Total errors corrected"); break;
case 4: printf(" Total times correction algorithm processed"); break;
case 5: printf(" Total bytes processed"); break;
case 6: printf(" Total uncorrected errors"); break;
default: printf(" Reserved or vendor specific [0x%x]", pc); break;
}
k = pl - 4;
xp = ucp + 4;
if (k > sizeof(ull)) {
xp += (k - sizeof(ull));
k = sizeof(ull);
}
ull = 0;
for (j = 0; j < k; ++j) {
if (j > 0)
ull <<= 8;
ull |= xp[j];
}
printf(" = %llu\n", ull);
num -= pl;
ucp += pl;
}
}
static void show_non_medium_error_page(unsigned char * resp, int len)
{
int k, j, num, pl, pc;
unsigned char * ucp;
unsigned char * xp;
unsigned long long ull;
printf("Non-medium error page\n");
num = len - 4;
ucp = &resp[0] + 4;
while (num > 3) {
pc = (ucp[0] << 8) | ucp[1];
pl = ucp[3] + 4;
switch (pc) {
case 0:
printf(" Non-medium error count"); break;
default:
if (pc <= 0x7fff)
printf(" Reserved [0x%x]", pc);
else
printf(" Vendor specific [0x%x]", pc);
break;
}
k = pl - 4;
xp = ucp + 4;
if (k > sizeof(ull)) {
xp += (k - sizeof(ull));
k = sizeof(ull);
}
ull = 0;
for (j = 0; j < k; ++j) {
if (j > 0)
ull <<= 8;
ull |= xp[j];
}
printf(" = %llu\n", ull);
num -= pl;
ucp += pl;
}
}
const char * self_test_code[] = {
"default", "background short", "background extended", "reserved",
"aborted background", "foreground short", "foreground extended",
"reserved"};
const char * self_test_result[] = {
"completed without error",
"aborted by SEND DIAGNOSTIC",
"aborted other than by SEND DIAGNOSTIC",
"unknown error, unable to complete",
"self test completed with failure in test segment (which one unkown)",
"first segment in self test failed",
"second segment in self test failed",
"another segment in self test failed",
"reserved", "reserved", "reserved", "reserved", "reserved", "reserved",
"reserved",
"self test in progress"};
static void show_self_test_page(unsigned char * resp, int len)
{
int k, num, n, res;
unsigned char * ucp;
unsigned long long ull;
num = len - 4;
if (num < 0x190) {
printf("badly formed self-test results page\n");
return;
}
printf("Self-test results page\n");
for (k = 0, ucp = resp + 4; k < 20; ++k, ucp += 20 ) {
n = (ucp[6] << 8) | ucp[7];
if ((0 == n) && (0 == ucp[4]))
break;
printf(" Parameter code=%d, accumulated power-on hours=%d\n",
(ucp[0] << 8) | ucp[1], n);
printf(" self test code: %s [%d]\n",
self_test_code[(ucp[4] >> 5) & 0x7], (ucp[4] >> 5) & 0x7);
res = ucp[4] & 0xf;
printf(" self test result: %s [%d]\n",
self_test_result[res], res);
if (ucp[5])
printf(" self-test number=%d\n", (int)ucp[5]);
ull = ucp[8]; ull <<= 8; ull |= ucp[9]; ull <<= 8; ull |= ucp[10];
ull <<= 8; ull |= ucp[11]; ull <<= 8; ull |= ucp[12];
ull <<= 8; ull |= ucp[13]; ull <<= 8; ull |= ucp[14];
ull <<= 8; ull |= ucp[14]; ull <<= 8; ull |= ucp[15];
if ((0xffffffffffffffffULL != ull) && (res > 0) && ( res < 0xf))
printf(" address of first error=0x%llx\n", ull);
if (ucp[16] & 0xf)
printf(" sense key=0x%x, asc=0x%x, asq=0x%x\n",
ucp[16] & 0xf, ucp[17], ucp[18]);
}
}
static void show_Temperature_page(unsigned char * resp, int len, int hdr)
{
int k, num, extra, pc;
unsigned char * ucp;
num = len - 4;
ucp = &resp[0] + 4;
if (num < 4) {
printf("badly formed Temperature log page\n");
return;
}
if (hdr)
printf("Temperature log page\n");
for (k = num; k > 0; k -= extra, ucp += extra) {
if (k < 3) {
printf("short Temperature log page\n");
return;
}
extra = ucp[3] + 4;
pc = ((ucp[0] << 8) & 0xff) + ucp[1];
if (0 == pc) {
if (extra > 5) {
if (ucp[5] < 0xff)
printf(" Current temperature= %d C\n", ucp[5]);
else
printf(" Current temperature=<not available>\n");
}
} else if (1 == pc) {
if (extra > 5) {
if (ucp[5] < 0xff)
printf(" Reference temperature= %d C\n", ucp[5]);
else
printf(" Reference temperature=<not available>\n");
}
}else {
printf(" parameter code=0x%x, contents in hex:\n", pc);
dStrHex((const char *)ucp, extra, 1);
}
}
}
static void show_IE_page(unsigned char * resp, int len, int full)
{
int k, num, extra, pc;
unsigned char * ucp;
num = len - 4;
ucp = &resp[0] + 4;
if (num < 4) {
printf("badly formed Informational Exceptions log page\n");
return;
}
if (full)
printf("Informational Exceptions log page\n");
for (k = num; k > 0; k -= extra, ucp += extra) {
if (k < 3) {
printf("short Informational Exceptions log page\n");
return;
}
extra = ucp[3] + 4;
pc = ((ucp[0] << 8) & 0xff) + ucp[1];
if (0 == pc) {
if (extra > 5) {
if (full)
printf(" IE asc=0x%x, ascq=0x%x", ucp[4], ucp[5]);
if (extra > 6) {
if (full)
printf(",");
if (ucp[6] < 0xff)
printf(" Current temperature=%d C", ucp[6]);
else
printf(" Current temperature=<not available>");
}
printf("\n");
}
} else if (full) {
printf(" parameter code=0x%x, contents in hex:\n", pc);
dStrHex((const char *)ucp, extra, 1);
}
}
}
static void show_ascii_page(unsigned char * resp, int len)
{
int k, n, num;
if (len < 0) {
printf("response has bad length\n");
return;
}
num = len - 4;
switch (resp[0]) {
case 0:
printf("Supported pages:\n");
for (k = 0; k < num; ++k)
show_page_name((int)resp[4 + k]);
break;
case 0x1:
show_buffer_under_overrun_page(resp, len);
break;
case 0x2:
case 0x3:
case 0x4:
case 0x5:
show_error_counter_page(resp, len);
break;
case 0x6:
show_non_medium_error_page(resp, len);
break;
case 0xd:
show_Temperature_page(resp, len, 1);
break;
case 0xe:
if (len < 40) {
printf("badly formed start-stop cycle counter page\n");
break;
}
printf("Start-stop cycle counter page\n");
printf(" Date of manufacture, year: %.4s, week: %.2s\n",
&resp[8], &resp[12]);
printf(" Accounting date, year: %.4s, week: %.2s\n",
&resp[18], &resp[22]);
n = (resp[28] << 24) | (resp[29] << 16) | (resp[30] << 8) | resp[31];
printf(" Specified cycle count over device lifetime=%d\n", n);
n = (resp[36] << 24) | (resp[37] << 16) | (resp[38] << 8) | resp[39];
printf(" Accumulated start-stop cycles=%d\n", n);
break;
case 0x10:
show_self_test_page(resp, len);
break;
case 0x2f:
show_IE_page(resp, len, 1);
break;
default:
printf("No ascii information for page=0x%x, here is hex:\n", resp[0]);
dStrHex((const char *)resp, len, 1);
break;
}
}
static int fetchTemperature(int sg_fd, int do_hex, unsigned char * resp,
int max_len)
{
int res = 0;
if (0 == do_logs(sg_fd, 0, 0, 1, 0xd, 0, resp, max_len, 0))
show_Temperature_page(resp, (resp[2] << 8) + resp[3] + 4, 0);
else if (0 == do_logs(sg_fd, 0, 0, 1, 0x2f, 0, resp, max_len, 0))
show_IE_page(resp, (resp[2] << 8) + resp[3] + 4, 0);
else {
printf("Unable to find temperature in either log page (temperature "
"or IE)\n");
res = 1;
}
close(sg_fd);
return res;
}
int main(int argc, char * argv[])
{
int sg_fd, k, num, pg_len;
char * file_name = 0;
char ebuff[EBUFF_SZ];
unsigned char rsp_buff[MX_ALLOC_LEN];
unsigned int u;
int pg_code = 0;
int pc = 1; /* N.B. some disks only give data for current cumulative */
int paramp = 0;
int do_list = 0;
int do_ppc = 0;
int do_sp = 0;
int do_hex = 0;
int do_all = 0;
int do_temp = 0;
int oflags = O_RDWR | O_NONBLOCK;
for (k = 1; k < argc; ++k) {
if (0 == strncmp("-p=", argv[k], 3)) {
num = sscanf(argv[k] + 3, "%x", &u);
if ((1 != num) || (u > 63)) {
printf("Bad page code after '-p' switch\n");
file_name = 0;
break;
}
pg_code = u;
}
else if (0 == strncmp("-c=", argv[k], 3)) {
num = sscanf(argv[k] + 3, "%x", &u);
if ((1 != num) || (u > 3)) {
printf("Bad page control after '-c' switch\n");
file_name = 0;
break;
}
pc = u;
}
else if (0 == strncmp("-paramp=", argv[k], 8)) {
num = sscanf(argv[k] + 8, "%x", &u);
if ((1 != num) || (u > 0xffff)) {
printf("Bad parameter pointer after '-paramp' switch\n");
file_name = 0;
break;
}
paramp = u;
}
else if (0 == strcmp("-l", argv[k]))
do_list = 1;
else if (0 == strcmp("-ppc", argv[k]))
do_ppc = 1;
else if (0 == strcmp("-sp", argv[k]))
do_sp = 1;
else if (0 == strcmp("-a", argv[k]))
do_all = 1;
else if (0 == strcmp("-t", argv[k]))
do_temp = 1;
else if (0 == strcmp("-h", argv[k]))
do_hex = 1;
else if (0 == strcmp("-?", argv[k])) {
file_name = 0;
break;
}
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 (0 == file_name) {
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;
}
/* Just to be safe, check we have a new sg device by trying an ioctl */
if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
printf(ME "%s doesn't seem to be a version 3 sg device\n",
file_name);
close(sg_fd);
return 1;
}
if (do_list || do_all)
pg_code = PG_CODE_ALL;
pg_len = 0;
if (1 == do_temp)
return fetchTemperature(sg_fd, do_hex, rsp_buff, MX_ALLOC_LEN);
if (0 == do_logs(sg_fd, do_ppc, do_sp, pc, pg_code, paramp,
rsp_buff, MX_ALLOC_LEN, 1))
{
pg_len = (rsp_buff[2] << 8) + rsp_buff[3];
if ((pg_len + 4) > MX_ALLOC_LEN) {
printf("Only fetched %d bytes of response, truncate output\n",
MX_ALLOC_LEN);
pg_len = MX_ALLOC_LEN - 4;
}
if (do_hex) {
printf("Returned log page code=0x%x, page len=0x%x\n",
rsp_buff[0], pg_len);
dStrHex((const char *)rsp_buff, pg_len + 4, 1);
}
else
show_ascii_page(rsp_buff, pg_len + 4);
}
if (do_all && (pg_len > 1)) {
int my_len = pg_len - 1;
unsigned char parr[256];
memcpy(parr, rsp_buff + 5, my_len);
for (k = 0; k < my_len; ++k) {
printf("\n");
pg_code = parr[k];
if (0 == do_logs(sg_fd, do_ppc, do_sp, pc, pg_code, paramp,
rsp_buff, MX_ALLOC_LEN, 1))
{
pg_len = (rsp_buff[2] << 8) + rsp_buff[3];
if ((pg_len + 4) > MX_ALLOC_LEN) {
printf("Only fetched %d bytes of response, truncate "
"output\n", MX_ALLOC_LEN);
pg_len = MX_ALLOC_LEN - 4;
}
if (do_hex) {
printf("Returned log page code=0x%x, page len=0x%x\n",
rsp_buff[0], pg_len);
dStrHex((const char *)rsp_buff, pg_len + 4, 1);
}
else
show_ascii_page(rsp_buff, pg_len + 4);
}
}
}
close(sg_fd);
return 0;
}