// // RESPONSE // {"ok":true,"killed":true,"locks":["…"],"error":null} // {"ok":true,"msg":"Not running"} // {"ok":false,"killed":false,"locks":[…], // "error":"Process still alive after SIGKILL (D-state) — lock may persist"} // {"ok":false,"error":"Invalid id"|"No stat file — script may not be running" // |"No valid PID in stat file"} // // DEPENDS ON // include/scheduler.php vv_job_stat_path() // /tmp/unraid_locks lock files written by common.sh's locking helper // ═══════════════════════════════════════════════════════════════════════════════════════════════ header('Content-Type: application/json'); require_once dirname(__DIR__) . '/include/scheduler.php'; $id = trim($_POST['id'] ?? ''); if (!$id || !preg_match('/^[a-zA-Z0-9_.\/\-]+\.sh$/', $id) || str_contains($id, '..')) { echo json_encode(['ok' => false, 'error' => 'Invalid id']); exit; } $statFile = vv_job_stat_path($id); if (!file_exists($statFile)) { echo json_encode(['ok' => false, 'error' => 'No stat file — script may not be running']); exit; } $stat = json_decode(file_get_contents($statFile) ?: '{}', true) ?: []; if (($stat['status'] ?? '') !== 'running') { echo json_encode(['ok' => true, 'msg' => 'Not running']); exit; } $pid = (int)($stat['pid'] ?? 0); if ($pid < 2) { echo json_encode(['ok' => false, 'error' => 'No valid PID in stat file']); exit; } // Kill the whole process group so the script and all its children die together. // pgid is usually the same as the session leader PID from run_job.sh. $pgid = (int)trim(shell_exec("ps -o pgid= -p $pid 2>/dev/null") ?: '0'); if ($pgid > 1) { shell_exec("kill -TERM -$pgid 2>/dev/null"); } else { // Fallback: kill the direct PID and its children shell_exec("pkill -TERM -P $pid 2>/dev/null"); shell_exec("kill -TERM $pid 2>/dev/null"); } // Give it up to 3s to exit gracefully $dead = false; for ($i = 0; $i < 6; $i++) { usleep(500000); if (!file_exists("/proc/$pid")) { $dead = true; break; } } // Force-kill if still alive if (!$dead) { if ($pgid > 1) shell_exec("kill -KILL -$pgid 2>/dev/null"); shell_exec("pkill -KILL -P $pid 2>/dev/null"); shell_exec("kill -KILL $pid 2>/dev/null"); usleep(300000); $dead = !file_exists("/proc/$pid"); } // Clear any lock files in /tmp/unraid_locks whose content matches this PID $lockDir = '/tmp/unraid_locks'; $cleared = []; foreach (glob("$lockDir/*.lock") ?: [] as $lf) { $content = trim(file_get_contents($lf) ?: ''); $lockPid = (int)explode(':', $content)[0]; if ($lockPid === $pid || !file_exists("/proc/$lockPid")) { @unlink($lf); $cleared[] = basename($lf); } } // Also clear by script name in case PID rotated $scriptBase = basename($id, '.sh'); $namedLock = "$lockDir/{$scriptBase}.lock"; if (file_exists($namedLock)) { @unlink($namedLock); if (!in_array(basename($namedLock), $cleared)) $cleared[] = basename($namedLock); } // Update stat file — only clear pid if actually dead (D-state processes survive SIGKILL) $now = time(); $stat['status'] = $dead ? 'stopped' : 'running'; $stat['end'] = $dead ? $now : ($stat['end'] ?? null); $stat['exit'] = $dead ? -1 : ($stat['exit'] ?? null); if ($dead) unset($stat['pid']); file_put_contents($statFile, json_encode($stat)); echo json_encode([ 'ok' => $dead, 'killed' => $dead, 'locks' => $cleared, 'error' => $dead ? null : 'Process still alive after SIGKILL (D-state) — lock may persist', ]);