"use strict"; const fs = require("fs").promises; const matchValue = require("match-value"); const DataLoader = require("dataloader"); const mapFromSource = require("../map-from-source"); const dlayerSource = require("../dlayer-source"); const All = require("../graphql-interface/symbols/all"); const treeFilterFlatAsync = require("../tree-filter-flat-async"); const assert = require("assert"); // FIXME: Simplify findmnt/lsblk source definitions, and probably separate out mounts and block devices into their own separate modules module.exports = { name: "sysquery.core", makeContext: function () { // MARKER: Complete sources migration, test, move smartctl return { lsblk: new DataLoader(require("./lsblk")()), findmnt: new DataLoader(require("./findmnt")()) }; }, types: { "sysquery.core.Mount": function ({ mountpoint }) { return dlayerSource.withSources({ mountpoint: mountpoint, sourceDevice: async (_, { lsblk, findmnt, $make }) => { let mount = await findmnt.load(mountpoint); if (mount == null) { // TODO: Can this ever happen for any legitimate reason? throw new Error(`Mountpoint '${mountpoint}' not found in findmnt output`); } else if (mount.sourceDevice == null) { // This occurs when the mount is not backed by a device, eg. an sshfs FUSE mount return null; } else { let sourcePath = await fs.realpath(mount.sourceDevice); if (await lsblk.load({ path: sourcePath }) != null) { return $make("sysquery.core.BlockDevice", { path: sourcePath }); } else { // This occurs when the `sourceDevice` is a valid device, but it is not a *block* device, eg. `/dev/fuse` return null; } } }, $sources: { findmnt: { [dlayerSource.ID]: mountpoint, id: "id", // FIXME: Aren't we inferring the below somewhere else in the code, using the square brackets? type: (mount) => (mount.rootPath === "/") ? "ROOT_MOUNT" : "SUBMOUNT", filesystem: "filesystem", options: "options", label: "label", uuid: "uuid", partitionLabel: "partitionLabel", partitionUUID: "partitionUUID", deviceNumber: "deviceNumber", totalSpace: "totalSpace", freeSpace: "freeSpace", usedSpace: "usedSpace", rootPath: "rootPath", taskID: "taskID", optionalFields: "optionalFields", propagationFlags: "propagationFlags", children: (mount, { $make }) => mount.children.map((child) => { return $make("sysquery.core.Mount", { mountpoint: child.mountpoint }); }) } } }); }, "sysquery.core.BlockDevice": function ({ name, path }) { return dlayerSource.withSources({ // TODO: Eventually make this produce a (filtered) tree instead? mounts: async function ({ type }, { findmnt, $make, $getProperty, $getPropertyPath }) { let mountTree = await findmnt.load(All); let relevantMounts = await treeFilterFlatAsync(mountTree, async (mount) => { let mountObject = $make("sysquery.core.Mount", { mountpoint: mount.mountpoint }); // console.log({ sourceDevice: await $getProperty(mountObject, "sourceDevice") }); let sourcePath = await $getPropertyPath(mountObject, "sourceDevice.path"); let sourceName = await $getPropertyPath(mountObject, "sourceDevice.name"); // TODO: This logic looks strange. Is it actually correct, even when only one of name/path is specified upon BlockDevice construction? let matchesDevice = ( (sourcePath != null && sourcePath === path) || (sourceName != null && sourceName === name) ); let matchesType = ( type == null || await $getProperty(mountObject, "type" === type) ); return matchesDevice && matchesType; }, { recurseFilteredSubtrees: true }); // This is a bit hacky; this approach should probably be replaced by a map-filter instead. But for now, this will do - as this all happens in the same request context, there's no real penalty to re-creating the mount objects a second time. return relevantMounts.map((mount) => { return $make("sysquery.core.Mount", { mountpoint: mount.mountpoint }); }); }, $sources: { 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.core.BlockDevice", { name: child.name }); }) } } }); } }, extensions: { // None }, 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.core.BlockDevice", { name: device.name }); }); } // FIXME: Add mounts } } };