69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import type { RuntimeProfile } from "./runtime.ts";
|
|
|
|
export function agentCommand(
|
|
profile: RuntimeProfile,
|
|
command: string,
|
|
args: string[],
|
|
options: Omit<Deno.CommandOptions, "args" | "uid" | "gid"> = {},
|
|
): 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<number[]> {
|
|
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<void> {
|
|
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<string> {
|
|
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);
|
|
}
|