]> git.ipfire.org Git - people/stevee/ipfire-2.x.git/commitdiff
media.cgi: Refactor code master-media.cgi
authorStefan Schantl <stefan.schantl@ipfire.org>
Fri, 1 Aug 2025 17:45:48 +0000 (19:45 +0200)
committerStefan Schantl <stefan.schantl@ipfire.org>
Fri, 1 Aug 2025 17:45:48 +0000 (19:45 +0200)
 * Refactor the code to use filesystem-functions.pl
 * Fix display issues when using BTRFS
 * Avoid from spawning subshells over and over
 * Code cleanup

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
html/cgi-bin/media.cgi

index c1914f46946ef4d5a2f984b3ae6baeec5b02ec56..36cc73042c49f770fcd43295c6ba553d0bc7b891 100644 (file)
@@ -2,7 +2,7 @@
 ###############################################################################
 #                                                                             #
 # 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        #
@@ -29,41 +29,50 @@ require '/var/ipfire/general-functions.pl';
 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>
@@ -74,34 +83,39 @@ while(<DF>){
 </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'>&nbsp;\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'>&nbsp;\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>
@@ -112,102 +126,172 @@ while(<DF>){
 </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'>&nbsp;\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'>&nbsp;\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;
 }