UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

440 lines (439 loc) • 17.1 kB
import { n as resolveDiagnosticProcessEnv, r as resolveEnvironmentValue } from "./process-env-DlZFJzq6.mjs"; import { createRequire } from "node:module"; import fs from "node:fs"; import path from "node:path"; import { endianness } from "node:os"; import childProcess, { spawnSync } from "node:child_process"; //#region src/infra/windows-process-start.ts const DEFAULT_TIMEOUT_MS = 5e3; const DEFAULT_PROCESS_START_TIMEOUT_MS = 1e4; const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows"; let nativeProcessStartTime; function loadNativeProcessStartTime() { const kernel32 = createRequire(import.meta.url)("koffi").load("kernel32.dll"); const openProcess = kernel32.func("void * __stdcall OpenProcess(uint32_t access, int32_t inheritHandle, uint32_t processId)"); const getProcessTimes = kernel32.func("int32_t __stdcall GetProcessTimes(void *process, void *creation, void *exit, void *kernel, void *user)"); const closeHandle = kernel32.func("int32_t __stdcall CloseHandle(void *handle)"); return (pid) => { const handle = openProcess(4096, 0, pid); if (handle === null) return null; try { const creation = Buffer.alloc(8); if (!getProcessTimes(handle, creation, Buffer.alloc(8), Buffer.alloc(8), Buffer.alloc(8))) return null; const ticks = creation.readBigUInt64LE(); if (ticks === 0n) return null; return Number(ticks / 10000n - 11644473600000n); } finally { closeHandle(handle); } }; } function readNativeProcessStartTime(pid) { if (process.platform !== "win32" || typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) return null; try { nativeProcessStartTime ??= loadNativeProcessStartTime(); return nativeProcessStartTime(pid); } catch { return null; } } function windowsSystemRoot(env) { const configured = resolveEnvironmentValue(env, "SystemRoot", "win32") ?? resolveEnvironmentValue(env, "WINDIR", "win32"); if (!configured) return DEFAULT_WINDOWS_SYSTEM_ROOT; const normalized = path.win32.normalize(configured); return /^[A-Za-z]:\\/.test(normalized) && !normalized.startsWith("\\\\") ? normalized : DEFAULT_WINDOWS_SYSTEM_ROOT; } function windowsPowerShellPath(env) { return path.win32.join(windowsSystemRoot(env), "System32", "WindowsPowerShell", "v1.0", "powershell.exe"); } function windowsWmicPath(env) { return path.win32.join(windowsSystemRoot(env), "System32", "wbem", "wmic.exe"); } function decodeWindowsProcessOutput(output) { if (!Buffer.isBuffer(output)) return output; return output.length >= 2 && output[0] === 255 && output[1] === 254 ? output.toString("utf16le") : output.toString("utf8"); } function parseWindowsProcessStartTime(raw) { const lines = decodeWindowsProcessOutput(raw).split(/\r?\n/).map((line) => line.trim()).filter(Boolean); const value = lines.find((line) => line.toLowerCase().startsWith("creationdate="))?.slice(13).trim() ?? lines.find((line) => line.toLowerCase() !== "creationdate") ?? ""; const parsedIso = Date.parse(value); if (Number.isFinite(parsedIso)) return parsedIso; const dmtf = value.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-])(\d{3})$/); if (!dmtf) return null; const [, year, month, day, hour, minute, second, microseconds, offsetSign, offset] = dmtf; return Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second), Math.floor(Number(microseconds) / 1e3)) - Number(offset) * 6e4 * (offsetSign === "+" ? 1 : -1); } /** Read a stable Windows process creation time for lock-owner identity checks. */ function readWindowsProcessStartTimeSync(pid, timeoutMs = DEFAULT_PROCESS_START_TIMEOUT_MS, env = process.env) { if (!Number.isInteger(pid) || pid <= 0 || pid > 4294967295) return null; const deadline = Date.now() + timeoutMs; const nativeStartTime = readNativeProcessStartTime(pid); if (nativeStartTime !== null) return nativeStartTime; const powershellBudgetMs = deadline - Date.now(); if (powershellBudgetMs <= 0) return null; const powershell = spawnSync(windowsPowerShellPath(env), [ "-NoProfile", "-NonInteractive", "-Command", `$process = [System.Diagnostics.Process]::GetProcessById(${pid}); try { [Console]::Out.Write($process.StartTime.ToUniversalTime().ToString("o")) } finally { $process.Dispose() }` ], { encoding: "utf8", env: resolveDiagnosticProcessEnv(env, "win32"), timeout: Math.min(powershellBudgetMs, DEFAULT_TIMEOUT_MS), windowsHide: true }); if (!powershell.error && powershell.status === 0) { const startTime = parseWindowsProcessStartTime(powershell.stdout); if (startTime !== null) return startTime; } const remainingMs = deadline - Date.now(); if (remainingMs <= 0) return null; const wmic = spawnSync(windowsWmicPath(env), [ "process", "where", `ProcessId=${pid}`, "get", "CreationDate", "/value" ], { env: resolveDiagnosticProcessEnv(env, "win32"), timeout: remainingMs, windowsHide: true, stdio: [ "ignore", "pipe", "ignore" ] }); return !wmic.error && wmic.status === 0 ? parseWindowsProcessStartTime(wmic.stdout) : null; } /** Read one process snapshot; a reused parent PID must not extend the ancestry. */ function readWindowsProcessAncestorsSync(pid, maxDepth, timeoutMs, env = process.env) { try { const result = spawnSync(windowsPowerShellPath(env), [ "-NoProfile", "-NonInteractive", "-Command", [ "ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process", "-Property ProcessId,ParentProcessId,CreationDate -ErrorAction Stop | ForEach-Object {", "if ($null -ne $_.CreationDate) { [pscustomobject]@{ pid = $_.ProcessId; parentPid = $_.ParentProcessId;", "startedAt = $_.CreationDate.ToUniversalTime().Ticks.ToString() } } })" ].join(" ") ], { encoding: "utf8", env: resolveDiagnosticProcessEnv(env, "win32"), timeout: timeoutMs, maxBuffer: 1048576, windowsHide: true }); if (result.error || result.status !== 0) return { pids: [], complete: false }; const parsed = JSON.parse(result.stdout); if (!Array.isArray(parsed)) return { pids: [], complete: false }; const rows = parsed; const processes = /* @__PURE__ */ new Map(); for (const row of rows) { if (!row || typeof row !== "object" || !("pid" in row) || typeof row.pid !== "number" || !Number.isSafeInteger(row.pid) || row.pid <= 0 || !("parentPid" in row) || typeof row.parentPid !== "number" || !Number.isSafeInteger(row.parentPid) || row.parentPid < 0 || !("startedAt" in row) || typeof row.startedAt !== "string" || !/^[1-9]\d{0,18}$/.test(row.startedAt)) continue; if (processes.has(row.pid)) return { pids: [], complete: false }; processes.set(row.pid, { parentPid: row.parentPid, startedAt: BigInt(row.startedAt) }); } const ancestors = /* @__PURE__ */ new Set(); let current = processes.get(pid); for (let depth = 0; current && depth < maxDepth; depth++) { const parentPid = current.parentPid; const parent = processes.get(parentPid); if (!parent || parentPid === pid || ancestors.has(parentPid) || parent.startedAt > current.startedAt) break; ancestors.add(parentPid); current = parent; } return { pids: [...ancestors], complete: current?.parentPid === 0 }; } catch { return { pids: [], complete: false }; } } //#endregion //#region src/shared/freebsd-process-identity-native.ts /** Normal installations use the dependency's public loader. */ function loadFreeBsdProcessIdentityNative() { if (typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) throw new Error("FreeBSD process identity is unavailable in this sealed runtime"); return createRequire(import.meta.url)("koffi/indirect"); } //#endregion //#region src/shared/freebsd-process-identity.ts let native; function getSysctl() { if (!native) { const library = loadFreeBsdProcessIdentityNative().load(null); native = { library, sysctl: library.func("int sysctl(const int *name, unsigned int namelen, _Out_ void *oldp, _Inout_ size_t *oldlenp, const void *newp, size_t newlen)") }; } return native.sysctl; } function readTimeval(bytes, offset) { const seconds = bytes.readBigInt64LE(offset); const microseconds = bytes.readBigInt64LE(offset + 8); if (seconds < 0n || microseconds < 0n || microseconds >= 1000000n) throw new Error("Invalid FreeBSD process identity timeval"); return seconds * 1000000n + microseconds; } /** Read the kernel's monotonic process start time in microseconds. */ function readFreeBsdProcessStartTime(pid) { if (process.platform !== "freebsd" || process.arch !== "x64" && process.arch !== "arm64" || endianness() !== "LE" || !Number.isInteger(pid) || pid <= 0 || pid > 2147483647) return null; try { const sysctl = getSysctl(); const bytes = Buffer.alloc(1120); for (const [mib, offset, length] of [ [ [1, 21], 0, 16 ], [ [ 1, 14, 1, pid ], 16, 1088 ], [ [1, 21], 1104, 16 ] ]) { const actual = [length]; if (sysctl(mib, mib.length, bytes.subarray(offset, offset + length), actual, null, 0) !== 0 || actual[0] !== length) return null; } if (bytes.readInt32LE(16) !== 1088 || bytes.readInt32LE(20) !== 0 || bytes.readInt32LE(88) !== pid || !bytes.subarray(0, 16).equals(bytes.subarray(1104))) return null; const start = readTimeval(bytes, 352) - readTimeval(bytes, 0); return start >= 0n && start <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(start) : null; } catch { return null; } } //#endregion //#region src/shared/pid-alive.ts const PROCESS_START_TIMEOUT_MS = 1e3; let darwinNative; function readDarwinNativeIdentity(pid) { if (process.platform !== "darwin" || process.arch !== "arm64" || pid > 2147483647 || typeof SEALED_RUNTIME_BUILD === "boolean" && SEALED_RUNTIME_BUILD) return null; try { if (!darwinNative) { const library = createRequire(import.meta.url)("koffi").load("/usr/lib/libproc.dylib"); darwinNative = { library, query: library.func("int proc_pidinfo(int pid, int flavor, uint64_t arg, _Out_ void *buffer, int buffersize)") }; } const bytes = Buffer.alloc(136); if (darwinNative.query(pid, 3, 0, bytes, bytes.length) !== bytes.length) return null; const parentPid = bytes.readUInt32LE(16); const seconds = bytes.readBigUInt64LE(120); const microseconds = bytes.readBigUInt64LE(128); if (bytes.readUInt32LE(12) !== pid || parentPid > 2147483647 || seconds === 0n || seconds > BigInt(Number.MAX_SAFE_INTEGER) || microseconds >= 1000000n) return null; return { parentPid, startedAt: Number(seconds), microseconds: Number(microseconds) }; } catch { return null; } } /** Project a best-effort ancestor chain without deciding liveness or authority. */ function collectProcessAncestorPids(immediateParent, readParentPid, throughPid) { const pids = /* @__PURE__ */ new Set([process.pid]); if (!Number.isFinite(immediateParent) || immediateParent <= 0) return pids; pids.add(immediateParent); let current = immediateParent; for (let depth = 0; depth < 32 && current !== throughPid; depth++) { const parent = readParentPid(current); if (parent == null || parent <= 0 || pids.has(parent)) break; pids.add(parent); current = parent; } return pids; } let selfStartTime = null; function isValidPid(pid) { return Number.isInteger(pid) && pid > 0; } /** * Check whether a Linux task is terminal and has no live sibling threads. * Missing or reaped procfs snapshots require a fresh existence probe. */ function isExitedLinuxProcess(pid) { if (process.platform !== "linux") return false; try { const status = fs.readFileSync(`/proc/${pid}/status`, "utf8"); const state = status.match(/^State:\s+(\S)/m)?.[1]; const threads = status.match(/^Threads:[ \t]+(\d+)[ \t]*$/m)?.[1]; if (threads !== "0") return (state === "Z" || state === "X") && threads === "1"; } catch {} try { process.kill(pid, 0); } catch (error) { return error.code === "ESRCH"; } return false; } /** Returns true only when a positive PID exists and is not a known terminal Linux task. */ function isPidAlive(pid) { if (!isValidPid(pid)) return false; try { process.kill(pid, 0); } catch (err) { if (err.code !== "EPERM") return false; } return !isExitedLinuxProcess(pid); } /** Returns true only when the PID is invalid, missing, or a known terminal Linux task. */ function isPidDefinitelyDead(pid) { if (!isValidPid(pid)) return true; try { process.kill(pid, 0); } catch (err) { return err.code === "ESRCH"; } return isExitedLinuxProcess(pid); } function getDarwinProcessStartTime(pid, env, timeoutMs) { const started = performance.now(); const native = readDarwinNativeIdentity(pid); if (native) return native.startedAt; const remainingMs = timeoutMs === void 0 ? PROCESS_START_TIMEOUT_MS : Math.ceil(timeoutMs - (performance.now() - started)); if (remainingMs <= 0) return null; try { const startedAt = childProcess.execFileSync("/bin/ps", [ "-o", "lstart=", "-p", String(pid) ], { encoding: "utf8", env: { ...resolveDiagnosticProcessEnv(env), LC_ALL: "C", TZ: "UTC" }, stdio: [ "ignore", "pipe", "ignore" ], timeout: remainingMs, killSignal: "SIGKILL" }).trim(); const startedAtMs = Date.parse(`${startedAt} UTC`); return Number.isFinite(startedAtMs) ? Math.floor(startedAtMs / 1e3) : null; } catch { return null; } } /** Read one Darwin PID's parent and birth together, without enumerating unrelated processes. */ function readDarwinProcessIdentity(pid, env = process.env, timeoutMs) { if (process.platform !== "darwin" || !isValidPid(pid)) return null; const started = performance.now(); const native = readDarwinNativeIdentity(pid); if (native) return { parentPid: native.parentPid, startedAt: native.startedAt }; const remainingMs = timeoutMs === void 0 ? PROCESS_START_TIMEOUT_MS : Math.ceil(timeoutMs - (performance.now() - started)); if (remainingMs <= 0) return null; try { const stdout = childProcess.execFileSync("/bin/ps", [ "-o", "pid=,ppid=,lstart=", "-p", String(pid) ], { encoding: "utf8", env: { ...resolveDiagnosticProcessEnv(env), LC_ALL: "C", TZ: "UTC" }, stdio: [ "ignore", "pipe", "ignore" ], timeout: remainingMs, killSignal: "SIGKILL", maxBuffer: 4096 }); if (!stdout.endsWith("\n") || /[\r\n]/.test(stdout.slice(0, -1))) return null; const match = /^[ \t]*(\d+)[ \t]+(\d+)[ \t]+(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d{1,2}) (\d{2}:\d{2}:\d{2}) (\d{4})[ \t]*$/.exec(stdout.slice(0, -1)); if (!match || Number(match[1]) !== pid || match[5] === void 0) return null; const parentPid = Number(match[2]); const date = `${match[3]}, ${match[5].padStart(2, "0")} ${match[4]} ${match[7]} ${match[6]} GMT`; const startedAtMs = Date.parse(date); if (!Number.isSafeInteger(parentPid) || parentPid < 0 || !Number.isFinite(startedAtMs) || new Date(startedAtMs).toUTCString() !== date) return null; return { parentPid, startedAt: Math.floor(startedAtMs / 1e3) }; } catch { return null; } } /** Read the Linux procfs start identity used by Linux-owned runtime state. */ function getProcessStartTime(pid) { if (!isValidPid(pid) || process.platform !== "linux") return null; try { const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8"); const commEndIndex = stat.lastIndexOf(")"); if (commEndIndex < 0) return null; const fields = stat.slice(commEndIndex + 1).trimStart().split(/\s+/); const starttime = Number(fields[19]); return Number.isInteger(starttime) && starttime >= 0 ? starttime : null; } catch { return null; } } /** Custody recovery needs native birth precision rather than the shipped lease timestamp format. */ function getProcessInstanceStartTime(pid) { if (!isValidPid(pid)) return null; if (process.platform === "linux") { const startedAt = getProcessStartTime(pid); return Number.isSafeInteger(startedAt) ? startedAt : null; } const native = readDarwinNativeIdentity(pid); if (!native) return null; const startedAt = native.startedAt * 1e6 + native.microseconds; return Number.isSafeInteger(startedAt) ? startedAt : null; } /** Read a cross-platform process identity for filesystem lock ownership. */ function getFileLockProcessStartTime(pid, env = process.env, timeoutMs) { if (!isValidPid(pid)) return null; const isSelf = pid === process.pid; if (isSelf && selfStartTime !== null) return selfStartTime; const startTime = process.platform === "darwin" ? getDarwinProcessStartTime(pid, env, timeoutMs) : process.platform === "win32" ? readWindowsProcessStartTimeSync(pid, timeoutMs, env) : process.platform === "freebsd" ? readFreeBsdProcessStartTime(pid) : getProcessStartTime(pid); if (isSelf && startTime !== null) selfStartTime = startTime; return startTime; } //#endregion export { isPidAlive as a, decodeWindowsProcessOutput as c, getProcessStartTime as i, readWindowsProcessAncestorsSync as l, getFileLockProcessStartTime as n, isPidDefinitelyDead as o, getProcessInstanceStartTime as r, readDarwinProcessIdentity as s, collectProcessAncestorPids as t, readWindowsProcessStartTimeSync as u };