Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Process.environ() on *BSD family #1800

Merged
merged 6 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions psutil/_psbsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
cext.SWAIT: _common.STATUS_WAITING,
cext.SLOCK: _common.STATUS_LOCKED,
}
elif OPENBSD or NETBSD:
elif OPENBSD:
PROC_STATUSES = {
cext.SIDL: _common.STATUS_IDLE,
cext.SSLEEP: _common.STATUS_SLEEPING,
Expand All @@ -76,12 +76,11 @@
elif NETBSD:
PROC_STATUSES = {
cext.SIDL: _common.STATUS_IDLE,
cext.SACTIVE: _common.STATUS_RUNNING,
cext.SDYING: _common.STATUS_ZOMBIE,
cext.SSLEEP: _common.STATUS_SLEEPING,
cext.SSTOP: _common.STATUS_STOPPED,
cext.SZOMB: _common.STATUS_ZOMBIE,
cext.SDEAD: _common.STATUS_DEAD,
cext.SSUSPENDED: _common.STATUS_SUSPENDED, # unique to NetBSD
cext.SRUN: _common.STATUS_WAKING,
cext.SONPROC: _common.STATUS_RUNNING,
}

TCP_STATUSES = {
Expand Down Expand Up @@ -669,6 +668,10 @@ def cmdline(self):
else:
return cext.proc_cmdline(self.pid)

@wrap_exceptions
def environ(self):
return cext.proc_environ(self.pid)

@wrap_exceptions
def terminal(self):
tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
Expand Down
150 changes: 149 additions & 1 deletion psutil/_psutil_bsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include <net/route.h>
#include <netinet/in.h> // process open files/connections
#include <sys/un.h>
#include <kvm.h>

#include "_psutil_common.h"
#include "_psutil_posix.h"
Expand Down Expand Up @@ -391,6 +392,145 @@ psutil_proc_cmdline(PyObject *self, PyObject *args) {
}


/*
* Return process environment as a Python dictionary
*/
PyObject *
psutil_proc_environ(PyObject *self, PyObject *args) {
int i, cnt = -1;
long pid;
char *s, **envs, errbuf[_POSIX2_LINE_MAX];
PyObject *py_value=NULL, *py_retdict=NULL;
kvm_t *kd;
#ifdef PSUTIL_NETBSD
struct kinfo_proc2 *p;
#else
struct kinfo_proc *p;
#endif

if (!PyArg_ParseTuple(args, "l", &pid))
return NULL;

#if defined(PSUTIL_FREEBSD)
kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, errbuf);
#else
kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
#endif
if (!kd) {
convert_kvm_err("kvm_openfiles", errbuf);
return NULL;
}

py_retdict = PyDict_New();
if (!py_retdict)
goto error;

#if defined(PSUTIL_FREEBSD)
p = kvm_getprocs(kd, KERN_PROC_PID, pid, &cnt);
#elif defined(PSUTIL_OPENBSD)
p = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(*p), &cnt);
#elif defined(PSUTIL_NETBSD)
p = kvm_getproc2(kd, KERN_PROC_PID, pid, sizeof(*p), &cnt);
#endif
if (!p) {
NoSuchProcess("kvm_getprocs");
goto error;
}
if (cnt <= 0) {
NoSuchProcess(cnt < 0 ? kvm_geterr(kd) : "kvm_getprocs: cnt==0");
goto error;
}

// On *BSD kernels there are a few kernel-only system processes without an
// environment (See e.g. "procstat -e 0 | 1 | 2 ..." on FreeBSD.)
//
// Some system process have no stats attached at all
// (they are marked with P_SYSTEM.)
//
// On FreeBSD, it's possible that the process is swapped or paged out,
// then there no access to the environ stored in the process' user area.
//
// On NetBSD, we cannot call kvm_getenvv2() for a zombie process.
//
// To make unittest suite happy, return an empty environment.
//
#if defined(PSUTIL_FREEBSD)
#if (defined(__FreeBSD_version) && __FreeBSD_version >= 700000)
if (!((p)->ki_flag & P_INMEM) || ((p)->ki_flag & P_SYSTEM)) {
#else
if ((p)->ki_flag & P_SYSTEM) {
#endif
#elif defined(PSUTIL_NETBSD)
if ((p)->p_stat == SZOMB) {
#elif defined(PSUTIL_OPENBSD)
if ((p)->p_flag & P_SYSTEM) {
#endif
kvm_close(kd);
giampaolo marked this conversation as resolved.
Show resolved Hide resolved
return py_retdict;
}

#if defined(PSUTIL_NETBSD)
envs = kvm_getenvv2(kd, p, 0);
#else
envs = kvm_getenvv(kd, p, 0);
#endif
if (!envs) {
// Map to "psutil" general high-level exceptions
switch (errno) {
case 0:
// Process has cleared it's environment, return empty one
kvm_close(kd);
return py_retdict;
case EPERM:
AccessDenied("kvm_getenvv");
break;
case ESRCH:
NoSuchProcess("kvm_getenvv");
break;
#if defined(PSUTIL_FREEBSD)
case ENOMEM:
// Unfortunately, under FreeBSD kvm_getenvv() returns
// failure for certain processes ( e.g. try
// "sudo procstat -e <pid of your XOrg server>".)
// Map the error condition to 'AccessDenied'.
sprintf(errbuf,
"kvm_getenvv(pid=%ld, ki_uid=%d): errno=ENOMEM",
pid, p->ki_uid);
AccessDenied(errbuf);
break;
#endif
default:
sprintf(errbuf, "kvm_getenvv(pid=%ld)", pid);
PyErr_SetFromOSErrnoWithSyscall(errbuf);
break;
}
goto error;
}

for (i = 0; envs[i] != NULL; i++) {
s = strchr(envs[i], '=');
if (!s)
continue;
*s++ = 0;
py_value = PyUnicode_DecodeFSDefault(s);
if (!py_value)
goto error;
if (PyDict_SetItemString(py_retdict, envs[i], py_value)) {
goto error;
}
Py_DECREF(py_value);
}

kvm_close(kd);
return py_retdict;

error:
Py_XDECREF(py_value);
Py_XDECREF(py_retdict);
kvm_close(kd);
return NULL;
}

/*
* Return the number of logical CPUs in the system.
* XXX this could be shared with macOS
Expand Down Expand Up @@ -617,8 +757,10 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
strlcat(opts, ",softdep", sizeof(opts));
if (flags & MNT_NOSYMFOLLOW)
strlcat(opts, ",nosymfollow", sizeof(opts));
#ifdef MNT_GJOURNAL
if (flags & MNT_GJOURNAL)
strlcat(opts, ",gjournal", sizeof(opts));
#endif
if (flags & MNT_MULTILABEL)
strlcat(opts, ",multilabel", sizeof(opts));
if (flags & MNT_ACLS)
Expand All @@ -627,8 +769,10 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
strlcat(opts, ",noclusterr", sizeof(opts));
if (flags & MNT_NOCLUSTERW)
strlcat(opts, ",noclusterw", sizeof(opts));
#ifdef MNT_NFS4ACLS
if (flags & MNT_NFS4ACLS)
strlcat(opts, ",nfs4acls", sizeof(opts));
#endif
#elif PSUTIL_NETBSD
if (flags & MNT_NODEV)
strlcat(opts, ",nodev", sizeof(opts));
Expand Down Expand Up @@ -831,7 +975,7 @@ psutil_users(PyObject *self, PyObject *args) {
py_tty, // tty
py_hostname, // hostname
(float)ut.ut_time, // start time
#ifdef PSUTIL_OPENBSD
#if defined(PSUTIL_OPENBSD) || (defined(__FreeBSD_version) && __FreeBSD_version < 900000)
-1 // process id (set to None later)
#else
ut.ut_pid // TODO: use PyLong_FromPid
Expand Down Expand Up @@ -956,6 +1100,8 @@ static PyMethodDef mod_methods[] = {
{"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS,
"Return an XML string to determine the number physical CPUs."},
#endif
{"proc_environ", psutil_proc_environ, METH_VARARGS,
"Return process environment"},

// --- system-related functions

Expand Down Expand Up @@ -1060,7 +1206,9 @@ static PyMethodDef mod_methods[] = {
if (PyModule_AddIntConstant(mod, "SSLEEP", LSSLEEP)) INITERR;
if (PyModule_AddIntConstant(mod, "SSTOP", LSSTOP)) INITERR;
if (PyModule_AddIntConstant(mod, "SZOMB", LSZOMB)) INITERR;
#if __NetBSD_Version__ < 500000000
if (PyModule_AddIntConstant(mod, "SDEAD", LSDEAD)) INITERR;
#endif
if (PyModule_AddIntConstant(mod, "SONPROC", LSONPROC)) INITERR;
// unique to NetBSD
if (PyModule_AddIntConstant(mod, "SSUSPENDED", LSSUSPENDED)) INITERR;
Expand Down
20 changes: 20 additions & 0 deletions psutil/_psutil_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,26 @@ psutil_setup(void) {
}


// ============================================================================
// Utility functions (BSD)
// ============================================================================

#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_OPENBSD) || defined(PSUTIL_NETBSD)
void
convert_kvm_err(const char *syscall, char *errbuf) {
char fullmsg[8192];

sprintf(fullmsg, "(originated from %s: %s)", syscall, errbuf);
if (strstr(errbuf, "Permission denied") != NULL)
AccessDenied(fullmsg);
else if (strstr(errbuf, "Operation not permitted") != NULL)
AccessDenied(fullmsg);
else
PyErr_Format(PyExc_RuntimeError, fullmsg);
}
#endif


// ====================================================================
// --- Windows
// ====================================================================
Expand Down
6 changes: 6 additions & 0 deletions psutil/_psutil_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ PyObject* psutil_set_testing(PyObject *self, PyObject *args);
void psutil_debug(const char* format, ...);
int psutil_setup(void);

// ====================================================================
// --- BSD
// ====================================================================

void convert_kvm_err(const char *syscall, char *errbuf);

// ====================================================================
// --- Windows
// ====================================================================
Expand Down
4 changes: 3 additions & 1 deletion psutil/arch/freebsd/specific.c
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ psutil_swap_mem(PyObject *self, PyObject *args) {
}


#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000
#if defined(__FreeBSD_version) && __FreeBSD_version >= 701000
giampaolo marked this conversation as resolved.
Show resolved Hide resolved
PyObject *
psutil_proc_cwd(PyObject *self, PyObject *args) {
pid_t pid;
Expand Down Expand Up @@ -795,9 +795,11 @@ psutil_proc_memory_maps(PyObject *self, PyObject *args) {
case KVME_TYPE_DEAD:
path = "[dead]";
break;
#ifdef KVME_TYPE_SG
case KVME_TYPE_SG:
path = "[sg]";
break;
#endif
case KVME_TYPE_UNKNOWN:
path = "[unknown]";
break;
Expand Down
5 changes: 4 additions & 1 deletion psutil/arch/freebsd/sys_socks.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include <sys/un.h>
#include <sys/unpcb.h>
#include <sys/sysctl.h>
#if defined(__FreeBSD_version) && __FreeBSD_version < 800000
#include <netinet/in_systm.h>
#endif
#include <netinet/in.h> // for xinpcb struct
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
Expand All @@ -30,7 +33,7 @@ static int psutil_nxfiles;


int
psutil_populate_xfiles() {
psutil_populate_xfiles(void) {
size_t len;

if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) {
Expand Down
14 changes: 0 additions & 14 deletions psutil/arch/openbsd/specific.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,6 @@
// ============================================================================


static void
convert_kvm_err(const char *syscall, char *errbuf) {
char fullmsg[8192];

sprintf(fullmsg, "(originated from %s: %s)", syscall, errbuf);
if (strstr(errbuf, "Permission denied") != NULL)
AccessDenied(fullmsg);
else if (strstr(errbuf, "Operation not permitted") != NULL)
AccessDenied(fullmsg);
else
PyErr_Format(PyExc_RuntimeError, fullmsg);
}


int
psutil_kinfo_proc(pid_t pid, struct kinfo_proc *proc) {
// Fills a kinfo_proc struct based on process pid.
Expand Down
3 changes: 2 additions & 1 deletion psutil/tests/test_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ class TestAvailProcessAPIs(PsutilTestCase):

def test_environ(self):
self.assertEqual(hasattr(psutil.Process, "environ"),
LINUX or MACOS or WINDOWS or AIX or SUNOS)
LINUX or MACOS or WINDOWS or AIX or SUNOS or
FREEBSD or OPENBSD or NETBSD)

def test_uids(self):
self.assertEqual(hasattr(psutil.Process, "uids"), POSIX)
Expand Down
2 changes: 1 addition & 1 deletion psutil/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def test_create_time(self):
@unittest.skipIf(TRAVIS or CIRRUS, 'not reliable on TRAVIS/CIRRUS')
def test_terminal(self):
terminal = psutil.Process().terminal()
if sys.stdout.isatty():
if sys.stdin.isatty():
tty = os.path.realpath(sh('tty'))
self.assertEqual(terminal, tty)
else:
Expand Down