#
# Module `filesystem`: implements `FileSystem` class.
# Peter Sulyok (C) 2022-2026.
#
import os
from typing import Tuple
from pyudev import Device
from diskinfo.utils import size_in_hrf, _pyudev_getenc
# Path to the kernel mount table.
_PROC_MOUNTS = "/proc/mounts"
[docs]
class FileSystem:
"""This class encapsulates filesystem-related information read from a block device's udev properties.
It is used by both :class:`~diskinfo.Partition` and :class:`~diskinfo.Disk` to represent filesystem data.
.. note::
1. The mounting point and available space are determined by reading ``/proc/mounts`` and calling
:func:`os.statvfs`. If the file system is not mounted, the mounting point will be empty and the
free size will be ``0``.
2. Instances are created internally by :class:`~diskinfo.Partition` and :class:`~diskinfo.Disk`.
Args:
_device (pyudev.Device): pyudev.Device class
"""
__fs_label: str # File system label
__fs_uuid: str # File system UUID
__fs_type: str # File system type
__fs_version: str # File system version
__fs_usage: str # File system usage
__fs_size: int # File system total size in 512-byte blocks
__fs_free_size: int # File system free/available 512-bytes blocks
__fs_mounting_point: str # File system mounting folder
def __init__(self, _device: Device) -> None:
# Read filesystem attributes from udev properties.
self.__fs_label = _pyudev_getenc(_device, "ID_FS_LABEL")
self.__fs_uuid = _pyudev_getenc(_device, "ID_FS_UUID")
self.__fs_type = _device.get("ID_FS_TYPE")
self.__fs_version = _device.get("ID_FS_VERSION")
self.__fs_usage = _device.get("ID_FS_USAGE")
self.__fs_mounting_point = ""
self.__fs_size = 0
self.__fs_free_size = 0
# Find mounting point from /proc/mounts and free space via os.statvfs().
path = _device.device_node
try:
with open(_PROC_MOUNTS, "r", encoding="utf-8") as f:
for line in f:
parts = line.split()
if len(parts) < 2:
continue
if parts[0] == path:
self.__fs_mounting_point = parts[1]
try:
st = os.statvfs(parts[1])
self.__fs_size = (st.f_blocks * st.f_frsize) // 512
self.__fs_free_size = (st.f_bavail * st.f_frsize) // 512
except OSError:
# statvfs can fail if the mount point becomes stale or inaccessible
# between reading /proc/mounts and calling statvfs (race condition),
# or if the filesystem does not support statvfs. In this case, the
# mounting point is still valid but free size remains 0.
pass
break
except OSError:
# /proc/mounts may not be available (e.g. in a minimal container or chroot
# without /proc mounted). In this case, both mounting point and free size
# remain at their defaults (empty string and 0 respectively).
pass
[docs]
def get_fs_label(self) -> str:
"""Returns the label of the file system. The result could be empty if the file system does not have a label.
"""
return self.__fs_label
[docs]
def get_fs_uuid(self) -> str:
"""Returns the UUID of the file system. The result could be empty if the device does not contain a
file system.
"""
return self.__fs_uuid
[docs]
def get_fs_type(self) -> str:
"""Returns the type of the file system. The result could be empty if the device does not contain a
file system.
"""
return self.__fs_type
[docs]
def get_fs_version(self) -> str:
"""Returns the version of the file system. The result could be empty if the device does not contain a
file system or does not have a version.
"""
return self.__fs_version
[docs]
def get_fs_usage(self) -> str:
"""Returns the usage of the file system. The result could be empty if the device does not contain a
file system. Valid values are ``filesystem`` or ``other`` for special devices (e.g. for a swap partition).
"""
return self.__fs_usage
[docs]
def get_fs_size(self) -> int:
"""Returns the total size of the file system in 512-byte blocks. The result could be 0 if the device does not
contain a file system or if the file system is not mounted.
"""
return self.__fs_size
[docs]
def get_fs_size_in_hrf(self, units: int = 0) -> Tuple[float, str]:
"""Returns the total size of the file system in human-readable form. The result could be 0 if the device
does not contain a file system or if the file system is not mounted.
Args:
units (int): unit system will be used for the calculation and in the result:
- 0 metric units (default)
- 1 IEC units
- 2 legacy units
Read more about `units here <https://en.wikipedia.org/wiki/Byte>`_.
Returns:
Tuple[float, str]: size in human-readable form, proper unit
"""
return size_in_hrf(self.__fs_size * 512, units)
[docs]
def get_fs_free_size(self) -> int:
"""Returns the free size of the file system in 512-byte blocks. The result could be 0 if the device does not
contain a file system or if the file system is not mounted.
"""
return self.__fs_free_size
[docs]
def get_fs_free_size_in_hrf(self, units: int = 0) -> Tuple[float, str]:
"""Returns the free size of the file system in human-readable form. The result could be 0 if the device
does not contain a file system or if the file system is not mounted.
Args:
units (int): unit system will be used for the calculation and in the result:
- 0 metric units (default)
- 1 IEC units
- 2 legacy units
Read more about `units here <https://en.wikipedia.org/wiki/Byte>`_.
Returns:
Tuple[float, str]: size in human-readable form, proper unit
"""
return size_in_hrf(self.__fs_free_size * 512, units)
[docs]
def get_fs_mounting_point(self) -> str:
"""Returns the mounting point of the file system. The result could be empty if the device does not
contain any file system, or it is not mounted.
"""
return self.__fs_mounting_point
def __repr__(self):
"""String representation of the FileSystem class."""
return (f"FileSystem(fs_label={self.__fs_label}, "
f"fs_uuid={self.__fs_uuid}, "
f"fs_type={self.__fs_type}, "
f"fs_version={self.__fs_version}, "
f"fs_usage={self.__fs_usage}, "
f"fs_size={self.__fs_size}, "
f"fs_free_size={self.__fs_free_size}, "
f"fs_mounting_point={self.__fs_mounting_point})")
# End