###############################################################################
# #
# IPFire.org - A linux based firewall #
-# Copyright (C) 2007-2022 IPFire Team <info@ipfire.org> #
+# Copyright (C) 2007-2025 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 #
require "${General::swroot}/lang.pl";
require "${General::swroot}/header.pl";
require "${General::swroot}/graphs.pl";
+require "${General::swroot}/filesystem-functions.pl";
my %color = ();
my %mainsettings = ();
&General::readhash("${General::swroot}/main/settings", \%mainsettings);
&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color);
-#workaround to suppress a warning when a variable is used only once
-my @dummy = ( ${Header::colourred} );
-undef (@dummy);
-
my %cgiparams=();
-my @devices = `ls -1 /sys/block | grep -E '^sd|^mmcblk|^nvme|^xvd|^vd|^md' | sort | uniq`;
+# Get all known block devices.
+my @devices = &Filesystem::get_block_devices();
+
+# Gather filesystem information
+my %volumes = &Filesystem::volumes_status(df => "True", inodes => "True");
+
+# Create sorted array which contains the volume names.
+my @volumes = sort keys %volumes;
+
+# Gather iostats.
+my %iostats = &iostats();
&Header::showhttpheaders();
&Header::openpage($Lang::tr{'media information'}, 1, '');
&Header::openbigbox('100%', 'left');
-foreach (@devices) {
- my $device = $_;
- chomp($device);
- my @array = split(/\//,$device);
- &Header::openbox('100%', 'center', "$array[$#array] $Lang::tr{'graph'}");
- diskbox($array[$#array]);
- &Graphs::makegraphbox("media.cgi",$array[$#array],"day");
+# Loop through the array of devices.
+foreach my $device (@devices) {
+ # Open a new box for displaying the device details and graph.
+ &Header::openbox('100%', 'center', "$device $Lang::tr{'graph'}");
+
+ # Call function to show additional diskbox details.
+ &diskbox($device);
+
+ # Show the graph.
+ &Graphs::makegraphbox("media.cgi",$device,"day");
+
+ # Close the device box.
&Header::closebox();
}
-
&Header::openbox('100%', 'center', $Lang::tr{'disk usage'});
+
print "<table width='95%' cellspacing='5'>\n";
-open(DF,'/bin/df -P -B M -x rootfs|');
-while(<DF>){
- if ($_ =~ m/^Filesystem/ ){
- print <<END
+print <<END
<tr>
<td align='center' class='boldbase'><b>$Lang::tr{'device'}</b></td>
<td align='center' class='boldbase'><b>$Lang::tr{'mounted on'}</b></td>
</tr>
END
;
- }else{
- my ($device,$size,$used,$free,$percent,$mount) = split;
- print <<END
-<tr>
- <td align='center'>$device</td>
- <td align='center'>$mount</td>
- <td align='center'>$size</td>
- <td align='center'>$used</td>
- <td align='center'>$free</td>
- <td align='left'>
-END
-;
- &percentbar($percent);
- print <<END
-</td>
- <td align='left'>$percent</td>
-</tr>
-END
-;
- }
+
+# Loop through the array of known volumes.
+foreach my $volume (@volumes) {
+ # Assign nice human read-able values.
+ my $name = $volume;
+ my $mountpoint = $volumes{$volume}{'mpoint'};
+
+ # Format into something different than bytes.
+ my $space_available = &General::formatBytes($volumes{$volume}{'avail'});
+ my $space_used = &General::formatBytes($volumes{$volume}{'used'});
+ my $space_free = &General::formatBytes($volumes{$volume}{'free'});
+
+ # Convert percent value into integer format and append a percent sign.
+ my $space_used_percent = int($volumes{$volume}{'percent_used'}) . "%";
+
+ # Skip swap devices.
+ next if ($volumes{$volume}{'filesystem'} eq "swap");
+
+ print "<tr>\n";
+ print "<td align='center'>$name</td>\n";
+ print "<td align='center'>$mountpoint</td>\n";
+ print "<td align='center'>$space_available</td>\n";
+ print "<td align='center'>$space_used</td>\n";
+ print "<td align='center'>$space_free</td>\n";
+ print "<td align='left'>\n";
+ &percentbar($space_used_percent);
+ print "</td>\n";
+ print "<td align='left'>$space_used_percent</td>\n";
+ print "</tr>\n";
}
-close DF;
-print "<tr><td colspan='7'> \n<tr><td colspan='7'><h3>$Lang::tr{'inodes'}</h3>\n";
-open(DF,'/bin/df -P -i -x rootfs|');
-while(<DF>){
- if ($_ =~ m/^Filesystem/ ){
- print <<END
+print "<tr><td colspan='7'> \n<tr><td colspan='7'><h3>$Lang::tr{'inodes'}</h3>\n";
+print <<END
<tr>
<td align='center' class='boldbase'><b>$Lang::tr{'device'}</b></td>
<td align='center' class='boldbase'><b>$Lang::tr{'mounted on'}</b></td>
</tr>
END
;
- }else{
- my ($device,$size,$used,$free,$percent,$mount) = split;
- print <<END
-<tr>
- <td align='center'>$device</td>
- <td align='center'>$mount</td>
- <td align='center'>$size</td>
- <td align='center'>$used</td>
- <td align='center'>$free</td>
-<td>
-END
-;
- &percentbar($percent);
- print <<END
-</td>
-<td align='left'>$percent</td>
-</tr>
-END
-;
- }
+
+# Loop through the array of known volumes.
+foreach my $volume (@volumes) {
+ # Assign nice human read-able values.
+ my $name = $volume;
+ my $mountpoint = $volumes{$volume}{'mpoint'};
+ my $inodes_available = $volumes{$volume}{'inodes'} || "-";
+ my $inodes_free = $volumes{$volume}{'inodes_free'} || "-";
+ my $inodes_used = $volumes{$volume}{'inodes_used'} || "-";
+
+ # Convert percent value into integer format and append a percent sign.
+ my $inodes_percent_used = int($volumes{$volume}{'inodes_percent_used'} || "0") . "%";
+
+ # Skip swap devices.
+ next if ($volumes{$volume}{'filesystem'} eq "swap");
+
+ print "<tr>\n";
+ print "<td align='center'>$name</td>\n";
+ print "<td align='center'>$mountpoint</td>\n";
+ print "<td align='center'>$inodes_available</td>\n";
+ print "<td align='center'>$inodes_used</td>\n";
+ print "<td align='center'>$inodes_free</td>\n";
+ print "<td>\n";
+ &percentbar($inodes_percent_used);
+ print "</td>\n";
+ print "<td align='left'>$inodes_percent_used</td>\n";
+ print "</tr>\n";
}
-close DF;
-my @iostat1 = qx(/usr/bin/iostat -dm -p | grep -v "Linux" | awk '{print \$1}');
-my @iostat2 = qx(/usr/bin/iostat -dm -p | grep -v "Linux" | awk '{print \$6}');
-my @iostat3 = qx(/usr/bin/iostat -dm -p | grep -v "Linux" | awk '{print \$7}');
-print "<tr><td colspan='3'> \n<tr><td colspan='3'><h3>$Lang::tr{'transfers'}</h3></td></tr>";
-my $i=0;
-
-for(my $i = 1; $i <= $#iostat1; $i++){
- if ( $i eq '1' ){
- print "<tr><td align='center' class='boldbase'><b>$Lang::tr{'device'}</b></td><td align='center' class='boldbase'><b>$Lang::tr{'MB read'}</b></td><td align='center' class='boldbase'><b>$Lang::tr{'MB written'}</b></td></tr>";
- }else{
- print "<tr><td align='center'>$iostat1[$i]</td><td align='center'>$iostat2[$i]</td><td align='center'>$iostat3[$i]</td></tr>";
- }
+
+print "<tr><td colspan='3'> \n<tr><td colspan='3'><h3>$Lang::tr{'transfers'}</h3></td></tr>\n";
+print "<tr>\n";
+print "<td align='center' class='boldbase'><b>$Lang::tr{'device'}</b></td>\n";
+print "<td align='center' class='boldbase'><b>$Lang::tr{'MB read'}</b></td>\n";
+print "<td align='center' class='boldbase'><b>$Lang::tr{'MB written'}</b></td>\n";
+print "</tr>\n";
+
+# Loop through the array of known volumes.
+foreach my $volume (@volumes) {
+ # Assign nice values and use format function to no use bytes.
+ my $read = &General::formatBytes($iostats{$volume}{'bytes_read'});
+ my $written = &General::formatBytes($iostats{$volume}{'bytes_written'});
+
+ print "<tr><td align='center'>$volume</td><td align='center'>$read</td><td align='center'>$written</td></tr>";
}
+
print "</table>\n";
&Header::closebox();
&Header::closebigbox();
&Header::closepage();
-sub percentbar
-{
- my $percent = $_[0];
- my $fg = '#a0a0a0';
- my $bg = '#e2e2e2';
+sub percentbar ($) {
+ my ($percent) = @_;
+ my $fg = '#a0a0a0';
+ my $bg = '#e2e2e2';
- if ($percent =~ m/^(\d+)%$/ )
- {
- print <<END
-<table width='100' border='1' cellspacing='0' cellpadding='0' style='border-width:1px;border-style:solid;border-color:$fg;width:100px;height:10px;'>
-<tr>
-END
-;
- if ($percent eq "100%") {
- print "<td width='100%' bgcolor='$fg' style='background-color:$fg;border-style:solid;border-width:1px;border-color:$bg'>"
- } elsif ($percent eq "0%") {
- print "<td width='100%' bgcolor='$bg' style='background-color:$bg;border-style:solid;border-width:1px;border-color:$bg'>"
- } else {
- print "<td width='$percent' bgcolor='$fg' style='background-color:$fg;border-style:solid;border-width:1px;border-color:$bg'></td><td width='" . (100-$1) . "%' bgcolor='$bg' style='background-color:$bg;border-style:solid;border-width:1px;border-color:$bg'>"
- }
- print <<END
-<img src='/images/null.gif' width='1' height='1' alt='' /></td></tr></table>
-END
-;
- }
+ # Early exit if the given percent amount is not numerical and does not contain
+ # a percent sign.
+ return unless ($percent =~ m/^(\d+)%$/);
+
+ print "<table width='100' border='1' cellspacing='0' cellpadding='0' style='border-width:1px;border-style:solid;border-color:$fg;width:100px;height:10px;'>\n";
+ print "<tr>\n";
+
+ # Handle 100%
+ if ($percent eq "100%") {
+ print "<td width='100%' bgcolor='$fg' style='background-color:$fg;border-style:solid;border-width:1px;border-color:$bg'>\n";
+
+ # Handle 0%
+ } elsif ($percent eq "0%") {
+ print "<td width='100%' bgcolor='$bg' style='background-color:$bg;border-style:solid;border-width:1px;border-color:$bg'>\n";
+
+ # Handle everything between.
+ } else {
+ print "<td width='$percent' bgcolor='$fg' style='background-color:$fg;border-style:solid;border-width:1px;border-color:$bg'></td>\n";
+ print "<td width='" . (100-$1) . "%' bgcolor='$bg' style='background-color:$bg;border-style:solid;border-width:1px;border-color:$bg'>\n";
+ }
+
+ print "<img src='/images/null.gif' width='1' height='1' alt='' /></td>\n";
+ print "</tr>\n";
+ print "</table>\n";
}
-sub diskbox {
- my $disk = $_[0];
- chomp $disk;
- my @status;
- if (-e "/var/run/hddstatus"){
- open(DATEI, "</var/run/hddstatus") || die "Datei nicht gefunden";
- my @diskstate = <DATEI>;
- close(DATEI);
-
- foreach (@diskstate){
- if ( $_ =~/$disk/ ){@status = split(/-/,$_);}
- }
+sub diskbox ($) {
+ my ($disk) = @_;
+
+ # Early return if the hddstatus file does not exist.
+ return unless (-e "/var/run/hddstatus");
- if ( $status[1]=~/standby/){
+ # Open the disk status file.
+ open(STATUS, "</var/run/hddstatus") or die "Could not open /var/run/hddstatus: $!";
+
+ # Loop through the file content.
+ while (<STATUS>) {
+ # Skip lines which does not belong to the requested disk.
+ next unless ($_ =~/$disk/);
+
+ # Split the line content and save it into the status array.
+ my ($d, $status) = split(/-/,$_);
+
+ # Check if the status is standby.
+ if ( $status =~ /standby/) {
+ # Get time since the disk is in standby.
my $ftime = localtime((stat("/var/run/hddshutdown-$disk"))[9]);
- print"<b>Disk $disk status:<span style='color:#FF0000'>".$status[1]."</b> ($Lang::tr{'since'} $ftime)";
- }else{
- print"<b>Disk $disk status:<span style='color:#00FF00'>".$status[1]."</b>";
+
+ # Print message with standby status and time.
+ print"<b>Disk $disk status:<span style='color:#FF0000'>" . $status . "</b> ($Lang::tr{'since'} $ftime)";
+ } else {
+ # Print message about disk is online.
+ print"<b>Disk $disk status:<span style='color:#00FF00'>" . $status . "</b>";
}
+
+ # Break the loop.
+ last;
}
+ # Close file handle.
+ close(STATUS);
+
+ # Get S.M.A.R.T details of the disk.
my $smart = `/usr/local/bin/smartctrl $disk`;
+
+ # Clenup output.
$smart = &Header::cleanhtml($smart);
- print <<END
-<br /><input type="button" onClick="swapVisibility('smart_$disk')" value="$Lang::tr{'smart information'}" />
-<div id='smart_$disk' style='display: none'>
- <hr /><table border='0'><tr><td align='left'><pre>$smart</pre></table>
-</div>
-END
-;
+
+ print "<br><input type='button' onClick=\"swapVisibility('smart_$disk')\" value='$Lang::tr{'smart information'}' />\n";
+ print "<div id='smart_$disk' style='display: none'>\n";
+ print "<hr><table border='0'><tr><td align='left'><pre>$smart</pre></table>\n";
+ print "</div>\n";
+}
+
+sub iostats () {
+ my %iostats = ();
+
+ # Open kernel diskstats file.
+ open(IOSTATS, "/proc/diskstats") or die "Could not open /proc/diskstats: $!\n";
+
+ # Loop through the stats.
+ while (<IOSTATS>) {
+ # Remove newlines.
+ chomp($_);
+
+ # Split the line into pieces by a single ore multiple spaces.
+ my @iostats = split(/\s+/, $_);
+
+ # Assign human read-able values.
+ my $disk = "/dev/" . $iostats[3];
+ my $sectors_read = $iostats[6];
+ my $sectors_written = $iostats[10];
+
+ # Skip if the current entry is not part of the volumes array.
+ next unless (grep (/^$disk$/, @volumes));
+
+ # The diskstats information provided by the kernel contains the read and written sectors.
+ # A sector equals 1Kb / 2 or 512 bytes - so we have to multiply the grabbed values by 512 to
+ # convert them into bytes.
+ #
+ # Convert the read sectors into bytes and add it to the hash.
+ $iostats{$disk}{'bytes_read'} = $sectors_read * 512;
+
+ # Convert the written sectors into bytes and add it to the hash.
+ $iostats{$disk}{'bytes_written'} = $sectors_written * 512;
+ }
+
+ # Close the file handle.
+ close(IOSTATS);
+
+ # Return the hash.
+ return %iostats;
}