Files
mcp-proc/src/lib/procfs-writer.ts
2025-10-11 03:22:03 +02:00

158 líneas
4.2 KiB
TypeScript

import * as fs from 'fs/promises';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { SysctlParam } from '../types/procfs';
const execAsync = promisify(exec);
/**
* ProcFS Writer - Handles writing to /proc filesystem and sysctl
*/
export class ProcFSWriter {
private procRoot: string;
constructor(procRoot = '/proc') {
this.procRoot = procRoot;
}
/**
* Write value to a procfs file
*/
async writeRaw(filePath: string, value: string | number): Promise<void> {
const fullPath = path.join(this.procRoot, filePath);
try {
await fs.writeFile(fullPath, String(value), 'utf-8');
} catch (error) {
throw new Error(`Failed to write to ${fullPath}: ${(error as Error).message}`);
}
}
/**
* Read sysctl parameter
*/
async readSysctl(key: string): Promise<SysctlParam> {
try {
const { stdout } = await execAsync(`sysctl -n ${key}`);
const value = stdout.trim();
// Try to parse as number
const numValue = parseFloat(value);
const finalValue = isNaN(numValue) ? value : numValue;
// Check if writable by attempting to read the file
const sysPath = `/proc/sys/${key.replace(/\./g, '/')}`;
let writable = false;
try {
await fs.access(sysPath, fs.constants.W_OK);
writable = true;
} catch {
writable = false;
}
return {
key,
value: finalValue,
writable,
};
} catch (error) {
throw new Error(`Failed to read sysctl ${key}: ${(error as Error).message}`);
}
}
/**
* Write sysctl parameter
*/
async writeSysctl(key: string, value: string | number): Promise<SysctlParam> {
try {
await execAsync(`sysctl -w ${key}=${value}`);
return await this.readSysctl(key);
} catch (error) {
throw new Error(`Failed to write sysctl ${key}: ${(error as Error).message}`);
}
}
/**
* List all sysctl parameters
*/
async listSysctl(): Promise<SysctlParam[]> {
try {
const { stdout } = await execAsync('sysctl -a');
const lines = stdout.split('\n');
const params: SysctlParam[] = [];
for (const line of lines) {
if (!line.trim()) continue;
const match = line.match(/^([^\s=]+)\s*=\s*(.+)$/);
if (match) {
const key = match[1].trim();
const value = match[2].trim();
const numValue = parseFloat(value);
params.push({
key,
value: isNaN(numValue) ? value : numValue,
writable: false, // Would need to check each individually
});
}
}
return params;
} catch (error) {
throw new Error(`Failed to list sysctl parameters: ${(error as Error).message}`);
}
}
/**
* Set process priority (nice value)
*/
async setProcessPriority(pid: number, priority: number): Promise<void> {
if (priority < -20 || priority > 19) {
throw new Error('Priority must be between -20 and 19');
}
try {
await execAsync(`renice ${priority} -p ${pid}`);
} catch (error) {
throw new Error(`Failed to set priority for PID ${pid}: ${(error as Error).message}`);
}
}
/**
* Set CPU affinity for a process
*/
async setProcessAffinity(pid: number, cpuList: string): Promise<void> {
try {
await execAsync(`taskset -cp ${cpuList} ${pid}`);
} catch (error) {
throw new Error(`Failed to set CPU affinity for PID ${pid}: ${(error as Error).message}`);
}
}
/**
* Send signal to a process
*/
async sendSignal(pid: number, signal: string | number = 'TERM'): Promise<void> {
try {
await execAsync(`kill -${signal} ${pid}`);
} catch (error) {
throw new Error(`Failed to send signal ${signal} to PID ${pid}: ${(error as Error).message}`);
}
}
/**
* Set OOM score adjustment for a process
*/
async setOOMScore(pid: number, score: number): Promise<void> {
if (score < -1000 || score > 1000) {
throw new Error('OOM score must be between -1000 and 1000');
}
try {
await this.writeRaw(`${pid}/oom_score_adj`, score);
} catch (error) {
throw new Error(`Failed to set OOM score for PID ${pid}: ${(error as Error).message}`);
}
}
}