* Failed dumps now cleanup correctly, deleting in-progress file
[BackupPC.git] / bin / BackupPC
index d18bcef..ba6cde9 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/perl -T
+#!/bin/perl
 #============================================================= -*-perl-*-
 #
 # BackupPC: Main program for PC backups.
@@ -29,7 +29,7 @@
 #   Craig Barratt  <cbarratt@users.sourceforge.net>
 #
 # COPYRIGHT
-#   Copyright (C) 2001  Craig Barratt
+#   Copyright (C) 2001-2003  Craig Barratt
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
 #
 #========================================================================
 #
-# Version 2.0.0beta1, released 30 Mar 2003.
+# Version 2.1.0_CVS, released 13 Mar 2004.
 #
 # See http://backuppc.sourceforge.net.
 #
 #========================================================================
 
 use strict;
+no  utf8;
 use vars qw(%Status %Info $Hosts);
 use lib "/usr/local/BackupPC/lib";
 use BackupPC::Lib;
@@ -299,6 +300,7 @@ sub Main_Initialize
     $Info{ConfigModTime} = $bpc->ConfigMTime();
     $Info{pid} = $$;
     $Info{startTime} = time;
+    $Info{ConfigLTime} = time;
     $Info{Version} = $bpc->{Version};
 
     #
@@ -319,6 +321,10 @@ sub Main_Initialize
         }
         $Status{$host}{activeJob} = 0;
     }
+    foreach my $host ( sort(keys(%Status)) ) {
+        next if ( defined($Hosts->{$host}) );
+       delete($Status{$host});
+    }
 
     #
     # Write out our initial status and save our PID
@@ -472,7 +478,7 @@ sub Main_TryToRun_Bg_or_User_Queue
                 next;
             }
             push(@args, $req->{doFull} ? "-f" : "-i")
