import codecs
import os
import re
from landscape.lib.compat import _PY3
# List of filesystem types authorized when generating disk use statistics.
STABLE_FILESYSTEMS = frozenset(
[
"ext",
"ext2",
"ext3",
"ext4",
"reiserfs",
"ntfs",
"msdos",
"dos",
"vfat",
"xfs",
"hpfs",
"jfs",
"ufs",
"hfs",
"hfsplus",
"simfs",
"drvfs",
"lxfs",
"zfs",
"btrfs",
],
)
EXTRACT_DEVICE = re.compile("([a-z]+)[0-9]*")
def get_mount_info(
mounts_file,
statvfs_,
filesystems_whitelist=STABLE_FILESYSTEMS,
):
"""
This is a generator that yields information about mounted filesystems.
@param mounts_file: A file with information about mounted filesystems,
such as C{/proc/mounts}.
@param statvfs_: A function to get file status information.
@param filesystems_whitelist: Optionally, a list of which filesystems to
stat.
@return: A C{dict} with C{device}, C{mount-point}, C{filesystem},
C{total-space} and C{free-space} keys. If the filesystem information
is not available, C{None} is returned. Both C{total-space} and
C{free-space} are in megabytes.
"""
for line in open(mounts_file):
try:
device, mount_point, filesystem = line.split()[:3]
if _PY3:
mount_point = codecs.decode(mount_point, "unicode_escape")
else:
mount_point = codecs.decode(mount_point, "string_escape")
except ValueError:
continue
if (
filesystems_whitelist is not None
and filesystem not in filesystems_whitelist
):
continue
megabytes = 1024 * 1024
try:
stats = statvfs_(mount_point)
except OSError:
continue
block_size = stats.f_bsize
total_space = (stats.f_blocks * block_size) // megabytes
free_space = (stats.f_bfree * block_size) // megabytes
yield {
"device": device,
"mount-point": mount_point,
"filesystem": filesystem,
"total-space": total_space,
"free-space": free_space,
}
def get_filesystem_for_path(path, mounts_file, statvfs_):
"""
Tries to determine to which of the mounted filesystem C{path} belongs to,
and then returns information about that filesystem or C{None} if it
couldn't be determined.
@param path: The path we want filesystem information about.
@param mounts_file: A file with information about mounted filesystems,
such as C{/proc/mounts}.
@param statvfs_: A function to get file status information.
@return: A C{dict} with C{device}, C{mount-point}, C{filesystem},
C{total-space} and C{free-space} keys. If the filesystem information
is not available, C{None} is returned. Both C{total-space} and
C{free-space} are in megabytes.
"""
candidate = None
path = os.path.realpath(path)
path_segments = path.split("/")
for info in get_mount_info(mounts_file, statvfs_):
mount_segments = info["mount-point"].split("/")
if path.startswith(info["mount-point"]):
if (not candidate) or path_segments[
: len(mount_segments)
] == mount_segments:
candidate = info
return candidate
def is_device_removable(device):
"""
This function returns whether a given device is removable or not by looking
at the corresponding /sys/block/<device>/removable file
@param device: The filesystem path to the device, e.g. /dev/sda1
"""
# Shortcut the case where the device an SD card. The kernel/udev currently
# consider SD cards (mmcblk devices) to be non-removable.
if os.path.basename(device).startswith("mmcblk"):
return True
path = _get_device_removable_file_path(device)
if not path:
return False
contents = None
try:
with open(path, "r") as f:
contents = f.readline()
except OSError:
return False
if contents.strip() == "1":
return True
return False
def _get_device_removable_file_path(device):
"""
Get a device's "removable" file path.
This function figures out the C{/sys/block/<device>/removable} path
associated with the given device. The file at that path contains either
a "0" if the device is not removable, or a "1" if it is.
@param device: File system path of the device.
"""
# The device will be a symlink if the disk is mounted by uuid or by label.
if os.path.islink(device):
# Paths are in the form "/dev/disk/by-uuid/<uuid>" and symlink
# to the device file under /dev
device = os.readlink(device) # /dev/disk/by-uuid/<uuid> -> ../../sda1
[device_name] = device.split("/")[-1:] # /dev/sda1 -> sda1
matched = EXTRACT_DEVICE.match(device_name) # sda1 -> sda
if not matched:
return None
device_name = matched.groups()[0]
removable_file = os.path.join("/sys/block/", device_name, "removable")
return removable_file
|