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