-                            if ( !$req->{restore} );
+                            if (( !$req->{restore} ) && ( !$req->{archive} ));
             $UserQueueOn{$req->{host}} = 0;
         } elsif ( $nJobs < $Conf{MaxBackups}
                         && (@CmdQueue + $nJobs)
@@ -533,6 +539,10 @@ sub Main_TryToRun_Bg_or_User_Queue
             $progName = "BackupPC_restore";
             $type     = "restore";
             push(@args, $req->{hostIP}, $req->{host}, $req->{reqFileName});
+       } elsif ( $req->{archive} ) {
+            $progName = "BackupPC_archive";
+            $type     = "archive";
+            push(@args, $req->{user}, $req->{host}, $req->{reqFileName});
         } else {
             $progName = "BackupPC_dump";
             $type     = "backup";
@@ -560,9 +570,12 @@ sub Main_TryToRun_Bg_or_User_Queue
         vec($FDread, $Jobs{$host}{fn}, 1) = 1;
         $Jobs{$host}{startTime}  = time;
         $Jobs{$host}{reqTime}    = $req->{reqTime};
+        $Jobs{$host}{userReq}    = $req->{userReq};
         $Jobs{$host}{cmd}        = join(" ", $progName, @args);
         $Jobs{$host}{user}       = $user;
         $Jobs{$host}{type}       = $type;
+       $Status{$host}{userReq}  = $req->{userReq}
+                                       if ( defined($Hosts->{$host}) );
         if ( !$req->{dhcp} ) {
             $Status{$host}{state}     = "Status_".$type."_starting";
             $Status{$host}{activeJob} = 1;
@@ -589,13 +602,7 @@ sub Main_Select
                                                = localtime(time);
         my($currHours) = $hour + $min / 60 + $sec / 3600;
         if ( $bpc->ConfigMTime() != $Info{ConfigModTime} ) {
-            my($mesg) = $bpc->ConfigRead()
-                        || "Re-read config file because mtime changed";
-            print(LOG $bpc->timeStamp, "$mesg\n");
-            %Conf = $bpc->Conf();
-            $Info{ConfigModTime} = $bpc->ConfigMTime();
-            umask($Conf{UmaskMode});
-            ServerSocketInit();
+            ServerReload("Re-read config file because mtime changed");
         }
         my $delta = -1;
         foreach my $t ( @{$Conf{WakeupSchedule} || [0..23]} ) {
@@ -643,23 +650,9 @@ sub Main_Process_Signal
     # Process signals
     #
     if ( $SigName eq "HUP" ) {
-        my($mesg) = $bpc->ConfigRead()
-                    || "Re-read config file because of a SIG_HUP";
-        print(LOG $bpc->timeStamp, "$mesg\n");
-        $Info{ConfigModTime} = $bpc->ConfigMTime();
-        %Conf = $bpc->Conf();
-        umask($Conf{UmaskMode});
-        ServerSocketInit();
-        HostsUpdate(0);
-        $NextWakeup = 0;
+        ServerReload("Re-read config file because of a SIG_HUP");
     } elsif ( $SigName ) {
-        print(LOG $bpc->timeStamp, "Got signal $SigName... cleaning up\n");
-        foreach my $host ( keys(%Jobs) ) {
-            kill(2, $Jobs{$host}{pid});
-        }
-        StatusWrite();
-        unlink("$TopDir/log/BackupPC.pid");
-        exit(1);
+        ServerShutdown("Got signal $SigName... cleaning up");
     }
     $SigName = "";
 }
@@ -779,7 +772,7 @@ sub Main_Check_Job_Messages
                     if ( defined($Jobs{$newHost}) ) {
                         print(LOG $bpc->timeStamp,
                                 "Backup on $newHost is already running\n");
-                        kill(2, $Jobs{$host}{pid});
+                        kill($bpc->sigName2num("INT"), $Jobs{$host}{pid});
                         $nbytes = 0;
                         last;
                     }
@@ -825,29 +818,60 @@ sub Main_Check_Job_Messages
                 $Status{$host}{startTime} = time;
                 $Status{$host}{deadCnt}   = 0;
                 $Status{$host}{aliveCnt}++;
+            } elsif ( $mesg =~ /^started_archive/ ) {
+                $Jobs{$host}{type}    = "archive";
+                print(LOG $bpc->timeStamp,
+                          "Started archive on $host"
+                          . " (pid=$Jobs{$host}{pid})\n");
+                $Status{$host}{state}     = "Status_archive_in_progress";
+                $Status{$host}{reason}    = "";
+                $Status{$host}{type}      = "archive";
+                $Status{$host}{startTime} = time;
+                $Status{$host}{deadCnt}   = 0;
+                $Status{$host}{aliveCnt}++;
             } elsif ( $mesg =~ /^(full|incr) backup complete/ ) {
                 print(LOG $bpc->timeStamp, "Finished $1 backup on $host\n");
                 $Status{$host}{reason}    = "Reason_backup_done";
                 delete($Status{$host}{error});
                 delete($Status{$host}{errorTime});
                 $Status{$host}{endTime}   = time;
+            } elsif ( $mesg =~ /^backups disabled/ ) {
+                print(LOG $bpc->timeStamp,
+                           "Ignoring old backup error on $host\n");
+                $Status{$host}{reason}    = "Reason_backup_done";
+                delete($Status{$host}{error});
+                delete($Status{$host}{errorTime});
+                $Status{$host}{endTime}   = time;
             } elsif ( $mesg =~ /^restore complete/ ) {
                 print(LOG $bpc->timeStamp, "Finished restore on $host\n");
                 $Status{$host}{reason}    = "Reason_restore_done";
                 delete($Status{$host}{error});
                 delete($Status{$host}{errorTime});
                 $Status{$host}{endTime}   = time;
+            } elsif ( $mesg =~ /^archive complete/ ) {
+                print(LOG $bpc->timeStamp, "Finished archive on $host\n");
+                $Status{$host}{reason}    = "Reason_archive_done";
+                delete($Status{$host}{error});
+                delete($Status{$host}{errorTime});
+                $Status{$host}{endTime}   = time;
             } elsif ( $mesg =~ /^nothing to do/ ) {
-                $Status{$host}{state}     = "Status_idle";
-                $Status{$host}{reason}    = "Reason_nothing_to_do";
-                $Status{$host}{startTime} = time;
+               if ( $Status{$host}{reason} ne "Reason_backup_failed"
+                       && $Status{$host}{reason} ne "Reason_restore_failed" ) {
+                   $Status{$host}{state}     = "Status_idle";
+                   $Status{$host}{reason}    = "Reason_nothing_to_do";
+                   $Status{$host}{startTime} = time;
+               }
                 $Status{$host}{dhcpCheckCnt}--
                                 if ( $Status{$host}{dhcpCheckCnt} > 0 );
             } elsif ( $mesg =~ /^no ping response/
-                            || $mesg =~ /^ping too slow/ ) {
+                            || $mesg =~ /^ping too slow/
+                            || $mesg =~ /^host not found/ ) {
                 $Status{$host}{state}     = "Status_idle";
-                if ( $Status{$host}{reason} ne "Reason_backup_failed" ) {
+                if ( $Status{$host}{userReq}
+                       || $Status{$host}{reason} ne "Reason_backup_failed"
+                       || $Status{$host}{error} =~ /^aborted by user/ ) {
                     $Status{$host}{reason}    = "Reason_no_ping";
+                   $Status{$host}{error}     = $mesg;
                     $Status{$host}{startTime} = time;
                 }
                 $Status{$host}{deadCnt}++;
@@ -856,11 +880,46 @@ sub Main_Check_Job_Messages
                }
             } elsif ( $mesg =~ /^dump failed: (.*)/ ) {
                 $Status{$host}{state}     = "Status_idle";
-                $Status{$host}{reason}    = "Reason_backup_failed";
+               $Status{$host}{error}     = $1;
+               $Status{$host}{errorTime} = time;
+               $Status{$host}{endTime}   = time;
+               if ( $Status{$host}{reason}
+                       eq "Reason_backup_canceled_by_user" ) {
+                   print(LOG $bpc->timeStamp,
+                           "Backup canceled on $host ($1)\n");
+               } else {
+                   $Status{$host}{reason} = "Reason_backup_failed";
+                   print(LOG $bpc->timeStamp,
+                           "Backup failed on $host ($1)\n");
+               }
+            } elsif ( $mesg =~ /^restore failed: (.*)/ ) {
+                $Status{$host}{state}     = "Status_idle";
                 $Status{$host}{error}     = $1;
                 $Status{$host}{errorTime} = time;
                 $Status{$host}{endTime}   = time;
-                print(LOG $bpc->timeStamp, "backup failed on $host ($1)\n");
+               if ( $Status{$host}{reason}
+                        eq "Reason_restore_canceled_by_user" ) {
+                   print(LOG $bpc->timeStamp,
+                           "Restore canceled on $host ($1)\n");
+               } else {
+                   $Status{$host}{reason} = "Reason_restore_failed";
+                   print(LOG $bpc->timeStamp,
+                           "Restore failed on $host ($1)\n");
+               }
+            } elsif ( $mesg =~ /^archive failed: (.*)/ ) {
+                $Status{$host}{state}     = "Status_idle";
+                $Status{$host}{error}     = $1;
+                $Status{$host}{errorTime} = time;
+                $Status{$host}{endTime}   = time;
+               if ( $Status{$host}{reason}
+                        eq "Reason_archive_canceled_by_user" ) {
+                   print(LOG $bpc->timeStamp,
+                           "Archive canceled on $host ($1)\n");
+               } else {
+                   $Status{$host}{reason} = "Reason_archive_failed";
+                   print(LOG $bpc->timeStamp,
+                           "Archive failed on $host ($1)\n");
+               }
             } elsif ( $mesg =~ /^log\s+(.*)/ ) {
                 print(LOG $bpc->timeStamp, "$1\n");
             } elsif ( $mesg =~ /^BackupPC_stats = (.*)/ ) {
@@ -1010,9 +1069,9 @@ sub Main_Check_Client_Messages
                 if ( $CmdJob ne $host && defined($Status{$host})
                                       && defined($Jobs{$host}) ) {
                     print(LOG $bpc->timeStamp,
-                               "Stopping current backup of $host,"
+                               "Stopping current $Jobs{$host}{type} of $host,"
                              . " request by $user (backoff=$backoff)\n");
-                    kill(2, $Jobs{$host}{pid});
+                    kill($bpc->sigName2num("INT"), $Jobs{$host}{pid});
                    #
                    # Don't close the pipe now; wait until the child
                    # really exits later.  Otherwise close() will
@@ -1022,11 +1081,20 @@ sub Main_Check_Client_Messages
                     ##close($Jobs{$host}{fh});
                     ##delete($Jobs{$host});
 
-                    $Status{$host}{state}     = "Status_idle";
-                    $Status{$host}{reason}    = "Reason_backup_canceled_by_user";
+                    $Status{$host}{state}    = "Status_idle";
+                   if ( $Jobs{$host}{type} eq "restore" ) {
+                       $Status{$host}{reason}
+                                   = "Reason_restore_canceled_by_user";
+                   } elsif ( $Jobs{$host}{type} eq "archive" ) {
+                       $Status{$host}{reason}
+                                   = "Reason_archive_canceled_by_user";
+                   } else {
+                       $Status{$host}{reason}
+                                   = "Reason_backup_canceled_by_user";
+                   }
                     $Status{$host}{activeJob} = 0;
                     $Status{$host}{startTime} = time;
-                    $reply = "ok: backup of $host cancelled";
+                    $reply = "ok: $Jobs{$host}{type} of $host canceled";
                 } elsif ( $BgQueueOn{$host} || $UserQueueOn{$host} ) {
                     print(LOG $bpc->timeStamp,
                                "Stopping pending backup of $host,"
@@ -1034,7 +1102,7 @@ sub Main_Check_Client_Messages
                     @BgQueue = grep($_->{host} ne $host, @BgQueue);
                     @UserQueue = grep($_->{host} ne $host, @UserQueue);
                     $BgQueueOn{$host} = $UserQueueOn{$host} = 0;
-                    $reply = "ok: pending backup of $host cancelled";
+                    $reply = "ok: pending backup of $host canceled";
                 } else {
                     print(LOG $bpc->timeStamp,
                                "Nothing to do for stop backup of $host,"
@@ -1085,11 +1153,42 @@ sub Main_Check_Client_Messages
                                 user    => $user,
                                 reqTime => time,
                                 doFull  => $doFull,
+                                userReq => 1,
                                 dhcp    => $hostIP eq $host ? 0 : 1,
                         });
                     $UserQueueOn{$hostIP} = 1;
                     $reply = "ok: requested backup of $host";
                 }
+            } elsif ( $cmd =~ /^archive (\S+)\s+(\S+)\s+(\S+)/ ) {
+                my $user         = $1;
+                my $archivehost  = $2;
+                my $reqFileName  = $3;
+               $host      = $bpc->uriUnesc($archivehost);
+                if ( !defined($Status{$host}) ) {
+                    print(LOG $bpc->timeStamp,
+                               "User $user requested archive of unknown archive host"
+                             . " $host");
+                    $reply = "archive error: unknown archive host $host";
+                } else {
+                    print(LOG $bpc->timeStamp,
+                               "User $user requested archive on $host"
+                             . " ($host)\n");
+                    if ( defined($Jobs{$host}) ) {
+                        $reply = "Archive currently running on $host, please try later";
+                    } else {
+                        unshift(@UserQueue, {
+                                host    => $host,
+                                hostIP  => $user,
+                                reqFileName => $reqFileName,
+                                reqTime => time,
+                                dhcp    => 0,
+                                archive => 1,
+                               userReq => 1,
+                        });
+                        $UserQueueOn{$host} = 1;
+                        $reply = "ok: requested archive on $host";
+                    }
+                }
             } elsif ( $cmd =~ /^restore (\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ ) {
                 my $hostIP = $1;
                 $host      = $2;
@@ -1113,6 +1212,7 @@ sub Main_Check_Client_Messages
                                 reqTime => time,
                                 dhcp    => 0,
                                 restore => 1,
+                               userReq => 1,
                         });
                     $UserQueueOn{$host} = 1;
                     if ( defined($Jobs{$host}) ) {
@@ -1174,6 +1274,15 @@ sub Main_Check_Client_Messages
                 QueueLink($host);
             } elsif ( $cmd =~ /^log\s+(.*)/ ) {
                 print(LOG $bpc->timeStamp, "$1\n");
+            } elsif ( $cmd =~ /^server\s+(\w+)/ ) {
+                my($type) = $1;
+                if ( $type eq 'reload' ) {
+                    ServerReload("Reloading config/host files via CGI request");
+                } elsif ( $type eq 'shutdown' ) {
+                    $reply = "Shutting down...\n";
+                    syswrite($Clients{$client}{fh}, $reply, length($reply));
+                    ServerShutdown("Server shutting down...");
+                }
             } elsif ( $cmd =~ /^quit/ || $cmd =~ /^exit/ ) {
                 $nbytes = 0;
                 last;
@@ -1403,7 +1512,7 @@ sub catch_signal
     if ( $SigName ) {
         $SigName = shift;
         foreach my $host ( keys(%Jobs) ) {
-            kill(2, $Jobs{$host}{pid});
+            kill($bpc->sigName2num("INT"), $Jobs{$host}{pid});
         }
         #
         # In case we are inside the exit handler, reopen the log file
@@ -1413,8 +1522,9 @@ sub catch_signal
         print(LOG "Fatal error: unhandled signal $SigName\n");
         unlink("$TopDir/log/BackupPC.pid");
         confess("Got new signal $SigName... quitting\n");
+    } else {
+       $SigName = shift;
     }
-    $SigName = shift;
 }
 
 #
@@ -1492,3 +1602,46 @@ sub ServerSocketInit
         $ServerInetPort = $Conf{ServerPort};
     }
 }
