p4checkpoint.pl #2

  • //
  • guest/
  • robert_cowham/
  • perforce/
  • utils/
  • p4checkpoint.pl
  • View
  • Commits
  • Open Download .zip Download (30 KB)
#!/usr/local/bin/perl
#
#  $Id: //guest/robert_cowham/perforce/utils/p4checkpoint.pl#2 $
#
# Modified by Robert Cowham (rc@vaccaperna.co.uk)
#   - make tar step configurable
#   - add Mailing option
#   - add verify option
#
#
# Copyright (c) 2000, Sandy Currier (sandy@releng.com)
# Distributed under the GNU GENERAL PUBLIC LICENSE:
#
#      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
#      the Free Software Foundation; either version 1, or (at your option)
#      any later version.
#
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
#
#      You should have received a copy of the GNU General Public License
#      along with this program; if not, write to the Free Software Foundation,
#      Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#
#
# This script will backup a perforce database
#
# This is done by:
#  - creating a new checkpoint
#  - keeping only a certain number of checkpoint files
#

#
# NOTE: to schedule this on NT (via the at command and the scheduler service)
#       'at 22:00 /every:M,T,W,Th,F,S,Su d:\perforce\backup.plx`
#
# NOTE: to schedule this on unix, use crontab (see manpages)
#
# BUT, the plx suffix must be a recognized file type (for the above to work)
#  1) goto MyComputer -> View <tab> -> Options... -> File Types <tab>
#  2) add a plx suffix (new type); fill in 'Description of Type' and
#     'associated extension'
#  3) click New <action>
#     a) the action is: 'open'
#     b) the application is something like 'c:\perl\bin\perl.exe "%1" %*'
#  That last bit is the Bill magic to pass args to perl scripts
#
# NOTE: to cron this on unix, add a cron entry
#

#
# first, see if unix or NT or what...
# need a recent version of perl on NT to have win32 module/config stuff
package main;

$SEND_MAIL = 0;     # Package assumed not to be installed

BEGIN: {
    require 5.004;
    unless ($Platform{'os'}) {
        unless ($Platform{'os'} = $^O) {
            use Config ();      # import nothing
            $Platform{'os'} = $Config::Config{'osname'};
        }
    }
    # You need to install this package if you want emails sent
    eval{use Mail::Sendmail qw(sendmail %mailcfg);};
    if (!$@) {
        $SEND_MAIL = 1;
    }
    # bottom layer OS specific variables/constants
    if ($Platform{'os'} =~ /Win/i) {
        #########################
        # win32
        #########################
        $Platform{'os'} = "win32";
        $Platform{'pd'} = '\\';
        $Platform{'cp'} = "xcopy /s /e /k /i";
        $Platform{'gzip'} = "gzip.exe";
        # Note on exit codes:
        # 0     Files were copied without error.
        # 1     No files were found to copy.
        # 2     The user pressed CTRL+C to terminate xcopy.
        # 4     Initialization error occurred. There is not enough
        #       memory or disk space, or you entered an invalid
        #       drive name or invalid syntax on the command line.
        # 5     Disk write error occurred.
    } elsif ($Platform{'os'}=~/vms/i) {
        #########################
        # vms
        #########################
        die "vms is currently not a supported platform";
    } elsif ($Platform{'os'}=~/os2/i) {
        #########################
        # os2
        #########################
        die "os2 is currently not a supported platform";
    } elsif ($Platform{'os'}=~/Mac/i or (defined($MacPerl::Version) and $MacPerl::Version)) {
        #########################
        # mac
        #########################
        $Platform{'pd'} = ':';  # use this in pathname pattern matching (mac)
        die "macintosh is currently not a supported platform";
    } else {
        #########################
        # unix
        #########################
        $Platform{'os'} = "unix";
        $Platform{'pd'} = '/';
        $Platform{'cp'} = "cp -rp";
        $Platform{'gzip'} = "gzip";
        # note on unix error codes
    }

    #
    # Unbuffer STDERR and STDOUT
    select(STDERR);
    $| = 1;                     # Make STDERR be unbuffered.
    select(STDOUT);
    $| = 1;                     # STDOUT too so, they can mix.

}

#
# set up some globale
# Note: assume that the PATH EV is going to be used to find p4
$err = "***";
$ThisCmd = "p4checkpoint.pl";   # this command name
$NetApp = "";                   # the name of the NetApp
$logfile_opened_p = 0;          # set when the logfile has been opened
$maxbackups = 5;                # the maximum number of backup copies
                                # to keep
