giampaolo / psutil

Cross-platform lib for process and system monitoring in Python
BSD 3-Clause "New" or "Revised" License
10.22k stars 1.38k forks source link

[Linux] disk_partitions() returns incorrect block device for bind-mounts #2347

Open matoro opened 9 months ago

matoro commented 9 months ago

Summary

Description

Collected information from https://github.com/giampaolo/psutil/pull/2344

Demonstration of issue:

$ mount test.img testdir
$ mount --rbind /usr/src testdir/usr/src
$ chroot testdir /bin/bash

$ python3
Python 3.11.6 (main, Dec  5 2023, 11:03:00) [GCC 13.2.1 20230826] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import psutil, pprint
>>> pprint.pprint(psutil.disk_partitions(all=False))
[sdiskpart(device='/dev/loop0', mountpoint='/', fstype='ext4', opts='rw,relatime', maxfile=255, maxpath=4096),
 sdiskpart(device='/dev/loop0', mountpoint='/usr/src', fstype='ext4', opts='ro,noatime', maxfile=255, maxpath=4096)]
>>> pprint.pprint([psutil.disk_usage(x.mountpoint) for x in psutil.disk_partitions(all=False)])
[sdiskusage(total=20530814976, used=7411703808, free=12053757952, percent=38.1),
 sdiskusage(total=134679105536, used=42708791296, free=85081747456, percent=33.4)]
>>>

$ df / && df /usr/src
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/loop0      20049624 7237992  11771248  39% /
Filesystem     1K-blocks     Used Available Use% Mounted on
/dev/root      131522564 41707804  83087644  34% /usr/src

psutil uses getmntent(3) for iterating over mountpoints and collecting this information. According to the man page:

These routines are used to access the filesystem description file /etc/fstab and the mounted filesystem description file /etc/mtab.

psutil uses this function to parse the contents of /etc/mtab (on modern systems a symlink to /proc/self/mounts to account for namespaces). However, /etc/mtab has several failings:

/proc/mounts in its current state fails to disambiguate bind mounts, especially when the bind mount is subrooted. Also it does not capture propagation state of the mounts(shared-subtree).

Thus it was superseded by this Linux change which introduced the file /proc/self/mountinfo, which contains a superset of the information from /proc/self/mounts, but uses a different format documented here.

See also this thread where GNOME ran into this same problem with their disks utility. Also see the golang mountinfo module which parses this file. Implementation is here which contains a good test corpus and some speed tricks.