source dump of claude code
at main 184 lines 6.4 kB view raw
1import { 2 execFileNoThrowWithCwd, 3 execSyncWithDefaults_DEPRECATED, 4} from './execFileNoThrow.js' 5 6// This file contains platform-agnostic implementations of common `ps` type commands. 7// When adding new code to this file, make sure to handle: 8// - Win32, as `ps` within cygwin and WSL may not behave as expected, particularly when attempting to access processes on the host. 9// - Unix vs BSD-style `ps` have different options. 10 11/** 12 * Check if a process with the given PID is running (signal 0 probe). 13 * 14 * PID ≤ 1 returns false (0 is current process group, 1 is init). 15 * 16 * Note: `process.kill(pid, 0)` throws EPERM when the process exists but is 17 * owned by another user. This reports such processes as NOT running, which 18 * is conservative for lock recovery (we won't steal a live lock). 19 */ 20export function isProcessRunning(pid: number): boolean { 21 if (pid <= 1) return false 22 try { 23 process.kill(pid, 0) 24 return true 25 } catch { 26 return false 27 } 28} 29 30/** 31 * Gets the ancestor process chain for a given process (up to maxDepth levels) 32 * @param pid - The starting process ID 33 * @param maxDepth - Maximum number of ancestors to fetch (default: 10) 34 * @returns Array of ancestor PIDs from immediate parent to furthest ancestor 35 */ 36export async function getAncestorPidsAsync( 37 pid: string | number, 38 maxDepth = 10, 39): Promise<number[]> { 40 if (process.platform === 'win32') { 41 // For Windows, use a PowerShell script that walks the process tree 42 const script = ` 43 $pid = ${String(pid)} 44 $ancestors = @() 45 for ($i = 0; $i -lt ${maxDepth}; $i++) { 46 $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$pid" -ErrorAction SilentlyContinue 47 if (-not $proc -or -not $proc.ParentProcessId -or $proc.ParentProcessId -eq 0) { break } 48 $pid = $proc.ParentProcessId 49 $ancestors += $pid 50 } 51 $ancestors -join ',' 52 `.trim() 53 54 const result = await execFileNoThrowWithCwd( 55 'powershell.exe', 56 ['-NoProfile', '-Command', script], 57 { timeout: 3000 }, 58 ) 59 if (result.code !== 0 || !result.stdout?.trim()) { 60 return [] 61 } 62 return result.stdout 63 .trim() 64 .split(',') 65 .filter(Boolean) 66 .map(p => parseInt(p, 10)) 67 .filter(p => !isNaN(p)) 68 } 69 70 // For Unix, use a shell command that walks up the process tree 71 // This uses a single process invocation instead of multiple sequential calls 72 const script = `pid=${String(pid)}; for i in $(seq 1 ${maxDepth}); do ppid=$(ps -o ppid= -p $pid 2>/dev/null | tr -d ' '); if [ -z "$ppid" ] || [ "$ppid" = "0" ] || [ "$ppid" = "1" ]; then break; fi; echo $ppid; pid=$ppid; done` 73 74 const result = await execFileNoThrowWithCwd('sh', ['-c', script], { 75 timeout: 3000, 76 }) 77 if (result.code !== 0 || !result.stdout?.trim()) { 78 return [] 79 } 80 return result.stdout 81 .trim() 82 .split('\n') 83 .filter(Boolean) 84 .map(p => parseInt(p, 10)) 85 .filter(p => !isNaN(p)) 86} 87 88/** 89 * Gets the command line for a given process 90 * @param pid - The process ID to get the command for 91 * @returns The command line string, or null if not found 92 * @deprecated Use getAncestorCommandsAsync instead 93 */ 94export function getProcessCommand(pid: string | number): string | null { 95 try { 96 const pidStr = String(pid) 97 const command = 98 process.platform === 'win32' 99 ? `powershell.exe -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=${pidStr}\\").CommandLine"` 100 : `ps -o command= -p ${pidStr}` 101 102 const result = execSyncWithDefaults_DEPRECATED(command, { timeout: 1000 }) 103 return result ? result.trim() : null 104 } catch { 105 return null 106 } 107} 108 109/** 110 * Gets the command lines for a process and its ancestors in a single call 111 * @param pid - The starting process ID 112 * @param maxDepth - Maximum depth to traverse (default: 10) 113 * @returns Array of command strings for the process chain 114 */ 115export async function getAncestorCommandsAsync( 116 pid: string | number, 117 maxDepth = 10, 118): Promise<string[]> { 119 if (process.platform === 'win32') { 120 // For Windows, use a PowerShell script that walks the process tree and collects commands 121 const script = ` 122 $currentPid = ${String(pid)} 123 $commands = @() 124 for ($i = 0; $i -lt ${maxDepth}; $i++) { 125 $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$currentPid" -ErrorAction SilentlyContinue 126 if (-not $proc) { break } 127 if ($proc.CommandLine) { $commands += $proc.CommandLine } 128 if (-not $proc.ParentProcessId -or $proc.ParentProcessId -eq 0) { break } 129 $currentPid = $proc.ParentProcessId 130 } 131 $commands -join [char]0 132 `.trim() 133 134 const result = await execFileNoThrowWithCwd( 135 'powershell.exe', 136 ['-NoProfile', '-Command', script], 137 { timeout: 3000 }, 138 ) 139 if (result.code !== 0 || !result.stdout?.trim()) { 140 return [] 141 } 142 return result.stdout.split('\0').filter(Boolean) 143 } 144 145 // For Unix, use a shell command that walks up the process tree and collects commands 146 // Using null byte as separator to handle commands with newlines 147 const script = `currentpid=${String(pid)}; for i in $(seq 1 ${maxDepth}); do cmd=$(ps -o command= -p $currentpid 2>/dev/null); if [ -n "$cmd" ]; then printf '%s\\0' "$cmd"; fi; ppid=$(ps -o ppid= -p $currentpid 2>/dev/null | tr -d ' '); if [ -z "$ppid" ] || [ "$ppid" = "0" ] || [ "$ppid" = "1" ]; then break; fi; currentpid=$ppid; done` 148 149 const result = await execFileNoThrowWithCwd('sh', ['-c', script], { 150 timeout: 3000, 151 }) 152 if (result.code !== 0 || !result.stdout?.trim()) { 153 return [] 154 } 155 return result.stdout.split('\0').filter(Boolean) 156} 157 158/** 159 * Gets the child process IDs for a given process 160 * @param pid - The parent process ID 161 * @returns Array of child process IDs as numbers 162 */ 163export function getChildPids(pid: string | number): number[] { 164 try { 165 const pidStr = String(pid) 166 const command = 167 process.platform === 'win32' 168 ? `powershell.exe -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ParentProcessId=${pidStr}\\").ProcessId"` 169 : `pgrep -P ${pidStr}` 170 171 const result = execSyncWithDefaults_DEPRECATED(command, { timeout: 1000 }) 172 if (!result) { 173 return [] 174 } 175 return result 176 .trim() 177 .split('\n') 178 .filter(Boolean) 179 .map(p => parseInt(p, 10)) 180 .filter(p => !isNaN(p)) 181 } catch { 182 return [] 183 } 184}