aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os/proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/os/proc.c')
-rw-r--r--src/nvim/os/proc.c286
1 files changed, 286 insertions, 0 deletions
diff --git a/src/nvim/os/proc.c b/src/nvim/os/proc.c
new file mode 100644
index 0000000000..053f5f3ba0
--- /dev/null
+++ b/src/nvim/os/proc.c
@@ -0,0 +1,286 @@
+/// 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/proc.h"
+
+#ifdef MSWIN
+# include "nvim/api/private/helpers.h"
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/proc.c.generated.h"
+#endif
+
+#ifdef MSWIN
+static bool os_proc_tree_kill_rec(HANDLE proc, int sig)
+{
+ if (proc == NULL) {
+ return false;
+ }
+ PROCESSENTRY32 pe;
+ DWORD pid = GetProcessId(proc);
+
+ 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(proc, (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.
+Dict os_proc_info(int pid, Arena *arena)
+{
+ Dict 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;
+}