import type { RuntimeProfile } from "./runtime.ts"; export function agentCommand( profile: RuntimeProfile, command: string, args: string[], options: Omit = {}, ): Deno.Command { return new Deno.Command(command, { ...options, args, uid: profile.uid, gid: profile.gid, }); } export function isRunnableProfileProcess( status: string, uid: number, ): boolean { const owner = status.match(/^Uid:\s+(\d+)/m)?.[1]; const state = status.match(/^State:\s+(\w)/m)?.[1]; return owner !== undefined && Number(owner) === uid && state !== "Z"; } async function profileProcessIds(uid: number): Promise { const ids: number[] = []; for await (const entry of Deno.readDir("/proc")) { if (!entry.isDirectory || !/^\d+$/.test(entry.name)) continue; const status = await Deno.readTextFile(`/proc/${entry.name}/status`).catch( () => null, ); if (status !== null && isRunnableProfileProcess(status, uid)) { ids.push(Number(entry.name)); } } return ids; } export async function killProfileProcesses(uid: number): Promise { for (let attempt = 0; attempt < 100; attempt++) { const ids = await profileProcessIds(uid); if (ids.length === 0) return; for (const pid of ids) { try { Deno.kill(pid, "SIGKILL"); } catch (error) { if (!(error instanceof Deno.errors.NotFound)) throw error; } } await new Promise((resolve) => setTimeout(resolve, 10)); } throw new Error(`profile UID ${uid} still owns processes after cleanup`); } export async function readProfileFile( profile: RuntimeProfile, path: string, ): Promise { const stat = await Deno.lstat(path); if ( !stat.isFile || stat.isSymlink || stat.uid !== profile.uid || (stat.mode! & 0o077) !== 0 ) { throw new Error(`${path}: expected a profile-owned regular private file`); } return await Deno.readTextFile(path); }