diff --git a/sm5/CHANGELOG b/sm5/CHANGELOG index 64056086829551c033adef451fdb039519e26330..888f47e0d88e746a20692accb00719bd69ae11e5 100644 --- a/sm5/CHANGELOG +++ b/sm5/CHANGELOG @@ -1,6 +1,6 @@ CHANGELOG for smartmontools -$Id: CHANGELOG,v 1.626 2007/07/23 15:33:13 ballen4705 Exp $ +$Id: CHANGELOG,v 1.627 2007/07/26 20:58:50 chrfranke Exp $ The most recent version of this file is: http://smartmontools.cvs.sourceforge.net/smartmontools/sm5/CHANGELOG?view=markup @@ -33,6 +33,9 @@ NOTES FOR FUTURE RELEASES: see TODO file. <DEVELOPERS: ADDITIONS TO THE CHANGE LOG GO JUST BELOW HERE, PLEASE> + [CF] smartctl: Added ability to parse '-r ataioctl,2' output from + stdin ('-') and simulate the ATA commands for testing purposes. + [BA] SMART Attributes: added 187, 189, more accurate name for 190. [CF] Windows: Added drive letters 'X:' as alternate disk device names. diff --git a/sm5/atacmds.cpp b/sm5/atacmds.cpp index c3e3241779ec95849fd349e708157f9a345b83a6..77129b6722ed3ac6fe257109b91ed73f5a8993f3 100644 --- a/sm5/atacmds.cpp +++ b/sm5/atacmds.cpp @@ -36,7 +36,7 @@ #include "extern.h" #include "utility.h" -const char *atacmds_c_cvsid="$Id: atacmds.cpp,v 1.187 2007/07/23 15:33:13 ballen4705 Exp $" +const char *atacmds_c_cvsid="$Id: atacmds.cpp,v 1.188 2007/07/26 20:58:50 chrfranke Exp $" ATACMDS_H_CVSID CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID SCSIATA_H_CVSID UTILITY_H_CVSID; // to hold onto exit code for atexit routine @@ -563,6 +563,8 @@ static void prettyprint(const unsigned char *p, const char *name){ pout("===== [%s] DATA END (512 Bytes) =====\n\n", name); } +static int parsedev_command_interface(int fd, smart_command_set command, int select, char * data); + // This function provides the pretty-print reporting for SMART // commands: it implements the various -r "reporting" options for ATA // ioctls. @@ -636,6 +638,9 @@ int smartcommandhandler(int device, smart_command_set command, int select, char case CONTROLLER_HPT: retval=highpoint_command_interface(device, command, select, data); break; + case CONTROLLER_PARSEDEV: + retval=parsedev_command_interface(device, command, select, data); + break; default: retval=ata_command_interface(device, command, select, data); } @@ -2211,3 +2216,266 @@ int ataSetSCTTempInterval(int device, unsigned interval, bool persistent) return 0; } + +///////////////////////////////////////////////////////////////////////////// +// Pseudo-device to parse "smartctl -r ataioctl,2 ..." output and simulate +// an ATA device with same behaviour + +// Table of parsed commands, return value, data +struct parsed_ata_command +{ + smart_command_set command; + int select; + int retval, errval; + char * data; +}; + +const int max_num_parsed_commands = 32; +static parsed_ata_command parsed_command_table[max_num_parsed_commands]; +static int num_parsed_commands; +static int next_replay_command; +static bool replay_out_of_sync; + + +static const char * nextline(const char * s, int & lineno) +{ + for (s += strcspn(s, "\r\n"); *s == '\r' || *s == '\n'; s++) { + if (*s == '\r' && s[1] == '\n') + s++; + lineno++; + } + return s; +} + +static int name2command(const char * s) +{ + for (int i = 0; i < (int)(sizeof(commandstrings)/sizeof(commandstrings[0])); i++) { + if (!strcmp(s, commandstrings[i])) + return i; + } + return -1; +} + +static bool matchcpy(char * dest, size_t size, const char * src, const regmatch_t & srcmatch) +{ + if (srcmatch.rm_so < 0) + return false; + size_t n = srcmatch.rm_eo - srcmatch.rm_so; + if (n >= size) + n = size-1; + memcpy(dest, src + srcmatch.rm_so, n); + dest[n] = 0; + return true; +} + +static inline int matchtoi(const char * src, const regmatch_t & srcmatch, int defval) +{ + if (srcmatch.rm_so < 0) + return defval; + return atoi(src + srcmatch.rm_so); +} + + +// Parse stdin and build command table +int parsedev_open(const char * pathname) +{ + if (strcmp(pathname, "-")) { + errno = EINVAL; return -1; + } + pathname = "<stdin>"; + // Fill buffer + char buffer[64*1024]; + int size = 0; + while (size < (int)sizeof(buffer)) { + int nr = fread(buffer, 1, sizeof(buffer), stdin); + if (nr <= 0) + break; + size += nr; + } + if (size <= 0) { + pout("%s: Unexpected EOF\n", pathname); + errno = ENOENT; return -1; + } + if (size >= (int)sizeof(buffer)) { + pout("%s: Buffer overflow\n", pathname); + errno = EIO; return -1; + } + buffer[size] = 0; + + // Regex to match output from "-r ataioctl,2" + static const char pattern[] = "^" + "(" // (1 + "REPORT-IOCTL: DeviceFD=[0-9]+ Command=([A-Z ]*[A-Z])" // (2) + "(" // (3 + "( InputParameter=([0-9]+))?" // (4 (5)) + "|" + "( returned (-?[0-9]+)( errno=([0-9]+)[^\r\n]*)?)" // (6 (7) (8 (9))) + ")" // ) + "[\r\n]" // EOL match necessary to match optional parts above + "|" + "===== \\[([A-Z ]*[A-Z])\\] DATA START " // (10) + ")"; // ) + + // Compile regex + regex_t rex; + if (compileregex(&rex, pattern, REG_EXTENDED)) { + errno = EIO; return -1; + } + + // Parse buffer + const char * errmsg = 0; + int i = -1, state = 0, lineno = 1; + for (const char * line = buffer; *line; line = nextline(line, lineno)) { + // Match line + if (!(line[0] == 'R' || line[0] == '=')) + continue; + const int nmatch = 1+10; + regmatch_t match[nmatch]; + if (regexec(&rex, line, nmatch, match, 0)) + continue; + + char cmdname[40]; + if (matchcpy(cmdname, sizeof(cmdname), line, match[2])) { // "REPORT-IOCTL:... Command=%s ..." + int nc = name2command(cmdname); + if (nc < 0) { + errmsg = "Unknown ATA command name"; break; + } + if (match[7].rm_so < 0) { // "returned %d" + // Start of command + if (!(state == 0 || state == 2)) { + errmsg = "Missing REPORT-IOCTL result"; break; + } + if (++i >= max_num_parsed_commands) { + errmsg = "Too many ATA commands"; break; + } + parsed_command_table[i].command = (smart_command_set)nc; + parsed_command_table[i].select = matchtoi(line, match[5], 0); // "InputParameter=%d" + state = 1; + } + else { + // End of command + if (!(state == 1 && (int)parsed_command_table[i].command == nc)) { + errmsg = "Missing REPORT-IOCTL start"; break; + } + parsed_command_table[i].retval = matchtoi(line, match[7], -1); // "returned %d" + parsed_command_table[i].errval = matchtoi(line, match[9], 0); // "errno=%d" + state = 2; + } + } + else if (matchcpy(cmdname, sizeof(cmdname), line, match[10])) { // "===== [%s] DATA START " + // Start of sector hexdump + int nc = name2command(cmdname); + if (!(state == (nc == WRITE_LOG ? 1 : 2) && (int)parsed_command_table[i].command == nc)) { + errmsg = "Unexpected DATA START"; break; + } + line = nextline(line, lineno); + char * data = (char *)malloc(512); + unsigned j; + for (j = 0; j < 32; j++) { + unsigned b[16]; + unsigned u1, u2; int n1 = -1; + if (!(sscanf(line, "%3u-%3u: " + "%2x %2x %2x %2x %2x %2x %2x %2x " + "%2x %2x %2x %2x %2x %2x %2x %2x%n", + &u1, &u2, + b+ 0, b+ 1, b+ 2, b+ 3, b+ 4, b+ 5, b+ 6, b+ 7, + b+ 8, b+ 9, b+10, b+11, b+12, b+13, b+14, b+15, &n1) == 18 + && n1 >= 56 && u1 == j*16 && u2 == j*16+15)) + break; + for (unsigned k = 0; k < 16; k++) + data[j*16+k] = b[k]; + line = nextline(line, lineno); + } + if (j < 32) { + free(data); + errmsg = "Incomplete sector hex dump"; break; + } + parsed_command_table[i].data = data; + if (nc != WRITE_LOG) + state = 0; + } + } + + if (!(state == 0 || state == 2)) + errmsg = "Missing REPORT-IOCTL result"; + + if (!errmsg && i < 0) + errmsg = "No information found"; + + num_parsed_commands = i+1; + next_replay_command = 0; + replay_out_of_sync = false; + + if (errmsg) { + pout("%s(%d): Syntax error: %s\n", pathname, lineno, errmsg); + errno = EIO; + parsedev_close(0); + return -1; + } + return 0; +} + +// Report warnings and free command table +void parsedev_close(int /*fd*/) +{ + if (replay_out_of_sync) + pout("REPLAY-IOCTL: Warning: commands replayed out of sync\n"); + else if (next_replay_command != 0) + pout("REPLAY-IOCTL: Warning: %d command(s) not replayed\n", num_parsed_commands-next_replay_command); + + for (int i = 0; i < num_parsed_commands; i++) { + if (parsed_command_table[i].data) { + free(parsed_command_table[i].data); parsed_command_table[i].data = 0; + } + } + num_parsed_commands = 0; +} + +// Simulate ATA command from command table +static int parsedev_command_interface(int /*fd*/, smart_command_set command, int select, char * data) +{ + // Find command, try round-robin of out of sync + int i = next_replay_command; + for (int j = 0; ; j++) { + if (j >= num_parsed_commands) { + pout("REPLAY-IOCTL: Warning: Command not found\n"); + errno = ENOSYS; + return -1; + } + if (parsed_command_table[i].command == command && parsed_command_table[i].select == select) + break; + if (!replay_out_of_sync) { + replay_out_of_sync = true; + pout("REPLAY-IOCTL: Warning: Command #%d is out of sync\n", i+1); + } + if (++i >= num_parsed_commands) + i = 0; + } + next_replay_command = i; + if (++next_replay_command >= num_parsed_commands) + next_replay_command = 0; + + // Return command data + switch (command) { + case IDENTIFY: + case PIDENTIFY: + case READ_VALUES: + case READ_THRESHOLDS: + case READ_LOG: + if (parsed_command_table[i].data) + memcpy(data, parsed_command_table[i].data, 512); + break; + case WRITE_LOG: + if (!(parsed_command_table[i].data && !memcmp(data, parsed_command_table[i].data, 512))) + pout("REPLAY-IOCTL: Warning: WRITE LOG data does not match\n"); + break; + case CHECK_POWER_MODE: + data[0] = (char)0xff; + default: + break; + } + + if (parsed_command_table[i].errval) + errno = parsed_command_table[i].errval; + return parsed_command_table[i].retval; +} diff --git a/sm5/atacmds.h b/sm5/atacmds.h index 2ce48fed1034125d8d5860b0f87be96704e84f53..5f69c9c4b82e5519eb7fe25b17d6c939c75e36a9 100644 --- a/sm5/atacmds.h +++ b/sm5/atacmds.h @@ -25,7 +25,7 @@ #ifndef ATACMDS_H_ #define ATACMDS_H_ -#define ATACMDS_H_CVSID "$Id: atacmds.h,v 1.88 2007/02/27 09:40:02 chrfranke Exp $\n" +#define ATACMDS_H_CVSID "$Id: atacmds.h,v 1.89 2007/07/26 20:58:50 chrfranke Exp $\n" // Macro to check expected size of struct at compile time using a // dummy typedef. On size mismatch, compiler reports a negative array @@ -629,6 +629,11 @@ int ata_command_interface(int device, smart_command_set command, int select, cha int escalade_command_interface(int fd, int escalade_port, int escalade_type, smart_command_set command, int select, char *data); int marvell_command_interface(int device, smart_command_set command, int select, char *data); int highpoint_command_interface(int device, smart_command_set command, int select, char *data); + +// "smartctl -r ataioctl,2 ..." output parser pseudo-device +int parsedev_open(const char * name); +void parsedev_close(int fd); + // Optional functions of os_*.c #ifdef HAVE_ATA_IDENTIFY_IS_CACHED // Return true if OS caches the ATA identify sector diff --git a/sm5/smartctl.8.in b/sm5/smartctl.8.in index 9ac278111ae47282c9689bedc4adeec7b19040cc..40e64d3f19f7424a27da3045e3a0e3e0c0308830 100644 --- a/sm5/smartctl.8.in +++ b/sm5/smartctl.8.in @@ -1,7 +1,7 @@ .ig Copyright (C) 2002-7 Bruce Allen <smartmontools-support@lists.sourceforge.net> - $Id: smartctl.8.in,v 1.100 2007/07/22 19:38:44 chrfranke Exp $ + $Id: smartctl.8.in,v 1.101 2007/07/26 20:58:50 chrfranke Exp $ 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 @@ -115,6 +115,10 @@ See "WINDOWS NT4/2000/XP/2003/Vista" above. .IP \fBOS/2,eComStation\fP: 9 Use the form \fB"/dev/hd[a\-z]"\fP for IDE/ATA devices. .PP +if \'\-\' is specified as the device path, \fBsmartctl\fP reads and +interprets it's own debug output from standard input. +See \'\-r ataioctl\' below for details. +.PP Based on the device path, \fBsmartctl\fP will guess the device type (ATA or SCSI). If necessary, the \'\-d\' option can be used to over\-ride this guess @@ -403,6 +407,13 @@ the integer with no spaces. For example, The default level is 1, so \'\-r ataioctl,1\' and \'\-r ataioctl\' are equivalent. +For testing purposes, the output of \'\-r ataioctl,2\' can later be parsed +by \fBsmartctl\fP itself if \'\-\' is used as device path argument. +The ATA command input parameters, sector data and return values are +reconstructed from the debug report read from stdin. +Then \fBsmartctl\fP internally simulates an ATA device with the same +behaviour. This is does not work for SCSI devices yet. + .TP .B \-n POWERMODE, \-\-nocheck=POWERMODE Specifieds if \fBsmartctl\fP should exit before performing any checks @@ -1488,7 +1499,7 @@ these documents may be found in the References section of the .SH CVS ID OF THIS PAGE: -$Id: smartctl.8.in,v 1.100 2007/07/22 19:38:44 chrfranke Exp $ +$Id: smartctl.8.in,v 1.101 2007/07/26 20:58:50 chrfranke Exp $ .\" Local Variables: .\" mode: nroff .\" End: diff --git a/sm5/smartctl.cpp b/sm5/smartctl.cpp index 2fdbec9059deacc3247bbf86e774d42f746898ce..d29d26c3ec2e89c4f5f2f0ab2f54d2fbeef56e46 100644 --- a/sm5/smartctl.cpp +++ b/sm5/smartctl.cpp @@ -50,7 +50,7 @@ extern const char *os_solaris_ata_s_cvsid; #endif extern const char *atacmdnames_c_cvsid, *atacmds_c_cvsid, *ataprint_c_cvsid, *knowndrives_c_cvsid, *os_XXXX_c_cvsid, *scsicmds_c_cvsid, *scsiprint_c_cvsid, *utility_c_cvsid; -const char* smartctl_c_cvsid="$Id: smartctl.cpp,v 1.165 2007/07/21 20:59:41 chrfranke Exp $" +const char* smartctl_c_cvsid="$Id: smartctl.cpp,v 1.166 2007/07/26 20:58:50 chrfranke Exp $" ATACMDS_H_CVSID ATAPRINT_H_CVSID CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID KNOWNDRIVES_H_CVSID SCSICMDS_H_CVSID SCSIPRINT_H_CVSID SMARTCTL_H_CVSID UTILITY_H_CVSID; // This is a block containing all the "control variables". We declare @@ -960,6 +960,16 @@ int main (int argc, char **argv){ device = argv[argc-1]; + // Device name "-": Parse "smartctl -r ataioctl,2 ..." output + if (!strcmp(device,"-")) { + if (con->controller_type != CONTROLLER_UNKNOWN) { + pout("Smartctl: -d option is not allowed in conjunction with device name \"-\".\n"); + UsageSummary(); + return FAILCMD; + } + con->controller_type = CONTROLLER_PARSEDEV; + } + // If use has specified 3ware controller, determine which interface if (con->controller_type == CONTROLLER_3WARE) { con->controller_type=guess_device_type(device); @@ -1002,7 +1012,10 @@ int main (int argc, char **argv){ // present (e.g. with st). Opening is retried O_RDONLY if read-only // media prevents opening O_RDWR (it cannot happen for scsi generic // devices, but it can for the others). - fd = deviceopen(device, mode); + if (con->controller_type != CONTROLLER_PARSEDEV) + fd = deviceopen(device, mode); + else + fd = parsedev_open(device); if (fd<0) { char errmsg[256]; snprintf(errmsg,256,"Smartctl open device: %s failed",argv[argc-1]); @@ -1034,5 +1047,10 @@ int main (int argc, char **argv){ break; } + if (con->controller_type != CONTROLLER_PARSEDEV) + deviceclose(fd); + else + parsedev_close(fd); + return retval; } diff --git a/sm5/utility.h b/sm5/utility.h index 93b3a8fc0b72242fb1bde917f20d673647db4776..e6cf2791b8b63cd119018639fbdde9125db13819 100644 --- a/sm5/utility.h +++ b/sm5/utility.h @@ -25,7 +25,7 @@ #ifndef UTILITY_H_ #define UTILITY_H_ -#define UTILITY_H_CVSID "$Id: utility.h,v 1.49 2007/02/03 15:14:14 chrfranke Exp $\n" +#define UTILITY_H_CVSID "$Id: utility.h,v 1.50 2007/07/26 20:58:50 chrfranke Exp $\n" #include <time.h> #include <sys/types.h> // for regex.h (according to POSIX) @@ -193,5 +193,6 @@ void MsecToText(unsigned int msec, char *txt); #define CONTROLLER_SAT 0x08 // SATA device behind a SCSI ATA Translation (SAT) layer #define CONTROLLER_HPT 0x09 // SATA drives behind HighPoint Raid controllers #define CONTROLLER_CCISS 0x10 // CCISS controller +#define CONTROLLER_PARSEDEV 0x11 // "smartctl -r ataioctl,2 ..." output parser pseudo-device #endif