$portnumber = "1666";           # the port number for p4port
$host = "perforce";             # the hostname to go with the above port number checkpoint
$stop_p = 0;                    # whether or not to stop/start the servers
$gzip_p = 0;                    # whether or not to gzip
$snap_p = 0;                    # whether or not to snapshot a Network Appliance
$tar_p = 0;                     # whether to create tars of depot (archive) files
$mail_p = 1;                    # whether to email the results
$verify_p = 1;                  # whether to run verify before checkpont
@depots = ();                   # the depots to tar up
$CkptName = "checkpoint";       # the filename (absolute or relative) of the checkpoint
$CkptSuffix = "ckp";
$JnlSuffix = "jnl";
$printonly = 0;
$fakeckp = 123;


# user overrides
$P4Port = "1666";
$P4InstallDir = "c:/program files/perforce";
$DepotRoot    = "c:/perforce";
$BackupDir    = "c:/perforce/backups";
$SnapshotDir  = "";

# Mail configuration
if ($SEND_MAIL) {
    if ($mail_p) {
        # Need to configure mail server and addresses
        $mailcfg{smtp} = [qw(smtp.aaisp.net.uk)];
        $mailcfg{port} = 25; # default
        $mailcfg{from} = 'Perforce Backup p4d@vaccaperna.co.uk'; # Email address required!
        # Decide who is going to get the mail - example for multiple users:
        # $MailTo = 'Someone <him@there.com>, Someone else her@there.com';
        $MailTo = 'rhc@vaccaperna.co.uk';
    }
}
else {
    $mail_p = 0;    # No point in being set if package not installed!
}

# set up the other global variable
sub SetGlobals {
    # Note: better to set the EV's so that it doesn't get printed all over the place
    $ENV{'P4CONFIG'} = "";
    $ENV{'P4PASSWD'} = "";
    $ENV{'P4USER'} = "";
    if ($Platform{'os'} eq "win32") {
        $P4Port       = "perforce:1666" unless ($P4Port);
        $P4InstallDir = "e:/perforce" unless ($P4InstallDir);
        $DepotRoot    = "e:/perforce" unless ($DepotRoot);
        $BackupDir    = "e:/perforce/backups" unless ($BackupDir);
        $SnapshotDir  = "d:/perforce/~snapshot/checkpoint/perforce" unless ($SnapshotDir);
        # the below are derived from above
        $P4           = "p4.exe";
        $P4D          = "p4d.exe";
        $CkptPname    = "$BackupDir/$CkptName";
        $CkptCmd      = &unix2dos("$P4D -r \"$DepotRoot\" -jc \"$CkptPname\"");
        $CkptJnlCmd   = &unix2dos("$P4D -r \"$DepotRoot\" -jj \"$CkptPname\"");
        # this one is odd - only used when snapshoting...
        $CkptFileCmd  = &unix2dos("$P4D -r \"$SnapshotDir\" -jd"); # the filename is generated on the fly
        $TarCmd       = &unix2dos("c:/toolkit/mksnt/tar");
        $StopCmd      = "net stop Perforce";
        $StartCmd     = "net start Perforce";
    }
    else {                      # unix
        $P4Port       = "$host:$portnumber" unless ($P4Port);
        $P4InstallDir = "/usr/local/bin" unless ($P4InstallDir);
        $DepotRoot    = "/perforce/perforce/p4files.$portnumber" unless ($DepotRoot);
        $BackupDir    = "/perforce/perforce/backups.$portnumber" unless ($BackupDir);
        $SnapshotDir  = "/perforce/.snapshot/checkpoint/perforce/p4files.$portnumber" unless ($SnapshotDir);
        # the below are derived from above
        $P4           = "$P4InstallDir/p4";
        $P4D          = "$P4InstallDir/p4d";
        $CkptPname    = "$BackupDir/$CkptName";
        $CkptCmd      = "$P4D -r $DepotRoot -jc $CkptPname";
        $CkptJnlCmd   = "$P4D -r $DepotRoot -jj $CkptPname";
        # this one is odd - only used when snapshoting...
        $CkptFileCmd  = "$P4D -r $SnapshotDir -jd"; # the filename is generated on the fly
        $TarCmd       = "/usr/local/bin/tar";
        $StopCmd      = "$P4 -p $P4Port admin stop";
        $StartCmd     = "$P4D -p $P4Port -d -r $DepotRoot -L $DepotRoot/p4d.log -J journal";
    }
    $VerifyCmd = "$P4 -p $P4Port verify -q //...";
    $VerifyUpdateCmd = "$P4 -p $P4Port verify -qu //...";
    $LogFile = "$BackupDir/backup.log";
    $SnapDeleteCmd = "sudo /bin/rsh $NetApp snap delete vol0 checkpoint";
    $SnapCreateCmd = "sudo /bin/rsh $NetApp snap create vol0 checkpoint";
}


