1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4/// OS process functions
5///
6/// psutil is a good reference for cross-platform syscall voodoo:
7/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
8
9#include <uv.h> // for HANDLE (win32)
10
11#ifdef WIN32
12# include <tlhelp32.h> // for CreateToolhelp32Snapshot
13#endif
14
15#if defined(__FreeBSD__) // XXX: OpenBSD ?
16# include <string.h>
17# include <sys/types.h>
18# include <sys/user.h>
19#endif
20
21#if defined(__NetBSD__) || defined(__OpenBSD__)
22# include <sys/param.h>
23#endif
24
25#if defined(__APPLE__) || defined(BSD)
26# include <sys/sysctl.h>
27# include <pwd.h>
28#endif
29
30#include "nvim/globals.h"
31#include "nvim/log.h"
32#include "nvim/os/process.h"
33#include "nvim/os/os.h"
34#include "nvim/os/os_defs.h"
35#include "nvim/api/private/helpers.h"
36
37#ifdef INCLUDE_GENERATED_DECLARATIONS
38# include "os/process.c.generated.h"
39#endif
40
41#ifdef WIN32
42static bool os_proc_tree_kill_rec(HANDLE process, int sig)
43{
44 if (process == NULL) {
45 return false;
46 }
47 PROCESSENTRY32 pe;
48 DWORD pid = GetProcessId(process);
49
50 if (pid != 0) {
51 HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
52 if (h != INVALID_HANDLE_VALUE) {
53 pe.dwSize = sizeof(PROCESSENTRY32);
54 if (!Process32First(h, &pe)) {
55 goto theend;
56 }
57 do {
58 if (pe.th32ParentProcessID == pid) {
59 HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID);
60 if (ph != NULL) {
61 os_proc_tree_kill_rec(ph, sig);
62 CloseHandle(ph);
63 }
64 }
65 } while (Process32Next(h, &pe));
66 CloseHandle(h);
67 }
68 }
69
70theend:
71 return (bool)TerminateProcess(process, (unsigned int)sig);
72}
73/// Kills process `pid` and its descendants recursively.
74bool os_proc_tree_kill(int pid, int sig)
75{
76 assert(sig >= 0);
77 assert(sig == SIGTERM || sig == SIGKILL);
78 if (pid > 0) {
79 ILOG("terminating process tree: %d", pid);
80 HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid);
81 return os_proc_tree_kill_rec(h, sig);
82 } else {
83 ELOG("invalid pid: %d", pid);
84 }
85 return false;
86}
87#else
88/// Kills process group where `pid` is the process group leader.
89bool os_proc_tree_kill(int pid, int sig)
90{
91 assert(sig == SIGTERM || sig == SIGKILL);
92 if (pid == 0) {
93 // Never kill self (pid=0).
94 return false;
95 }
96 ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid);
97 return uv_kill(-pid, sig) == 0;
98}
99#endif
100
101/// Gets the process ids of the immediate children of process `ppid`.
102///
103/// @param ppid Process to inspect.
104/// @param[out,allocated] proc_list Child process ids.
105/// @param[out] proc_count Number of child processes.
106/// @return 0 on success, 1 if process not found, 2 on other error.
107int os_proc_children(int ppid, int **proc_list, size_t *proc_count)
108{
109 if (ppid < 0) {
110 return 2;
111 }
112
113 int *temp = NULL;
114 *proc_list = NULL;
115 *proc_count = 0;
116
117#ifdef WIN32
118 PROCESSENTRY32 pe;
119
120 // Snapshot of all processes.
121 HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
122 if (h == INVALID_HANDLE_VALUE) {
123 return 2;
124 }
125
126 pe.dwSize = sizeof(PROCESSENTRY32);
127 // Get root process.
128 if (!Process32First(h, &pe)) {
129 CloseHandle(h);
130 return 2;
131 }
132 // Collect processes whose parent matches `ppid`.
133 do {
134 if (pe.th32ParentProcessID == (DWORD)ppid) {
135 temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
136 temp[*proc_count] = (int)pe.th32ProcessID;
137 (*proc_count)++;
138 }
139 } while (Process32Next(h, &pe));
140 CloseHandle(h);
141
142#elif defined(__APPLE__) || defined(BSD)
143# if defined(__APPLE__)
144# define KP_PID(o) o.kp_proc.p_pid
145# define KP_PPID(o) o.kp_eproc.e_ppid
146# elif defined(__FreeBSD__)
147# define KP_PID(o) o.ki_pid
148# define KP_PPID(o) o.ki_ppid
149# else
150# define KP_PID(o) o.p_pid
151# define KP_PPID(o) o.p_ppid
152# endif
153# ifdef __NetBSD__
154 static int name[] = {
155 CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0
156 };
157# else
158 static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
159# endif
160
161 // Get total process count.
162 size_t len = 0;
163 int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0);
164 if (rv) {
165 return 2;
166 }
167
168 // Get ALL processes.
169# ifdef __NetBSD__
170 struct kinfo_proc2 *p_list = xmalloc(len);
171# else
172 struct kinfo_proc *p_list = xmalloc(len);
173# endif
174 rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0);
175 if (rv) {
176 xfree(p_list);
177 return 2;
178 }
179
180 // Collect processes whose parent matches `ppid`.
181 bool exists = false;
182 size_t p_count = len / sizeof(*p_list);
183 for (size_t i = 0; i < p_count; i++) {
184 exists = exists || KP_PID(p_list[i]) == ppid;
185 if (KP_PPID(p_list[i]) == ppid) {
186 temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
187 temp[*proc_count] = KP_PID(p_list[i]);
188 (*proc_count)++;
189 }
190 }
191 xfree(p_list);
192 if (!exists) {
193 return 1; // Process not found.
194 }
195
196#elif defined(__linux__)
197 char proc_p[256] = { 0 };
198 // Collect processes whose parent matches `ppid`.
199 // Rationale: children are defined in thread with same ID of process.
200 snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid);
201 FILE *fp = fopen(proc_p, "r");
202 if (fp == NULL) {
203 return 2; // Process not found, or /proc/…/children not supported.
204 }
205 int match_pid;
206 while (fscanf(fp, "%d", &match_pid) > 0) {
207 temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp));
208 temp[*proc_count] = match_pid;
209 (*proc_count)++;
210 }
211 fclose(fp);
212#endif
213
214 *proc_list = temp;
215 return 0;
216}
217
218#ifdef WIN32
219/// Gets various properties of the process identified by `pid`.
220///
221/// @param pid Process to inspect.
222/// @return Map of process properties, empty on error.
223Dictionary os_proc_info(int pid)
224{
225 Dictionary pinfo = ARRAY_DICT_INIT;
226 PROCESSENTRY32 pe;
227
228 // Snapshot of all processes. This is used instead of:
229 // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …)
230 // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376
231 HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
232 if (h == INVALID_HANDLE_VALUE) {
233 return pinfo; // Return empty.
234 }
235
236 pe.dwSize = sizeof(PROCESSENTRY32);
237 // Get root process.
238 if (!Process32First(h, &pe)) {
239 CloseHandle(h);
240 return pinfo; // Return empty.
241 }
242 // Find the process.
243 do {
244 if (pe.th32ProcessID == (DWORD)pid) {
245 break;
246 }
247 } while (Process32Next(h, &pe));
248 CloseHandle(h);
249
250 if (pe.th32ProcessID == (DWORD)pid) {
251 PUT(pinfo, "pid", INTEGER_OBJ(pid));
252 PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
253 PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile)));
254 }
255
256 return pinfo;
257}
258#endif
259
260/// Return true if process `pid` is running.
261bool os_proc_running(int pid)
262{
263 return uv_kill(pid, 0) == 0;
264}
265