You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.1 KiB
JavaScript
159 lines
5.1 KiB
JavaScript
"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"),
|
|
/* TODO: Should the below include the FAILING_NOW state? */
|
|
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;
|
|
});
|
|
}
|
|
}; |