feature/node-rewrite
Sven Slootweg 3 years ago
parent 98a4bcb6d8
commit f85ab90be1

@ -1,5 +1,6 @@
MARKER: MARKER:
- Move to pegjs-import - Move to pegjs-import
- Replace all 'this is a bug' errors with @joepie91/unreachable
- LVM / mdraid support and tabs (+ complete refactoring LVM implementation) - LVM / mdraid support and tabs (+ complete refactoring LVM implementation)
- Switch hashing to argon2id - Switch hashing to argon2id
- Switch child_process to execa - Switch child_process to execa

@ -14,16 +14,19 @@
"license": "WTFPL", "license": "WTFPL",
"dependencies": { "dependencies": {
"@babel/register": "^7.8.3", "@babel/register": "^7.8.3",
"@invisible/pegjs-import": "^1.1.1",
"@joepie91/express-react-views": "^1.0.1", "@joepie91/express-react-views": "^1.0.1",
"@joepie91/unreachable": "^1.0.0", "@joepie91/unreachable": "^1.0.0",
"@validatem/allow-extra-properties": "^0.1.0", "@validatem/allow-extra-properties": "^0.1.0",
"@validatem/anything": "^0.1.0", "@validatem/anything": "^0.1.0",
"@validatem/array-of": "^0.1.2", "@validatem/array-of": "^0.1.2",
"@validatem/core": "^0.3.15", "@validatem/core": "^0.3.15",
"@validatem/default-to": "^0.1.0",
"@validatem/dynamic": "^0.1.2", "@validatem/dynamic": "^0.1.2",
"@validatem/either": "^0.1.9", "@validatem/either": "^0.1.9",
"@validatem/error": "^1.1.0", "@validatem/error": "^1.1.0",
"@validatem/is-array": "^0.1.1", "@validatem/is-array": "^0.1.1",
"@validatem/is-boolean": "^0.1.1",
"@validatem/is-function": "^0.1.0", "@validatem/is-function": "^0.1.0",
"@validatem/is-number": "^0.1.3", "@validatem/is-number": "^0.1.3",
"@validatem/is-plain-object": "^0.1.1", "@validatem/is-plain-object": "^0.1.1",

@ -101,6 +101,9 @@ module.exports = function () {
// } // }
console.log(errorChain.getContext(sourceError));
res.render("error", { res.render("error", {
error: err error: err
}); });

@ -7,7 +7,7 @@ const util = require("util");
const execFileAsync = util.promisify(require("child_process").execFile); const execFileAsync = util.promisify(require("child_process").execFile);
const debug = require("debug")("cvm:execBinary"); const debug = require("debug")("cvm:execBinary");
const asExpression = require("as-expression"); const asExpression = require("as-expression");
const { rethrowAs } = require("error-chain"); const { rethrowAs, chain } = require("error-chain");
const textParser = require("../text-parser"); const textParser = require("../text-parser");
const errors = require("./errors"); const errors = require("./errors");
@ -194,7 +194,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
if (!this._settings.expectedExitCodes.includes(exitCode)) { if (!this._settings.expectedExitCodes.includes(exitCode)) {
// FIXME: Can we actually pass `error` to be chained onto here, when there's a case where `error` is undefined? Namely, when requiring a non-zero exit code, but the process exits with 0. // FIXME: Can we actually pass `error` to be chained onto here, when there's a case where `error` is undefined? Namely, when requiring a non-zero exit code, but the process exits with 0.
throw new errors.NonZeroExitCode.chain(error, `Expected exit code to be one of ${JSON.stringify(this._settings.expectedExitCodes)}, but got '${exitCode}'`, { throw chain(error, errors.NonZeroExitCode, `Expected exit code to be one of ${JSON.stringify(this._settings.expectedExitCodes)}, but got '${exitCode}'`, {
exitCode: exitCode, exitCode: exitCode,
stdout: stdout, stdout: stdout,
stderr: stderr stderr: stderr
@ -212,6 +212,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
return undefined; return undefined;
} }
} else { } else {
// FIXME: use @joepie91/unreachable
throw new Error(`Encountered expectation for unexpected channel '${expectation.channel}'; this is a bug, please report it`, { throw new Error(`Encountered expectation for unexpected channel '${expectation.channel}'; this is a bug, please report it`, {
failedChannel: expectation.channel failedChannel: expectation.channel
}); });
@ -230,7 +231,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
return undefined; return undefined;
} }
} else { } else {
throw errors.OutputParsingFailed.chain(error, `An error occurred while parsing '${expectation.channel}'`, { throw chain(error, errors.OutputParsingFailed, `An error occurred while parsing '${expectation.channel}'`, {
failedChannel: expectation.channel failedChannel: expectation.channel
}); });
} }
@ -270,7 +271,7 @@ module.exports = function createBinaryInvocation(command, args = []) {
? `Failed while processing ${error.failedChannel} of command` ? `Failed while processing ${error.failedChannel} of command`
: "Failed while processing result of command execution"; : "Failed while processing result of command execution";
throw errors.CommandExecutionFailed.chain(error, message, { throw chain(error, errors.CommandExecutionFailed, message, {
exitCode: exitCode, exitCode: exitCode,
stdout: stdout, stdout: stdout,
stderr: stderr stderr: stderr

@ -1,6 +1,7 @@
"use strict"; "use strict";
const Promise = require("bluebird"); const Promise = require("bluebird");
const { chain } = require("error-chain");
const execBinary = require("../exec-binary"); const execBinary = require("../exec-binary");
const parseIECBytes = require("../parse-bytes-iec"); const parseIECBytes = require("../parse-bytes-iec");
@ -120,11 +121,11 @@ module.exports = {
}).then((_output) => { }).then((_output) => {
return true; return true;
}).catch(hasFlag("deviceNotFound"), (error) => { }).catch(hasFlag("deviceNotFound"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${devicePath}' does not exist`, { throw chain(error, errors.InvalidPath, `Specified device '${devicePath}' does not exist`, {
path: devicePath path: devicePath
}); });
}).catch(hasFlag("partitionTableExists"), (error) => { }).catch(hasFlag("partitionTableExists"), (error) => {
throw errors.PartitionExists.chain(error, `Refused to create a Physical Volume, as a partition or partition table already exists on device '${devicePath}'`, { throw chain(error, errors.PartitionExists, `Refused to create a Physical Volume, as a partition or partition table already exists on device '${devicePath}'`, {
path: devicePath path: devicePath
}); });
}); });
@ -141,11 +142,11 @@ module.exports = {
}).then((_output) => { }).then((_output) => {
return true; return true;
}).catch(hasFlag("deviceNotFound"), (error) => { }).catch(hasFlag("deviceNotFound"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${devicePath}' does not exist`, { throw chain(error, errors.InvalidPath, `Specified device '${devicePath}' does not exist`, {
path: devicePath path: devicePath
}); });
}).catch(hasFlag("notAPhysicalVolume"), (error) => { }).catch(hasFlag("notAPhysicalVolume"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${devicePath}' is not a Physical Volume`, { throw chain(error, errors.InvalidPath, `Specified device '${devicePath}' is not a Physical Volume`, {
path: devicePath path: devicePath
}); });
}); });
@ -178,17 +179,17 @@ module.exports = {
}).catch(hasFlag("deviceNotFound"), (error) => { }).catch(hasFlag("deviceNotFound"), (error) => {
let failedDevices = error.getAllContext().result.deviceNotFound; let failedDevices = error.getAllContext().result.deviceNotFound;
throw errors.InvalidPath.chain(error, `The following specified devices do not exist: ${failedDevices.join(", ")}`, { throw chain(error, errors.InvalidPath, `The following specified devices do not exist: ${failedDevices.join(", ")}`, {
paths: failedDevices paths: failedDevices
}); });
}).catch(hasFlag("partitionTableExists"), (error) => { }).catch(hasFlag("partitionTableExists"), (error) => {
let failedDevices = error.getAllContext().result.partitionTableExists; let failedDevices = error.getAllContext().result.partitionTableExists;
throw errors.PartitionExists.chain(error, `Refused to create a Volume Group, as partitions or partition tables already exist on the following devices: ${failedDevices.join(", ")}`, { throw chain(error, errors.PartitionExists, `Refused to create a Volume Group, as partitions or partition tables already exist on the following devices: ${failedDevices.join(", ")}`, {
paths: failedDevices paths: failedDevices
}); });
}).catch(hasFlag("volumeGroupExists"), (error) => { }).catch(hasFlag("volumeGroupExists"), (error) => {
throw errors.VolumeGroupExists.chain(error, `A volume group with the name '${name}' already exists`, { throw chain(error, errors.VolumeGroupExists, `A volume group with the name '${name}' already exists`, {
volumeGroupName: name volumeGroupName: name
}); });
}).catch(hasFlag("physicalVolumeInUse"), (error) => { }).catch(hasFlag("physicalVolumeInUse"), (error) => {
@ -198,7 +199,7 @@ module.exports = {
return `${device} (${volumeGroup})`; return `${device} (${volumeGroup})`;
}).join(", "); }).join(", ");
throw errors.PhysicalVolumeInUse.chain(error, `The following specified Physical Volumes are already in use in another Volume Group: ${failedItemString}`, { throw chain(error, errors.PhysicalVolumeInUse, `The following specified Physical Volumes are already in use in another Volume Group: ${failedItemString}`, {
volumes: failedItems volumes: failedItems
}); });
}); });
@ -218,21 +219,21 @@ module.exports = {
}).then((_output) => { }).then((_output) => {
return true; return true;
}).catch(hasFlag("deviceNotFound"), (error) => { }).catch(hasFlag("deviceNotFound"), (error) => {
throw errors.InvalidPath.chain(error, `Specified device '${physicalVolume}' does not exist`, { throw chain(error, errors.InvalidPath, `Specified device '${physicalVolume}' does not exist`, {
path: physicalVolume path: physicalVolume
}); });
}).catch(hasFlag("volumeGroupNotFound"), (error) => { }).catch(hasFlag("volumeGroupNotFound"), (error) => {
throw errors.InvalidVolumeGroup.chain(error, `Specified Volume Group '${volumeGroup}' does not exist`, { throw chain(error, errors.InvalidVolumeGroup, `Specified Volume Group '${volumeGroup}' does not exist`, {
volumeGroupName: volumeGroup volumeGroupName: volumeGroup
}); });
}).catch(hasFlag("physicalVolumeInUse"), (error) => { }).catch(hasFlag("physicalVolumeInUse"), (error) => {
let volume = error.getAllContext().result.physicalVolumeInUse; let volume = error.getAllContext().result.physicalVolumeInUse;
throw errors.PhysicalVolumeInUse.chain(error, `Specified Physical Volume '${physicalVolume}' is already in use in another Volume Group (${volume.volumeGroup})`, { throw chain(error, errors.PhysicalVolumeInUse, `Specified Physical Volume '${physicalVolume}' is already in use in another Volume Group (${volume.volumeGroup})`, {
volume: volume volume: volume
}); });
}).catch(hasFlag("partitionTableExists"), (error) => { }).catch(hasFlag("partitionTableExists"), (error) => {
throw errors.PartitionExists.chain(error, `Refused to add device to Volume Group, as a partition or partition table already exists on device '${physicalVolume}'`, { throw chain(error, errors.PartitionExists, `Refused to add device to Volume Group, as a partition or partition table already exists on device '${physicalVolume}'`, {
path: physicalVolume path: physicalVolume
}); });
}); });

