2 # XXX add to released modules list
4 BEGIN { push(@INC, ".."); };
7 &foreign_require("cron", "cron-lib.pl");
8 &foreign_require("mailboxes", "mailboxes-lib.pl");
10 $cron_cmd = "$module_config_directory/copy.pl";
11 $copies_dir = "$module_config_directory/copies";
14 # Returns an array of scheduled copies to multiple servers
18 opendir(DIR, $copies_dir);
19 foreach $f (sort { $a cmp $b } readdir(DIR)) {
20 next if ($f !~ /^(\S+)\.copy$/);
21 push(@rv, &get_copy($1));
31 &read_file("$copies_dir/$_[0].copy", \%copy) || return undef;
33 if (!defined($copy{'email'})) {
34 # compat - continue email to root
35 $copy{'email'} = "root";
43 $_[0]->{'id'} ||= time().$$;
44 mkdir($copies_dir, 0700);
45 &lock_file("$copies_dir/$_[0]->{'id'}.copy");
46 &write_file("$copies_dir/$_[0]->{'id'}.copy", $_[0]);
47 &unlock_file("$copies_dir/$_[0]->{'id'}.copy");
53 &lock_file("$copies_dir/$_[0]->{'id'}.copy");
54 unlink("$copies_dir/$_[0]->{'id'}.copy");
55 &unlock_file("$copies_dir/$_[0]->{'id'}.copy");
58 # run_cluster_job(&job, &callback)
59 # Runs a cluster cron job on all configured servers, and for each result calls
60 # the callback function with parameters 0/1, a server object, and the list of
61 # files copied or an error message
67 # Work out which servers to run on
68 &foreign_require("servers", "servers-lib.pl");
69 local @servers = &servers::list_servers_sorted();
70 local @groups = &servers::list_all_groups(\@servers);
72 foreach $s (split(/\s+/, $_[0]->{'servers'})) {
73 if ($s =~ /^group_(.*)$/) {
74 # All members of a group
75 ($group) = grep { $_->{'name'} eq $1 } @groups;
76 foreach $m (@{$group->{'members'}}) {
77 push(@run, grep { $_->{'host'} eq $m && $_->{'user'} }
83 push(@run, ( { 'desc' => $text{'edit_this'} } ));
86 # All servers with login
87 push(@run, grep { $_->{'user'} } @servers);
90 # A single remote server
91 push(@run, grep { $_->{'host'} eq $s } @servers);
96 # Setup error handler for down hosts
99 $inst_error_msg = join("", @_);
101 &remote_error_setup(\&inst_error);
103 # Run the pre command on local
104 if ($_[0]->{'before'} && $_[0]->{'beforelocal'}) {
105 &system_logged("$_[0]->{'before'} >/dev/null 2>&1");
108 # Run one each one in parallel and display the output
111 local ($rh = "READ$p", $wh = "WRITE$p");
113 select($wh); $| = 1; select(STDOUT);
115 # Run the command in a subprocess
118 &remote_foreign_require($s->{'host'}, "webmin",
120 if ($inst_error_msg) {
121 # Failed to contact host ..
122 print $wh &serialise_variable([ 0, $inst_error_msg ]);
126 # Run the pre command on remote
128 if ($_[0]->{'before'} && !$_[0]->{'beforelocal'}) {
129 $bout = &remote_foreign_call($s->{'host'},
130 "webmin", "backquote_logged", $_[0]->{'before'});
133 # Work out which files to transfer, and which directories
135 local @allnames = split(/\t+/, $_[0]->{'files'});
136 local (@allfiles, @alldirs);
137 foreach $f (@allnames) {
139 # Expand this directory
140 &expand_dir($f, \@allfiles, \@alldirs);
147 # Create each of the needed directories
149 foreach $d (@alldirs) {
151 if ($_[0]->{'dmode'}) {
152 # Relative to directory
154 $dest = $_[0]->{'dest'}."/".$1;
157 # Full path under directory
158 $dest = $_[0]->{'dest'}.$d;
160 local $qdest = quotemeta($dest);
161 local $rmd = &remote_eval($s->{'host'}, "webmin",
162 "-d \"$qdest\" ? \"already\" : &make_dir(\"$qdest\", 0755, 1) ? undef : \$!");
166 elsif ($rmd ne "already") {
167 push(@errs, [ $d, "Failed to create $dest : $rmd" ]);
171 # Transfer each of the files, and make sure each was done OK
173 foreach $f (@allfiles) {
175 if ($_[0]->{'dmode'}) {
176 # Relative to directory
178 $dest = $_[0]->{'dest'}."/".$1;
181 # Full path under directory
182 $dest = $_[0]->{'dest'}.$f;
185 push(@errs, [ $f, "Source file not found" ]);
188 if (&simplify_path($f) eq &simplify_path($dest) &&
190 push(@errs, [ $f, "Cannot overwrite same file on this server" ]);
193 &remote_write($s->{'host'}, $f, $dest);
194 if ($inst_error_msg) {
195 push(@errs, [ $f, "Copy failed : $inst_error_msg" ]);
198 local $qdest = quotemeta($dest);
199 local $rst = &remote_eval($s->{'host'}, "webmin",
200 "[ stat(\"$qdest\") ]");
201 local @st = stat($f);
202 if ($st[7] == $rst->[7]) {
206 push(@errs, [ $f, "Copy was incomplete" ]);
210 # Run the post command on remote
212 if ($_[0]->{'cmd'} && !$_[0]->{'cmdlocal'}) {
213 $out = &remote_foreign_call($s->{'host'},
214 "webmin", "backquote_logged", $_[0]->{'cmd'});
217 print $wh &serialise_variable([ 1, \@files, \@errs, \@dirs,
226 # Get back all the results
229 local $rh = "READ$p";
232 local $rv = &unserialise_variable($line);
235 &$func(0, $s, "Unknown reason");
238 &$func($rv->[0], $s, $rv->[1], $rv->[2], $rv->[3],
245 # Run the post command on local
246 if ($_[0]->{'cmd'} && $_[0]->{'cmdlocal'}) {
247 &system_logged("$_[0]->{'cmd'} >/dev/null 2>&1");
253 # find_cron_job(©)
256 local @jobs = &cron::list_cron_jobs();
257 local ($job) = grep { $_->{'user'} eq 'root' &&
258 $_->{'command'} eq "$cron_cmd $_[0]->{'id'}" } @jobs;
262 # expand_dir(dir, &files, &dirs)
265 push(@{$_[2]}, $_[0]);
268 foreach $f (readdir(DIR)) {
269 next if ($f eq "." || $f eq "..");
270 local $fp = "$_[0]/$f";
273 &expand_dir($fp, $_[1], $_[2]);