extrahd.cgi: Add support for LVM and MDADM devices

Message ID 20230923105455.4960-1-stefan.schantl@ipfire.org
State Staged
Commit 15d9c996f9aff92f6cb266125e9d406cdf438ab1
Headers
Series extrahd.cgi: Add support for LVM and MDADM devices |

Commit Message

Stefan Schantl Sept. 23, 2023, 10:54 a.m. UTC
  This commit adds support for using LVM and mdadm based RAID devices
for the CGI page.

In case one or more drives/partitions are used by such a "grouped"
volume they still will displayed on the page, but can not be
configured/used. Instead the "master" volume of which the
drive/partition is part of is shown in the "mountpoint" input box.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 html/cgi-bin/extrahd.cgi | 303 +++++++++++++++++++++++++++++----------
 1 file changed, 225 insertions(+), 78 deletions(-)
  

Patch

diff --git a/html/cgi-bin/extrahd.cgi b/html/cgi-bin/extrahd.cgi
index c3a14c9e4..afe79479b 100644
--- a/html/cgi-bin/extrahd.cgi
+++ b/html/cgi-bin/extrahd.cgi
@@ -51,6 +51,12 @@  my @devices = &get_block_devices();
 # Grab all known UUID's.
 my %uuids = &get_uuids();
 
+# Detect device mapper devices.
+my %device_mapper = &get_device_mapper();
+
+# Grab members of group devices (RAID, LVM)
+my %grouped_devices = &collect_grouped_devices();
+
 # Grab all mountpoints.
 my %mountpoints = &get_mountpoints();
 
@@ -236,102 +242,144 @@  END
 		# Grab the known partitions of the current block device.
 		my @partitions = &get_device_partitions($device);
 
-		foreach my $partition (@partitions) {
-			my $disabled;
+		# Check if the block device has any partitions for display.
+		if (@partitions) {
+			# Loop through the partitions.
+			foreach my $partition (@partitions) {
+				# Call function to display the row in the WUI.
+				&print_row($partition);
+			}
+		}
+
+		# Also print rows for devices with an UUID.
+		&print_row($device) if($uuids{$device});
+	}
 
-			# Omit the partition size.
-			my $bsize = &get_device_size($partition);
+        print <<END
+        <tr><td align="center" colspan="5">&nbsp;</td></tr>
+        <tr><td align="center" colspan="5">&nbsp;</td></tr>
+        <tr><td align="center" colspan="5">$Lang::tr{'extrahd install or load driver'}</td></tr>
+        </table>
+END
+;
 
-			# Convert into human-readable format.
-			my $size = &General::formatBytes($bsize);
+&Header::closebox();
 
-			# Try to omit the used filesystem.
-			my $fs = $filesystems{$partition};
+&Header::closebigbox();
+&Header::closepage();
 
-			# Get the mountpoint.
-			my $mountpoint = $mountpoints{$partition};
 
