Handle hostnames with upper-case letters
[webmin.git] / fsdump / fsdump-lib.pl
1 #!/usr/local/bin/perl
2 # fsdump-lib.pl
3 # Common functions for doing filesystem backups with dump
4
5 BEGIN { push(@INC, ".."); };
6 use WebminCore;
7 &init_config();
8 if ($gconfig{'os_type'} =~ /^\S+\-linux$/) {
9         do "linux-lib.pl";
10         }
11 else {
12         do "$gconfig{'os_type'}-lib.pl";
13         }
14 &foreign_require("mount", "mount-lib.pl");
15 %access = &get_module_acl();
16
17 $cron_cmd = "$module_config_directory/backup.pl";
18 $newtape_cmd = "$module_config_directory/newtape.pl";
19 $notape_cmd = "$module_config_directory/notape.pl";
20 $multi_cmd = "$module_config_directory/multi.pl";
21 $rmulti_cmd = "$module_config_directory/rmulti.pl";
22 $ftp_cmd = "$module_config_directory/ftp.pl";
23
24 # list_dumps()
25 # Returns a list of all scheduled dumps
26 sub list_dumps
27 {
28 local (@rv, $f);
29 opendir(DIR, $module_config_directory);
30 foreach $f (sort { $a cmp $b } readdir(DIR)) {
31         next if ($f !~ /^(\S+)\.dump$/);
32         push(@rv, &get_dump($1));
33         }
34 closedir(DIR);
35 return @rv;
36 }
37
38 # get_dump(id)
39 sub get_dump
40 {
41 local %dump;
42 &read_file("$module_config_directory/$_[0].dump", \%dump) || return undef;
43 $dump{'id'} = $_[0];
44 return \%dump;
45 }
46
47 # save_dump(&dump)
48 sub save_dump
49 {
50 $_[0]->{'id'} = $$.time() if (!$_[0]->{'id'});
51 &lock_file("$module_config_directory/$_[0]->{'id'}.dump");
52 &write_file("$module_config_directory/$_[0]->{'id'}.dump", $_[0]);
53 &unlock_file("$module_config_directory/$_[0]->{'id'}.dump");
54 }
55
56 # delete_dump(&dump)
57 sub delete_dump
58 {
59 &lock_file("$module_config_directory/$_[0]->{'id'}.dump");
60 unlink("$module_config_directory/$_[0]->{'id'}.dump");
61 &unlock_file("$module_config_directory/$_[0]->{'id'}.dump");
62 }
63
64 # directory_filesystem(dir)
65 # Returns the filesystem type of some directory , or the full details
66 # if requesting an array
67 sub directory_filesystem
68 {
69 local $fs;
70 foreach my $m (sort { length($a->[0]) <=> length($b->[0]) }
71                  &mount::list_mounted()) {
72         local $l = length($m->[0]);
73         if ($m->[0] eq $_[0] || $m->[0] eq "/" ||
74             (length($_[0]) >= $l && substr($_[0], 0, $l+1) eq $m->[0]."/")) {
75                 $fs = $m;
76                 }
77         }
78 return wantarray ? @$fs : $fs->[2];
79 }
80
81 # is_mount_point(dir)
82 # Returns 1 if some directory is a filesystem mount point
83 sub is_mount_point
84 {
85 local ($dir) = @_;
86 foreach my $m (&mount::list_mounted()) {
87         return 1 if ($m->[0] eq $dir);
88         }
89 return 0;
90 }
91
92 # same_filesystem(fs1, fs2)
93 # Returns 1 if type filesystem types are the same
94 sub same_filesystem
95 {
96 local ($fs1, $fs2) = @_;
97 $fs1 = "ext2" if ($fs1 eq "ext3");
98 $fs2 = "ext2" if ($fs2 eq "ext3");
99 return lc($fs1) eq lc($fs2);
100 }
101
102 # date_subs(string, [time])
103 sub date_subs
104 {
105 if ($config{'date_subs'}) {
106         eval "use POSIX";
107         eval "use posix" if ($@);
108         local @tm = localtime($_[1] || time());
109         &clear_time_locale();
110         local $rv = strftime($_[0], @tm);
111         &reset_time_locale();
112         return $rv;
113         }
114 else {
115         return $_[0];
116         }
117 }
118
119 # execute_before(&dump, handle, escape)
120 # Executes the before-dump command, and prints the output. Returns 1 on success
121 # or 0 on failure
122 sub execute_before
123 {
124 if ($_[0]->{'before'}) {
125         local $h = $_[1];
126         &open_execute_command(before, "($_[0]->{'before'}) 2>&1 </dev/null", 1);
127         while(<before>) {
128                 print $h $_[2] ? &html_escape($_) : $_;
129                 }
130         close(before);
131         return !$?;
132         }
133 return 1;
134 }
135
136 # execute_after(&dump, handle, escape)
137 sub execute_after
138 {
139 if ($_[0]->{'after'}) {
140         local $h = $_[1];
141         &open_execute_command(after, "($_[0]->{'after'}) 2>&1 </dev/null", 1);
142         while(<after>) {
143                 print $h $_[2] ? &html_escape($_) : $_;
144                 }
145         close(after);
146         return !$?;
147         }
148 return 1;
149 }
150
151 # running_dumps(&procs)
152 # Returns a list of backup jobs currently in progress, and their statuses
153 sub running_dumps
154 {
155 local ($p, @rv, %got);
156 foreach $p (@{$_[0]}) {
157         if (($p->{'args'} =~ /$cron_cmd\s+(\S+)/ ||
158             $p->{'args'} =~ /$module_root_directory\/backup.pl\s+(\S+)/) &&
159             $p->{'args'} !~ /^\/bin\/(sh|bash|csh|tcsh)/) {
160                 local $backup = &get_dump($1);
161                 local $sfile = "$module_config_directory/$1.$p->{'pid'}.status";
162                 local %status;
163                 if (&read_file($sfile, \%status)) {
164                         $backup->{'status'} = \%status;
165                         $backup->{'pid'} = $p->{'pid'};
166                         push(@rv, $backup);
167                         $got{$sfile} = 1 if (!$status{'end'});
168                         }
169                 }
170         }
171 # Remove any left over .status files
172 opendir(DIR, $module_config_directory);
173 local $f;
174 foreach $f (readdir(DIR)) {
175         local $path = "$module_config_directory/$f";
176         unlink($path) if ($path =~ /\.status$/ && !$got{$path});
177         }
178 closedir(DIR);
179 return @rv;
180 }
181
182 # can_edit_dir(dir)
183 # Returns 1 if some backup can be used or edited
184 sub can_edit_dir
185 {
186 return 1 if ($access{'dirs'} eq '*');
187 local ($d, $dd);
188 local @ddirs = !ref($_[0]) ? ( $_[0] ) :
189                $supports_multiple ? split(/\s+/, $_[0]->{'dir'}) :
190                                     ( $_[0]->{'dir'} );
191 foreach $dd (@ddirs) {
192         local $anyok = 0;
193         foreach $d (split(/\t+/, $access{'dirs'})) {
194                 $anyok = 1 if (&is_under_directory($d, $dd));
195                 }
196         return 0 if (!$anyok);
197         }
198 return 1;
199 }
200
201 sub create_wrappers
202 {
203 &foreign_require("cron", "cron-lib.pl");
204 &cron::create_wrapper($notape_cmd, $module_name, "notape.pl");
205 &cron::create_wrapper($newtape_cmd, $module_name, "newtape.pl");
206 &cron::create_wrapper($multi_cmd, $module_name, "multi.pl");
207 &cron::create_wrapper($rmulti_cmd, $module_name, "rmulti.pl");
208 }
209
210 # new_header(title)
211 sub new_header
212 {
213 print "</table></td></tr></table><br>\n";
214 print "<table border width=100%>\n";
215 print "<tr $tb> <td><b>$_[0]</b></td> </tr>\n";
216 print "<tr $cb> <td><table width=100%>\n";
217 }
218
219 # dump_directories(&dump)
220 sub dump_directories
221 {
222 if (!&multiple_directory_support($_[0]->{'fs'})) {
223         return $_[0]->{'dir'};
224         }
225 elsif ($_[0]->{'tabs'}) {
226         return split(/\t+/, $_[0]->{'dir'});
227         }
228 else {
229         return split(/\s+/, $_[0]->{'dir'});
230         }
231 }
232
233 # run_ssh_command(command, output-fh, output-mode, password)
234 # Run some command and display it's output, possibly providing a password
235 # if one is requested
236 sub run_ssh_command
237 {
238 local ($cmd, $fh, $fhmode, $pass) = @_;
239 &foreign_require("proc", "proc-lib.pl");
240 local ($cfh, $fpid) = &proc::pty_process_exec_logged($cmd);
241 local ($wrong_password, $got_login, $connect_failed);
242 local $out;
243 local $stars = ("*" x length($pass));
244 while(1) {
245         local $rv = &wait_for($cfh, "password:", "yes\\/no", "(^|\\n)\\s*Permission denied.*\n", "ssh: connect.*\n", ".*\n");
246         if ($wait_for_input !~ /^\s*DUMP:\s+ACLs\s+in\s+inode/i) {
247                 $wait_for_input =~ s/\Q$pass\E/$stars/g;
248                 if ($fhmode) {
249                         print $fh &html_escape($wait_for_input);
250                         }
251                 else {
252                         print $fh $wait_for_input;
253                         }
254                 }
255         if ($rv == 0) {
256                 syswrite($cfh, "$pass\n");
257                 }
258         elsif ($rv == 1) {
259                 syswrite($cfh, "yes\n");
260                 }
261         elsif ($rv == 2) {
262                 $wrong_password++;
263                 last;
264                 }
265         elsif ($rv == 3) {
266                 $connect_failed++;
267                 }
268         elsif ($rv < 0) {
269                 last;
270                 }
271         }
272 close($cfh);
273 local $got = waitpid($fpid, 0);
274 return $?;
275 }
276
277 # rsh_command_input(selname, textname, value)
278 # Returns HTML for selecting an rsh command
279 sub rsh_command_input
280 {
281 local ($selname, $textname, $rsh) = @_;
282 local $ssh = &has_command("ssh");
283 local $r = $ssh && $rsh eq $ssh ? 1 :
284            $rsh eq $ftp_cmd ? 3 :
285            $rsh ? 2 : 0;
286 local @opts = ( [ 0, $text{'dump_rsh0'} ],
287                 [ 1, $text{'dump_rsh1'} ],
288                 [ 3, $text{'dump_rsh3'} ] );
289 if ($r == 2) {
290         push(@opts, [ 2, $text{'dump_rsh2'}." ".
291                          &ui_textbox($textname, $rsh, 30) ]);
292         }
293 return &ui_radio($selname, $r, \@opts);
294 }
295
296 # rsh_command_parse(selname, textname)
297 # Returns the rsh command to use for a backup/restore, based on %in
298 sub rsh_command_parse
299 {
300 local ($selname, $textname) = @_;
301 if ($in{$selname} == 0) {
302         return undef;
303         }
304 elsif ($in{$selname} == 1) {
305         local $ssh = &has_command("ssh");
306         $ssh || &error($text{'dump_essh'});
307         return $ssh;
308         }
309 elsif ($in{$selname} == 3) {
310         return $ftp_cmd;
311         }
312 else {
313         $in{$textname} =~ /^(\S+)/ && &has_command("$1") ||
314                 &error($text{'dump_ersh'});
315         return $in{$textname};
316         }
317 }
318
319 1;
320