From e05cd144b16d2b8cb30d97f743f7d784de4e0b9f Mon Sep 17 00:00:00 2001 From: Stefan Schantl Date: Wed, 1 May 2024 13:00:14 +0200 Subject: [PATCH] filesystem-functions.pl: New perl functions library This library contains various functions to gather device and filesystem details. Signed-off-by: Stefan Schantl --- config/cfgroot/filesystem-functions.pl | 456 +++++++++++++++++++++++++ config/rootfiles/common/configroot | 1 + lfs/configroot | 1 + 3 files changed, 458 insertions(+) create mode 100644 config/cfgroot/filesystem-functions.pl diff --git a/config/cfgroot/filesystem-functions.pl b/config/cfgroot/filesystem-functions.pl new file mode 100644 index 000000000..2c3877a9d --- /dev/null +++ b/config/cfgroot/filesystem-functions.pl @@ -0,0 +1,456 @@ +#!/usr/bin/perl -w +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2007-2024 IPFire Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +package Filesystem; + +use strict; + +# Import perl module to get details about the space usage. +use Filesys::Df; + +# SYSFS directory which contains all block device data. +my $sysfs_block_dir = "/sys/class/block"; + +# Directory where the uuid mappings can be found. +my $uuid_dir = "/dev/disk/by-uuid"; + +# +## Function which returns a lot of details about the known devices +## as a hash of hashes. +## +## Please note that all size values are in bytes! +## +## Passing the following options as a hash allows to customize what +## should be returned: +## +## * devices => "all" - To get details about all known devices +## * uuids => "True" - To also grab the UUIDS for each device +## * df => "True" - To grab details about the filesystem usage +## +## Example: {'/dev/vda1' => { +# 'free' => '1234567890', +# 'size' => '1234567890', +# 'filesystem' => 'ext4', +# 'avail' => '1234567890', +# 'percent_free' => 60, +# 'percent_used' => 40, +# 'mount_options' => 'rw,noatime,....', +# 'used' => '1234567890', +# 'mpoint' => '/somewhere' +# } +# }; +# +sub volumes_status (%) { + my (%options) = @_; + my %volumes; + + # Grab all available devices if requested. + if($options{'devices'} eq "all") { + # Get all available devices. + my @devices = &get_block_devices(); + + # Loop through the array of devices. + foreach my $device (@devices) { + my $dev_name = "/dev/" . "$device"; + + # Get the device vendor. + my $vendor = &get_device_vendor($device); + + # Add the grabbed vendor to the hash of volumes. + $volumes{$dev_name}{'vendor'} = $vendor if($vendor); + + # Get the device model. + my $model = &get_device_model($device); + + # Add the grabbed model to the hash of volumes. + $volumes{$dev_name}{'model'} = $model if($model); + + # Grab the size of the device. + my $size = &get_device_size($device); + + # Add the size to the hash of volumes. + $volumes{$dev_name}{'size'} = $size if($size); + + # Try to obtain the device mapper (LVM) group name. + my $lvm_group = &get_device_mapper_group($device); + + # Add the grabbed lvm group name to the hash of volumes. + $volumes{$dev_name}{'lvm_group'} = $lvm_group if($lvm_group); + + # Try to grab the members in case it is a grouped device (mdraid). + my @mdraid_members = &get_mdraid_members($device); + + # Add the mdraid members to the hash of volumes. + $volumes{$dev_name}{'mdraid_members'} = [ @mdraid_members ] if(@mdraid_members); + + # Grab the device partitions. + my @partitions = &get_device_partitions($device); + my @partlist = (); + + # Loop through the array of partitions. + foreach my $partition(@partitions) { + my $dev_name = "/dev/" . "$partition"; + + # Add the partition to the partlist array. + push(@partlist, $dev_name); + + # Grab the size of the partition. + my $size = &get_device_size($partition); + + # Add the size to the hash of volumes. + $volumes{$dev_name}{'size'} = $size if ($size); + } + + # Add the array of partitions to the hash of volumes. + $volumes{$dev_name}{'partitions'} = [ @partlist ] if (@partlist); + } + } + + # Obtain the uuids if requested. + if ($options{'uuids'} eq "True") { + # Open uuid directory and read-in the current known uuids. + opendir(UUIDS, "$uuid_dir"); + + # Loop through the uuids. + foreach my $uuid (readdir(UUIDS)) { + # Skip . and .. + next if($uuid eq "." or $uuid eq ".."); + + # Skip everything which is not a symbolic link. + next unless(-l "$uuid_dir/$uuid"); + + # Resolve the target of the symbolic link. + my $target = readlink("$uuid_dir/$uuid"); + + # Split the link target into pieces. + my @tmp = split("/", $target); + + # Assign the last element of the array to the dev variable. + my $device = "/dev/" . "$tmp[-1]"; + + # Add the found uuid to the volumes hash. + $volumes{$device}{'uuid'} = $uuid unless($volumes{$device}{'uuid'}); + } + + # Close directory handle. + closedir(UUIDS); + } + + # Open and read-in the current mounts from the + # kernel file system. + open(MOUNT, "/proc/mounts"); + + # Loop through the known mounts. + while() { + # Skip mounts which does not belong to a device. + next unless ($_ =~ "^/dev"); + + # Cut the line into pieces and assign nice variables. + my ($dev, $mpoint, $fs, $options, $a, $b) = split(/ /, $_); + + # Check if the processed device is not part of the hash and + # add the grabbed data. + $volumes{$dev}{'filesystem'} = $fs unless($volumes{$dev}{'filesystem'}); + $volumes{$dev}{'mount_options'} = $options unless($volumes{$dev}{'mount_options'}); + $volumes{$dev}{'mpoint'} = $mpoint unless($volumes{$dev}{'mpoint'}); + + if ($options{'df'} eq "True") { + # Call df module to get details about space usage + # and request the output in bytes (1). + my $df = Filesys::Df::df($mpoint, 1); + + # Assign grabbed storrage details to the hash. + if (defined($df)) { + $volumes{$dev}{"size"} = $df->{blocks}; + $volumes{$dev}{"free"} = $df->{bfree}; + $volumes{$dev}{"avail"} = $df->{bavail}; + $volumes{$dev}{"used"} = $df->{used}; + $volumes{$dev}{"percent_used"} = $df->{per}; + $volumes{$dev}{"percent_free"} = int(100) - $df->{per}; + + # Undefine created df object for the processed mountpoint. + undef $df; + } + } + } + + # Close file handle. + close(MOUNT); + + # Open and read the swaps file. + open(SWAP, "/proc/swaps"); + + # Loop though the file content. + while() { + # Skip lines which does not belong to a device. + next unless ($_ =~ "^/dev"); + + # Split the line and assign nice variables. + my ($dev, $type, $size, $used, $prio) = split(/ /, $_); + + # Assign "swap" as filesystem if the volume is used as swap. + $volumes{$dev}{"filesystem"} = "swap" unless($volumes{$dev}{"filesystem"}); + } + + # Close file handle. + close(SWAP); + + # Return the hash of hashes. + return %volumes; +} + +# +## Returns the vendor of a given block device. +# +sub get_device_vendor ($) { + my ($device) = @_; + + # Assign device directory. + my $device_dir = "$sysfs_block_dir/$device"; + + # Abort and return nothing if the device dir does not exist + # or no vendor file exists. + return unless(-d "$device_dir"); + return unless(-f "$device_dir/device/vendor"); + + # Open and read-in the device vendor. + open(VENDOR, "$device_dir/device/vendor"); + my $vendor = ; + close(VENDOR); + + # Abort and return nothing if no vendor could be read. + return unless($vendor); + + # Remove any newlines from the vendor string. + chomp($vendor); + + # Return the omited vendor. + return $vendor; +} + +# +## Returns the model name (string) of a given block device. +# +sub get_device_model ($) { + my ($device) = @_; + + # Assign device directory. + my $device_dir = "$sysfs_block_dir/$device"; + + # Abort and return nothing if the device dir does not exist + # or no model file exists. + return unless(-d "$device_dir"); + return unless(-f "$device_dir/device/model"); + + # Open and read-in the device model. + open(MODEL, "$device_dir/device/model"); + my $model = ; + close(MODEL); + + # Abort and return nothing if no model could be read. + return unless($model); + + # Remove any newlines from the model string. + chomp($model); + + # Return the model string. + return $model; +} + +# +## Returns the size of a given device in bytes. +# +sub get_device_size ($) { + my ($device) = @_; + + # Assign device directory. + my $device_dir = "$sysfs_block_dir/$device"; + + # Abort and return nothing if the device dir does not exist + # or no size file exists. + return unless(-d "$device_dir"); + return unless(-f "$device_dir/size"); + + # Open and read-in the device size. + open(SIZE, "$device_dir/size"); + my $size = ; + close(SIZE); + + # Abort and return nothing if the size could not be read. + return unless($size); + + # Remove any newlines for the size string. + chomp($size); + + # The omited size only contains the amount of blocks from the + # given device. To convert this into bytes we have to multiply this + # value with 512 bytes for each block. This is a static value used by + # the linux kernel. + $size = $size * 512; + + # Return the size in bytes. + return $size; +} + +# +## Function which return an array with all available block devices. +# +sub get_block_devices () { + my @devices; + + # Open directory from kernel sysfs. + opendir(DEVICES, "/sys/block"); + + # Loop through the directory. + while(readdir(DEVICES)) { + # Skip . and .. + next if($_ =~ /^\.$/); + next if($_ =~ /^\..$/); + + # Skip any loopback and ram devices. + next if($_ =~ "^loop"); + next if($_ =~ "^ram"); + + # Add the device to the array of found devices. + push(@devices, $_); + } + + # Close directory handle. + closedir(DEVICES); + + # Sort the block devices alphabetically. + @devices = sort(@devices); + + # Return the devices array. + return @devices; +} + +# +## Function which return all partitions of a given block device. +# +sub get_device_partitions ($) { + my ($device) = @_; + + # Array to store the known partitions for the given + # device. + my @partitions; + + # Assign device directory. + my $device_dir = "$sysfs_block_dir/$device"; + + # Abort and return nothing if the device dir does not exist. + return unless(-d "$device_dir"); + + # Open device directory. + opendir(DEVICE, "$sysfs_block_dir/$device"); + + # Loop through the directory + while(readdir(DEVICE)) { + # Skip everything which does not start with the device name. + next unless($_ =~ "^$device"); + + # Add found partition to the array of partitions. + push(@partitions, $_); + } + + # Close directory handle. + closedir(DEVICE); + + # Sort the array of partitions. + @partitions = sort(@partitions); + + # Return the sortet array. + return @partitions; +} + +# +## Function which will collect the members of a grouped device (mdraid) and returns them as array. +# +sub get_mdraid_members ($) { + my ($device) = @_; + + # Generate device directory. + my $device_dir = "$sysfs_block_dir/$device"; + + # Skip device if it has no members. + # In this case the "slaves" directory does not exist. + return unless (-e "$device_dir/slaves"); + + # Tempoarary array to store the members of a group. + my @members = (); + + # Grab all members. + opendir(MEMBERS, "$device_dir/slaves"); + while(readdir(MEMBERS)) { + next if($_ eq "."); + next if($_ eq ".."); + + # Format found member with full /dev path. + my $member = "/dev/" . "$_"; + + # Add the found member to the array of members. + push(@members, $member); + } + + closedir(MEMBERS); + + # Skip the device if no members could be grabbed. + return unless (@members); + + # Sort the members in alpabetical order. + @members = sort(@members); + + # Return the sorted array. + return @members; +} + +# +## Function which tries to obtain the group name in case it is managed by the device mapper (LVM). +# +sub get_device_mapper_group ($) { + my ($device) = @_; + + # Generate device directory. + my $device_dir = "$sysfs_block_dir/$device"; + + # Skip the device if it is not managed by device mapper + # In this case the "bd" is not present. + return unless (-e "$device_dir/dm"); + + # Grab the group and volume name. + open(NAME, "$device_dir/dm/name") if (-e "$device_dir/dm/name"); + my $name = ; + close(NAME); + + # Skip device if no name could be determined. + return unless($name); + + # Remove any newlines from the name string. + chomp($name); + + # Generate path to the dev node in devfs. + my $dev_path = "/dev/mapper/$name"; + + # Return the path. + return $dev_path; +} + +1; diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot index a286a1538..5e4c9744e 100644 --- a/config/rootfiles/common/configroot +++ b/config/rootfiles/common/configroot @@ -58,6 +58,7 @@ var/ipfire/extrahd/bin/extrahd.pl #var/ipfire/extrahd/partitions #var/ipfire/extrahd/scan #var/ipfire/extrahd/settings +var/ipfire/filesystem-functions.pl var/ipfire/firewall #var/ipfire/firewall/config #var/ipfire/firewall/input diff --git a/lfs/configroot b/lfs/configroot index 9f6c1ff8c..1f2288c72 100644 --- a/lfs/configroot +++ b/lfs/configroot @@ -81,6 +81,7 @@ $(TARGET) : cp $(DIR_SRC)/config/cfgroot/location-functions.pl $(CONFIG_ROOT)/ cp $(DIR_SRC)/config/cfgroot/ipblocklist-functions.pl $(CONFIG_ROOT)/ cp $(DIR_SRC)/config/cfgroot/ids-functions.pl $(CONFIG_ROOT)/ + cp $(DIR_SRC)/config/cfgroot/filesystem-functions.pl $(CONFIG_ROOT)/ cp $(DIR_SRC)/config/cfgroot/lang.pl $(CONFIG_ROOT)/ cp $(DIR_SRC)/config/cfgroot/countries.pl $(CONFIG_ROOT)/ cp $(DIR_SRC)/config/cfgroot/graphs.pl $(CONFIG_ROOT)/ -- 2.39.5