"use strict"; const fs = require("fs").promises; const memoizee = require("memoizee"); const DataLoader = require("dataloader"); const findmnt = require("../exec-findmnt"); const dlayerSource = require("../dlayer-source"); const treeFilterFlatAsync = require("../tree-filter-flat-async"); const treeMapAsync = require("../tree-map-async"); const treeFind = require("../tree-find"); const All = require("../graphql-interface/symbols/all"); module.exports = { name: "sysquery.mounts", makeContext: function () { let findmntOnce = memoizee(async () => { return treeMapAsync(await findmnt(), async (mount) => { return { ... mount, sourceDevice: (mount.sourceDevice?.startsWith("/")) ? await fs.realpath(mount.sourceDevice) : mount.sourceDevice }; }); }); return { findmnt: new DataLoader(async function (mountpoints) { let mounts = await findmntOnce(); // TODO: It's kind of strange that it sometimes returns a tree and sometimes a list, this can probably be improved? let matches = mountpoints.map((mountpoint) => { if (mountpoint === All) { return mounts; } else { return treeFind(mounts, (mount) => mount.mountpoint === mountpoint); } }); return matches; }) }; }, types: { "sysquery.mounts.Mount": function ({ mountpoint }) { return { mountpoint: mountpoint, ... dlayerSource("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", sourceDevicePath: "sourceDevice", totalSpace: "totalSpace", freeSpace: "freeSpace", usedSpace: "usedSpace", rootPath: "rootPath", taskID: "taskID", optionalFields: "optionalFields", propagationFlags: "propagationFlags", children: (mount, { $make }) => mount.children.map((child) => { return $make("sysquery.mounts.Mount", { mountpoint: child.mountpoint }); }) }) }; }, }, extensions: { "sysquery.blockDevices.BlockDevice": { // 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.mounts.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 === await $getProperty(this, "path")) || (sourceName != null && sourceName === await $getProperty(this, "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.mounts.Mount", { mountpoint: mount.mountpoint }); }); } } }, root: {} // FIXME: Expose root mounts endpoint };