1From 13892923cf7b6b8d8f329e764a40c8eb2e0d2602 Mon Sep 17 00:00:00 2001
2From: =?UTF-8?q?Fran=C3=A7ois=20Revol?= <revol@free.fr>
3Date: Wed, 12 Feb 2020 04:16:40 +0100
4Subject: WIP: preliminary Haiku port
5
6
7diff --git a/psutil/__init__.py b/psutil/__init__.py
8index 8138db4..b326a86 100644
9--- a/psutil/__init__.py
10+++ b/psutil/__init__.py
11@@ -16,6 +16,7 @@ sensors) in Python. Supported platforms:
12  - NetBSD
13  - Sun Solaris
14  - AIX
15+ - Haiku
16 
17 Works with Python versions 2.7 and 3.6+.
18 """
19@@ -55,6 +56,7 @@ from ._common import CONN_SYN_RECV
20 from ._common import CONN_SYN_SENT
21 from ._common import CONN_TIME_WAIT
22 from ._common import FREEBSD  # NOQA
23+from ._common import HAIKU
24 from ._common import LINUX
25 from ._common import MACOS
26 from ._common import NETBSD  # NOQA
27@@ -141,6 +143,9 @@ elif AIX:
28     # via sys.modules.
29     PROCFS_PATH = "/proc"
30 
31+elif HAIKU:
32+    from . import _pshaiku as _psplatform
33+
34 else:  # pragma: no cover
35     raise NotImplementedError('platform %s is not supported' % sys.platform)
36 
37diff --git a/psutil/_common.py b/psutil/_common.py
38index 6989fea..34daca1 100644
39--- a/psutil/_common.py
40+++ b/psutil/_common.py
41@@ -50,8 +50,8 @@ _DEFAULT = object()
42 # fmt: off
43 __all__ = [
44     # OS constants
45-    'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
46-    'SUNOS', 'WINDOWS',
47+    'FREEBSD', 'BSD', 'HAIKU', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX',
48+    'POSIX', 'SUNOS', 'WINDOWS',
49     # connection constants
50     'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
51     'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
52@@ -97,6 +97,7 @@ NETBSD = sys.platform.startswith("netbsd")
53 BSD = FREEBSD or OPENBSD or NETBSD
54 SUNOS = sys.platform.startswith(("sunos", "solaris"))
55 AIX = sys.platform.startswith("aix")
56+HAIKU = sys.platform.startswith("haiku")
57 
58 
59 # ===================================================================
60diff --git a/psutil/_pshaiku.py b/psutil/_pshaiku.py
61new file mode 100644
62index 0000000..89115f7
63--- /dev/null
64+++ b/psutil/_pshaiku.py
65@@ -0,0 +1,433 @@
66+# Copyright (c) 2009, Giampaolo Rodola'
67+# Copyright (c) 2017, Arnon Yaari
68+# Copyright (c) 2020, Fran��ois Revol
69+# All rights reserved.
70+# Use of this source code is governed by a BSD-style license that can be
71+# found in the LICENSE file.
72+
73+"""Haiku platform implementation."""
74+
75+import functools
76+# import glob
77+import os
78+# import re
79+# import subprocess
80+# import sys
81+from collections import namedtuple
82+
83+from . import _common
84+from . import _psposix
85+from . import _psutil_haiku as cext
86+from . import _psutil_posix as cext_posix
87+from ._common import AccessDenied
88+# from ._common import conn_to_ntuple
89+from ._common import memoize_when_activated
90+from ._common import NoSuchProcess
91+from ._common import usage_percent
92+from ._common import ZombieProcess
93+from ._compat import FileNotFoundError
94+from ._compat import PermissionError
95+from ._compat import ProcessLookupError
96+# from ._compat import PY3
97+
98+
99+__extra__all__ = []
100+
101+
102+# =====================================================================
103+# --- globals
104+# =====================================================================
105+
106+
107+HAS_NET_IO_COUNTERS = hasattr(cext, "net_io_counters")
108+HAS_PROC_IO_COUNTERS = hasattr(cext, "proc_io_counters")
109+
110+PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
111+AF_LINK = cext_posix.AF_LINK
112+
113+PROC_STATUSES = {
114+    cext.B_THREAD_RUNNING: _common.STATUS_RUNNING,
115+    cext.B_THREAD_READY: _common.STATUS_IDLE,
116+    cext.B_THREAD_RECEIVING: _common.STATUS_WAITING,
117+    cext.B_THREAD_ASLEEP: _common.STATUS_SLEEPING,
118+    cext.B_THREAD_SUSPENDED: _common.STATUS_STOPPED,
119+    cext.B_THREAD_WAITING: _common.STATUS_WAITING,
120+}
121+
122+team_info_map = dict(
123+    thread_count=0,
124+    image_count=1,
125+    area_count=2,
126+    uid=3,
127+    gid=4,
128+    name=5)
129+
130+team_usage_info_map = dict(
131+    user_time=0,
132+    kernel_time=1)
133+
134+thread_info_map = dict(
135+    id=0,
136+    user_time=1,
137+    kernel_time=2,
138+    state=3)
139+
140+
141+# =====================================================================
142+# --- named tuples
143+# =====================================================================
144+
145+
146+# psutil.Process.memory_info()
147+pmem = namedtuple('pmem', ['rss', 'vms'])
148+# psutil.Process.memory_full_info()
149+pfullmem = pmem
150+# psutil.Process.cpu_times()
151+scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
152+# psutil.virtual_memory()
153+svmem = namedtuple('svmem', ['total', 'used', 'percent', 'cached', 'buffers',
154+                             'ignored', 'needed', 'available'])
155+
156+
157+# =====================================================================
158+# --- memory
159+# =====================================================================
160+
161+
162+def virtual_memory():
163+    total, inuse, cached, buffers, ignored, needed, avail = cext.virtual_mem()
164+    percent = usage_percent((total - avail), total, round_=1)
165+    return svmem(total, inuse, percent, cached, buffers, ignored, needed,
166+                 avail)
167+
168+
169+def swap_memory():
170+    """Swap system memory as a (total, used, free, sin, sout) tuple."""
171+    total, free = cext.swap_mem()
172+    sin = 0
173+    sout = 0
174+    used = total - free
175+    percent = usage_percent(used, total, round_=1)
176+    return _common.sswap(total, used, free, percent, sin, sout)
177+
178+
179+# =====================================================================
180+# --- CPU
181+# =====================================================================
182+
183+
184+def cpu_times():
185+    """Return system-wide CPU times as a named tuple"""
186+    ret = cext.per_cpu_times()
187+    return scputimes(*[sum(x) for x in zip(*ret)])
188+
189+
190+def per_cpu_times():
191+    """Return system per-CPU times as a list of named tuples"""
192+    ret = cext.per_cpu_times()
193+    return [scputimes(*x) for x in ret]
194+
195+
196+def cpu_count_logical():
197+    """Return the number of logical CPUs in the system."""
198+    try:
199+        return os.sysconf("SC_NPROCESSORS_ONLN")
200+    except ValueError:
201+        # mimic os.cpu_count() behavior
202+        return None
203+
204+
205+def cpu_count_physical():
206+    # TODO:
207+    return None
208+
209+
210+def cpu_stats():
211+    """Return various CPU stats as a named tuple."""
212+    ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats()
213+    return _common.scpustats(
214+        ctx_switches, interrupts, soft_interrupts, syscalls)
215+
216+
217+def cpu_freq():
218+    """Return CPU frequency.
219+    """
220+    curr, min_, max_ = cext.cpu_freq()
221+    return [_common.scpufreq(curr, min_, max_)]
222+
223+
224+# =====================================================================
225+# --- disks
226+# =====================================================================
227+
228+
229+disk_io_counters = cext.disk_io_counters
230+disk_usage = _psposix.disk_usage
231+
232+
233+def disk_partitions(all=False):
234+    """Return system disk partitions."""
235+    # TODO - the filtering logic should be better checked so that
236+    # it tries to reflect 'df' as much as possible
237+    retlist = []
238+    partitions = cext.disk_partitions()
239+    for partition in partitions:
240+        device, mountpoint, fstype, opts = partition
241+        if device == 'none':
242+            device = ''
243+        if not all:
244+            # Differently from, say, Linux, we don't have a list of
245+            # common fs types so the best we can do, AFAIK, is to
246+            # filter by filesystem having a total size > 0.
247+            if not disk_usage(mountpoint).total:
248+                continue
249+        ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
250+        retlist.append(ntuple)
251+    return retlist
252+
253+
254+# =====================================================================
255+# --- network
256+# =====================================================================
257+
258+
259+net_if_addrs = cext_posix.net_if_addrs
260+
261+if HAS_NET_IO_COUNTERS:
262+    net_io_counters = cext.net_io_counters
263+
264+
265+def net_connections(kind, _pid=-1):
266+    """Return socket connections.  If pid == -1 return system-wide
267+    connections (as opposed to connections opened by one process only).
268+    """
269+    # TODO
270+    return None
271+
272+
273+def net_if_stats():
274+    """Get NIC stats (isup, duplex, speed, mtu)."""
275+    # TODO
276+    return None
277+
278+
279+# =====================================================================
280+# --- other system functions
281+# =====================================================================
282+
283+
284+def boot_time():
285+    """The system boot time expressed in seconds since the epoch."""
286+    return cext.boot_time()
287+
288+
289+def users():
290+    """Return currently connected users as a list of namedtuples."""
291+    retlist = []
292+    rawlist = cext.users()
293+    localhost = (':0.0', ':0')
294+    for item in rawlist:
295+        user, tty, hostname, tstamp, user_process, pid = item
296+        # note: the underlying C function includes entries about
297+        # system boot, run level and others.  We might want
298+        # to use them in the future.
299+        if not user_process:
300+            continue
301+        if hostname in localhost:
302+            hostname = 'localhost'
303+        nt = _common.suser(user, tty, hostname, tstamp, pid)
304+        retlist.append(nt)
305+    return retlist
306+
307+
308+# =====================================================================
309+# --- processes
310+# =====================================================================
311+
312+
313+def pids():
314+    ls = cext.pids()
315+    return ls
316+
317+
318+pid_exists = _psposix.pid_exists
319+
320+
321+def wrap_exceptions(fun):
322+    """Call callable into a try/except clause and translate ENOENT,
323+    EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
324+    """
325+    @functools.wraps(fun)
326+    def wrapper(self, *args, **kwargs):
327+        try:
328+            return fun(self, *args, **kwargs)
329+        except (FileNotFoundError, ProcessLookupError):
330+            # ENOENT (no such file or directory) gets raised on open().
331+            # ESRCH (no such process) can get raised on read() if
332+            # process is gone in meantime.
333+            if not pid_exists(self.pid):
334+                raise NoSuchProcess(self.pid, self._name)
335+            else:
336+                raise ZombieProcess(self.pid, self._name, self._ppid)
337+        except PermissionError:
338+            raise AccessDenied(self.pid, self._name)
339+    return wrapper
340+
341+
342+class Process(object):
343+    """Wrapper class around underlying C implementation."""
344+
345+    __slots__ = ["pid", "_name", "_ppid", "_cache"]
346+
347+    def __init__(self, pid):
348+        self.pid = pid
349+        self._name = None
350+        self._ppid = None
351+
352+    def oneshot_enter(self):
353+        self._proc_team_info.cache_activate(self)
354+        self._proc_team_usage_info.cache_activate(self)
355+
356+    def oneshot_exit(self):
357+        self._proc_team_info.cache_deactivate(self)
358+        self._proc_team_usage_info.cache_deactivate(self)
359+
360+    @wrap_exceptions
361+    @memoize_when_activated
362+    def _proc_team_info(self):
363+        ret = cext.proc_team_info_oneshot(self.pid)
364+        print("%d %d\n" % (len(ret), len(team_info_map)))
365+        assert len(ret) == len(team_info_map)
366+        return ret
367+
368+    @wrap_exceptions
369+    @memoize_when_activated
370+    def _proc_team_usage_info(self):
371+        ret = cext.proc_team_usage_info_oneshot(self.pid)
372+        print("%d %d\n" % (len(ret), len(team_usage_info_map)))
373+        assert len(ret) == len(team_usage_info_map)
374+        return ret
375+
376+    @wrap_exceptions
377+    def name(self):
378+        return cext.proc_name(self.pid).rstrip("\x00")
379+
380+    @wrap_exceptions
381+    def exe(self):
382+        return cext.proc_exe(self.pid)
383+
384+    @wrap_exceptions
385+    def cmdline(self):
386+        return cext.proc_cmdline(self.pid)
387+
388+    @wrap_exceptions
389+    def environ(self):
390+        return cext.proc_environ(self.pid)
391+
392+    @wrap_exceptions
393+    def create_time(self):
394+        return None
395+
396+    @wrap_exceptions
397+    def num_threads(self):
398+        return self._proc_team_info()[team_info_map['thread_count']]
399+
400+    @wrap_exceptions
401+    def threads(self):
402+        rawlist = cext.proc_threads(self.pid)
403+        retlist = []
404+        for thread_id, utime, stime, state in rawlist:
405+            ntuple = _common.pthread(thread_id, utime, stime)
406+            retlist.append(ntuple)
407+        return retlist
408+
409+    @wrap_exceptions
410+    def connections(self, kind='inet'):
411+        ret = net_connections(kind, _pid=self.pid)
412+        # The underlying C implementation retrieves all OS connections
413+        # and filters them by PID.  At this point we can't tell whether
414+        # an empty list means there were no connections for process or
415+        # process is no longer active so we force NSP in case the PID
416+        # is no longer there.
417+        if not ret:
418+            # will raise NSP if process is gone
419+            os.stat('%s/%s' % (self._procfs_path, self.pid))
420+        return ret
421+
422+    @wrap_exceptions
423+    def nice_get(self):
424+        return cext_posix.getpriority(self.pid)
425+
426+    @wrap_exceptions
427+    def nice_set(self, value):
428+        return cext_posix.setpriority(self.pid, value)
429+
430+    @wrap_exceptions
431+    def ppid(self):
432+        return None
433+
434+    @wrap_exceptions
435+    def uids(self):
436+        uid = self._proc_team_info()[team_info_map['uid']]
437+        return _common.puids(uid, uid, uid)
438+
439+    @wrap_exceptions
440+    def gids(self):
441+        gid = self._proc_team_info()[team_info_map['gid']]
442+        return _common.puids(gid, gid, gid)
443+
444+    @wrap_exceptions
445+    def cpu_times(self):
446+        _user, _kern = self._proc_team_usage_info()
447+        return _common.pcputimes(_user, _kern, _user, _kern)
448+
449+    @wrap_exceptions
450+    def terminal(self):
451+        # TODO
452+        return None
453+
454+    @wrap_exceptions
455+    def cwd(self):
456+        return None
457+
458+    @wrap_exceptions
459+    def memory_info(self):
460+        # TODO:
461+        rss = 0
462+        vms = 0
463+        return pmem(rss, vms)
464+
465+    memory_full_info = memory_info
466+
467+    @wrap_exceptions
468+    def status(self):
469+        threads = cext.proc_threads(self.pid)
470+        code = threads[0][thread_info_map['state']]
471+        # XXX is '?' legit? (we're not supposed to return it anyway)
472+        return PROC_STATUSES.get(code, '?')
473+
474+    def open_files(self):
475+        # TODO:
476+        return []
477+
478+    @wrap_exceptions
479+    def num_fds(self):
480+        # TODO:
481+        return None
482+
483+    @wrap_exceptions
484+    def num_ctx_switches(self):
485+        return _common.pctxsw(
486+            *cext.proc_num_ctx_switches(self.pid))
487+
488+    @wrap_exceptions
489+    def wait(self, timeout=None):
490+        return _psposix.wait_pid(self.pid, timeout, self._name)
491+
492+    @wrap_exceptions
493+    def io_counters(self):
494+        return _common.pio(
495+            0,
496+            0,
497+            -1,
498+            -1)
499diff --git a/psutil/_psutil_haiku.cpp b/psutil/_psutil_haiku.cpp
500new file mode 100644
501index 0000000..f63ba07
502--- /dev/null
503+++ b/psutil/_psutil_haiku.cpp
504@@ -0,0 +1,840 @@
505+/*
506+ * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
507+ * Use of this source code is governed by a BSD-style license that can be
508+ * found in the LICENSE file.
509+ *
510+ * macOS platform-specific module methods.
511+ */
512+
513+#include <Python.h>
514+#include <assert.h>
515+#include <errno.h>
516+#include <stdbool.h>
517+#include <stdlib.h>
518+#include <stdio.h>
519+#include <arpa/inet.h>
520+#include <net/if_dl.h>
521+#include <pwd.h>
522+
523+#include <fs_info.h>
524+#include <image.h>
525+#include <OS.h>
526+#include <String.h>
527+
528+extern "C" {
529+
530+#include "_psutil_common.h"
531+#include "_psutil_posix.h"
532+//#include "arch/haiku/process_info.h"
533+
534+}
535+
536+static PyObject *ZombieProcessError;
537+
538+
539+/*
540+ * Return a Python list of all the PIDs running on the system.
541+ */
542+static PyObject *
543+psutil_pids(PyObject *self, PyObject *args) {
544+    int32 cookie = 0;
545+    team_info info;
546+    PyObject *py_pid = NULL;
547+    PyObject *py_retlist = PyList_New(0);
548+
549+    if (py_retlist == NULL)
550+        return NULL;
551+
552+    while (get_next_team_info(&cookie, &info) == B_OK) {
553+        /* quite wasteful: we have all team infos already */
554+        py_pid = Py_BuildValue("i", info.team);
555+        if (! py_pid)
556+            goto error;
557+        if (PyList_Append(py_retlist, py_pid))
558+            goto error;
559+        Py_CLEAR(py_pid);
560+    }
561+
562+    return py_retlist;
563+
564+error:
565+    Py_XDECREF(py_pid);
566+    Py_DECREF(py_retlist);
567+    return NULL;
568+}
569+
570+
571+/*
572+ * Return multiple process info as a Python tuple in one shot by
573+ * using get_team_info() and filling up a team_info struct.
574+ */
575+static PyObject *
576+psutil_proc_team_info_oneshot(PyObject *self, PyObject *args) {
577+    pid_t pid;
578+    team_info info;
579+    PyObject *py_name;
580+    PyObject *py_retlist;
581+
582+    if (! PyArg_ParseTuple(args, "l", &pid))
583+        return NULL;
584+    if (get_team_info(pid, &info) != B_OK)
585+        return NULL;
586+
587+    py_name = PyUnicode_DecodeFSDefault(info.args);
588+    if (! py_name) {
589+        // Likely a decoding error. We don't want to fail the whole
590+        // operation. The python module may retry with proc_name().
591+        PyErr_Clear();
592+        py_name = Py_None;
593+    }
594+
595+    py_retlist = Py_BuildValue(
596+        "lllllO",
597+        (long)info.thread_count,                   // (long) thread_count
598+        (long)info.image_count,                    // (long) image_count
599+        (long)info.area_count,                     // (long) area_count
600+        (long)info.uid,                            // (long) uid
601+        (long)info.gid,                            // (long) gid
602+        py_name                                    // (pystr) name
603+    );
604+
605+    if (py_retlist != NULL) {
606+        // XXX shall we decref() also in case of Py_BuildValue() error?
607+        Py_DECREF(py_name);
608+    }
609+    return py_retlist;
610+}
611+
612+
613+/*
614+ * Return multiple process info as a Python tuple in one shot by
615+ * using get_team_usage_info() and filling up a team_usage_info struct.
616+ */
617+static PyObject *
618+psutil_proc_team_usage_info_oneshot(PyObject *self, PyObject *args) {
619+    pid_t pid;
620+    team_usage_info info;
621+    PyObject *py_retlist;
622+
623+    if (! PyArg_ParseTuple(args, "l", &pid))
624+        return NULL;
625+    if (get_team_usage_info(pid, B_TEAM_USAGE_SELF, &info) != B_OK)
626+        return NULL;
627+
628+    py_retlist = Py_BuildValue(
629+        "KK",
630+        (unsigned long long)info.user_time,
631+        (unsigned long long)info.kernel_time
632+    );
633+
634+    return py_retlist;
635+}
636+
637+
638+/*
639+ * Return process name from kinfo_proc as a Python string.
640+ */
641+static PyObject *
642+psutil_proc_name(PyObject *self, PyObject *args) {
643+    pid_t pid;
644+    team_info info;
645+    PyObject *py_name;
646+    PyObject *py_retlist;
647+
648+    if (! PyArg_ParseTuple(args, "l", &pid))
649+        return NULL;
650+    if (get_team_info(pid, &info) != B_OK)
651+        return NULL;
652+
653+    return PyUnicode_DecodeFSDefault(info.args);
654+}
655+
656+
657+/*
658+ * Return process current working directory.
659+ * Raises NSP in case of zombie process.
660+ */
661+static PyObject *
662+psutil_proc_cwd(PyObject *self, PyObject *args) {
663+    /* TODO */
664+    return NULL;
665+}
666+
667+
668+/*
669+ * Return path of the process executable.
670+ */
671+static PyObject *
672+psutil_proc_exe(PyObject *self, PyObject *args) {
673+    pid_t pid;
674+    image_info info;
675+    int32 cookie = 0;
676+    PyObject *py_name;
677+    PyObject *py_retlist;
678+
679+    if (! PyArg_ParseTuple(args, "l", &pid))
680+        return NULL;
681+    while (get_next_image_info(pid, &cookie, &info) == B_OK) {
682+        if (info.type != B_APP_IMAGE)
683+            continue;
684+        return PyUnicode_DecodeFSDefault(info.name);
685+    }
686+    return NULL;
687+}
688+
689+
690+/*
691+ * Return process cmdline as a Python list of cmdline arguments.
692+ */
693+static PyObject *
694+psutil_proc_cmdline(PyObject *self, PyObject *args) {
695+    return Py_BuildValue("[]");
696+    /* TODO! */
697+    pid_t pid;
698+    team_info info;
699+    PyObject *py_arg;
700+    PyObject *py_retlist = NULL;
701+
702+    if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
703+        return NULL;
704+    if (get_team_info(pid, &info) != B_OK)
705+        return NULL;
706+
707+    py_retlist = PyList_New(0);
708+    if (py_retlist == NULL)
709+        return NULL;
710+
711+    /* TODO: we can't really differentiate args as we have a single string */
712+    /* TODO: try to split? */
713+    py_arg = PyUnicode_DecodeFSDefault(info.args);
714+    if (!py_arg)
715+        goto error;
716+    if (PyList_Append(py_retlist, py_arg))
717+        goto error;
718+
719+    return Py_BuildValue("N", py_retlist);
720+
721+error:
722+    Py_XDECREF(py_arg);
723+    Py_DECREF(py_retlist);
724+    return NULL;
725+}
726+
727+
728+/*
729+ * Return process environment as a Python string.
730+ */
731+static PyObject *
732+psutil_proc_environ(PyObject *self, PyObject *args) {
733+    /* TODO: likely impossible */
734+    return NULL;
735+}
736+
737+
738+/*
739+ * Return the number of logical CPUs in the system.
740+ */
741+static PyObject *
742+psutil_cpu_count_logical(PyObject *self, PyObject *args) {
743+    /* TODO:get_cpu_topology_info */
744+    return NULL;
745+}
746+
747+
748+/*
749+ * Return the number of physical CPUs in the system.
750+ */
751+static PyObject *
752+psutil_cpu_count_phys(PyObject *self, PyObject *args) {
753+    /* TODO:get_cpu_topology_info */
754+    return NULL;
755+}
756+
757+
758+/*
759+ * Returns the USS (unique set size) of the process. Reference:
760+ * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/
761+ *     nsMemoryReporterManager.cpp
762+ */
763+static PyObject *
764+psutil_proc_memory_uss(PyObject *self, PyObject *args) {
765+    /* TODO */
766+    return NULL;
767+}
768+
769+
770+/*
771+ * Return system virtual memory stats.
772+ * See:
773+ * https://opensource.apple.com/source/system_cmds/system_cmds-790/
774+ *     vm_stat.tproj/vm_stat.c.auto.html
775+ */
776+static PyObject *
777+psutil_virtual_mem(PyObject *self, PyObject *args) {
778+    system_info info;
779+    status_t ret;
780+    int pagesize = getpagesize();
781+
782+    
783+    ret = get_system_info(&info);
784+    if (ret != B_OK) {
785+        PyErr_Format(
786+            PyExc_RuntimeError, "get_system_info() syscall failed: %s",
787+            strerror(ret));
788+        return NULL;
789+    }
790+
791+    return Py_BuildValue(
792+        "KKKKKKK",
793+        (unsigned long long) info.max_pages * pagesize,  // total
794+        (unsigned long long) info.used_pages * pagesize,  // used
795+        (unsigned long long) info.cached_pages * pagesize,  // cached
796+        (unsigned long long) info.block_cache_pages * pagesize,  // buffers
797+        (unsigned long long) info.ignored_pages * pagesize,  // ignored
798+        (unsigned long long) info.needed_memory,  // needed
799+        (unsigned long long) info.free_memory  // available
800+    );
801+}
802+
803+
804+/*
805+ * Return stats about swap memory.
806+ */
807+static PyObject *
808+psutil_swap_mem(PyObject *self, PyObject *args) {
809+    system_info info;
810+    status_t ret;
811+    int pagesize = getpagesize();
812+
813+    
814+    ret = get_system_info(&info);
815+    if (ret != B_OK) {
816+        PyErr_Format(
817+            PyExc_RuntimeError, "get_system_info() syscall failed: %s",
818+            strerror(ret));
819+        return NULL;
820+    }
821+
822+    return Py_BuildValue(
823+        "KK",
824+        (unsigned long long) info.max_swap_pages * pagesize,
825+        (unsigned long long) info.free_swap_pages * pagesize);
826+        /* XXX: .page_faults? */
827+}
828+
829+
830+/*
831+ * Return a Python tuple representing user, kernel and idle CPU times
832+ */
833+static PyObject *
834+psutil_cpu_times(PyObject *self, PyObject *args) {
835+    system_info info;
836+    status_t ret;
837+    int pagesize = getpagesize();
838+
839+    ret = get_system_info(&info);
840+    if (ret != B_OK) {
841+        PyErr_Format(
842+            PyExc_RuntimeError, "get_system_info() syscall failed: %s",
843+            strerror(ret));
844+        return NULL;
845+    }
846+
847+    cpu_info cpus[info.cpu_count];
848+
849+    ret = get_cpu_info(0, info.cpu_count, cpus);
850+    if (ret != B_OK) {
851+        PyErr_Format(
852+            PyExc_RuntimeError, "get_cpu_info() syscall failed: %s",
853+            strerror(ret));
854+        return NULL;
855+    }
856+
857+    /* TODO */
858+    return Py_BuildValue(
859+        "(dddd)",
860+        (double)0.0,
861+        (double)0.0,
862+        (double)0.0,
863+        (double)0.0
864+    );
865+}
866+
867+
868+/*
869+ * Return a Python list of tuple representing per-cpu times
870+ */
871+static PyObject *
872+psutil_per_cpu_times(PyObject *self, PyObject *args) {
873+    system_info info;
874+    status_t ret;
875+    uint32 i, pagesize = getpagesize();
876+    PyObject *py_retlist = PyList_New(0);
877+    PyObject *py_cputime = NULL;
878+
879+    ret = get_system_info(&info);
880+    if (ret != B_OK) {
881+        PyErr_Format(
882+            PyExc_RuntimeError, "get_system_info() syscall failed: %s",
883+            strerror(ret));
884+        return NULL;
885+    }
886+
887+    cpu_info cpus[info.cpu_count];
888+
889+    ret = get_cpu_info(0, info.cpu_count, cpus);
890+    if (ret != B_OK) {
891+        PyErr_Format(
892+            PyExc_RuntimeError, "get_cpu_info() syscall failed: %s",
893+            strerror(ret));
894+        return NULL;
895+    }
896+
897+    /* TODO: check idle thread times? */
898+
899+    for (i = 0; i < info.cpu_count; i++) {
900+        py_cputime = Py_BuildValue(
901+            "(dddd)",
902+            (double)0.0,
903+            (double)0.0,
904+            (double)0.0,
905+            (double)0.0
906+        );
907+        if (!py_cputime)
908+            goto error;
909+        if (PyList_Append(py_retlist, py_cputime))
910+            goto error;
911+        Py_CLEAR(py_cputime);
912+    }
913+    return py_retlist;
914+
915+error:
916+    Py_XDECREF(py_cputime);
917+    Py_DECREF(py_retlist);
918+    return NULL;
919+}
920+
921+
922+/*
923+ * Retrieve CPU frequency.
924+ */
925+static PyObject *
926+psutil_cpu_freq(PyObject *self, PyObject *args) {
927+    status_t ret;
928+	uint32 i, topologyNodeCount = 0;
929+	ret = get_cpu_topology_info(NULL, &topologyNodeCount);
930+	if (ret != B_OK || topologyNodeCount == 0)
931+	    return NULL;
932+    cpu_topology_node_info topology[topologyNodeCount];
933+	ret = get_cpu_topology_info(topology, &topologyNodeCount);
934+
935+	for (i = 0; i < topologyNodeCount; i++) {
936+	    if (topology[i].type == B_TOPOLOGY_CORE)
937+            /* TODO: find min / max? */
938+            return Py_BuildValue(
939+                "KKK",
940+                topology[i].data.core.default_frequency,
941+                topology[i].data.core.default_frequency,
942+                topology[i].data.core.default_frequency);
943+	}
944+
945+    return NULL;
946+}
947+
948+
949+/*
950+ * Return a Python float indicating the system boot time expressed in
951+ * seconds since the epoch.
952+ */
953+static PyObject *
954+psutil_boot_time(PyObject *self, PyObject *args) {
955+    system_info info;
956+    status_t ret;
957+    int pagesize = getpagesize();
958+
959+    ret = get_system_info(&info);
960+    if (ret != B_OK) {
961+        PyErr_Format(
962+            PyExc_RuntimeError, "get_system_info() syscall failed: %s",
963+            strerror(ret));
964+        return NULL;
965+    }
966+
967+    return Py_BuildValue("f", (float)info.boot_time / 1000000.0);
968+}
969+
970+
971+/*
972+ * Return a list of tuples including device, mount point and fs type
973+ * for all partitions mounted on the system.
974+ */
975+static PyObject *
976+psutil_disk_partitions(PyObject *self, PyObject *args) {
977+    int32 cookie = 0;
978+    dev_t dev;
979+    fs_info info;
980+    uint32 flags;
981+    BString opts;
982+    PyObject *py_dev = NULL;
983+    PyObject *py_mountp = NULL;
984+    PyObject *py_tuple = NULL;
985+    PyObject *py_retlist = PyList_New(0);
986+
987+    if (py_retlist == NULL)
988+        return NULL;
989+
990+    while ((dev = next_dev(&cookie)) >= 0) {
991+        opts = "";
992+        if (fs_stat_dev(dev, &info) != B_OK)
993+            continue;
994+        flags = info.flags;
995+
996+        // see fs_info.h
997+        if (flags & B_FS_IS_READONLY)
998+            opts << "ro";
999+        else
1000+            opts << "rw";
1001+        // TODO
1002+        if (flags & B_FS_IS_REMOVABLE)
1003+            opts << ",removable";
1004+        if (flags & B_FS_IS_PERSISTENT)
1005+            opts << ",persistent";
1006+        if (flags & B_FS_IS_SHARED)
1007+            opts << ",shared";
1008+        if (flags & B_FS_HAS_MIME)
1009+            opts << ",has_mime";
1010+        if (flags & B_FS_HAS_ATTR)
1011+            opts << ",has_attr";
1012+        if (flags & B_FS_HAS_QUERY)
1013+            opts << ",has_query";
1014+        if (flags & B_FS_HAS_SELF_HEALING_LINKS)
1015+            opts << ",has_self_healing_links";
1016+        if (flags & B_FS_HAS_ALIASES)
1017+            opts << ",has_aliases";
1018+        if (flags & B_FS_SUPPORTS_NODE_MONITORING)
1019+            opts << ",has_node_monitoring";
1020+        if (flags & B_FS_SUPPORTS_MONITOR_CHILDREN)
1021+            opts << ",cmdflags";
1022+
1023+        py_dev = PyUnicode_DecodeFSDefault(info.device_name);
1024+        if (! py_dev)
1025+            goto error;
1026+        py_mountp = PyUnicode_DecodeFSDefault(info.volume_name);
1027+        if (! py_mountp)
1028+            goto error;
1029+        py_tuple = Py_BuildValue(
1030+            "(OOss)",
1031+            py_dev,               // device
1032+            py_mountp,            // mount point
1033+            info.fsh_name,        // fs type
1034+            opts.String());       // options
1035+        if (!py_tuple)
1036+            goto error;
1037+        if (PyList_Append(py_retlist, py_tuple))
1038+            goto error;
1039+        Py_CLEAR(py_dev);
1040+        Py_CLEAR(py_mountp);
1041+        Py_CLEAR(py_tuple);
1042+    }
1043+
1044+    return py_retlist;
1045+
1046+error:
1047+    Py_XDECREF(py_dev);
1048+    Py_XDECREF(py_mountp);
1049+    Py_XDECREF(py_tuple);
1050+    Py_DECREF(py_retlist);
1051+    return NULL;
1052+}
1053+
1054+
1055+/*
1056+ * Return process threads
1057+ */
1058+static PyObject *
1059+psutil_proc_threads(PyObject *self, PyObject *args) {
1060+    pid_t pid;
1061+    int32 cookie = 0;
1062+    thread_info info;
1063+    int err, ret;
1064+    PyObject *py_tuple = NULL;
1065+    PyObject *py_retlist = PyList_New(0);
1066+
1067+    if (py_retlist == NULL)
1068+        return NULL;
1069+
1070+    if (! PyArg_ParseTuple(args, "l", &pid))
1071+        goto error;
1072+
1073+    while (get_next_thread_info(pid, &cookie, &info) == B_OK) {
1074+        py_tuple = Py_BuildValue(
1075+            "Iffl",
1076+            info.thread,
1077+            (float)info.user_time / 1000000.0,
1078+            (float)info.kernel_time / 1000000.0,
1079+            (long)info.state
1080+            //XXX: priority, ?
1081+        );
1082+        if (!py_tuple)
1083+            goto error;
1084+        if (PyList_Append(py_retlist, py_tuple))
1085+            goto error;
1086+        Py_CLEAR(py_tuple);
1087+    }
1088+
1089+    return py_retlist;
1090+
1091+error:
1092+    Py_XDECREF(py_tuple);
1093+    Py_DECREF(py_retlist);
1094+    return NULL;
1095+}
1096+
1097+
1098+/*
1099+ * Return process open files as a Python tuple.
1100+ * References:
1101+ * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/m78fd
1102+ * - /usr/include/sys/proc_info.h
1103+ */
1104+static PyObject *
1105+psutil_proc_open_files(PyObject *self, PyObject *args) {
1106+    /* TODO */
1107+    return NULL;
1108+}
1109+
1110+
1111+/*
1112+ * Return process TCP and UDP connections as a list of tuples.
1113+ * Raises NSP in case of zombie process.
1114+ * References:
1115+ * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0
1116+ * - /usr/include/sys/proc_info.h
1117+ */
1118+static PyObject *
1119+psutil_proc_connections(PyObject *self, PyObject *args) {
1120+    /* TODO */
1121+    return NULL;
1122+}
1123+
1124+
1125+/*
1126+ * Return number of file descriptors opened by process.
1127+ * Raises NSP in case of zombie process.
1128+ */
1129+static PyObject *
1130+psutil_proc_num_fds(PyObject *self, PyObject *args) {
1131+    /* TODO */
1132+    return NULL;
1133+}
1134+
1135+
1136+/*
1137+ * Return a Python list of named tuples with overall network I/O information
1138+ */
1139+static PyObject *
1140+psutil_net_io_counters(PyObject *self, PyObject *args) {
1141+    /* TODO */
1142+    return NULL;
1143+}
1144+
1145+
1146+/*
1147+ * Return a Python dict of tuples for disk I/O information
1148+ */
1149+static PyObject *
1150+psutil_disk_io_counters(PyObject *self, PyObject *args) {
1151+    /* TODO */
1152+    return NULL;
1153+}
1154+
1155+
1156+/*
1157+ * Return currently connected users as a list of tuples.
1158+ */
1159+static PyObject *
1160+psutil_users(PyObject *self, PyObject *args) {
1161+    /* TODO */
1162+    return NULL;
1163+}
1164+
1165+
1166+/*
1167+ * Return CPU statistics.
1168+ */
1169+static PyObject *
1170+psutil_cpu_stats(PyObject *self, PyObject *args) {
1171+    /* TODO */
1172+    return NULL;
1173+}
1174+
1175+
1176+/*
1177+ * Return battery information.
1178+ */
1179+static PyObject *
1180+psutil_sensors_battery(PyObject *self, PyObject *args) {
1181+    /* TODO */
1182+    return NULL;
1183+}
1184+
1185+
1186+/*
1187+ * define the psutil C module methods and initialize the module.
1188+ */
1189+static PyMethodDef mod_methods[] = {
1190+    // --- per-process functions
1191+
1192+    {"proc_team_info_oneshot", psutil_proc_team_info_oneshot, METH_VARARGS,
1193+     "Return multiple process info."},
1194+    {"proc_team_usage_info_oneshot", psutil_proc_team_usage_info_oneshot, METH_VARARGS,
1195+     "Return multiple process info."},
1196+    {"proc_name", psutil_proc_name, METH_VARARGS,
1197+     "Return process name"},
1198+    {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS,
1199+     "Return process cmdline as a list of cmdline arguments"},
1200+    {"proc_environ", psutil_proc_environ, METH_VARARGS,
1201+     "Return process environment data"},
1202+    {"proc_exe", psutil_proc_exe, METH_VARARGS,
1203+     "Return path of the process executable"},
1204+    {"proc_cwd", psutil_proc_cwd, METH_VARARGS,
1205+     "Return process current working directory."},
1206+    {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS,
1207+     "Return process USS memory"},
1208+    {"proc_threads", psutil_proc_threads, METH_VARARGS,
1209+     "Return process threads as a list of tuples"},
1210+    {"proc_open_files", psutil_proc_open_files, METH_VARARGS,
1211+     "Return files opened by process as a list of tuples"},
1212+    {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS,
1213+     "Return the number of fds opened by process."},
1214+    {"proc_connections", psutil_proc_connections, METH_VARARGS,
1215+     "Get process TCP and UDP connections as a list of tuples"},
1216+
1217+    // --- system-related functions
1218+
1219+    {"pids", psutil_pids, METH_VARARGS,
1220+     "Returns a list of PIDs currently running on the system"},
1221+    {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS,
1222+     "Return number of logical CPUs on the system"},
1223+    {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
1224+     "Return number of physical CPUs on the system"},
1225+    {"virtual_mem", psutil_virtual_mem, METH_VARARGS,
1226+     "Return system virtual memory stats"},
1227+    {"swap_mem", psutil_swap_mem, METH_VARARGS,
1228+     "Return stats about swap memory, in bytes"},
1229+    {"cpu_times", psutil_cpu_times, METH_VARARGS,
1230+     "Return system cpu times as a tuple (user, system, nice, idle, irc)"},
1231+    {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS,
1232+     "Return system per-cpu times as a list of tuples"},
1233+    {"cpu_freq", psutil_cpu_freq, METH_VARARGS,
1234+     "Return cpu current frequency"},
1235+    {"boot_time", psutil_boot_time, METH_VARARGS,
1236+     "Return the system boot time expressed in seconds since the epoch."},
1237+    {"disk_partitions", psutil_disk_partitions, METH_VARARGS,
1238+     "Return a list of tuples including device, mount point and "
1239+     "fs type for all partitions mounted on the system."},
1240+    {"net_io_counters", psutil_net_io_counters, METH_VARARGS,
1241+     "Return dict of tuples of networks I/O information."},
1242+    {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS,
1243+     "Return dict of tuples of disks I/O information."},
1244+    {"users", psutil_users, METH_VARARGS,
1245+     "Return currently connected users as a list of tuples"},
1246+    {"cpu_stats", psutil_cpu_stats, METH_VARARGS,
1247+     "Return CPU statistics"},
1248+    {"sensors_battery", psutil_sensors_battery, METH_VARARGS,
1249+     "Return battery information."},
1250+
1251+    // --- others
1252+    {"check_pid_range", psutil_check_pid_range, METH_VARARGS},
1253+    {"set_debug", psutil_set_debug, METH_VARARGS},
1254+    {NULL, NULL, 0, NULL}
1255+};
1256+
1257+
1258+#if PY_MAJOR_VERSION >= 3
1259+    #define INITERR return NULL
1260+
1261+    static struct PyModuleDef moduledef = {
1262+        PyModuleDef_HEAD_INIT,
1263+        "_psutil_haiku",
1264+        NULL,
1265+        -1,
1266+        mod_methods,
1267+        NULL,
1268+        NULL,
1269+        NULL,
1270+        NULL
1271+    };
1272+
1273+extern "C" PyObject *PyInit__psutil_haiku(void);
1274+
1275+    PyObject *PyInit__psutil_haiku(void)
1276+#else  /* PY_MAJOR_VERSION */
1277+    #define INITERR return
1278+
1279+extern "C" void init_psutil_haiku(void);
1280+
1281+    void init_psutil_haiku(void)
1282+#endif  /* PY_MAJOR_VERSION */
1283+{
1284+#if PY_MAJOR_VERSION >= 3
1285+    PyObject *mod = PyModule_Create(&moduledef);
1286+#else
1287+    PyObject *mod = Py_InitModule("_psutil_haiku", mod_methods);
1288+#endif
1289+    if (mod == NULL)
1290+        INITERR;
1291+
1292+    if (psutil_setup() != 0)
1293+        INITERR;
1294+
1295+    if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION))
1296+        INITERR;
1297+    // process status constants, defined in:
1298+    // headers/os/kernel/OS.h
1299+    if (PyModule_AddIntConstant(mod, "B_THREAD_RUNNING", B_THREAD_RUNNING))
1300+        INITERR;
1301+    if (PyModule_AddIntConstant(mod, "B_THREAD_READY", B_THREAD_READY))
1302+        INITERR;
1303+    if (PyModule_AddIntConstant(mod, "B_THREAD_RECEIVING", B_THREAD_RECEIVING))
1304+        INITERR;
1305+    if (PyModule_AddIntConstant(mod, "B_THREAD_ASLEEP", B_THREAD_ASLEEP))
1306+        INITERR;
1307+    if (PyModule_AddIntConstant(mod, "B_THREAD_SUSPENDED", B_THREAD_SUSPENDED))
1308+        INITERR;
1309+    if (PyModule_AddIntConstant(mod, "B_THREAD_WAITING", B_THREAD_WAITING))
1310+        INITERR;
1311+    // connection status constants
1312+/*XXX:
1313+    if (PyModule_AddIntConstant(mod, "TCPS_CLOSED", TCPS_CLOSED))
1314+        INITERR;
1315+    if (PyModule_AddIntConstant(mod, "TCPS_CLOSING", TCPS_CLOSING))
1316+        INITERR;
1317+    if (PyModule_AddIntConstant(mod, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT))
1318+        INITERR;
1319+    if (PyModule_AddIntConstant(mod, "TCPS_LISTEN", TCPS_LISTEN))
1320+        INITERR;
1321+    if (PyModule_AddIntConstant(mod, "TCPS_ESTABLISHED", TCPS_ESTABLISHED))
1322+        INITERR;
1323+    if (PyModule_AddIntConstant(mod, "TCPS_SYN_SENT", TCPS_SYN_SENT))
1324+        INITERR;
1325+    if (PyModule_AddIntConstant(mod, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED))
1326+        INITERR;
1327+    if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1))
1328+        INITERR;
1329+    if (PyModule_AddIntConstant(mod, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2))
1330+        INITERR;
1331+    if (PyModule_AddIntConstant(mod, "TCPS_LAST_ACK", TCPS_LAST_ACK))
1332+        INITERR;
1333+    if (PyModule_AddIntConstant(mod, "TCPS_TIME_WAIT", TCPS_TIME_WAIT))
1334+        INITERR;
1335+*/
1336+    if (PyModule_AddIntConstant(mod, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE))
1337+        INITERR;
1338+
1339+    if (mod == NULL)
1340+        INITERR;
1341+#if PY_MAJOR_VERSION >= 3
1342+    return mod;
1343+#endif
1344+}
1345diff --git a/psutil/_psutil_posix.c b/psutil/_psutil_posix.c
1346index 24628af..bd40126 100644
1347--- a/psutil/_psutil_posix.c
1348+++ b/psutil/_psutil_posix.c
1349@@ -45,6 +45,11 @@
1350 #if defined(PSUTIL_AIX)
1351     #include <netdb.h>
1352 #endif
1353+#if defined(PSUTIL_HAIKU)
1354+    #include <netdb.h>
1355+    #include <sys/sockio.h>
1356+    #define IFF_RUNNING 0x0001
1357+#endif
1358 #if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
1359     #include <sys/resource.h>
1360 #endif
1361@@ -916,7 +921,8 @@ static PyMethodDef mod_methods[] = {
1362 #if defined(PSUTIL_BSD) || \
1363         defined(PSUTIL_OSX) || \
1364         defined(PSUTIL_SUNOS) || \
1365-        defined(PSUTIL_AIX)
1366+        defined(PSUTIL_AIX) || \
1367+        defined(PSUTIL_HAIKU)
1368     if (PyModule_AddIntConstant(mod, "AF_LINK", AF_LINK)) INITERR;
1369 #endif
1370 
1371diff --git a/setup.py b/setup.py
1372index 7c59f56..32ff198 100755
1373--- a/setup.py
1374+++ b/setup.py
1375@@ -48,6 +48,7 @@ sys.path.insert(0, os.path.join(HERE, "psutil"))
1376 from _common import AIX  # NOQA
1377 from _common import BSD  # NOQA
1378 from _common import FREEBSD  # NOQA
1379+from _common import HAIKU  # NOQA
1380 from _common import LINUX  # NOQA
1381 from _common import MACOS  # NOQA
1382 from _common import NETBSD  # NOQA
1383@@ -382,6 +383,16 @@ elif AIX:
1384         # fmt: on
1385     )
1386 
1387+elif HAIKU:
1388+    macros.append(("PSUTIL_HAIKU", 1))
1389+    macros.append(("_DEFAULT_SOURCE", 1))
1390+    ext = Extension(
1391+        'psutil._psutil_haiku',
1392+        sources=sources + [
1393+            'psutil/_psutil_haiku.cpp'],
1394+        libraries=['be', 'network'],
1395+        define_macros=macros)
1396+
1397 else:
1398     sys.exit('platform %s is not supported' % sys.platform)
1399 
1400-- 
14012.43.2
1402
1403