You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
5.3 KiB
JavaScript

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