1# mount-lib.pl 2# Functions for handling the /etc/[v]fstab file. Some functions are defined in 3# here, and some in OS-specific files named <os_type>-lib.pl 4 5BEGIN { push(@INC, ".."); }; 6use WebminCore; 7&init_config(); 8%access = &get_module_acl(); 9$filesystem_users_file = "$module_config_directory/filesystem-users"; 10@access_fs = split(/\s+/, $access{'fs'}); 11 12# get_mount(directory|'swap', device) 13# Returns the index of this mount, or -1 if not known 14sub get_mount 15{ 16local(@mlist, $p, $d, $i); 17@mlist = &list_mounts(); 18for($i=0; $i<@mlist; $i++) { 19 $p = $mlist[$i]; 20 if ($_[0] eq "*" && $p->[1] eq $_[1]) { 21 # found by match on device 22 return $i; 23 } 24 elsif ($_[1] eq "*" && $p->[0] eq $_[0]) { 25 # found by match on directory 26 return $i; 27 } 28 elsif ($p->[0] eq $_[0] && $p->[1] eq $_[1]) { 29 # found by match on both 30 return $i; 31 } 32 } 33return -1; 34} 35 36 37# get_mounted(directory|'swap', device) 38# Returns the index of this current mount, or -1 if not known 39sub get_mounted 40{ 41local(@mlist, $p, $d, $i); 42@mlist = &list_mounted(); 43for($i=0; $i<@mlist; $i++) { 44 $p = $mlist[$i]; 45 if ($_[0] eq "*" && $p->[1] eq $_[1]) { 46 # found by match on device 47 return $i; 48 } 49 elsif ($_[1] eq "*" && $p->[0] eq $_[0]) { 50 # found by match on directory 51 return $i; 52 } 53 elsif ($p->[0] eq $_[0] && $p->[1] eq $_[1]) { 54 # found by match on both 55 return $i; 56 } 57 } 58return -1; 59} 60 61 62# parse_options(type, options) 63# Convert an options string for some filesystem into the associative 64# array %options 65sub parse_options 66{ 67local($_); 68undef(%options); 69if ($_[1] ne "-") { 70 foreach (split(/,/, $_[1])) { 71 if (/^([^=]+)=(.*)$/) { $options{$1} = $2; } 72 else { $options{$_} = ""; } 73 } 74 } 75return \%options; 76} 77 78# join_options(type, [&hash]) 79# Returns a string constructed from the %options hash 80sub join_options 81{ 82local $h = $_[1] || \%options; 83local (@rv, $k); 84foreach $k (keys %$h) { 85 push(@rv, $h->{$k} eq "" ? $k : $k."=".$h->{$k}); 86 } 87return @rv ? join(",", @rv) : "-"; 88} 89 90# swap_form(path) 91# This function should be called by os-specific code to display a form 92# asking for the size of a swap file to create. The form will be submitted 93# to a creation program, and then redirected back to the original mount cgi 94sub swap_form 95{ 96local ($file) = @_; 97&ui_print_header(undef, "Create Swap File", ""); 98print &ui_form_start("create_swap.cgi"); 99foreach my $k (keys %in) { 100 print &ui_hidden($k, $in{$k}); 101 } 102print &ui_hidden("cswap_file", $file); 103print &text('cswap_file', "<tt>$file</tt>"),"<p>\n"; 104print $text{'cswap_size'},"\n"; 105print &ui_textbox("cswap_size", undef, 6)," ", 106 &ui_select("cswap_units", "m", 107 [ [ "m", "MB" ], [ "g", "GB" ], [ "t", "TB" ] ])."\n"; 108print &ui_form_end([ [ undef, $text{'create'} ] ]); 109&ui_print_footer("", $text{'index_return'}); 110exit; 111} 112 113# nfs_server_chooser_button(input, [form]) 114sub nfs_server_chooser_button 115{ 116local($form); 117$form = @_ > 1 ? $_[1] : 0; 118if ($access{'browse'}) { 119 return "<input type=button onClick='ifield = document.forms[$form].$_[0]; nfs_server = window.open(\"../$module_name/nfs_server.cgi\", \"nfs_server\", \"toolbar=no,menubar=no,scrollbars=yes,width=400,height=300\"); nfs_server.ifield = ifield; window.ifield = ifield' value=\"...\">\n"; 120 } 121return undef; 122} 123 124# nfs_export_chooser_button(serverinput, exportinput, [form]) 125sub nfs_export_chooser_button 126{ 127local($form); 128$form = @_ > 2 ? $_[2] : 0; 129if ($access{'browse'}) { 130 return "<input type=button onClick='if (document.forms[$form].$_[0].value != \"\") { ifield = document.forms[$form].$_[1]; nfs_export = window.open(\"../$module_name/nfs_export.cgi?server=\"+document.forms[$form].$_[0].value, \"nfs_export\", \"toolbar=no,menubar=no,scrollbars=yes,width=500,height=200\"); nfs_export.ifield = ifield; window.ifield = ifield }' value=\"...\">\n"; 131 } 132return undef; 133} 134 135# smb_server_chooser_button(serverinput, [form]) 136sub smb_server_chooser_button 137{ 138local($form); 139$form = @_ > 1 ? $_[1] : 0; 140if (&has_command($config{'smbclient_path'}) && $access{'browse'}) { 141 return "<input type=button onClick='ifield = document.forms[$form].$_[0]; smb_server = window.open(\"../$module_name/smb_server.cgi\", \"smb_server\", \"toolbar=no,menubar=no,scrollbars=yes,width=400,height=300\"); smb_server.ifield = ifield; window.ifield = ifield' value=\"...\">\n"; 142 } 143return undef; 144} 145 146# smb_share_chooser_button(serverinput, shareinput, [form]) 147sub smb_share_chooser_button 148{ 149local($form); 150$form = @_ > 2 ? $_[2] : 0; 151if (&has_command($config{'smbclient_path'}) && $access{'browse'}) { 152 return "<input type=button onClick='if (document.forms[$form].$_[0].value != \"\") { ifield = document.forms[$form].$_[1]; smb_share = window.open(\"../$module_name/smb_share.cgi?server=\"+document.forms[$form].$_[0].value, \"smb_share\", \"toolbar=no,menubar=no,scrollbars=yes,width=400,height=300\"); smb_share.ifield = ifield; window.ifield = ifield }' value=\"...\">\n"; 153 } 154return undef; 155} 156 157# Include the correct OS-specific functions file 158if ($gconfig{'os_type'} =~ /^\S+\-linux$/) { 159 do "linux-lib.pl"; 160 } 161else { 162 do "$gconfig{'os_type'}-lib.pl"; 163 } 164 165# can_edit_fs(dir, device, type, options, [is-new]) 166# Returns 1 if a filesystem can be edited, 0 otherwise 167sub can_edit_fs 168{ 169local $ok = 1; 170if (@access_fs) { 171 local $d; 172 foreach $d (@access_fs) { 173 $ok = 0 if (!&is_under_directory($d, $_[0])); 174 } 175 } 176$ok = 0 if (!&can_fstype($_[2])); 177if ($access{'user'} && !$_[4]) { 178 local $users = &get_filesystem_users(); 179 $ok = 0 if ($users->{$_[0]} ne $remote_user); 180 } 181return $ok; 182} 183 184# can_fstype(type) 185sub can_fstype 186{ 187return 1 if (!$access{'types'}); 188local @types = split(/\s+/, $access{'types'}); 189return &indexof($_[0], @types) >= 0; 190} 191 192# compile_program(name, default-arch) 193# Ensures that some C program is compiled and copied to /etc/webmin . Uses the 194# supplied native versions if possible 195sub compile_program 196{ 197return if (-r "$module_config_directory/$_[0]" && 198 &execute_command("$module_config_directory/$_[0]", undef, undef, undef, 0, 1) == 0); 199local $arch = &backquote_command("uname -m"); 200$arch =~ s/\r|\n//g; 201local $re = $_[1]; 202if ($re && $arch =~ /^$re$/i && -r "$module_root_directory/$_[0]" && 203 &execute_command("$module_root_directory/$_[0]", undef, undef, undef, 0, 1) == 0) { 204 # Compiled program for this architecture already exists and is working, 205 # so can just copy 206 &execute_command("cp $module_root_directory/$_[0] $module_config_directory/$_[0]"); 207 } 208else { 209 # Need to compile 210 local ($cc) = (&has_command("gcc") || &has_command("cc") || 211 &has_command("gcc-4.0") || &has_command("gcc-3.3")); 212 $cc || &error($text{'egcc'}); 213 local $out = &backquote_logged("$cc -o $module_config_directory/$_[0] $module_root_directory/$_[0].c 2>&1"); 214 if ($?) { 215 &error(&text('ecompile', "<pre>$out</pre>")); 216 } 217 } 218&set_ownership_permissions(undef, undef, 0755,"$module_config_directory/$_[0]"); 219} 220 221# get_filesystem_users() 222# Returns a mapping between filesystems and their owners 223sub get_filesystem_users 224{ 225local %users; 226&read_file($filesystem_users_file, \%users); 227return \%users; 228} 229 230# save_filesystem_users(&usermap) 231# Saves the filesystem owner mapping 232sub save_filesystem_users 233{ 234&write_file($filesystem_users_file, $_[0]); 235} 236 237# can_delete_directory(mount-point) 238# Returns 1 if some directory should be deleted when un-mounting 239sub can_delete_directory 240{ 241local @dirs = split(/\s+/, $config{'delete_under'}); 242return 0 if (!@dirs); 243return 0 if ($_[0] eq "swap"); 244local $d; 245foreach $d (@dirs) { 246 return 1 if (&is_under_directory($d, $_[0])); 247 } 248return 0; 249} 250 251# delete_unmounted(dir, device) 252# If some directory is no longer in the permanent mount list, delete it if it 253# is under the list of dirs to auto-delete 254sub delete_unmounted 255{ 256if (&can_delete_directory($_[0]) && 257 &get_mount($_[0], $_[1]) < 0) { 258 &system_logged("rmdir ".quotemeta($_[0])); 259 } 260} 261 262# remount_dir(directory, device, type, options) 263# Adjusts the options for some mounted filesystem 264sub remount_dir 265{ 266if (defined(&os_remount_dir)) { 267 return &os_remount_dir(@_); 268 } 269else { 270 local $err = &unmount_dir(@_); 271 return $err if ($err); 272 return &mount_dir(@_); 273 } 274} 275 276# filesystem_for_dir(dir) 277# Give a directory, returns the details filesystem it is on (dir, device, 278# type, options) 279sub filesystem_for_dir 280{ 281local @stdir = stat($_[0]); 282foreach my $m (&list_mounted()) { 283 local @stm = stat($m->[0]); 284 if ($stm[0] == $stdir[0]) { 285 # Save device number! 286 return @$m; 287 } 288 } 289return ( ); 290} 291 292# local_disk_space([&always-count]) 293# Returns the total local and free disk space on the system, plus a list of 294# per-filesystem total and free 295sub local_disk_space 296{ 297my ($always) = @_; 298my ($total, $free) = (0, 0); 299my @fs; 300my @mounted = &mount::list_mounted(); 301my %donezone; 302my %donevzfs; 303my %donedevice; 304my %donedevno; 305 306# Get list of zone pools 307my %zpools = ( 'zones' => 1, 'zroot' => 1 ); 308if (&has_command("zpool")) { 309 foreach my $flag ("-P", "-p") { 310 my @out = &backquote_command("zpool list $flag 2>/dev/null"); 311 foreach my $l (@out) { 312 if (/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/) { 313 $zpools{$1} = [ $2 / 1024, $4 / 1024 ]; 314 } 315 } 316 } 317 } 318 319# Add up all local filesystems 320foreach my $m (@mounted) { 321 if ($m->[2] =~ /^ext/ || 322 $m->[2] eq "reiserfs" || $m->[2] eq "ufs" || $m->[2] eq "f2fs" || 323 $m->[2] eq "zfs" || $m->[2] eq "simfs" || $m->[2] eq "vzfs" || 324 $m->[2] eq "xfs" || $m->[2] eq "jfs" || $m->[2] eq "btrfs" || 325 $m->[1] =~ /^\/dev\// || 326 &indexof($m->[1], @$always) >= 0) { 327 my $zp; 328 if ($m->[1] =~ /^([^\/]+)(\/(\S+))?/ && 329 $m->[2] eq "zfs" && $zpools{$1}) { 330 # Don't double-count maps from the same zone pool 331 next if ($donezone{$1}++); 332 $zp = $zpools{$1}; 333 } 334 if ($donedevice{$m->[0]}++ || 335 $donedevice{$m->[1]}++) { 336 # Don't double-count mounts from the same device, or 337 # on the same directory. 338 next; 339 } 340 my @st = stat($m->[0]); 341 if (@st && $donedevno{$st[0]}++) { 342 # Don't double-count same filesystem by device number 343 next; 344 } 345 if ($m->[1] eq "/dev/fuse") { 346 # Skip fuse user-space filesystem mounts 347 next; 348 } 349 if ($m->[2] eq "swap") { 350 # Skip virtual memory 351 next; 352 } 353 if ($m->[2] eq "squashfs") { 354 # Skip /snap mounts 355 next; 356 } 357 if ($m->[1] =~ /^\/dev\/sr/) { 358 # Skip CDs 359 next; 360 } 361 # Get the size - for ZFS mounts, this comes from the underlying 362 # total pool size and free 363 my ($t, $f); 364 if ($zp) { 365 ($t, $f) = @$zp; 366 } 367 else { 368 ($t, $f) = &disk_space($m->[2], $m->[0]); 369 } 370 if (($m->[2] eq "simfs" || $m->[2] eq "vzfs" || 371 $m->[0] eq "/dev/vzfs" || 372 $m->[0] eq "/dev/simfs") && 373 $donevzfs{$t,$f}++) { 374 # Don't double-count VPS filesystems 375 next; 376 } 377 $total += $t*1024; 378 $free += $f*1024; 379 my ($it, $if); 380 if (defined(&inode_space)) { 381 ($it, $if) = &inode_space($m->[2], $m->[0]); 382 } 383 push(@fs, { 'total' => $t*1024, 384 'free' => $f*1024, 385 'itotal' => $it, 386 'ifree' => $if, 387 'dir' => $m->[0], 388 'device' => $m->[1], 389 'type' => $m->[2] }); 390 } 391 } 392return ($total, $free, \@fs); 393} 394 3951; 396 397