6.7. Backing Up a Repository A CVS repository can be backed up using the same backup tools and schedule that you use for ordinary text and binary files. You must be able to restore the repository with the same permissions and structure it had when it was backed up. You must restore the CVSROOT directory and its contents before the project directories are useful, but you can restore project root directories and their contents independently of each other. If a file in a repository is being written to while a backup is in progress, it is possible that the file could be backed up in a state that might make it difficult for CVS to read it accurately when it is restored. For this reason, you should prevent processes from writing to the repository during a backup. 6.7.1. Freezing a Repository "Freezing a repository" is CVS-speak for the act of preventing users from changing the repository. The most common reason to freeze a repository is to ensure that a backup operation copies all the repository files in a consistent state. The simplest way to freeze a repository is to block clients from accessing the CVS server. There are many ways to do this, such as shutting down the CVS server program, disallowing logins to the server system, blocking the server with a firewall rule, setting the server to single-user mode, or pulling out the network cable. Rather than blocking access to the server completely, you can use CVS's locks to read-lock the repository. To keep the server running and allow clients to continue to read the repository files while a backup or other critical work takes place, use a script that creates lock files like CVS's internal locks. CVS honors these locks as if they were its own. Lock the directories you want to back up, make your backup, and then unlock the directories. See "Locks," later in this chapter, for a full explanation of CVS locks. Ideally, you should write your backup script to leave out the lock files when it's backing up CVS; if you don't, you'll need to remove those locks as part of the restoration. Example 6-8 shows a script that locks an entire repository. It attempts to lock the repository and backs off if it can't lock the whole repository. The backing off is necessary to prevent deadlocks. Example 6-9 shows a script that unlocks a repository. If it can't lock the repository within nine attempts, the script backs off, reports an error, and exits. This prevents it from trying endlessly when there's a stale lock from a crashed CVS program. (See "Clearing Locks," later in this chapter, for how to fix stale locks.) Example 6-8. Script to lock a CVS repository #!/bin/sh # Freeze - Lock a whole repository for backup. KEYMAGIC=$$ TMPFILE=/tmp/freeze.$KEYMAGIC bail_out ( ) { # Exit abruptly. Return a failure code and display # an error message. echo "$1" rm -f $TMPFILE exit 1 } freeze_directory ( ) { echo "FREEZE: $1" # Obtain the master lock mkdir "$1/#cvs.lock" if [ $? != 0 ] then # Could not get master lock return 1 fi # Create the read lock touch "$1/#cvs.rfl.$KEYMAGIC" # Record it in case of trouble echo $1 >> $TMPFILE rmdir "$1/#cvs.lock" return 0 } thaw_repository ( ) { # If we encounter anyone else playing with the # CVS locks during this, then there's a small risk # of deadlock. In that event, we should undo everything # we've done to the repository, wait and try again. # This function removes all the locks we've produced during # the run so far. for dir in 'cat $TMPFILE' do echo "** THAW ** $dir" mkdir "$dir/#cvs.lock" if [ $? ] then # Remove read lock rm -f "$dir/#cvs.rfl.$KEYMAGIC" # Remove masterlock rmdir "$dir/#cvs.lock" fi done return 0 } freeze_repository ( ) { for dirname in 'find $CVSROOT/$REPOSITORY -type d ! -iname CVS ! \ -iname Attic ! -iname "#cvs.lock"' do freeze_directory $dirname if [ $? != 0 ] then # We couldn't get the master lock. # Someone else must be working on the # repository thaw_repository return 1 fi done return 0 } if [ "$CVSROOT" = = "" ] then echo "No CVSROOT specified in the environment" bail_out "Aborting" fi if [ "$KEYROOT" = = "" ] then KEYROOT="$CVSROOT" fi if [ "$1" = = "" ] then echo "No Repository specified." echo "Usage: $0 repository" bail_out "Aborting" else REPOSITORY="$1" fi # Double-check the validity of supplied paths KEYFILE=$KEYROOT/.$REPOSITORY test -d $CVSROOT || bail_out "Can't access $CVSROOT - is it a directory?" touch $KEYFILE || bail_out "Can't access $KEYFILE - aborting" TRIES=0 while ! freeze_repository do let TRIES=$TRIES+1 echo "Could not freeze. Repository in use. (Attempt $TRIES)" if [ $TRIES -gt 9 ] then bail_out "Giving up" fi echo " Sleeping 1 second." sleep 1 rm -f $TMPFILE echo "Trying again.." done echo "** Repository $REPOSITORY frozen" echo "$KEYMAGIC" >> $KEYROOT/.$REPOSITORY rm -f $TMPFILE exit 0 | Example 6-9. Script to unlock a CVS repository #!/bin/sh # Unfreeze - Unlock a whole repository. bail_out ( ) { # Exit abruptly. Return a failure code and display # an error message. echo "$*" rm -f $TMPFILE exit 1 } unfreeze_directory ( ) { echo "UNFREEZE: $1" mkdir "$1/#cvs.lock" if [ $? != 0 ] then # Could not get master lock return 1 fi test -f "$1/#cvs.rfl.$KEYMAGIC" || echo "THAW: Expected to find a lock file: \ $1/#cvs.rfl.$KEYMAGIC" # Proceed anyway. rm -f "$1/#cvs.rfl.$KEYMAGIC" rmdir "$1/#cvs.lock" return 0 } unfreeze_repository ( ) { TMPFILE=/tmp/freeze.$KEYMAGIC for dirname in 'find $CVSROOT/$REPOSITORY -type d ! -iname CVS ! \ -iname Attic ! -iname "#cvs.lock"' do unfreeze_directory $dirname if [ $? != 0 ] then return 1 fi done return 0 } if [ "$CVSROOT" = = "" ] then echo "No CVSROOT specified in the environment" bail_out "Aborting" fi if [ "$KEYROOT" = = "" ] then KEYROOT="$CVSROOT" fi if [ "$1" = = "" ] then echo "No Repository specified." echo "Usage: $0 repository" bail_out "Aborting" else REPOSITORY="$1" fi # Double-check the validity of supplied paths KEYFILE=$KEYROOT/.$REPOSITORY test -d $CVSROOT || bail_out "Can't access $CVSROOT - is it a directory?" test -f $KEYFILE || bail_out "No $KEYFILE appears to exist. Repository does \ not appear to be frozen" TRIES=0 # Walk through each of the keys that the repository has been frozen with # and unlock each one in turn. A single run of unfreeze thaws multiple # runs of freeze. for KEYMAGIC in 'cat $KEYFILE' do unfreeze_repository if [ "$?" = "1" ] then echo "** Unable to obtain master locks for all directories." let TRIES=$TRIES+1 if [ "$TRIES" = "10" ] then bail_out "Too many attempts. Giving up." fi sleep 1 echo "** Trying again." else echo "** Repository $REPOSITORY thawed from freeze $KEYMAGIC" fi done echo "** Unfreeze complete" echo "** Repository $REPOSITORY thawed" rm -f $KEYFILE exit 0 | | Because the for loop in bash breaks on spaces, this script will not work if there are spaces in the filenames. A variant of this script, done in a language such as Perl, Python, or Ruby, would work. |
| If you need to freeze the repository entirely, preventing anyone from either reading or writing, modify the scripts in Examples 6-8 and 6-9 to create and remove write locks. For example, you need to do this when attempting to restore a backup of the whole repository. 6.7.2. Restoring a Backup A CVS repository can be restored using the same tools that you use to restore ordinary text and binary files. You must restore the CVSROOT directory and its contents to be able to use the project files reliably. You need not restore every project in the repository, but when you restore a project, you should restore all its files and directories. | If the repository was locked for backup, and the locks were not removed in the backup script, remove them before the repository can be used again. |
|
When you restore a repository, you must ensure that no one can write to it. You can do this by freezing the repository as described earlier in "Freezing a Repository," but if you use locks, you must use write locks rather than the read locks described in that section, because the repository will be in an inconsistent state while the files are being restored; you don't want anyone to read from or write to the repository during the restore process. After you have restored a CVS repository from a backup, it is safest to assume that any sandbox files are based on revisions that do not exist in the backup of the repository. This means that individual developers are likely to have changes in their sandboxes that need to be committed (or recommitted) to the repository in order to bring it up to date. Here is the simplest way to restore changes that have been preserved in sandboxes: Check out a new sandbox from the restored repository. Try to use commands such as cvs diff, cvs status, and cvs update on a sandbox from before the repository was restored, to determine which files have been changed and what the changes are. If this does not work, try using the Unix diff program to determine the differences between an old and the new sandbox. Copy the old sandbox files you want to commit or recommit into the new sandbox. Commit the changes as if they were ordinary changes. Make sure the log message indicates what happened. |