-			# If no mountpoint could be determined try to grab from
-			# configured drives.
-			unless($mountpoint) {
-				my $uuid = $uuids{$partition};
+#
+# Function to print a table row with device data on the WUI.
+#
+sub print_row ($) {
+	my ($partition) = @_;
 
-				# Build uuid string.
-				$uuid = "UUID=" . $uuid;
+	my $disabled;
 
-				# Try to obtain a possible moutpoint from configured drives.
-				$mountpoint = $configured_drives{$uuid} if ($configured_drives{$uuid});
-			}
+	# Omit the partition size.
+	my $bsize = &get_device_size($partition);
 
-			# Check if the mountpoint is used as root or boot device.
-			if ($mountpoint eq "/" or $mountpoint =~ "^/boot") {
-				$disabled = "disabled";
+	# Convert into human-readable format.
+	my $size = &General::formatBytes($bsize);
 
-			# Check if it is mounted.
-			} elsif(&is_mounted($mountpoint)) {
-				$disabled = "disabled";
+	# Try to omit the used filesystem.
+	my $fs = $filesystems{$partition};
 
-			# Check if the device is used as swap.
-			} elsif (&is_swap($partition)) {
-				$disabled = "disabled";
-				$mountpoint = "swap";
-				$fs = "swap";
-			}
+	# Get the mountpoint.
+	my $mountpoint = $mountpoints{$partition};
 
-			print <<END
-
-			<form method='post' action='$ENV{'SCRIPT_NAME'}'>
-			<tr><td align="left" colspan=5><strong>UUID=$uuids{$partition}</strong></td></tr>
-			<tr>
-			<td align="list">/dev/$partition</td>
-				<td align="center">$Lang::tr{'size'} $size</td>
-				<td align="center">$fs</td>
-				<td align="center"><input type='text' name='PATH' value='$mountpoint' $disabled></td>
-				<td align="center">
-					<input type='hidden' name='DEVICE' value='/dev/$partition' />
-					<input type='hidden' name='UUID' value='$uuids{$partition}' />
-END
-;
-					# Check if the mountpoint refers to a known configured drive.
-					if(&is_configured($mountpoint)) {
-						print "<input type='hidden' name='ACTION' value='$Lang::tr{'delete'}'>\n";
-						print "<input type='hidden' name='PATH' value='$mountpoint'>\n";
-
-						# Check if the device is mounted properly.
-						if(&is_mounted($mountpoint)) {
-							print "<img src='/images/updbooster/updxl-led-green.gif' alt='$Lang::tr{'extrahd mounted'}' title='$Lang::tr{'extrahd mounted'}'>&nbsp;\n";
-						} else {
-							print "<img src='/images/updbooster/updxl-led-red.gif' alt='$Lang::tr{'extrahd not mounted'}' title='$Lang::tr{'extrahd not mounted'}'>&nbsp;\n";
-						}
-
-						print "<input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/delete.gif'>\n";
-					} else {
-						unless($disabled) {
-							print "<input type='hidden' name='ACTION' value='$Lang::tr{'add'}'>\n";
-							print "<input type='hidden' name='FS' value='auto'>\n";
-							print "<img src='/images/updbooster/updxl-led-gray.gif' alt='$Lang::tr{'extrahd not configured'}' title='$Lang::tr{'extrahd not configured'}'>&nbsp;\n";
-							print "<input type='image' alt='$Lang::tr{'add'}' title='$Lang::tr{'add'}' src='/images/add.gif'>\n";
-						}
-					}
-
-				print <<END
-				</form></td></tr>
-END
-;		}
+	# Generate partition string.
+	my $partition_string = "/dev/$partition";
 
+	# Check if the given partition is managed by device mapper.
+	if (exists($device_mapper{$partition})) {
+		# Alter the partition string to used one by the device mapper.
+		$partition_string = "$device_mapper{$partition}";
+	}
+
+	# Check if the device is part of a group.
+	my $grouped_device = &is_grouped_member($partition);
+
+	# If no mountpoint could be determined try to grab from
+	# configured drives.
+	unless($mountpoint) {
+		my $uuid = $uuids{$partition};
+
+		# Build uuid string.
+		$uuid = "UUID=" . $uuid;
+
+		# Try to obtain a possible moutpoint from configured drives.
+		$mountpoint = $configured_drives{$uuid} if ($configured_drives{$uuid});
+	}
+
+	# Check if the mountpoint is used as root or boot device.
+	if ($mountpoint eq "/" or $mountpoint =~ "^/boot") {
+		$disabled = "disabled";
+
+	# Check if it is mounted.
+	} elsif(&is_mounted($mountpoint)) {
+		$disabled = "disabled";
+
+	# Check if the device is used as swap.
+	} elsif (&is_swap($partition)) {
+		$disabled = "disabled";
+		$mountpoint = "swap";
+		$fs = "swap";
+
+	# Check if the device is part of a group.
+	} elsif ($grouped_device) {
+		$disabled = "disabled";
+		$mountpoint = "/dev/$grouped_device";
+		$mountpoint = $device_mapper{$grouped_device} if (exists($device_mapper{$grouped_device}));
+	}
+
+	print "<form method='post' action='$ENV{'SCRIPT_NAME'}'>\n";
+
+	# Only display UUID details if an UUID could be obtained.
+	if ( $uuids{$partition} ) {
+		print "<tr><td align='left' colspan=5><strong>UUID=$uuids{$partition}</strong></td></tr>\n";
 	}
 
 	print <<END
-	<tr><td align="center" colspan="5">&nbsp;</td></tr>
-	<tr><td align="center" colspan="5">&nbsp;</td></tr>
-	<tr><td align="center" colspan="5">$Lang::tr{'extrahd install or load driver'}</td></tr>
-	</table>
+
+	<tr>
+	<td align="list">$partition_string</td>
+		<td align="center">$Lang::tr{'size'} $size</td>
+		<td align="center">$fs</td>
+		<td align="center"><input type='text' name='PATH' value='$mountpoint' $disabled></td>
+		<td align="center">
+			<input type='hidden' name='DEVICE' value='$partition_string' />
+			<input type='hidden' name='UUID' value='$uuids{$partition}' />
 END
 ;
