aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/api/vim.c34
-rw-r--r--src/nvim/os/process.c103
-rw-r--r--src/nvim/os/process.h2
-rw-r--r--test/functional/api/proc_spec.lua53
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)