+
+#
+# Reload the server.  Used by Main_Process_Signal when $SigName eq "HUP"
+# or when the command "server reload" is received.
+#
+sub ServerReload
+{
+    my($mesg) = @_;
+    $mesg = $bpc->ConfigRead() || $mesg;
+    print(LOG $bpc->timeStamp, "$mesg\n");
+    $Info{ConfigModTime} = $bpc->ConfigMTime();
+    %Conf = $bpc->Conf();
+    umask($Conf{UmaskMode});
+    ServerSocketInit();
+    HostsUpdate(0);
+    $NextWakeup = 0;
+    $Info{ConfigLTime} = time;
+}
+
+#
+# Gracefully shutdown the server.  Used by Main_Process_Signal when
+# $SigName ne "" && $SigName ne "HUP" or when the command
+# "server shutdown" is received.
+#
+sub ServerShutdown
+{
+    my($mesg) = @_;
+    print(LOG $bpc->timeStamp, "$mesg\n");
+    if ( keys(%Jobs) ) {
+        foreach my $host ( keys(%Jobs) ) {
+            kill($bpc->sigName2num("INT"), $Jobs{$host}{pid});
+        }
+        sleep(1);
+        foreach my $host ( keys(%Jobs) ) {
+            kill($bpc->sigName2num("KILL"), $Jobs{$host}{pid});
+        }
+        %Jobs = ();
+    }
+    StatusWrite();
+    unlink("$TopDir/log/BackupPC.pid");
+    exit(1);
+}
+