From 0e3f6c465181734181f2428b4a99429a82f3fc45 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Mon, 17 Apr 2023 01:01:44 +0200 Subject: [PATCH] fix #2238 if cwd() cannot be determined always return "" instead of None --- HISTORY.rst | 8 ++++++-- docs/index.rst | 11 +++++++---- psutil/_psaix.py | 2 +- psutil/_psbsd.py | 2 +- psutil/_pssunos.py | 2 +- psutil/arch/netbsd/proc.c | 9 ++++++--- psutil/arch/openbsd/proc.c | 4 ++-- psutil/tests/test_contracts.py | 16 +++++++++------- psutil/tests/test_posix.py | 7 +------ 9 files changed, 34 insertions(+), 27 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index d946381798..97c2c20e22 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -15,6 +15,10 @@ empty string) - The function is faster since it no longer iterates over all processes. - No longer produces duplicate connection entries. +- 2238_: there are cases where `Process.cwd()`_ cannot be determined + (e.g. directory no longer exists), in which case we returned either ``None`` + or an empty string. This was consolidated and we now return ``""`` on all + platforms. **Bug fixes** @@ -39,8 +43,8 @@ *used* are too high. We now match values shown by *htop* CLI utility. - 2236_, [NetBSD]: `Process.num_threads()`_ and `Process.threads()`_ return threads that are already terminated. -- 2237_, [OpenBSD]: `Process.cwd()`_ may raise ``FileNotFoundError`` if cwd no - longer exists. Return ``None`` instead. +- 2237_, [OpenBSD], [NetBSD]: `Process.cwd()`_ may raise ``FileNotFoundError`` + if cwd no longer exists. Return an empty string instead. 5.9.4 ===== diff --git a/docs/index.rst b/docs/index.rst index 2a1c5b6abc..6f0132d2c8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1180,9 +1180,10 @@ Process class .. method:: exe() - The process executable as an absolute path. - On some systems this may also be an empty string. - The return value is cached after first call. + The process executable as an absolute path. On some systems, if exe cannot + be determined for some internal reason (e.g. system process or path no + longer exists), this may be an empty string. The return value is cached + after first call. >>> import psutil >>> psutil.Process().exe() @@ -1281,7 +1282,9 @@ Process class .. method:: cwd() - The process current working directory as an absolute path. + The process current working directory as an absolute path. If cwd cannot be + determined for some internal reason (e.g. system process or directiory no + longer exists) it may return an empty string. .. versionchanged:: 5.6.4 added support for NetBSD diff --git a/psutil/_psaix.py b/psutil/_psaix.py index 5c41069cff..67f0314f74 100644 --- a/psutil/_psaix.py +++ b/psutil/_psaix.py @@ -487,7 +487,7 @@ def cwd(self): return result.rstrip('/') except FileNotFoundError: os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD - return None + return "" @wrap_exceptions def memory_info(self): diff --git a/psutil/_psbsd.py b/psutil/_psbsd.py index 8fc256b215..b77a9d6888 100644 --- a/psutil/_psbsd.py +++ b/psutil/_psbsd.py @@ -839,7 +839,7 @@ def cwd(self): elif NETBSD or HAS_PROC_OPEN_FILES: # FreeBSD < 8 does not support functions based on # kinfo_getfile() and kinfo_getvmmap() - return cext.proc_cwd(self.pid) or None + return cext.proc_cwd(self.pid) or "" else: raise NotImplementedError( "supported only starting from FreeBSD 8" if diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py index 8663de3cd3..d44bf2d788 100644 --- a/psutil/_pssunos.py +++ b/psutil/_pssunos.py @@ -542,7 +542,7 @@ def cwd(self): return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid)) except FileNotFoundError: os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD - return None + return "" @wrap_exceptions def memory_info(self): diff --git a/psutil/arch/netbsd/proc.c b/psutil/arch/netbsd/proc.c index e71afb388f..2688dceda1 100644 --- a/psutil/arch/netbsd/proc.c +++ b/psutil/arch/netbsd/proc.c @@ -115,10 +115,13 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { ssize_t len = readlink(buf, path, sizeof(path) - 1); free(buf); if (len == -1) { - if (errno == ENOENT) - NoSuchProcess("readlink -> ENOENT"); - else + if (errno == ENOENT) { + psutil_debug("sysctl(KERN_PROC_CWD) -> ENOENT converted to ''"); + return Py_BuildValue("", ""); + } + else { PyErr_SetFromErrno(PyExc_OSError); + } return NULL; } path[len] = '\0'; diff --git a/psutil/arch/openbsd/proc.c b/psutil/arch/openbsd/proc.c index e66cac667b..38538a4a9e 100644 --- a/psutil/arch/openbsd/proc.c +++ b/psutil/arch/openbsd/proc.c @@ -306,8 +306,8 @@ psutil_proc_cwd(PyObject *self, PyObject *args) { int name[] = { CTL_KERN, KERN_PROC_CWD, pid }; if (sysctl(name, 3, path, &pathlen, NULL, 0) != 0) { if (errno == ENOENT) { - psutil_debug("sysctl(KERN_PROC_CWD) -> ENOENT converted to None"); - Py_RETURN_NONE; // mimic os.cpu_count() + psutil_debug("sysctl(KERN_PROC_CWD) -> ENOENT converted to ''"); + return Py_BuildValue("", ""); } else { PyErr_SetFromErrno(PyExc_OSError); diff --git a/psutil/tests/test_contracts.py b/psutil/tests/test_contracts.py index 6859f69fd8..7f299a6243 100755 --- a/psutil/tests/test_contracts.py +++ b/psutil/tests/test_contracts.py @@ -452,10 +452,9 @@ def cmdline(self, ret, info): self.assertIsInstance(part, str) def exe(self, ret, info): - self.assertIsInstance(ret, (str, unicode, type(None))) - if not ret: - self.assertEqual(ret, '') - else: + self.assertIsInstance(ret, (str, unicode)) + self.assertEqual(ret.strip(), ret) + if ret: if WINDOWS and not ret.endswith('.exe'): return # May be "Registry", "MemCompression", ... assert os.path.isabs(ret), ret @@ -520,7 +519,8 @@ def gids(self, ret, info): def username(self, ret, info): self.assertIsInstance(ret, str) - assert ret + self.assertEqual(ret.strip(), ret) + assert ret.strip() def status(self, ret, info): self.assertIsInstance(ret, str) @@ -619,6 +619,7 @@ def open_files(self, ret, info): for f in ret: self.assertIsInstance(f.fd, int) self.assertIsInstance(f.path, str) + self.assertEqual(f.path.strip(), f.path) if WINDOWS: self.assertEqual(f.fd, -1) elif LINUX: @@ -651,8 +652,9 @@ def connections(self, ret, info): check_connection_ntuple(conn) def cwd(self, ret, info): - if ret: # 'ret' can be None or empty - self.assertIsInstance(ret, str) + self.assertIsInstance(ret, (str, unicode)) + self.assertEqual(ret.strip(), ret) + if ret: assert os.path.isabs(ret), ret try: st = os.stat(ret) diff --git a/psutil/tests/test_posix.py b/psutil/tests/test_posix.py index d26312151b..8ea6cf7c23 100755 --- a/psutil/tests/test_posix.py +++ b/psutil/tests/test_posix.py @@ -351,12 +351,7 @@ def test_users(self): self.assertEqual(u.name, users[idx]) self.assertEqual(u.terminal, terminals[idx]) if u.pid is not None: # None on OpenBSD - p = psutil.Process(u.pid) - # on macOS time is off by ~47 secs for some reason, but - # the next test against 'who' CLI succeeds - delta = 60 if MACOS else 1 - self.assertAlmostEqual( - u.started, p.create_time(), delta=delta) + psutil.Process(u.pid) @retry_on_failure() def test_users_started(self):