-&Header::closebox();
+		# Check if the mountpoint refers to a known configured drive.
+		if(&is_configured($mountpoint)) {
+			print "<input type='hidden' name='ACTION' value='$Lang::tr{'delete'}'>\n";
+			print "<input type='hidden' name='PATH' value='$mountpoint'>\n";
+
+			# Check if the device is mounted properly.
+			if(&is_mounted($mountpoint)) {
+				print "<img src='/images/updbooster/updxl-led-green.gif' alt='$Lang::tr{'extrahd mounted'}' title='$Lang::tr{'extrahd mounted'}'>&nbsp;\n";
+			} else {
+				print "<img src='/images/updbooster/updxl-led-red.gif' alt='$Lang::tr{'extrahd not mounted'}' title='$Lang::tr{'extrahd not mounted'}'>&nbsp;\n";
+			}
 
-&Header::closebigbox();
-&Header::closepage();
+				print "<input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/delete.gif'>\n";
+		} else {
+			unless($disabled) {
+				print "<input type='hidden' name='ACTION' value='$Lang::tr{'add'}'>\n";
+				print "<input type='hidden' name='FS' value='auto'>\n";
+				print "<img src='/images/updbooster/updxl-led-gray.gif' alt='$Lang::tr{'extrahd not configured'}' title='$Lang::tr{'extrahd not configured'}'>&nbsp;\n";
+				print "<input type='image' alt='$Lang::tr{'add'}' title='$Lang::tr{'add'}' src='/images/add.gif'>\n";
+			}
+		}
+
+	print <<END
+	</form></td></tr>
+END
+;
+}
 
 #
 ## Function which return an array with all available block devices.
@@ -486,6 +534,86 @@  sub get_device_size ($) {
 	return $size;
 }
 
+#
+## Function which tries to detect if a block device is a device mapper device and returns the alias a
+## a hash. Example: "dm-0" -> "/dev/mapper/GROUP-DEVICE"
+#
+sub get_device_mapper () {
+	my %mapper_devices = ();
+
+	# Loop through all known block devices.
+	foreach my $block_device (@devices) {
+		# Generate  device directory.
+		my $device_dir = "$sysfs_block_dir/$block_device";
+
+		# Skip the device if it is not managed by device mapper
+		# In this case the "bd" is not present.
+		next 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.
+		next 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";
+
+		# Store the device and the omited mapper name in the hash.
+		$mapper_devices{$block_device} = $dev_path;
+	}
+
+	# Return the hash of omited device mapper devices.
+	return %mapper_devices;
+}
+
+#
+## Function which will collect grouped devices and their members as array in a hash and returns them.
+## For example: "sda1" -> "dm-0" in case /dev/sda1 is assigned to a device mapper group.
+#
+sub collect_grouped_devices () {
+	my %grouped_devices = ();
+
+	# Loop through the array of known block devices.
+	foreach my $device (@devices) {
+		# 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.
+		next 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 "..");
+
+			# Add the found member to the array of members.
+			push(@members, $_);
+		}
+
+		closedir(MEMBERS);
+
+		# Skip the device if no members could be grabbed.
+		next unless (@members);
+
+		# Add the array of found members as value to the hash of grouped devices.
+		$grouped_devices{$device} = [ @members ];
+	}
+
+	# Return the hash of found grouped devices and their members.
+	return %grouped_devices;
+}
+
 #
 ## Function which returns all currently mounted devices as a hash.
 ## example: "sda1" -> "/boot"
@@ -708,3 +836,22 @@  sub is_configured ($) {
 	       return 1 if($configured_drives{$uuid} eq "$path");
 	}
 }
+
+#
+## Retruns the device name of the grouped device,if a given device is a group member.
+#
+sub is_grouped_member ($) {
+	my ($device) = @_;
+
+	# Loop through the hash of found grouped devices.
+	foreach my $grouped_device(keys %grouped_devices) {
+		# The found members are stored as arrays.
+		my @members = @{ $grouped_devices{$grouped_device} };
+
+		# Loop through array of members and check if the given
+		# device is part of it.
+		foreach my $member (@members) {
+			return $grouped_device if ($member eq $device);
+		}
+	}
+}