neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }