Handle hostnames with upper-case letters
[webmin.git] / exports / exports-lib.pl
1 # export-lib.pl
2 # Common functions for the linux exports file
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 %access = &get_module_acl();
8 &foreign_require("mount", "mount-lib.pl");
9
10 # list_exports()
11 # Returns a list of all exports
12 sub list_exports
13 {
14 local (@rv, $pos, $lnum, $h, $o, $line);
15 return @list_exports_cache if (@list_exports_cache);
16 open(EXP, $config{'exports_file'});
17 $lnum = 0;
18 while($line = <EXP>) {
19         local $slnum = $lnum;
20         $line =~ s/\s+$//g;
21         while($line =~ /\\$/) {
22                 # continuation character!
23                 $line =~ s/\\$//;
24                 $line .= <EXP>;
25                 $line =~ s/\s+$//g;
26                 $lnum++;
27                 }
28         if ($line =~ /^(#*)\s*(\/\S*)\s+(.*)$/) {
29                 local $active = !$1;
30                 local $dir = $2;
31                 local $rest = $3;
32                 if ($dir =~ /^$config{'exports_file'}/) {
33                         $lnum++;
34                         next;
35                         }
36                 $pos = 0;
37                 while($rest =~ /^([^\s+\(\)]*)\(([^\)]*)\)\s*(.*)$/ ||
38                       $rest =~ /^([^\s+\(\)]+)\s*()(.*)$/) {
39                         local %exp;
40                         $exp{'active'} = $active;
41                         $exp{'dir'} = $dir;
42                         $exp{'host'} = $1;
43                         local $ostr = $2;
44                         $rest = $3;
45                         while($ostr =~ /^([a-z_]+)=([0-9,\-]+)\s*,\s*(.*)$/ ||
46                               $ostr =~ /^([a-z_]+)=([0-9,\-]+)(.*)$/ ||
47                               $ostr =~ /^([a-z_]+)=([^,\s]+),(.*)$/ ||
48                               $ostr =~ /^([a-z_]+)=([^,\s]+)(.*)$/ ||
49                               $ostr =~ /^([a-z_]+)()\s*,\s*(.*)$/ ||
50                               $ostr =~ /^([a-z_]+)()(.*)$/) {
51                                 if ($2 ne "") { $exp{'options'}->{$1} = $2; }
52                                 else { $exp{'options'}->{$1} = ""; }
53                                 $ostr = $3;
54                                 }
55                         $exp{'line'} = $slnum;
56                         $exp{'eline'} = $lnum;
57                         $exp{'pos'} = $pos++;
58                         $exp{'index'} = scalar(@rv);
59                         push(@rv, \%exp);
60                         }
61                 }
62         $lnum++;
63         }
64 close(EXP);
65 @list_exports_cache = @rv;
66 return @list_exports_cache;
67 }
68
69 # delete_export(&export)
70 # Delete an existing export
71 sub delete_export
72 {
73 local @exps = &list_exports();
74 local @same = grep { $_ ne $_[0] && $_->{'line'} eq $_[0]->{'line'} } @exps;
75 local $lref = &read_file_lines($config{'exports_file'});
76 if (@same) {
77         # other exports on the same line.. cannot totally delete
78         splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'}-$_[0]->{'line'}+1,
79                &make_exports_line(@same));
80         map { $_->{'line'} = $_->{'eline'} = $_[0]->{'line'} } @same;
81         }
82 else {
83         # remove export line
84         splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'}-$_[0]->{'line'}+1);
85         # unmount the directory if it is mounted with --bind
86         local $dir = $_[0]->{'dir'};
87         local @mounted = &mount::list_mounted();
88         for(my $i=0; $i<@mounted; $i++) {
89             my $p = $mounted[$i];
90             if (($p->[0] eq $dir) and ($p->[2] eq "bind")) {
91                 &mount::unmount_dir($p->[1], $p->[0], $p->[2]);
92             }
93         }
94         # remove it from the fstab file
95         local @mounts = &mount::list_mounts();
96         for(my $i=0; $i<@mounts; $i++) {
97             my $p = $mounts[$i];
98             if (($p->[0] eq $dir) and ($p->[2] eq "bind")) {
99                 &mount::delete_mount($i);
100             }
101         }
102     }
103 @list_exports_cache = grep { $_ ne $_[0] } @list_exports_cache;
104 &flush_file_lines();
105 }
106
107 # create_export_via_pfs(&export)
108 sub create_export_via_pfs
109 {
110     use File::Basename;
111     use File::Path;
112     local $export_pfs = 1;
113     local $pfs = $_[0]->{'pfs'};
114     $pfs  =~ s/\/$//;
115     # Check if the pfs is already exported
116     open(EXP, "< $config{'exports_file'}");
117     while (my $line = <EXP>) {
118         if ($line =~ /^$pfs[\s|\t]/) {
119             if ($line !~ /fsid=0/) {
120                 &error($text{'save_pfs'}, $pfs);
121             }
122             $export_pfs = 0;
123         }
124     }
125     close(EXP);
126     
127     # Mount the directory in the pfs
128     local $add_line = 1;
129     local $to_be_mounted = 1;
130     local $dir = $_[0]->{'dir'};
131     local $expt_dir = $dir;
132     $expt_dir =~ s/\/$//;
133     $expt_dir = $pfs."/".basename($expt_dir);
134     
135     # Add it in the fstab file if it is not already in
136     local @mounts = &mount::list_mounts();
137     for(my $i=0; $i<@mounts; $i++) {
138         my $p = $mounts[$i];
139         if (($p->[0] eq $expt_dir) and ($p->[1] eq $dir) and ($p->[2] eq "bind")) {
140             $add_line = 0;
141         }
142     }
143     if ($add_line) {
144         &mount::create_mount($expt_dir, $dir, "bind", "");
145     }
146
147     # Mount it if it is not already mounted
148     local @mounted = &mount::list_mounted();
149     for(my $i=0; $i<@mounted; $i++) {
150         my $p = $mounted[$i];
151         if (($p->[0] eq $expt_dir) and ($p->[1] eq $dir) and ($p->[2] eq "bind")) {
152             $to_be_mounted = 0;
153         }
154     }
155     if ($to_be_mounted) {
156         eval { mkpath($expt_dir) };
157         if ($@) {
158             &error($text{'save_create_dir'}, $expt_dir );
159         }
160         my $err = &mount::mount_dir($expt_dir, $dir, "bind", "");
161         &error($err) if ($err);
162         &webmin_log("mount", undef, undef, { 'dev' => $expt_dir,
163                                              'type' => "bind",
164                                              'dir' => $dir });
165     }
166     
167     # Export the directory $expt_dir
168     $_[0]->{'dir'} = $expt_dir;
169     create_export($_[0]);
170
171     if ($export_pfs) {
172         # Export the pfs with the "fsid=0" option
173         $_[0]->{'dir'} = $pfs;
174         $_[0]->{'options'}->{'fsid'} = "0";
175         create_export($_[0]);
176     }
177 }
178
179 # create_export(&export)
180 sub create_export
181 {
182 &open_tempfile(EXP, ">>$config{'exports_file'}");
183 &print_tempfile(EXP, &make_exports_line($_[0]),"\n");
184 &close_tempfile(EXP);
185 }
186
187 # modify_export(&export, &old)
188 sub modify_export
189 {
190 local @exps = &list_exports();
191 local @same = grep { $_->{'line'} eq $_[1]->{'line'} } @exps;
192 local $lref = &read_file_lines($config{'exports_file'});
193 if ($_[0]->{'dir'} eq $_[1]->{'dir'} &&
194     $_[0]->{'active'} == $_[1]->{'active'} || @same == 1) {
195         # directory or active not changed, or on a line of it's own
196         splice(@same, &indexof($_[1],@same), 1, $_[0]);
197         splice(@$lref, $_[1]->{'line'}, $_[1]->{'eline'}-$_[1]->{'line'}+1,
198                &make_exports_line(@same));
199         }
200 else {
201         # move to a line of it's own
202         splice(@same, &indexof($_[1],@same), 1);
203         splice(@$lref, $_[1]->{'line'}, $_[1]->{'eline'}-$_[1]->{'line'}+1,
204                &make_exports_line(@same));
205         push(@$lref, &make_exports_line($_[0]));
206         }
207 &flush_file_lines();
208 }
209
210 # make_exports_line([&export]+)
211 sub make_exports_line
212 {
213 local ($e, @htxt);
214 foreach $e (@_) {
215         local %opts = %{$e->{'options'}};
216         if (%opts || !$e->{'host'}) {
217                 push(@htxt, $e->{'host'}."(".
218                             join(",", map { $opts{$_} eq "" ? $_
219                                                             : "$_=$opts{$_}" }
220                             (keys %opts)).")");
221                 }
222         else { push(@htxt, $e->{'host'}); }
223         }
224 return ($_[0]->{'active'} ? "" : "#").$_[0]->{'dir'}."\t".join(" ", @htxt);
225 }
226
227 # file_chooser_button2(input, type, name, disabled)
228 # A file_chooser_button which can be disabled
229 sub file_chooser_button2
230 {
231     local $disabled = ($_[3] == 1) ? "disabled" : "";
232     return "<input type=button name=$_[2] onClick='ifield = document.forms[0].$_[0]; chooser = window.open(\"$gconfig{'webprefix'}/chooser.cgi?type=$_[1]&chroot=/&file=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbar=no,width=400,height=300\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\" $disabled>\n";
233 }
234
235 # nfs_max_version(host)
236 # Return the max NFS version allowed on a server
237 sub nfs_max_version
238 {
239     local($_, $max, $out);
240     $max = 0;
241     $out = `rpcinfo -p $_[0] 2>&1`;
242     if ($?) { return 3; }
243     foreach (split(/\n/, $out)) {
244         if ((/ +(\d) +.*nfs/) && ($1 > $max)) {
245             $max = $1; }
246     }
247     return $max;
248 }
249
250 # describe_host(host)
251 # Given a host, regexp or netgroup return a human-readable version
252 sub describe_host
253 {
254 local $h = &html_escape($_[0]);
255 if ($h eq "=public") { return $text{'exports_webnfs'}; }
256 elsif ($h =~ /^gss\//) { return &text('exports_gss', "<i>$h</i>"); }
257 elsif ($h =~ /^\@(.*)/) { return &text('exports_ngroup', "<i>$1</i>"); }
258 elsif ($h =~ /^(\S+)\/(\S+)$/) {
259         return &text('exports_net', "<i>$1/$2</i>"); }
260 elsif ($h eq "" || $h eq "*") { return $text{'exports_all'}; }
261 elsif ($h =~ /\*/) { return &text('exports_hosts', "<i>$h</i>"); }
262 else { return &text('exports_host', "<i>$h</i>"); }
263 }
264
265 sub has_nfs_commands
266 {
267 return !&has_command("rpc.nfsd") && !&has_command("nfsd") &&
268        !&has_command("rpc.knfsd") ? 0 : 1;
269 }
270
271 # restart_mountd()
272 # Apply the /etc/exports configuration
273 sub restart_mountd
274 {
275 # Try exportfs -r first
276 if ($config{'apply_cmd'} && &find_byname("nfsd") && &find_byname("mountd")) {
277         local $out = &backquote_logged("$config{'apply_cmd'} 2>&1 </dev/null");
278         if (!$? && $out !~ /invalid|error|failed/i) {
279                 # Looks like it worked!
280                 return undef;
281                 }
282         }
283
284 &system_logged("$config{'portmap_command'} >/dev/null 2>&1 </dev/null")
285         if ($config{'portmap_command'});
286 local $temp = &transname();
287 local $rv = &system_logged("($config{'restart_command'}) </dev/null >$temp 2>&1");
288 local $out = `cat $temp`;
289 unlink($temp);
290 if ($rv) {
291         # something went wrong.. return an error
292         return "<pre>$out</pre>";
293         }
294 return undef;
295 }
296
297 1;
298