summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2021-10-14 21:34:57 +0200
committerGitHub <noreply@github.com>2021-10-14 21:34:57 +0200
commit4d03c917561297de1e6017059c60eb879cf8353c (patch)
treeb40a1c7de2e151623058ff18014ac7d57461025a
parent74c4d65957379b17fb82aa59a3ecdbba209a7cd4 (diff)
downloadpsutil-4d03c917561297de1e6017059c60eb879cf8353c.tar.gz
[Linux] convert /dev/root device to real path (fixes #1999) (#2000)
Signed-off-by: Giampaolo Rodola <g.rodola@gmail.com>
-rw-r--r--HISTORY.rst2
-rw-r--r--psutil/_pslinux.py76
-rwxr-xr-xpsutil/tests/test_linux.py64
3 files changed, 142 insertions, 0 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index 3399e186..fb43a196 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -14,6 +14,8 @@ XXXX-XX-XX
- 1992_: error classes (NoSuchProcess, AccessDenied, etc.) now have a better
formatted and separated `__repr__` and `__str__` implementations.
- 1996_: add support for MidnightBSD. (patch by Saeed Rasooli)
+- 1999_: [Linux] disk_partitions(): convert "/dev/root" device (an alias used
+ on some Linux distros) to real root device path.
**Bug fixes**
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index 1917e20d..f6a83301 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -1189,6 +1189,80 @@ def disk_io_counters(perdisk=False):
return retdict
+class RootFsDeviceFinder:
+ """disk_partitions() may return partitions with device == "/dev/root"
+ or "rootfs". This container class uses different strategies to try to
+ obtain the real device path. Resources:
+ https://bootlin.com/blog/find-root-device/
+ https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/
+ """
+ __slots__ = ['major', 'minor']
+
+ def __init__(self):
+ dev = os.stat("/").st_dev
+ self.major = os.major(dev)
+ self.minor = os.minor(dev)
+
+ def ask_proc_partitions(self):
+ with open_text("%s/partitions" % get_procfs_path()) as f:
+ for line in f.readlines()[2:]:
+ fields = line.split()
+ if len(fields) < 4: # just for extra safety
+ continue
+ major = int(fields[0]) if fields[0].isdigit() else None
+ minor = int(fields[1]) if fields[1].isdigit() else None
+ name = fields[3]
+ if major == self.major and minor == self.minor:
+ if name: # just for extra safety
+ return "/dev/%s" % name
+
+ def ask_sys_dev_block(self):
+ path = "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor)
+ with open_text(path) as f:
+ for line in f:
+ if line.startswith("DEVNAME="):
+ name = line.strip().rpartition("DEVNAME=")[2]
+ if name: # just for extra safety
+ return "/dev/%s" % name
+
+ def ask_sys_class_block(self):
+ needle = "%s:%s" % (self.major, self.minor)
+ files = glob.iglob("/sys/class/block/*/dev")
+ for file in files:
+ try:
+ f = open_text(file)
+ except FileNotFoundError: # race condition
+ continue
+ else:
+ with f:
+ data = f.read().strip()
+ if data == needle:
+ name = os.path.basename(os.path.dirname(file))
+ return "/dev/%s" % name
+
+ def find(self):
+ path = None
+ if path is None:
+ try:
+ path = self.ask_proc_partitions()
+ except (IOError, OSError) as err:
+ debug(err)
+ if path is None:
+ try:
+ path = self.ask_sys_dev_block()
+ except (IOError, OSError) as err:
+ debug(err)
+ if path is None:
+ try:
+ path = self.ask_sys_class_block()
+ except (IOError, OSError) as err:
+ debug(err)
+ # We use exists() because the "/dev/*" part of the path is hard
+ # coded, so we want to be sure.
+ if path is not None and os.path.exists(path):
+ return path
+
+
def disk_partitions(all=False):
"""Return mounted disk partitions as a list of namedtuples."""
fstypes = set()
@@ -1216,6 +1290,8 @@ def disk_partitions(all=False):
device, mountpoint, fstype, opts = partition
if device == 'none':
device = ''
+ if device in ("/dev/root", "rootfs"):
+ device = RootFsDeviceFinder().find() or device
if not all:
if device == '' or fstype not in fstypes:
continue
diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py
index d2d8e7d4..38322db6 100755
--- a/psutil/tests/test_linux.py
+++ b/psutil/tests/test_linux.py
@@ -28,6 +28,7 @@ from psutil._compat import FileNotFoundError
from psutil._compat import PY3
from psutil._compat import u
from psutil.tests import call_until
+from psutil.tests import GITHUB_ACTIONS
from psutil.tests import GLOBAL_TIMEOUT
from psutil.tests import HAS_BATTERY
from psutil.tests import HAS_CPU_FREQ
@@ -51,6 +52,7 @@ if LINUX:
from psutil._pslinux import calculate_avail_vmem
from psutil._pslinux import CLOCK_TICKS
from psutil._pslinux import open_binary
+ from psutil._pslinux import RootFsDeviceFinder
HERE = os.path.abspath(os.path.dirname(__file__))
@@ -1263,6 +1265,68 @@ class TestSystemDiskIoCounters(PsutilTestCase):
self.assertRaises(NotImplementedError, psutil.disk_io_counters)
+@unittest.skipIf(not LINUX, "LINUX only")
+class TestRootFsDeviceFinder(PsutilTestCase):
+
+ def setUp(self):
+ dev = os.stat("/").st_dev
+ self.major = os.major(dev)
+ self.minor = os.minor(dev)
+
+ def test_call_methods(self):
+ finder = RootFsDeviceFinder()
+ if os.path.exists("/proc/partitions"):
+ finder.ask_proc_partitions()
+ else:
+ self.assertRaises(FileNotFoundError, finder.ask_proc_partitions)
+ if os.path.exists("/sys/dev/block/%s:%s/uevent" % (
+ self.major, self.minor)):
+ finder.ask_sys_dev_block()
+ else:
+ self.assertRaises(FileNotFoundError, finder.ask_sys_dev_block)
+ finder.ask_sys_class_block()
+
+ @unittest.skipIf(GITHUB_ACTIONS, "unsupported on GITHUB_ACTIONS")
+ def test_comparisons(self):
+ finder = RootFsDeviceFinder()
+ self.assertIsNotNone(finder.find())
+
+ a = b = c = None
+ if os.path.exists("/proc/partitions"):
+ a = finder.ask_proc_partitions()
+ if os.path.exists("/sys/dev/block/%s:%s/uevent" % (
+ self.major, self.minor)):
+ b = finder.ask_sys_class_block()
+ c = finder.ask_sys_dev_block()
+
+ base = a or b or c
+ if base and a:
+ self.assertEqual(base, a)
+ if base and b:
+ self.assertEqual(base, b)
+ if base and c:
+ self.assertEqual(base, c)
+
+ @unittest.skipIf(not which("findmnt"), "findmnt utility not available")
+ @unittest.skipIf(GITHUB_ACTIONS, "unsupported on GITHUB_ACTIONS")
+ def test_against_findmnt(self):
+ psutil_value = RootFsDeviceFinder().find()
+ findmnt_value = sh("findmnt -o SOURCE -rn /")
+ self.assertEqual(psutil_value, findmnt_value)
+
+ def test_disk_partitions_mocked(self):
+ with mock.patch(
+ 'psutil._pslinux.cext.disk_partitions',
+ return_value=[('/dev/root', '/', 'ext4', 'rw')]) as m:
+ part = psutil.disk_partitions()[0]
+ assert m.called
+ if not GITHUB_ACTIONS:
+ self.assertNotEqual(part.device, "/dev/root")
+ self.assertEqual(part.device, RootFsDeviceFinder().find())
+ else:
+ self.assertEqual(part.device, "/dev/root")
+
+
# =====================================================================
# --- misc
# =====================================================================