text: /*
   * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil.
   * All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include <Python.h>
  #include <assert.h>
  #include <errno.h>
  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>
  #include <fcntl.h>
  #include <sys/types.h>
  #include <sys/param.h>
  #include <sys/sysctl.h>
  #include <sys/user.h>
  #include <sys/proc.h>
  #include <signal.h>
  #include <kvm.h>
  #include <netdb.h>  // for NI_MAXHOST
  #include <sys/socket.h>
  #define _KERNEL  // for DTYPE_*
  #include <sys/file.h>
  #undef _KERNEL
  #include <arpa/inet.h> // for inet_ntoa()
  
  #include "../../_psutil_common.h"
  #include "../../_psutil_posix.h"
  
  #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0)
  // #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0)
  
  
  // ============================================================================
  // Utility functions
  // ============================================================================
  
  
  int
  psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) {
      // Fills a kinfo_proc struct based on process pid.
      int ret;
      int mib[6];
      size_t size = sizeof(struct kinfo_proc);
  
      mib[0] = CTL_KERN;
      mib[1] = KERN_PROC;
      mib[2] = KERN_PROC_PID;
      mib[3] = pid;
      mib[4] = size;
      mib[5] = 1;
  
      ret = sysctl((int*)mib, 6, proc, &size, NULL, 0);
      if (ret == -1) {
          PyErr_SetFromErrno(PyExc_OSError);
          return -1;
      }
      // sysctl stores 0 in the size if we can't find the process information.
      if (size == 0) {
          NoSuchProcess("sysctl (size = 0)");
          return -1;
      }
      return 0;
  }
  
  
  struct kinfo_file *
  kinfo_getfile(pid_t pid, int* cnt) {
      // Mimic's FreeBSD kinfo_file call, taking a pid and a ptr to an
      // int as arg and returns an array with cnt struct kinfo_file.
      int mib[6];
      size_t len;
      struct kinfo_file* kf;
      mib[0] = CTL_KERN;
      mib[1] = KERN_FILE;
      mib[2] = KERN_FILE_BYPID;
      mib[3] = pid;
      mib[4] = sizeof(struct kinfo_file);
      mib[5] = 0;
  
      /* get the size of what would be returned */
      if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
          PyErr_SetFromErrno(PyExc_OSError);
          return NULL;
      }
      if ((kf = malloc(len)) == NULL) {
          PyErr_NoMemory();
          return NULL;
      }
      mib[5] = (int)(len / sizeof(struct kinfo_file));
      if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) {
          free(kf);
          PyErr_SetFromErrno(PyExc_OSError);
          return NULL;
      }
  
      *cnt = (int)(len / sizeof(struct kinfo_file));
      return kf;
  }
  
  
  // ============================================================================
  // APIS
  // ============================================================================
  
  int
  psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount) {
      // Returns a list of all BSD processes on the system.  This routine
      // allocates the list and puts it in *procList and a count of the
      // number of entries in *procCount.  You are responsible for freeing
      // this list (use "free" from System framework).
      // On success, the function returns 0.
      // On error, the function returns a BSD errno value.
      struct kinfo_proc *result;
      // Declaring name as const requires us to cast it when passing it to
      // sysctl because the prototype doesn't include the const modifier.
      char errbuf[_POSIX2_LINE_MAX];
      int cnt;
      kvm_t *kd;
  
      assert(procList != NULL);
      assert(*procList == NULL);
      assert(procCount != NULL);
  
      kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
      if (! kd) {
          convert_kvm_err("kvm_openfiles", errbuf);
          return 1;
      }
  
      result = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &cnt);
      if (result == NULL) {
          PyErr_Format(PyExc_RuntimeError, "kvm_getprocs syscall failed");
          kvm_close(kd);
          return 1;
      }
  
      *procCount = (size_t)cnt;
  
      size_t mlen = cnt * sizeof(struct kinfo_proc);
  
      if ((*procList = malloc(mlen)) == NULL) {
          PyErr_NoMemory();
          kvm_close(kd);
          return 1;
      }
  
      memcpy(*procList, result, mlen);
      assert(*procList != NULL);
      kvm_close(kd);
  
      return 0;
  }
  
  
  static char **
  _psutil_get_argv(pid_t pid) {
      static char **argv;
      int argv_mib[] = {CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV};
      size_t argv_size = 128;
      // Loop and reallocate until we have enough space to fit argv.
      for (;; argv_size *= 2) {
          if (argv_size >= 8192) {
              PyErr_SetString(PyExc_RuntimeError,
                              "can't allocate enough space for KERN_PROC_ARGV");
              return NULL;
          }
          if ((argv = realloc(argv, argv_size)) == NULL)
              continue;
          if (sysctl(argv_mib, 4, argv, &argv_size, NULL, 0) == 0)
              return argv;
          if (errno == ENOMEM)
              continue;
          PyErr_SetFromErrno(PyExc_OSError);
          return NULL;
      }
  }
  
  
  // returns the command line as a python list object
  PyObject *
  psutil_get_cmdline(pid_t pid) {
      static char **argv;
      char **p;
      PyObject *py_arg = NULL;
      PyObject *py_retlist = Py_BuildValue("[]");
  
      if (!py_retlist)
          return NULL;
      if (pid < 0)
          return py_retlist;
  
      if ((argv = _psutil_get_argv(pid)) == NULL)
          goto error;
  
      for (p = argv; *p != NULL; p++) {
          py_arg = PyUnicode_DecodeFSDefault(*p);
          if (!py_arg)
              goto error;
          if (PyList_Append(py_retlist, py_arg))
              goto error;
          Py_DECREF(py_arg);
      }
      return py_retlist;
  
  error:
      Py_XDECREF(py_arg);
      Py_DECREF(py_retlist);
      return NULL;
  }
  
  
  PyObject *
  psutil_proc_threads(PyObject *self, PyObject *args) {
      // OpenBSD reference:
      // https://github.com/janmojzis/pstree/blob/master/proc_kvm.c
      // Note: this requires root access, else it will fail trying
      // to access /dev/kmem.
      pid_t pid;
      kvm_t *kd = NULL;
      int nentries, i;
      char errbuf[4096];
      struct kinfo_proc *kp;
      PyObject *py_retlist = PyList_New(0);
      PyObject *py_tuple = NULL;
  
      if (py_retlist == NULL)
          return NULL;
      if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
          goto error;
  
      kd = kvm_openfiles(0, 0, 0, O_RDONLY, errbuf);
      if (! kd) {
          convert_kvm_err("kvm_openfiles()", errbuf);
          goto error;
      }
  
      kp = kvm_getprocs(
          kd, KERN_PROC_PID | KERN_PROC_SHOW_THREADS | KERN_PROC_KTHREAD, pid,
          sizeof(*kp), &nentries);
      if (! kp) {
          if (strstr(errbuf, "Permission denied") != NULL)
              AccessDenied("kvm_getprocs");
          else
              PyErr_Format(PyExc_RuntimeError, "kvm_getprocs() syscall failed");
          goto error;
      }
  
      for (i = 0; i < nentries; i++) {
          if (kp[i].p_tid < 0)
              continue;
          if (kp[i].p_pid == pid) {
              py_tuple = Py_BuildValue(
                  _Py_PARSE_PID "dd",
                  kp[i].p_tid,
                  PSUTIL_KPT2DOUBLE(kp[i].p_uutime),
                  PSUTIL_KPT2DOUBLE(kp[i].p_ustime));
              if (py_tuple == NULL)
                  goto error;
              if (PyList_Append(py_retlist, py_tuple))
                  goto error;
              Py_DECREF(py_tuple);
          }
      }
  
      kvm_close(kd);
      return py_retlist;
  
  error:
      Py_XDECREF(py_tuple);
      Py_DECREF(py_retlist);
      if (kd != NULL)
          kvm_close(kd);
      return NULL;
  }
  
  
  PyObject *
  psutil_proc_num_fds(PyObject *self, PyObject *args) {
      pid_t pid;
      int cnt;
  
      struct kinfo_file *freep;
      struct kinfo_proc kipp;
  
      if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
          return NULL;
  
      if (psutil_kinfo_proc(pid, &kipp) == -1)
          return NULL;
  
      freep = kinfo_getfile(pid, &cnt);
      if (freep == NULL)
          return NULL;
  
      free(freep);
      return Py_BuildValue("i", cnt);
  }
  
  
  PyObject *
  psutil_proc_cwd(PyObject *self, PyObject *args) {
      // Reference:
      // https://github.com/openbsd/src/blob/
      //     588f7f8c69786211f2d16865c552afb91b1c7cba/bin/ps/print.c#L191
      pid_t pid;
      struct kinfo_proc kp;
      char path[MAXPATHLEN];
      size_t pathlen = sizeof path;
  
      if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
          return NULL;
      if (psutil_kinfo_proc(pid, &kp) == -1)
          return NULL;
  
      int name[] = { CTL_KERN, KERN_PROC_CWD, pid };
      if (sysctl(name, 3, path, &pathlen, NULL, 0) != 0) {
          PyErr_SetFromErrno(PyExc_OSError);
          return NULL;
      }
      return PyUnicode_DecodeFSDefault(path);
  }
  
  
  // see sys/kern/kern_sysctl.c lines 1100 and
  // usr.bin/fstat/fstat.c print_inet_details()
  static char *
  psutil_convert_ipv4(int family, uint32_t addr[4]) {
      struct in_addr a;
      memcpy(&a, addr, sizeof(a));
      return inet_ntoa(a);
  }
  
  
  static char *
  psutil_inet6_addrstr(struct in6_addr *p)
  {
      struct sockaddr_in6 sin6;
      static char hbuf[NI_MAXHOST];
      const int niflags = NI_NUMERICHOST;
  
      memset(&sin6, 0, sizeof(sin6));
      sin6.sin6_family = AF_INET6;
      sin6.sin6_len = sizeof(struct sockaddr_in6);
      sin6.sin6_addr = *p;
      if (IN6_IS_ADDR_LINKLOCAL(p) &&
          *(u_int16_t *)&sin6.sin6_addr.s6_addr[2] != 0) {
          sin6.sin6_scope_id =
              ntohs(*(u_int16_t *)&sin6.sin6_addr.s6_addr[2]);
          sin6.sin6_addr.s6_addr[2] = sin6.sin6_addr.s6_addr[3] = 0;
      }
  
      if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
          hbuf, sizeof(hbuf), NULL, 0, niflags))
          return "invalid";
  
      return hbuf;
  }
  
  
  /*
   * List process connections.
   * Note: there is no net_connections() on OpenBSD. The Python
   * implementation will iterate over all processes and use this
   * function.
   * Note: local and remote paths cannot be determined for UNIX sockets.
   */
  PyObject *
  psutil_proc_connections(PyObject *self, PyObject *args) {
      pid_t pid;
      int i;
      int cnt;
      struct kinfo_file *freep = NULL;
      struct kinfo_file *kif;
      char *tcplist = NULL;
      PyObject *py_retlist = PyList_New(0);
      PyObject *py_tuple = NULL;
      PyObject *py_laddr = NULL;
      PyObject *py_raddr = NULL;
      PyObject *py_af_filter = NULL;
      PyObject *py_type_filter = NULL;
      PyObject *py_family = NULL;
      PyObject *_type = NULL;
  
      if (py_retlist == NULL)
          return NULL;
      if (! PyArg_ParseTuple(args, _Py_PARSE_PID "OO", &pid, &py_af_filter,
                             &py_type_filter))
          goto error;
      if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) {
          PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence");
          goto error;
      }
  
      freep = kinfo_getfile(pid, &cnt);
      if (freep == NULL) {
          goto error;
      }
  
      for (i = 0; i < cnt; i++) {
          int state;
          int lport;
          int rport;
          char addrbuf[NI_MAXHOST + 2];
          int inseq;
          struct in6_addr laddr6;
          py_tuple = NULL;
          py_laddr = NULL;
          py_raddr = NULL;
  
          kif = &freep[i];
          if (kif->f_type == DTYPE_SOCKET) {
              // apply filters
              py_family = PyLong_FromLong((long)kif->so_family);
              inseq = PySequence_Contains(py_af_filter, py_family);
              Py_DECREF(py_family);
              if (inseq == 0)
                  continue;
              _type = PyLong_FromLong((long)kif->so_type);
              inseq = PySequence_Contains(py_type_filter, _type);
              Py_DECREF(_type);
              if (inseq == 0)
                  continue;
  
              // IPv4 / IPv6 socket
              if ((kif->so_family == AF_INET) || (kif->so_family == AF_INET6)) {
                  // fill status
                  if (kif->so_type == SOCK_STREAM)
                      state = kif->t_state;
                  else
                      state = PSUTIL_CONN_NONE;
  
                  // ports
                  lport = ntohs(kif->inp_lport);
                  rport = ntohs(kif->inp_fport);
  
                  // local address, IPv4
                  if (kif->so_family == AF_INET) {
                      py_laddr = Py_BuildValue(
                          "(si)",
                          psutil_convert_ipv4(kif->so_family, kif->inp_laddru),
                          lport);
                      if (!py_laddr)
                          goto error;
                  }
                  else {
                      // local address, IPv6
                      memcpy(&laddr6, kif->inp_laddru, sizeof(laddr6));
                      snprintf(addrbuf, sizeof(addrbuf), "%s",
                               psutil_inet6_addrstr(&laddr6));
                      py_laddr = Py_BuildValue("(si)", addrbuf, lport);
                      if (!py_laddr)
                          goto error;
                  }
  
                  if (rport != 0) {
                      // remote address, IPv4
                      if (kif->so_family == AF_INET) {
                          py_raddr = Py_BuildValue(
                              "(si)",
                              psutil_convert_ipv4(
                                  kif->so_family, kif->inp_faddru),
                              rport);
                      }
                      else {
                          // remote address, IPv6
                          memcpy(&laddr6, kif->inp_faddru, sizeof(laddr6));
                          snprintf(addrbuf, sizeof(addrbuf), "%s",
                                   psutil_inet6_addrstr(&laddr6));
                          py_raddr = Py_BuildValue("(si)", addrbuf, rport);
                          if (!py_raddr)
                              goto error;
                      }
                  }
                  else {
                      py_raddr = Py_BuildValue("()");
                  }
  
                  if (!py_raddr)
                      goto error;
                  py_tuple = Py_BuildValue(
                      "(iiiNNi)",
                      kif->fd_fd,
                      kif->so_family,
                      kif->so_type,
                      py_laddr,
                      py_raddr,
                      state);
                  if (!py_tuple)
                      goto error;
                  if (PyList_Append(py_retlist, py_tuple))
                      goto error;
                  Py_DECREF(py_tuple);
              }
              // UNIX socket.
              // XXX: local addr is supposed to be in "unp_path" but it
              // always empty; also "fstat" command is not able to show
              // UNIX socket paths.
              else if (kif->so_family == AF_UNIX) {
                  py_tuple = Py_BuildValue(
                      "(iiissi)",
                      kif->fd_fd,
                      kif->so_family,
                      kif->so_type,
                      "",  // laddr (kif->unp_path is empty)
                      "",  // raddr
                      PSUTIL_CONN_NONE);
                  if (!py_tuple)
                      goto error;
                  if (PyList_Append(py_retlist, py_tuple))
                      goto error;
                  Py_DECREF(py_tuple);
                  Py_INCREF(Py_None);
              }
          }
      }
      free(freep);
      free(tcplist);
      return py_retlist;
  
  error:
      Py_XDECREF(py_tuple);
      Py_XDECREF(py_laddr);
      Py_XDECREF(py_raddr);
      Py_DECREF(py_retlist);
      if (freep != NULL)
          free(freep);
      if (tcplist != NULL)
          free(tcplist);
      return NULL;
  }
  