diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index d53eb0427..0568e3a85 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -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, @@ -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 = { @@ -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']] diff --git a/psutil/_psutil_bsd.c b/psutil/_psutil_bsd.c index 953fcd083..c4450d7df 100644 --- a/psutil/_psutil_bsd.c +++ b/psutil/_psutil_bsd.c @@ -57,6 +57,7 @@ #include #include // process open files/connections #include +#include #include "_psutil_common.h" #include "_psutil_posix.h" @@ -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); + 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 ".) + // 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 @@ -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) @@ -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)); @@ -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 @@ -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 @@ -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; diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index 7a87c3c19..f821aba38 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -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 // ==================================================================== diff --git a/psutil/_psutil_common.h b/psutil/_psutil_common.h index 2408c9f63..3162772eb 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -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 // ==================================================================== diff --git a/psutil/arch/freebsd/specific.c b/psutil/arch/freebsd/specific.c index 3f37a08e2..c78326479 100644 --- a/psutil/arch/freebsd/specific.c +++ b/psutil/arch/freebsd/specific.c @@ -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 PyObject * psutil_proc_cwd(PyObject *self, PyObject *args) { pid_t pid; @@ -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; diff --git a/psutil/arch/freebsd/sys_socks.c b/psutil/arch/freebsd/sys_socks.c index ab61f393b..9f7cf8d55 100644 --- a/psutil/arch/freebsd/sys_socks.c +++ b/psutil/arch/freebsd/sys_socks.c @@ -16,6 +16,9 @@ #include #include #include +#if defined(__FreeBSD_version) && __FreeBSD_version < 800000 +#include +#endif #include // for xinpcb struct #include #include @@ -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) { diff --git a/psutil/arch/openbsd/specific.c b/psutil/arch/openbsd/specific.c index d97a8f9b9..aa4568f59 100644 --- a/psutil/arch/openbsd/specific.c +++ b/psutil/arch/openbsd/specific.c @@ -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. diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index 51bbb9f09..2d9e5917e 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -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) diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index e3394799c..8b5e5c56d 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -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: