"use strict"; const Promise = require("bluebird"); const execBinary = require("../exec-binary"); const {B} = require("../units/bytes/iec"); const matchOrError = require("../match-or-error"); const errors = require("../errors"); const mapValue = require("../map-value"); /* FIXME: Error handling, eg. device not found errors */ function mapAttributeFlags(flagString) { let flagBuffer = Buffer.from(flagString.slice(2), "hex"); let flagByte = flagBuffer.readUInt16BE(0); if (flagByte & 128 || flagByte & 64) { throw new Error(`Encountered unknown flag byte in flag ${flagString}`); } else { return { autoKeep: Boolean(flagByte & 32), eventCount: Boolean(flagByte & 16), errorRate: Boolean(flagByte & 8), affectsPerformance: Boolean(flagByte & 4), updatedOnline: Boolean(flagByte & 2), indicatesFailure: Boolean(flagByte & 1), }; } } module.exports = { attributes: function ({ devicePath }) { return Promise.try(() => { return execBinary("smartctl", [devicePath]) .asRoot() .withFlags({ attributes: true }) .singleResult() .expectStdout("attributes", /^\s*([0-9]+)\s+([a-zA-Z_-]+)\s+(0x[0-9a-f]{4})\s+([0-9]{3})\s+([0-9]{3})\s+([0-9]{3})\s+(Pre-fail|Old_age)\s+(Always|Offline)\s+(FAILING_NOW|In_the_past|-)\s+(.+)$/gm, { required: true, matchAll: true, result: ([id, attributeName, flags, value, worst, threshold, type, updatedWhen, failedWhen, rawValue]) => { return { id: parseInt(id), name: attributeName, flags: mapAttributeFlags(flags), value: parseInt(value), rawValue: rawValue, worstValueSeen: parseInt(worst), failureThreshold: parseInt(threshold), type: mapValue(type, { "Pre-fail": "preFail", "Old_age": "oldAge" }), failingNow: (failedWhen === "FAILING_NOW"), failedBefore: (failedWhen === "In_the_past"), updatedWhen: mapValue(updatedWhen, { "Always": "always", "Offline": "offline" }) }; } }) .execute(); }).then((output) => { return output.result; }); }, info: function ({ devicePath }) { return Promise.try(() => { return execBinary("smartctl", [devicePath]) .asRoot() .withFlags({ info: true }) .expectStdout("smartAvailable", /^SMART support is:\s*(Available|Unavailable|Ambiguous).+$/m, { result: ([availability]) => { return mapValue(availability, { Available: true, Unavailable: false, Ambiguous: null }); } }) .expectStdout("model", /^Device Model:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("modelFamily", /^Model Family:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("serialNumber", /^Serial Number:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("wwn", /^LU WWN Device Id:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("firmwareVersion", /^Firmware Version:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("size", /^User Capacity:\s*(.+)$/m, { result: ([value]) => { try { let match = matchOrError(/^([0-9,]+) bytes \[[^\]]+\]$/, value); return B(parseInt(match[0].replace(/,/g, ""))); } catch (error) { throw errors.UnexpectedOutput.chain(error, "Could not parse drive capacity", { input: value }); } } }) .expectStdout("rpm", /^Rotation Rate:\s*(.+)$/m, { result: ([value]) => { try { let match = matchOrError(/^([0-9]+) rpm$/, value); return parseInt(match[0]); } catch (error) { throw errors.UnexpectedOutput.chain(error, "Could not parse drive RPM", { input: value }); } } }) .expectStdout("sectorSizes", /^Sector Sizes:\s*(.+)$/m, { result: ([value]) => { try { let match = matchOrError(/^([0-9]+) bytes logical, ([0-9]+) bytes physical$/, value); return { logical: B(parseInt(match[0])), physical: B(parseInt(match[1])) }; } catch (error) { throw errors.UnexpectedOutput.chain(error, "Could not parse drive sector sizes", { input: value }); } } }) .expectStdout("formFactor", /^Form Factor:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("ataVersion", /^ATA Version is:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("sataVersion", /^SATA Version is:\s*(.+)$/m, { result: ([value]) => value }) .expectStdout("smartEnabled", /^SMART support is:\s*(Enabled|Disabled)$/m, { result: ([value]) => { return mapValue(value, { Enabled: true, Disabled: false }); } }) .execute(); }).then((output) => { return output.result; }); }, scan: function () { return Promise.try(() => { return execBinary("smartctl") .asRoot() .withFlags({ scan: true }) .singleResult() .expectStdout("devices", /^([^ ]+) -d ([^ ]+) #.+$/gm, { matchAll: true, result: ([devicePath, interface_]) => { return { path: devicePath, interface: interface_ }; } }) .execute(); }).then((output) => { return output.result; }); } };