diff options
-rw-r--r-- | src/nvim/api/vim.c | 34 | ||||
-rw-r--r-- | src/nvim/os/process.c | 103 | ||||
-rw-r--r-- | src/nvim/os/process.h | 2 | ||||
-rw-r--r-- | test/functional/api/proc_spec.lua | 53 |
4 files changed, 188 insertions, 4 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index dad67c5e4b..850f892c18 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,7 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/os/process.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" #include "nvim/ui.h" @@ -1478,3 +1479,36 @@ Array nvim_list_uis(void) { return ui_array(); } + +/// Gets the immediate children of process `pid`. +/// +/// @return Array of child process ids, or empty array if process not found. +Array nvim_get_proc_children(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Array proc_array = ARRAY_DICT_INIT; + int *proc_list = NULL; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %d", pid); + goto end; + } + + size_t proc_count; + int rv = os_proc_children((int)pid, &proc_list, &proc_count); + if (rv == 1) { + goto end; // Process not found; return empty list. + } else if (rv != 0) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%d error=%d", pid, rv); + goto end; + } + + for (size_t i = 0; i < proc_count; i++) { + ADD(proc_array, INTEGER_OBJ(proc_list[i])); + } + +end: + xfree(proc_list); + return proc_array; +} diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index eefc94faf6..da66d78e0d 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -6,6 +6,17 @@ # include <tlhelp32.h> // for CreateToolhelp32Snapshot #endif +#if defined(__FreeBSD__) // XXX: OpenBSD, NetBSD ? +# include <string.h> +# include <sys/types.h> +# include <sys/user.h> +#endif + +#if defined(__APPLE__) || defined(BSD) +# include <sys/sysctl.h> +# include <pwd.h> +#endif + #include "nvim/log.h" #include "nvim/os/process.h" #include "nvim/os/os.h" @@ -16,8 +27,7 @@ #endif #ifdef WIN32 -/// Kills process `pid` and its descendants recursively. -bool os_proc_tree_kill_rec(HANDLE process, int sig) +static bool os_proc_tree_kill_rec(HANDLE process, int sig) { if (process == NULL) { return false; @@ -35,7 +45,7 @@ bool os_proc_tree_kill_rec(HANDLE process, int sig) do { if (pe.th32ParentProcessID == pid) { - HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); + HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID); if (ph != NULL) { os_proc_tree_kill_rec(ph, sig); CloseHandle(ph); @@ -50,13 +60,14 @@ bool os_proc_tree_kill_rec(HANDLE process, int sig) theend: return (bool)TerminateProcess(process, (unsigned int)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); + HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid); return os_proc_tree_kill_rec(h, sig); } else { ELOG("invalid pid: %d", pid); @@ -85,3 +96,87 @@ bool os_proc_tree_kill(int pid, int sig) return false; } #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) +{ + // + // psutil is a good reference for cross-platform syscall voodoo: + // https://github.com/giampaolo/psutil/tree/master/psutil/arch + // + + int *temp = NULL; + *proc_list = NULL; + *proc_count = 0; + +#if 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 + static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; + + // 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. + struct kinfo_proc *p_list = xmalloc(len); + 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 1; // Process not found. + } + 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; +} + diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h index 9549ae9081..9a83942169 100644 --- a/src/nvim/os/process.h +++ b/src/nvim/os/process.h @@ -1,6 +1,8 @@ #ifndef NVIM_OS_PROCESS_H #define NVIM_OS_PROCESS_H +#include <stddef.h> + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/process.h.generated.h" #endif diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua new file mode 100644 index 0000000000..eee54f9465 --- /dev/null +++ b/test/functional/api/proc_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear, eq = helpers.clear, helpers.eq +local funcs = helpers.funcs +local nvim_argv = helpers.nvim_argv +local request = helpers.request +local retry = helpers.retry + +describe('api', function() + before_each(clear) + + describe('nvim_get_proc_children', function() + it('returns child process ids', function() + local this_pid = funcs.getpid() + + local job1 = funcs.jobstart(nvim_argv) + retry(nil, nil, function() + eq(1, #request('nvim_get_proc_children', this_pid)) + end) + + local job2 = funcs.jobstart(nvim_argv) + retry(nil, nil, function() + eq(2, #request('nvim_get_proc_children', this_pid)) + end) + + funcs.jobstop(job1) + retry(nil, nil, function() + eq(1, #request('nvim_get_proc_children', this_pid)) + end) + + funcs.jobstop(job2) + retry(nil, nil, function() + eq(0, #request('nvim_get_proc_children', this_pid)) + end) + end) + + it('validates input', function() + local status, rv = pcall(request, "nvim_get_proc_children", -1) + eq(false, status) + eq("Invalid pid: -1", string.match(rv, "Invalid.*")) + + status, rv = pcall(request, "nvim_get_proc_children", 0) + eq(false, status) + eq("Invalid pid: 0", string.match(rv, "Invalid.*")) + + -- Assume PID 99999999 does not exist. + status, rv = pcall(request, "nvim_get_proc_children", 99999999) + eq(true, status) + eq({}, rv) + end) + end) + +end) |