#
# now parse any args
# the usage message (for -h or on error)
$help = "$ThisCmd portnumber [options...]
Function:
    This command will checkpoint a perforce repository.
    The checkpoint command is: '$CkptCmd'

Args:
    portnumber       The port number for the P4PORT to backup.
                     Several variables are derived from this value.

Switches/Options:
    -h               Prints this help message
    -n               Will not run write to disk - mostly print
    -maxbackups N    Specify the number of backup files
                     to retain (def = $maxbackups)
    -depotroot <str> Specify another depot root
";

# parse command line
{
    my($i);
    my($param) = 0;
    while($i <= $#ARGV) {
        # scan for a help switch
        if ($ARGV[$i] =~ /^-h/i) {
            &DieHelp("", $help);
        }
        elsif ($ARGV[$i] =~ /^-n/) {
            $printonly = 1;
            $i++;
        }
        # scan for variable definitions (-variable value)
        elsif ($ARGV[$i] =~ /^-\w+/ and defined($ARGV[$i+1]) and $ARGV[$i+1] !~ /^-[^-]/) {
            # NOTE: nt has a difficult time with '=' on a command line...
            # process any variable value switches
            my($var) = $ARGV[$i];
            $var =~ s/^-//;
            my($value) = $ARGV[$i+1];
            if (defined $$var) {
                $$var = $value;
            }
            else {
                &DieHelp("Unknown parameter '$var'\n", $help);
            }
            $i=$i+2;
        }
        # catch unsupported switches
        elsif ($ARGV[$i] =~ /^-/) {
            &DieHelp("Unsupported switch \"$ARGV[$i]\"\n", $help);
        }
        elsif ($param == 0) {
            $portnumber = $ARGV[$i];
            $i++;
            $param++;
        }
        else {
            &DieHelp("Extra args: @ARGV\n", $help);
        }
    }
}

#
# Note: if the user overwrote the $DepotRoot value...
$DepotRoot = &other2unix($DepotRoot) if ($DepotRoot);
&SetGlobals();

#
# algorithm:
#  verify depot files
#  stop the perforce service
#  create a new checkpoint
#  create a new tar file
#  if this is the 7th day of the month, restart the databases
#  start the perforce service
#  limit number of each thing
# algorithm ($snapshot_p == 1)
#  get a list of depots
#  stop the server
#  truncate the journal (will bump the journal counter)
#  create the snapshot
#  restart the server
#  moving to the snapshot directorty:
#    create a checkpoint (do not truncate the journal nor bump the counter)
#    tar up the depots??? - maybe not
#
# the magic command (must by verbatum)
# sudo /bin/rsh $NetApp snap delete vol0 checkpoint
# sudo /bin/rsh $NetApp snap create vol0 checkpoint
# sudo /bin/rsh $NetApp snap list vol0
#
# so, delete snapshot, stop server, journal checkpoint, snapshot, start server,
#     cd to snapshot,
#       create checkpoint file only (into backupdir), tar into backupdir, punt
#       gzip (no need), exit
#

#
# In the beginning, log the time
$tmp = &TimeString(time);
&PrintAndLog(">>> $ThisCmd: beginning backup at: $tmp\n");

#
# But first, cd there...
{
    if (!-d $P4InstallDir) {
        &AbortOnError("$ThisCmd: error - the p4installdir directory $P4InstallDir is not a directory\n");
    }
    if (!-d $DepotRoot) {
        &AbortOnError("$ThisCmd: error - the p4root directory $DepotRoot is not a directory or doesn't exist\n");
    }
    if (!-d $BackupDir) {
        &AbortOnError("$ThisCmd: error - the backup directory $BackupDir is not a directory or doesn't exist\n");
    }
    &PrintAndLog(">>> chdir to $P4InstallDir\n");
    $tmp = chdir $P4InstallDir;
    unless ($tmp) {
        &AbortOnError("$ThisCmd: error - could not cd to $P4InstallDir\n$!");
    }
}

#
# get the active depots first
{
    local($printonly) = 0;      # do it even if $printonly
    @depots = &GetDepots();
}

#
# if snapshot'ing, do one thing; else, the other
if ($snap_p) {                  # snapshots...
    my($output);

    #
    # 1a, verify archive files
    if ($verify_p) {
        $tmp = &TimeString(time);
        &PrintAndLog(">>> $ThisCmd: verifying archive files at: $tmp\n");
        &VerifyArchives();
    }

    #
    # delete old checkpoint
    $output = &ExecuteCommand($SnapDeleteCmd);
    if ($output !~ /^(deleting snapshot\.+|No such snapshot.)$/ and !$printonly) {
        &AbortOnError("$ThisCmd: could not delete snapshot checkpoint\n$output");
    }
    else {
        &PrintAndLog($output);
    }

    #
    # stop server
    if ($stop_p) {
        $tmp = &TimeString(time);
        &PrintAndLog(">>> $ThisCmd: stopping perforce server at: $tmp\n");
        &StopServer();
    }

    #
    # truncate the journal
    ($ckptnumber, $journalfile) = &Truncate();

    #
    # snapshot
    $output = &ExecuteCommand($SnapCreateCmd);
    if ($output !~ /^creating snapshot\.+$/ and !$printonly) {
        &AbortOnError("$ThisCmd: could not create snapshot checkpoint\n$output");
    }
    else {
        &PrintAndLog($output);
    }

    #
    # start server
    if ($stop_p) {
        $tmp = &TimeString(time);
        &PrintAndLog(">>> $ThisCmd: starting perforce server at: $tmp\n");
        &StartServer();
    }

    #
    # checkpoint snapshot dir (writing to backup dir)
    &CheckPointOnly($ckptnumber);

    #
    # third, tar up all depots - after finding all depots
    if ($tar_p) {
        foreach my $depot (@depots) {
            # however, if the depot directory does not exist, just print and punt
            if (! -d "$SnapshotDir/$depot") {
                &PrintAndLog("Warning: $depot directory (in $SnapshotDir) does not exist - punting...\n");
            }
            else {
                &ExecuteCommand("$TarCmd -C \"$SnapshotDir\" -cf \"$BackupDir/$depot.$ckptnumber.tar\" $depot");
            }
        }
    }
}
else {                          # no snapshots...

    #
    # 1a, verify archive files
    if ($verify_p) {
        $tmp = &TimeString(time);
        &PrintAndLog(">>> $ThisCmd: verifying archive files at: $tmp\n");
        &VerifyArchives();
    }

    #
    # 1b, stop perforce
    if ($stop_p) {
        $tmp = &TimeString(time);
        &PrintAndLog(">>> $ThisCmd: stopping perforce server at: $tmp\n");
        &StopServer();
    }

    #
    # second, checkpoint the meta-data
    # note: need to save the checkpoint number and the journal file name for later...
    ($ckptnumber, $journalfile) = &CheckPoint();

    #
    # third, tar up all depots - after finding all depots
    if ($tar_p) {
        foreach my $depot (@depots) {
            # however, if the depot directory doesnot exist, just print and punt
            if (! -d "$DepotRoot/$depot") {
                &PrintAndLog("Warning: $depot directory (in $DepotRoot) does not exist - punting...\n");
            }
            else {
                &ExecuteCommand("$TarCmd -C \"$DepotRoot\" -cf \"$BackupDir/$depot.$ckptnumber.tar\" $depot");
            }
        }
    }

    #
    # fourth, restart perforce
    if ($stop_p) {
        $tmp = &TimeString(time);
        &PrintAndLog(">>> $ThisCmd: starting perforce server at: $tmp\n");
        &StartServer();
    }
}

#
# at this point, do the same thing whether or not snapshoting

#
# fifth, limit the number of checkpoints
{
    my(@checkpoints) = &GetCkpts($BackupDir, $CkptName, $CkptSuffix);
    &DeleteExtras($BackupDir, @checkpoints);
}

