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