--- /dev/null
+#!/usr/bin/perl -w
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2007-2024 IPFire Team <info@ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+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(<MOUNT>) {
+ # 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(<SWAP>) {
+ # 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 = <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 = <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 = <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 = <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;