From de86f824835b1556cc0070dd5720cdae484a0296 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 11 Mar 2018 21:46:14 +0100 Subject: win: os_proc_tree_kill() XXX: comment at https://stackoverflow.com/q/1173342 : > Windows recycles PIDs quite fast, you have to be extra careful not > to kill unrelated processes. These APIs will report PPIDs for long > dead processes whose PIDs may have been recycled. Check the parent > start date to make sure it is related to the processes you spawned. --- src/nvim/os/process.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/nvim/os/process.c (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c new file mode 100644 index 0000000000..eefc94faf6 --- /dev/null +++ b/src/nvim/os/process.c @@ -0,0 +1,87 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include // for HANDLE (win32) +#ifdef WIN32 +# include // for CreateToolhelp32Snapshot +#endif + +#include "nvim/log.h" +#include "nvim/os/process.h" +#include "nvim/os/os.h" +#include "nvim/os/os_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.c.generated.h" +#endif + +#ifdef WIN32 +/// Kills process `pid` and its descendants recursively. +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 int)sig); +} +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); + int pgid = getpgid(pid); + if (pgid > 0) { // Ignore error. Never kill self (pid=0). + if (pgid == pid) { + ILOG("sending %s to process group: -%d", + sig == SIGTERM ? "SIGTERM" : "SIGKILL", pgid); + int rv = uv_kill(-pgid, sig); + return rv == 0; + } else { + // Should never happen, because process_spawn() did setsid() in the child. + ELOG("pgid %d != pid %d", pgid, pid); + } + } else { + ELOG("getpgid(%d) returned %d", pid, pgid); + } + return false; +} +#endif -- cgit From dbad797edd4636f830abd7ade1138a1a27ac30d2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 11 Mar 2018 21:47:12 +0100 Subject: API: nvim_get_proc_children() ref https://github.com/libuv/libuv/pull/836 --- src/nvim/os/process.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 4 deletions(-) (limited to 'src/nvim/os/process.c') 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 // for CreateToolhelp32Snapshot #endif +#if defined(__FreeBSD__) // XXX: OpenBSD, NetBSD ? +# include +# include +# include +#endif + +#if defined(__APPLE__) || defined(BSD) +# include +# include +#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; +} + -- cgit From 12af7016e23e7b7f507dc99a1b73e64d0bb5ccf3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 14 Mar 2018 23:26:37 +0100 Subject: nvim_get_proc_children: fallback to shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /proc/…/children may be unavailable because of an unset kernel option. Fallback to `pgrep` invoked in a shell. --- src/nvim/os/process.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index da66d78e0d..80c2dad64d 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include // for HANDLE (win32) + #ifdef WIN32 # include // for CreateToolhelp32Snapshot #endif @@ -165,7 +166,7 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) 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. + return 2; // Process not found, or /proc/…/children not supported. } int match_pid; while (fscanf(fp, "%d", &match_pid) > 0) { -- cgit From 330e5acbcec00d1bc43a9c1110c4325fa62ba9ad Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 15 Mar 2018 02:33:20 +0100 Subject: win: nvim_get_proc_children() TODO: Raymond Chen explains[1] racy behavior of the CreateToolhelp32Snapshot approach. Better approach: > create a job object and put process P in it. Then call > QueryInformationJobObject with JobObjectBasicProcessIdList to get the > list of child processes. [1] "Why is CreateToolhelp32Snapshot returning incorrect parent process IDs all of a sudden?" https://blogs.msdn.microsoft.com/oldnewthing/20150403-00/?p=44313 --- src/nvim/os/process.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 80c2dad64d..09769925ac 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -43,7 +43,6 @@ static bool os_proc_tree_kill_rec(HANDLE process, int sig) if (!Process32First(h, &pe)) { goto theend; } - do { if (pe.th32ParentProcessID == pid) { HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID); @@ -53,7 +52,6 @@ static bool os_proc_tree_kill_rec(HANDLE process, int sig) } } } while (Process32Next(h, &pe)); - CloseHandle(h); } } @@ -111,11 +109,40 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) // https://github.com/giampaolo/psutil/tree/master/psutil/arch // + if (ppid < 0) { + return 2; + } + int *temp = NULL; *proc_list = NULL; *proc_count = 0; -#if defined(__APPLE__) || defined(BSD) +#ifdef WIN32 + 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 -- cgit From a034d4b69d6032b3431c10b8a11c998551700fc2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 16 Mar 2018 05:13:38 +0100 Subject: API: nvim_get_proc() TODO: "exepath" field (win32: QueryFullProcessImageName()) On unix-likes `ps` is used because the platform-specific APIs are a nightmare. For reference, below is a (incomplete) attempt: diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 09769925aca5..99afbbf290c1 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -208,3 +210,60 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) return 0; } +/// 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) +{ + Dictionary pinfo = ARRAY_DICT_INIT; +#ifdef WIN32 + +#elif defined(__APPLE__) + char buf[PROC_PIDPATHINFO_MAXSIZE]; + if (proc_pidpath(pid, buf, sizeof(buf))) { + name = getName(buf); + PUT(pinfo, "exepath", STRING_OBJ(cstr_to_string(buf))); + return name; + } else { + ILOG("proc_pidpath() failed for pid: %d", pid); + } +#elif defined(BSD) +# if defined(__FreeBSD__) +# define KP_COMM(o) o.ki_comm +# else +# define KP_COMM(o) o.p_comm +# endif + struct kinfo_proc *proc = kinfo_getproc(pid); + if (proc) { + PUT(pinfo, "name", cstr_to_string(KP_COMM(proc))); + xfree(proc); + } else { + ILOG("kinfo_getproc() failed for pid: %d", pid); + } + +#elif defined(__linux__) + char fname[256] = { 0 }; + char buf[MAXPATHL]; + snprintf(fname, sizeof(fname), "/proc/%d/comm", pid); + FILE *fp = fopen(fname, "r"); + // FileDescriptor *f = file_open_new(&error, fname, kFileReadOnly, 0); + // ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, + // const size_t size) + if (fp == NULL) { + ILOG("fopen() of /proc/%d/comm failed", pid); + } else { + size_t n = fread(buf, sizeof(char), sizeof(buf) - 1, fp); + if (n == 0) { + WLOG("fread() of /proc/%d/comm failed", pid); + } else { + size_t end = MIN(sizeof(buf) - 1, n); + end = (end > 0 && buf[end - 1] == '\n') ? end - 1 : end; + buf[end] = '\0'; + PUT(pinfo, "name", STRING_OBJ(cstr_to_string(buf))); + } + } + fclose(fp); +#endif + return pinfo; +} --- src/nvim/os/process.c | 57 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 7 deletions(-) (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 09769925ac..e23ba8a4ee 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -1,6 +1,11 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +/// OS process functions +/// +/// psutil is a good reference for cross-platform syscall voodoo: +/// https://github.com/giampaolo/psutil/tree/master/psutil/arch + #include // for HANDLE (win32) #ifdef WIN32 @@ -18,10 +23,12 @@ # include #endif +#include "nvim/globals.h" #include "nvim/log.h" #include "nvim/os/process.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" +#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/process.c.generated.h" @@ -104,11 +111,6 @@ bool os_proc_tree_kill(int pid, int sig) /// @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 - // - if (ppid < 0) { return 2; } @@ -122,13 +124,13 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) // Snapshot of all processes. HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if(h == INVALID_HANDLE_VALUE) { + if (h == INVALID_HANDLE_VALUE) { return 2; } pe.dwSize = sizeof(PROCESSENTRY32); // Get root process. - if(!Process32First(h, &pe)) { + if (!Process32First(h, &pe)) { CloseHandle(h); return 2; } @@ -208,3 +210,44 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) return 0; } +#ifdef WIN32 +/// 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) +{ + 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) { + PUT(pinfo, "pid", INTEGER_OBJ(pid)); + PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID)); + PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile))); + } + + return pinfo; +} +#endif -- cgit From e9cf40f2b658970d37e0b1a8f70b64073464c648 Mon Sep 17 00:00:00 2001 From: Utkarsh Anand Date: Thu, 29 Mar 2018 14:07:49 +0530 Subject: build/NetBSD: use kinfo_proc2; undef uint64_t (#8197) closes #8196 For historical reasons, uint64_t and friends are defined both as typedefs and macros. Some platforms that do that define the macros as identity (#define uint64_t uint64_t), others like NetBSD define to the backing type (#define uint64_t __uint64_t). This is normally transparent, except when multiple levels of macro expansions are used inconsistently. --- src/nvim/os/process.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index e23ba8a4ee..60ca890e0e 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -12,12 +12,16 @@ # include // for CreateToolhelp32Snapshot #endif -#if defined(__FreeBSD__) // XXX: OpenBSD, NetBSD ? +#if defined(__FreeBSD__) // XXX: OpenBSD ? # include # include # include #endif +#if defined(__NetBSD__) +# include +#endif + #if defined(__APPLE__) || defined(BSD) # include # include @@ -155,7 +159,11 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) # 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; @@ -165,7 +173,11 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) } // 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); -- cgit From e54ff10d44a18b59350503accc68811e4a5be29f Mon Sep 17 00:00:00 2001 From: James McCoy Date: Thu, 29 Mar 2018 10:32:12 -0400 Subject: lint --- src/nvim/os/process.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 60ca890e0e..63eea9022b 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -160,7 +160,9 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) # 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 }; + 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 -- cgit From 9b7ce004867e0ac3692afbef0cc15e2474242057 Mon Sep 17 00:00:00 2001 From: Utkarsh Anand Date: Mon, 2 Apr 2018 14:38:11 +0530 Subject: build/OpenBSD: need -lpthread -lc++abi for LuaJIT (#8215) --- src/nvim/os/process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/os/process.c') diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 63eea9022b..a67e7487eb 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -18,7 +18,7 @@ # include #endif -#if defined(__NetBSD__) +#if defined(__NetBSD__) || defined(__OpenBSD__) # include #endif -- cgit