#
# sixth, limit the number of tar files (a no-op in the snapshot case)
if ($tar_p) {
    foreach my $depot (@depots) {
        my(@tarfiles) = &GetTars($BackupDir, $depot, "tar");
        &DeleteExtras($BackupDir, @tarfiles);
    }
}

#
# seventh, limit the number of journal files
{
    my(@checkpoints) = &GetJournals($BackupDir, $CkptName, $JnlSuffix);
    &DeleteExtras($BackupDir, @checkpoints);
}

#
# eighth, compress the tar files and journal files
# note: gzip compresses in place...
if ($gzip_p) {
    if ($tar_p) {
        foreach my $depot (@depots) {
            if (-r "$BackupDir/$depot.$ckptnumber.tar") {
                &ExecuteCommand("$Platform{'gzip'} \"$BackupDir/$depot.$ckptnumber.tar\"");
            }
        }
    }
    &ExecuteCommand("$Platform{'gzip'} $CkptPname.ckp.$ckptnumber")
        if (-r "$CkptPname.ckp.$ckptnumber");
    &ExecuteCommand("$Platform{'gzip'} $journalfile")
        if (-r $journalfile);
}

#
# and in the end, the love we take, is equal to the love we make...
$tmp = &TimeString(time);
$msg = ">>> $ThisCmd: ending backup at: $tmp\n";
&PrintAndLog($msg);
&MailMsg("Finished successful backup", $msg);
if ($logfile_opened_p) {
    close(LOG);
}
exit(0);

#
# will cleanly abort when in the middle of stuff, trying to restart the server if stopped
sub AbortOnError {
    my($string) = @_;
    &PrintErrorAndLog("$err $string\n") if ($string);
    &MailMsg("Error with backup", "$err $string");
    if ($stopped_p) {
        $stopped_p = 0;         # so to avoid an infinite loop...
        &ExecuteCommandSystem($StartCmd);
    }
    if ($logfile_opened_p) {
        close(LOG);
    }
    exit(1);
}

#
# Send appropriate Mail
sub MailMsg {
    my($subject, $msg) = @_;
    return if !$mail_p;
    %mail = ( To      => $MailTo,
              Subject => $subject,
              Message => $msg
           );
    if (sendmail(%mail)) {
        $logmsg = "Successfully sent mail\n"
    }
    else {
        $logmsg = "$err Failed to send mail $Mail::Sendmail::error \n"
    }
    &PrintAndLog($logmsg);
}

#
# will execute a random OS command
sub ExecuteCommand {
    my($cmd, $ignore_error) = @_;
    my($tmp);
    unless ($printonly) {
        &PrintAndLog("running: $cmd\n");
        # special cased commands
        $tmp = `$cmd 2>&1`;
        if ($? and !$ignore_error) {
            &AbortOnError("$ThisCmd: error - could not execute $cmd\n$?");
        }
    }
    else {
        &PrintAndLog("not running: $cmd\n");
    }
    return($tmp);
}

#
# a system version of the above (sometimes needed in strange situations where the
# fork (note the -d switch to p4d) will get hung up, like during perldb...)
sub ExecuteCommandSystem {
    my($cmd, $ignore_error) = @_;
    my($tmp);
    unless ($printonly) {
        &PrintAndLog("running: $cmd\n");
        # special cased commands
        system($cmd);
        if ($? and !$ignore_error) {
            &AbortOnError("$ThisCmd: error - could not execute $cmd\n$?");
        }
    }
    else {
        &PrintAndLog("not running: $cmd\n");
    }
    return($tmp);
}

#
# will delete extra backups of stuff
# NOTE: @_ must be an time ordered array of old to youngest copies
sub DeleteExtras {
    my($directory, @array) = @_;
    my($i, $tmp);
    if ($#array >= $maxbackups) {
        # delete some
        for ($i=0; $i<=$#array - $maxbackups; $i++) {
            # delete it
            &PrintAndLog("running: unlink \"$directory/$array[$i]\"\n");
            unless ($printonly) {
                $tmp = unlink "$directory/$array[$i]";
                unless ($tmp) {
                    &AbortOnError("$ThisCmd: error - could not delete old checkpoint file \"$directory/$array[$i]\"\n$?");
                }
            }
        }
    }
}

