"use strict"; const DataLoader = require("dataloader"); const fs = require("fs").promises; const matchValue = require("match-value"); const memoizee = require("memoizee"); const unreachable = require("@joepie91/unreachable")("@sysquery/block-devices"); const dlayerSource = require("../dlayer-source"); const All = require("../graphql-interface/symbols/all"); const lsblk = require("../exec-lsblk"); const mapFromSource = require("../map-from-source"); const treeMapAsync = require("../tree-map-async"); const treeFind = require("../tree-find"); function makePredicate({ path, name }) { if (path != null) { return (device) => device.path === path; } else if (name != null) { return (device) => device.name === name; } else { unreachable("No selector specified for lsblk"); } } module.exports = { name: "sysquery.blockDevice", makeContext: function () { let lsblkOnce = memoizee(async () => { return treeMapAsync(await lsblk(), async (device) => { return { ... device, path: await fs.realpath(device.path) }; }); }); return { lsblk: new DataLoader(async function (selectors) { let blockDeviceTree = await lsblkOnce(); return selectors.map((selector) => { if (selector === All) { return blockDeviceTree; } else { return treeFind(blockDeviceTree, makePredicate(selector)); } }); }) }; }, types: { "sysquery.blockDevices.BlockDevice": function ({ name, path }) { return { ... dlayerSource("lsblk", { [dlayerSource.ID]: { name, path }, name: "name", path: (device) => fs.realpath(device.path), type: (device) => matchValue(device.type, { partition: "PARTITION", disk: "DISK", loopDevice: "LOOP_DEVICE" }), size: "size", mountpoint: "mountpoint", // FIXME: Isn't this obsoleted by `mounts`? deviceNumber: "deviceNumber", removable: "removable", readOnly: "readOnly", children: (device, { $make }) => device.children.map((child) => { return $make("sysquery.blockDevices.BlockDevice", { name: child.name }); }) }) }; } }, extensions: { "sysquery.mounts.Mount": { sourceDevice: async function (_, { lsblk, $make, $getProperty }) { let sourceDevicePath = await $getProperty(this, "sourceDevicePath"); if (sourceDevicePath == null) { // This occurs when the mount is not backed by a device, eg. an sshfs FUSE mount return null; } else { let realSourcePath = await fs.realpath(sourceDevicePath); if (await lsblk.load({ path: realSourcePath }) != null) { return $make("sysquery.blockDevices.BlockDevice", { path: realSourcePath }); } else { // This occurs when the `sourceDevice` is a valid device, but it is not a *block* device, eg. `/dev/fuse` return null; } } } } }, root: { resources: { blockDevices: ({ names, paths }, { lsblk, $make }) => { // TODO: Design better abstraction for this sort of case let selectors = (names == null && paths == null) ? null : [ ... (names ?? []).map((name) => ({ name: name })), ... (paths ?? []).map((path) => ({ path: path })) ]; return mapFromSource(lsblk, selectors, (device) => { return $make("sysquery.blockDevices.BlockDevice", { name: device.name }); }); } } } };