+
+
+// Read SCT Status
+int ataReadSCTStatus(ata_device * device, ata_sct_status_response * sts)
+{
+ // read SCT status via SMART log 0xe0
+ memset(sts, 0, sizeof(*sts));
+ if (smartcommandhandler(device, READ_LOG, 0xe0, (char *)sts)){
+ pout("Read SCT Status failed: %s\n", device->get_errmsg());
+ return -1;
+ }
+
+ // swap endian order if needed
+ if (isbigendian()){
+ swapx(&sts->format_version);
+ swapx(&sts->sct_version);
+ swapx(&sts->sct_spec);
+ swapx(&sts->ext_status_code);
+ swapx(&sts->action_code);
+ swapx(&sts->function_code);
+ swapx(&sts->over_limit_count);
+ swapx(&sts->under_limit_count);
+ swapx(&sts->smart_status);
+ }
+
+ // Check format version
+ if (!(sts->format_version == 2 || sts->format_version == 3)) {
+ pout("Unknown SCT Status format version %u, should be 2 or 3.\n", sts->format_version);
+ return -1;
+ }
+ return 0;
+}
+
+// Read SCT Temperature History Table
+int ataReadSCTTempHist(ata_device * device, ata_sct_temperature_history_table * tmh,
+ ata_sct_status_response * sts)
+{
+ // Initial SCT status must be provided by caller
+
+ // Do nothing if other SCT command is executing
+ if (sts->ext_status_code == 0xffff) {
+ pout("Another SCT command is executing, abort Read Data Table\n"
+ "(SCT ext_status_code 0x%04x, action_code=%u, function_code=%u)\n",
+ sts->ext_status_code, sts->action_code, sts->function_code);
+ return -1;
+ }
+
+ ata_sct_data_table_command cmd; memset(&cmd, 0, sizeof(cmd));
+ // CAUTION: DO NOT CHANGE THIS VALUE (SOME ACTION CODES MAY ERASE DISK)
+ cmd.action_code = 5; // Data table command
+ cmd.function_code = 1; // Read table
+ cmd.table_id = 2; // Temperature History Table
+
+ // swap endian order if needed
+ if (isbigendian()) {
+ swapx(&cmd.action_code);
+ swapx(&cmd.function_code);
+ swapx(&cmd.table_id);
+ }
+
+ // write command via SMART log page 0xe0
+ if (smartcommandhandler(device, WRITE_LOG, 0xe0, (char *)&cmd)){
+ pout("Write SCT Data Table failed: %s\n", device->get_errmsg());
+ return -1;
+ }
+
+ // read SCT data via SMART log page 0xe1
+ memset(tmh, 0, sizeof(*tmh));
+ if (smartcommandhandler(device, READ_LOG, 0xe1, (char *)tmh)){
+ pout("Read SCT Data Table failed: %s\n", device->get_errmsg());
+ return -1;
+ }
+
+ // re-read and check SCT status
+ if (ataReadSCTStatus(device, sts))
+ return -1;
+
+ if (!(sts->ext_status_code == 0 && sts->action_code == 5 && sts->function_code == 1)) {
+ pout("Unexpected SCT status 0x%04x (action_code=%u, function_code=%u)\n",
+ sts->ext_status_code, sts->action_code, sts->function_code);
+ return -1;
+ }
+
+ // swap endian order if needed
+ if (isbigendian()){
+ swapx(&tmh->format_version);
+ swapx(&tmh->sampling_period);
+ swapx(&tmh->interval);
+ swapx(&tmh->cb_index);
+ swapx(&tmh->cb_size);
+ }
+ return 0;
+}
+
+// Get/Set Write Cache Reordering
+int ataGetSetSCTWriteCacheReordering(ata_device * device, bool enable, bool persistent, bool set)
+{
+ // Check initial status
+ ata_sct_status_response sts;
+ if (ataReadSCTStatus(device, &sts))
+ return -1;
+
+ // Do nothing if other SCT command is executing
+ if (sts.ext_status_code == 0xffff) {
+ pout("Another SCT command is executing, abort Feature Control\n"
+ "(SCT ext_status_code 0x%04x, action_code=%u, function_code=%u)\n",
+ sts.ext_status_code, sts.action_code, sts.function_code);
+ return -1;
+ }
+
+ ata_sct_feature_control_command cmd; memset(&cmd, 0, sizeof(cmd));
+ // CAUTION: DO NOT CHANGE THIS VALUE (SOME ACTION CODES MAY ERASE DISK)
+ cmd.action_code = 4; // Feature Control command
+ cmd.function_code = (set ? 1 : 2); // 1=Set, 2=Get
+ cmd.feature_code = 2; // Enable/Disable Write Cache Reordering
+ cmd.state = (enable ? 1 : 2); // 1 enable, 2 disable
+ cmd.option_flags = (persistent ? 0x01 : 0x00);
+
+ // swap endian order if needed
+ if (isbigendian()) {
+ swapx(&cmd.action_code);
+ swapx(&cmd.function_code);
+ swapx(&cmd.feature_code);
+ swapx(&cmd.state);
+ swapx(&cmd.option_flags);
+ }
+
+ // write command via SMART log page 0xe0
+ // TODO: Debug output
+ ata_cmd_in in;
+ in.in_regs.command = ATA_SMART_CMD;
+ in.in_regs.lba_high = SMART_CYL_HI; in.in_regs.lba_mid = SMART_CYL_LOW;
+ in.in_regs.features = ATA_SMART_WRITE_LOG_SECTOR;
+ in.in_regs.lba_low = 0xe0;
+ in.set_data_out(&cmd, 1);
+
+ if (!set)
+ // Time limit returned in ATA registers
+ in.out_needed.sector_count = in.out_needed.lba_low = true;
+
+ ata_cmd_out out;
+ if (!device->ata_pass_through(in, out)) {
+ pout("Write SCT (%cet) Feature Control Command failed: %s\n",
+ (!set ? 'G' : 'S'), device->get_errmsg());
+ return -1;
+ }
+ int state = out.out_regs.sector_count | (out.out_regs.lba_low << 8);
+
+ // re-read and check SCT status
+ if (ataReadSCTStatus(device, &sts))
+ return -1;
+
+ if (!(sts.ext_status_code == 0 && sts.action_code == 4 && sts.function_code == (set ? 1 : 2))) {
+ pout("Unexpected SCT status 0x%04x (action_code=%u, function_code=%u)\n",
+ sts.ext_status_code, sts.action_code, sts.function_code);
+ return -1;
+ }
+ return state;
+}
+
+
+// Set SCT Temperature Logging Interval
+int ataSetSCTTempInterval(ata_device * device, unsigned interval, bool persistent)
+{
+ // Check initial status
+ ata_sct_status_response sts;
+ if (ataReadSCTStatus(device, &sts))
+ return -1;
+
+ // Do nothing if other SCT command is executing
+ if (sts.ext_status_code == 0xffff) {
+ pout("Another SCT command is executing, abort Feature Control\n"
+ "(SCT ext_status_code 0x%04x, action_code=%u, function_code=%u)\n",
+ sts.ext_status_code, sts.action_code, sts.function_code);
+ return -1;
+ }
+
+ ata_sct_feature_control_command cmd; memset(&cmd, 0, sizeof(cmd));
+ // CAUTION: DO NOT CHANGE THIS VALUE (SOME ACTION CODES MAY ERASE DISK)
+ cmd.action_code = 4; // Feature Control command
+ cmd.function_code = 1; // Set state
+ cmd.feature_code = 3; // Temperature logging interval
+ cmd.state = interval;
+ cmd.option_flags = (persistent ? 0x01 : 0x00);
+
+ // swap endian order if needed
+ if (isbigendian()) {
+ swapx(&cmd.action_code);
+ swapx(&cmd.function_code);
+ swapx(&cmd.feature_code);
+ swapx(&cmd.state);
+ swapx(&cmd.option_flags);
+ }
+
+ // write command via SMART log page 0xe0
+ if (smartcommandhandler(device, WRITE_LOG, 0xe0, (char *)&cmd)){
+ pout("Write SCT Feature Control Command failed: %s\n", device->get_errmsg());
+ return -1;
+ }
+
+ // re-read and check SCT status
+ if (ataReadSCTStatus(device, &sts))
+ return -1;
+
+ if (!(sts.ext_status_code == 0 && sts.action_code == 4 && sts.function_code == 1)) {
+ pout("Unexpected SCT status 0x%04x (action_code=%u, function_code=%u)\n",
+ sts.ext_status_code, sts.action_code, sts.function_code);
+ return -1;
+ }
+ return 0;
+}
+
+// Get/Set SCT Error Recovery Control
+static int ataGetSetSCTErrorRecoveryControltime(ata_device * device, unsigned type,
+ bool set, unsigned short & time_limit)
+{
+ // Check initial status
+ ata_sct_status_response sts;
+ if (ataReadSCTStatus(device, &sts))
+ return -1;
+
+ // Do nothing if other SCT command is executing
+ if (sts.ext_status_code == 0xffff) {
+ pout("Another SCT command is executing, abort Error Recovery Control\n"
+ "(SCT ext_status_code 0x%04x, action_code=%u, function_code=%u)\n",
+ sts.ext_status_code, sts.action_code, sts.function_code);
+ return -1;
+ }
+
+ ata_sct_error_recovery_control_command cmd; memset(&cmd, 0, sizeof(cmd));
+ // CAUTION: DO NOT CHANGE THIS VALUE (SOME ACTION CODES MAY ERASE DISK)
+ cmd.action_code = 3; // Error Recovery Control command
+ cmd.function_code = (set ? 1 : 2); // 1=Set timer, 2=Get timer
+ cmd.selection_code = type; // 1=Read timer, 2=Write timer
+ if (set)
+ cmd.time_limit = time_limit;
+
+ // swap endian order if needed
+ if (isbigendian()) {
+ swapx(&cmd.action_code);
+ swapx(&cmd.function_code);
+ swapx(&cmd.selection_code);
+ swapx(&cmd.time_limit);
+ }
+
+ // write command via SMART log page 0xe0
+ // TODO: Debug output
+ ata_cmd_in in;
+ in.in_regs.command = ATA_SMART_CMD;
+ in.in_regs.lba_high = SMART_CYL_HI; in.in_regs.lba_mid = SMART_CYL_LOW;
+ in.in_regs.features = ATA_SMART_WRITE_LOG_SECTOR;
+ in.in_regs.lba_low = 0xe0;
+ in.set_data_out(&cmd, 1);
+
+ if (!set)
+ // Time limit returned in ATA registers
+ in.out_needed.sector_count = in.out_needed.lba_low = true;
+
+ ata_cmd_out out;
+ if (!device->ata_pass_through(in, out)) {
+ pout("Write SCT (%cet) Error Recovery Control Command failed: %s\n",
+ (!set ? 'G' : 'S'), device->get_errmsg());
+ return -1;
+ }
+
+ // re-read and check SCT status
+ if (ataReadSCTStatus(device, &sts))
+ return -1;
+
+ if (!(sts.ext_status_code == 0 && sts.action_code == 3 && sts.function_code == (set ? 1 : 2))) {
+ pout("Unexpected SCT status 0x%04x (action_code=%u, function_code=%u)\n",
+ sts.ext_status_code, sts.action_code, sts.function_code);
+ return -1;
+ }
+
+ if (!set) {
+ // Check whether registers are properly returned by ioctl()
+ if (!(out.out_regs.sector_count.is_set() && out.out_regs.lba_low.is_set())) {
+ // TODO: Output register support should be checked within each ata_pass_through()
+ // implementation before command is issued.
+ pout("SMART WRITE LOG does not return COUNT and LBA_LOW register\n");
+ return -1;
+ }
+ if ( out.out_regs.sector_count == in.in_regs.sector_count
+ && out.out_regs.lba_low == in.in_regs.lba_low ) {
+ // 0xe001 (5734.5s) - this is most likely a broken ATA pass-through implementation
+ pout("SMART WRITE LOG returns COUNT and LBA_LOW register unchanged\n");
+ return -1;
+ }
+
+ // Return value to caller
+ time_limit = out.out_regs.sector_count | (out.out_regs.lba_low << 8);
+ }
+
+ return 0;
+}
+
+// Get SCT Error Recovery Control
+int ataGetSCTErrorRecoveryControltime(ata_device * device, unsigned type, unsigned short & time_limit)
+{
+ return ataGetSetSCTErrorRecoveryControltime(device, type, false/*get*/, time_limit);
+}
+
+// Set SCT Error Recovery Control
+int ataSetSCTErrorRecoveryControltime(ata_device * device, unsigned type, unsigned short time_limit)
+{
+ return ataGetSetSCTErrorRecoveryControltime(device, type, true/*set*/, time_limit);
+}
+
+
+// Print one self-test log entry.
+// Returns:
+// -1: self-test failed
+// 1: extended self-test completed without error
+// 0: otherwise
+int ataPrintSmartSelfTestEntry(unsigned testnum, unsigned char test_type,
+ unsigned char test_status,
+ unsigned short timestamp,
+ uint64_t failing_lba,
+ bool print_error_only, bool & print_header)
+{
+ // Check status and type for return value
+ int retval = 0;
+ switch (test_status >> 4) {
+ case 0x0:
+ if ((test_type & 0x0f) == 0x02)
+ retval = 1; // extended self-test completed without error
+ break;
+ case 0x3: case 0x4:
+ case 0x5: case 0x6:
+ case 0x7: case 0x8:
+ retval = -1; // self-test failed
+ break;
+ }
+
+ if (retval >= 0 && print_error_only)
+ return retval;
+
+ std::string msgtest;
+ switch (test_type) {
+ case 0x00: msgtest = "Offline"; break;
+ case 0x01: msgtest = "Short offline"; break;
+ case 0x02: msgtest = "Extended offline"; break;
+ case 0x03: msgtest = "Conveyance offline"; break;
+ case 0x04: msgtest = "Selective offline"; break;
+ case 0x7f: msgtest = "Abort offline test"; break;
+ case 0x81: msgtest = "Short captive"; break;
+ case 0x82: msgtest = "Extended captive"; break;
+ case 0x83: msgtest = "Conveyance captive"; break;
+ case 0x84: msgtest = "Selective captive"; break;
+ default:
+ if ((0x40 <= test_type && test_type <= 0x7e) || 0x90 <= test_type)
+ msgtest = strprintf("Vendor (0x%02x)", test_type);
+ else
+ msgtest = strprintf("Reserved (0x%02x)", test_type);
+ }
+
+ std::string msgstat;
+ switch (test_status >> 4) {
+ case 0x0: msgstat = "Completed without error"; break;
+ case 0x1: msgstat = "Aborted by host"; break;
+ case 0x2: msgstat = "Interrupted (host reset)"; break;
+ case 0x3: msgstat = "Fatal or unknown error"; break;
+ case 0x4: msgstat = "Completed: unknown failure"; break;
+ case 0x5: msgstat = "Completed: electrical failure"; break;
+ case 0x6: msgstat = "Completed: servo/seek failure"; break;
+ case 0x7: msgstat = "Completed: read failure"; break;
+ case 0x8: msgstat = "Completed: handling damage??"; break;
+ case 0xf: msgstat = "Self-test routine in progress"; break;
+ default: msgstat = strprintf("Unknown status (0x%x)", test_status >> 4);
+ }
+
+ // Print header once
+ if (print_header) {
+ print_header = false;
+ pout("Num Test_Description Status Remaining LifeTime(hours) LBA_of_first_error\n");
+ }
+
+ char msglba[32];
+ if (retval < 0 && failing_lba < 0xffffffffffffULL)
+ snprintf(msglba, sizeof(msglba), "%" PRIu64, failing_lba);
+ else {
+ msglba[0] = '-'; msglba[1] = 0;
+ }
+
+ pout("#%2u %-19s %-29s %1d0%% %8u %s\n", testnum,
+ msgtest.c_str(), msgstat.c_str(), test_status & 0x0f, timestamp, msglba);
+
+ return retval;
+}
+
+// Print Smart self-test log, used by smartctl and smartd.
+// return value is:
+// bottom 8 bits: number of entries found where self-test showed an error
+// remaining bits: if nonzero, power on hours of last self-test where error was found
+int ataPrintSmartSelfTestlog(const ata_smart_selftestlog * data, bool allentries,
+ firmwarebug_defs firmwarebugs)
+{
+ if (allentries)
+ pout("SMART Self-test log structure revision number %d\n",(int)data->revnumber);
+ if (data->revnumber != 0x0001 && allentries && !firmwarebugs.is_set(BUG_SAMSUNG))
+ pout("Warning: ATA Specification requires self-test log structure revision number = 1\n");
+ if (data->mostrecenttest==0){
+ if (allentries)
+ pout("No self-tests have been logged. [To run self-tests, use: smartctl -t]\n");
+ return 0;
+ }
+
+ bool noheaderprinted = true;
+ int errcnt = 0, hours = 0, igncnt = 0;
+ int testno = 0, ext_ok_testno = -1;
+
+ // print log
+ for (int i = 20; i >= 0; i--) {
+ // log is a circular buffer
+ int j = (i+data->mostrecenttest)%21;
+ const ata_smart_selftestlog_struct * log = data->selftest_struct+j;
+
+ if (nonempty(log, sizeof(*log))) {
+ // count entry based on non-empty structures -- needed for
+ // Seagate only -- other vendors don't have blank entries 'in
+ // the middle'
+ testno++;
+
+ // T13/1321D revision 1c: (Data structure Rev #1)
+
+ //The failing LBA shall be the LBA of the uncorrectable sector
+ //that caused the test to fail. If the device encountered more
+ //than one uncorrectable sector during the test, this field
+ //shall indicate the LBA of the first uncorrectable sector
+ //encountered. If the test passed or the test failed for some
+ //reason other than an uncorrectable sector, the value of this
+ //field is undefined.
+
+ // This is true in ALL ATA-5 specs
+ uint64_t lba48 = (log->lbafirstfailure < 0xffffffff ? log->lbafirstfailure : 0xffffffffffffULL);
+
+ // Print entry
+ int state = ataPrintSmartSelfTestEntry(testno,
+ log->selftestnumber, log->selfteststatus,
+ log->timestamp, lba48, !allentries, noheaderprinted);
+
+ if (state < 0) {
+ // Self-test showed an error
+ if (ext_ok_testno < 0) {
+ errcnt++;
+
+ // keep track of time of most recent error
+ if (!hours)
+ hours = log->timestamp;
+ }
+ else
+ // Newer successful extended self-test exits
+ igncnt++;
+ }
+ else if (state > 0 && ext_ok_testno < 0) {
+ // Latest successful extended self-test
+ ext_ok_testno = testno;
+ }
+ }
+ }
+
+ if (igncnt)
+ pout("%d of %d failed self-tests are outdated by newer successful extended offline self-test #%2d\n",
+ igncnt, igncnt+errcnt, ext_ok_testno);
+
+ if (!allentries && !noheaderprinted)
+ pout("\n");
+
+ return ((hours << 8) | errcnt);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Pseudo-device to parse "smartctl -r ataioctl,2 ..." output and simulate
+// an ATA device with same behaviour
+
+namespace {
+
+class parsed_ata_device
+: public /*implements*/ ata_device_with_command_set
+{
+public:
+ parsed_ata_device(smart_interface * intf, const char * dev_name);
+
+ virtual ~parsed_ata_device() throw();
+
+ virtual bool is_open() const;
+
+ virtual bool open();
+
+ virtual bool close();
+
+ virtual bool ata_identify_is_cached() const;
+
+protected:
+ virtual int ata_command_interface(smart_command_set command, int select, char * data);
+
+private:
+ // Table of parsed commands, return value, data
+ struct parsed_ata_command
+ {
+ smart_command_set command;
+ int select;
+ int retval, errval;
+ char * data;
+ };
+
+ enum { max_num_commands = 32 };
+ parsed_ata_command m_command_table[max_num_commands];
+
+ int m_num_commands;
+ int m_next_replay_command;
+ bool m_replay_out_of_sync;
+ bool m_ata_identify_is_cached;
+};
+
+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);
+}
+
+parsed_ata_device::parsed_ata_device(smart_interface * intf, const char * dev_name)
+: smart_device(intf, dev_name, "ata", ""),
+ m_num_commands(0),
+ m_next_replay_command(0),
+ m_replay_out_of_sync(false),
+ m_ata_identify_is_cached(false)
+{
+ memset(m_command_table, 0, sizeof(m_command_table));
+}
+
+parsed_ata_device::~parsed_ata_device() throw()
+{
+ close();
+}
+
+bool parsed_ata_device::is_open() const
+{
+ return (m_num_commands > 0);
+}
+
+// Parse stdin and build command table
+bool parsed_ata_device::open()
+{
+ const char * pathname = get_dev_name();
+ if (strcmp(pathname, "-"))
+ return set_err(EINVAL);
+ 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)
+ return set_err(ENOENT, "%s: Unexpected EOF", pathname);
+ if (size >= (int)sizeof(buffer))
+ return set_err(EIO, "%s: Buffer overflow", pathname);
+ buffer[size] = 0;
+
+ // Regex to match output from "-r ataioctl,2"
+ static const char pattern[] = "^"
+ "(" // (1
+ "REPORT-IOCTL: DeviceF?D?=[^ ]+ 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)
+ "|"
+ " *(En|Dis)abled status cached by OS, " // (11)
+ ")"; // )
+
+ // Compile regex
+ const regular_expression regex(pattern, REG_EXTENDED);
+
+ // 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] == '=' || line[0] == ' '))
+ continue;
+ const int nmatch = 1+11;
+ regmatch_t match[nmatch];
+ if (!regex.execute(line, nmatch, match))
+ 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_commands) {
+ errmsg = "Too many ATA commands"; break;
+ }
+ m_command_table[i].command = (smart_command_set)nc;
+ m_command_table[i].select = matchtoi(line, match[5], 0); // "InputParameter=%d"
+ state = 1;
+ }
+ else {
+ // End of command
+ if (!(state == 1 && (int)m_command_table[i].command == nc)) {
+ errmsg = "Missing REPORT-IOCTL start"; break;
+ }
+ m_command_table[i].retval = matchtoi(line, match[7], -1); // "returned %d"
+ m_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)m_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;
+ }
+ m_command_table[i].data = data;
+ if (nc != WRITE_LOG)
+ state = 0;
+ }
+ else if (match[11].rm_so > 0) { // "(En|Dis)abled status cached by OS"
+ m_ata_identify_is_cached = true;
+ }
+ }
+
+ if (!(state == 0 || state == 2))
+ errmsg = "Missing REPORT-IOCTL result";
+
+ if (!errmsg && i < 0)
+ errmsg = "No information found";
+
+ m_num_commands = i+1;
+ m_next_replay_command = 0;
+ m_replay_out_of_sync = false;
+
+ if (errmsg) {
+ close();
+ return set_err(EIO, "%s(%d): Syntax error: %s", pathname, lineno, errmsg);
+ }
+ return true;
+}
+
+// Report warnings and free command table
+bool parsed_ata_device::close()
+{
+ if (m_replay_out_of_sync)
+ pout("REPLAY-IOCTL: Warning: commands replayed out of sync\n");
+ else if (m_next_replay_command != 0)
+ pout("REPLAY-IOCTL: Warning: %d command(s) not replayed\n", m_num_commands-m_next_replay_command);
+
+ for (int i = 0; i < m_num_commands; i++) {
+ if (m_command_table[i].data) {
+ free(m_command_table[i].data); m_command_table[i].data = 0;
+ }
+ }
+ m_num_commands = 0;
+ m_next_replay_command = 0;
+ m_replay_out_of_sync = false;
+ return true;
+}
+
+
+bool parsed_ata_device::ata_identify_is_cached() const
+{
+ return m_ata_identify_is_cached;
+}
+
+
+// Simulate ATA command from command table
+int parsed_ata_device::ata_command_interface(smart_command_set command, int select, char * data)
+{
+ // Find command, try round-robin if out of sync
+ int i = m_next_replay_command;
+ for (int j = 0; ; j++) {
+ if (j >= m_num_commands) {
+ pout("REPLAY-IOCTL: Warning: Command not found\n");
+ errno = ENOSYS;
+ return -1;
+ }
+ if (m_command_table[i].command == command && m_command_table[i].select == select)
+ break;
+ if (!m_replay_out_of_sync) {
+ m_replay_out_of_sync = true;
+ pout("REPLAY-IOCTL: Warning: Command #%d is out of sync\n", i+1);
+ }
+ if (++i >= m_num_commands)
+ i = 0;
+ }
+ m_next_replay_command = i;
+ if (++m_next_replay_command >= m_num_commands)
+ m_next_replay_command = 0;
+
+ // Return command data
+ switch (command) {
+ case IDENTIFY:
+ case PIDENTIFY:
+ case READ_VALUES:
+ case READ_THRESHOLDS:
+ case READ_LOG:
+ if (m_command_table[i].data)
+ memcpy(data, m_command_table[i].data, 512);
+ break;
+ case WRITE_LOG:
+ if (!(m_command_table[i].data && !memcmp(data, m_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 (m_command_table[i].errval)
+ errno = m_command_table[i].errval;
+ return m_command_table[i].retval;
+}
+
+} // namespace
+
+ata_device * get_parsed_ata_device(smart_interface * intf, const char * dev_name)
+{
+ return new parsed_ata_device(intf, dev_name);
+}