#
# will convert a random OS delimited pathname to a perl pathname
sub other2unix {
    my($filename) = @_;
    my($pattern) = $Platform{'pd'};
    $pattern =~ s/(\W)/\\$1/g;  # escape wildchars
    $filename =~ s|$pattern|/|g;
    # if just "^/..." but not "^//..." (which could either by a UNC name
    # or perforce depot name)
    if ($Platform{'os'} eq 'win32' and $filename =~ /^\/[^\/]/) {
        # try to convert to a drive letter - ignore errors at this low a level
        my($tmp) = `cd`;
        if ($tmp =~ /^([a-zA-Z]:)/) {
            return($1);
        }
    }
    return("/") if ($filename =~ /^\/+$/); # if just /+, return just /
    if ($filename =~ /^\/\//) {
        # add them back in later
        $filename =~ s|/+|/|g;  # remove doubles
        $filename = "/$filename";
    }
    else {
        $filename =~ s|/+|/|g;  # remove doubles
    }
    # remove trailing
    $filename =~ s|/+$||;
    return($filename);
}

#
# blindly converts "/" to "\"
sub unix2dos {
    my($filename) = @_;
    $filename =~ s|/|\\|g;
    return($filename);
}


#
# will print a help message and then exit
sub DieHelp {
    my($str, $help) = @_;
    print STDERR "$err $str\nUsage: $help";
    exit(2);
}

#
# get the depots
sub GetDepots {
    my($command) = "$P4 -p $P4Port depots";
    my(@depots, $tmp);
    $tmp = &ExecuteCommand($command);
    @depots = split('\n', $tmp);
    chomp(@depots);
    if ($#depots < 0) {
        @depots = ("depot");
    }
    else {
        # only do local depots
        @depots = grep(/\d{4}\/\d{2}\/\d{2} local subdir/, @depots);
        foreach (@depots) {
            s|^.*\d{4}/\d{2}/\d{2} local subdir ([^\/]+).*$|$1|;
        }
    }
    return(@depots);
}

#
# will return an ordered list of backups (whether or not gzip'ed)
sub GetCkpts {
    my($directory, $CkptName, $suffix) = @_;
    my(@filenames, @tmp);
    # read the directory
    if (! -d $directory) {
        &PrintErrorAndLog("$err GetCkpts - '$directory' is not a dir\n");
        return;
    }
    if (!opendir(THEDIR,$directory)) {
        &PrintErrorAndLog("$err GetCkpts - cannot open $directory for reading\n");
        return;
    }
    @tmp = grep(/^$CkptName\.$suffix\.[0-9]+/,readdir(THEDIR));
    closedir(THEDIR);
    @filenames = sort sortbyckptname (@tmp);
    return(@filenames);
}

#
# will return an ordered list of backups (whether or not gzip'ed)
sub GetJournals {
    my($directory, $CkptName, $suffix) = @_;
    my(@filenames, @tmp);
    # read the directory
    if (! -d $directory) {
        &PrintErrorAndLog("$err GetJournals - '$directory' is not a dir\n");
        return;
    }
    if (!opendir(THEDIR,$directory)) {
        &PrintErrorAndLog("$err GetJournals - cannot open $directory for reading\n");
        return;
    }
    @tmp = grep(/^$CkptName\.$suffix\.[0-9]+/,readdir(THEDIR));
    closedir(THEDIR);
    @filenames = sort sortbyckptname (@tmp);
    return(@filenames);
}

#
# will return an ordered list of backups (whether or not gzip'ed)
sub GetTars {
    my($directory, $name, $suffix) = @_;
    my(@filenames, @tmp);
    # read the directory
    if (! -d $directory) {
        &PrintErrorAndLog("$err GetCkpts - '$directory' is not a dir\n");
        return;
    }
    if (!opendir(THEDIR,$directory)) {
        &PrintErrorAndLog("$err GetCkpts - cannot open $directory for reading\n");
        return;
    }
    @tmp = grep(/^$name\.[0-9]+\.$suffix/,readdir(THEDIR));
    closedir(THEDIR);
    @filenames = sort sortbytarname (@tmp);
    return(@filenames);
}

#
# will return an ordered list of backups
sub GetNextTarfileName {
    my($directory) = @_;
    my(@tmp, $newname);
    # read the directory
    if (! -d $directory) {
        &PrintErrorAndLog("$err GetNextTarfileName - '$directory' is not a dir\n");
        return;
    }
    if (!opendir(THEDIR,$directory)) {
        &PrintErrorAndLog("$err GetNextTarfileName - cannot open $directory for reading\n");
        return;
    }
    @tmp = grep(/^depot\.[0-9]+\.tar$/,readdir(THEDIR));
    closedir(THEDIR);
    @filenames = reverse sort sortbytarname (@tmp);
    $newname = $filenames[0];
    # now increment the middle field
    @tmp = split(/\./, $newname);
    $tmp[1]++;
    $newname = join("", @tmp);
    return($newname);
}

