diff options
author | Ben Summers <ben@fluffy.co.uk> | 2005-10-14 08:50:54 +0000 |
---|---|---|
committer | Ben Summers <ben@fluffy.co.uk> | 2005-10-14 08:50:54 +0000 |
commit | 99f8ce096bc5569adbfea1911dbcda24c28d8d8b (patch) | |
tree | 049c302161fea1f2f6223e1e8f3c40d9e8aadc8b /lib/backupclient/BackupClientRestore.cpp |
Box Backup 0.09 with a few tweeks
Diffstat (limited to 'lib/backupclient/BackupClientRestore.cpp')
-rwxr-xr-x | lib/backupclient/BackupClientRestore.cpp | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/lib/backupclient/BackupClientRestore.cpp b/lib/backupclient/BackupClientRestore.cpp new file mode 100755 index 00000000..ebe1d365 --- /dev/null +++ b/lib/backupclient/BackupClientRestore.cpp @@ -0,0 +1,468 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientRestore.cpp +// Purpose: +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <string> +#include <set> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> + +#include "BackupClientRestore.h" +#include "autogen_BackupProtocolClient.h" +#include "CommonException.h" +#include "BackupClientFileAttributes.h" +#include "IOStream.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "CollectInBufferStream.h" +#include "FileStream.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#define MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES (128*1024) + +class RestoreResumeInfo +{ +public: + // constructor + RestoreResumeInfo() + : mNextLevelID(0), + mpNextLevel(0) + { + } + + // destructor + ~RestoreResumeInfo() + { + delete mpNextLevel; + mpNextLevel = 0; + } + + // Get a next level object + RestoreResumeInfo &AddLevel(int64_t ID, const std::string &rLocalName) + { + ASSERT(mpNextLevel == 0 && mNextLevelID == 0); + mpNextLevel = new RestoreResumeInfo; + mNextLevelID = ID; + mNextLevelLocalName = rLocalName; + return *mpNextLevel; + } + + // Remove the next level info + void RemoveLevel() + { + ASSERT(mpNextLevel != 0 && mNextLevelID != 0); + delete mpNextLevel; + mpNextLevel = 0; + mNextLevelID = 0; + mNextLevelLocalName.erase(); + } + + void Save(const std::string &rFilename) const + { + // TODO: use proper buffered streams when they're done + // Build info in memory buffer + CollectInBufferStream write; + + // Save this level + SaveLevel(write); + + // Store in file + write.SetForReading(); + FileStream file(rFilename.c_str(), O_WRONLY | O_CREAT); + write.CopyStreamTo(file, IOStream::TimeOutInfinite, 8*1024 /* large buffer */); + } + + void SaveLevel(IOStream &rWrite) const + { + // Write the restored objects + int64_t numObjects = mRestoredObjects.size(); + rWrite.Write(&numObjects, sizeof(numObjects)); + for(std::set<int64_t>::const_iterator i(mRestoredObjects.begin()); i != mRestoredObjects.end(); ++i) + { + int64_t id = *i; + rWrite.Write(&id, sizeof(id)); + } + + // Next level? + if(mpNextLevel != 0) + { + // ID + rWrite.Write(&mNextLevelID, sizeof(mNextLevelID)); + // Name string + int32_t nsize = mNextLevelLocalName.size(); + rWrite.Write(&nsize, sizeof(nsize)); + rWrite.Write(mNextLevelLocalName.c_str(), nsize); + // And then the level itself + mpNextLevel->SaveLevel(rWrite); + } + else + { + // Just write a zero + int64_t zero = 0; + rWrite.Write(&zero, sizeof(zero)); + } + } + + // Not written to be efficient -- shouldn't be called very often. + bool Load(const std::string &rFilename) + { + // Delete and reset if necessary + if(mpNextLevel != 0) + { + RemoveLevel(); + } + + // Open file + FileStream file(rFilename.c_str()); + + // Load this level + return LoadLevel(file); + } + + #define CHECKED_READ(x, s) if(!rRead.ReadFullBuffer(x, s, 0)) {return false;} + bool LoadLevel(IOStream &rRead) + { + // Load the restored objects list + mRestoredObjects.clear(); + int64_t numObjects = 0; + CHECKED_READ(&numObjects, sizeof(numObjects)); + for(int64_t o = 0; o < numObjects; ++o) + { + int64_t id; + CHECKED_READ(&id, sizeof(id)); + mRestoredObjects.insert(id); + } + + // ID of next level? + int64_t nextID = 0; + CHECKED_READ(&nextID, sizeof(nextID)); + if(nextID != 0) + { + // Load the next level! + std::string name; + int32_t nsize = 0; + CHECKED_READ(&nsize, sizeof(nsize)); + char n[PATH_MAX]; + if(nsize > PATH_MAX) return false; + CHECKED_READ(n, nsize); + name.assign(n, nsize); + + // Create a new level + mpNextLevel = new RestoreResumeInfo; + mNextLevelID = nextID; + mNextLevelLocalName = name; + + // And ask it to read itself in + if(!mpNextLevel->LoadLevel(rRead)) + { + return false; + } + } + + return true; + } + + // List of objects at this level which have been done already + std::set<int64_t> mRestoredObjects; + // Next level ID + int64_t mNextLevelID; + // Pointer to next level + RestoreResumeInfo *mpNextLevel; + // Local filename of next level + std::string mNextLevelLocalName; +}; + +// parameters structure +typedef struct +{ + bool PrintDots; + bool RestoreDeleted; + std::string mRestoreResumeInfoFilename; + RestoreResumeInfo mResumeInfo; +} RestoreParams; + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientRestoreDir(BackupProtocolClient &, int64_t, const char *, bool) +// Purpose: Restore a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +static void BackupClientRestoreDir(BackupProtocolClient &rConnection, int64_t DirectoryID, std::string &rLocalDirectoryName, + RestoreParams &Params, RestoreResumeInfo &rLevel) +{ + // If we're resuming... check that we haven't got a next level to look at + if(rLevel.mpNextLevel != 0) + { + // Recurse immediately + std::string localDirname(rLocalDirectoryName + DIRECTORY_SEPARATOR_ASCHAR + rLevel.mNextLevelLocalName); + BackupClientRestoreDir(rConnection, rLevel.mNextLevelID, localDirname, Params, *rLevel.mpNextLevel); + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(rLevel.mNextLevelID); + + // Remove the level for the recursed directory + rLevel.RemoveLevel(); + } + + // Save the resumption information + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + + // Create the local directory (if not already done) -- path and owner set later, just use restrictive owner mode + switch(ObjectExists(rLocalDirectoryName.c_str())) + { + case ObjectExists_Dir: + // Do nothing + break; + case ObjectExists_File: + { + // File exists with this name, which is fun. Get rid of it. + ::printf("WARNING: File present with name '%s', removing out of the way of restored directory. Use specific restore with ID to restore this object.", rLocalDirectoryName.c_str()); + if(::unlink(rLocalDirectoryName.c_str()) != 0) + { + THROW_EXCEPTION(CommonException, OSFileError); + } + TRACE1("In restore, directory name collision with file %s", rLocalDirectoryName.c_str()); + } + // follow through to... (no break) + case ObjectExists_NoObject: + if(::mkdir(rLocalDirectoryName.c_str(), S_IRWXU) != 0) + { + THROW_EXCEPTION(CommonException, OSFileError); + } + break; + default: + ASSERT(false); + break; + } + + // Fetch the directory listing from the server -- getting a list of files which is approparite to the restore type + rConnection.QueryListDirectory( + DirectoryID, + Params.RestoreDeleted?(BackupProtocolClientListDirectory::Flags_Deleted):(BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING), + BackupProtocolClientListDirectory::Flags_OldVersion | (Params.RestoreDeleted?(0):(BackupProtocolClientListDirectory::Flags_Deleted)), + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(rConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, rConnection.GetTimeout()); + + // Apply attributes to the directory + const StreamableMemBlock &dirAttrBlock(dir.GetAttributes()); + BackupClientFileAttributes dirAttr(dirAttrBlock); + dirAttr.WriteAttributes(rLocalDirectoryName.c_str()); + + int64_t bytesWrittenSinceLastRestoreInfoSave = 0; + + // Process files + { + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0) + { + // Check ID hasn't already been done + if(rLevel.mRestoredObjects.find(en->GetObjectID()) == rLevel.mRestoredObjects.end()) + { + // Local name + BackupStoreFilenameClear nm(en->GetName()); + std::string localFilename(rLocalDirectoryName + DIRECTORY_SEPARATOR_ASCHAR + nm.GetClearFilename()); + + // Unlink anything which already exists -- for resuming restores, we can't overwrite files already there. + ::unlink(localFilename.c_str()); + + // Request it from the store + rConnection.QueryGetFile(DirectoryID, en->GetObjectID()); + + // Stream containing encoded file + std::auto_ptr<IOStream> objectStream(rConnection.ReceiveStream()); + + // Decode the file -- need to do different things depending on whether + // the directory entry has additional attributes + if(en->HasAttributes()) + { + // Use these attributes + const StreamableMemBlock &storeAttr(en->GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout(), &attr); + } + else + { + // Use attributes stored in file + BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout()); + } + + // Progress display? + if(Params.PrintDots) + { + printf("."); + fflush(stdout); + } + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(en->GetObjectID()); + + // Save restore info? + int64_t fileSize; + if(FileExists(localFilename.c_str(), &fileSize, true /* treat links as not existing */)) + { + // File exists... + bytesWrittenSinceLastRestoreInfoSave += fileSize; + + if(bytesWrittenSinceLastRestoreInfoSave > MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES) + { + // Save the restore info, in case it's needed later + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + bytesWrittenSinceLastRestoreInfoSave = 0; + } + } + } + } + } + + // Make sure the restore info has been saved + if(bytesWrittenSinceLastRestoreInfoSave != 0) + { + // Save the restore info, in case it's needed later + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + bytesWrittenSinceLastRestoreInfoSave = 0; + } + + + // Recuse to directories + { + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir)) != 0) + { + // Check ID hasn't already been done + if(rLevel.mRestoredObjects.find(en->GetObjectID()) == rLevel.mRestoredObjects.end()) + { + // Local name + BackupStoreFilenameClear nm(en->GetName()); + std::string localDirname(rLocalDirectoryName + DIRECTORY_SEPARATOR_ASCHAR + nm.GetClearFilename()); + + // Add the level for the next entry + RestoreResumeInfo &rnextLevel(rLevel.AddLevel(en->GetObjectID(), nm.GetClearFilename())); + + // Recurse + BackupClientRestoreDir(rConnection, en->GetObjectID(), localDirname, Params, rnextLevel); + + // Remove the level for the above call + rLevel.RemoveLevel(); + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(en->GetObjectID()); + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientRestore(BackupProtocolClient &, int64_t, const char *, bool, bool) +// Purpose: Restore a directory on the server to a local directory on the disc. +// +// The local directory must not already exist. +// +// If a restore is aborted for any reason, then it may be resumed if +// Resume == true. If Resume == false and resumption is possible, then +// Restore_ResumePossible is returned. +// +// Set RestoreDeleted to restore a deleted directory. This may not give the +// directory structure when it was deleted, because files may have been deleted +// within it before it was deleted. +// +// Returns Restore_TargetExists if the target directory exists, but +// there is no restore possible. (Won't attempt to overwrite things.) +// +// Returns Restore_Complete on success. (Exceptions on error.) +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +int BackupClientRestore(BackupProtocolClient &rConnection, int64_t DirectoryID, const char *LocalDirectoryName, + bool PrintDots, bool RestoreDeleted, bool UndeleteAfterRestoreDeleted, bool Resume) +{ + // Parameter block + RestoreParams params; + params.PrintDots = PrintDots; + params.RestoreDeleted = RestoreDeleted; + params.mRestoreResumeInfoFilename = LocalDirectoryName; + params.mRestoreResumeInfoFilename += ".boxbackupresume"; + + // Target exists? + int targetExistance = ObjectExists(LocalDirectoryName); + + // Does any resumption information exist? + bool doingResume = false; + if(FileExists(params.mRestoreResumeInfoFilename.c_str()) && targetExistance == ObjectExists_Dir) + { + if(!Resume) + { + // Caller didn't specify that resume should be done, so refuse to do it + // but say why. + return Restore_ResumePossible; + } + + // Attempt to load the resume info file + if(!params.mResumeInfo.Load(params.mRestoreResumeInfoFilename)) + { + // failed -- bad file, so things have gone a bit wrong + return Restore_TargetExists; + } + + // Flag as doing resume so next check isn't actually performed + doingResume = true; + } + + // Does the directory already exist? + if(targetExistance != ObjectExists_NoObject && !doingResume) + { + // Don't do anything in this case! + return Restore_TargetExists; + } + + // Restore the directory + std::string localName(LocalDirectoryName); + BackupClientRestoreDir(rConnection, DirectoryID, localName, params, params.mResumeInfo); + + // Undelete the directory on the server? + if(RestoreDeleted && UndeleteAfterRestoreDeleted) + { + // Send the command + rConnection.QueryUndeleteDirectory(DirectoryID); + } + + // Finish progress display? + if(PrintDots) + { + printf("\n"); + fflush(stdout); + } + + // Delete the resume information file + ::unlink(params.mRestoreResumeInfoFilename.c_str()); + + return Restore_Complete; +} + + + + |