{ 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 }; }