1=head1 backup-config-lib.pl 2 3Functions for creating configuration file backups. Some example code : 4 5 foreign_require('backup-config', 'backup-config-lib.pl'); 6 @backups = backup_config::list_backups(); 7 ($apache_backup) = grep { $_->{'mods'} eq 'apache' } @backups; 8 $apache_backup->{'dest'} = '/tmp/apache.tar.gz'; 9 &backup_config::save_backup($apache_backup); 10 11=cut 12 13BEGIN { push(@INC, ".."); }; 14use strict; 15use warnings; 16use WebminCore; 17our (%text, $module_config_directory, %config); 18&init_config(); 19&foreign_require("cron", "cron-lib.pl"); 20 21our $cron_cmd = "$module_config_directory/backup.pl"; 22our $backups_dir = "$module_config_directory/backups"; 23our $manifests_dir = "/tmp/backup-config-manifests"; 24 25=head2 list_backup_modules 26 27Returns details of all modules that allow backups, each of which is a hash 28ref in the same format as returned by get_module_info. 29 30=cut 31sub list_backup_modules 32{ 33my ($m, @rv); 34foreach $m (&get_all_module_infos()) { 35 my $mdir = &module_root_directory($m->{'dir'}); 36 if (&check_os_support($m) && 37 -r "$mdir/backup_config.pl") { 38 push(@rv, $m); 39 } 40 } 41return sort { $a->{'desc'} cmp $b->{'desc'} } @rv; 42} 43 44=head2 list_backups 45 46Returns a list of all configured backups, each of which is a hash ref with 47at least the following keys : 48 49=item mods - Space-separate list of modules to include. 50 51=item dest - Destination file, FTP or SSH server. 52 53=item configfile - Set to 1 if /etc/webmin/modulename files are included. 54 55=item nofiles - Set to 1 if server config files (like httpd.conf) are NOT included. 56 57=item others - A tab-separated list of other files to include. 58 59=item email -Email address to notify. 60 61=item emode - Set to 0 to send email only on failure, 1 to always send. 62 63=item sched - Set to 1 if regular scheduled backups are enabled. 64 65=item mins,hours,days,months,weekdays - Cron-style specification of backup time. 66 67=cut 68sub list_backups 69{ 70my (@rv, $f); 71opendir(DIR, $backups_dir) || return (); 72foreach $f (sort { $a cmp $b } readdir(DIR)) { 73 next if ($f !~ /^(\S+)\.backup$/); 74 push(@rv, &get_backup($1)); 75 } 76closedir(DIR); 77return @rv; 78} 79 80=head2 get_backup(id) 81 82Given a unique backup ID, returns a hash ref containing its details, in the 83same format as list_backups. 84 85=cut 86sub get_backup 87{ 88my %backup; 89&read_file("$backups_dir/$_[0].backup", \%backup) || return undef; 90$backup{'id'} = $_[0]; 91return \%backup; 92} 93 94=head2 save_backup(&backup) 95 96Given a hash ref containing backup details, saves them to disk. Must be in 97the same format as returned by list_backups, except for the ID which will be 98randomly assigned if missing. 99 100=cut 101sub save_backup 102{ 103$_[0]->{'id'} ||= time().$$; 104mkdir($backups_dir, 0700); 105&lock_file("$backups_dir/$_[0]->{'id'}.backup"); 106&write_file("$backups_dir/$_[0]->{'id'}.backup", $_[0]); 107&unlock_file("$backups_dir/$_[0]->{'id'}.backup"); 108} 109 110=head2 delete_backup(&backup) 111 112Deletes the backup whose details are in the given hash ref. 113 114=cut 115sub delete_backup 116{ 117&unlink_logged("$backups_dir/$_[0]->{'id'}.backup"); 118} 119 120=head2 parse_backup_url(string) 121 122Converts a URL like ftp:// or a filename into its components. These are 123user, pass, host, page, port (optional) 124 125=cut 126sub parse_backup_url 127{ 128if ($_[0] && $_[0] =~ /^ftp:\/\/([^:]*):([^\@]*)\@([^\/:]+)(:(\d+))?(\/.*)$/) { 129 return (1, $1, $2, $3, $6, $5); 130 } 131elsif ($_[0] && 132 $_[0] =~ /^ssh:\/\/([^:]*):([^\@]*)\@([^\/:]+)(:(\d+))?(\/.*)$/) { 133 return (2, $1, $2, $3, $6, $5); 134 } 135elsif ($_[0] && $_[0] =~ /^upload:(.*)$/) { 136 return (3, undef, undef, undef, $1); 137 } 138elsif ($_[0] && $_[0] =~ /^download:$/) { 139 return (4, undef, undef, undef, undef); 140 } 141else { 142 return (0, undef, undef, undef, $_[0]); 143 } 144} 145 146=head2 show_backup_destination(name, value, [local-mode]) 147 148Returns HTML for a field for selecting a local or FTP file. 149 150=cut 151sub show_backup_destination 152{ 153my ($mode, $user, $pass, $server, $path, $port) = &parse_backup_url($_[1]); 154my $rv; 155$rv .= "<table id='show_backup_destination' cellpadding=1 cellspacing=0>"; 156 157# Local file field 158$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 0, undef, $mode == 0)."</td>\n"; 159$rv .= "<td>$text{'backup_mode0'} </td><td colspan='3'>". 160 &ui_textbox("$_[0]_file", $mode == 0 ? $path : "", 60). 161 " ".&file_chooser_button("$_[0]_file")."</td> </tr>\n"; 162 163# FTP file fields 164$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 1, undef, $mode == 1)."</td>\n"; 165$rv .= "<td>$text{'backup_mode1'} </td><td>". 166 &ui_textbox("$_[0]_server", $mode == 1 ? $server : undef, 20). 167 "</td>\n"; 168$rv .= "<td> $text{'backup_path'} </td><td> ". 169 &ui_textbox("$_[0]_path", $mode == 1 ? $path : undef, 20). 170 "</td> </tr>\n"; 171$rv .= "<tr> <td></td>\n"; 172$rv .= "<td>$text{'backup_login'} </td><td> ". 173 &ui_textbox("$_[0]_user", $mode == 1 ? $user : undef, 20). 174 "</td>\n"; 175$rv .= "<td> $text{'backup_pass'} </td><td> ". 176 &ui_password("$_[0]_pass", $mode == 1 ? $pass : undef, 20). 177 "</td> </tr>\n"; 178$rv .= "<tr> <td></td>\n"; 179$rv .= "<td colspan='4'>$text{'backup_port'} ". 180 &ui_opt_textbox("$_[0]_port", $mode == 1 ? $port : undef, 5, 181 $text{'default'})."</td> </tr>\n"; 182 183# SCP file fields 184$rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 2, undef, $mode == 2)."</td>\n"; 185$rv .= "<td>$text{'backup_mode2'} </td><td>". 186 &ui_textbox("$_[0]_sserver", $mode == 2 ? $server : undef, 20). 187 "</td>\n"; 188$rv .= "<td> $text{'backup_path'} </td><td> ". 189 &ui_textbox("$_[0]_spath", $mode == 2 ? $path : undef, 20). 190 "</td> </tr>\n"; 191$rv .= "<tr> <td></td>\n"; 192$rv .= "<td>$text{'backup_login'} </td><td> ". 193 &ui_textbox("$_[0]_suser", $mode == 2 ? $user : undef, 20). 194 "</td>\n"; 195$rv .= "<td> $text{'backup_pass'} </td><td> ". 196 &ui_password("$_[0]_spass", $mode == 2 ? $pass : undef, 20). 197 "</td> </tr>\n"; 198$rv .= "<tr> <td></td>\n"; 199$rv .= "<td colspan='4'>$text{'backup_port'} ". 200 &ui_opt_textbox("$_[0]_sport", $mode == 2 ? $port : undef, 5, 201 $text{'default'})."</td> </tr>\n"; 202 203if ($_[2] == 1) { 204 # Uploaded file field 205 $rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 3, undef, $mode == 3). 206 "</td>\n"; 207 $rv .= "<td colspan=4>$text{'backup_mode3'} ". 208 &ui_upload("$_[0]_upload", 40). 209 "</td> </tr>\n"; 210 } 211elsif ($_[2] == 2) { 212 # Output to browser option 213 $rv .= "<tr><td>".&ui_oneradio("$_[0]_mode", 4, undef, $mode == 4). 214 "</td>\n"; 215 $rv .= "<td colspan=4>$text{'backup_mode4'}</td> </tr>\n"; 216 } 217 218$rv .= "</table>\n"; 219return $rv; 220} 221 222=head2 parse_backup_destination(name, &in) 223 224Returns a backup destination string, or calls error. 225 226=cut 227sub parse_backup_destination 228{ 229my %in = %{$_[1]}; 230my $mode = $in{"$_[0]_mode"} || 0; 231if ($mode == 0) { 232 # Local file 233 $in{"$_[0]_file"} && $in{"$_[0]_file"} =~ /^\/\S/ || 234 &error($text{'backup_edest'}); 235 return $in{"$_[0]_file"}; 236 } 237elsif ($mode == 1) { 238 # FTP server 239 &to_ipaddress($in{"$_[0]_server"}) || 240 &to_ip6address($in{"$_[0]_server"}) || 241 &error($text{'backup_eserver1'}); 242 $in{"$_[0]_path"} =~ /^\/\S/ || &error($text{'backup_epath'}); 243 $in{"$_[0]_user"} =~ /^[^:]*$/ || &error($text{'backup_euser'}); 244 $in{"$_[0]_pass"} =~ /^[^\@]*$/ || &error($text{'backup_epass'}); 245 $in{"$_[0]_port_def"} || $in{"$_[0]_port"} =~ /^\d+$/ || 246 &error($text{'backup_eport'}); 247 return "ftp://".$in{"$_[0]_user"}.":".$in{"$_[0]_pass"}."\@". 248 $in{"$_[0]_server"}. 249 ($in{"$_[0]_port_def"} ? "" : ":".$in{"$_[0]_port"}). 250 $in{"$_[0]_path"}; 251 } 252elsif ($mode == 2) { 253 # SSH server 254 &to_ipaddress($in{"$_[0]_sserver"}) || 255 &to_ip6address($in{"$_[0]_sserver"}) || 256 &error($text{'backup_eserver2'}); 257 $in{"$_[0]_spath"} =~ /^\/\S/ || &error($text{'backup_epath2'}); 258 $in{"$_[0]_suser"} =~ /^[^:]*$/ || &error($text{'backup_euser'}); 259 $in{"$_[0]_spass"} =~ /^[^\@]*$/ || &error($text{'backup_epass'}); 260 $in{"$_[0]_sport_def"} || $in{"$_[0]_sport"} =~ /^\d+$/ || 261 &error($text{'backup_esport'}); 262 return "ssh://".$in{"$_[0]_suser"}.":".$in{"$_[0]_spass"}."\@". 263 $in{"$_[0]_sserver"}. 264 ($in{"$_[0]_sport_def"} ? "" : ":".$in{"$_[0]_sport"}). 265 $in{"$_[0]_spath"}; 266 } 267elsif ($mode == 3) { 268 # Uploaded file .. save as temp file? 269 $in{"$_[0]_upload"} || &error($text{'backup_eupload'}); 270 return "upload:$_[0]_upload"; 271 } 272elsif ($mode == 4) { 273 return "download:"; 274 } 275} 276 277=head2 execute_backup(&modules, dest, &size, &files, include-webmin, exclude-files, &others) 278 279Backs up the configuration files for the modules to the selected destination. 280The backup is simply a tar file of config files. Returns undef on success, 281or an error message on failure. 282 283=cut 284sub execute_backup 285{ 286# Work out modules we can use 287my @mods; 288foreach my $m (@{$_[0]}) { 289 my $mdir = &module_root_directory($m); 290 if ($m && &foreign_check($m) && -r "$mdir/backup_config.pl") { 291 push(@mods, $m); 292 } 293 } 294 295# Work out where to write to 296my ($mode, $user, $pass, $host, $path, $port) = &parse_backup_url($_[1]); 297my $file; 298if ($mode == 0) { 299 $file = &date_subs($path); 300 } 301else { 302 my $ext = &has_command("gzip") ? '.tar.gz' : '.tar'; 303 $file = &transname_timestamped("backup", $ext); 304 } 305 306# Get module descriptions 307my $m; 308my %desc; 309foreach $m (@mods) { 310 my %minfo = &get_module_info($m); 311 $desc{$m} = $minfo{'desc'}; 312 } 313 314my @files; 315my %manifestfiles; 316if (!$_[5]) { 317 # Build list of all files to save from modules 318 foreach my $m (@mods) { 319 &foreign_require($m, "backup_config.pl"); 320 my @mfiles = &foreign_call($m, "backup_config_files"); 321 foreach my $f (@mfiles) { 322 next if (!$f); 323 if (-d $f) { 324 # A directory .. recursively expand 325 foreach my $sf (&expand_directory($f)) { 326 next if (!$sf); 327 push(@files, $sf); 328 push(@{$manifestfiles{$m}}, $sf); 329 } 330 } 331 else { 332 # Just one file 333 push(@files, $f); 334 push(@{$manifestfiles{$m}}, $f); 335 } 336 } 337 } 338 } 339 340# Add module config files and custom langs 341if ($_[4]) { 342 foreach $m (@mods) { 343 my @cfiles = ( "$config_directory/$m/config" ); 344 push(@cfiles, glob("$config_directory/$m/custom-lang*")); 345 push(@files, @cfiles); 346 push(@{$manifestfiles{$m}}, @cfiles); 347 } 348 } 349 350# Add other files 351foreach my $f (@{$_[6]}) { 352 next if (!$f); 353 if (-d $f) { 354 # A directory .. recursively expand 355 foreach my $sf (&expand_directory($f)) { 356 next if (!$sf); 357 push(@files, $sf); 358 push(@{$manifestfiles{"other"}}, $sf); 359 } 360 } 361 else { 362 # Just one file 363 push(@files, $f); 364 push(@{$manifestfiles{"other"}}, $f); 365 } 366 } 367 368# Save the manifest files 369&execute_command("rm -rf ".quotemeta($manifests_dir)); 370mkdir($manifests_dir, 0755); 371my @manifests; 372foreach $m (@mods, "_others") { 373 next if (!defined($manifestfiles{$m})); 374 my $man = "$manifests_dir/$m"; 375 my $fh; 376 &open_tempfile($fh, ">$man"); 377 &print_tempfile($fh, map { "$_\n" } @{$manifestfiles{$m}}); 378 &close_tempfile($fh); 379 push(@manifests, $man); 380 } 381 382# Make sure we have something to do 383@files = grep { $_ && -e $_ } @files; 384@files || (return $text{'backup_enone'}); 385 386if (!$_[5]) { 387 # Call all module pre functions 388 my $m; 389 foreach $m (@mods) { 390 if (&foreign_defined($m, "pre_backup")) { 391 my $err = &foreign_call($m, "pre_backup", \@files); 392 if ($err) { 393 return &text('backup_epre', $desc{$m}, $err); 394 } 395 } 396 } 397 } 398 399# Make the tar (possibly .gz) file 400my $filestemp = &transname(); 401my $fh; 402&open_tempfile($fh, ">$filestemp"); 403foreach my $f (&unique(@files), @manifests) { 404 my $frel = $f; 405 $frel =~ s/^\///; 406 &print_tempfile($fh, $frel."\n"); 407 } 408&close_tempfile($fh); 409my $qfile = quotemeta($file); 410my $out; 411if (&has_command("gzip")) { 412 &execute_command("cd / ; tar cfT - $filestemp | gzip -c >$qfile", 413 undef, \$out, \$out); 414 } 415else { 416 &execute_command("cd / ; tar cfT $qfile $filestemp", 417 undef, \$out, \$out); 418 } 419my $ex = $?; 420&unlink_file($filestemp); 421if ($ex) { 422 &unlink_file($file) if ($mode != 0); 423 return &text('backup_etar', "<pre>$out</pre>"); 424 } 425my @st = stat($file); 426${$_[2]} = $st[7] if ($_[2]); 427@{$_[3]} = &unique(@files) if ($_[3]); 428&set_ownership_permissions(undef, undef, 0600, $file); 429 430if (!$_[5]) { 431 # Call all module post functions 432 foreach $m (@mods) { 433 if (&foreign_defined($m, "post_backup")) { 434 &foreign_call($m, "post_backup", \@files); 435 } 436 } 437 } 438 439if ($mode == 1) { 440 # FTP upload to destination 441 my $err; 442 &ftp_upload($host, &date_subs($path), $file, \$err, undef, 443 $user, $pass, $port); 444 &unlink_file($file); 445 return $err if ($err); 446 } 447elsif ($mode == 2) { 448 # SCP to destination 449 my $err; 450 &scp_copy($file, "$user\@$host:".&date_subs($path), $pass, \$err,$port); 451 &unlink_file($file); 452 return $err if ($err); 453 } 454 455return undef; 456} 457 458=head2 execute_restore(&mods, source, &files, apply, [show-only], 459 [&other-files]) 460 461Restore configuration files from the specified source for the listed modules. 462Returns undef on success, or an error message. 463 464=cut 465sub execute_restore 466{ 467my ($mods, $src, $files, $apply, $show, $others) = @_; 468 469# Fetch file if needed 470my ($mode, $user, $pass, $host, $path, $port) = &parse_backup_url($src); 471my $file; 472if ($mode == 0) { 473 $file = $path; 474 } 475else { 476 $file = &transname(); 477 if ($mode == 2) { 478 # Download with SCP 479 my $err; 480 &scp_copy("$user\@$host:$path", $file, $pass, \$err, $port); 481 if ($err) { 482 &unlink_file($file); 483 return $err; 484 } 485 } 486 elsif ($mode == 1) { 487 # Download with FTP 488 my $err; 489 &ftp_download($host, $path, $file, \$err, undef, 490 $user, $pass, $port); 491 if ($err) { 492 &unlink_file($file); 493 return $err; 494 } 495 } 496 } 497 498# Validate archive 499open(FILE, "<".$file); 500my $two; 501read(FILE, $two, 2); 502close(FILE); 503my $qfile = quotemeta($file); 504my $gzipped = ($two eq "\037\213"); 505my $cmd; 506if ($gzipped) { 507 # Gzipped 508 &has_command("gunzip") || return $text{'backup_egunzip'}; 509 $cmd = "gunzip -c $qfile | tar tf -"; 510 } 511else { 512 $cmd = "tar tf $qfile"; 513 } 514my $out; 515&execute_command($cmd, undef, \$out, \$out, 0, 1); 516if ($?) { 517 &unlink_file($file) if ($mode != 0); 518 return &text('backup_euntar', "<pre>$out</pre>"); 519 } 520my @tarfiles = map { "/$_" } split(/\r?\n/, $out); 521my %tarfiles = map { $_, 1 } @tarfiles; 522 523# Extract manifests for each module 524my %hasmod = map { $_, 1 } @$mods; 525$hasmod{"_others"} = 1; 526&execute_command("rm -rf ".quotemeta($manifests_dir)); 527my $rel_manifests_dir = $manifests_dir; 528$rel_manifests_dir =~ s/^\///; 529if ($gzipped) { 530 &execute_command("cd / ; gunzip -c $qfile | tar xf - $rel_manifests_dir", undef, \$out, \$out); 531 } 532else { 533 &execute_command("cd / ; tar xf $qfile $rel_manifests_dir", undef, \$out, \$out); 534 } 535opendir(DIR, $manifests_dir); 536my $m; 537my %mfiles; 538my @files; 539while($m = readdir(DIR)) { 540 next if ($m eq "." || $m eq ".." || !$hasmod{$m}); 541 open(MAN, "<$manifests_dir/$m"); 542 my @mfiles; 543 while(<MAN>) { 544 s/\r|\n//g; 545 if ($tarfiles{$_}) { 546 push(@mfiles, $_); 547 } 548 } 549 close(MAN); 550 $mfiles{$m} = \@mfiles; 551 push(@files, @mfiles); 552 } 553closedir(DIR); 554push(@files, @$others) if ($others); 555if (!@files) { 556 &unlink_file($file) if ($mode != 0); 557 return $text{'backup_enone2'}; 558 } 559 560# Get descriptions for each module 561my %desc; 562foreach my $m (@$mods) { 563 my %minfo = &get_module_info($m); 564 $desc{$m} = $minfo{'desc'}; 565 } 566 567# Call module pre functions 568foreach my $m (@$mods) { 569 my $mdir = &module_root_directory($m); 570 if ($m && &foreign_check($m) && !$show && 571 -r "$mdir/backup_config.pl") { 572 &foreign_require($m, "backup_config.pl"); 573 if (&foreign_defined($m, "pre_restore")) { 574 my $err = &foreign_call($m, "pre_restore", \@files); 575 if ($err) { 576 &unlink_file($file) if ($mode != 0); 577 return &text('backup_epre2', $desc{$m}, $err); 578 } 579 } 580 } 581 } 582 583# Lock all files being extracted 584if (!$show) { 585 my $f; 586 foreach $f (@files) { 587 &lock_file($f); 588 } 589 } 590 591# Extract contents (only files specified by manifests) 592my $flag = $show ? "t" : "xv"; 593my $qfiles = join(" ", map { s/^\///; quotemeta($_) } &unique(@files)); 594if ($gzipped) { 595 &execute_command("cd / ; gunzip -c $qfile | tar ${flag}f - $qfiles", 596 undef, \$out, \$out); 597 } 598else { 599 &execute_command("cd / ; tar ${flag}f $qfile $qfiles", 600 undef, \$out, \$out); 601 } 602my $ex = $?; 603 604# Un-lock all files being extracted 605if (!$show) { 606 my $f; 607 foreach $f (@files) { 608 &unlock_file($f); 609 } 610 } 611 612# Check for tar error 613if ($ex) { 614 &unlink_file($file) if ($mode != 0); 615 return &text('backup_euntar', "<pre>$out</pre>"); 616 } 617 618if ($apply && !$show) { 619 # Call all module apply functions 620 foreach $m (@$mods) { 621 if (&foreign_defined($m, "post_restore")) { 622 &foreign_call($m, "post_restore", \@files); 623 } 624 } 625 } 626 627@$files = split(/\n/, $out); 628return undef; 629} 630 631=head2 scp_copy(source, dest, password, &error, [port]) 632 633Copies a file from some source to a destination. One or the other can be 634a server, like user@foo:/path/to/bar/ 635 636=cut 637sub scp_copy 638{ 639&foreign_require("proc", "proc-lib.pl"); 640my $cmd = "scp -r ".($_[4] ? "-P $_[4] " : ""). 641 quotemeta($_[0])." ".quotemeta($_[1]); 642my ($fh, $fpid) = &proc::pty_process_exec($cmd); 643my $out; 644while(1) { 645 my $rv = &wait_for($fh, "password:", "yes\\/no", ".*\n"); 646 $out .= $wait_for_input; 647 if ($rv == 0) { 648 syswrite($fh, "$_[2]\n"); 649 } 650 elsif ($rv == 1) { 651 syswrite($fh, "yes\n"); 652 } 653 elsif ($rv < 0) { 654 last; 655 } 656 } 657close($fh); 658my $got = waitpid($fpid, 0); 659if ($? || $out =~ /permission\s+denied/i) { 660 ${$_[3]} = "scp failed : <pre>$out</pre>"; 661 } 662} 663 664=head2 find_cron_job(&backup) 665 666MISSING DOCUMENTATION 667 668=cut 669sub find_cron_job 670{ 671my @jobs = &cron::list_cron_jobs(); 672my ($job) = grep { $_->{'user'} eq 'root' && 673 $_->{'command'} eq "$cron_cmd $_[0]->{'id'}" } @jobs; 674return $job; 675} 676 677=head2 nice_dest(destination, [subdates]) 678 679Returns a backup filename in a human-readable format, with dates substituted. 680 681=cut 682sub nice_dest 683{ 684my ($url, $subdates) = @_; 685my ($mode, $user, $pass, $server, $path, $port) = &parse_backup_url($url); 686if ($subdates) { 687 $path = &date_subs($path); 688 } 689if ($mode == 0) { 690 return "<tt>$path</tt>"; 691 } 692elsif ($mode == 1) { 693 return &text($port ? 'nice_ftpp' : 'nice_ftp', 694 "<tt>$server</tt>", "<tt>$path</tt>", 695 $port ? "<tt>$port</tt>" : ""); 696 } 697elsif ($mode == 2) { 698 return &text($port ? 'nice_sshp' : 'nice_ssh', 699 "<tt>$server</tt>", "<tt>$path</tt>", 700 $port ? "<tt>$port</tt>" : ""); 701 } 702elsif ($mode == 3) { 703 return $text{'nice_upload'}; 704 } 705elsif ($mode == 4) { 706 return $text{'nice_download'}; 707 } 708} 709 710=head2 date_subs(string) 711 712Given a string with strftime-style format characters in it like %Y and %S, 713replaces them with the correct values for the current date and time. 714 715=cut 716sub date_subs 717{ 718my ($path) = @_; 719my $rv; 720if ($config{'date_subs'}) { 721 eval "use POSIX"; 722 eval "use posix" if ($@); 723 my @tm = localtime(time()); 724 $rv = strftime($path, @tm); 725 } 726else { 727 $rv = $path; 728 } 729if ($config{'webmin_subs'}) { 730 $rv = &substitute_template($rv, { }); 731 } 732return $rv; 733} 734 735=head2 show_backup_what(name, webmin?, nofiles?, others) 736 737Returns HTML for selecting what gets included in a backup. 738 739=cut 740sub show_backup_what 741{ 742my ($name, $webmin, $nofiles, $others) = @_; 743$others ||= ""; 744return &ui_checkbox($name."_webmin", 1, $text{'edit_webmin'}, $webmin)."\n". 745 &ui_checkbox($name."_nofiles", 1, $text{'edit_nofiles'}, !$nofiles)."\n". 746 &ui_checkbox($name."_other", 1, $text{'edit_other'}, $others)."<br>". 747 &ui_textarea($name."_files", join("\n", split(/\t+/, $others)), 3, 50); 748} 749 750=head2 parse_backup_what(name, &in) 751 752Returns the webmin and nofiles flags, and a tab-separated list of other 753files to include. 754 755=cut 756sub parse_backup_what 757{ 758my ($name, $in) = @_; 759my $webmin = $in->{$name."_webmin"}; 760my $nofiles = !$in->{$name."_nofiles"}; 761$in->{$name."_files"} =~ s/\r//g; 762my $others = $in->{$name."_other"} ? 763 join("\t", split(/\n+/, $in->{$name."_files"})) : undef; 764$webmin || !$nofiles || $others || &error($text{'save_ewebmin'}); 765return ($webmin, $nofiles, $others); 766} 767 768=head2 expand_directory(directory) 769 770Given a directory, return a list of full paths to all files within it. 771 772=cut 773sub expand_directory 774{ 775my ($dir) = @_; 776my @rv; 777opendir(EXPAND, $dir); 778my @sf = readdir(EXPAND); 779closedir(EXPAND); 780foreach my $sf (@sf) { 781 next if ($sf eq "." || $sf eq ".."); 782 my $path = "$dir/$sf"; 783 if (-l $path || !-d $path) { 784 push(@rv, $path); 785 } 786 elsif (-d $path) { 787 push(@rv, &expand_directory($path)); 788 } 789 } 790return @rv; 791} 792 7931; 794 795