#
# sort numerically
sub sortbyckptname {
    my($tmpa, $tmpb, $junk);
    ($junk, $junk, $tmpa) = split(/\./, $a);
    ($junk, $junk, $tmpb) = split(/\./, $b);
    $tmpa <=> $tmpb;
}

#
# sort numerically
sub sortbytarname {
    my($tmpa, $tmpb, $junk);
    ($junk, $tmpa, $junk) = split(/\./, $a, 3);
    ($junk, $tmpb, $junk) = split(/\./, $b, 3);
    $tmpa <=> $tmpb;
}

sub PrintAndLog {
    my($string) = @_;
    if ($LogFile and !$printonly) {
        unless ($logfile_opened_p) {
            if (!open(LOG, ">>$LogFile")) {
                print STDERR "$err $ThisCmd: could not open logfile $LogFile\n$!\n";
                $LogFile = "";  # do not do it again
            }
            else {
                $logfile_opened_p = 1;
            }
        }
        if ($LogFile) {
            print LOG $string;
        }
    }
    print STDOUT $string;
}

sub PrintErrorAndLog {
    my($string) = @_;
    if ($LogFile) {
        unless ($logfile_opened_p) {
            if (!open(LOG, ">>$LogFile")) {
                print STDERR "$err $ThisCmd: could not open logfile $LogFile\n$!\n";
                $LogFile = "";  # do not do it again
            }
            else {
                $logfile_opened_p = 1;
            }
        }
        if ($LogFile) {
            print LOG $string;
        }
    }
   print STDERR $string;
}

# will print time in a yyyymmdd.hhmmss format
sub TimeString {
    my($time) = @_;
    my(@ltime);
    # Normally: ($sec,$min,$hour,$mday,$mon,$year) = localtime($time);
    @ltime = localtime($time);
    # do not forget to add 1900 to the century, and 1 to the month
    return(sprintf("%04d%02d%02d.%02d%02d%02d",
                   ($ltime[5]+1900), $ltime[4]+1, $ltime[3],
                   $ltime[2], $ltime[1], $ltime[0]));
}

# returns true if true
sub VerifyServerState {
    my($string) = @_;
    # see if server is running or stopped
    my(@output);
    @output = &ExecuteCommand("$P4 -p $P4Port info", 1);
    if ($string eq "stop") {
        if (grep(/^Perforce client error:/, @output) and grep(/Connect to server failed/, @output)) {
            return(1);
        }
        else {
            return(0);
        }
    }
    else {
        if (grep(/^Perforce client error:/, @output) and grep(/Connect to server failed/, @output)) {
            return(0);
        }
        else {
            return(1);
        }
    }
}

# stop the server
sub StopServer {
    my($tmp) = &ExecuteCommand($StopCmd);
    # wait 5 seconds
    sleep 5;
    # verify the server is stopped
    $tmp = &VerifyServerState("stop");
    unless ($tmp or $printonly) {
        &AbortOnError("$ThisCmd: error - couldn't stop the perforce server\n$tmp");
    }
    $stopped_p = 1;
}

# start server
sub StartServer {
    my($tmp) = &ExecuteCommandSystem($StartCmd);
    if ($tmp) {
        &AbortOnError("$ThisCmd: error - something went wrong with perforce server start\n$tmp");
    }
    # sleep for 5 seconds
    sleep 5;
    # verify the server is stopped
    $tmp = &VerifyServerState("start");
    unless ($tmp or $printonly) {
        &AbortOnError("$ThisCmd: error - couldn't start the perforce server\n$tmp");
    }
    $stopped_p = 0;
}

# verify archive files
sub VerifyArchives {
    my($output) = &ExecuteCommand($VerifyCmd);
    if ($output) {
        &PrintErrorAndLog("$ThisCmd: error - verifying archive files\n$output");
        &MailMsg("Verify error", $output);
    }
    $output = &ExecuteCommand($VerifyUpdateCmd);    # Update signatures for new files
    if ($output) {
        &PrintErrorAndLog("$ThisCmd: error - updating archive file signatures\n$output");
    }
}

