/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
*/
/**
* A backup manager for JE backends.
*/
public class BackupManager
{
/**
* The tracer object for the debug logger.
*/
/**
* The common prefix for archive files.
*/
/**
* The name of the property that holds the name of the latest log file
* at the time the backup was created.
*/
/**
* The name of the property that holds the size of the latest log file
* at the time the backup was created.
*/
/**
* The name of the entry in an incremental backup archive file
* containing a list of log files that are unchanged since the
* previous backup.
*/
/**
* The name of a dummy entry in the backup archive file that will act
* as a placeholder in case a backup is done on an empty backend.
*/
/**
* The backend ID.
*/
/**
* Construct a backup manager for a JE backend.
* @param backendID The ID of the backend instance for which a backup
* manager is required.
*/
{
}
/**
* Create a backup of the JE backend. The backup is stored in a single zip
* file in the backup directory. If the backup is incremental, then the
* first entry in the zip is a text file containing a list of all the JE
* log files that are unchanged since the previous backup. The remaining
* zip entries are the JE log files themselves, which, for an incremental,
* only include those files that have changed.
* @param backendDir The directory of the backend instance for
* which the backup is required.
* @param backupConfig The configuration to use when performing the backup.
* @throws DirectoryException If a Directory Server error occurs.
*/
throws DirectoryException
{
// Get the properties to use for the backup.
// Create a hash map that will hold the extra backup property information
// for this backup.
// Get the crypto manager and use it to obtain references to the message
if (hash)
{
if (signHash)
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
throw new DirectoryException(
}
}
else
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
throw new DirectoryException(
}
}
}
// Date the backup.
// If this is an incremental, determine the base backup for this backup.
/*
FilenameFilter backupTagFilter = new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.startsWith(BackupInfo.PROPERTY_BACKUP_ID);
}
};
*/
if (incremental)
{
if (incrBaseID == null)
{
// The default is to use the latest backup as base.
{
}
}
// Get the set of possible base backups from the current database.
/*
String[] files = backendDir.list(backupTagFilter);
if (files == null || files.length == 0)
{
// Incremental not allowed until after a full.
Message msg = ERR_JEB_INCR_BACKUP_REQUIRES_FULL.get();
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
msg);
}
HashSet<String> backups = new HashSet<String>();
int prefixLen = BackupInfo.PROPERTY_BACKUP_ID.length()+1;
for (String s : files)
{
String actualBaseID = s.substring(prefixLen);
backups.add(actualBaseID);
}
// Check that it makes sense to do this incremental.
if (incrBaseID == null || !backups.contains(incrBaseID))
{
Message msg =
ERR_JEB_INCR_BACKUP_FROM_WRONG_BASE.get(backups.toString());
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
msg);
}
*/
if (incrBaseID == null)
{
// No incremental backup ID: log a message informing that a backup
// could not be found and that a normal backup will be done.
incremental = false;
}
else
{
}
}
// Get information about the latest log file from the base backup.
long latestFileSize = 0;
if (baseBackup != null)
{
}
// Create an output stream that will be used to write the archive file. At
// its core, it will be a file output stream to put a file on the disk. If
// we are to encrypt the data, then that file output stream will be wrapped
// in a cipher output stream. The resulting output stream will then be
// wrapped by a zip output stream (which may or may not actually use
// compression).
try
{
if (archiveFile.exists())
{
int i=1;
while (true)
{
archiveFilename + "." + i);
if (archiveFile.exists())
{
i++;
}
else
{
break;
}
}
}
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
// If we should encrypt the data, then wrap the output stream in a cipher
// output stream.
if (encrypt)
{
try
{
}
catch (CryptoManagerException e)
{
if (debugEnabled())
{
}
message, e);
}
}
// Wrap the file output stream in a zip output stream.
if (compress)
{
}
else
{
}
// Record this backup in the database itself.
/*
String backupTag = BackupInfo.PROPERTY_BACKUP_ID + "_" + backupID;
File tagFile = new File(backendDir, backupTag);
try
{
tagFile.createNewFile();
}
catch (IOException e)
{
assert debugException(CLASS_NAME, "createBackup", e);
Message msg = ERR_JEB_CANNOT_CREATE_BACKUP_TAG_FILE.get(
backupTag, backendDir.getPath());
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
msg);
}
*/
// Get a list of all the log files comprising the database.
{
{
}
};
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
// Check to see if backend is empty. If so, insert placeholder entry into
// archive
{
try
{
}
catch (IOException e)
{
if (debugEnabled())
{
}
throw new DirectoryException(
}
}
// Sort the log files from oldest to youngest since this is the order
// in which they must be copied.
// This is easy since the files are created in alphabetical order by JE.
try
{
// Archive the backup tag files.
/*
File[] tagFiles = backendDir.listFiles(backupTagFilter);
if (tagFiles != null)
{
for (File f : tagFiles)
{
try
{
archiveFile(zipStream, mac, digest, f);
}
catch (IOException e)
{
assert debugException(CLASS_NAME, "createBackup", e);
Message message = ERR_JEB_BACKUP_CANNOT_WRITE_ARCHIVE_FILE.get(
backupTag, stackTraceToSingleLineString(e));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(), message, e);
}
}
}
*/
// Process log files that are unchanged from the base backup.
int indexCurrent = 0;
if (latestFileName != null)
{
{
// Stop when we get to the first log file that has been
// written since the base backup.
if (compareResult > 0 ||
{
break;
}
indexCurrent++;
}
// Write a file containing the list of unchanged log files.
if (!unchangedList.isEmpty())
{
try
{
}
catch (IOException e)
{
if (debugEnabled())
{
}
throw new DirectoryException(
}
// Set the dependency.
}
}
// Write the new log files to the zip file.
do
{
boolean deletedFiles = false;
{
try
{
}
catch (FileNotFoundException e)
{
if (debugEnabled())
{
}
// A log file has been deleted by the cleaner since we started.
deletedFiles = true;
}
catch (IOException e)
{
if (debugEnabled())
{
}
throw new DirectoryException(
}
indexCurrent++;
}
if (deletedFiles)
{
// The cleaner is active and has deleted one or more of the log files
// since we started. The in-use data from those log files will have
// been written to new log files, so we must include those new files.
final long latestSize = latestFileSize;
{
{
if (compareTo > 0) return true;
return false;
}
};
try
{
indexCurrent = 0;
}
catch (Exception e)
{
if (debugEnabled())
{
}
throw new DirectoryException(
}
{
break;
}
}
else
{
// We are done.
break;
}
}
while (true);
}
catch (DirectoryException e)
{
if (debugEnabled())
{
}
try
{
}
// We're done writing the file, so close the zip stream (which should also
// close the underlying stream).
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
// Get the digest or MAC bytes if appropriate.
byte[] digestBytes = null;
if (hash)
{
if (signHash)
{
}
else
{
}
}
// Create a descriptor for this backup.
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
// Remove the backup if this operation was cancelled since the
// backup may be incomplete
if (backupConfig.isCancelled())
{
}
}
/**
* Restore a JE backend from backup, or verify the backup.
* @param backendDir The configuration of the backend instance to be
* restored.
* @param restoreConfig The configuration to use when performing the restore.
* @throws DirectoryException If a Directory Server error occurs.
*/
throws DirectoryException
{
// Get the properties to use for the restore.
// Create a restore directory with a different name to the backend
// directory.
if (!verifyOnly)
{
{
{
f.delete();
}
}
restoreDir.mkdir();
}
// Get the set of restore files that are in dependencies.
try
{
}
catch (IOException e)
{
if (debugEnabled())
{
}
message, e);
}
// Restore any dependencies.
{
try
{
}
catch (IOException e)
{
if (debugEnabled())
{
}
message, e);
}
}
// Restore the final archive file.
try
{
}
catch (IOException e)
{
if (debugEnabled())
{
}
message, e);
}
// Delete the current backend directory and rename the restore directory.
if (!verifyOnly)
{
{
{
f.delete();
}
}
backendDir.delete();
{
msg);
}
}
}
/**
* Removes the specified backup if it is possible to do so.
*
* @param backupDir The backup directory structure with which the
* specified backup is associated.
* @param backupID The backup ID for the backup to be removed.
*
* @throws DirectoryException If it is not possible to remove the specified
* backup for some reason (e.g., no such backup
* exists or there are other backups that are
* dependent upon it).
*/
throws DirectoryException
{
try
{
}
catch (ConfigException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
// Remove the archive file.
}
/**
* Restore the contents of an archive file. If the archive is being
* restored as a dependency, then only files in the specified set
* are restored, and the restored files are removed from the set. Otherwise
* all files from the archive are restored, and files that are to be found
* in dependencies are added to the set.
*
* @param restoreDir The directory in which files are to be restored.
* @param restoreConfig The restore configuration.
* @param backupInfo The backup containing the files to be restored.
* @param includeFiles The set of files to be restored. If null, then
* all files are restored.
* @throws DirectoryException If a Directory Server error occurs.
* @throws IOException If an I/O exception occurs during the restore.
*/
throws DirectoryException,IOException
{
// Get the crypto manager and use it to obtain references to the message
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
}
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
message, e);
}
}
// If the data is encrypted, then wrap the input stream in a cipher
// input stream.
if (encrypt)
{
try
{
}
catch (CryptoManagerException e)
{
if (debugEnabled())
{
}
message, e);
}
}
// Wrap the file input stream in a zip input stream.
// Iterate through the entries in the zip file.
{
{
// This entry is treated specially to indicate a backup of an empty
// backend was attempted.
continue;
}
{
// This entry is treated specially. It is never restored,
// and its hash is computed on the strings, not the bytes.
{
// The file name is part of the hash.
{
}
{
}
{
{
}
{
}
}
}
continue;
}
// See if we need to restore the file.
{
if (!verifyOnly)
{
}
}
{
if (verifyOnly)
{
}
// The file name is part of the hash.
{
}
{
}
// Process the file.
long totalBytesRead = 0;
byte[] buffer = new byte[8192];
{
{
}
{
}
if (outputStream != null)
{
}
}
if (outputStream != null)
{
}
}
}
// Check the hash.
{
{
message);
}
}
{
{
message);
}
}
}
/**
* Writes a file to an entry in the archive file.
* @param zipStream The zip output stream to which the file is to be
* written.
* @param mac A message authentication code to be updated, if not null.
* @param digest A message digest to be updated, if not null.
* @param file The file to be written.
* @return The number of bytes written from the file.
* @throws FileNotFoundException If the file to be archived does not exist.
* @throws IOException If an I/O error occurs while archiving the file.
*/
throws IOException, FileNotFoundException
{
// Open the file for reading.
// Start the zip entry.
// Put the name in the hash.
{
}
{
}
// Write the file.
long totalBytesRead = 0;
byte[] buffer = new byte[8192];
{
{
}
{
}
}
inputStream.close();
// Finish the zip entry.
return totalBytesRead;
}
/**
* Write a list of strings to an entry in the archive file.
* @param zipStream The zip output stream to which the entry is to be
* written.
* @param mac An optional MAC to be updated.
* @param digest An optional message digest to be updated.
* @param fileName The name of the zip entry to be written.
* @param list A list of strings to be written. The strings must not
* contain newlines.
* @throws IOException If an I/O error occurs while writing the archive entry.
*/
throws IOException
{
// Start the zip entry.
// Put the name in the hash.
{
}
{
}
{
{
}
{
}
}
// Finish the zip entry.
}
/**
* Obtains the set of files in a backup that are unchanged from its
* dependent backup or backups. This list is stored as the first entry
* in the archive file.
* @param backupDir The backup directory.
* @param backupInfo The backup info.
* @return The set of files that were unchanged.
* @throws DirectoryException If an error occurs while trying to get the
* appropriate cipher algorithm for an encrypted backup.
* @throws IOException If an I/O error occurs while reading the backup
* archive file.
*/
throws DirectoryException, IOException
{
// Get the crypto manager and use it to obtain references to the message
// If the data is encrypted, then wrap the input stream in a cipher
// input stream.
if (encrypt)
{
try
{
}
catch (CryptoManagerException e)
{
if (debugEnabled())
{
}
message, e);
}
}
// Wrap the file input stream in a zip input stream.
// Iterate through the entries in the zip file.
{
// We are looking for the entry containing the list of unchanged files.
{
{
}
break;
}
}
return hashSet;
}
/**
* Obtains a list of the dependencies of a given backup in order from
* the oldest (the full backup), to the most recent.
* @param backupDir The backup directory.
* @param backupInfo The backup for which dependencies are required.
* @return A list of dependent backups.
* @throws DirectoryException If a Directory Server error occurs.
*/
throws DirectoryException
{
{
if (backupInfo != null)
{
}
}
return dependents;
}
/**
* Get the information for a given backup ID from the backup directory.
* @param backupDir The backup directory.
* @param backupID The backup ID.
* @return The backup information, never null.
* @throws DirectoryException If the backup information cannot be found.
*/
{
if (backupInfo == null)
{
message);
}
return backupInfo;
}
}