From 741c143ea371ef3ecf83254341f443182a9547f1 Mon Sep 17 00:00:00 2001 From: Nikita Radchenko Date: Tue, 5 Oct 2021 21:13:38 +0300 Subject: Handle ENAMETOOLONG on Linux (#1940) (#1955) When resolving process file descriptors symlinks in procfs (/proc/PID/fd/FD), the kernel can only deal with file paths no longer than PAGE_SIZE (which usually equals to PATH_MAX). https://elixir.bootlin.com/linux/v5.12/source/fs/proc/base.c#L1759 Resolving fd symlink that corresponds to a file with a path longer than PATH_MAX with readlink(2) would result in ENAMETOOLONG error (see details in #1940). We can do nothing to fix this in userspace; therefore these errors should be ignored. --- CREDITS | 6 +++++- HISTORY.rst | 2 ++ psutil/_pslinux.py | 6 ++++++ psutil/tests/test_linux.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CREDITS b/CREDITS index 280c3b1c..cc7e7a59 100644 --- a/CREDITS +++ b/CREDITS @@ -745,6 +745,10 @@ N: David Knaack W: https://github.com/davidkna I: 1921 +N: Nikita Radchenko +W: https://github.com/nradchenko +I: 1940 + N: MaWe2019 W: https://github.com/MaWe2019 I: 1953 @@ -761,4 +765,4 @@ I: 1598 N: Xuehai Pan W: https://github.com/XuehaiPan -I: 1948 +I: 1948 \ No newline at end of file diff --git a/HISTORY.rst b/HISTORY.rst index a8284ad2..ccc5c829 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -34,6 +34,8 @@ XXXX-XX-XX - 1913_: [Linux] wait_procs seemingly ignoring timeout, TimeoutExpired thrown - 1919_: [Linux] sensors_battery() can raise TypeError on PureOS. - 1921_: [Windows] psutil.swap_memory() shows committed memory instead of swap +- 1940_: [Linux] psutil does not handle ENAMETOOLONG when accessing process + file descriptors in procfs - 1948_: Process' memoize_when_activated decorator was not thread-safe. (patch by Xuehai Pan) - 1953_: [Windows] disk_partitions() crashes due to insufficient buffer len. diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 3afe6c65..1917e20d 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -832,6 +832,9 @@ class Connections: if err.errno == errno.EINVAL: # not a link continue + if err.errno == errno.ENAMETOOLONG: + # file name too long + continue raise else: if inode.startswith('socket:['): @@ -2101,6 +2104,9 @@ class Process(object): if err.errno == errno.EINVAL: # not a link continue + if err.errno == errno.ENAMETOOLONG: + # file name too long + continue raise else: # If path is not an absolute there's no way to tell diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 23f6b221..3fda7874 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -1446,6 +1446,25 @@ class TestMisc(PsutilTestCase): assert psutil.pid_exists(os.getpid()) assert m.called + def test_get_proc_inodes_fd_broken(self): + # Simulate a case where /proc/{pid}/fd/{fd} symlink + # points to a file with full path longer than PATH_MAX + + def get_inodes(): + connections = psutil._pslinux.Connections() + connections._procfs_path = psutil._common.get_procfs_path() + return connections.get_proc_inodes(os.getpid()) + + p = psutil.Process() + files = p.open_files() + with open(self.get_testfn(), 'w'): + # give the kernel some time to see the new file + call_until(p.open_files, "len(ret) != %i" % len(files)) + patch_point = 'psutil._pslinux.os.readlink' + with mock.patch(patch_point, + side_effect=OSError(errno.ENAMETOOLONG, "")) as m: + assert not get_inodes() + assert m.called # ===================================================================== # --- sensors @@ -1830,6 +1849,22 @@ class TestProcess(PsutilTestCase): assert not files assert m.called + def test_open_files_fd_broken(self): + # Simulate a case where /proc/{pid}/fd/{fd} symlink + # points to a file with full path longer than PATH_MAX + + p = psutil.Process() + files = p.open_files() + with open(self.get_testfn(), 'w'): + # give the kernel some time to see the new file + call_until(p.open_files, "len(ret) != %i" % len(files)) + patch_point = 'psutil._pslinux.os.readlink' + with mock.patch(patch_point, + side_effect=OSError(errno.ENAMETOOLONG, "")) as m: + files = p.open_files() + assert not files + assert m.called + # --- mocked tests def test_terminal_mocked(self): -- cgit v1.2.1