]> git.ipfire.org Git - ipfire-2.x.git/blob - html/cgi-bin/extrahd.cgi
extrahd.cgi: Refactor code to use new introduced perl functions.
[ipfire-2.x.git] / html / cgi-bin / extrahd.cgi
1 #!/usr/bin/perl
2 ###############################################################################
3 # #
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2011 IPFire Team <info@ipfire.org> #
6 # #
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. #
11 # #
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. #
16 # #
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/>. #
19 # #
20 ###############################################################################
21
22 use strict;
23 # enable only the following on debugging purpose
24 #use warnings;
25 #use CGI::Carp 'fatalsToBrowser';
26
27 require '/var/ipfire/general-functions.pl';
28 require "${General::swroot}/lang.pl";
29 require "${General::swroot}/header.pl";
30
31 my %extrahdsettings = ();
32 my $errormessage = "";
33
34 # SYSFS directory which contains all block device data.
35 my $sysfs_block_dir = "/sys/class/block";
36
37 # Array which contains the valid mount directories.
38 # Only mounting to subdirectories inside them is allowed.
39 my @valid_mount_dirs = (
40 "/data",
41 "/media",
42 "/mnt",
43 );
44
45 # Array which contains the supported file systems.
46 my @supported_filesystems = (
47 "auto",
48 "ext3",
49 "ext4",
50 "xfs",
51 "vfat",
52 "ntfs-3g"
53 );
54
55 # Grab all available block devices.
56 my @devices = &get_block_devices();
57
58 # Grab all known UUID's.
59 my %uuids = &get_uuids();
60
61 # Grab all mountpoints.
62 my %mountpoints = &get_mountpoints();
63
64 # Omit the file system types of the mounted devices.
65 my %filesystems = &get_mountedfs();
66
67 # Gather all used swap devices.
68 my @swaps = &get_swaps();
69
70 # The config file which contains the configured devices.
71 my $devicefile = "/var/ipfire/extrahd/devices";
72
73 #workaround to suppress a warning when a variable is used only once
74 my @dummy = ( ${Header::colourgreen}, ${Header::colourred} );
75 undef (@dummy);
76
77 &Header::showhttpheaders();
78
79 ### Values that have to be initialized
80 $extrahdsettings{'PATH'} = '';
81 $extrahdsettings{'FS'} = '';
82 $extrahdsettings{'DEVICE'} = '';
83 $extrahdsettings{'ACTION'} = '';
84 $extrahdsettings{'UUID'} = '';
85
86 &Header::getcgihash(\%extrahdsettings);
87
88 &Header::openpage('ExtraHD', 1, '');
89 &Header::openbigbox('100%', 'left', '', $errormessage);
90
91 ############################################################################################################################
92 ############################################################################################################################
93
94 #
95 ## Add a new device.
96 #
97 if ($extrahdsettings{'ACTION'} eq $Lang::tr{'add'}) {
98 # Open device file for reading.
99 open( FILE, "< $devicefile" ) or die "Unable to read $devicefile";
100 my @devices = <FILE>;
101 close FILE;
102
103 # Loop through the entries line-by-line.
104 foreach my $entry (sort @devices) {
105 # Split the line into pieces and assign nice variables.
106 my ($uuid, $fs, $path) = split( /\;/, $entry );
107
108 # Check if the path is allready used.
109 if ( "$extrahdsettings{'PATH'}" eq "$path" ) {
110 $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'}.";
111 }
112
113 # Check if the uuid is allready used.
114 if ("$extrahdsettings{'DEVICE'} eq $uuid") {
115 $errormessage = "$extrahdsettings{'DEVICE'} is allready mounted.";
116 }
117 }
118
119 # Check if a valid mount path has been choosen.
120 unless(&is_valid_dir("$extrahdsettings{'PATH'}")) {
121 $errormessage = "$Lang::tr{'extrahd you cant mount'} $extrahdsettings{'DEVICE'} $Lang::tr{'extrahd to root'}.";
122 }
123
124 # Check if the given path allready is mounted somewhere.
125 if(&is_mounted("$extrahdsettings{'PATH'}")) {
126 $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'}.";
127 }
128
129 # Check if there was an error message.
130 unless($errormessage) {
131 # Re-open the device file for writing.
132 open(FILE, ">> $devicefile" ) or die "Unable to write $devicefile";
133
134 # Write the config line.
135 print FILE "UUID=$extrahdsettings{'UUID'};$extrahdsettings{'FS'};$extrahdsettings{'PATH'};\n";
136
137 # Close file handle.
138 close(FILE);
139
140 # Call helper binary to mount the device.
141 &General::system("/usr/local/bin/extrahdctrl", "mount", "$extrahdsettings{'PATH'}");
142 }
143
144 #
145 # Remove an existing one.
146 #
147 } elsif ($extrahdsettings{'ACTION'} eq $Lang::tr{'delete'}) {
148 # Call helper binary to unmount the device.
149 &General::system("/usr/local/bin/extrahdctrl", "umount", "$extrahdsettings{'PATH'}");
150
151 # Open the device file for reading.
152 open(FILE, "< $devicefile" ) or die "Unable to read $devicefile";
153
154 # Read the file content into a temporary array.
155 my @tmp = <FILE>;
156
157 # Close file handle.
158 close(FILE);
159
160 # Re-open device file for writing.
161 open(FILE, "> $devicefile" ) or die "Unable to write $devicefile";
162
163 # Loop through the previous read file content.
164 foreach my $line (sort @tmp) {
165 # Split line content and assign nice variables.
166 my ($uuid, $fs, $path) = split( /\;/, $line );
167
168 # Write the line in case it does not contain our element to delete.
169 if ($path ne $extrahdsettings{'PATH'}) {
170 print FILE "$line";
171 }
172 }
173
174 # Close file handle.
175 close(FILE);
176 }
177
178 if ($errormessage) {
179 &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
180 print "<class name='base'>$errormessage\n";
181 print "&nbsp;</class>\n";
182 &Header::closebox();
183 }
184
185 ############################################################################################################################
186 ############################################################################################################################
187
188 print <<END
189 <table border='0' width='600' cellspacing="0">
190 END
191 ;
192 # Re-read mountpoints.
193 %mountpoints = &get_mountpoints();
194
195 # Read-in the device config file.
196 open( FILE, "< $devicefile" ) or die "Unable to read $devicefile";
197 my @configfile = <FILE>;
198 close FILE;
199
200 # Loop through the file content.
201 foreach my $entry (sort @configfile) {
202 my ($uuid, $fs, $path) = split( /\;/, $entry );
203 my $color="$Header::colourred";
204
205 # Check if the device is currently mounted.
206 if (&is_mounted($path)) {
207 $color=$Header::colourgreen;
208 }
209
210 print <<END
211 <tr><td colspan="4">&nbsp;</td></tr>
212 <tr><td align='left'><font color=$color><b>$uuid</b></font></td>
213 <td align='left'>$fs</td>
214 <td align='left'>$path</td>
215 <td align='center'>
216 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
217 <input type='hidden' name='DEVICE' value='$uuid' />
218 <input type='hidden' name='FS' value='$fs' />
219 <input type='hidden' name='PATH' value='$path' />
220 <input type='hidden' name='ACTION' value='$Lang::tr{'delete'}' />
221 <input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/delete.gif' />
222 </form></td></tr>
223 END
224 ;
225 }
226 print <<END
227 </table>
228 END
229 ;
230
231 &Header::openbox('100%', 'center', $Lang::tr{'extrahd detected drives'});
232 print <<END
233 <table border='0' width='600' cellspacing="0">
234 END
235 ;
236 foreach my $device (sort @devices) {
237 # Grab the device details.
238 my $vendor = &get_device_vendor($device);
239 my $model = &get_device_model($device);
240 my $bsize = &get_device_size($device);
241
242 # Convert size into human-readable format.
243 my $size = &General::formatBytes($bsize);
244
245 print <<END
246 <tr><td colspan="5">&nbsp;</td></tr>
247 <tr><td align='left' colspan="2"><b>/dev/$device</b></td>
248 <td align='center' colspan="2">$vendor $model</td>
249
250 <td align='center'>$Lang::tr{'size'} $size</td>
251 <td>&nbsp;</td></tr>
252 <tr><td colspan="5">&nbsp;</td></tr>
253 END
254 ;
255
256 # Grab the known partitions of the current block device.
257 my @partitions = &get_device_partitions($device);
258
259 foreach my $partition (@partitions) {
260 my $disabled;
261
262 # Omit the partition size.
263 my $bsize = &get_device_size($partition);
264
265 # Convert into human-readable format.
266 my $size = &General::formatBytes($bsize);
267
268 # Get the mountpoint.
269 my $mountpoint = $mountpoints{$partition};
270
271 if ($mountpoint eq "/" or $mountpoint =~ "^/boot") {
272 $disabled = "disabled";
273 } elsif(&is_mounted($mountpoint)) {
274 $disabled = "disabled";
275 }
276
277 # Omit the used filesystem.
278 my $fs = $filesystems{$partition};
279
280 # Check if the device is used as swap.
281 if (&is_swap($partition)) {
282 $disabled = "disabled";
283 $mountpoint = "swap";
284 }
285
286 print <<END
287
288 <form method='post' action='$ENV{'SCRIPT_NAME'}'>
289 <tr><td align="left" colspan=5><strong>UUID=$uuids{$partition}</strong></td></tr>
290 <tr>
291 <td align="list">/dev/$partition</td>
292 <td align="center">$Lang::tr{'size'} $size</td>
293 <td align="center"><select name="FS" $disabled>
294 END
295 ;
296 # Loop through the array of supported filesystems.
297 foreach my $filesystem (@supported_filesystems) {
298 my $selected;
299
300 # Mark the used filesystem as selected.
301 if ($filesystem eq $fs) {
302 $selected = "selected";
303 }
304
305 print "<option value='$filesystem' $selected>$filesystem</option>\n";
306 }
307
308 print <<END
309 </select></td>
310 <td align="center"><input type='text' name='PATH' value=$mountpoint $disabled></td>
311 <td align="center">
312 <input type='hidden' name='DEVICE' value='/dev/$partition' />
313 <input type='hidden' name='UUID' value='$uuids{$partition}' />
314 <input type='hidden' name='ACTION' value='$Lang::tr{'add'}' />
315 END
316 ; unless($disabled) {
317 print"<input type='image' alt='$Lang::tr{'add'}' title='$Lang::tr{'add'}' src='/images/add.gif' />\n";
318 }
319
320 print <<END
321 </form></td></tr>
322 END
323 ; }
324
325 }
326
327 print <<END
328 <tr><td align="center" colspan="5">&nbsp;</td></tr>
329 <tr><td align="center" colspan="5">&nbsp;</td></tr>
330 <tr><td align="center" colspan="5">$Lang::tr{'extrahd install or load driver'}</td></tr>
331 </table>
332 END
333 ;
334 &Header::closebox();
335
336 &Header::closebigbox();
337 &Header::closepage();
338
339 #
340 ## Function which return an array with all available block devices.
341 #
342 sub get_block_devices () {
343 my @devices;
344
345 # Open directory from kernel sysfs.
346 opendir(DEVICES, "/sys/block");
347
348 # Loop through the directory.
349 while(readdir(DEVICES)) {
350 # Skip . and ..
351 next if($_ =~ /^\.$/);
352 next if($_ =~ /^\..$/);
353
354 # Skip any loopback and ram devices.
355 next if($_ =~ "^loop");
356 next if($_ =~ "^ram");
357
358 # Add the device to the array of found devices.
359 push(@devices, $_);
360 }
361
362 # Close directory handle.
363 closedir(DEVICES);
364
365 # Return the devices array.
366 return @devices;
367 }
368
369 #
370 ## Function which return all partitions of a given block device.
371 #
372 sub get_device_partitions ($) {
373 my ($device) = @_;
374
375 # Array to store the known partitions for the given
376 # device.
377 my @partitions;
378
379 # Assign device directory.
380 my $device_dir = "$sysfs_block_dir/$device";
381
382 # Abort and return nothing if the device dir does not exist.
383 return unless(-d "$device_dir");
384
385 opendir(DEVICE, "$sysfs_block_dir/$device");
386 while(readdir(DEVICE)) {
387 next unless($_ =~ "^$device");
388
389 push(@partitions, $_);
390 }
391
392 closedir(DEVICE);
393
394 @partitions = sort(@partitions);
395
396 return @partitions;
397 }
398
399 #
400 ## Returns the vendor of a given block device.
401 #
402 sub get_device_vendor ($) {
403 my ($device) = @_;
404
405 # Assign device directory.
406 my $device_dir = "$sysfs_block_dir/$device";
407
408 # Abort and return nothing if the device dir does not exist
409 # or no vendor file exists.
410 return unless(-d "$device_dir");
411 return unless(-f "$device_dir/device/vendor");
412
413 # Open and read-in the device vendor.
414 open(VENDOR, "$device_dir/device/vendor");
415 my $vendor = <VENDOR>;
416 close(VENDOR);
417
418 # Abort and return nothing if no vendor could be read.
419 return unless($vendor);
420
421 # Remove any newlines from the vendor string.
422 chomp($vendor);
423
424 # Return the omited vendor.
425 return $vendor;
426 }
427
428 #
429 ## Returns the model name (string) of a given block device.
430 #
431 sub get_device_model ($) {
432 my ($device) = @_;
433
434 # Assign device directory.
435 my $device_dir = "$sysfs_block_dir/$device";
436
437 # Abort and return nothing if the device dir does not exist
438 # or no model file exists.
439 return unless(-d "$device_dir");
440 return unless(-f "$device_dir/device/model");
441
442 # Open and read-in the device model.
443 open(MODEL, "$device_dir/device/model");
444 my $model = <MODEL>;
445 close(MODEL);
446
447 # Abort and return nothing if no model could be read.
448 return unless($model);
449
450 # Remove any newlines from the model string.
451 chomp($model);
452
453 # Return the model string.
454 return $model;
455 }
456
457 #
458 ## Returns the size of a given device in bytes.
459 #
460 sub get_device_size ($) {
461 my ($device) = @_;
462
463 # Assign device directory.
464 my $device_dir = "$sysfs_block_dir/$device";
465
466 # Abort and return nothing if the device dir does not exist
467 # or no size file exists.
468 return unless(-d "$device_dir");
469 return unless(-f "$device_dir/size");
470
471 # Open and read-in the device size.
472 open(SIZE, "$device_dir/size");
473 my $size = <SIZE>;
474 close(SIZE);
475
476 # Abort and return nothing if the size could not be read.
477 return unless($size);
478
479 # Remove any newlines for the size string.
480 chomp($size);
481
482 # The omited size only contains the amount of blocks from the
483 # given device. To convert this into bytes we have to multiply this
484 # value with 512 bytes for each block. This is a static value used by
485 # the linux kernel.
486 $size = $size * 512;
487
488 # Return the size in bytes.
489 return $size;
490 }
491
492 #
493 ## Function which returns all currently mounted devices as a hash.
494 ## example: "sda1" -> "/boot"
495 #
496 sub get_mountpoints () {
497 my %mounts;
498
499 # Open and read-in the current mounts from the
500 # kernel file system.
501 open(MOUNT, "/proc/mounts");
502
503 # Loop through the known mounts.
504 while(<MOUNT>) {
505 # Skip mounts which does not belong to a device.
506 next unless ($_ =~ "^/dev");
507
508 # Cut the line into pieces and assign nice variables.
509 my ($dev, $mpoint, $fs, $options, $a, $b) = split(/ /, $_);
510
511 # Split the device name.
512 my @tmp = split("/", $dev);
513
514 # Assign the plain device name to a new variable.
515 # It is the last element of the array.
516 my $device = $tmp[-1];
517
518 # Add the mountpoint to the hash of mountpoints.
519 $mounts{"$device"} = $mpoint;
520 }
521
522 # Close file handle.
523 close(MOUNT);
524
525 # Return the hash of known mountpoints.
526 return %mounts;
527 }
528
529 sub get_swaps () {
530 my @swaps;
531
532 # Open and read the swaps file.
533 open(SWAP, "/proc/swaps");
534
535 # Loop though the file content.
536 while(<SWAP>) {
537 # Skip lines which does not belong to a device.
538 next unless ($_ =~ "^/dev");
539
540 # Split the line and assign nice variables.
541 my ($dev, $type, $size, $used, $prio) = split(/ /, $_);
542
543 # Cut the device line into pieces.
544 my @tmp = split("/", $dev);
545
546 my $device = @tmp[-1];
547
548 # Add the found swap to the array of swaps.
549 push(@swaps, $device);
550 }
551
552 # Close file handle.
553 close(SWAP);
554
555 # Sort the array.
556 @swaps = sort(@swaps);
557
558 # Return the array.
559 return @swaps;
560 }
561
562 #
563 ## Function with returns the mounted devices and the used filesystems as a hash.
564 ## Example: "sda1" -> "ext4"
565 #
566 sub get_mountedfs () {
567 my %mountedfs;
568
569 # Open and read the current mounts from the kernel
570 # file system.
571 open(MOUNT, "/proc/mounts");
572
573 # Loop through the known mounts.
574 while(<MOUNT>) {
575 # Skip mounts which does not belong to a device.
576 next unless ($_ =~ "^/dev");
577
578 # Split line and assign nice variables.
579 my ($dev, $mpoint, $fs, $options, $a, $b) = split(/ /, $_);
580
581 # Cut the device line into pieces.
582 my @tmp = split("/", $dev);
583
584 # Assign the plain device name to a variable
585 # It is the last element of the temporary array.
586 my $device = $tmp[-1];
587
588 # Convert the filesystem into lower case format.
589 $fs = lc($fs);
590
591 # Add the mounted file system.
592 $mountedfs{$device} = $fs;
593 }
594
595 # Close file handle.
596 close(MOUNT);
597
598 # Return the hash with the mounted filesystems.
599 return %mountedfs;
600 }
601
602 #
603 ## Function which returns all known UUID's as a hash.
604 ## Example: "sda1" -> "1234-5678-abcd"
605 #
606 sub get_uuids () {
607 my %uuids;
608
609 # Directory where the uuid mappings can be found.
610 my $uuid_dir = "/dev/disk/by-uuid";
611
612 # Open uuid directory and read-in the current known uuids.
613 opendir(UUIDS, "$uuid_dir");
614
615 # Loop through the uuids.
616 foreach my $uuid (readdir(UUIDS)) {
617 # Skip . and ..
618 next if($uuid eq "." or $uuid eq "..");
619
620 # Skip everything which is not a symbolic link.
621 next unless(-l "$uuid_dir/$uuid");
622
623 # Resolve the target of the symbolic link.
624 my $target = readlink("$uuid_dir/$uuid");
625
626 # Split the link target into pieces.
627 my @tmp = split("/", $target);
628
629 # Assign the last element of the array to the dev variable.
630 my $dev = "$tmp[-1]";
631
632 # Add the device and uuid to the hash of uuids.
633 $uuids{$dev} = $uuid;
634 }
635
636 # Close directory handle.
637 closedir(UUIDS);
638
639 # Return the hash of uuids.
640 return %uuids;
641 }
642
643 #
644 ## Returns the device name of a given uuid.
645 #
646 sub device_by_uuid ($) {
647 my ($uuid) = @_;
648
649 # Reverse the main uuids hash.
650 my %uuids = reverse %uuids;
651
652 # Lookup and return the device name.
653 return $uuids{$uuid};
654 }
655
656 #
657 ## Returns "True" in case a given path is a known mountpoint.
658 #
659 sub is_mounted ($) {
660 my ($mpoint) = @_;
661
662 my %mountpoints = reverse %mountpoints;
663
664 # Return "True" if the requested mountpoint is known and
665 # therefore mounted.
666 return 1 if($mountpoints{$mpoint});
667 }
668
669 #
670 ## Returns "True" if a given mountpoint is a subdirectory of one
671 ## of the directories specified by the valid_mount_dirs array abouve.
672 #
673 sub is_valid_dir ($) {
674 my ($mpoint) = @_;
675
676 # Split the given mountpoint into pieces and store them
677 # in a temporay array.
678 my @tmp = split("/", $mpoint);
679
680 # Exit and return nothing if the temporary array is empty.
681 return unless(@tmp);
682
683 # Build the root path based on the given mount point.
684 my $root_path = "/" . @tmp[1];
685
686 # Check if the root path is valid.
687 return 1 if(grep /$root_path/, @valid_mount_dirs);
688 }
689
690 #
691 # Returns "True" if a device is used as swap.
692 #
693 sub is_swap ($) {
694 my ($device) = @_;
695
696 return 1 if(grep /$device/, @swaps);
697 }