diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2015-11-12 02:46:59 -0800 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2015-11-12 02:46:59 -0800 |
commit | a7f4fe2dfd14e53244dcb9fa75ef6093432762c2 (patch) | |
tree | 434f04cbf95c47ecee9c57a8de7c4065bfa8740f | |
parent | e079f3c8feeeec70c1ea32491cf39721c1c82d15 (diff) | |
parent | d4becc3f156bdc7565a64061f1c5711eb9f6791a (diff) | |
download | psutil-a7f4fe2dfd14e53244dcb9fa75ef6093432762c2.tar.gz |
Merge pull request #709 from giampaolo/landryb-openbsd
Landryb openbsd
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | docs/index.rst | 7 | ||||
-rw-r--r-- | psutil/__init__.py | 64 | ||||
-rw-r--r-- | psutil/_compat.py | 53 | ||||
-rw-r--r-- | psutil/_psbsd.py | 166 | ||||
-rw-r--r-- | psutil/_psutil_bsd.h | 20 | ||||
-rw-r--r-- | psutil/_psutil_openbsd.c | 2419 | ||||
-rw-r--r-- | psutil/_psutil_posix.c | 14 | ||||
-rw-r--r-- | psutil/arch/bsd/openbsd.c | 761 | ||||
-rw-r--r-- | psutil/arch/bsd/openbsd.h | 28 | ||||
-rw-r--r-- | psutil/arch/bsd/process_info.h | 3 | ||||
-rw-r--r-- | setup.py | 13 | ||||
-rw-r--r-- | test/_freebsd.py (renamed from test/_bsd.py) | 10 | ||||
-rw-r--r-- | test/test_psutil.py | 140 |
14 files changed, 3556 insertions, 144 deletions
@@ -35,6 +35,8 @@ HIGHER PRIORITY * Process.threads(): thread names; patch for OSX available at: https://code.google.com/p/plcrashreporter/issues/detail?id=65 + Sample code: + https://github.com/janmojzis/pstree/blob/master/proc_kvm.c * Asynchronous psutil.Popen (see http://bugs.python.org/issue1191964) diff --git a/docs/index.rst b/docs/index.rst index 31ba1fc3..9561b45c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -892,7 +892,8 @@ Process class .. method:: threads() Return threads opened by process as a list of namedtuples including thread - id and thread CPU times (user/system). + id and thread CPU times (user/system). On OpenBSD this method requires + root access. .. method:: cpu_times() @@ -959,7 +960,7 @@ Process class >>> p.cpu_affinity(all_cpus) >>> - Availability: Linux, Windows, BSD + Availability: Linux, Windows, FreeBSD .. versionchanged:: 2.2.0 added support for FreeBSD @@ -1042,6 +1043,8 @@ Process class ...] >>> + Availability: All platforms except OpenBSD. + .. method:: children(recursive=False) Return the children of this process as a list of :Class:`Process` objects, diff --git a/psutil/__init__.py b/psutil/__init__.py index fa654771..9e68d5b0 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -122,7 +122,7 @@ elif sys.platform.startswith("win32"): elif sys.platform.startswith("darwin"): from . import _psosx as _psplatform -elif sys.platform.startswith("freebsd"): +elif sys.platform.startswith("freebsd") or sys.platform.startswith("openbsd"): from . import _psbsd as _psplatform elif sys.platform.startswith("sunos"): @@ -167,6 +167,7 @@ AF_LINK = _psplatform.AF_LINK _TOTAL_PHYMEM = None _POSIX = os.name == 'posix' _WINDOWS = os.name == 'nt' +_OPENBSD = sys.platform.startswith("openbsd") _timer = getattr(time, 'monotonic', time.time) @@ -715,7 +716,7 @@ class Process(object): else: return self._proc.rlimit(resource, limits) - # Windows, Linux and BSD only + # Windows, Linux and FreeBSD only if hasattr(_psplatform.Process, "cpu_affinity_get"): def cpu_affinity(self, cpus=None): @@ -754,6 +755,7 @@ class Process(object): """Return threads opened by process as a list of (id, user_time, system_time) namedtuples representing thread id and thread CPU times (user/system). + On OpenBSD this method requires root access. """ return self._proc.threads() @@ -961,32 +963,33 @@ class Process(object): except ZeroDivisionError: return 0.0 - def memory_maps(self, grouped=True): - """Return process' mapped memory regions as a list of namedtuples - whose fields are variable depending on the platform. + if not _OPENBSD: + def memory_maps(self, grouped=True): + """Return process' mapped memory regions as a list of namedtuples + whose fields are variable depending on the platform. - If 'grouped' is True the mapped regions with the same 'path' - are grouped together and the different memory fields are summed. + If 'grouped' is True the mapped regions with the same 'path' + are grouped together and the different memory fields are summed. - If 'grouped' is False every mapped region is shown as a single - entity and the namedtuple will also include the mapped region's - address space ('addr') and permission set ('perms'). - """ - it = self._proc.memory_maps() - if grouped: - d = {} - for tupl in it: - path = tupl[2] - nums = tupl[3:] - try: - d[path] = map(lambda x, y: x + y, d[path], nums) - except KeyError: - d[path] = nums - nt = _psplatform.pmmap_grouped - return [nt(path, *d[path]) for path in d] # NOQA - else: - nt = _psplatform.pmmap_ext - return [nt(*x) for x in it] + If 'grouped' is False every mapped region is shown as a single + entity and the namedtuple will also include the mapped region's + address space ('addr') and permission set ('perms'). + """ + it = self._proc.memory_maps() + if grouped: + d = {} + for tupl in it: + path = tupl[2] + nums = tupl[3:] + try: + d[path] = map(lambda x, y: x + y, d[path], nums) + except KeyError: + d[path] = nums + nt = _psplatform.pmmap_grouped + return [nt(path, *d[path]) for path in d] # NOQA + else: + nt = _psplatform.pmmap_ext + return [nt(*x) for x in it] def open_files(self): """Return files opened by process as a list of @@ -1028,8 +1031,13 @@ class Process(object): os.kill(self.pid, sig) except OSError as err: if err.errno == errno.ESRCH: - self._gone = True - raise NoSuchProcess(self.pid, self._name) + if _OPENBSD and pid_exists(self.pid): + # We do this because os.kill() lies in case of + # zombie processes. + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + self._gone = True + raise NoSuchProcess(self.pid, self._name) if err.errno in (errno.EPERM, errno.EACCES): raise AccessDenied(self.pid, self._name) raise diff --git a/psutil/_compat.py b/psutil/_compat.py index 9b743753..f79af3ac 100644 --- a/psutil/_compat.py +++ b/psutil/_compat.py @@ -6,6 +6,7 @@ import collections import functools +import os import sys __all__ = ["PY3", "long", "xrange", "unicode", "callable", "lru_cache"] @@ -191,3 +192,55 @@ except ImportError: return functools.update_wrapper(wrapper, user_function) return decorating_function + + +# python 3.3 +try: + from shutil import which +except ImportError: + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + """ + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + if os.curdir not in path: + path.insert(0, os.curdir) + + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if normdir not in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index 82bf91f5..57d847b9 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -7,6 +7,7 @@ import errno import functools import os +import sys import xml.etree.ElementTree as ET from collections import namedtuple @@ -18,21 +19,47 @@ from ._common import conn_tmap from ._common import sockfam_to_enum from ._common import socktype_to_enum from ._common import usage_percent +from ._compat import which __extra__all__ = [] # --- constants -PROC_STATUSES = { - cext.SIDL: _common.STATUS_IDLE, - cext.SRUN: _common.STATUS_RUNNING, - cext.SSLEEP: _common.STATUS_SLEEPING, - cext.SSTOP: _common.STATUS_STOPPED, - cext.SZOMB: _common.STATUS_ZOMBIE, - cext.SWAIT: _common.STATUS_WAITING, - cext.SLOCK: _common.STATUS_LOCKED, -} +FREEBSD = sys.platform.startswith("freebsd") +OPENBSD = sys.platform.startswith("openbsd") + +if FREEBSD: + PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SWAIT: _common.STATUS_WAITING, + cext.SLOCK: _common.STATUS_LOCKED, + } +elif OPENBSD: + PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + # According to /usr/include/sys/proc.h SZOMB is unused. + # test_zombie_process() shows that SDEAD is the right + # equivalent. Also it appears there's no equivalent of + # psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE. + # cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SDEAD: _common.STATUS_ZOMBIE, + # From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt + # OpenBSD has SRUN and SONPROC: SRUN indicates that a process + # is runnable but *not* yet running, i.e. is on a run queue. + # SONPROC indicates that the process is actually executing on + # a CPU, i.e. it is no longer on a run queue. + # As such we'll map SRUN to STATUS_WAKING and SONPROC to + # STATUS_RUNNING + cext.SRUN: _common.STATUS_WAKING, + cext.SONPROC: _common.STATUS_RUNNING, + } TCP_STATUSES = { cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, @@ -84,6 +111,8 @@ def virtual_memory(): def swap_memory(): """System swap memory as (total, used, free, sin, sout) namedtuple.""" + if OPENBSD: + PAGESIZE = 1 total, used, free, sin, sout = [x * PAGESIZE for x in cext.swap_mem()] percent = usage_percent(used, total, _round=1) return _common.sswap(total, used, free, percent, sin, sout) @@ -135,6 +164,8 @@ def cpu_count_physical(): # We may get None in case "sysctl kern.sched.topology_spec" # is not supported on this BSD version, in which case we'll mimic # os.cpu_count() and return None. + if sys.platform.startswith("openbsd"): + return cext.cpu_count_logical() ret = None s = cext.cpu_count_phys() if s is not None: @@ -189,6 +220,20 @@ def users(): def net_connections(kind): + if OPENBSD: + ret = [] + for pid in pids(): + try: + cons = Process(pid).connections(kind) + except (NoSuchProcess, ZombieProcess): + continue + else: + for conn in cons: + conn = list(conn) + conn.append(pid) + ret.append(_common.sconn(*conn)) + return ret + if kind not in _common.conn_tmap: raise ValueError("invalid %r kind argument; choose between %s" % (kind, ', '.join([repr(x) for x in conn_tmap]))) @@ -226,8 +271,20 @@ def net_if_stats(): return ret +if OPENBSD: + def pid_exists(pid): + exists = _psposix.pid_exists(pid) + if not exists: + # We do this because _psposix.pid_exists() lies in case of + # zombie processes. + return pid in pids() + else: + return True +else: + pid_exists = _psposix.pid_exists + + pids = cext.pids -pid_exists = _psposix.pid_exists disk_usage = _psposix.disk_usage net_io_counters = cext.net_io_counters disk_io_counters = cext.disk_io_counters @@ -274,10 +331,24 @@ class Process(object): @wrap_exceptions def exe(self): - return cext.proc_exe(self.pid) + if FREEBSD: + return cext.proc_exe(self.pid) + else: + # exe cannot be determined on OpenBSD; references: + # https://chromium.googlesource.com/chromium/src/base/+/ + # master/base_paths_posix.cc + # We try our best guess by using which against the first + # cmdline arg (may return None). + cmdline = self.cmdline() + if cmdline: + return which(cmdline[0]) + else: + return "" @wrap_exceptions def cmdline(self): + if OPENBSD and self.pid == 0: + return None # ...else it crashes return cext.proc_cmdline(self.pid) @wrap_exceptions @@ -323,7 +394,11 @@ class Process(object): @wrap_exceptions def num_threads(self): - return cext.proc_num_threads(self.pid) + if hasattr(cext, "proc_num_threads"): + # FreeBSD + return cext.proc_num_threads(self.pid) + else: + return len(self.threads()) @wrap_exceptions def num_ctx_switches(self): @@ -331,11 +406,17 @@ class Process(object): @wrap_exceptions def threads(self): + # Note: on OpenSBD this (/dev/mem) requires root access. rawlist = cext.proc_threads(self.pid) retlist = [] for thread_id, utime, stime in rawlist: ntuple = _common.pthread(thread_id, utime, stime) retlist.append(ntuple) + if OPENBSD: + # On OpenBSD the underlying C function does not raise NSP + # in case the process is gone (and the returned list may + # incomplete). + self.name() # raise NSP if the process disappeared on us return retlist @wrap_exceptions @@ -353,6 +434,11 @@ class Process(object): status = TCP_STATUSES[status] nt = _common.pconn(fd, fam, type, laddr, raddr, status) ret.append(nt) + if OPENBSD: + # On OpenBSD the underlying C function does not raise NSP + # in case the process is gone (and the returned list may + # incomplete). + self.name() # raise NSP if the process disappeared on us return ret @wrap_exceptions @@ -406,6 +492,8 @@ class Process(object): """Return process current working directory.""" # sometimes we get an empty string, in which case we turn # it into None + if OPENBSD and self.pid == 0: + return None # ...else raises EINVAL return cext.proc_cwd(self.pid) or None @wrap_exceptions @@ -426,30 +514,32 @@ class Process(object): memory_maps = _not_implemented num_fds = _not_implemented - @wrap_exceptions - def cpu_affinity_get(self): - return cext.proc_cpu_affinity_get(self.pid) + if FREEBSD: + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) - @wrap_exceptions - def cpu_affinity_set(self, cpus): - # Pre-emptively check if CPUs are valid because the C - # function has a weird behavior in case of invalid CPUs, - # see: https://github.com/giampaolo/psutil/issues/586 - allcpus = tuple(range(len(per_cpu_times()))) - for cpu in cpus: - if cpu not in allcpus: - raise ValueError("invalid CPU #%i (choose between %s)" - % (cpu, allcpus)) - try: - cext.proc_cpu_affinity_set(self.pid, cpus) - except OSError as err: - # 'man cpuset_setaffinity' about EDEADLK: - # <<the call would leave a thread without a valid CPU to run - # on because the set does not overlap with the thread's - # anonymous mask>> - if err.errno in (errno.EINVAL, errno.EDEADLK): - for cpu in cpus: - if cpu not in allcpus: - raise ValueError("invalid CPU #%i (choose between %s)" - % (cpu, allcpus)) - raise + @wrap_exceptions + def cpu_affinity_set(self, cpus): + # Pre-emptively check if CPUs are valid because the C + # function has a weird behavior in case of invalid CPUs, + # see: https://github.com/giampaolo/psutil/issues/586 + allcpus = tuple(range(len(per_cpu_times()))) + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + # 'man cpuset_setaffinity' about EDEADLK: + # <<the call would leave a thread without a valid CPU to run + # on because the set does not overlap with the thread's + # anonymous mask>> + if err.errno in (errno.EINVAL, errno.EDEADLK): + for cpu in cpus: + if cpu not in allcpus: + raise ValueError( + "invalid CPU #%i (choose between %s)" % ( + cpu, allcpus)) + raise diff --git a/psutil/_psutil_bsd.h b/psutil/_psutil_bsd.h index 803957da..41d548be 100644 --- a/psutil/_psutil_bsd.h +++ b/psutil/_psutil_bsd.h @@ -9,7 +9,6 @@ // --- per-process functions static PyObject* psutil_proc_cmdline(PyObject* self, PyObject* args); -static PyObject* psutil_proc_connections(PyObject* self, PyObject* args); static PyObject* psutil_proc_cpu_times(PyObject* self, PyObject* args); static PyObject* psutil_proc_create_time(PyObject* self, PyObject* args); static PyObject* psutil_proc_exe(PyObject* self, PyObject* args); @@ -19,11 +18,9 @@ static PyObject* psutil_proc_memory_info(PyObject* self, PyObject* args); static PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args); static PyObject* psutil_proc_name(PyObject* self, PyObject* args); static PyObject* psutil_proc_num_ctx_switches(PyObject* self, PyObject* args); -static PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args); static PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); static PyObject* psutil_proc_ppid(PyObject* self, PyObject* args); static PyObject* psutil_proc_status(PyObject* self, PyObject* args); -static PyObject* psutil_proc_threads(PyObject* self, PyObject* args); static PyObject* psutil_proc_tty_nr(PyObject* self, PyObject* args); static PyObject* psutil_proc_uids(PyObject* self, PyObject* args); static PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args); @@ -31,8 +28,19 @@ static PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args); #if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 static PyObject* psutil_proc_open_files(PyObject* self, PyObject* args); +#endif +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 static PyObject* psutil_proc_cwd(PyObject* self, PyObject* args); #endif +#ifdef __FreeBSD__ +static PyObject* psutil_proc_threads(PyObject* self, PyObject* args); +#endif +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +static PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args); +#endif +#ifdef __FreeBSD__ +static PyObject* psutil_proc_connections(PyObject* self, PyObject* args); +#endif // --- system-related functions @@ -40,13 +48,15 @@ static PyObject* psutil_boot_time(PyObject* self, PyObject* args); static PyObject* psutil_cpu_count_logical(PyObject* self, PyObject* args); static PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args); static PyObject* psutil_cpu_times(PyObject* self, PyObject* args); -static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); static PyObject* psutil_net_io_counters(PyObject* self, PyObject* args); static PyObject* psutil_pids(PyObject* self, PyObject* args); -static PyObject* psutil_swap_mem(PyObject* self, PyObject* args); static PyObject* psutil_users(PyObject* self, PyObject* args); +#ifdef __FreeBSD__ static PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); +static PyObject* psutil_swap_mem(PyObject* self, PyObject* args); +static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +#endif #if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 static PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); diff --git a/psutil/_psutil_openbsd.c b/psutil/_psutil_openbsd.c new file mode 100644 index 00000000..9c979306 --- /dev/null +++ b/psutil/_psutil_openbsd.c @@ -0,0 +1,2419 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil (OpenBSD). + * All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Platform-specific module methods for FreeBSD and OpenBSD. + + * OpenBSD references: + * - OpenBSD source code: http://anoncvs.spacehopper.org/openbsd-src/ + * + * OpenBSD: missing compared to FreeBSD implementation: + * - psutil.net_connections() + * - psutil.Process.get/set_cpu_affinity() (not supported natively) + * - psutil.Process.memory_maps() + */ + + +#include <Python.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <fcntl.h> +#include <paths.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <sys/proc.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <net/route.h> +#include <sys/socketvar.h> // for struct xsocket +#include <sys/un.h> +#include <sys/unpcb.h> +// for xinpcb struct +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> +#include <netinet/tcp.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> // for struct xtcpcb +#include <netinet/tcp_fsm.h> // for TCP connection states +#include <arpa/inet.h> // for inet_ntop() + +#include <sys/mount.h> + +#include <net/if.h> // net io counters +#include <net/if_dl.h> +#include <net/route.h> + +#include <netinet/in.h> // process open files/connections +#include <sys/un.h> + +#include "_psutil_bsd.h" +#include "_psutil_common.h" + +#ifdef __FreeBSD__ + #include "arch/bsd/process_info.h" +#elif __OpenBSD__ + #include "arch/bsd/openbsd.h" +#endif + +#ifdef __FreeBSD__ + #include <sys/cpuset.h> + #include <net/if_media.h> + #include <devstat.h> // get io counters + #include <sys/vmmeter.h> // needed for vmtotal struct + #include <libutil.h> // process open files, shared libs (kinfo_getvmmap) + + #if __FreeBSD_version < 900000 + #include <utmp.h> // system users + #else + #include <utmpx.h> + #endif +#endif + +#ifdef __OpenBSD__ + #include <utmp.h> + #include <sys/vnode.h> // for VREG + #define _KERNEL // for DTYPE_VNODE + #include <sys/file.h> + #undef _KERNEL + #include <sys/sched.h> // for CPUSTATES & CP_* +#endif + + +#define TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + +#ifdef __FreeBSD__ + // convert a timeval struct to a double + // convert a bintime struct to milliseconds + #define BT2MSEC(bt) (bt.sec * 1000 + ( ( (uint64_t) 1000000000 * (uint32_t) (bt.frac >> 32) ) >> 32 ) / 1000000) +#endif + +#ifdef __OpenBSD__ + #define KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) +#endif + + +#ifdef __FreeBSD__ +/* + * Utility function which fills a kinfo_proc struct based on process pid + */ +static int +psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc) { + int mib[4]; + size_t size; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + + size = sizeof(struct kinfo_proc); + + if (sysctl((int *)mib, 4, proc, &size, NULL, 0) == -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(); + return -1; + } + return 0; +} +#endif + + +#ifdef __FreeBSD__ +/* + * Set exception to AccessDenied if pid exists else NoSuchProcess. + */ +void +psutil_raise_ad_or_nsp(long pid) { + int ret; + ret = psutil_pid_exists(pid); + if (ret == 0) + NoSuchProcess(); + else if (ret == 1) + AccessDenied(); + else + return NULL; +} +#endif + + +/* + * Return a Python list of all the PIDs running on the system. + */ +static PyObject * +psutil_pids(PyObject *self, PyObject *args) { + kinfo_proc *proclist = NULL; + kinfo_proc *orig_address = NULL; + size_t num_processes; + size_t idx; + PyObject *py_retlist = PyList_New(0); + PyObject *py_pid = NULL; + + if (py_retlist == NULL) + return NULL; + if (psutil_get_proc_list(&proclist, &num_processes) != 0) { + PyErr_SetString(PyExc_RuntimeError, + "failed to retrieve process list."); + goto error; + } + + if (num_processes > 0) { + orig_address = proclist; // save so we can free it after we're done + for (idx = 0; idx < num_processes; idx++) { +#ifdef __FreeBSD__ + py_pid = Py_BuildValue("i", proclist->ki_pid); +#elif __OpenBSD__ + py_pid = Py_BuildValue("i", proclist->p_pid); +#endif + if (!py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_DECREF(py_pid); + proclist++; + } + free(orig_address); + } + + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + if (orig_address != NULL) + free(orig_address); + return NULL; +} + + +/* + * Return a Python float indicating the system boot time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + // fetch sysctl "kern.boottime" + static int request[2] = { CTL_KERN, KERN_BOOTTIME }; + struct timeval boottime; + size_t len = sizeof(boottime); + + if (sysctl(request, 2, &boottime, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return Py_BuildValue("d", (double)boottime.tv_sec); +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; +#ifdef __FreeBSD__ + return Py_BuildValue("s", kp.ki_comm); +#elif __OpenBSD__ + return Py_BuildValue("s", kp.p_comm); +#endif +} + + +#ifdef __FreeBSD__ +/* + * Return process pathname executable. + * Thanks to Robert N. M. Watson: + * http://fxr.googlebit.com/source/usr.bin/procstat/procstat_bin.c?v=8-CURRENT + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + long pid; + char pathname[PATH_MAX]; + int error; + int mib[4]; + int ret; + size_t size; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = pid; + + size = sizeof(pathname); + error = sysctl(mib, 4, pathname, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (size == 0 || strlen(pathname) == 0) { + ret = psutil_pid_exists(pid); + if (ret == -1) + return NULL; + else if (ret == 0) + return NoSuchProcess(); + else + strcpy(pathname, ""); + } + return Py_BuildValue("s", pathname); +} +#endif + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) { + long pid; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + py_retlist = psutil_get_cmdline(pid); + // psutil_get_cmdline() returns NULL only if psutil_cmd_args + // failed with ESRCH (no process with that PID) + if (NULL == py_retlist) + return PyErr_SetFromErrno(PyExc_OSError); + return Py_BuildValue("N", py_retlist); +} + + +/* + * Return process parent pid from kinfo_proc as a Python integer. + */ +static PyObject * +psutil_proc_ppid(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; +#ifdef __FreeBSD__ + return Py_BuildValue("l", (long)kp.ki_ppid); +#elif __OpenBSD__ + return Py_BuildValue("l", (long)kp.p_ppid); +#endif +} + + +/* + * Return process status as a Python integer. + */ +static PyObject * +psutil_proc_status(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; +#ifdef __FreeBSD__ + return Py_BuildValue("i", (int)kp.ki_stat); +#elif __OpenBSD__ + return Py_BuildValue("i", (int)kp.p_stat); +#endif +} + + +/* + * Return process real, effective and saved user ids from kinfo_proc + * as a Python tuple. + */ +static PyObject * +psutil_proc_uids(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("lll", +#ifdef __FreeBSD__ + (long)kp.ki_ruid, + (long)kp.ki_uid, + (long)kp.ki_svuid); +#elif __OpenBSD__ + (long)kp.p_ruid, + (long)kp.p_uid, + (long)kp.p_svuid); +#endif +} + + +/* + * Return process real, effective and saved group ids from kinfo_proc + * as a Python tuple. + */ +static PyObject * +psutil_proc_gids(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("lll", +#ifdef __FreeBSD__ + (long)kp.ki_rgid, + (long)kp.ki_groups[0], + (long)kp.ki_svuid); +#elif __OpenBSD__ + (long)kp.p_rgid, + (long)kp.p_groups[0], + (long)kp.p_svuid); +#endif +} + + +/* + * Return process real, effective and saved group ids from kinfo_proc + * as a Python tuple. + */ +static PyObject * +psutil_proc_tty_nr(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; +#ifdef __FreeBSD__ + return Py_BuildValue("i", kp.ki_tdev); +#elif __OpenBSD__ + return Py_BuildValue("i", kp.p_tdev); +#endif +} + + +/* + * Return the number of context switches performed by process as a tuple. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("(ll)", +#ifdef __FreeBSD__ + kp.ki_rusage.ru_nvcsw, + kp.ki_rusage.ru_nivcsw); +#elif __OpenBSD__ + kp.p_uru_nvcsw, + kp.p_uru_nivcsw); +#endif +} + + +#ifdef __FreeBSD__ +/* + * Return number of threads used by process as a Python integer. + */ +static PyObject * +psutil_proc_num_threads(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("l", (long)kp.ki_numthreads); +} +#endif + + +/* + * Retrieves all threads used by process returning a list of tuples + * including thread id, user time and system time. + * Thanks to Robert N. M. Watson (FreeBSD): + * http://fxr.googlebit.com/source/usr.bin/procstat/ + * procstat_threads.c?v=8-CURRENT + */ + +#ifdef __FreeBSD__ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + long pid; + int mib[4]; + struct kinfo_proc *kip = NULL; + struct kinfo_proc *kipp = NULL; + int error; + unsigned int i; + size_t size; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + // we need to re-query for thread information, so don't use *kipp + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID | KERN_PROC_INC_THREAD; + mib[3] = pid; + + size = 0; + error = sysctl(mib, 4, NULL, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (size == 0) { + NoSuchProcess(); + goto error; + } + + kip = malloc(size); + if (kip == NULL) { + PyErr_NoMemory(); + goto error; + } + + error = sysctl(mib, 4, kip, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (size == 0) { + NoSuchProcess(); + goto error; + } + + for (i = 0; i < size / sizeof(*kipp); i++) { + kipp = &kip[i]; + py_tuple = Py_BuildValue("Idd", + kipp->ki_tid, + TV2DOUBLE(kipp->ki_rusage.ru_utime), + TV2DOUBLE(kipp->ki_rusage.ru_stime)); + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + free(kip); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (kip != NULL) + free(kip); + return NULL; +} +#endif + + +/* + * Return a Python tuple (user_time, kernel_time) + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) { + long pid; + double user_t, sys_t; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + // convert from microseconds to seconds +#ifdef __FreeBSD__ + user_t = TV2DOUBLE(kp.ki_rusage.ru_utime); + sys_t = TV2DOUBLE(kp.ki_rusage.ru_stime); +#elif __OpenBSD__ + user_t = KPT2DOUBLE(kp.p_uutime); + sys_t = KPT2DOUBLE(kp.p_ustime); +#endif + return Py_BuildValue("(dd)", user_t, sys_t); +} + + +/* + * Return the number of logical CPUs in the system. + * XXX this could be shared with OSX + */ +static PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) { + int mib[2]; + int ncpu; + size_t len; + + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", ncpu); +} + + +#ifdef __FreeBSD__ +/* + * Return an XML string from which we'll determine the number of + * physical CPU cores in the system. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) { + void *topology = NULL; + size_t size = 0; + PyObject *py_str; + + if (sysctlbyname("kern.sched.topology_spec", NULL, &size, NULL, 0)) + goto error; + + topology = malloc(size); + if (!topology) { + PyErr_NoMemory(); + return NULL; + } + + if (sysctlbyname("kern.sched.topology_spec", topology, &size, NULL, 0)) + goto error; + + py_str = Py_BuildValue("s", topology); + free(topology); + return py_str; + +error: + if (topology != NULL) + free(topology); + Py_RETURN_NONE; +} +#endif + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_create_time(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; +#ifdef __FreeBSD__ + return Py_BuildValue("d", TV2DOUBLE(kp.ki_start)); +#elif __OpenBSD__ + return Py_BuildValue("d", KPT2DOUBLE(kp.p_ustart)); +#endif +} + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_io_counters(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + // there's apparently no way to determine bytes count, hence return -1. + return Py_BuildValue("(llll)", +#ifdef __FreeBSD__ + kp.ki_rusage.ru_inblock, + kp.ki_rusage.ru_oublock, +#elif __OpenBSD__ + kp.p_uru_inblock, + kp.p_uru_oublock, +#endif + -1, + -1); +} + + +#ifdef __OpenBSD__ +#define ptoa(x) ((paddr_t)(x) << PAGE_SHIFT) +#endif + +/* + * Return extended memory info for a process as a Python tuple. + */ +static PyObject * +psutil_proc_memory_info(PyObject *self, PyObject *args) { + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue( + "(lllll)", +#ifdef __FreeBSD__ + ptoa(kp.ki_rssize), // rss + (long)kp.ki_size, // vms + ptoa(kp.ki_tsize), // text + ptoa(kp.ki_dsize), // data + ptoa(kp.ki_ssize)); // stack +#elif __OpenBSD__ + ptoa(kp.p_vm_rssize), // rss + // vms, this is how ps does it, see: + // http://anoncvs.spacehopper.org/openbsd-src/tree/bin/ps/print.c#n461 + ptoa(kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize), // vms + ptoa(kp.p_vm_tsize), // text + ptoa(kp.p_vm_dsize), // data + ptoa(kp.p_vm_ssize)); // stack +#endif +} + + +#ifdef __FreeBSD__ +/* + * Return virtual memory usage statistics. + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + unsigned int total, active, inactive, wired, cached, free; + size_t size = sizeof(total); + struct vmtotal vm; + int mib[] = {CTL_VM, VM_METER}; + long pagesize = getpagesize(); +#if __FreeBSD_version > 702101 + long buffers; +#else + int buffers; +#endif + size_t buffers_size = sizeof(buffers); + + if (sysctlbyname("vm.stats.vm.v_page_count", &total, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_active_count", &active, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_inactive_count", + &inactive, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_wire_count", &wired, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_cache_count", &cached, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_free_count", &free, &size, NULL, 0)) + goto error; + if (sysctlbyname("vfs.bufspace", &buffers, &buffers_size, NULL, 0)) + goto error; + + size = sizeof(vm); + if (sysctl(mib, 2, &vm, &size, NULL, 0) != 0) + goto error; + + return Py_BuildValue("KKKKKKKK", + (unsigned long long) total * pagesize, + (unsigned long long) free * pagesize, + (unsigned long long) active * pagesize, + (unsigned long long) inactive * pagesize, + (unsigned long long) wired * pagesize, + (unsigned long long) cached * pagesize, + (unsigned long long) buffers, + (unsigned long long) (vm.t_vmshr + vm.t_rmshr) * pagesize // shared + ); + +error: + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} +#endif + + +#ifndef _PATH_DEVNULL +#define _PATH_DEVNULL "/dev/null" +#endif + +#ifdef __FreeBSD__ +/* + * Return swap memory stats (see 'swapinfo' cmdline tool) + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + kvm_t *kd; + struct kvm_swap kvmsw[1]; + unsigned int swapin, swapout, nodein, nodeout; + size_t size = sizeof(unsigned int); + + kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open failed"); + if (kd == NULL) { + PyErr_SetString(PyExc_RuntimeError, "kvm_open failed"); + return NULL; + } + + if (kvm_getswapinfo(kd, kvmsw, 1, 0) < 0) { + kvm_close(kd); + PyErr_SetString(PyExc_RuntimeError, "kvm_getswapinfo failed"); + return NULL; + } + + kvm_close(kd); + + if (sysctlbyname("vm.stats.vm.v_swapin", &swapin, &size, NULL, 0) == -1) + goto sbn_error; + if (sysctlbyname("vm.stats.vm.v_swapout", &swapout, &size, NULL, 0) == -1) + goto sbn_error; + if (sysctlbyname("vm.stats.vm.v_vnodein", &nodein, &size, NULL, 0) == -1) + goto sbn_error; + if (sysctlbyname("vm.stats.vm.v_vnodeout", &nodeout, &size, NULL, 0) == -1) + goto sbn_error; + + return Py_BuildValue("(iiiII)", + kvmsw[0].ksw_total, // total + kvmsw[0].ksw_used, // used + kvmsw[0].ksw_total - kvmsw[0].ksw_used, // free + swapin + swapout, // swap in + nodein + nodeout); // swap out + +sbn_error: + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} +#endif + + +/* + * Return a Python tuple representing user, kernel and idle CPU times + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) { + long cpu_time[CPUSTATES]; + size_t size = sizeof(cpu_time); + int ret; + +#ifdef __FreeBSD__ + ret = sysctlbyname("kern.cp_time", &cpu_time, &size, NULL, 0); +#elif __OpenBSD__ + int mib[] = {CTL_KERN, KERN_CPTIME}; + ret = sysctl(mib, 2, &cpu_time, &size, NULL, 0); +#endif + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC + ); +} + + + /* + * Return files opened by process as a list of (path, fd) tuples. + * TODO: this is broken as it may report empty paths. 'procstat' + * utility has the same problem see: + * https://github.com/giampaolo/psutil/issues/595 + */ +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || __OpenBSD__ +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + long pid; + int i, cnt; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + struct kinfo_proc kipp; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kipp) == -1) + goto error; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + for (i = 0; i < cnt; i++) { + kif = &freep[i]; +#ifdef __FreeBSD__ + if ((kif->kf_type == KF_TYPE_VNODE) && + (kif->kf_vnode_type == KF_VTYPE_VREG)) + { + py_tuple = Py_BuildValue("(si)", kif->kf_path, kif->kf_fd); +#else + if ((kif->f_type == DTYPE_VNODE) && + (kif->v_type == VREG)) + { + py_tuple = Py_BuildValue("(si)", "", kif->fd_fd); +#endif + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } + free(freep); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + return NULL; +} +#endif + + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +/* + * Return files opened by process as a list of (path, fd) tuples + */ +static PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + long pid; + int cnt; + + struct kinfo_file *freep; + struct kinfo_proc kipp; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kipp) == -1) + return NULL; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + return NULL; + } + free(freep); + + return Py_BuildValue("i", cnt); +} +#endif + + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +/* + * Return process current working directory. + */ +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + long pid; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + struct kinfo_proc kipp; + PyObject *py_path = NULL; + + int i, cnt; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kipp) == -1) + goto error; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + for (i = 0; i < cnt; i++) { + kif = &freep[i]; + if (kif->kf_fd == KF_FD_TYPE_CWD) { + py_path = Py_BuildValue("s", kif->kf_path); + if (!py_path) + goto error; + break; + } + } + /* + * For lower pids it seems we can't retrieve any information + * (lsof can't do that it either). Since this happens even + * as root we return an empty string instead of AccessDenied. + */ + if (py_path == NULL) + py_path = Py_BuildValue("s", ""); + free(freep); + return py_path; + +error: + Py_XDECREF(py_path); + if (freep != NULL) + free(freep); + return NULL; +} +#endif + + +#ifdef __FreeBSD__ +// The tcplist fetching and walking is borrowed from netstat/inet.c. +static char * +psutil_fetch_tcplist(void) { + char *buf; + size_t len; + + for (;;) { + if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) { + free(buf); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return buf; + } +} + +static int +psutil_sockaddr_port(int family, struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (sin->sin_port); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (sin6->sin6_port); + } +} + +static void * +psutil_sockaddr_addr(int family, struct sockaddr_storage *ss) { + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (&sin->sin_addr); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (&sin6->sin6_addr); + } +} + +static socklen_t +psutil_sockaddr_addrlen(int family) { + if (family == AF_INET) + return (sizeof(struct in_addr)); + else + return (sizeof(struct in6_addr)); +} + +static int +psutil_sockaddr_matches(int family, int port, void *pcb_addr, + struct sockaddr_storage *ss) { + if (psutil_sockaddr_port(family, ss) != port) + return (0); + return (memcmp(psutil_sockaddr_addr(family, ss), pcb_addr, + psutil_sockaddr_addrlen(family)) == 0); +} + +static struct tcpcb * +psutil_search_tcplist(char *buf, struct kinfo_file *kif) { + struct tcpcb *tp; + struct inpcb *inp; + struct xinpgen *xig, *oxig; + struct xsocket *so; + + oxig = xig = (struct xinpgen *)buf; + for (xig = (struct xinpgen *)((char *)xig + xig->xig_len); + xig->xig_len > sizeof(struct xinpgen); + xig = (struct xinpgen *)((char *)xig + xig->xig_len)) { + tp = &((struct xtcpcb *)xig)->xt_tp; + inp = &((struct xtcpcb *)xig)->xt_inp; + so = &((struct xtcpcb *)xig)->xt_socket; + + if (so->so_type != kif->kf_sock_type || + so->xso_family != kif->kf_sock_domain || + so->xso_protocol != kif->kf_sock_protocol) + continue; + + if (kif->kf_sock_domain == AF_INET) { + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_lport, &inp->inp_laddr, + &kif->kf_sa_local)) + continue; + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_fport, &inp->inp_faddr, + &kif->kf_sa_peer)) + continue; + } else { + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_lport, &inp->in6p_laddr, + &kif->kf_sa_local)) + continue; + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_fport, &inp->in6p_faddr, + &kif->kf_sa_peer)) + continue; + } + + return (tp); + } + return NULL; +} + + +// a signaler for connections without an actual status +static int PSUTIL_CONN_NONE = 128; + +/* + * Return connections opened by process. + */ +static PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + long pid; + int i, cnt; + + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + char *tcplist = NULL; + struct tcpcb *tcp; + + 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 *py_type = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "lOO", &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) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + tcplist = psutil_fetch_tcplist(); + if (tcplist == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < cnt; i++) { + int lport, rport, state; + char lip[200], rip[200]; + char path[PATH_MAX]; + int inseq; + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + + kif = &freep[i]; + if (kif->kf_type == KF_TYPE_SOCKET) { + // apply filters + py_family = PyLong_FromLong((long)kif->kf_sock_domain); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + py_type = PyLong_FromLong((long)kif->kf_sock_type); + inseq = PySequence_Contains(py_type_filter, py_type); + Py_DECREF(py_type); + if (inseq == 0) + continue; + // IPv4 / IPv6 socket + if ((kif->kf_sock_domain == AF_INET) || + (kif->kf_sock_domain == AF_INET6)) { + // fill status + state = PSUTIL_CONN_NONE; + if (kif->kf_sock_type == SOCK_STREAM) { + tcp = psutil_search_tcplist(tcplist, kif); + if (tcp != NULL) + state = (int)tcp->t_state; + } + + // build addr and port + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, + &kif->kf_sa_local), + lip, + sizeof(lip)); + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, + &kif->kf_sa_peer), + rip, + sizeof(rip)); + lport = htons(psutil_sockaddr_port(kif->kf_sock_domain, + &kif->kf_sa_local)); + rport = htons(psutil_sockaddr_port(kif->kf_sock_domain, + &kif->kf_sa_peer)); + + // construct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue( + "(iiiNNi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_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 + else if (kif->kf_sock_domain == AF_UNIX) { + struct sockaddr_un *sun; + + sun = (struct sockaddr_un *)&kif->kf_sa_local; + snprintf( + path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + + py_tuple = Py_BuildValue( + "(iiisOi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + path, + Py_None, + 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; +} +#endif + + +#ifdef __FreeBSD__ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + static int maxcpus; + int mib[2]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + // retrieve maxcpus value + size = sizeof(maxcpus); + if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { + Py_DECREF(py_retlist); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + long cpu_time[maxcpus][CPUSTATES]; + + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // per-cpu info + size = sizeof(cpu_time); + if (sysctlbyname("kern.cp_times", &cpu_time, &size, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < ncpu; i++) { + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[i][CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_INTR] / CLOCKS_PER_SEC); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} +#endif + + +#ifdef __FreeBSD__ +// remove spaces from string +void remove_spaces(char *str) { + char *p1 = str; + char *p2 = str; + do + while (*p2 == ' ') + p2++; + while (*p1++ = *p2++); +} + + +/* + * Return a list of tuples for every process memory maps. + * 'procstat' cmdline utility has been used as an example. + */ +static PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) { + long pid; + int ptrwidth; + int i, cnt; + char addr[1000]; + char perms[4]; + const char *path; + struct kinfo_proc kp; + struct kinfo_vmentry *freep = NULL; + struct kinfo_vmentry *kve; + ptrwidth = 2 * sizeof(void *); + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kp) == -1) + goto error; + + freep = kinfo_getvmmap(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + for (i = 0; i < cnt; i++) { + py_tuple = NULL; + kve = &freep[i]; + addr[0] = '\0'; + perms[0] = '\0'; + sprintf(addr, "%#*jx-%#*jx", ptrwidth, (uintmax_t)kve->kve_start, + ptrwidth, (uintmax_t)kve->kve_end); + remove_spaces(addr); + strlcat(perms, kve->kve_protection & KVME_PROT_READ ? "r" : "-", + sizeof(perms)); + strlcat(perms, kve->kve_protection & KVME_PROT_WRITE ? "w" : "-", + sizeof(perms)); + strlcat(perms, kve->kve_protection & KVME_PROT_EXEC ? "x" : "-", + sizeof(perms)); + + if (strlen(kve->kve_path) == 0) { + switch (kve->kve_type) { + case KVME_TYPE_NONE: + path = "[none]"; + break; + case KVME_TYPE_DEFAULT: + path = "[default]"; + break; + case KVME_TYPE_VNODE: + path = "[vnode]"; + break; + case KVME_TYPE_SWAP: + path = "[swap]"; + break; + case KVME_TYPE_DEVICE: + path = "[device]"; + break; + case KVME_TYPE_PHYS: + path = "[phys]"; + break; + case KVME_TYPE_DEAD: + path = "[dead]"; + break; + case KVME_TYPE_SG: + path = "[sg]"; + break; + case KVME_TYPE_UNKNOWN: + path = "[unknown]"; + break; + default: + path = "[?]"; + break; + } + } + else { + path = kve->kve_path; + } + + py_tuple = Py_BuildValue("sssiiii", + addr, // "start-end" address + perms, // "rwx" permissions + path, // path + kve->kve_resident, // rss + kve->kve_private_resident, // private + kve->kve_ref_count, // ref count + kve->kve_shadow_count); // shadow count + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + free(freep); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (freep != NULL) + free(freep); + return NULL; +} +#endif + + +/* + * Return a list of tuples including device, mount point and fs type + * for all partitions mounted on the system. + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + int num; + int i; + long len; + uint64_t flags; + char opts[200]; + struct statfs *fs = NULL; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + + // get the number of mount points + Py_BEGIN_ALLOW_THREADS + num = getfsstat(NULL, 0, MNT_NOWAIT); + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + len = sizeof(*fs) * num; + fs = malloc(len); + if (fs == NULL) { + PyErr_NoMemory(); + goto error; + } + + Py_BEGIN_ALLOW_THREADS + num = getfsstat(fs, len, MNT_NOWAIT); + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < num; i++) { + py_tuple = NULL; + opts[0] = 0; + flags = fs[i].f_flags; + + // see sys/mount.h + if (flags & MNT_RDONLY) + strlcat(opts, "ro", sizeof(opts)); + else + strlcat(opts, "rw", sizeof(opts)); +#ifdef __FreeBSD__ + if (flags & MNT_SYNCHRONOUS) + strlcat(opts, ",sync", sizeof(opts)); + if (flags & MNT_NOEXEC) + strlcat(opts, ",noexec", sizeof(opts)); + if (flags & MNT_NOSUID) + strlcat(opts, ",nosuid", sizeof(opts)); + if (flags & MNT_UNION) + strlcat(opts, ",union", sizeof(opts)); + if (flags & MNT_ASYNC) + strlcat(opts, ",async", sizeof(opts)); + if (flags & MNT_SUIDDIR) + strlcat(opts, ",suiddir", sizeof(opts)); + if (flags & MNT_SOFTDEP) + strlcat(opts, ",softdep", sizeof(opts)); + if (flags & MNT_NOSYMFOLLOW) + strlcat(opts, ",nosymfollow", sizeof(opts)); + if (flags & MNT_GJOURNAL) + strlcat(opts, ",gjournal", sizeof(opts)); + if (flags & MNT_MULTILABEL) + strlcat(opts, ",multilabel", sizeof(opts)); + if (flags & MNT_ACLS) + strlcat(opts, ",acls", sizeof(opts)); + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); + if (flags & MNT_NOCLUSTERR) + strlcat(opts, ",noclusterr", sizeof(opts)); + if (flags & MNT_NOCLUSTERW) + strlcat(opts, ",noclusterw", sizeof(opts)); + if (flags & MNT_NFS4ACLS) + strlcat(opts, ",nfs4acls", sizeof(opts)); +#elif __OpenBSD__ + if (flags & MNT_SYNCHRONOUS) + strlcat(opts, ",sync", sizeof(opts)); + if (flags & MNT_NOEXEC) + strlcat(opts, ",noexec", sizeof(opts)); + if (flags & MNT_NOSUID) + strlcat(opts, ",nosuid", sizeof(opts)); + if (flags & MNT_ASYNC) + strlcat(opts, ",async", sizeof(opts)); + if (flags & MNT_SOFTDEP) + strlcat(opts, ",softdep", sizeof(opts)); + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); +#endif + py_tuple = Py_BuildValue("(ssss)", + fs[i].f_mntfromname, // device + fs[i].f_mntonname, // mount point + fs[i].f_fstypename, // fs type + opts); // options + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + + free(fs); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (fs != NULL) + free(fs); + return NULL; +} + + +/* + * Return a Python list of named tuples with overall network I/O information + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + char *buf = NULL, *lim, *next; + struct if_msghdr *ifm; + int mib[6]; + size_t len; + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + if (py_retdict == NULL) + return NULL; + + mib[0] = CTL_NET; // networking subsystem + mib[1] = PF_ROUTE; // type of information + mib[2] = 0; // protocol (IPPROTO_xxx) + mib[3] = 0; // address family + mib[4] = NET_RT_IFLIST; // operation + mib[5] = 0; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + lim = buf + len; + + for (next = buf; next < lim; ) { + py_ifc_info = NULL; + ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + + if (ifm->ifm_type == RTM_IFINFO) { + struct if_msghdr *if2m = (struct if_msghdr *)ifm; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); + char ifc_name[32]; + + strncpy(ifc_name, sdl->sdl_data, sdl->sdl_nlen); + ifc_name[sdl->sdl_nlen] = 0; + // XXX: ignore usbus interfaces: + // http://lists.freebsd.org/pipermail/freebsd-current/ + // 2011-October/028752.html + // 'ifconfig -a' doesn't show them, nor do we. + if (strncmp(ifc_name, "usbus", 5) == 0) + continue; + + py_ifc_info = Py_BuildValue("(kkkkkkki)", + if2m->ifm_data.ifi_obytes, + if2m->ifm_data.ifi_ibytes, + if2m->ifm_data.ifi_opackets, + if2m->ifm_data.ifi_ipackets, + if2m->ifm_data.ifi_ierrors, + if2m->ifm_data.ifi_oerrors, + if2m->ifm_data.ifi_iqdrops, + 0); // dropout not supported + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ifc_name, py_ifc_info)) + goto error; + Py_DECREF(py_ifc_info); + } + else { + continue; + } + } + + free(buf); + return py_retdict; + +error: + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (buf != NULL) + free(buf); + return NULL; +} + + +#ifdef __FreeBSD__ +/* + * Return a Python dict of tuples for disk I/O information + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + int i; + struct statinfo stats; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + + if (py_retdict == NULL) + return NULL; + if (devstat_checkversion(NULL) < 0) { + PyErr_Format(PyExc_RuntimeError, "devstat_checkversion() failed"); + goto error; + } + + stats.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo)); + if (stats.dinfo == NULL) { + PyErr_NoMemory(); + goto error; + } + bzero(stats.dinfo, sizeof(struct devinfo)); + + if (devstat_getdevs(NULL, &stats) == -1) { + PyErr_Format(PyExc_RuntimeError, "devstat_getdevs() failed"); + goto error; + } + + for (i = 0; i < stats.dinfo->numdevs; i++) { + py_disk_info = NULL; + struct devstat current; + char disk_name[128]; + current = stats.dinfo->devices[i]; + snprintf(disk_name, sizeof(disk_name), "%s%d", + current.device_name, + current.unit_number); + + py_disk_info = Py_BuildValue( + "(KKKKLL)", + current.operations[DEVSTAT_READ], // no reads + current.operations[DEVSTAT_WRITE], // no writes + current.bytes[DEVSTAT_READ], // bytes read + current.bytes[DEVSTAT_WRITE], // bytes written + (long long) BT2MSEC(current.duration[DEVSTAT_READ]), // r time + (long long) BT2MSEC(current.duration[DEVSTAT_WRITE]) // w time + ); // finished transactions + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, disk_name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + if (stats.dinfo->mem_ptr) + free(stats.dinfo->mem_ptr); + free(stats.dinfo); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats.dinfo != NULL) + free(stats.dinfo); + return NULL; +} +#endif + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) { + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + +#if __FreeBSD_version < 900000 || __OpenBSD__ + struct utmp ut; + FILE *fp; + + fp = fopen(_PATH_UTMP, "r"); + if (fp == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + while (fread(&ut, sizeof(ut), 1, fp) == 1) { + if (*ut.ut_name == '\0') + continue; + py_tuple = Py_BuildValue( + "(sssf)", + ut.ut_name, // username + ut.ut_line, // tty + ut.ut_host, // hostname + (float)ut.ut_time); // start time + if (!py_tuple) { + fclose(fp); + goto error; + } + if (PyList_Append(py_retlist, py_tuple)) { + fclose(fp); + goto error; + } + Py_DECREF(py_tuple); + } + + fclose(fp); +#else + struct utmpx *utx; + + while ((utx = getutxent()) != NULL) { + if (utx->ut_type != USER_PROCESS) + continue; + py_tuple = Py_BuildValue( + "(sssf)", + utx->ut_user, // username + utx->ut_line, // tty + utx->ut_host, // hostname + (float)utx->ut_tv.tv_sec // start time + ); + + if (!py_tuple) { + endutxent(); + goto error; + } + if (PyList_Append(py_retlist, py_tuple)) { + endutxent(); + goto error; + } + Py_DECREF(py_tuple); + } + + endutxent(); +#endif + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + + +#ifdef __FreeBSD__ +/* + * System-wide open connections. + */ +#define HASHSIZE 1009 +static struct xfile *psutil_xfiles; +static int psutil_nxfiles; + +int +psutil_populate_xfiles() { + size_t len; + + if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) { + PyErr_NoMemory(); + return 0; + } + while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) { + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + return 0; + } + len *= 2; + if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) { + PyErr_NoMemory(); + return 0; + } + } + if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) { + PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch"); + return 0; + } + psutil_nxfiles = len / sizeof *psutil_xfiles; + return 1; +} + +int +psutil_get_pid_from_sock(int sock_hash) { + struct xfile *xf; + int hash, n; + for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) { + if (xf->xf_data == NULL) + continue; + hash = (int)((uintptr_t)xf->xf_data % HASHSIZE); + if (sock_hash == hash) + return xf->xf_pid; + } + return -1; +} + + +// Reference: +// https://gitorious.org/freebsd/freebsd/source/ +// f1d6f4778d2044502209708bc167c05f9aa48615:usr.bin/sockstat/sockstat.c +int psutil_gather_inet(int proto, PyObject *py_retlist) { + struct xinpgen *xig, *exig; + struct xinpcb *xip; + struct xtcpcb *xtp; + struct inpcb *inp; + struct xsocket *so; + const char *varname = NULL; + size_t len, bufsize; + void *buf; + int hash; + int retry; + int type; + + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + + switch (proto) { + case IPPROTO_TCP: + varname = "net.inet.tcp.pcblist"; + type = SOCK_STREAM; + break; + case IPPROTO_UDP: + varname = "net.inet.udp.pcblist"; + type = SOCK_DGRAM; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) + continue; // XXX + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xig = (struct xinpgen *)buf; + exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig); + if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xig->xig_gen != exig->xig_gen && retry--); + + + for (;;) { + int lport, rport, pid, status, family; + + xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); + if (xig >= exig) + break; + + switch (proto) { + case IPPROTO_TCP: + xtp = (struct xtcpcb *)xig; + if (xtp->xt_len != sizeof *xtp) { + PyErr_Format(PyExc_RuntimeError, + "struct xtcpcb size mismatch"); + goto error; + } + inp = &xtp->xt_inp; + so = &xtp->xt_socket; + status = xtp->xt_tp.t_state; + break; + case IPPROTO_UDP: + xip = (struct xinpcb *)xig; + if (xip->xi_len != sizeof *xip) { + PyErr_Format(PyExc_RuntimeError, + "struct xinpcb size mismatch"); + goto error; + } + inp = &xip->xi_inp; + so = &xip->xi_socket; + status = PSUTIL_CONN_NONE; + break; + default: + PyErr_Format(PyExc_RuntimeError, "invalid proto"); + goto error; + } + + char lip[200], rip[200]; + + hash = (int)((uintptr_t)so->xso_so % HASHSIZE); + pid = psutil_get_pid_from_sock(hash); + if (pid < 0) + continue; + lport = ntohs(inp->inp_lport); + rport = ntohs(inp->inp_fport); + + if (inp->inp_vflag & INP_IPV4) { + family = AF_INET; + inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip)); + inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip)); + } + else if (inp->inp_vflag & INP_IPV6) { + family = AF_INET6; + inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip)); + inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip)); + } + + // construct python tuple/list + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue("(iiiNNii)", -1, family, type, py_laddr, + py_raddr, status, pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + + free(buf); + return 1; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + free(buf); + return 0; +} + + +int psutil_gather_unix(int proto, PyObject *py_retlist) { + struct xunpgen *xug, *exug; + struct xunpcb *xup; + const char *varname = NULL; + const char *protoname = NULL; + size_t len; + size_t bufsize; + void *buf; + int hash; + int retry; + int pid; + struct sockaddr_un *sun; + char path[PATH_MAX]; + + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + + switch (proto) { + case SOCK_STREAM: + varname = "net.local.stream.pcblist"; + protoname = "stream"; + break; + case SOCK_DGRAM: + varname = "net.local.dgram.pcblist"; + protoname = "dgram"; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xug = (struct xunpgen *)buf; + exug = (struct xunpgen *)(void *) + ((char *)buf + len - sizeof *exug); + if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xug->xug_gen != exug->xug_gen && retry--); + + for (;;) { + xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); + if (xug >= exug) + break; + xup = (struct xunpcb *)xug; + if (xup->xu_len != sizeof *xup) + goto error; + + hash = (int)((uintptr_t) xup->xu_socket.xso_so % HASHSIZE); + pid = psutil_get_pid_from_sock(hash); + if (pid < 0) + continue; + + sun = (struct sockaddr_un *)&xup->xu_addr; + snprintf(path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + + py_tuple = Py_BuildValue("(iiisOii)", -1, AF_UNIX, proto, path, + Py_None, PSUTIL_CONN_NONE, pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_INCREF(Py_None); + } + + free(buf); + return 1; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + free(buf); + return 0; +} + + +/* + * Return system-wide open connections. + */ +static PyObject* +psutil_net_connections(PyObject* self, PyObject* args) { + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (psutil_populate_xfiles() != 1) + goto error; + if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0) + goto error; + if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0) + goto error; + + free(psutil_xfiles); + return py_retlist; + +error: + Py_DECREF(py_retlist); + free(psutil_xfiles); + return NULL; +} + + +/* + * Get process CPU affinity. + * Reference: http://sources.freebsd.org/RELENG_9/src/usr.bin/cpuset/cpuset.c + */ +static PyObject* +psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args) { + long pid; + int ret; + int i; + cpuset_t mask; + PyObject* py_retlist; + PyObject* py_cpu_num; + + if (!PyArg_ParseTuple(args, "i", &pid)) + return NULL; + ret = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, + sizeof(mask), &mask); + if (ret != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + py_retlist = PyList_New(0); + if (py_retlist == NULL) + return NULL; + + for (i = 0; i < CPU_SETSIZE; i++) { + if (CPU_ISSET(i, &mask)) { + py_cpu_num = Py_BuildValue("i", i); + if (py_cpu_num == NULL) + goto error; + if (PyList_Append(py_retlist, py_cpu_num)) + goto error; + } + } + + return py_retlist; + +error: + Py_XDECREF(py_cpu_num); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Set process CPU affinity. + * Reference: http://sources.freebsd.org/RELENG_9/src/usr.bin/cpuset/cpuset.c + */ +static PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + long pid; + int i; + int seq_len; + int ret; + cpuset_t cpu_set; + PyObject *py_cpu_set; + PyObject *py_cpu_seq = NULL; + + if (!PyArg_ParseTuple(args, "lO", &pid, &py_cpu_set)) + return NULL; + + py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer"); + if (!py_cpu_seq) + return NULL; + seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq); + + // calculate the mask + CPU_ZERO(&cpu_set); + for (i = 0; i < seq_len; i++) { + PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i); +#if PY_MAJOR_VERSION >= 3 + long value = PyLong_AsLong(item); +#else + long value = PyInt_AsLong(item); +#endif + if (value == -1 && PyErr_Occurred()) + goto error; + CPU_SET(value, &cpu_set); + } + + // set affinity + ret = cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, + sizeof(cpu_set), &cpu_set); + if (ret != 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + Py_DECREF(py_cpu_seq); + Py_RETURN_NONE; + +error: + if (py_cpu_seq != NULL) + Py_DECREF(py_cpu_seq); + return NULL; +} +#endif + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = { + // --- per-process functions + + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Return connections opened by process"}, + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_ppid", psutil_proc_ppid, METH_VARARGS, + "Return process ppid as an integer"}, + {"proc_uids", psutil_proc_uids, METH_VARARGS, + "Return process real effective and saved user ids as a Python tuple"}, + {"proc_gids", psutil_proc_gids, METH_VARARGS, + "Return process real effective and saved group ids as a Python tuple"}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return tuple of user/kern time for the given PID"}, + {"proc_create_time", psutil_proc_create_time, METH_VARARGS, + "Return a float indicating the process create time expressed in " + "seconds since the epoch"}, + {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS, + "Return extended memory info for a process as a Python tuple."}, + {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Return the number of context switches performed by process"}, + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads"}, + {"proc_status", psutil_proc_status, METH_VARARGS, + "Return process status as an integer"}, + {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS, + "Return process IO counters"}, + {"proc_tty_nr", psutil_proc_tty_nr, METH_VARARGS, + "Return process tty (terminal) number"}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory."}, +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || __OpenBSD__ + {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, + "Return the number of file descriptors opened by this process"}, +#endif +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 || __OpenBSD__ + {"proc_open_files", psutil_proc_open_files, METH_VARARGS, + "Return files opened by process as a list of (path, fd) tuples"}, +#endif + +#ifdef __FreeBSD__ + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return process pathname executable"}, + {"proc_num_threads", psutil_proc_num_threads, METH_VARARGS, + "Return number of threads used by process"}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return a list of tuples for every process's memory map"}, + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, + "Return process CPU affinity."}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, + "Set process CPU affinity."}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Return an XML string to determine the number physical CPUs."}, +#endif + + // --- system-related functions + + {"pids", psutil_pids, METH_VARARGS, + "Returns a list of PIDs currently running on the system"}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, + "Return number of logical CPUs on the system"}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory usage statistics"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return swap mem stats"}, + {"cpu_times", psutil_cpu_times, METH_VARARGS, + "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return the system boot time expressed in seconds since the epoch."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return a list of tuples including device, mount point and " + "fs type for all partitions mounted on the system."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return dict of tuples of networks I/O information."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return a Python dict of tuples for disk I/O information"}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users as a list of tuples"}, +#ifdef __FreeBSD__ + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return system-wide open connections."}, +#endif + {NULL, NULL, 0, NULL} +}; + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_bsd_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_bsd_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef + moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_bsd", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_bsd_traverse, + psutil_bsd_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_bsd(void) + +#else +#define INITERROR return + +void init_psutil_bsd(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_bsd", PsutilMethods); +#endif + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + // process status constants + +#ifdef __FreeBSD__ + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SRUN", SRUN); + PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + PyModule_AddIntConstant(module, "SWAIT", SWAIT); + PyModule_AddIntConstant(module, "SLOCK", SLOCK); +#elif __OpenBSD__ + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SRUN", SRUN); + PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); // unused + PyModule_AddIntConstant(module, "SDEAD", SDEAD); + PyModule_AddIntConstant(module, "SONPROC", SONPROC); +#endif + + // connection status constants + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + // PSUTIL_CONN_NONE + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", 128); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c index 1e27cdb0..07404206 100644 --- a/psutil/_psutil_posix.c +++ b/psutil/_psutil_posix.c @@ -26,7 +26,7 @@ #include <linux/if_packet.h> #endif // end linux -#if defined(__FreeBSD__) || defined(__APPLE__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) #include <netdb.h> #include <netinet/in.h> #include <net/if_dl.h> @@ -120,7 +120,7 @@ psutil_convert_ipaddr(struct sockaddr *addr, int family) { data = (const char *)lladdr->sll_addr; } #endif -#if defined(__FreeBSD__) || defined(__APPLE__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) else if (addr->sa_family == AF_LINK) { // Note: prior to Python 3.4 socket module does not expose // AF_LINK so we'll do. @@ -250,7 +250,7 @@ error: * net_if_stats() implementation. This is here because it is common * to both OSX and FreeBSD and I didn't know where else to put it. */ -#if defined(__FreeBSD__) || defined(__APPLE__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) #include <sys/sockio.h> #include <net/if_media.h> @@ -287,8 +287,8 @@ int psutil_get_nic_speed(int ifm_active) { case(IFM_1000_SX): // 1000BaseSX - multi-mode fiber case(IFM_1000_LX): // 1000baseLX - single-mode fiber case(IFM_1000_CX): // 1000baseCX - 150ohm STP -#if defined(IFM_1000_TX) && !defined(OPENBSD) - // FreeBSD 4 and others (but NOT OpenBSD)? +#if defined(IFM_1000_TX) && !defined(__OpenBSD__) + // FreeBSD 4 and others (but NOT OpenBSD) -> #define IFM_1000_T in net/if_media.h case(IFM_1000_TX): #endif #ifdef IFM_1000_FX @@ -478,7 +478,7 @@ PsutilMethods[] = { "Set process priority"}, {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, "Retrieve NICs information"}, -#if defined(__FreeBSD__) || defined(__APPLE__) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) {"net_if_stats", psutil_net_if_stats, METH_VARARGS, "Return NIC stats."}, #endif @@ -537,7 +537,7 @@ void init_psutil_posix(void) PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods); #endif -#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__sun) +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__sun) PyModule_AddIntConstant(module, "AF_LINK", AF_LINK); #endif diff --git a/psutil/arch/bsd/openbsd.c b/psutil/arch/bsd/openbsd.c new file mode 100644 index 00000000..5e949241 --- /dev/null +++ b/psutil/arch/bsd/openbsd.c @@ -0,0 +1,761 @@ +/* + * 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. + * + * Platform-specific module methods for OpenBSD. + */ + +#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 <sys/swap.h> // for swap_mem +#include <signal.h> +#include <kvm.h> +// connection stuff +#include <netdb.h> // for NI_MAXHOST +#include <sys/socket.h> +#include <sys/sched.h> // for CPUSTATES & CP_* +#define _KERNEL // for DTYPE_* +#include <sys/file.h> +#undef _KERNEL +#include <sys/disk.h> // struct diskstats + + +#include "openbsd.h" + +// a signaler for connections without an actual status +int PSUTIL_CONN_NONE = 128; + +#define KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0) +#define 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(); + return -1; + } + return 0; +} + + +struct kinfo_file * +kinfo_getfile(long 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] = (int) 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_SetFromErrno(PyExc_OSError); + return NULL; + } + mib[5] = (int)(len / sizeof(struct kinfo_file)); + if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + *cnt = (int)(len / sizeof(struct kinfo_file)); + return kf; +} + + +int +psutil_pid_exists(long pid) { + // Return 1 if PID exists in the current process list, else 0, -1 + // on error. + // TODO: this should live in _psutil_posix.c but for some reason if I + // move it there I get a "include undefined symbol" error. + int ret; + if (pid < 0) + return 0; + ret = kill(pid , 0); + if (ret == 0) + return 1; + else { + if (ret == ESRCH) + return 0; + else if (ret == EPERM) + return 1; + else { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + } +} + + +int +psutil_raise_ad_or_nsp(long pid) { + // Set exception to AccessDenied if pid exists else NoSuchProcess. + if (psutil_pid_exists(pid) == 0) + NoSuchProcess(); + else + AccessDenied(); +} + + +// ============================================================================ +// Process related 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; + int done; + static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC, 0 }; + // Declaring name as const requires us to cast it when passing it to + // sysctl because the prototype doesn't include the const modifier. + size_t length; + char errbuf[_POSIX2_LINE_MAX]; + struct kinfo_proc *x; + 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 == NULL) { + return errno; + } + + result = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &cnt); + if (result == NULL) { + err(1, NULL); + return errno; + } + + *procCount = (size_t)cnt; + + size_t mlen = cnt * sizeof(struct kinfo_proc); + + if ((*procList = malloc(mlen)) == NULL) { + err(1, NULL); + return errno; + } + + memcpy(*procList, result, mlen); + assert(*procList != NULL); + kvm_close(kd); + + return 0; +} + + +char ** +_psutil_get_argv(long pid) { + static char **argv; + char **p; + 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 = realloc(argv, argv_size)) == NULL) + err(1, NULL); + if (sysctl(argv_mib, 4, argv, &argv_size, NULL, 0) == 0) + return argv; + if (errno == ESRCH) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (errno != ENOMEM) + err(1, NULL); + } +} + +// returns the command line as a python list object +PyObject * +psutil_get_cmdline(long 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 = Py_BuildValue("s", *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. + long 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, "l", &pid)) + goto error; + + kd = kvm_openfiles(0, 0, 0, O_RDONLY, errbuf); + if (! kd) { + if (strstr(errbuf, "Permission denied") != NULL) + AccessDenied(); + else + PyErr_Format(PyExc_RuntimeError, "kvm_openfiles() failed"); + 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(); + else + PyErr_Format(PyExc_RuntimeError, "kvm_getprocs() 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( + "Idd", + kp[i].p_tid, + KPT2DOUBLE(kp[i].p_uutime), + 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_virtual_mem(PyObject *self, PyObject *args) { + unsigned int total, active, inactive, wired, cached, free; + size_t size = sizeof(total); + struct uvmexp uvmexp; + int mib[] = {CTL_VM, VM_UVMEXP}; + long pagesize = getpagesize(); + size = sizeof(uvmexp); + + if (sysctl(mib, 2, &uvmexp, &size, NULL, 0) < 0) { + warn("failed to get vm.uvmexp"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return Py_BuildValue("KKKKKKKK", + (unsigned long long) uvmexp.npages * pagesize, + (unsigned long long) uvmexp.free * pagesize, + (unsigned long long) uvmexp.active * pagesize, + (unsigned long long) uvmexp.inactive * pagesize, + (unsigned long long) uvmexp.wired * pagesize, + (unsigned long long) 0, + (unsigned long long) 0, + (unsigned long long) 0 + ); +} + + +PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + uint64_t swap_total, swap_free; + struct swapent *swdev; + int nswap, i; + + if ((nswap = swapctl(SWAP_NSWAP, 0, 0)) == 0) { + warn("failed to get swap device count"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if ((swdev = calloc(nswap, sizeof(*swdev))) == NULL) { + warn("failed to allocate memory for swdev structures"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (swapctl(SWAP_STATS, swdev, nswap) == -1) { + free(swdev); + warn("failed to get swap stats"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + /* Total things up */ + swap_total = swap_free = 0; + for (i = 0; i < nswap; i++) { + if (swdev[i].se_flags & SWF_ENABLE) { + swap_free += (swdev[i].se_nblks - swdev[i].se_inuse); + swap_total += swdev[i].se_nblks; + } + } + return Py_BuildValue("(LLLII)", + swap_total * DEV_BSIZE, + (swap_total - swap_free) * DEV_BSIZE, + swap_free * DEV_BSIZE, + 0 /* XXX swap in */, + 0 /* XXX swap out */); +} + + +PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + long pid; + int cnt; + + struct kinfo_file *freep; + struct kinfo_proc kipp; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kipp) == -1) + return NULL; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + return NULL; + } + free(freep); + + return Py_BuildValue("i", cnt); +} + + +PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + // Reference: + // http://anoncvs.spacehopper.org/openbsd-src/tree/bin/ps/print.c#n179 + long pid; + struct kinfo_proc kp; + char path[MAXPATHLEN]; + size_t pathlen = sizeof path; + + if (! PyArg_ParseTuple(args, "l", &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 Py_BuildValue("s", 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; +} + + +PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + long pid; + int i, cnt; + + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + char *tcplist = NULL; + struct tcpcb *tcp; + + 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, "lOO", &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) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + for (i = 0; i < cnt; i++) { + int state; + int lport; + int rport; + char path[PATH_MAX]; + 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)); + (void *)(uintptr_t)kif->inp_ppcb; + 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)); + (void *)(uintptr_t)kif->inp_ppcb; + 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 + else if (kif->so_family == AF_UNIX) { + py_tuple = Py_BuildValue( + "(iiisOi)", + kif->fd_fd, + kif->so_family, + kif->so_type, + kif->unp_path, + Py_None, + 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; +} + + +PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + static int maxcpus; + int mib[3]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + uint64_t cpu_time[CPUSTATES]; + + for (i = 0; i < ncpu; i++) { + // per-cpu info + mib[0] = CTL_KERN; + mib[1] = KERN_CPTIME2; + mib[2] = i; + size = sizeof(cpu_time); + if (sysctl(mib, 3, &cpu_time, &size, NULL, 0) == -1) { + warn("failed to get kern.cptime2"); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} + + +PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + int i, dk_ndrive, mib[3]; + size_t len; + struct diskstats *stats; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + if (py_retdict == NULL) + return NULL; + + mib[0] = CTL_HW; + mib[1] = HW_DISKSTATS; + len = 0; + if (sysctl(mib, 2, NULL, &len, NULL, 0) < 0) { + warn("can't get hw.diskstats size"); + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + dk_ndrive = (int)(len / sizeof(struct diskstats)); + + stats = malloc(len); + if (stats == NULL) { + warn("can't malloc"); + PyErr_NoMemory(); + goto error; + } + if (sysctl(mib, 2, stats, &len, NULL, 0) < 0 ) { + warn("could not read hw.diskstats"); + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < dk_ndrive; i++) { + py_disk_info = Py_BuildValue( + "(KKKKLL)", + stats[i].ds_rxfer, + stats[i].ds_wxfer, + stats[i].ds_rbytes, + stats[i].ds_wbytes, + (long long) TV2DOUBLE(stats[i].ds_time) / 2, /* assume half read - half writes.. */ + (long long) TV2DOUBLE(stats[i].ds_time) / 2); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, stats[i].ds_name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + free(stats); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats != NULL) + free(stats); + return NULL; +} diff --git a/psutil/arch/bsd/openbsd.h b/psutil/arch/bsd/openbsd.h new file mode 100644 index 00000000..e859278e --- /dev/null +++ b/psutil/arch/bsd/openbsd.h @@ -0,0 +1,28 @@ +/* + * 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> + +typedef struct kinfo_proc kinfo_proc; + +int psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc); +struct kinfo_file * kinfo_getfile(long pid, int* cnt); +int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount); +char **_psutil_get_argv(long pid); +PyObject * psutil_get_cmdline(long pid); +int psutil_pid_exists(long pid); +int psutil_raise_ad_or_nsp(long pid); + +// +PyObject *psutil_proc_threads(PyObject *self, PyObject *args); +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_mem(PyObject *self, PyObject *args); +PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); +PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); +PyObject *psutil_proc_connections(PyObject *self, PyObject *args); +PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); +PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); diff --git a/psutil/arch/bsd/process_info.h b/psutil/arch/bsd/process_info.h index b70ca23c..a53adc05 100644 --- a/psutil/arch/bsd/process_info.h +++ b/psutil/arch/bsd/process_info.h @@ -8,6 +8,9 @@ typedef struct kinfo_proc kinfo_proc; +#ifdef __OpenBSD__ +struct kinfo_file * kinfo_getfile(long pid, int* cnt); +#endif char *psutil_get_cmd_args(long pid, size_t *argsize); char *psutil_get_cmd_path(long pid, size_t *pathsize); int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount); @@ -137,6 +137,18 @@ elif sys.platform.startswith("freebsd"): define_macros=[VERSION_MACRO], libraries=["devstat"]) extensions = [ext, posix_extension] +# OpenBSD +elif sys.platform.startswith("openbsd"): + ext = Extension( + 'psutil._psutil_bsd', + sources=[ + 'psutil/_psutil_openbsd.c', + 'psutil/_psutil_common.c', + 'psutil/arch/bsd/openbsd.c' + ], + define_macros=[VERSION_MACRO], + libraries=["kvm"]) + extensions = [ext, posix_extension] # Linux elif sys.platform.startswith("linux"): def get_ethtool_macro(): @@ -218,6 +230,7 @@ def main(): 'Operating System :: Microsoft', 'Operating System :: OS Independent', 'Operating System :: POSIX :: BSD :: FreeBSD', + 'Operating System :: POSIX :: BSD :: OpenBSD', 'Operating System :: POSIX :: Linux', 'Operating System :: POSIX :: SunOS/Solaris', 'Operating System :: POSIX', diff --git a/test/_bsd.py b/test/_freebsd.py index 7223fb96..1aac6213 100644 --- a/test/_bsd.py +++ b/test/_freebsd.py @@ -6,7 +6,7 @@ # TODO: add test for comparing connections with 'sockstat' cmd -"""BSD specific tests. These are implicitly run by test_psutil.py.""" +"""FreeBSD specific tests. These are implicitly run by test_psutil.py.""" import os import subprocess @@ -15,7 +15,7 @@ import time import psutil from psutil._compat import PY3 -from test_psutil import BSD +from test_psutil import FREEBSD from test_psutil import get_test_subprocess from test_psutil import MEMORY_TOLERANCE from test_psutil import reap_children @@ -55,8 +55,8 @@ def muse(field): return int(line.split()[1]) -@unittest.skipUnless(BSD, "not a BSD system") -class BSDSpecificTestCase(unittest.TestCase): +@unittest.skipUnless(FREEBSD, "not a FreeBSD system") +class FreeBSDSpecificTestCase(unittest.TestCase): @classmethod def setUpClass(cls): @@ -248,7 +248,7 @@ class BSDSpecificTestCase(unittest.TestCase): def main(): test_suite = unittest.TestSuite() - test_suite.addTest(unittest.makeSuite(BSDSpecificTestCase)) + test_suite.addTest(unittest.makeSuite(FreeBSDSpecificTestCase)) result = unittest.TextTestRunner(verbosity=2).run(test_suite) return result.wasSuccessful() diff --git a/test/test_psutil.py b/test/test_psutil.py index df00e3d5..9ca4d175 100644 --- a/test/test_psutil.py +++ b/test/test_psutil.py @@ -60,6 +60,7 @@ from psutil._compat import callable from psutil._compat import long from psutil._compat import PY3 from psutil._compat import unicode +from psutil._compat import which if sys.version_info < (2, 7): import unittest2 as unittest # https://pypi.python.org/pypi/unittest2 @@ -104,7 +105,9 @@ if WINDOWS: WIN_VISTA = (6, 0, 0) LINUX = sys.platform.startswith("linux") OSX = sys.platform.startswith("darwin") -BSD = sys.platform.startswith("freebsd") +FREEBSD = sys.platform.startswith("freebsd") +OPENBSD = sys.platform.startswith("openbsd") +BSD = FREEBSD or OPENBSD SUNOS = sys.platform.startswith("sunos") VALID_PROC_STATUSES = [getattr(psutil, x) for x in dir(psutil) if x.startswith('STATUS_')] @@ -214,23 +217,6 @@ def sh(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE): return stdout.strip() -def which(program): - """Same as UNIX which command. Return None on command not found.""" - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - - if POSIX: def get_kernel_version(): """Return a tuple such as (2, 6, 36).""" @@ -1585,7 +1571,13 @@ class TestProcess(unittest.TestCase): # thread number, since we always have with 1 thread per process, # but this does not apply across all platforms (OSX, Windows) p = psutil.Process() - step1 = p.num_threads() + if OPENBSD: + try: + step1 = p.num_threads() + except psutil.AccessDenied: + raise unittest.SkipTest("on OpenBSD this requires root access") + else: + step1 = p.num_threads() thread = ThreadTask() thread.start() @@ -1605,7 +1597,13 @@ class TestProcess(unittest.TestCase): def test_threads(self): p = psutil.Process() - step1 = p.threads() + if OPENBSD: + try: + step1 = p.threads() + except psutil.AccessDenied: + raise unittest.SkipTest("on OpenBSD this requires root access") + else: + step1 = p.threads() thread = ThreadTask() thread.start() @@ -1629,6 +1627,12 @@ class TestProcess(unittest.TestCase): def test_threads_2(self): p = psutil.Process() + if OPENBSD: + try: + p.threads() + except psutil.AccessDenied: + raise unittest.SkipTest( + "on OpenBSD this requires root access") self.assertAlmostEqual(p.cpu_times().user, p.threads()[0].user_time, delta=0.1) self.assertAlmostEqual(p.cpu_times().system, @@ -1657,6 +1661,7 @@ class TestProcess(unittest.TestCase): # def test_memory_info_ex(self): # # tested later in fetch all test suite + @unittest.skipIf(OPENBSD, "not available on OpenBSD") def test_memory_maps(self): p = psutil.Process() maps = p.memory_maps() @@ -1847,7 +1852,7 @@ class TestProcess(unittest.TestCase): p = psutil.Process(sproc.pid) call_until(p.cwd, "ret == os.path.dirname(os.getcwd())") - @unittest.skipUnless(WINDOWS or LINUX or BSD, + @unittest.skipUnless(WINDOWS or LINUX or FREEBSD, 'not available on this platform') @unittest.skipIf(LINUX and TRAVIS, "unknown failure on travis") def test_cpu_affinity(self): @@ -1946,8 +1951,8 @@ class TestProcess(unittest.TestCase): for c in psutil.net_connections(kind='all'): if c.pid == pid: sys_cons.append(pconn(*c[:-1])) - if BSD: - # on BSD all fds are set to -1 + if FREEBSD: + # on FreeBSD all fds are set to -1 proc_cons = [pconn(*[-1] + list(x[1:])) for x in proc_cons] self.assertEqual(sorted(proc_cons), sorted(sys_cons)) @@ -2123,6 +2128,7 @@ class TestProcess(unittest.TestCase): self.assertEqual(p.num_fds(), start) @skip_on_not_implemented(only_if=LINUX) + @unittest.skipIf(OPENBSD, "not reliable on OpenBSD") def test_num_ctx_switches(self): p = psutil.Process() before = sum(p.num_ctx_switches()) @@ -2280,6 +2286,11 @@ class TestProcess(unittest.TestCase): name) except psutil.NoSuchProcess: pass + except psutil.AccessDenied: + if OPENBSD and name in ('threads', 'num_threads'): + pass + else: + raise except NotImplementedError: pass else: @@ -2377,7 +2388,7 @@ class TestProcess(unittest.TestCase): def test_pid_0(self): # Process(0) is supposed to work on all platforms except Linux - if 0 not in psutil.pids(): + if 0 not in psutil.pids() and not OPENBSD: self.assertRaises(psutil.NoSuchProcess, psutil.Process, 0) return @@ -2418,8 +2429,11 @@ class TestProcess(unittest.TestCase): except psutil.AccessDenied: pass - self.assertIn(0, psutil.pids()) - self.assertTrue(psutil.pid_exists(0)) + p.as_dict() + + if not OPENBSD: + self.assertIn(0, psutil.pids()) + self.assertTrue(psutil.pid_exists(0)) def test_Popen(self): # Popen class test @@ -2505,7 +2519,7 @@ class TestFetchAllProcesses(unittest.TestCase): if ret not in (0, 0.0, [], None, ''): assert ret, ret meth = getattr(self, name) - meth(ret) + meth(ret, p) except Exception as err: s = '\n' + '=' * 70 + '\n' s += "FAIL: test_%s (proc=%s" % (name, p) @@ -2515,6 +2529,7 @@ class TestFetchAllProcesses(unittest.TestCase): s += '-' * 70 s += "\n%s" % traceback.format_exc() s = "\n".join((" " * 4) + i for i in s.splitlines()) + s += '\n' failures.append(s) break @@ -2525,10 +2540,10 @@ class TestFetchAllProcesses(unittest.TestCase): # special cases. self.assertTrue(valid_procs > 0) - def cmdline(self, ret): + def cmdline(self, ret, proc): pass - def exe(self, ret): + def exe(self, ret, proc): if not ret: self.assertEqual(ret, '') else: @@ -2541,49 +2556,55 @@ class TestFetchAllProcesses(unittest.TestCase): # XXX may fail on OSX self.assertTrue(os.access(ret, os.X_OK)) - def ppid(self, ret): + def ppid(self, ret, proc): self.assertTrue(ret >= 0) - def name(self, ret): + def name(self, ret, proc): self.assertIsInstance(ret, (str, unicode)) self.assertTrue(ret) - def create_time(self, ret): - self.assertTrue(ret > 0) + def create_time(self, ret, proc): + try: + self.assertGreaterEqual(ret, 0) + except AssertionError: + if OPENBSD and proc.status == psutil.STATUS_ZOMBIE: + pass + else: + raise # this can't be taken for granted on all platforms # self.assertGreaterEqual(ret, psutil.boot_time()) # make sure returned value can be pretty printed # with strftime time.strftime("%Y %m %d %H:%M:%S", time.localtime(ret)) - def uids(self, ret): + def uids(self, ret, proc): for uid in ret: self.assertTrue(uid >= 0) self.assertIn(uid, self._uids) - def gids(self, ret): + def gids(self, ret, proc): # note: testing all gids as above seems not to be reliable for # gid == 30 (nodoby); not sure why. for gid in ret: self.assertTrue(gid >= 0) # self.assertIn(uid, self.gids - def username(self, ret): + def username(self, ret, proc): self.assertTrue(ret) if POSIX: self.assertIn(ret, self._usernames) - def status(self, ret): + def status(self, ret, proc): self.assertTrue(ret != "") self.assertTrue(ret != '?') self.assertIn(ret, VALID_PROC_STATUSES) - def io_counters(self, ret): + def io_counters(self, ret, proc): for field in ret: if field != -1: self.assertTrue(field >= 0) - def ionice(self, ret): + def ionice(self, ret, proc): if LINUX: self.assertTrue(ret.ioclass >= 0) self.assertTrue(ret.value >= 0) @@ -2591,24 +2612,24 @@ class TestFetchAllProcesses(unittest.TestCase): self.assertTrue(ret >= 0) self.assertIn(ret, (0, 1, 2)) - def num_threads(self, ret): + def num_threads(self, ret, proc): self.assertTrue(ret >= 1) - def threads(self, ret): + def threads(self, ret, proc): for t in ret: self.assertTrue(t.id >= 0) self.assertTrue(t.user_time >= 0) self.assertTrue(t.system_time >= 0) - def cpu_times(self, ret): + def cpu_times(self, ret, proc): self.assertTrue(ret.user >= 0) self.assertTrue(ret.system >= 0) - def memory_info(self, ret): + def memory_info(self, ret, proc): self.assertTrue(ret.rss >= 0) self.assertTrue(ret.vms >= 0) - def memory_info_ex(self, ret): + def memory_info_ex(self, ret, proc): for name in ret._fields: self.assertTrue(getattr(ret, name) >= 0) if POSIX and ret.vms != 0: @@ -2623,7 +2644,7 @@ class TestFetchAllProcesses(unittest.TestCase): assert ret.peak_nonpaged_pool >= ret.nonpaged_pool, ret assert ret.peak_pagefile >= ret.pagefile, ret - def open_files(self, ret): + def open_files(self, ret, proc): for f in ret: if WINDOWS: assert f.fd == -1, f @@ -2632,15 +2653,15 @@ class TestFetchAllProcesses(unittest.TestCase): assert os.path.isabs(f.path), f assert os.path.isfile(f.path), f - def num_fds(self, ret): + def num_fds(self, ret, proc): self.assertTrue(ret >= 0) - def connections(self, ret): + def connections(self, ret, proc): self.assertEqual(len(ret), len(set(ret))) for conn in ret: check_connection_ntuple(conn) - def cwd(self, ret): + def cwd(self, ret, proc): if ret is not None: # BSD may return None assert os.path.isabs(ret), ret try: @@ -2652,21 +2673,21 @@ class TestFetchAllProcesses(unittest.TestCase): else: self.assertTrue(stat.S_ISDIR(st.st_mode)) - def memory_percent(self, ret): + def memory_percent(self, ret, proc): assert 0 <= ret <= 100, ret - def is_running(self, ret): + def is_running(self, ret, proc): self.assertTrue(ret) - def cpu_affinity(self, ret): + def cpu_affinity(self, ret, proc): assert ret != [], ret - def terminal(self, ret): + def terminal(self, ret, proc): if ret is not None: assert os.path.isabs(ret), ret assert os.path.exists(ret), ret - def memory_maps(self, ret): + def memory_maps(self, ret, proc): for nt in ret: for fname in nt._fields: value = getattr(nt, fname) @@ -2682,13 +2703,13 @@ class TestFetchAllProcesses(unittest.TestCase): self.assertIsInstance(value, (int, long)) assert value >= 0, value - def num_handles(self, ret): + def num_handles(self, ret, proc): if WINDOWS: self.assertGreaterEqual(ret, 0) else: self.assertGreaterEqual(ret, 0) - def nice(self, ret): + def nice(self, ret, proc): if POSIX: assert -20 <= ret <= 20, ret else: @@ -2696,11 +2717,11 @@ class TestFetchAllProcesses(unittest.TestCase): if x.endswith('_PRIORITY_CLASS')] self.assertIn(ret, priorities) - def num_ctx_switches(self, ret): + def num_ctx_switches(self, ret, proc): self.assertTrue(ret.voluntary >= 0) self.assertTrue(ret.involuntary >= 0) - def rlimit(self, ret): + def rlimit(self, ret, proc): self.assertEqual(len(ret), 2) self.assertGreaterEqual(ret[0], -1) self.assertGreaterEqual(ret[1], -1) @@ -3092,6 +3113,7 @@ class TestExampleScripts(unittest.TestCase): def test_ifconfig(self): self.assert_stdout('ifconfig.py') + @unittest.skipIf(OPENBSD, "OpenBSD does not support memory maps") def test_pmap(self): self.assert_stdout('pmap.py', args=str(os.getpid())) @@ -3204,8 +3226,8 @@ def main(): tests.append(TestDualProcessImplementation) elif OSX: from _osx import OSXSpecificTestCase as stc - elif BSD: - from _bsd import BSDSpecificTestCase as stc + elif FREEBSD: + from _freebsd import FreeBSDSpecificTestCase as stc elif SUNOS: from _sunos import SunOSSpecificTestCase as stc if stc is not None: |