Loading include/storaged/api.h +7 −4 Changes for include/storaged/api.h: 7 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -110,16 +110,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Activate / Deactivate {vg, lv?} without lv: every plain LV * * SMART (ATA drives over SG_IO -- SATA, SAS and USB bridges that pass ATA * through --, NVMe drives with admin commands; no smartctl). An ATA drive * in standby isn't spun up: its last reading is shown (read_at), power * through --, SCSI/SAS drives with LOG SENSE, NVMe drives with admin * commands; no smartctl). An ATA or SCSI drive in standby isn't spun up: its last reading is shown (read_at), power * says standby. A drive that doesn't answer keeps its last reading too, * with problem set. * ListSmart -> per disk: disk (name), size, model, serial?, firmware?, * transport (ata|nvme|none), smart (available), enabled, rotation? * transport (ata|scsi|nvme|none), smart (available), enabled, rotation? * (ssd|rpm), power (active|idle|standby|unknown), health * (passed|failing|unknown), temperature? (°C), power_on_hours?, * power_cycles?, ATA: reallocated?, pending?, uncorrectable?, * crc_errors?; NVMe: critical_warning (bits), available_spare, * crc_errors?; SCSI: reallocated? (grown defects), uncorrectable?, * start_stop_cycles?, load_cycles?, non_medium_errors?, data_read?, * data_written? (bytes), exception? (ASC/ASCQ of an informational * exception); NVMe: critical_warning (bits), available_spare, * spare_threshold, percentage_used (%), data_read, data_written * (bytes), unsafe_shutdowns, media_errors, error_log_entries; * self_test_supported, self_test? (passed|failed|aborted| Loading src/smart.cpp +238 −2 Changes for src/smart.cpp: 238 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -401,6 +401,228 @@ void ataSummary(Fields &f) { f.insert(first, s.begin(), s.end()); } // ─── SCSI (SAS drives: LOG SENSE, MODE SENSE, SEND DIAGNOSTIC) ────────────── uint64_t be(const uint8_t *p, int n) { uint64_t v = 0; for (int i = 0; i < n; ++i) v = (v << 8) | p[i]; return v; } // a SCSI command; data: len bytes in (nullptr: none). sense: the sense // key, ASC, ASCQ of a CHECK CONDITION (else 0) struct Sense { int key = 0, asc = 0, ascq = 0; }; bool scsi(int fd, const uint8_t *cdb, int cdbLen, uint8_t *data, unsigned len, std::string &err, Sense *out = nullptr) { uint8_t sense[32] = {}; SgIoHdr io{}; io.interface_id = 'S'; io.dxfer_direction = data ? SgDxferFromDev : SgDxferNone; io.cmd_len = cdbLen; io.cmdp = const_cast<uint8_t *>(cdb); io.mx_sb_len = sizeof(sense); io.sbp = sense; io.dxfer_len = data ? len : 0; io.dxferp = data; io.timeout = 15000; if (ioctl(fd, SgIo, &io) < 0) { err = blk::sysErr("SG_IO", errno); return false; } if (io.host_status != 0) { err = "host status " + std::to_string(io.host_status); return false; } Sense s; if (io.sb_len_wr > 0) { int code = sense[0] & 0x7f; if (code == 0x72 || code == 0x73) s = {sense[1] & 0x0f, sense[2], sense[3]}; else if (code == 0x70 || code == 0x71) s = {sense[2] & 0x0f, sense[12], sense[13]}; } if (out) *out = s; if (s.key != 0 && s.key != 1) { err = s.key == 5 ? "not supported" : "sense key " + std::to_string(s.key); return false; } if (io.status != 0 && s.key == 0) { err = "SCSI status " + std::to_string(io.status); return false; } return true; } // LOG SENSE, current cumulative values; the page's length (0: none) unsigned logSense(int fd, uint8_t page, uint8_t *buf, unsigned len) { uint8_t cdb[10] = {0x4D, 0, (uint8_t)(0x40 | page), 0, 0, 0, 0, (uint8_t)(len >> 8), (uint8_t)len, 0}; std::string err; std::memset(buf, 0, len); if (!scsi(fd, cdb, sizeof(cdb), buf, len, err) || (buf[0] & 0x3f) != page) return 0; return std::min<unsigned>(len, 4 + (unsigned)be(buf + 2, 2)); } // the parameters of a log page: code -> its data std::map<int, std::pair<const uint8_t *, int>> logParams(const uint8_t *buf, unsigned len) { std::map<int, std::pair<const uint8_t *, int>> p; for (unsigned i = 4; i + 4 <= len;) { int n = buf[i + 3]; if (i + 4 + n > len) break; p[(int)be(buf + i, 2)] = {buf + i + 4, n}; i += 4 + n; } return p; } // MODE SENSE (10) of one page, current values; the page itself (nullptr: none) const uint8_t *modeSense(int fd, uint8_t page, uint8_t *buf, unsigned len) { uint8_t cdb[10] = {0x5A, 0x08, page, 0, 0, 0, 0, (uint8_t)(len >> 8), (uint8_t)len, 0}; // DBD std::string err; std::memset(buf, 0, len); if (!scsi(fd, cdb, sizeof(cdb), buf, len, err)) return nullptr; unsigned off = 8 + (unsigned)be(buf + 6, 2); // header, block descriptors if (off + 2 > len || (buf[off] & 0x3f) != page) return nullptr; return buf + off; } // a VPD page of INQUIRY; its length (0: none) unsigned vpd(int fd, uint8_t page, uint8_t *buf, unsigned len) { uint8_t cdb[6] = {0x12, 0x01, page, (uint8_t)(len >> 8), (uint8_t)len, 0}; std::string err; std::memset(buf, 0, len); if (!scsi(fd, cdb, sizeof(cdb), buf, len, err) || buf[1] != page) return 0; return std::min<unsigned>(len, 4 + (unsigned)be(buf + 2, 2)); } Fields readScsi(int fd, bool wake, const std::string &ataProblem) { Fields f; std::string err; uint8_t inq[96] = {}; uint8_t cdb[6] = {0x12, 0, 0, 0, sizeof(inq), 0}; if (!scsi(fd, cdb, sizeof(cdb), inq, sizeof(inq), err) || (inq[0] & 0x1f) != 0x00) { f.emplace_back("transport", "none"); f.emplace_back("smart", "no"); f.emplace_back("problem", ataProblem); return f; } f.emplace_back("transport", "scsi"); std::string vendor = trimmed(std::string((char *)inq + 8, 8)), product = trimmed(std::string((char *)inq + 16, 16)); f.emplace_back("model", vendor.empty() ? product : vendor + " " + product); uint8_t buf[1024]; if (unsigned n = vpd(fd, 0x80, buf, 255)) f.emplace_back("serial", trimmed(std::string((char *)buf + 4, n - 4))); f.emplace_back("firmware", trimmed(std::string((char *)inq + 32, 4))); // Block Device Characteristics: medium rotation rate if (vpd(fd, 0xB1, buf, 64) >= 6) { unsigned rpm = (unsigned)be(buf + 4, 2); if (rpm == 1) f.emplace_back("rotation", "ssd"); else if (rpm >= 0x401 && rpm < 0xFFFF) f.emplace_back("rotation", std::to_string(rpm)); } std::set<int> pages; if (unsigned n = logSense(fd, 0x00, buf, 255)) for (unsigned i = 4; i < n; ++i) pages.insert(buf[i] & 0x3f); // Informational Exceptions: the drive's failure prediction const uint8_t *iec = modeSense(fd, 0x1C, buf, 64); bool supported = pages.count(0x2F) || iec, enabled = iec ? !(iec[2] & 0x08) : supported; // DEXCPT f.emplace_back("smart", supported ? "yes" : "no"); f.emplace_back("enabled", enabled ? "yes" : "no"); if (!supported) { f.emplace_back("problem", "the drive has no SMART"); return f; } // a drive in standby isn't spun up: REQUEST SENSE doesn't wake it uint8_t rs[18] = {}; uint8_t rsCdb[6] = {0x03, 0, 0, 0, sizeof(rs), 0}; if (scsi(fd, rsCdb, sizeof(rsCdb), rs, sizeof(rs), err)) { int asc = rs[12], ascq = rs[13]; bool stopped = (rs[2] & 0x0f) == 2 && asc == 0x04 && ascq == 0x02; // needs START UNIT bool standby = stopped || (asc == 0x5E && (ascq == 0x02 || ascq == 0x04 || ascq == 0x09 || ascq == 0x0A)); bool idle = asc == 0x5E && (ascq == 0x01 || ascq == 0x03 || (ascq >= 0x05 && ascq <= 0x08)); f.emplace_back("power", standby ? "standby" : idle ? "idle" : "active"); if (standby && !wake) { f.emplace_back("asleep", "yes"); return f; } } else { f.emplace_back("power", "unknown"); } int temperature = -1; if (pages.count(0x2F)) { unsigned n = logSense(fd, 0x2F, buf, sizeof(buf)); auto p = logParams(buf, n); if (p.count(0) && p[0].second >= 2) { const uint8_t *d = p[0].first; // ASC 5Dh: a failure prediction threshold exceeded f.emplace_back("health", d[0] == 0x5D ? "failing" : "passed"); if (d[0]) f.emplace_back("exception", std::to_string(d[0]) + "/" + std::to_string(d[1])); if (p[0].second >= 3 && d[2] && d[2] != 0xFF) temperature = d[2]; } else { f.emplace_back("health", "unknown"); } } else { f.emplace_back("health", "unknown"); } if (pages.count(0x0D)) { auto p = logParams(buf, logSense(fd, 0x0D, buf, sizeof(buf))); if (p.count(0) && p[0].second >= 2 && p[0].first[1] != 0xFF) temperature = p[0].first[1]; } if (temperature >= 0) f.emplace_back("temperature", std::to_string(temperature)); // Background Scan Results: accumulated power-on minutes if (pages.count(0x15)) { auto p = logParams(buf, logSense(fd, 0x15, buf, sizeof(buf))); if (p.count(0) && p[0].second >= 4) f.emplace_back("power_on_hours", std::to_string(be(p[0].first, 4) / 60)); } if (pages.count(0x0E)) { auto p = logParams(buf, logSense(fd, 0x0E, buf, sizeof(buf))); if (p.count(4) && p[4].second >= 4) f.emplace_back("start_stop_cycles", std::to_string(be(p[4].first, 4))); if (p.count(6) && p[6].second >= 4) f.emplace_back("load_cycles", std::to_string(be(p[6].first, 4))); } // the grown defect list: sectors the drive replaced (READ DEFECT DATA (10)) uint8_t dd[8] = {}; uint8_t ddCdb[10] = {0x37, 0, 0x08 | 0x05, 0, 0, 0, 0, 0, sizeof(dd), 0}; // GLIST, physical sector format if (scsi(fd, ddCdb, sizeof(ddCdb), dd, sizeof(dd), err) && (dd[1] & 0x08)) f.emplace_back("reallocated", std::to_string(be(dd + 2, 2) / 8)); // Error counters: uncorrected errors (parameter 6), bytes processed (5) long long uncorrected = -1; for (uint8_t page : {0x03, 0x02, 0x05}) { // read, write, verify if (!pages.count(page)) continue; auto p = logParams(buf, logSense(fd, page, buf, sizeof(buf))); if (p.count(6) && p[6].second > 0) uncorrected = std::max(uncorrected, 0LL) + (long long)be(p[6].first, std::min(p[6].second, 8)); if (p.count(5) && p[5].second > 0 && page != 0x05) f.emplace_back(page == 0x03 ? "data_read" : "data_written", std::to_string(be(p[5].first, std::min(p[5].second, 8)))); } if (uncorrected >= 0) f.emplace_back("uncorrectable", std::to_string(uncorrected)); if (pages.count(0x06)) { auto p = logParams(buf, logSense(fd, 0x06, buf, sizeof(buf))); if (p.count(0) && p[0].second > 0) f.emplace_back("non_medium_errors", std::to_string(be(p[0].first, std::min(p[0].second, 8)))); } // Self-Test Results: parameter 1 is the newest bool selfTest = pages.count(0x10); f.emplace_back("self_test_supported", selfTest ? "yes" : "no"); if (selfTest) { auto p = logParams(buf, logSense(fd, 0x10, buf, sizeof(buf))); if (p.count(1) && p[1].second >= 1) { int code = p[1].first[0] >> 5, result = p[1].first[0] & 0x0f; f.emplace_back("self_test", code == 0 && result == 0 ? "none" : result == 0 ? "passed" : result == 1 ? "aborted" : result == 2 ? "interrupted" : result == 15 ? "running" : "failed"); } // Control mode page: extended self-test completion time (s) if (const uint8_t *ctl = modeSense(fd, 0x0A, buf, 64)) if (ctl[1] >= 10) { unsigned s = (unsigned)be(ctl + 10, 2); if (s && s != 0xFFFF) f.emplace_back("long_test_minutes", std::to_string((s + 59) / 60)); } } return f; } // ─── NVMe (admin commands) ────────────────────────────────────────────────── int nvmeAdmin(int fd, uint8_t opcode, uint32_t nsid, uint32_t cdw10, void *buf, uint32_t len) { Loading Loading @@ -510,8 +732,10 @@ std::string readDrive(const std::string &name, bool wake) { int fd = open(("/dev/" + name).c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) return pack({{"transport", "none"}, {"smart", "no"}, {"problem", blk::sysErr("/dev/" + name, errno)}}); Fields f = k == Kind::Nvme ? readNvme(fd) : readAta(fd, wake); // no ATA, not even through a SAT layer: a SCSI drive (SAS) if (k == Kind::Ata && get(f, "transport") == "none") f = readScsi(fd, wake, get(f, "problem")); close(fd); if (k == Kind::Ata) ataSummary(f); if (get(f, "transport") == "ata") ataSummary(f); return pack(f); } Loading @@ -525,8 +749,20 @@ std::string runTest(const std::string &name, const std::string &type) { int rc = nvmeAdmin(fd, 0x14, 0xFFFFFFFF, code, nullptr, 0); if (rc != 0) err = nvmeErr(rc); } else { uint8_t id[512]; AtaCmd identify; identify.command = 0xEC; identify.count = 1; if (ata(fd, identify, id, nullptr, err)) { uint8_t sub = type == "short" ? 1 : type == "long" ? 2 : 127; // offline immediate: 127 aborts if (!ata(fd, smartCmd(0xD4, sub), nullptr, nullptr, err) && err.empty()) err = "failed"; } else { // SCSI: SEND DIAGNOSTIC, background short/extended, or abort err.clear(); uint8_t code = type == "short" ? 1 : type == "long" ? 2 : 4; uint8_t cdb[6] = {0x1D, (uint8_t)(code << 5), 0, 0, 0, 0}; if (!scsi(fd, cdb, sizeof(cdb), nullptr, 0, err) && err.empty()) err = "failed"; } } close(fd); return err.empty() ? "ok" : err; Loading Loading @@ -585,7 +821,7 @@ std::map<std::string, std::pair<Fields, std::string>> readAll(const std::vector< // what is wrong with a drive, in the language of the call std::vector<std::string> warnings(const Fields &f) { std::vector<std::string> w; if (get(f, "health") == "failing" && get(f, "transport") == "ata") if (get(f, "health") == "failing" && get(f, "transport") != "nvme") w.push_back(tr("the drive predicts its own failure (SMART status)", "das Laufwerk sagt seinen Ausfall voraus (SMART-Status)")); std::string attr, name; for (auto &kv : f) { Loading src/smart.h +2 −1 Changes for src/smart.h: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -26,7 +26,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /* SMART, without smartctl: ATA drives (SATA, and SAS/USB bridges that * translate ATA PASS-THROUGH) over SG_IO, NVMe drives with the admin * translate ATA PASS-THROUGH) over SG_IO, SCSI drives (SAS) with LOG * SENSE / SEND DIAGNOSTIC over SG_IO, NVMe drives with the admin * commands of the nvme driver's ioctl. The methods are in * include/storaged/api.h. * Loading Loading
include/storaged/api.h +7 −4 Changes for include/storaged/api.h: 7 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -110,16 +110,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Activate / Deactivate {vg, lv?} without lv: every plain LV * * SMART (ATA drives over SG_IO -- SATA, SAS and USB bridges that pass ATA * through --, NVMe drives with admin commands; no smartctl). An ATA drive * in standby isn't spun up: its last reading is shown (read_at), power * through --, SCSI/SAS drives with LOG SENSE, NVMe drives with admin * commands; no smartctl). An ATA or SCSI drive in standby isn't spun up: its last reading is shown (read_at), power * says standby. A drive that doesn't answer keeps its last reading too, * with problem set. * ListSmart -> per disk: disk (name), size, model, serial?, firmware?, * transport (ata|nvme|none), smart (available), enabled, rotation? * transport (ata|scsi|nvme|none), smart (available), enabled, rotation? * (ssd|rpm), power (active|idle|standby|unknown), health * (passed|failing|unknown), temperature? (°C), power_on_hours?, * power_cycles?, ATA: reallocated?, pending?, uncorrectable?, * crc_errors?; NVMe: critical_warning (bits), available_spare, * crc_errors?; SCSI: reallocated? (grown defects), uncorrectable?, * start_stop_cycles?, load_cycles?, non_medium_errors?, data_read?, * data_written? (bytes), exception? (ASC/ASCQ of an informational * exception); NVMe: critical_warning (bits), available_spare, * spare_threshold, percentage_used (%), data_read, data_written * (bytes), unsafe_shutdowns, media_errors, error_log_entries; * self_test_supported, self_test? (passed|failed|aborted| Loading
src/smart.cpp +238 −2 Changes for src/smart.cpp: 238 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -401,6 +401,228 @@ void ataSummary(Fields &f) { f.insert(first, s.begin(), s.end()); } // ─── SCSI (SAS drives: LOG SENSE, MODE SENSE, SEND DIAGNOSTIC) ────────────── uint64_t be(const uint8_t *p, int n) { uint64_t v = 0; for (int i = 0; i < n; ++i) v = (v << 8) | p[i]; return v; } // a SCSI command; data: len bytes in (nullptr: none). sense: the sense // key, ASC, ASCQ of a CHECK CONDITION (else 0) struct Sense { int key = 0, asc = 0, ascq = 0; }; bool scsi(int fd, const uint8_t *cdb, int cdbLen, uint8_t *data, unsigned len, std::string &err, Sense *out = nullptr) { uint8_t sense[32] = {}; SgIoHdr io{}; io.interface_id = 'S'; io.dxfer_direction = data ? SgDxferFromDev : SgDxferNone; io.cmd_len = cdbLen; io.cmdp = const_cast<uint8_t *>(cdb); io.mx_sb_len = sizeof(sense); io.sbp = sense; io.dxfer_len = data ? len : 0; io.dxferp = data; io.timeout = 15000; if (ioctl(fd, SgIo, &io) < 0) { err = blk::sysErr("SG_IO", errno); return false; } if (io.host_status != 0) { err = "host status " + std::to_string(io.host_status); return false; } Sense s; if (io.sb_len_wr > 0) { int code = sense[0] & 0x7f; if (code == 0x72 || code == 0x73) s = {sense[1] & 0x0f, sense[2], sense[3]}; else if (code == 0x70 || code == 0x71) s = {sense[2] & 0x0f, sense[12], sense[13]}; } if (out) *out = s; if (s.key != 0 && s.key != 1) { err = s.key == 5 ? "not supported" : "sense key " + std::to_string(s.key); return false; } if (io.status != 0 && s.key == 0) { err = "SCSI status " + std::to_string(io.status); return false; } return true; } // LOG SENSE, current cumulative values; the page's length (0: none) unsigned logSense(int fd, uint8_t page, uint8_t *buf, unsigned len) { uint8_t cdb[10] = {0x4D, 0, (uint8_t)(0x40 | page), 0, 0, 0, 0, (uint8_t)(len >> 8), (uint8_t)len, 0}; std::string err; std::memset(buf, 0, len); if (!scsi(fd, cdb, sizeof(cdb), buf, len, err) || (buf[0] & 0x3f) != page) return 0; return std::min<unsigned>(len, 4 + (unsigned)be(buf + 2, 2)); } // the parameters of a log page: code -> its data std::map<int, std::pair<const uint8_t *, int>> logParams(const uint8_t *buf, unsigned len) { std::map<int, std::pair<const uint8_t *, int>> p; for (unsigned i = 4; i + 4 <= len;) { int n = buf[i + 3]; if (i + 4 + n > len) break; p[(int)be(buf + i, 2)] = {buf + i + 4, n}; i += 4 + n; } return p; } // MODE SENSE (10) of one page, current values; the page itself (nullptr: none) const uint8_t *modeSense(int fd, uint8_t page, uint8_t *buf, unsigned len) { uint8_t cdb[10] = {0x5A, 0x08, page, 0, 0, 0, 0, (uint8_t)(len >> 8), (uint8_t)len, 0}; // DBD std::string err; std::memset(buf, 0, len); if (!scsi(fd, cdb, sizeof(cdb), buf, len, err)) return nullptr; unsigned off = 8 + (unsigned)be(buf + 6, 2); // header, block descriptors if (off + 2 > len || (buf[off] & 0x3f) != page) return nullptr; return buf + off; } // a VPD page of INQUIRY; its length (0: none) unsigned vpd(int fd, uint8_t page, uint8_t *buf, unsigned len) { uint8_t cdb[6] = {0x12, 0x01, page, (uint8_t)(len >> 8), (uint8_t)len, 0}; std::string err; std::memset(buf, 0, len); if (!scsi(fd, cdb, sizeof(cdb), buf, len, err) || buf[1] != page) return 0; return std::min<unsigned>(len, 4 + (unsigned)be(buf + 2, 2)); } Fields readScsi(int fd, bool wake, const std::string &ataProblem) { Fields f; std::string err; uint8_t inq[96] = {}; uint8_t cdb[6] = {0x12, 0, 0, 0, sizeof(inq), 0}; if (!scsi(fd, cdb, sizeof(cdb), inq, sizeof(inq), err) || (inq[0] & 0x1f) != 0x00) { f.emplace_back("transport", "none"); f.emplace_back("smart", "no"); f.emplace_back("problem", ataProblem); return f; } f.emplace_back("transport", "scsi"); std::string vendor = trimmed(std::string((char *)inq + 8, 8)), product = trimmed(std::string((char *)inq + 16, 16)); f.emplace_back("model", vendor.empty() ? product : vendor + " " + product); uint8_t buf[1024]; if (unsigned n = vpd(fd, 0x80, buf, 255)) f.emplace_back("serial", trimmed(std::string((char *)buf + 4, n - 4))); f.emplace_back("firmware", trimmed(std::string((char *)inq + 32, 4))); // Block Device Characteristics: medium rotation rate if (vpd(fd, 0xB1, buf, 64) >= 6) { unsigned rpm = (unsigned)be(buf + 4, 2); if (rpm == 1) f.emplace_back("rotation", "ssd"); else if (rpm >= 0x401 && rpm < 0xFFFF) f.emplace_back("rotation", std::to_string(rpm)); } std::set<int> pages; if (unsigned n = logSense(fd, 0x00, buf, 255)) for (unsigned i = 4; i < n; ++i) pages.insert(buf[i] & 0x3f); // Informational Exceptions: the drive's failure prediction const uint8_t *iec = modeSense(fd, 0x1C, buf, 64); bool supported = pages.count(0x2F) || iec, enabled = iec ? !(iec[2] & 0x08) : supported; // DEXCPT f.emplace_back("smart", supported ? "yes" : "no"); f.emplace_back("enabled", enabled ? "yes" : "no"); if (!supported) { f.emplace_back("problem", "the drive has no SMART"); return f; } // a drive in standby isn't spun up: REQUEST SENSE doesn't wake it uint8_t rs[18] = {}; uint8_t rsCdb[6] = {0x03, 0, 0, 0, sizeof(rs), 0}; if (scsi(fd, rsCdb, sizeof(rsCdb), rs, sizeof(rs), err)) { int asc = rs[12], ascq = rs[13]; bool stopped = (rs[2] & 0x0f) == 2 && asc == 0x04 && ascq == 0x02; // needs START UNIT bool standby = stopped || (asc == 0x5E && (ascq == 0x02 || ascq == 0x04 || ascq == 0x09 || ascq == 0x0A)); bool idle = asc == 0x5E && (ascq == 0x01 || ascq == 0x03 || (ascq >= 0x05 && ascq <= 0x08)); f.emplace_back("power", standby ? "standby" : idle ? "idle" : "active"); if (standby && !wake) { f.emplace_back("asleep", "yes"); return f; } } else { f.emplace_back("power", "unknown"); } int temperature = -1; if (pages.count(0x2F)) { unsigned n = logSense(fd, 0x2F, buf, sizeof(buf)); auto p = logParams(buf, n); if (p.count(0) && p[0].second >= 2) { const uint8_t *d = p[0].first; // ASC 5Dh: a failure prediction threshold exceeded f.emplace_back("health", d[0] == 0x5D ? "failing" : "passed"); if (d[0]) f.emplace_back("exception", std::to_string(d[0]) + "/" + std::to_string(d[1])); if (p[0].second >= 3 && d[2] && d[2] != 0xFF) temperature = d[2]; } else { f.emplace_back("health", "unknown"); } } else { f.emplace_back("health", "unknown"); } if (pages.count(0x0D)) { auto p = logParams(buf, logSense(fd, 0x0D, buf, sizeof(buf))); if (p.count(0) && p[0].second >= 2 && p[0].first[1] != 0xFF) temperature = p[0].first[1]; } if (temperature >= 0) f.emplace_back("temperature", std::to_string(temperature)); // Background Scan Results: accumulated power-on minutes if (pages.count(0x15)) { auto p = logParams(buf, logSense(fd, 0x15, buf, sizeof(buf))); if (p.count(0) && p[0].second >= 4) f.emplace_back("power_on_hours", std::to_string(be(p[0].first, 4) / 60)); } if (pages.count(0x0E)) { auto p = logParams(buf, logSense(fd, 0x0E, buf, sizeof(buf))); if (p.count(4) && p[4].second >= 4) f.emplace_back("start_stop_cycles", std::to_string(be(p[4].first, 4))); if (p.count(6) && p[6].second >= 4) f.emplace_back("load_cycles", std::to_string(be(p[6].first, 4))); } // the grown defect list: sectors the drive replaced (READ DEFECT DATA (10)) uint8_t dd[8] = {}; uint8_t ddCdb[10] = {0x37, 0, 0x08 | 0x05, 0, 0, 0, 0, 0, sizeof(dd), 0}; // GLIST, physical sector format if (scsi(fd, ddCdb, sizeof(ddCdb), dd, sizeof(dd), err) && (dd[1] & 0x08)) f.emplace_back("reallocated", std::to_string(be(dd + 2, 2) / 8)); // Error counters: uncorrected errors (parameter 6), bytes processed (5) long long uncorrected = -1; for (uint8_t page : {0x03, 0x02, 0x05}) { // read, write, verify if (!pages.count(page)) continue; auto p = logParams(buf, logSense(fd, page, buf, sizeof(buf))); if (p.count(6) && p[6].second > 0) uncorrected = std::max(uncorrected, 0LL) + (long long)be(p[6].first, std::min(p[6].second, 8)); if (p.count(5) && p[5].second > 0 && page != 0x05) f.emplace_back(page == 0x03 ? "data_read" : "data_written", std::to_string(be(p[5].first, std::min(p[5].second, 8)))); } if (uncorrected >= 0) f.emplace_back("uncorrectable", std::to_string(uncorrected)); if (pages.count(0x06)) { auto p = logParams(buf, logSense(fd, 0x06, buf, sizeof(buf))); if (p.count(0) && p[0].second > 0) f.emplace_back("non_medium_errors", std::to_string(be(p[0].first, std::min(p[0].second, 8)))); } // Self-Test Results: parameter 1 is the newest bool selfTest = pages.count(0x10); f.emplace_back("self_test_supported", selfTest ? "yes" : "no"); if (selfTest) { auto p = logParams(buf, logSense(fd, 0x10, buf, sizeof(buf))); if (p.count(1) && p[1].second >= 1) { int code = p[1].first[0] >> 5, result = p[1].first[0] & 0x0f; f.emplace_back("self_test", code == 0 && result == 0 ? "none" : result == 0 ? "passed" : result == 1 ? "aborted" : result == 2 ? "interrupted" : result == 15 ? "running" : "failed"); } // Control mode page: extended self-test completion time (s) if (const uint8_t *ctl = modeSense(fd, 0x0A, buf, 64)) if (ctl[1] >= 10) { unsigned s = (unsigned)be(ctl + 10, 2); if (s && s != 0xFFFF) f.emplace_back("long_test_minutes", std::to_string((s + 59) / 60)); } } return f; } // ─── NVMe (admin commands) ────────────────────────────────────────────────── int nvmeAdmin(int fd, uint8_t opcode, uint32_t nsid, uint32_t cdw10, void *buf, uint32_t len) { Loading Loading @@ -510,8 +732,10 @@ std::string readDrive(const std::string &name, bool wake) { int fd = open(("/dev/" + name).c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) return pack({{"transport", "none"}, {"smart", "no"}, {"problem", blk::sysErr("/dev/" + name, errno)}}); Fields f = k == Kind::Nvme ? readNvme(fd) : readAta(fd, wake); // no ATA, not even through a SAT layer: a SCSI drive (SAS) if (k == Kind::Ata && get(f, "transport") == "none") f = readScsi(fd, wake, get(f, "problem")); close(fd); if (k == Kind::Ata) ataSummary(f); if (get(f, "transport") == "ata") ataSummary(f); return pack(f); } Loading @@ -525,8 +749,20 @@ std::string runTest(const std::string &name, const std::string &type) { int rc = nvmeAdmin(fd, 0x14, 0xFFFFFFFF, code, nullptr, 0); if (rc != 0) err = nvmeErr(rc); } else { uint8_t id[512]; AtaCmd identify; identify.command = 0xEC; identify.count = 1; if (ata(fd, identify, id, nullptr, err)) { uint8_t sub = type == "short" ? 1 : type == "long" ? 2 : 127; // offline immediate: 127 aborts if (!ata(fd, smartCmd(0xD4, sub), nullptr, nullptr, err) && err.empty()) err = "failed"; } else { // SCSI: SEND DIAGNOSTIC, background short/extended, or abort err.clear(); uint8_t code = type == "short" ? 1 : type == "long" ? 2 : 4; uint8_t cdb[6] = {0x1D, (uint8_t)(code << 5), 0, 0, 0, 0}; if (!scsi(fd, cdb, sizeof(cdb), nullptr, 0, err) && err.empty()) err = "failed"; } } close(fd); return err.empty() ? "ok" : err; Loading Loading @@ -585,7 +821,7 @@ std::map<std::string, std::pair<Fields, std::string>> readAll(const std::vector< // what is wrong with a drive, in the language of the call std::vector<std::string> warnings(const Fields &f) { std::vector<std::string> w; if (get(f, "health") == "failing" && get(f, "transport") == "ata") if (get(f, "health") == "failing" && get(f, "transport") != "nvme") w.push_back(tr("the drive predicts its own failure (SMART status)", "das Laufwerk sagt seinen Ausfall voraus (SMART-Status)")); std::string attr, name; for (auto &kv : f) { Loading
src/smart.h +2 −1 Changes for src/smart.h: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -26,7 +26,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /* SMART, without smartctl: ATA drives (SATA, and SAS/USB bridges that * translate ATA PASS-THROUGH) over SG_IO, NVMe drives with the admin * translate ATA PASS-THROUGH) over SG_IO, SCSI drives (SAS) with LOG * SENSE / SEND DIAGNOSTIC over SG_IO, NVMe drives with the admin * commands of the nvme driver's ioctl. The methods are in * include/storaged/api.h. * Loading