openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
310 lines (309 loc) • 12.2 kB
JavaScript
import { n as resolveDiagnosticProcessEnv, r as resolveEnvironmentValue } from "./process-env-DlZFJzq6.mjs";
import { createRequire } from "node:module";
import childProcess, { spawnSync } from "node:child_process";
import fsSync from "node:fs";
import path from "node:path";
import { endianness } from "node:os";
//#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;
}
//#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;
}
}
let selfStartTime = null;
function isValidPid(pid) {
return Number.isInteger(pid) && pid > 0;
}
/**
* Check if every thread has exited by reading Linux /proc/<pid>/status.
* Returns false on non-Linux platforms or if the proc file can't be read.
*/
function isZombieProcess(pid) {
if (process.platform !== "linux") return false;
try {
const status = fsSync.readFileSync(`/proc/${pid}/status`, "utf8");
return status.match(/^State:\s+(\S)/m)?.[1] === "Z" && /^Threads:[ \t]+1[ \t]*$/m.test(status);
} catch {
return false;
}
}
/** Returns true only when a positive PID exists and is not a Linux zombie process. */
function isPidAlive(pid) {
if (!isValidPid(pid)) return false;
try {
process.kill(pid, 0);
} catch (err) {
if (err.code !== "EPERM") return false;
}
return !isZombieProcess(pid);
}
/** Returns true only when the PID is invalid, missing, or known to be a Linux zombie. */
function isPidDefinitelyDead(pid) {
if (!isValidPid(pid)) return true;
try {
process.kill(pid, 0);
} catch (err) {
return err.code === "ESRCH";
}
return isZombieProcess(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 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 = fsSync.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 { isPidDefinitelyDead as i, getProcessInstanceStartTime as n, isPidAlive as r, getFileLockProcessStartTime as t };