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.
cvm/src/wrappers/smartctl.js

158 lines
5.0 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"),
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;
});
}
};