mirror of
https://github.com/neovim/neovim.git
synced 2026-01-30 17:11:11 +10:00
287 lines
7.1 KiB
C
287 lines
7.1 KiB
C
/// OS process functions
|
|
///
|
|
/// psutil is a good reference for cross-platform syscall voodoo:
|
|
/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
|
|
|
|
// IWYU pragma: no_include <sys/param.h>
|
|
|
|
#include <assert.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <uv.h>
|
|
|
|
#ifdef MSWIN
|
|
# include <tlhelp32.h>
|
|
#endif
|
|
|
|
#if defined(__FreeBSD__)
|
|
# include <string.h>
|
|
# include <sys/types.h>
|
|
# include <sys/user.h>
|
|
#endif
|
|
|
|
#if defined(__NetBSD__) || defined(__OpenBSD__)
|
|
# include <sys/param.h>
|
|
#endif
|
|
|
|
#if defined(__APPLE__) || defined(BSD)
|
|
# include <sys/sysctl.h>
|
|
|
|
# include "nvim/macros_defs.h"
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
# include <stdio.h>
|
|
#endif
|
|
|
|
#include "nvim/log.h"
|
|
#include "nvim/memory.h"
|
|
#include "nvim/os/process.h"
|
|
|
|
#ifdef MSWIN
|
|
# include "nvim/api/private/helpers.h"
|
|
#endif
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "os/process.c.generated.h"
|
|
#endif
|
|
|
|
#ifdef MSWIN
|
|
static bool os_proc_tree_kill_rec(HANDLE process, int sig)
|
|
{
|
|
if (process == NULL) {
|
|
return false;
|
|
}
|
|
PROCESSENTRY32 pe;
|
|
DWORD pid = GetProcessId(process);
|
|
|
|
if (pid != 0) {
|
|
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (h != INVALID_HANDLE_VALUE) {
|
|
pe.dwSize = sizeof(PROCESSENTRY32);
|
|
if (!Process32First(h, &pe)) {
|
|
goto theend;
|
|
}
|
|
do {
|
|
if (pe.th32ParentProcessID == pid) {
|
|
HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID);
|
|
if (ph != NULL) {
|
|
os_proc_tree_kill_rec(ph, sig);
|
|
CloseHandle(ph);
|
|
}
|
|
}
|
|
} while (Process32Next(h, &pe));
|
|
CloseHandle(h);
|
|
}
|
|
}
|
|
|
|
theend:
|
|
return (bool)TerminateProcess(process, (unsigned)sig);
|
|
}
|
|
/// Kills process `pid` and its descendants recursively.
|
|
bool os_proc_tree_kill(int pid, int sig)
|
|
{
|
|
assert(sig >= 0);
|
|
assert(sig == SIGTERM || sig == SIGKILL);
|
|
if (pid > 0) {
|
|
ILOG("terminating process tree: %d", pid);
|
|
HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);
|
|
return os_proc_tree_kill_rec(h, sig);
|
|
} else {
|
|
ELOG("invalid pid: %d", pid);
|
|
}
|
|
return false;
|
|
}
|
|
#else
|
|
/// Kills process group where `pid` is the process group leader.
|
|
bool os_proc_tree_kill(int pid, int sig)
|
|
{
|
|
assert(sig == SIGTERM || sig == SIGKILL);
|
|
if (pid == 0) {
|
|
// Never kill self (pid=0).
|
|
return false;
|
|
}
|
|
ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid);
|
|
return uv_kill(-pid, sig) == 0;
|
|
}
|
|
#endif
|
|
|
|
/// Gets the process ids of the immediate children of process `ppid`.
|
|
///
|
|
/// @param ppid Process to inspect.
|
|
/// @param[out,allocated] proc_list Child process ids.
|
|
/// @param[out] proc_count Number of child processes.
|
|
/// @return 0 on success, 1 if process not found, 2 on other error.
|
|
int os_proc_children(int ppid, int **proc_list, size_t *proc_count)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (ppid < 0) {
|
|
return 2;
|
|
}
|
|
|
|
int *temp = NULL;
|
|
*proc_list = NULL;
|
|
*proc_count = 0;
|
|
|
|
#ifdef MSWIN
|
|
PROCESSENTRY32 pe;
|
|
|
|
// Snapshot of all processes.
|
|
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
return 2;
|
|
}
|
|
|
|
pe.dwSize = sizeof(PROCESSENTRY32);
|
|
// Get root process.
|
|
if (!Process32First(h, &pe)) {
|
|
CloseHandle(h);
|
|
return 2;
|
|
}
|
|
// Collect processes whose parent matches `ppid`.
|
|
do {
|
|
if (pe.th32ParentProcessID == (DWORD)ppid) {
|
|
temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
|
|
temp[*proc_count] = (int)pe.th32ProcessID;
|
|
(*proc_count)++;
|
|
}
|
|
} while (Process32Next(h, &pe));
|
|
CloseHandle(h);
|
|
|
|
#elif defined(__APPLE__) || defined(BSD)
|
|
# if defined(__APPLE__)
|
|
# define KP_PID(o) o.kp_proc.p_pid
|
|
# define KP_PPID(o) o.kp_eproc.e_ppid
|
|
# elif defined(__FreeBSD__)
|
|
# define KP_PID(o) o.ki_pid
|
|
# define KP_PPID(o) o.ki_ppid
|
|
# else
|
|
# define KP_PID(o) o.p_pid
|
|
# define KP_PPID(o) o.p_ppid
|
|
# endif
|
|
# ifdef __NetBSD__
|
|
static int name[] = {
|
|
CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0
|
|
};
|
|
# else
|
|
static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
|
|
# endif
|
|
|
|
// Get total process count.
|
|
size_t len = 0;
|
|
int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0);
|
|
if (rv) {
|
|
return 2;
|
|
}
|
|
|
|
// Get ALL processes.
|
|
# ifdef __NetBSD__
|
|
struct kinfo_proc2 *p_list = xmalloc(len);
|
|
# else
|
|
struct kinfo_proc *p_list = xmalloc(len);
|
|
# endif
|
|
rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0);
|
|
if (rv) {
|
|
xfree(p_list);
|
|
return 2;
|
|
}
|
|
|
|
// Collect processes whose parent matches `ppid`.
|
|
bool exists = false;
|
|
size_t p_count = len / sizeof(*p_list);
|
|
for (size_t i = 0; i < p_count; i++) {
|
|
exists = exists || KP_PID(p_list[i]) == ppid;
|
|
if (KP_PPID(p_list[i]) == ppid) {
|
|
temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
|
|
temp[*proc_count] = KP_PID(p_list[i]);
|
|
(*proc_count)++;
|
|
}
|
|
}
|
|
xfree(p_list);
|
|
if (!exists) {
|
|
return 1; // Process not found.
|
|
}
|
|
|
|
#elif defined(__linux__)
|
|
char proc_p[256] = { 0 };
|
|
// Collect processes whose parent matches `ppid`.
|
|
// Rationale: children are defined in thread with same ID of process.
|
|
snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid);
|
|
FILE *fp = fopen(proc_p, "r");
|
|
if (fp == NULL) {
|
|
return 2; // Process not found, or /proc/…/children not supported.
|
|
}
|
|
int match_pid;
|
|
while (fscanf(fp, "%d", &match_pid) > 0) {
|
|
temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
|
|
temp[*proc_count] = match_pid;
|
|
(*proc_count)++;
|
|
}
|
|
fclose(fp);
|
|
#endif
|
|
|
|
*proc_list = temp;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MSWIN
|
|
/// Gets various properties of the process identified by `pid`.
|
|
///
|
|
/// @param pid Process to inspect.
|
|
/// @return Map of process properties, empty on error.
|
|
Dictionary os_proc_info(int pid, Arena *arena)
|
|
{
|
|
Dictionary pinfo = ARRAY_DICT_INIT;
|
|
PROCESSENTRY32 pe;
|
|
|
|
// Snapshot of all processes. This is used instead of:
|
|
// OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …)
|
|
// to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376
|
|
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
return pinfo; // Return empty.
|
|
}
|
|
|
|
pe.dwSize = sizeof(PROCESSENTRY32);
|
|
// Get root process.
|
|
if (!Process32First(h, &pe)) {
|
|
CloseHandle(h);
|
|
return pinfo; // Return empty.
|
|
}
|
|
// Find the process.
|
|
do {
|
|
if (pe.th32ProcessID == (DWORD)pid) {
|
|
break;
|
|
}
|
|
} while (Process32Next(h, &pe));
|
|
CloseHandle(h);
|
|
|
|
if (pe.th32ProcessID == (DWORD)pid) {
|
|
pinfo = arena_dict(arena, 3);
|
|
PUT_C(pinfo, "pid", INTEGER_OBJ(pid));
|
|
PUT_C(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
|
|
PUT_C(pinfo, "name", CSTR_TO_ARENA_OBJ(arena, pe.szExeFile));
|
|
}
|
|
|
|
return pinfo;
|
|
}
|
|
#endif
|
|
|
|
/// Return true if process `pid` is running.
|
|
bool os_proc_running(int pid)
|
|
{
|
|
int err = uv_kill(pid, 0);
|
|
// If there is no error the process must be running.
|
|
if (err == 0) {
|
|
return true;
|
|
}
|
|
// If the error is ESRCH then the process is not running.
|
|
if (err == UV_ESRCH) {
|
|
return false;
|
|
}
|
|
// If the process is running and owned by another user we get EPERM. With
|
|
// other errors the process might be running, assuming it is then.
|
|
return true;
|
|
}
|