@ -8,22 +8,25 @@ const itemsToObject = require("../items-to-object");
/* FIXME: Error handling, eg. device not found errors */ /* FIXME: Error handling, eg. device not found errors */
function outputParser(rootRule) { function outputParser(parserPath) {
return createPegParser({ return createPegParser({
grammarFile: path.join(__dirname, "./parser.pegjs"), grammarFile: path.join(__dirname, parserPath)
options: {
allowedStartRules: [ rootRule ]
}
}); });
} }
let attributesParser = outputParser("./parsers/commands/attributes.pegjs");
let infoParser = outputParser("./parsers/commands/info.pegjs");
let scanParser = outputParser("./parsers/commands/scan.pegjs");
module.exports = { module.exports = {
attributes: function ({ devicePath }) { attributes: function ({ devicePath }) {
return Promise.try(() => { return Promise.try(() => {
return attributesParser;
}).then((parser) => {
return execBinary("smartctl", [devicePath]) return execBinary("smartctl", [devicePath])
.asRoot() .asRoot()
.withFlags({ attributes: true }) .withFlags({ attributes: true })
.requireOnStdout(outputParser("RootAttributes")) .requireOnStdout(parser)
.execute(); .execute();
}).then((output) => { }).then((output) => {
// NOTE: Ignore the header, for now // NOTE: Ignore the header, for now
@ -32,10 +35,12 @@ module.exports = {
}, },
info: function ({ devicePath }) { info: function ({ devicePath }) {
return Promise.try(() => { return Promise.try(() => {
return infoParser;
}).then((parser) => {
return execBinary("smartctl", [devicePath]) return execBinary("smartctl", [devicePath])
.asRoot() .asRoot()
.withFlags({ info: true }) .withFlags({ info: true })
.requireOnStdout(outputParser("RootInfo")) .requireOnStdout(parser)
.execute(); .execute();
}).then((output) => { }).then((output) => {
// NOTE: Ignore the header, for now // NOTE: Ignore the header, for now
@ -44,10 +49,12 @@ module.exports = {
}, },
scan: function () { scan: function () {
return Promise.try(() => { return Promise.try(() => {
return scanParser;
}).then((parser) => {
return execBinary("smartctl") return execBinary("smartctl")
.asRoot() .asRoot()
.withFlags({ scan: true }) .withFlags({ scan: true })
.requireOnStdout(outputParser("RootScan")) .requireOnStdout(parser)
.execute(); .execute();
}).then((output) => { }).then((output) => {
// NOTE: Ignore the header, for now // NOTE: Ignore the header, for now

@ -1,249 +0,0 @@
{
const matchValue = require("match-value");
const syncpipe = require("syncpipe");
const {B} = require("../unit-bytes-iec");
const mapAttributeFlags = require("./map-attribute-flags");
}
RootInfo
= header:Header infoSection:InfoSection Newline* {
return { ...header, fields: infoSection }
};
RootScan
= devices:ScanDevice* {
return { devices: devices };
}
RootAttributes
= header:Header attributesSection:AttributesSection Newline* {
return { ...header, attributes: attributesSection }
};
_
= (" " / "\t")*
RestOfLine
= content:$[^\n]+ Newline {
return content;
}
Newline
= "\n"
/ "\r\n"
Header 'header'
= "smartctl " versionString:RestOfLine "Copyright" copyrightStatement:RestOfLine Newline {
return { versionString, copyrightStatement };
}
BytesValue
= value:SeparatedNumberValue {
return B(value);
}
NumberValue
= value:$[0-9]+ {
return parseInt(value);
}
SeparatedNumberValue
= value:$[0-9,]+ {
return syncpipe(value, [
(_) => _.replace(/,/g, ""),
(_) => parseInt(_)
]);
}
HexNumberValue
= value:$[0-9A-Fa-f]+ {
return parseInt(value, 16);
}
IdentifierValue
= value:$[a-zA-Z_-]+ {
return value;
}
// smartctl --scan
ScanDevice 'scanned device'
= path:$[^ ]+ _ "-d" _ interface_:$[^ ]+ _ RestOfLine {
return { path: path, interface: interface_ };
}
// smartctl --info
InfoSection 'information section'
= "=== START OF INFORMATION SECTION ===" Newline fields:(InfoField+) {
return fields.filter((field) => field != null);
}
InfoField 'information field'
= InfoFieldSimple
/ InfoFieldIgnored
/ InfoFieldSize
/ InfoFieldRPM
/ InfoFieldSectorSizes
/ InfoFieldBoolean
/ InfoFieldUnknown
InfoFieldSimpleKey
= "Device Model" { return "model"; }
/ "Model Number" { return "model"; }
/ "Model Family" { return "modelFamily"; }
/ "Serial Number" { return "serialNumber"; }
/ "LU WWN Device Id" { return "wwn"; }
/ "Firmware Version" { return "firmwareVersion"; }
/ "Form Factor" { return "formFactor"; }
/ "ATA Version is" { return "ataVersion"; }
/ "SATA Version is" { return "sataVersion"; }
InfoFieldSimple
= key:InfoFieldSimpleKey ":" _ value:RestOfLine {
return { key: key, value: value };
}
InfoFieldUnknown
= key:$[^:]+ ":" _ RestOfLine {
console.warn(`Encountered unrecognized SMART info key: ${key}`);
return null;
}
InfoFieldIgnoredKey
= "Device is"
/ "Local Time is"
InfoFieldIgnored
= key:InfoFieldIgnoredKey ":" _ RestOfLine {
return null;
}
/ "SMART support is:" _ ("Available" / "Unavailable") RestOfLine {
// We don't actually care about this entry, but have to specify its possible values explicitly, to distinguish it from the entry we *do* care about that (annoyingly) uses the same key; see InfoFieldBoolean
return null;
}
InfoFieldSize
// NOTE: We don't actually care about the human-friendly display size after the 'bytes' specifier, hence the RestOfLine
= InfoFieldSizeKey _ value:SeparatedNumberValue _ "bytes"? _ RestOfLine {
return {
key: "size",
value: B(value)
};
}
InfoFieldSizeKey
= "User Capacity:"
/ "Total NVM Capacity:"
InfoFieldRPM
= "Rotation Rate:" _ value:NumberValue _ "rpm" Newline {
return {
key: "rpm",
value: value
};
}
InfoFieldSectorSizes
= "Sector Sizes:" _ logicalSize:BytesValue _ "bytes logical," _ physicalSize:BytesValue _ "bytes physical" Newline {
return {
key: "sectorSizes",
value: {
logical: logicalSize,
physical: physicalSize
}
};
}
InfoFieldBooleanKey
= "SMART support is" { return "smartEnabled"; }
InfoFieldBoolean
= key:InfoFieldBooleanKey ":" _ value:RestOfLine {
return {
key: key,
value: matchValue(value, {
Enabled: true,
Disabled: false
})
};
}
// smartctl --attributes
AttributesSection
= AttributesSectionSATA
/ AttributesSectionNVMe
AttributesSectionSATA
= "=== START OF READ SMART DATA SECTION ===" Newline
"SMART Attributes Data Structure revision number:" _ NumberValue Newline
"Vendor Specific SMART Attributes with Thresholds:" Newline
"ID#" _ "ATTRIBUTE_NAME" _ "FLAG" _ "VALUE" _ "WORST" _ "THRESH" _ "TYPE" _ "UPDATED" _ "WHEN_FAILED" _ "RAW_VALUE" Newline
attributes:AttributeFieldSATA+ {
return attributes;
}
AttributesSectionNVMe
= "=== START OF SMART DATA SECTION ===" Newline
"SMART/Health Information (NVMe Log 0x02)" Newline
attributes:AttributeFieldNVMe+ {
return attributes;
}
AttributeFlags
= "0x" number:HexNumberValue {
return mapAttributeFlags(number);
}
AttributeUpdatedWhen
= "Always"
/ "Offline"
AttributeFailedWhen
= "FAILING_NOW"
/ "In_the_past"
/ "-"
AttributeFieldType
= "Pre-fail"
/ "Old_age"
AttributeFieldSATA
= _ id:NumberValue
_ attributeName:IdentifierValue
_ flags:AttributeFlags
_ value:NumberValue
_ worstValue:NumberValue
_ threshold:NumberValue
_ type:AttributeFieldType
_ updatedWhen:AttributeUpdatedWhen
_ failedWhen:AttributeFailedWhen
_ rawValue:RestOfLine {
return {
id,
attributeName,
flags,
value,
worstValue,
threshold,
rawValue,
updatedWhen: matchValue(updatedWhen, {
"Always": "always",
"Offline": "offline"
}),
type: matchValue(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")
};
}
AttributeFieldNVMe
= label:$[^:]+ ":" _ value:RestOfLine {
return { label: label, value };
}

@ -0,0 +1,88 @@
import { _, RestOfLine, Newline, NumberValue, HexNumberValue, IdentifierValue } from "../primitives"
import { Header } from "../shared"
{
const matchValue = require("match-value");
const mapAttributeFlags = require("../../map-attribute-flags");
}
RootAttributes
= header:Header attributesSection:AttributesSection Newline* {
return { ...header, attributes: attributesSection }
};
AttributesSection
= AttributesSectionSATA
/ AttributesSectionNVMe
AttributesSectionSATA
= "=== START OF READ SMART DATA SECTION ===" Newline
"SMART Attributes Data Structure revision number:" _ NumberValue Newline
"Vendor Specific SMART Attributes with Thresholds:" Newline
"ID#" _ "ATTRIBUTE_NAME" _ "FLAG" _ "VALUE" _ "WORST" _ "THRESH" _ "TYPE" _ "UPDATED" _ "WHEN_FAILED" _ "RAW_VALUE" Newline
attributes:AttributeFieldSATA+ {
return attributes;
}
AttributesSectionNVMe
= "=== START OF SMART DATA SECTION ===" Newline
"SMART/Health Information (NVMe Log 0x02)" Newline
attributes:AttributeFieldNVMe+ {
return attributes;
}
AttributeFlags
= "0x" number:HexNumberValue {
return mapAttributeFlags(number);
}
AttributeUpdatedWhen
= "Always"
/ "Offline"
AttributeFailedWhen
= "FAILING_NOW"
/ "In_the_past"
/ "-"
AttributeFieldType
= "Pre-fail"
/ "Old_age"
AttributeFieldSATA
= _ id:NumberValue
_ attributeName:IdentifierValue
_ flags:AttributeFlags
_ value:NumberValue
_ worstValue:NumberValue
_ threshold:NumberValue
_ type:AttributeFieldType
_ updatedWhen:AttributeUpdatedWhen
_ failedWhen:AttributeFailedWhen
_ rawValue:RestOfLine {
return {
id,
attributeName,
flags,
value,
worstValue,
threshold,
rawValue,
updatedWhen: matchValue(updatedWhen, {
"Always": "always",
"Offline": "offline"
}),
type: matchValue(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")
};
}
AttributeFieldNVMe
= label:$[^:]+ ":" _ value:RestOfLine {
return { label: label, value };
}

@ -0,0 +1,106 @@
import { _, RestOfLine, Newline, NumberValue, SeparatedNumberValue, BytesValue } from "../primitives"
import { Header } from "../shared"
{
const matchValue = require("match-value");
}
RootInfo
= header:Header infoSection:InfoSection Newline* {
return { ...header, fields: infoSection }
};
InfoSection 'information section'
= "=== START OF INFORMATION SECTION ===" Newline fields:(InfoField+) {
return fields.filter((field) => field != null);
}
InfoField 'information field'
= InfoFieldSimple
/ InfoFieldIgnored
/ InfoFieldSize
/ InfoFieldRPM
/ InfoFieldSectorSizes
/ InfoFieldBoolean
/ InfoFieldUnknown
InfoFieldSimpleKey
= "Device Model" { return "model"; }
/ "Model Number" { return "model"; }
/ "Model Family" { return "modelFamily"; }
/ "Serial Number" { return "serialNumber"; }
/ "LU WWN Device Id" { return "wwn"; }
/ "Firmware Version" { return "firmwareVersion"; }
/ "Form Factor" { return "formFactor"; }
/ "ATA Version is" { return "ataVersion"; }
/ "SATA Version is" { return "sataVersion"; }
InfoFieldSimple
= key:InfoFieldSimpleKey ":" _ value:RestOfLine {
return { key: key, value: value };
}
InfoFieldUnknown
= key:$[^:]+ ":" _ RestOfLine {
console.warn(`Encountered unrecognized SMART info key: ${key}`);
return null;
}
InfoFieldIgnoredKey
= "Device is"
/ "Local Time is"
InfoFieldIgnored
= key:InfoFieldIgnoredKey ":" _ RestOfLine {
return null;
}
/ "SMART support is:" _ ("Available" / "Unavailable") RestOfLine {
// We don't actually care about this entry, but have to specify its possible values explicitly, to distinguish it from the entry we *do* care about that (annoyingly) uses the same key; see InfoFieldBoolean
return null;
}
InfoFieldSize
// NOTE: We don't actually care about the human-friendly display size after the 'bytes' specifier, hence the RestOfLine
= InfoFieldSizeKey _ value:SeparatedNumberValue _ "bytes"? _ RestOfLine {
return {
key: "size",
value: B(value)
};
}
InfoFieldSizeKey
= "User Capacity:"
/ "Total NVM Capacity:"
InfoFieldRPM
= "Rotation Rate:" _ value:NumberValue _ "rpm" Newline {
return {
key: "rpm",
value: value
};
}
InfoFieldSectorSizes
= "Sector Sizes:" _ logicalSize:BytesValue _ "bytes logical," _ physicalSize:BytesValue _ "bytes physical" Newline {
return {
key: "sectorSizes",
value: {
logical: logicalSize,
physical: physicalSize
}
};
}
InfoFieldBooleanKey
= "SMART support is" { return "smartEnabled"; }
InfoFieldBoolean
= key:InfoFieldBooleanKey ":" _ value:RestOfLine {
return {
key: key,
value: matchValue(value, {
Enabled: true,
Disabled: false
})
};
}

@ -0,0 +1,11 @@
import { _, RestOfLine } from "../primitives"
RootScan
= devices:ScanDevice* {
return { devices: devices };
}
ScanDevice 'scanned device'
= path:$[^ ]+ _ "-d" _ interface_:$[^ ]+ _ RestOfLine {
return { path: path, interface: interface_ };
}

@ -0,0 +1,44 @@
{
const syncpipe = require("syncpipe");
const {B} = require("../../unit-bytes-iec");
}
_
= (" " / "\t")*
RestOfLine
= content:$[^\n]+ Newline {
return content;
}
Newline
= "\n"
/ "\r\n"
BytesValue
= value:SeparatedNumberValue {
return B(value);
}
NumberValue
= value:$[0-9]+ {
return parseInt(value);
}
SeparatedNumberValue
= value:$[0-9,]+ {
return syncpipe(value, [
(_) => _.replace(/,/g, ""),
(_) => parseInt(_)
]);
}
HexNumberValue
= value:$[0-9A-Fa-f]+ {
return parseInt(value, 16);
}
IdentifierValue
= value:$[a-zA-Z_-]+ {
return value;
}

@ -0,0 +1,6 @@
import { RestOfLine, Newline } from "./primitives"
Header 'header'
= "smartctl " versionString:RestOfLine "Copyright" copyrightStatement:RestOfLine Newline {
return { versionString, copyrightStatement };
}

@ -1,10 +1,11 @@
"use strict"; "use strict";
const pegjs = require("pegjs"); const Promise = require("bluebird");
const fs = require("fs"); const pegRedux = require("peg-redux");
const moduleEval = require("eval"); const moduleEval = require("eval");
const vm = require("vm"); const vm = require("vm");
const asExpression = require("as-expression"); const asExpression = require("as-expression");
const { chain } = require("error-chain");
const textParser = require("../text-parser"); const textParser = require("../text-parser");
const { validateOptions } = require("@validatem/core"); const { validateOptions } = require("@validatem/core");
@ -12,8 +13,8 @@ const isString = require("@validatem/is-string");
const isPlainObject = require("@validatem/is-plain-object"); const isPlainObject = require("@validatem/is-plain-object");
const requireEither = require("@validatem/require-either"); const requireEither = require("@validatem/require-either");
module.exports = function createPegParser({ grammar, grammarFile, options }) { module.exports = function createPegParser(_options) {
validateOptions(arguments, [ let { grammar, grammarFile, options } = validateOptions(arguments, [
{ {
grammar: [ isString ], grammar: [ isString ],
grammarFile: [ isString ], grammarFile: [ isString ],
@ -21,52 +22,53 @@ module.exports = function createPegParser({ grammar, grammarFile, options }) {
}, requireEither([ "grammar", "grammarFile" ]) }, requireEither([ "grammar", "grammarFile" ])
]); ]);
if (grammarFile != null) { let parserOptions = {
// FIXME: cache
grammar = fs.readFileSync(grammarFile, "utf8");
}
let parserCode = pegjs.generate(grammar, {
... options, ... options,
output: "source", output: "source",
format: "commonjs" format: "commonjs"
}); };
let parser = asExpression(() => {
if (grammarFile != null) {
return moduleEval(parserCode, grammarFile, {}, true);
} else {
let exports_ = {};
let sandbox = { return Promise.try(() => {
exports: exports_, return (grammarFile != null)
module: { ? pegRedux.generateFromFile(grammarFile, parserOptions)
: pegRedux.generate(grammar, parserOptions);
}).then((parserCode) => {
let parser = asExpression(() => {
if (grammarFile != null) {
return moduleEval(parserCode, grammarFile, {}, true);
} else {
let exports_ = {};
let sandbox = {
exports: exports_, exports: exports_,
}, module: {
require: function () { exports: exports_,
throw new Error("You cannot use require() when loading a grammar as a string; use the `grammarFile` option instead"); },
} require: function () {
}; throw new Error("You cannot use require() when loading a grammar as a string; use the `grammarFile` option instead");
}
let script = new vm.Script(parserCode.replace(/^\#\!.*/, '')); };
script.runInNewContext(sandbox);
let script = new vm.Script(parserCode.replace(/^\#\!.*/, ''));
return sandbox.module.exports; script.runInNewContext(sandbox);
}
});
return { return sandbox.module.exports;
supportsStreams: false, }
parse: function (text) { });
try {
return parser.parse(text); return {
} catch (error) { supportsStreams: false,
if (error.name === "SyntaxError") { parse: function (text) {
throw textParser.NoResult.chain(error, "Parsing output failed"); try {
} else { return parser.parse(text);
throw error; } catch (error) {
if (error.name === "SyntaxError") {
throw chain(error, textParser.NoResult, "Parsing output failed");
} else {
throw error;
}
} }
} }
} };
}; });
}; };

@ -26,11 +26,11 @@ return Promise.try(() => {
// return lvm.addVolumeToVolumeGroup({ volumeGroup: "vg-name", physicalVolume: "/dev/loop1" }); // return lvm.addVolumeToVolumeGroup({ volumeGroup: "vg-name", physicalVolume: "/dev/loop1" });
// return lvm.destroyPhysicalVolume({ devicePath: "/dev/loop0" }); // return lvm.destroyPhysicalVolume({ devicePath: "/dev/loop0" });
// return lsblk(); // return lsblk();
// return smartctl.scan(); return smartctl.scan();
// return smartctl.info({ devicePath: "/dev/sda" }) // return smartctl.info({ devicePath: "/dev/sda" })
// return smartctl.info({ devicePath: process.argv[2] }) // return smartctl.info({ devicePath: process.argv[2] })
// return smartctl.attributes({ devicePath: process.argv[2] }); // return smartctl.attributes({ devicePath: process.argv[2] });
return findmnt(); // return findmnt();
// return nvmeCli.listNamespaces({ devicePath: "/dev/nvme0" }); // return nvmeCli.listNamespaces({ devicePath: "/dev/nvme0" });
}).then((result) => { }).then((result) => {
console.log(util.inspect(result, {colors: true, depth: null})); console.log(util.inspect(result, {colors: true, depth: null}));

@ -949,6 +949,13 @@
lodash "^4.17.19" lodash "^4.17.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@invisible/pegjs-import@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@invisible/pegjs-import/-/pegjs-import-1.1.1.tgz#1c5feb6fd768604cadd63efc9dfb999e8b0a0c7f"
integrity sha512-TiUoDxO08miDb6EQaWQeuBtkPPAsOpw55HBCbBN+EtIXy7URT1fwWNt/5k/k0pI+U58FnnGSj361oRSPATuSmw==
dependencies:
pegjs "^0.10.0"
"@joepie91/eslint-config@^1.1.0": "@joepie91/eslint-config@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@joepie91/eslint-config/-/eslint-config-1.1.0.tgz#9397e6ce0a010cb57dcf8aef8754d3a5ce0ae36a" resolved "https://registry.yarnpkg.com/@joepie91/eslint-config/-/eslint-config-1.1.0.tgz#9397e6ce0a010cb57dcf8aef8754d3a5ce0ae36a"

Loading…
Cancel
Save