# checkpoint the database and truncate (rename) the journal file (bumping the journal counter)
sub CheckPoint {
    my($ckpt_output) = &ExecuteCommand($CkptCmd);
    my($ckptnumber, $journalfile);
    $ckpt_output = &other2unix($ckpt_output); # make sure that everything is unix
    # Note: inspect $ckpt_output to determine checkpoint number
    unless ($printonly) {
        unless ($ckpt_output =~ /^Checkpointing to $CkptPname\.$CkptSuffix\.([0-9]+)/i) {
            $ckptnumber = $1;   # save away the ckptnumber for later
            &AbortOnError("$ThisCmd: error - trouble creating checkpoint\n$ckpt_output");
        }
        else {
            $ckptnumber = $1;   # save away the ckptnumber for later
            &PrintAndLog("$ckpt_output");
        }
        if ($ckptnumber eq "") {
            &AbortOnError("$ThisCmd: internal error - checkpoint number is nil");
        }
        # note: get journal file too...
        unless ($ckpt_output =~ /Saving journal to (.*\.[0-9]+)/i) {
            $journalfile = $1;
            &PrintErrorAndLog("$ThisCmd: error - trouble creating journal\n$ckpt_output");
        }
        else {
            $journalfile = $1;
        }
    }
    else {
        $ckptnumber = $fakeckp;
        my($foo) = $fakeckp - 1;
        $journalfile = "$CkptPname.$JnlSuffix.$foo";
    }
    return($ckptnumber, $journalfile);
}

# truncate (rename) the journal file (bumping the journal counter)
sub Truncate {
    my($ckpt_output) = &ExecuteCommand($CkptJnlCmd);
    my($ckptnumber, $journalfile);
    $ckpt_output = &other2unix($ckpt_output); # make sure that everything is unix
    # note: get journal file too...
    unless ($printonly) {
        unless ($ckpt_output =~ /^Saving journal to ($CkptPname\.$JnlSuffix\.[0-9]+)/i) {
            $journalfile = $1;
            &PrintErrorAndLog("$ThisCmd: error - trouble creating journal\n$ckpt_output");
        }
        else {
            $journalfile = $1;
            &PrintAndLog("$ckpt_output");
        }
        $ckpt_output =~ /^Saving journal to $CkptPname\.$JnlSuffix\.([0-9]+)/i;
        $ckptnumber = $1;
        if ($ckptnumber eq "") {
            &AbortOnError("$ThisCmd: internal error - checkpoint number is nil");
        }
        # journal numbers are always one less than a ckptnumber...
        $ckptnumber++;
    }
    else {
        $ckptnumber = $fakeckp;
        my($foo) = $fakeckp - 1;
        $journalfile = "$CkptPname.$JnlSuffix.$foo";
    }
    return($ckptnumber, $journalfile);
}

# checkpoint the database but no journal truncate
sub CheckPointOnly {
    my($number) = @_;
    my($ckpt_output) = &ExecuteCommand("$CkptFileCmd \"$CkptPname.$CkptSuffix.$number\"");
    my($ckptnumber);
    $ckpt_output = &other2unix($ckpt_output); # make sure that everything is unix
    # Note: inspect $ckpt_output to determine checkpoint number
    unless ($printonly) {
        unless ($ckpt_output =~ /^Dumping to $CkptPname\.$CkptSuffix\.([0-9]+)/i) {
            &AbortOnError("$ThisCmd: error - trouble creating checkpoint\n$ckpt_output");
        }
        else {
            &PrintAndLog("$ckpt_output");
        }
    }
}

# Change User Description Committed
#2 3730 Robert Cowham Added verify option
#1 3729 Robert Cowham Rename //guest/robert_cowham/perforce/utils/backup.pl To //guest/robert_cowham/perforce/utils/p4checkpoint.pl
//guest/robert_cowham/perforce/utils/backup.pl
#3 3726 Robert Cowham Added Emailing (configurable)
#2 3724 Robert Cowham Make tar step configurable
#1 3722 Robert Cowham Initial version
//guest/sandy_currier/utils/backup.pl
#3 912 sandy_currier these versions should all be xtext only
#2 307 sandy_currier some minor updates
#1 294 sandy_currier initial public versions of some personally useful scripts