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 |
42 | static 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 | |
70 | theend: |
71 | return (bool)TerminateProcess(process, (unsigned int)sig); |
72 | } |
73 | /// Kills process `pid` and its descendants recursively. |
74 | bool 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. |
89 | bool 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. |
107 | int 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. |
223 | Dictionary 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. |
261 | bool os_proc_running(int pid) |
262 | { |
263 | return uv_kill(pid, 0) == 0; |
264 | } |
265 | |