Vydané zdroje
Stáhnout zdroje ZIP
CH-J Tools
Procházejte adresáře a soubory konkrétního vydání aplikace.
CH-J / Server Manager
406 souborů
core-20261024T135205Z-7c487d719de2
Přidáno: · Sestaveno:
1
"use strict";3
// Builds the chj.disks model from the output of remoteDisks.sh "inspect". The device graph comes from sysfs4
// (holders/slaves), so layered stacks such as disk -> partition -> LUKS -> LVM PV -> VG -> LV -> ext4 are5
// represented without assuming that a partition directly contains a file system. lsblk/blkid/sfdisk/LVM JSON only6
// enrich nodes; without them (BusyBox, no root) the model is marked partial instead of guessing.7
const text = (value, limit = 200) => String(value ?? "").replace(/[\x00-\x1f\x7f]/g, " ").trim().slice(0, limit);8
const int = (value) => { const result = Number(value); return value === "" || value === null || value === undefined || !Number.isFinite(result) ? null : result; };9
const lines = (value) => String(value || "").split(/\r?\n/).filter(Boolean);10
const MiB = 1048576;12
const GPT_TYPES = Object.freeze({13
"0FC63DAF-8483-4772-8E79-3D69D8477DE4": "linux",14
"4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709": "linux-root-x86-64",15
"B921B045-1DF0-41C3-AF44-4C6F280D3FAE": "linux-root-arm64",16
"BC13C2FF-59E6-4262-A352-B275FD6F7172": "linux-extended-boot",17
"933AC7E1-2EB4-4F13-B844-0E14E2AEF915": "linux-home",18
"3B8F8425-20E0-4F3B-907F-1A25A76F98E8": "linux-server-data",19
"4D21B016-B534-45C2-A9FB-5C16E091FD2D": "linux-var",20
"0657FD6D-A4AB-43C4-84E5-0933C84B4F4F": "swap",21
"E6D6D379-F507-44C2-A23C-238F2A3DF928": "lvm",22
"A19D880F-05FC-4D3B-A006-743F0F84911E": "raid",23
"CA7D7CCB-63ED-4C53-861C-1742536059CC": "luks",24
"C12A7328-F81F-11D2-BA4B-00A0C93EC93B": "esp",25
"21686148-6449-6E6F-744E-656564454649": "bios-boot",26
"EBD0A0A2-B9E5-4433-87C0-68B6B72699C7": "basic-data",27
"E3C9E316-0B5C-4DB8-817D-F92DF00215AE": "microsoft-reserved",28
"DE94BBA4-06D1-4D40-A16A-BFD50179D6AC": "windows-recovery",29
"6A898CC3-1DD2-11B2-99A6-080020736631": "zfs",30
"516E7CBA-6ECF-11D6-8FF8-00022D09712B": "freebsd-zfs"31
});32
const DOS_TYPES = Object.freeze({ "83": "linux", "82": "swap", "8e": "lvm", "fd": "raid", "ef": "esp", "7": "basic-data", "b": "fat32", "c": "fat32", "e": "fat16", "5": "extended", "f": "extended", "85": "extended", "ee": "gpt-protective" });33
// Partition types CH-J writes (GPT GUID / MBR type) for new partitions and type flags.34
const PARTITION_TYPE_CODES = Object.freeze({35
linux: { gpt: "0FC63DAF-8483-4772-8E79-3D69D8477DE4", dos: "83" },36
swap: { gpt: "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", dos: "82" },37
lvm: { gpt: "E6D6D379-F507-44C2-A23C-238F2A3DF928", dos: "8e" },38
raid: { gpt: "A19D880F-05FC-4D3B-A006-743F0F84911E", dos: "fd" },39
esp: { gpt: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", dos: "ef" },40
"bios-boot": { gpt: "21686148-6449-6E6F-744E-656564454649", dos: null },41
"basic-data": { gpt: "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", dos: "7" },42
fat32: { gpt: "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", dos: "c" }43
});44
const SYSTEM_MOUNTS = /^(\/|\/boot(\/.*)?|\/efi|\/usr(\/.*)?|\/var(\/.*)?|\/home|\/opt|\/srv|\/root|\/tmp|\/nix|\/snap(\/.*)?)$/;45
const VIRTUAL_MODEL = /QEMU|VBOX|VirtualBox|VMware|Virtual disk|Virtual HD|Msft Virtual|Xen|Hyper-V|BHYVE|Red Hat VirtIO/i;47
function splitSections(stdout) {48
const sections = {};49
const pattern = /\x1eCHJ_SECTION=([a-z]+)\r?\n/g;50
let current = null; let start = 0; let match;51
while ((match = pattern.exec(stdout))) {52
if (current) sections[current] = stdout.slice(start, match.index);53
current = match[1]; start = pattern.lastIndex;54
}55
if (current) sections[current] = stdout.slice(start);56
return sections;57
}58
const keyValues = (value) => Object.fromEntries(lines(value).map((line) => [line.slice(0, line.indexOf("=")), line.slice(line.indexOf("=") + 1)]).filter(([key]) => key));59
const parseJson = (value) => { try { return JSON.parse(value); } catch { return null; } };60
const unescapeMount = (value) => String(value).replace(/\\(\d{3})/g, (_m, octal) => String.fromCharCode(Number.parseInt(octal, 8)));62
function parseSysblock(section) {63
return lines(section).map((line) => {64
const f = line.split("|");65
return {66
kname: text(f[0], 64), devno: text(f[1], 16), sectors512: int(f[2]), readOnly: f[3] === "1", removable: f[4] === "1",67
partition: f[5] === "-" ? null : int(f[5]), start512: f[6] === "-" ? null : int(f[6]), parent: f[7] === "-" ? null : text(f[7], 64),68
rotational: f[8] === "" ? null : f[8] === "1", logicalSectorSize: int(f[9]) || 512, physicalSectorSize: int(f[10]) || 512,69
model: text(f[11], 80), vendor: text(f[12], 40), serial: text(f[13], 80), wwid: text(f[14], 120),70
holders: String(f[15] || "").split(",").filter(Boolean).map((name) => text(name, 64)),71
slaves: String(f[16] || "").split(",").filter(Boolean).map((name) => text(name, 64)),72
dmName: text(f[17], 128), dmUuid: text(f[18], 160), loopBacking: text(f[19], 200), mdLevel: text(f[20], 16),73
sysPath: text(f[21], 200), discardMax: int(f[22])74
};75
}).filter((entry) => /^[A-Za-z0-9._:+-]{1,64}$/.test(entry.kname));76
}78
function parseBlkid(section) {79
const result = new Map();80
for (const block of String(section || "").split(/\n\s*\n/)) {81
const values = keyValues(block);82
if (values.DEVNAME) result.set(values.DEVNAME, values);83
}84
return result;85
}87
function parseMountinfo(section) {88
return lines(section).map((line) => {89
const [left, right = ""] = line.split(" - ");90
const fields = left.split(" "); const tail = right.split(" ");91
return { devno: fields[2], root: unescapeMount(fields[3] || ""), mountpoint: unescapeMount(fields[4] || ""), options: fields[5] || "", fstype: tail[0] || "", source: unescapeMount(tail[1] || "") };92
}).filter((entry) => entry.mountpoint);93
}95
function parseDf(section) {96
const result = new Map();97
for (const line of lines(section).slice(1)) {98
const match = line.match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(.+)$/);99
if (match) result.set(match[6], { size: Number(match[2]) * 1024, used: Number(match[3]) * 1024, available: Number(match[4]) * 1024 });100
}101
return result;102
}104
function parseSwaps(section) {105
return lines(section).slice(1).map((line) => {106
const [name, type, size, used, priority] = line.trim().split(/\s+/);107
return { path: unescapeMount(name), type, size: Number(size) * 1024, used: Number(used) * 1024, priority: Number(priority) };108
});109
}111
function parsePartitionTables(section) {112
const tables = new Map();113
const pattern = /@@disk (\S+) (\S+)\n([\s\S]*?)\n@@end/g;114
let match;115
while ((match = pattern.exec(String(section || "")))) {116
const json = parseJson(match[3]);117
if (json?.partitiontable) tables.set(match[1], { ...json.partitiontable, hash: /^[a-f0-9]{64}$/.test(match[2]) ? match[2] : null });118
}119
return tables;120
}122
function parseLvm(section) {123
const parts = {};124
for (const block of String(section || "").split(/^@@/m).filter(Boolean)) {125
const name = block.slice(0, block.indexOf("\n")).trim();126
parts[name] = parseJson(block.slice(block.indexOf("\n") + 1))?.report?.[0] || {};127
}128
const n = (value) => int(String(value ?? "").trim());129
return {130
available: Boolean(section),131
pvs: (parts.pvs?.pv || []).map((pv) => ({ path: text(pv.pv_name), vg: text(pv.vg_name, 128), size: n(pv.pv_size), free: n(pv.pv_free), deviceSize: n(pv.dev_size), uuid: text(pv.pv_uuid, 64), attr: text(pv.pv_attr, 16) })),132
vgs: (parts.vgs?.vg || []).map((vg) => ({ name: text(vg.vg_name, 128), uuid: text(vg.vg_uuid, 64), size: n(vg.vg_size), free: n(vg.vg_free), extentSize: n(vg.vg_extent_size), freeExtents: n(vg.vg_free_count), pvCount: n(vg.pv_count), lvCount: n(vg.lv_count), attr: text(vg.vg_attr, 16) })),133
lvs: (parts.lvs?.lv || []).filter((lv) => !String(lv.lv_name).startsWith("[")).map((lv) => {134
const attr = text(lv.lv_attr, 16);135
return {136
name: text(lv.lv_name, 128), vg: text(lv.vg_name, 128), uuid: text(lv.lv_uuid, 64), size: n(lv.lv_size), attr, path: text(lv.lv_path), dmPath: text(lv.lv_dm_path),137
pool: text(lv.pool_lv, 128), dataPercent: n(lv.data_percent), metadataPercent: n(lv.metadata_percent), segtype: text(lv.segtype, 32), origin: text(lv.origin, 128),138
// lv_attr[0]: t = thin pool, V = thin volume, s/S = snapshot, o = origin; lv_attr[4]: a = active.139
thinPool: attr[0] === "t", thinVolume: attr[0] === "V", snapshot: /^[sS]/.test(attr), active: attr[4] === "a",140
pveGuest: (String(lv.lv_name).match(/^(?:vm|base)-(\d+)-(?:disk|cloudinit|state|tpmstate)/) || [])[1] || null141
};142
})143
};144
}146
function parseCrypt(section) {147
const result = new Map();148
for (const block of String(section || "").split(/^@@map /m).filter(Boolean)) {149
const name = block.slice(0, block.indexOf("\n")).trim();150
const values = {};151
for (const line of lines(block.slice(block.indexOf("\n") + 1))) {152
const index = line.indexOf(":");153
if (index > 0) values[line.slice(0, index).trim().toLowerCase()] = text(line.slice(index + 1), 120);154
}155
result.set(name, { type: values.type || null, cipher: values.cipher || null, keySize: values.keysize || null, keyLocation: values["key location"] || null, device: values.device || null, mode: values.mode || null, sectorSize: int(values["sector size"]) });156
}157
return result;158
}160
function parseFsinfo(section) {161
const result = new Map();162
for (const block of String(section || "").split(/^@@fs /m).filter(Boolean)) {163
const [device, type] = block.slice(0, block.indexOf("\n")).trim().split(/\s+/);164
const body = block.slice(block.indexOf("\n") + 1);165
if (/^ext[234]$/.test(type)) {166
const value = (key) => int((body.match(new RegExp(`^${key}:\\s*(\\d+)`, "m")) || [])[1]);167
const blocks = value("Block count"); const blockSize = value("Block size"); const free = value("Free blocks");168
if (blocks && blockSize) result.set(device, { fsSize: blocks * blockSize, fsUsed: free === null ? null : (blocks - free) * blockSize, blockSize, state: text((body.match(/^Filesystem state:\s*(.+)$/m) || [])[1], 40) });169
} else if (type === "xfs") {170
const data = body.match(/^data\s*=.*?bsize=(\d+)\s+blocks=(\d+)/m);171
if (data) result.set(device, { fsSize: Number(data[1]) * Number(data[2]), blockSize: Number(data[1]) });172
}173
}174
return result;175
}177
function parseBtrfs(section) {178
const filesystems = [];179
let current = null;180
for (const line of lines(section)) {181
const label = line.match(/^Label:\s*(?:'(.*)'|none)\s+uuid:\s*([0-9a-f-]+)/);182
if (label) { current = { label: label[1] || null, uuid: label[2], devices: [] }; filesystems.push(current); continue; }183
const device = line.match(/^\s*devid\s+(\d+)\s+size\s+(\d+)\s+used\s+(\d+)\s+path\s+(\S+)/);184
if (device && current) current.devices.push({ devid: Number(device[1]), size: Number(device[2]), used: Number(device[3]), path: device[4] });185
}186
return filesystems;187
}189
function parseZpool(section) {190
const pools = [];191
let current = null;192
for (const line of lines(section)) {193
const fields = line.split("\t");194
if (fields[0]) { current = { name: text(fields[0], 64), size: int(fields[1]), allocated: int(fields[2]), free: int(fields[3]), health: text(fields[9] || fields[fields.length - 1], 16), devices: [] }; pools.push(current); }195
else if (current && fields[1] && fields[1].startsWith("/dev/")) current.devices.push(text(fields[1], 200));196
}197
return pools;198
}200
function parseLsblk(section) {201
const json = parseJson(section);202
const map = new Map();203
for (const entry of json?.blockdevices || []) if (entry.kname || entry.name) map.set(entry.kname || entry.name, entry);204
return map;205
}207
function mediaType(entry) {208
if (/^loop/.test(entry.kname)) return "loop";209
if (/^nvme/.test(entry.kname)) return VIRTUAL_MODEL.test(entry.model) ? "virtual" : "nvme";210
if (/^(vd|xvd)/.test(entry.kname) || VIRTUAL_MODEL.test(entry.model) || VIRTUAL_MODEL.test(entry.vendor)) return "virtual";211
if (entry.rotational === true) return "hdd";212
if (entry.rotational === false) return "ssd";213
return "unknown";214
}215
function transport(entry, lsblk) {216
if (lsblk?.tran) return text(lsblk.tran, 16);217
const path = entry.sysPath;218
if (/^nvme/.test(entry.kname)) return "nvme";219
if (/\/virtio\d+\/block\//.test(path)) return "virtio";220
if (/\/virtio\d+\/host\d+\//.test(path)) return "virtio-scsi";221
if (/\/usb\d+\//.test(path)) return "usb";222
if (/\/ata\d+\//.test(path)) return "sata";223
if (/\/host\d+\/target/.test(path)) return "scsi";224
if (/^mmcblk/.test(entry.kname)) return "mmc";225
return null;226
}228
function nodeKind(entry) {229
if (entry.partition !== null) return "part";230
if (/^dm-/.test(entry.kname)) {231
if (/^CRYPT-/.test(entry.dmUuid)) return "crypt";232
if (/^LVM-/.test(entry.dmUuid)) return "lvm";233
return "dm";234
}235
if (/^md\d/.test(entry.kname)) return "md";236
if (/^loop/.test(entry.kname)) return "loop";237
return "disk";238
}240
// Unescape a device-mapper LVM name ("vg--data-lv" -> vg "vg-data", lv "lv").241
function splitDmLvName(name) {242
const match = String(name).match(/^((?:[^-]|--)+)-((?:[^-]|--)+)$/);243
return match ? { vg: match[1].replace(/--/g, "-"), lv: match[2].replace(/--/g, "-") } : null;244
}246
function buildInventory(stdout) {247
const sections = splitSections(stdout);248
const meta = keyValues(sections.meta);249
const tools = keyValues(sections.tools);250
const versions = keyValues(sections.versions);251
const privileged = meta.uid === "0";252
const sys = parseSysblock(sections.sysblock);253
const lsblk = parseLsblk(sections.lsblk);254
const blkid = parseBlkid(sections.blkid);255
const mounts = parseMountinfo(sections.mountinfo);256
const df = parseDf(sections.df);257
const swaps = parseSwaps(sections.swaps);258
const tables = parsePartitionTables(sections.pt);259
const lvm = parseLvm(sections.lvm);260
const crypt = parseCrypt(sections.crypt);261
const fsinfo = parseFsinfo(sections.fsinfo);262
const btrfs = parseBtrfs(sections.btrfs);263
const zpools = parseZpool(sections.zpool);264
const mdstat = String(sections.mdstat || "");266
const nodes = new Map();267
for (const entry of sys) {268
if (/^(ram|zram|fd)\d*$/.test(entry.kname)) continue;269
// Unused loop devices (size 0) and read-only snap images are not disks anyone manages here.270
if (/^loop\d+$/.test(entry.kname) && (!entry.sectors512 || entry.readOnly || !entry.loopBacking)) continue;271
if (/^sr\d+$/.test(entry.kname)) continue;272
const ls = lsblk.get(entry.kname) || {};273
const path = entry.dmName && /^dm-/.test(entry.kname) ? `/dev/mapper/${entry.dmName}` : `/dev/${entry.kname}`;274
const probe = blkid.get(`/dev/${entry.kname}`) || blkid.get(path) || {};275
const kind = nodeKind(entry);276
const fstype = text(probe.TYPE || ls.fstype || "", 32) || null;277
const node = {278
kname: entry.kname, path, kind, devno: entry.devno,279
size: entry.sectors512 === null ? null : entry.sectors512 * 512,280
readOnly: entry.readOnly, parent: entry.parent, partition: entry.partition,281
start512: entry.start512, holders: entry.holders, slaves: entry.slaves,282
fstype, fsVersion: text(probe.VERSION || ls.fsver || "", 16) || null,283
label: text(probe.LABEL ?? ls.label ?? "", 64) || null,284
uuid: text(probe.UUID ?? ls.uuid ?? "", 64) || null,285
partuuid: text(probe.PARTUUID ?? ls.partuuid ?? "", 64) || null,286
partlabel: text(probe.PARTLABEL ?? ls.partlabel ?? "", 72) || null,287
parttype: text(ls.parttype ?? "", 40) || null,288
dmName: entry.dmName || null, dmUuid: entry.dmUuid || null,289
loopBacking: entry.loopBacking || null,290
mdLevel: entry.mdLevel || null,291
mountpoints: [], usage: null, fsSize: null, fsUsed: null, swapActive: false,292
lvm: null, crypt: null, zfsPool: null, btrfs: null, _sys: entry, _lsblk: ls293
};294
nodes.set(entry.kname, node);295
}296
const byDevno = new Map([...nodes.values()].map((node) => [node.devno, node]));297
const byPath = new Map();298
for (const node of nodes.values()) {299
byPath.set(node.path, node); byPath.set(`/dev/${node.kname}`, node);300
if (node.dmName) byPath.set(`/dev/mapper/${node.dmName}`, node);301
}303
// Mounts: by device number first (exact), by source path otherwise (Btrfs uses anonymous device numbers).304
for (const mount of mounts) {305
const node = byDevno.get(mount.devno) || byPath.get(mount.source);306
if (!node) continue;307
if (!node.mountpoints.includes(mount.mountpoint)) node.mountpoints.push(mount.mountpoint);308
node.mountedFstype ||= mount.fstype;309
node.mountedReadOnly = /(^|,)ro(,|$)/.test(mount.options);310
}311
for (const swap of swaps) { const node = byPath.get(swap.path); if (node) { node.swapActive = true; node.swapUsed = swap.used; } }312
for (const node of nodes.values()) {313
const mountpoint = node.mountpoints.find((point) => df.has(point));314
if (mountpoint) { node.usage = { ...df.get(mountpoint), source: "df", mountpoint }; }315
const info = fsinfo.get(node.path) || fsinfo.get(`/dev/${node.kname}`);316
if (info) {317
node.fsSize = info.fsSize; node.fsState = info.state || null;318
if (!node.usage && info.fsUsed !== null && info.fsUsed !== undefined) node.usage = { size: info.fsSize, used: info.fsUsed, available: info.fsSize - info.fsUsed, source: "superblock" };319
}320
if (!node.fsSize && node.usage && node.fstype === "xfs") node.fsSize = null;321
}322
for (const fs of btrfs) for (const device of fs.devices) {323
const node = byPath.get(device.path);324
if (node) { node.btrfs = { uuid: fs.uuid, devid: device.devid, devices: fs.devices.length, allocated: device.used }; node.fsSize = device.size; }325
}326
for (const pool of zpools) for (const device of pool.devices) { const node = byPath.get(device); if (node) node.zfsPool = pool.name; }328
// LVM: privileged JSON when available, otherwise the device-mapper name.329
const pvByPath = new Map(lvm.pvs.map((pv) => [pv.path, pv]));330
for (const node of nodes.values()) {331
const pv = pvByPath.get(node.path) || pvByPath.get(`/dev/${node.kname}`);332
if (pv) node.pv = pv;333
if (node.kind === "lvm") {334
const lv = lvm.lvs.find((entry) => entry.dmPath === node.path || `/dev/mapper/${node.dmName}` === entry.dmPath);335
const names = splitDmLvName(node.dmName);336
node.lvm = lv ? { ...lv } : names ? { name: names.lv, vg: names.vg, size: node.size, attr: null, thinPool: /-tpool$|_tdata$|_tmeta$/.test(names.lv), thinVolume: false, partial: true } : null;337
if (node.lvm) node.displayPath = `/dev/${node.lvm.vg}/${node.lvm.name}`;338
}339
if (node.kind === "crypt") {340
const status = crypt.get(node.dmName);341
const type = (String(node.dmUuid).match(/^CRYPT-([A-Z0-9]+)-/) || [])[1] || null;342
node.crypt = { type: status?.type || type, cipher: status?.cipher || null, keyLocation: status?.keyLocation || null, mode: status?.mode || null, open: true };343
}344
}346
// Graph edges. Partitions hang below their disk; dm/md below their slaves.347
for (const node of nodes.values()) {348
node.parents = node.kind === "part" ? [node.parent].filter((name) => nodes.has(name)) : node.slaves.filter((name) => nodes.has(name));349
node.children = node.holders.filter((name) => nodes.has(name));350
}351
const descendants = (node, seen = new Set()) => {352
for (const name of node.children) if (!seen.has(name)) { seen.add(name); descendants(nodes.get(name), seen); }353
return seen;354
};355
for (const node of nodes.values()) {356
node.inUseReasons = [];357
if (node.mountpoints.length) node.inUseReasons.push({ reason: "mounted", detail: node.mountpoints.join(", ") });358
if (node.swapActive) node.inUseReasons.push({ reason: "swap" });359
if (node.children.length) node.inUseReasons.push({ reason: "holders", detail: node.children.map((name) => nodes.get(name).displayPath || nodes.get(name).path).join(", ") });360
if (node.fstype === "zfs_member") node.inUseReasons.push({ reason: "zfs", detail: node.zfsPool || "" });361
if (node.fstype === "linux_raid_member" && !node.children.length && /\b(md\d+)\b/.test(mdstat)) node.inUseReasons.push({ reason: "raid-member" });362
node.inUse = node.inUseReasons.length > 0;363
const stack = [node, ...[...descendants(node)].map((name) => nodes.get(name))];364
node.systemRoles = [...new Set(stack.flatMap((item) => [365
...item.mountpoints.filter((point) => SYSTEM_MOUNTS.test(point)).map((point) => `mount:${point}`),366
...(item.swapActive ? ["swap"] : [])367
]))];368
}370
const disks = [];371
for (const node of nodes.values()) {372
if (!["disk", "loop"].includes(node.kind) || !node.size) continue;373
const entry = node._sys;374
const table = tables.get(node.kname) || null;375
const lbs = Number(table?.sectorsize) || entry.logicalSectorSize || 512;376
const sectors = Math.floor(node.size / lbs);377
const align = Math.max(1, Math.floor(MiB / lbs));378
const partNodes = [...nodes.values()].filter((item) => item.kind === "part" && item.parent === node.kname);379
let label = table?.label || text(node._lsblk.pttype || "", 8) || null;380
if (!label && partNodes.length) label = "unknown";381
const partitions = [];382
if (table) {383
for (const part of [...table.partitions || []].sort((a, b) => a.start - b.start)) {384
const number = Number((String(part.node).match(/(\d+)$/) || [])[1]);385
const kernelNode = partNodes.find((item) => item.partition === number) || null;386
partitions.push(describePartition(label, number, part.start, part.size, lbs, part, kernelNode));387
}388
} else {389
for (const part of partNodes.sort((a, b) => a.start512 - b.start512)) {390
const factor = lbs / 512;391
partitions.push(describePartition(label, part.partition, Math.round(part.start512 / factor), Math.round(part.size / lbs), lbs, { type: part.parttype, uuid: part.partuuid, name: part.partlabel }, part));392
}393
}394
// Usable area of the table; GPT keeps 33 sectors (header + 128 entries) at both ends.395
const gptTail = Math.ceil(16384 / lbs) + 1;396
let firstUsable = label === "gpt" ? Number(table?.firstlba) || gptTail + 1 : align;397
if (label === "gpt" && firstUsable < align) firstUsable = Math.min(align, firstUsable);398
const lastUsableNow = label === "gpt" ? Number(table?.lastlba) || sectors - gptTail - 1 : Math.min(sectors - 1, 0xffffffff);399
const lastUsable = label === "gpt" ? sectors - gptTail - 1 : lastUsableNow;400
const gptBackupMisplaced = label === "gpt" && Boolean(table?.lastlba) && lastUsableNow < lastUsable;401
const mbrLimited = label === "dos" && sectors - 1 > 0xffffffff;402
const segments = label ? buildSegments(partitions, firstUsable, lastUsable, align, lbs, lastUsableNow) : [];403
const whole = !partitions.length ? node : null;404
const allRoles = [...new Set([node, ...partitions.map((part) => part.kernel && nodes.get(part.kernel))].filter(Boolean).flatMap((item) => item.systemRoles))];405
disks.push({406
name: node.kname, path: node.path, kind: node.kind,407
model: entry.model || null, vendor: entry.vendor || null, serial: entry.serial || text(node._lsblk.serial || "", 80) || null, wwid: entry.wwid || text(node._lsblk.wwn || "", 120) || null,408
transport: transport(entry, node._lsblk), mediaType: mediaType(entry), rotational: entry.rotational,409
discard: entry.discardMax > 0, readOnly: node.readOnly, removable: entry.removable, loopBacking: node.loopBacking,410
size: node.size, sectors, logicalSectorSize: lbs, physicalSectorSize: entry.physicalSectorSize, alignSectors: align,411
table: label ? {412
type: label, id: text(table?.id || node._lsblk.ptuuid || "", 64) || null, firstUsable, lastUsable, lastUsableNow,413
hash: table?.hash || null, source: table ? "sfdisk" : "sysfs", gptBackupMisplaced, mbrLimited, maxPrimary: label === "dos" ? 4 : 128414
} : null,415
partitions, segments,416
wholeDevice: whole ? { kname: whole.kname, fstype: whole.fstype } : null,417
systemRoles: allRoles, system: allRoles.length > 0,418
inUse: node.inUse || partitions.some((part) => part.inUse)419
});420
}422
const pve = meta.pve === "1";423
for (const disk of disks) {424
const stackKnames = new Set([disk.name, ...disk.partitions.map((part) => part.kernel).filter(Boolean)]);425
for (const kname of [...stackKnames]) for (const child of descendantsOf(nodes, kname)) stackKnames.add(child);426
const stackNodes = [...stackKnames].map((name) => nodes.get(name)).filter(Boolean);427
disk.layers = { luks: stackNodes.some((item) => item.fstype === "crypto_LUKS"), lvm: stackNodes.some((item) => item.fstype === "LVM2_member" || item.kind === "lvm"), raid: stackNodes.some((item) => item.fstype === "linux_raid_member" || item.kind === "md"), zfs: stackNodes.some((item) => item.fstype === "zfs_member") };428
// Proxmox host storage (LVM/ZFS behind guests or the host) is never a target of destructive changes.429
disk.protected = pve && (disk.layers.lvm || disk.layers.zfs || disk.system) ? "pve-host-storage" : null;430
}432
const publicNodes = [...nodes.values()].map(({ _sys, _lsblk, holders, slaves, ...rest }) => rest);433
return {434
platform: "linux", supported: true, privileged,435
os: text(meta.os, 120), osId: text(meta.osId, 40), osLike: text(meta.osLike, 80), kernel: text(meta.kernel, 80),436
systemd: meta.systemd === "1", pve, selinux: meta.selinux ? text(meta.selinux, 20) : null,437
tools: Object.fromEntries(Object.entries(tools).map(([key, value]) => [text(key, 32), text(value, 120)])),438
versions: Object.fromEntries(Object.entries(versions).map(([key, value]) => [text(key, 32), text(value, 120)])),439
sources: { lsblk: lsblk.size > 0, blkid: blkid.size > 0, sfdisk: tables.size > 0, lvm: lvm.available, crypt: crypt.size > 0 },440
partial: !privileged,441
disks: disks.sort((a, b) => a.name.localeCompare(b.name, "en", { numeric: true })),442
nodes: publicNodes,443
lvm: { available: lvm.available, pvs: lvm.pvs, vgs: lvm.vgs, lvs: lvm.lvs },444
btrfs, zpools,445
remoteTime: int(meta.time)446
};448
function describePartition(tableType, number, start, size, lbs, part, kernelNode) {449
const typeCode = text(part.type || "", 40).toUpperCase();450
const typeName = tableType === "gpt" ? GPT_TYPES[typeCode] || null : DOS_TYPES[typeCode.toLowerCase().replace(/^0+(?=.)/, "")] || null;451
const extended = typeName === "extended";452
return {453
number, start, size, end: start + size - 1, bytes: size * lbs,454
type: typeCode || null, typeName, uuid: text(part.uuid || kernelNode?.partuuid || "", 64) || null, name: text(part.name || kernelNode?.partlabel || "", 72) || null,455
attrs: text(part.attrs || "", 160) || null, bootable: part.bootable === true,456
extended, logical: tableType === "dos" && number > 4,457
kernel: kernelNode?.kname || null, path: kernelNode?.path || null,458
kernelMatches: kernelNode ? Math.round(kernelNode.size / lbs) === size || extended : false,459
fstype: kernelNode?.fstype || null, label: kernelNode?.label || null, fsUuid: kernelNode?.uuid || null,460
mountpoints: kernelNode?.mountpoints || [], usage: kernelNode?.usage || null, fsSize: kernelNode?.fsSize || null,461
inUse: Boolean(kernelNode?.inUse), inUseReasons: kernelNode?.inUseReasons || [], systemRoles: kernelNode?.systemRoles || [],462
children: kernelNode?.children || []463
};464
}465
}467
function descendantsOf(nodes, kname, seen = new Set()) {468
for (const child of nodes.get(kname)?.children || []) if (!seen.has(child)) { seen.add(child); descendantsOf(nodes, child, seen); }469
return seen;470
}472
// Ordered partitions and unallocated gaps (>= one alignment unit) between firstUsable and lastUsable.473
function buildSegments(partitions, firstUsable, lastUsable, align, lbs, lastUsableNow = lastUsable) {474
const top = partitions.filter((part) => !part.logical).sort((a, b) => a.start - b.start);475
const segments = [];476
let cursor = firstUsable;477
const pushFree = (from, to) => {478
if (to - from + 1 < align) return;479
segments.push({ kind: "free", start: from, size: to - from + 1, end: to, bytes: (to - from + 1) * lbs, needsGptRelocation: to > lastUsableNow });480
};481
for (const part of top) {482
if (part.start > cursor) pushFree(cursor, part.start - 1);483
segments.push({ kind: "partition", number: part.number, start: part.start, size: part.size, end: part.end, bytes: part.bytes });484
cursor = Math.max(cursor, part.end + 1);485
}486
if (cursor <= lastUsable) pushFree(cursor, lastUsable);487
return segments;488
}490
module.exports = { DOS_TYPES, GPT_TYPES, MiB, PARTITION_TYPE_CODES, SYSTEM_MOUNTS, buildInventory, buildSegments, parseBtrfs, parseLvm, parseMountinfo, splitDmLvName, splitSections };SHA-256
60f0e11482a6b76774419ed3a7471872c0d587f50880933ade67b7fdbaf14b65CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
SHA-256 archivu
476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6