]>
git.ipfire.org Git - ipfire-2.x.git/blob - html/cgi-bin/extrahd.cgi
2 ###############################################################################
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2023 IPFire Team <info@ipfire.org> #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
23 # enable only the following on debugging purpose
25 #use CGI::Carp 'fatalsToBrowser';
27 require '/var/ipfire/general-functions.pl' ;
28 require "${General::swroot}/lang.pl" ;
29 require "${General::swroot}/header.pl" ;
31 my %extrahdsettings = ();
32 my $errormessage = "" ;
34 # Hash to store the configured drives.
35 my %configured_drives ;
37 # SYSFS directory which contains all block device data.
38 my $sysfs_block_dir = "/sys/class/block" ;
40 # Array which contains the valid mount directories.
41 # Only mounting to subdirectories inside them is allowed.
42 my @valid_mount_dirs = (
48 # Grab all available block devices.
49 my @devices = & get_block_devices
();
51 # Grab all known UUID's.
52 my %uuids = & get_uuids
();
54 # Grab all mountpoints.
55 my %mountpoints = & get_mountpoints
();
57 # Omit the file system types of the mounted devices.
58 my %filesystems = & get_mountedfs
();
60 # Gather all used swap devices.
61 my @swaps = & get_swaps
();
63 # The config file which contains the configured devices.
64 my $devicefile = "/var/ipfire/extrahd/devices" ;
66 #workaround to suppress a warning when a variable is used only once
67 my @dummy = ( ${ Header
:: colourgreen
}, ${ Header
:: colourred
} );
70 & Header
:: showhttpheaders
();
72 ### Values that have to be initialized
73 $extrahdsettings { 'PATH' } = '' ;
74 $extrahdsettings { 'FS' } = '' ;
75 $extrahdsettings { 'DEVICE' } = '' ;
76 $extrahdsettings { 'ACTION' } = '' ;
77 $extrahdsettings { 'UUID' } = '' ;
79 & Header
:: getcgihash
( \
%extrahdsettings );
81 & Header
:: openpage
( 'ExtraHD' , 1 , '' );
82 & Header
:: openbigbox
( '100%' , 'left' , '' , $errormessage );
84 ############################################################################################################################
85 ############################################################################################################################
90 if ( $extrahdsettings { 'ACTION' } eq $Lang :: tr
{ 'add' }) {
91 # Check if a mount path has been given.
92 if ( not $extrahdsettings { 'PATH' }) {
93 $errormessage = " $Lang ::tr{'extrahd no mount point given'}." ;
95 # Check if a valid mount path has been choosen.
96 } elsif ( not & is_valid_dir
( " $extrahdsettings {'PATH'}" )) {
97 $errormessage = " $Lang ::tr{'extrahd you cant mount'} $extrahdsettings {'DEVICE'} $Lang ::tr{'extrahd to'} $extrahdsettings {'PATH'} $Lang ::tr{'extrahd because it is outside the allowed mount path'}." ;
99 # Check if the given path allready is mounted somewhere.
100 } elsif (& is_mounted
( " $extrahdsettings {'PATH'}" )) {
101 $errormessage = " $Lang ::tr{'extrahd you cant mount'} $extrahdsettings {'DEVICE'} $Lang ::tr{'extrahd to'} $extrahdsettings {'PATH'} $Lang ::tr{'extrahd because there is already a device mounted'}." ;
104 # Check against may previously configured drives.
105 unless ( $errormessage ) {
106 # Open device file for reading.
107 open ( FILE
, "< $devicefile " ) or die "Unable to read $devicefile " ;
108 my @devices = < FILE
>;
111 # Loop through the entries line-by-line.
112 foreach my $entry ( sort @devices ) {
113 # Split the line into pieces and assign nice variables.
114 my ( $uuid , $fs , $path ) = split ( /\;/ , $entry );
116 # Remove tailing UUID= from uuid string.
117 $uuid =~ s{^UUID=}{} ;
119 # Check if the path is allready used.
120 if ( " $extrahdsettings {'PATH'}" eq " $path " ) {
121 $errormessage = " $Lang ::tr{'extrahd you cant mount'} $extrahdsettings {'DEVICE'} $Lang ::tr{'extrahd to'} $extrahdsettings {'PATH'} $Lang ::tr{'extrahd because there is already a device mounted'}." ;
124 # Check if the uuid is allready used.
125 if ( " $extrahdsettings {'UUID'}" eq " $uuid " ) {
126 $errormessage = " $extrahdsettings {'DEVICE'} is allready mounted." ;
131 # Go further if there was no error message.
132 unless ( $errormessage ) {
133 # Re-open the device file for writing.
134 open ( FILE
, ">> $devicefile " ) or die "Unable to write $devicefile " ;
136 # Write the config line.
137 print FILE
"UUID= $extrahdsettings {'UUID'}; $extrahdsettings {'FS'}; $extrahdsettings {'PATH'}; \n " ;
142 # Call helper binary to mount the device.
143 & General
:: system ( "/usr/local/bin/extrahdctrl" , "mount" , " $extrahdsettings {'PATH'}" );
147 # Remove an existing one.
149 } elsif ( $extrahdsettings { 'ACTION' } eq $Lang :: tr
{ 'delete' }) {
150 # Call helper binary to unmount the device.
151 unless (& General
:: system ( "/usr/local/bin/extrahdctrl" , "umount" , " $extrahdsettings {'PATH'}" )) {
152 # Open the device file for reading.
153 open ( FILE
, "< $devicefile " ) or die "Unable to read $devicefile " ;
155 # Read the file content into a temporary array.
161 # Re-open device file for writing.
162 open ( FILE
, "> $devicefile " ) or die "Unable to write $devicefile " ;
164 # Loop through the previous read file content.
165 foreach my $line ( sort @tmp ) {
166 # Split line content and assign nice variables.
167 my ( $uuid , $fs , $path ) = split ( /\;/ , $line );
169 # Write the line in case it does not contain our element to delete.
170 if ( $path ne $extrahdsettings { 'PATH' }) {
178 $errormessage = " $Lang ::tr{'extrahd cant umount'} $extrahdsettings {'PATH'} $Lang ::tr{'extrahd maybe the device is in use'}?" ;
183 & Header
:: openbox
( '100%' , 'left' , $Lang :: tr
{ 'error messages' });
184 print "<class name='base'> $errormessage \n " ;
185 print " </class> \n " ;
189 ############################################################################################################################
190 ############################################################################################################################
192 & Header
:: openbox
( '100%' , 'center' , $Lang :: tr
{ 'extrahd detected drives' });
194 # Re-read mountpoints.
195 %mountpoints = & get_mountpoints
();
197 # Read-in the device config file.
198 open ( FILE
, "< $devicefile " ) or die "Unable to read $devicefile " ;
200 # Loop through the file content.
202 # Cut the line into pieces.
203 my ( $uuid , $fs , $path ) = split ( /\;/ , $_ );
205 # Add the found entry to the hash of configured drives.
206 $configured_drives { $uuid } = $path ;
209 # Close the file handle.
213 <table border='0' width='600' cellspacing="0">
216 foreach my $device ( sort @devices ) {
217 # Grab the device details.
218 my $vendor = & get_device_vendor
( $device );
219 my $model = & get_device_model
( $device );
220 my $bsize = & get_device_size
( $device );
222 # Convert size into human-readable format.
223 my $size = & General
:: formatBytes
( $bsize );
226 <tr><td colspan="5"> </td></tr>
227 <tr><td align='left' colspan="2"><b>/dev/ $device </b></td>
228 <td align='center' colspan="2"> $vendor $model </td>
230 <td align='center'> $Lang ::tr{'size'} $size </td>
232 <tr><td colspan="5"> </td></tr>
236 # Grab the known partitions of the current block device.
237 my @partitions = & get_device_partitions
( $device );
239 foreach my $partition ( @partitions ) {
242 # Omit the partition size.
243 my $bsize = & get_device_size
( $partition );
245 # Convert into human-readable format.
246 my $size = & General
:: formatBytes
( $bsize );
248 # Try to omit the used filesystem.
249 my $fs = $filesystems { $partition };
251 # Get the mountpoint.
252 my $mountpoint = $mountpoints { $partition };
254 # If no mountpoint could be determined try to grab from
256 unless ( $mountpoint ) {
257 my $uuid = $uuids { $partition };
260 $uuid = "UUID=" . $uuid ;
262 # Try to obtain a possible moutpoint from configured drives.
263 $mountpoint = $configured_drives { $uuid } if ( $configured_drives { $uuid });
266 # Check if the mountpoint is used as root or boot device.
267 if ( $mountpoint eq "/" or $mountpoint =~ "^/boot" ) {
268 $disabled = "disabled" ;
270 # Check if it is mounted.
271 } elsif (& is_mounted
( $mountpoint )) {
272 $disabled = "disabled" ;
274 # Check if the device is used as swap.
275 } elsif (& is_swap
( $partition )) {
276 $disabled = "disabled" ;
277 $mountpoint = "swap" ;
283 <form method='post' action=' $ENV {'SCRIPT_NAME'}'>
284 <tr><td align="left" colspan=5><strong>UUID= $uuids { $partition }</strong></td></tr>
286 <td align="list">/dev/ $partition </td>
287 <td align="center"> $Lang ::tr{'size'} $size </td>
288 <td align="center"> $fs </td>
289 <td align="center"><input type='text' name='PATH' value=' $mountpoint ' $disabled ></td>
291 <input type='hidden' name='DEVICE' value='/dev/ $partition ' />
292 <input type='hidden' name='UUID' value=' $uuids { $partition }' />
295 # Check if the mountpoint refers to a known configured drive.
296 if (& is_configured
( $mountpoint )) {
297 print "<input type='hidden' name='ACTION' value=' $Lang ::tr{'delete'}'> \n " ;
298 print "<input type='hidden' name='PATH' value=' $mountpoint '> \n " ;
300 # Check if the device is mounted properly.
301 if (& is_mounted
( $mountpoint )) {
302 print "<img src='/images/updbooster/updxl-led-green.gif' alt=' $Lang ::tr{'extrahd mounted'}' title=' $Lang ::tr{'extrahd mounted'}'> \n " ;
304 print "<img src='/images/updbooster/updxl-led-red.gif' alt=' $Lang ::tr{'extrahd not mounted'}' title=' $Lang ::tr{'extrahd not mounted'}'> \n " ;
307 print "<input type='image' alt=' $Lang ::tr{'delete'}' title=' $Lang ::tr{'delete'}' src='/images/delete.gif'> \n " ;
310 print "<input type='hidden' name='ACTION' value=' $Lang ::tr{'add'}'> \n " ;
311 print "<input type='hidden' name='FS' value='auto'> \n " ;
312 print "<img src='/images/updbooster/updxl-led-gray.gif' alt=' $Lang ::tr{'extrahd not configured'}' title=' $Lang ::tr{'extrahd not configured'}'> \n " ;
313 print "<input type='image' alt=' $Lang ::tr{'add'}' title=' $Lang ::tr{'add'}' src='/images/add.gif'> \n " ;
325 <tr><td align="center" colspan="5"> </td></tr>
326 <tr><td align="center" colspan="5"> </td></tr>
327 <tr><td align="center" colspan="5"> $Lang ::tr{'extrahd install or load driver'}</td></tr>
333 & Header
:: closebigbox
();
334 & Header
:: closepage
();
337 ## Function which return an array with all available block devices.
339 sub get_block_devices
() {
342 # Open directory from kernel sysfs.
343 opendir ( DEVICES
, "/sys/block" );
345 # Loop through the directory.
346 while ( readdir ( DEVICES
)) {
348 next if ( $_ =~ /^\.$/ );
349 next if ( $_ =~ /^\..$/ );
351 # Skip any loopback and ram devices.
352 next if ( $_ =~ "^loop" );
353 next if ( $_ =~ "^ram" );
355 # Add the device to the array of found devices.
359 # Close directory handle.
362 # Return the devices array.
367 ## Function which return all partitions of a given block device.
369 sub get_device_partitions
($) {
372 # Array to store the known partitions for the given
376 # Assign device directory.
377 my $device_dir = " $sysfs_block_dir / $device " ;
379 # Abort and return nothing if the device dir does not exist.
380 return unless (- d
" $device_dir " );
382 opendir ( DEVICE
, " $sysfs_block_dir / $device " );
383 while ( readdir ( DEVICE
)) {
384 next unless ( $_ =~ "^ $device " );
386 push ( @partitions , $_ );
391 @partitions = sort ( @partitions );
397 ## Returns the vendor of a given block device.
399 sub get_device_vendor
($) {
402 # Assign device directory.
403 my $device_dir = " $sysfs_block_dir / $device " ;
405 # Abort and return nothing if the device dir does not exist
406 # or no vendor file exists.
407 return unless (- d
" $device_dir " );
408 return unless (- f
" $device_dir /device/vendor" );
410 # Open and read-in the device vendor.
411 open ( VENDOR
, " $device_dir /device/vendor" );
412 my $vendor = < VENDOR
>;
415 # Abort and return nothing if no vendor could be read.
416 return unless ( $vendor );
418 # Remove any newlines from the vendor string.
421 # Return the omited vendor.
426 ## Returns the model name (string) of a given block device.
428 sub get_device_model
($) {
431 # Assign device directory.
432 my $device_dir = " $sysfs_block_dir / $device " ;
434 # Abort and return nothing if the device dir does not exist
435 # or no model file exists.
436 return unless (- d
" $device_dir " );
437 return unless (- f
" $device_dir /device/model" );
439 # Open and read-in the device model.
440 open ( MODEL
, " $device_dir /device/model" );
444 # Abort and return nothing if no model could be read.
445 return unless ( $model );
447 # Remove any newlines from the model string.
450 # Return the model string.
455 ## Returns the size of a given device in bytes.
457 sub get_device_size
($) {
460 # Assign device directory.
461 my $device_dir = " $sysfs_block_dir / $device " ;
463 # Abort and return nothing if the device dir does not exist
464 # or no size file exists.
465 return unless (- d
" $device_dir " );
466 return unless (- f
" $device_dir /size" );
468 # Open and read-in the device size.
469 open ( SIZE
, " $device_dir /size" );
473 # Abort and return nothing if the size could not be read.
474 return unless ( $size );
476 # Remove any newlines for the size string.
479 # The omited size only contains the amount of blocks from the
480 # given device. To convert this into bytes we have to multiply this
481 # value with 512 bytes for each block. This is a static value used by
485 # Return the size in bytes.
490 ## Function which returns all currently mounted devices as a hash.
491 ## example: "sda1" -> "/boot"
493 sub get_mountpoints
() {
496 # Open and read-in the current mounts from the
497 # kernel file system.
498 open ( MOUNT
, "/proc/mounts" );
500 # Loop through the known mounts.
502 # Skip mounts which does not belong to a device.
503 next unless ( $_ =~ "^/dev" );
505 # Cut the line into pieces and assign nice variables.
506 my ( $dev , $mpoint , $fs , $options , $a , $b ) = split ( / / , $_ );
508 # Split the device name.
509 my @tmp = split ( "/" , $dev );
511 # Assign the plain device name to a new variable.
512 # It is the last element of the array.
513 my $device = $tmp [- 1 ];
515 # Add the mountpoint to the hash of mountpoints.
516 $mounts { " $device " } = $mpoint ;
522 # Return the hash of known mountpoints.
529 # Open and read the swaps file.
530 open ( SWAP
, "/proc/swaps" );
532 # Loop though the file content.
534 # Skip lines which does not belong to a device.
535 next unless ( $_ =~ "^/dev" );
537 # Split the line and assign nice variables.
538 my ( $dev , $type , $size , $used , $prio ) = split ( / / , $_ );
540 # Cut the device line into pieces.
541 my @tmp = split ( "/" , $dev );
543 my $device = @tmp [- 1 ];
545 # Add the found swap to the array of swaps.
546 push ( @swaps , $device );
553 @swaps = sort ( @swaps );
560 ## Function with returns the mounted devices and the used filesystems as a hash.
561 ## Example: "sda1" -> "ext4"
563 sub get_mountedfs
() {
566 # Open and read the current mounts from the kernel
568 open ( MOUNT
, "/proc/mounts" );
570 # Loop through the known mounts.
572 # Skip mounts which does not belong to a device.
573 next unless ( $_ =~ "^/dev" );
575 # Split line and assign nice variables.
576 my ( $dev , $mpoint , $fs , $options , $a , $b ) = split ( / / , $_ );
578 # Cut the device line into pieces.
579 my @tmp = split ( "/" , $dev );
581 # Assign the plain device name to a variable
582 # It is the last element of the temporary array.
583 my $device = $tmp [- 1 ];
585 # Convert the filesystem into lower case format.
588 # Add the mounted file system.
589 $mountedfs { $device } = $fs ;
595 # Return the hash with the mounted filesystems.
600 ## Function which returns all known UUID's as a hash.
601 ## Example: "sda1" -> "1234-5678-abcd"
606 # Directory where the uuid mappings can be found.
607 my $uuid_dir = "/dev/disk/by-uuid" ;
609 # Open uuid directory and read-in the current known uuids.
610 opendir ( UUIDS
, " $uuid_dir " );
612 # Loop through the uuids.
613 foreach my $uuid ( readdir ( UUIDS
)) {
615 next if ( $uuid eq "." or $uuid eq ".." );
617 # Skip everything which is not a symbolic link.
618 next unless (- l
" $uuid_dir / $uuid " );
620 # Resolve the target of the symbolic link.
621 my $target = readlink ( " $uuid_dir / $uuid " );
623 # Split the link target into pieces.
624 my @tmp = split ( "/" , $target );
626 # Assign the last element of the array to the dev variable.
627 my $dev = " $tmp [-1]" ;
629 # Add the device and uuid to the hash of uuids.
630 $uuids { $dev } = $uuid ;
633 # Close directory handle.
636 # Return the hash of uuids.
641 ## Returns the device name of a given uuid.
643 sub device_by_uuid
($) {
646 # Reverse the main uuids hash.
647 my %uuids = reverse %uuids ;
649 # Lookup and return the device name.
650 return $uuids { $uuid };
654 ## Returns "True" in case a given path is a known mountpoint.
659 my %mountpoints = reverse %mountpoints ;
661 # Return "True" if the requested mountpoint is known and
663 return 1 if ( $mountpoints { $mpoint });
667 ## Returns "True" if a given mountpoint is a subdirectory of one
668 ## of the directories specified by the valid_mount_dirs array abouve.
670 sub is_valid_dir
($) {
673 # Do not allow "/mnt" or "/media" as mount points.
674 return if ( $mpoint eq "/mnt" );
675 return if ( $mpoint eq "/media" );
677 # Split the given mountpoint into pieces and store them
678 # in a temporay array.
679 my @tmp = split ( "/" , $mpoint );
681 # Exit and return nothing if the temporary array is empty.
684 # Build the root path based on the given mount point.
685 my $root_path = "/" . @tmp [ 1 ];
687 # Check if the root path is valid.
688 return 1 if ( grep /$root_path/ , @valid_mount_dirs );
692 # Returns "True" if a device is used as swap.
697 return 1 if ( grep /$device/ , @swaps );
701 ## Returns "True" if a drive is a configured one.
703 sub is_configured
($) {
706 # Loop through the hash of configured drives.
707 foreach my $uuid ( keys %configured_drives ) {
708 return 1 if ( $configured_drives { $uuid } eq " $path " );