diff options
author | Reinhard Tartler <siretart@tauware.de> | 2009-04-02 13:58:11 +0200 |
---|---|---|
committer | Reinhard Tartler <siretart@tauware.de> | 2009-04-02 13:58:11 +0200 |
commit | a84d45498bd861c9225080232948a99c2e317bb8 (patch) | |
tree | 8f1f5fb7bf7ffbf6f24cf4a4fd6888a235dbcc08 /bin | |
parent | 25db897553a0db0f912602b375029e724f51556e (diff) |
Import upstream version 0.11~rc3~r2491
Diffstat (limited to 'bin')
34 files changed, 3365 insertions, 2275 deletions
diff --git a/bin/bbackupctl/bbackupctl.cpp b/bin/bbackupctl/bbackupctl.cpp index 3795cbed..2c41f614 100644 --- a/bin/bbackupctl/bbackupctl.cpp +++ b/bin/bbackupctl/bbackupctl.cpp @@ -15,6 +15,8 @@ #include <unistd.h> #endif +#include <cstdlib> + #include "MainHelper.h" #include "BoxPortsAndFiles.h" #include "BackupDaemonConfigVerify.h" @@ -61,9 +63,7 @@ int main(int argc, const char *argv[]) MAINHELPER_START -#if defined WIN32 && ! defined NDEBUG - ::openlog("Box Backup (bbackupctl)", 0, 0); -#endif + Logging::SetProgramName("bbackupctl"); // Filename for configuration file? std::string configFilename; @@ -256,7 +256,8 @@ int main(int argc, const char *argv[]) case SyncAndWaitForEnd: { // send a sync command - std::string cmd("force-sync\n"); + commandName = "force-sync"; + std::string cmd = commandName + "\n"; connection.Write(cmd.c_str(), cmd.size()); connection.WriteAllBuffered(); @@ -336,13 +337,17 @@ int main(int argc, const char *argv[]) { if(!quiet) { - BOX_INFO("Succeeded."); + BOX_INFO("Control command " + "sent: " << + commandName); } finished = true; } else if(line == "error") { - BOX_ERROR("Check command spelling"); + BOX_ERROR("Control command failed: " << + commandName << ". Check " + "command spelling."); returnCode = 1; finished = true; } @@ -352,7 +357,7 @@ int main(int argc, const char *argv[]) MAINHELPER_END -#if defined WIN32 && ! defined NDEBUG +#if defined WIN32 && ! defined BOX_RELEASE_BUILD closelog(); #endif diff --git a/bin/bbackupd/BackupClientContext.cpp b/bin/bbackupd/BackupClientContext.cpp index 4b4efd90..b978f54c 100644 --- a/bin/bbackupd/BackupClientContext.cpp +++ b/bin/bbackupd/BackupClientContext.cpp @@ -41,17 +41,20 @@ // -------------------------------------------------------------------------- BackupClientContext::BackupClientContext ( - BackupDaemon &rDaemon, + LocationResolver &rResolver, TLSContext &rTLSContext, const std::string &rHostname, + int Port, int32_t AccountNumber, bool ExtendedLogging, bool ExtendedLogToFile, - std::string ExtendedLogFile + std::string ExtendedLogFile, + ProgressNotifier& rProgressNotifier ) - : mrDaemon(rDaemon), + : mrResolver(rResolver), mrTLSContext(rTLSContext), mHostname(rHostname), + mPort(Port), mAccountNumber(AccountNumber), mpSocket(0), mpConnection(0), @@ -66,8 +69,9 @@ BackupClientContext::BackupClientContext mStorageLimitExceeded(false), mpExcludeFiles(0), mpExcludeDirs(0), - mKeepAliveTimer(0), - mbIsManaged(false) + mKeepAliveTimer(0, "KeepAliveTime"), + mbIsManaged(false), + mrProgressNotifier(rProgressNotifier) { } @@ -129,7 +133,8 @@ BackupProtocolClient &BackupClientContext::GetConnection() mHostname << "'..."); // Connect! - mpSocket->Open(mrTLSContext, Socket::TypeINET, mHostname.c_str(), BOX_PORT_BBSTORED); + mpSocket->Open(mrTLSContext, Socket::TypeINET, + mHostname.c_str(), mPort); // And create a procotol object mpConnection = new BackupProtocolClient(*mpSocket); @@ -146,8 +151,8 @@ BackupProtocolClient &BackupClientContext::GetConnection() if (!mpExtendedLogFileHandle) { - BOX_ERROR("Failed to open extended log " - "file: " << strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to open extended " + "log file: " << mExtendedLogFile); } else { @@ -465,7 +470,7 @@ bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirec { // Location name -- look up in daemon's records std::string locPath; - if(!mrDaemon.FindLocationPathName(elementName.GetClearFilename(), locPath)) + if(!mrResolver.FindLocationPathName(elementName.GetClearFilename(), locPath)) { // Didn't find the location... so can't give the local filename return false; @@ -504,7 +509,7 @@ void BackupClientContext::SetKeepAliveTime(int iSeconds) { mKeepAliveTime = iSeconds < 0 ? 0 : iSeconds; BOX_TRACE("Set keep-alive time to " << mKeepAliveTime << " seconds"); - mKeepAliveTimer = Timer(mKeepAliveTime); + mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime"); } // -------------------------------------------------------------------------- @@ -564,7 +569,7 @@ void BackupClientContext::DoKeepAlive() BOX_TRACE("KeepAliveTime reached, sending keep-alive message"); mpConnection->QueryGetIsAlive(); - mKeepAliveTimer = Timer(mKeepAliveTime); + mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime"); } int BackupClientContext::GetMaximumDiffingTime() diff --git a/bin/bbackupd/BackupClientContext.h b/bin/bbackupd/BackupClientContext.h index 152d8556..4665df2b 100644 --- a/bin/bbackupd/BackupClientContext.h +++ b/bin/bbackupd/BackupClientContext.h @@ -12,6 +12,8 @@ #include "BoxTime.h" #include "BackupClientDeleteList.h" +#include "BackupClientDirectoryRecord.h" +#include "BackupDaemonInterface.h" #include "BackupStoreFile.h" #include "ExcludeList.h" #include "Timer.h" @@ -25,6 +27,7 @@ class BackupStoreFilenameClear; #include <string> + // -------------------------------------------------------------------------- // // Class @@ -38,13 +41,15 @@ class BackupClientContext : public DiffTimer public: BackupClientContext ( - BackupDaemon &rDaemon, + LocationResolver &rResolver, TLSContext &rTLSContext, const std::string &rHostname, + int32_t Port, int32_t AccountNumber, bool ExtendedLogging, bool ExtendedLogToFile, - std::string ExtendedLogFile + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier ); virtual ~BackupClientContext(); private: @@ -69,6 +74,7 @@ public: int64_t GetClientStoreMarker() const {return mClientStoreMarker;} bool StorageLimitExceeded() {return mStorageLimitExceeded;} + void SetStorageLimitExceeded() {mStorageLimitExceeded = true;} // -------------------------------------------------------------------------- // @@ -197,10 +203,16 @@ public: virtual int GetMaximumDiffingTime(); virtual bool IsManaged() { return mbIsManaged; } + ProgressNotifier& GetProgressNotifier() const + { + return mrProgressNotifier; + } + private: - BackupDaemon &mrDaemon; + LocationResolver &mrResolver; TLSContext &mrTLSContext; std::string mHostname; + int mPort; int32_t mAccountNumber; SocketStreamTLS *mpSocket; BackupProtocolClient *mpConnection; @@ -219,6 +231,7 @@ private: bool mbIsManaged; int mKeepAliveTime; int mMaximumDiffingTime; + ProgressNotifier &mrProgressNotifier; }; #endif // BACKUPCLIENTCONTEXT__H diff --git a/bin/bbackupd/BackupClientDeleteList.cpp b/bin/bbackupd/BackupClientDeleteList.cpp index f6d8e0dc..b9b5b53e 100644 --- a/bin/bbackupd/BackupClientDeleteList.cpp +++ b/bin/bbackupd/BackupClientDeleteList.cpp @@ -42,21 +42,38 @@ BackupClientDeleteList::~BackupClientDeleteList() { } +BackupClientDeleteList::FileToDelete::FileToDelete(int64_t DirectoryID, + const BackupStoreFilename& rFilename, + const std::string& rLocalPath) +: mDirectoryID(DirectoryID), + mFilename(rFilename), + mLocalPath(rLocalPath) +{ } + +BackupClientDeleteList::DirToDelete::DirToDelete(int64_t ObjectID, + const std::string& rLocalPath) +: mObjectID(ObjectID), + mLocalPath(rLocalPath) +{ } + // -------------------------------------------------------------------------- // // Function -// Name: BackupClientDeleteList::AddDirectoryDelete(int64_t) +// Name: BackupClientDeleteList::AddDirectoryDelete(int64_t, +// const BackupStoreFilename&) // Purpose: Add a directory to the list of directories to be deleted. // Created: 10/11/03 // // -------------------------------------------------------------------------- -void BackupClientDeleteList::AddDirectoryDelete(int64_t ObjectID) +void BackupClientDeleteList::AddDirectoryDelete(int64_t ObjectID, + const std::string& rLocalPath) { // Only add the delete to the list if it's not in the "no delete" set - if(mDirectoryNoDeleteList.find(ObjectID) == mDirectoryNoDeleteList.end()) + if(mDirectoryNoDeleteList.find(ObjectID) == + mDirectoryNoDeleteList.end()) { // Not in the list, so should delete it - mDirectoryList.push_back(ObjectID); + mDirectoryList.push_back(DirToDelete(ObjectID, rLocalPath)); } } @@ -64,18 +81,22 @@ void BackupClientDeleteList::AddDirectoryDelete(int64_t ObjectID) // -------------------------------------------------------------------------- // // Function -// Name: BackupClientDeleteList::AddFileDelete(int64_t, BackupStoreFilenameClear &) +// Name: BackupClientDeleteList::AddFileDelete(int64_t, +// const BackupStoreFilename &) // Purpose: // Created: 10/11/03 // // -------------------------------------------------------------------------- -void BackupClientDeleteList::AddFileDelete(int64_t DirectoryID, const BackupStoreFilename &rFilename) +void BackupClientDeleteList::AddFileDelete(int64_t DirectoryID, + const BackupStoreFilename &rFilename, const std::string& rLocalPath) { // Try to find it in the no delete list - std::vector<std::pair<int64_t, BackupStoreFilename> >::iterator delEntry(mFileNoDeleteList.begin()); + std::vector<std::pair<int64_t, BackupStoreFilename> >::iterator + delEntry(mFileNoDeleteList.begin()); while(delEntry != mFileNoDeleteList.end()) { - if((delEntry)->first == DirectoryID && (delEntry)->second == rFilename) + if((delEntry)->first == DirectoryID + && (delEntry)->second == rFilename) { // Found! break; @@ -86,7 +107,8 @@ void BackupClientDeleteList::AddFileDelete(int64_t DirectoryID, const BackupStor // Only add it to the delete list if it wasn't in the no delete list if(delEntry == mFileNoDeleteList.end()) { - mFileList.push_back(std::pair<int64_t, BackupStoreFilename>(DirectoryID, rFilename)); + mFileList.push_back(FileToDelete(DirectoryID, rFilename, + rLocalPath)); } } @@ -113,18 +135,24 @@ void BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) BackupProtocolClient &connection(rContext.GetConnection()); // Do the deletes - for(std::vector<int64_t>::iterator i(mDirectoryList.begin()); i != mDirectoryList.end(); ++i) + for(std::vector<DirToDelete>::iterator i(mDirectoryList.begin()); + i != mDirectoryList.end(); ++i) { - connection.QueryDeleteDirectory(*i); + connection.QueryDeleteDirectory(i->mObjectID); + rContext.GetProgressNotifier().NotifyDirectoryDeleted( + i->mObjectID, i->mLocalPath); } // Clear the directory list mDirectoryList.clear(); // Delete the files - for(std::vector<std::pair<int64_t, BackupStoreFilename> >::iterator i(mFileList.begin()); i != mFileList.end(); ++i) + for(std::vector<FileToDelete>::iterator i(mFileList.begin()); + i != mFileList.end(); ++i) { - connection.QueryDeleteFile(i->first, i->second); + connection.QueryDeleteFile(i->mDirectoryID, i->mFilename); + rContext.GetProgressNotifier().NotifyFileDeleted( + i->mDirectoryID, i->mLocalPath); } } @@ -140,7 +168,15 @@ void BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID) { // First of all, is it in the delete vector? - std::vector<int64_t>::iterator delEntry(std::find(mDirectoryList.begin(), mDirectoryList.end(), ObjectID)); + std::vector<DirToDelete>::iterator delEntry(mDirectoryList.begin()); + for(; delEntry != mDirectoryList.end(); delEntry++) + { + if(delEntry->mObjectID == ObjectID) + { + // Found! + break; + } + } if(delEntry != mDirectoryList.end()) { // erase this entry @@ -148,7 +184,8 @@ void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID) } else { - // Haven't been asked to delete it yet, put it in the no delete list + // Haven't been asked to delete it yet, put it in the + // no delete list mDirectoryNoDeleteList.insert(ObjectID); } } @@ -162,13 +199,15 @@ void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID) // Created: 19/11/03 // // -------------------------------------------------------------------------- -void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID, const BackupStoreFilename &rFilename) +void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID, + const BackupStoreFilename &rFilename) { // Find this in the delete list - std::vector<std::pair<int64_t, BackupStoreFilename> >::iterator delEntry(mFileList.begin()); + std::vector<FileToDelete>::iterator delEntry(mFileList.begin()); while(delEntry != mFileList.end()) { - if((delEntry)->first == DirectoryID && (delEntry)->second == rFilename) + if(delEntry->mDirectoryID == DirectoryID + && delEntry->mFilename == rFilename) { // Found! break; @@ -186,10 +225,5 @@ void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID, const BackupS // Haven't been asked to delete it yet, put it in the no delete list mFileNoDeleteList.push_back(std::pair<int64_t, BackupStoreFilename>(DirectoryID, rFilename)); } - } - - - - diff --git a/bin/bbackupd/BackupClientDeleteList.h b/bin/bbackupd/BackupClientDeleteList.h index 5940cf50..b0fbf51a 100644 --- a/bin/bbackupd/BackupClientDeleteList.h +++ b/bin/bbackupd/BackupClientDeleteList.h @@ -28,22 +28,46 @@ class BackupClientContext; // -------------------------------------------------------------------------- class BackupClientDeleteList { +private: + class FileToDelete + { + public: + int64_t mDirectoryID; + BackupStoreFilename mFilename; + std::string mLocalPath; + FileToDelete(int64_t DirectoryID, + const BackupStoreFilename& rFilename, + const std::string& rLocalPath); + }; + + class DirToDelete + { + public: + int64_t mObjectID; + std::string mLocalPath; + DirToDelete(int64_t ObjectID, const std::string& rLocalPath); + }; + public: BackupClientDeleteList(); ~BackupClientDeleteList(); - void AddDirectoryDelete(int64_t ObjectID); - void AddFileDelete(int64_t DirectoryID, const BackupStoreFilename &rFilename); + void AddDirectoryDelete(int64_t ObjectID, + const std::string& rLocalPath); + void AddFileDelete(int64_t DirectoryID, + const BackupStoreFilename &rFilename, + const std::string& rLocalPath); void StopDirectoryDeletion(int64_t ObjectID); - void StopFileDeletion(int64_t DirectoryID, const BackupStoreFilename &rFilename); + void StopFileDeletion(int64_t DirectoryID, + const BackupStoreFilename &rFilename); void PerformDeletions(BackupClientContext &rContext); private: - std::vector<int64_t> mDirectoryList; + std::vector<DirToDelete> mDirectoryList; std::set<int64_t> mDirectoryNoDeleteList; // note: things only get in this list if they're not present in mDirectoryList when they are 'added' - std::vector<std::pair<int64_t, BackupStoreFilename> > mFileList; + std::vector<FileToDelete> mFileList; std::vector<std::pair<int64_t, BackupStoreFilename> > mFileNoDeleteList; }; diff --git a/bin/bbackupd/BackupClientDirectoryRecord.cpp b/bin/bbackupd/BackupClientDirectoryRecord.cpp index 0a0703c2..b8d42d47 100644 --- a/bin/bbackupd/BackupClientDirectoryRecord.cpp +++ b/bin/bbackupd/BackupClientDirectoryRecord.cpp @@ -2,7 +2,8 @@ // // File // Name: BackupClientDirectoryRecord.cpp -// Purpose: Implementation of record about directory for backup client +// Purpose: Implementation of record about directory for +// backup client // Created: 2003/10/08 // // -------------------------------------------------------------------------- @@ -100,16 +101,27 @@ void BackupClientDirectoryRecord::DeleteSubDirectories() // -------------------------------------------------------------------------- // // Function -// Name: BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::SyncParams &, int64_t, const std::string &, bool) -// Purpose: Syncronise, recusively, a local directory with the server. +// Name: BackupClientDirectoryRecord::SyncDirectory(i +// BackupClientDirectoryRecord::SyncParams &, +// int64_t, const std::string &, +// const std::string &, bool) +// Purpose: Recursively synchronise a local directory +// with the server. // Created: 2003/10/08 // // -------------------------------------------------------------------------- -void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::SyncParams &rParams, int64_t ContainingDirectoryID, - const std::string &rLocalPath, bool ThisDirHasJustBeenCreated) +void BackupClientDirectoryRecord::SyncDirectory( + BackupClientDirectoryRecord::SyncParams &rParams, + int64_t ContainingDirectoryID, + const std::string &rLocalPath, + const std::string &rRemotePath, + bool ThisDirHasJustBeenCreated) { + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + // Signal received by daemon? - if(rParams.mrDaemon.StopRun()) + if(rParams.mrRunStatusProvider.StopRun()) { // Yes. Stop now. THROW_EXCEPTION(BackupStoreException, SignalReceived) @@ -118,49 +130,66 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Start by making some flag changes, marking this sync as not done, // and on the immediate sub directories. mSyncDone = false; - for(std::map<std::string, BackupClientDirectoryRecord *>::iterator i = mSubDirectories.begin(); + for(std::map<std::string, BackupClientDirectoryRecord *>::iterator + i = mSubDirectories.begin(); i != mSubDirectories.end(); ++i) { i->second->mSyncDone = false; } - // Work out the time in the future after which the file should be uploaded regardless. - // This is a simple way to avoid having too many problems with file servers when they have - // clients with badly out of sync clocks. - rParams.mUploadAfterThisTimeInTheFuture = GetCurrentBoxTime() + rParams.mMaxFileTimeInFuture; + // Work out the time in the future after which the file should + // be uploaded regardless. This is a simple way to avoid having + // too many problems with file servers when they have clients + // with badly out of sync clocks. + rParams.mUploadAfterThisTimeInTheFuture = GetCurrentBoxTime() + + rParams.mMaxFileTimeInFuture; - // Build the current state checksum to compare against while getting info from dirs - // Note checksum is used locally only, so byte order isn't considered. + // Build the current state checksum to compare against while + // getting info from dirs. Note checksum is used locally only, + // so byte order isn't considered. MD5Digest currentStateChecksum; + EMU_STRUCT_STAT dest_st; // Stat the directory, to get attribute info + // If it's a symbolic link, we want the link target here + // (as we're about to back up the contents of the directory) { - struct stat st; - if(::stat(rLocalPath.c_str(), &st) != 0) + if(EMU_STAT(rLocalPath.c_str(), &dest_st) != 0) { - // The directory has probably been deleted, so just ignore this error. - // In a future scan, this deletion will be noticed, deleted from server, and this object deleted. - rParams.GetProgressNotifier().NotifyDirStatFailed( - this, rLocalPath, strerror(errno)); + // The directory has probably been deleted, so + // just ignore this error. In a future scan, this + // deletion will be noticed, deleted from server, + // and this object deleted. + rNotifier.NotifyDirStatFailed(this, rLocalPath, + strerror(errno)); return; } - // Store inode number in map so directories are tracked in case they're renamed + // Store inode number in map so directories are tracked + // in case they're renamed { - BackupClientInodeToIDMap &idMap(rParams.mrContext.GetNewIDMap()); - idMap.AddToMap(st.st_ino, mObjectID, ContainingDirectoryID); + BackupClientInodeToIDMap &idMap( + rParams.mrContext.GetNewIDMap()); + idMap.AddToMap(dest_st.st_ino, mObjectID, + ContainingDirectoryID); } // Add attributes to checksum - currentStateChecksum.Add(&st.st_mode, sizeof(st.st_mode)); - currentStateChecksum.Add(&st.st_uid, sizeof(st.st_uid)); - currentStateChecksum.Add(&st.st_gid, sizeof(st.st_gid)); + currentStateChecksum.Add(&dest_st.st_mode, + sizeof(dest_st.st_mode)); + currentStateChecksum.Add(&dest_st.st_uid, + sizeof(dest_st.st_uid)); + currentStateChecksum.Add(&dest_st.st_gid, + sizeof(dest_st.st_gid)); // Inode to be paranoid about things moving around - currentStateChecksum.Add(&st.st_ino, sizeof(st.st_ino)); + currentStateChecksum.Add(&dest_st.st_ino, + sizeof(dest_st.st_ino)); #ifdef HAVE_STRUCT_STAT_ST_FLAGS - currentStateChecksum.Add(&st.st_flags, sizeof(st.st_flags)); + currentStateChecksum.Add(&dest_st.st_flags, + sizeof(dest_st.st_flags)); #endif StreamableMemBlock xattr; - BackupClientFileAttributes::FillExtendedAttr(xattr, rLocalPath.c_str()); + BackupClientFileAttributes::FillExtendedAttr(xattr, + rLocalPath.c_str()); currentStateChecksum.Add(xattr.GetBuffer(), xattr.GetSize()); } @@ -170,13 +199,13 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn std::vector<std::string> files; bool downloadDirectoryRecordBecauseOfFutureFiles = false; - struct stat dir_st; - if(::lstat(rLocalPath.c_str(), &dir_st) != 0) + EMU_STRUCT_STAT link_st; + if(EMU_LSTAT(rLocalPath.c_str(), &link_st) != 0) { // Report the error (logs and // eventual email to administrator) - rParams.GetProgressNotifier().NotifyFileStatFailed(this, - rLocalPath, strerror(errno)); + rNotifier.NotifyFileStatFailed(this, rLocalPath, + strerror(errno)); // FIXME move to NotifyFileStatFailed() SetErrorWhenReadingFilesystemObject(rParams, @@ -192,8 +221,7 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn DIR *dirHandle = 0; try { - rParams.GetProgressNotifier().NotifyScanDirectory( - this, rLocalPath); + rNotifier.NotifyScanDirectory(this, rLocalPath); dirHandle = ::opendir(rLocalPath.c_str()); if(dirHandle == 0) @@ -202,17 +230,19 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // eventual email to administrator) if (errno == EACCES) { - rParams.GetProgressNotifier().NotifyDirListFailed( - this, rLocalPath, "Access denied"); + rNotifier.NotifyDirListFailed(this, + rLocalPath, "Access denied"); } else { - rParams.GetProgressNotifier().NotifyDirListFailed(this, + rNotifier.NotifyDirListFailed(this, rLocalPath, strerror(errno)); } - // Report the error (logs and eventual email to administrator) - SetErrorWhenReadingFilesystemObject(rParams, rLocalPath.c_str()); + // Report the error (logs and eventual email + // to administrator) + SetErrorWhenReadingFilesystemObject(rParams, + rLocalPath.c_str()); // Ignore this directory for now. return; } @@ -228,14 +258,17 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn ::memset(&checksum_info, 0, sizeof(checksum_info)); struct dirent *en = 0; - struct stat st; + EMU_STRUCT_STAT file_st; std::string filename; while((en = ::readdir(dirHandle)) != 0) { rParams.mrContext.DoKeepAlive(); - // Don't need to use LinuxWorkaround_FinishDirentStruct(en, rLocalPath.c_str()); - // on Linux, as a stat is performed to get all this info + // Don't need to use + // LinuxWorkaround_FinishDirentStruct(en, + // rLocalPath.c_str()); + // on Linux, as a stat is performed to + // get all this info if(en->d_name[0] == '.' && (en->d_name[1] == '\0' || (en->d_name[1] == '.' && en->d_name[2] == '\0'))) @@ -259,11 +292,11 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // prefer S_IFREG, S_IFDIR... int type = en->d_type; #else - if(::lstat(filename.c_str(), &st) != 0) + if(EMU_LSTAT(filename.c_str(), &file_st) != 0) { // Report the error (logs and // eventual email to administrator) - rParams.GetProgressNotifier().NotifyFileStatFailed(this, + rNotifier.NotifyFileStatFailed(this, filename, strerror(errno)); // FIXME move to NotifyFileStatFailed() @@ -274,19 +307,18 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn continue; } - if(st.st_dev != dir_st.st_dev) + if(file_st.st_dev != dest_st.st_dev) { if(!(rParams.mrContext.ExcludeDir( filename))) { - rParams.GetProgressNotifier() - .NotifyMountPointSkipped( - this, filename); + rNotifier.NotifyMountPointSkipped( + this, filename); } continue; } - int type = st.st_mode & S_IFMT; + int type = file_st.st_mode & S_IFMT; #endif if(type == S_IFREG || type == S_IFLNK) @@ -296,8 +328,7 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Exclude it? if(rParams.mrContext.ExcludeFile(filename)) { - rParams.GetProgressNotifier() - .NotifyFileExcluded( + rNotifier.NotifyFileExcluded( this, filename); @@ -315,8 +346,7 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Exclude it? if(rParams.mrContext.ExcludeDir(filename)) { - rParams.GetProgressNotifier() - .NotifyDirExcluded( + rNotifier.NotifyDirExcluded( this, filename); @@ -327,19 +357,22 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Store on list dirs.push_back(std::string(en->d_name)); } + else if (type == S_IFSOCK || type == S_IFIFO) + { + // removed notification for these types + // see Debian bug 479145, no objections + } else { if(rParams.mrContext.ExcludeFile(filename)) { - rParams.GetProgressNotifier() - .NotifyFileExcluded( + rNotifier.NotifyFileExcluded( this, filename); } else { - rParams.GetProgressNotifier() - .NotifyUnsupportedFileType( + rNotifier.NotifyUnsupportedFileType( this, filename); SetErrorWhenReadingFilesystemObject( rParams, filename.c_str()); @@ -354,10 +387,9 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn #ifdef WIN32 // We didn't stat the file before, // but now we need the information. - if(::lstat(filename.c_str(), &st) != 0) + if(emu_stat(filename.c_str(), &file_st) != 0) { - rParams.GetProgressNotifier() - .NotifyFileStatFailed(this, + rNotifier.NotifyFileStatFailed(this, filename, strerror(errno)); @@ -370,18 +402,17 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn continue; } - if(st.st_dev != dir_st.st_dev) + if(file_st.st_dev != link_st.st_dev) { - rParams.GetProgressNotifier() - .NotifyMountPointSkipped(this, + rNotifier.NotifyMountPointSkipped(this, filename); continue; } #endif - checksum_info.mModificationTime = FileModificationTime(st); - checksum_info.mAttributeModificationTime = FileAttrModificationTime(st); - checksum_info.mSize = st.st_size; + checksum_info.mModificationTime = FileModificationTime(file_st); + checksum_info.mAttributeModificationTime = FileAttrModificationTime(file_st); + checksum_info.mSize = file_st.st_size; currentStateChecksum.Add(&checksum_info, sizeof(checksum_info)); currentStateChecksum.Add(en->d_name, strlen(en->d_name)); @@ -394,7 +425,7 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Log that this has happened if(!rParams.mHaveLoggedWarningAboutFutureFileTimes) { - rParams.GetProgressNotifier().NotifyFileModifiedInFuture( + rNotifier.NotifyFileModifiedInFuture( this, filename); rParams.mHaveLoggedWarningAboutFutureFileTimes = true; } @@ -468,7 +499,8 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn } // Do the directory reading - bool updateCompleteSuccess = UpdateItems(rParams, rLocalPath, pdirOnStore, entriesLeftOver, files, dirs); + bool updateCompleteSuccess = UpdateItems(rParams, rLocalPath, + rRemotePath, pdirOnStore, entriesLeftOver, files, dirs); // LAST THING! (think exception safety) // Store the new checksum -- don't fetch things unnecessarily in the future @@ -604,11 +636,18 @@ void BackupClientDirectoryRecord::UpdateAttributes(BackupClientDirectoryRecord:: // Created: 2003/10/09 // // -------------------------------------------------------------------------- -bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncParams &rParams, - const std::string &rLocalPath, BackupStoreDirectory *pDirOnStore, +bool BackupClientDirectoryRecord::UpdateItems( + BackupClientDirectoryRecord::SyncParams &rParams, + const std::string &rLocalPath, + const std::string &rRemotePath, + BackupStoreDirectory *pDirOnStore, std::vector<BackupStoreDirectory::Entry *> &rEntriesLeftOver, - std::vector<std::string> &rFiles, const std::vector<std::string> &rDirs) + std::vector<std::string> &rFiles, + const std::vector<std::string> &rDirs) { + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + bool allUpdatedSuccessfully = true; // Decrypt all the directory entries. @@ -634,7 +673,7 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP f != rFiles.end(); ++f) { // Send keep-alive message if needed - rParams.mrContext.DoKeepAlive(); + rContext.DoKeepAlive(); // Filename of this file std::string filename(MakeFullPath(rLocalPath, *f)); @@ -648,10 +687,10 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // BLOCK { // Stat the file - struct stat st; - if(::lstat(filename.c_str(), &st) != 0) + EMU_STRUCT_STAT st; + if(EMU_LSTAT(filename.c_str(), &st) != 0) { - rParams.GetProgressNotifier().NotifyFileStatFailed(this, + rNotifier.NotifyFileStatFailed(this, filename, strerror(errno)); // Report the error (logs and @@ -689,7 +728,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == 0)) { // Directory exists in the place of this file -- sort it out - RemoveDirectoryInPlaceOfFile(rParams, pDirOnStore, en->GetObjectID(), *f); + RemoveDirectoryInPlaceOfFile(rParams, pDirOnStore, + en, *f); en = 0; } @@ -701,7 +741,7 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // 2) It's not in the store // Do we know about the inode number? - const BackupClientInodeToIDMap &idMap(rParams.mrContext.GetCurrentIDMap()); + const BackupClientInodeToIDMap &idMap(rContext.GetCurrentIDMap()); int64_t renameObjectID = 0, renameInDirectory = 0; if(idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) { @@ -711,24 +751,24 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP bool isCurrentVersion = false; box_time_t srvModTime = 0, srvAttributesHash = 0; BackupStoreFilenameClear oldLeafname; - if(rParams.mrContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion, &srvModTime, &srvAttributesHash, &oldLeafname)) + if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion, &srvModTime, &srvAttributesHash, &oldLeafname)) { // Only interested if it's a file and the latest version if(!isDir && isCurrentVersion) { // Check that the object we found in the ID map doesn't exist on disc - struct stat st; - if(::stat(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) + EMU_STRUCT_STAT st; + if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) { // Doesn't exist locally, but does exist on the server. // Therefore we can safely rename it to this new file. // Get the connection to the server - BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + BackupProtocolClient &connection(rContext.GetConnection()); // Only do this step if there is room on the server. // This step will be repeated later when there is space available - if(!rParams.mrContext.StorageLimitExceeded()) + if(!rContext.StorageLimitExceeded()) { // Rename the existing files (ie include old versions) on the server connection.QueryMoveObject(renameObjectID, renameInDirectory, mObjectID /* move to this directory */, @@ -736,7 +776,7 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP storeFilename); // Stop the attempt to delete the file in the original location - BackupClientDeleteList &rdelList(rParams.mrContext.GetDeleteList()); + BackupClientDeleteList &rdelList(rContext.GetDeleteList()); rdelList.StopFileDeletion(renameInDirectory, oldLeafname); // Create new entry in the directory for it @@ -799,13 +839,15 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP if (pDirOnStore != 0 && en == 0) { doUpload = true; - BOX_TRACE(filename << ": will upload " + BOX_TRACE("Upload decision: " << + filename << ": will upload " "(not on server)"); } else if (modTime >= rParams.mSyncPeriodStart) { doUpload = true; - BOX_TRACE(filename << ": will upload " + BOX_TRACE("Upload decision: " << + filename << ": will upload " "(modified since last sync)"); } } @@ -823,7 +865,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP > rParams.mMaxUploadWait) { doUpload = true; - BOX_TRACE(filename << ": will upload " + BOX_TRACE("Upload decision: " << + filename << ": will upload " "(continually modified)"); } @@ -840,7 +883,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP en->GetModificationTime() != modTime) { doUpload = true; - BOX_TRACE(filename << ": will upload " + BOX_TRACE("Upload decision: " << + filename << ": will upload " "(mod time changed)"); } @@ -852,64 +896,121 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP rParams.mUploadAfterThisTimeInTheFuture) { doUpload = true; - BOX_TRACE(filename << ": will upload " + BOX_TRACE("Upload decision: " << + filename << ": will upload " "(mod time in the future)"); } } - - if (!doUpload) + + if (en != 0 && en->GetModificationTime() == modTime) { - BOX_TRACE(filename << ": will not upload " - "(no reason to upload, mod time is " - << modTime << " versus sync period " - << rParams.mSyncPeriodStart << " to " - << rParams.mSyncPeriodEnd << ")"); + BOX_TRACE("Upload decision: " << + filename << ": will not upload " + "(not modified since last upload)"); + } + else if (!doUpload) + { + if (modTime > rParams.mSyncPeriodEnd) + { + box_time_t now = GetCurrentBoxTime(); + int age = BoxTimeToSeconds(now - + modTime); + BOX_TRACE("Upload decision: " << + filename << ": will not upload " + "(modified too recently: " + "only " << age << "seconds ago)"); + } + else + { + BOX_TRACE("Upload decision: " << + filename << ": will not upload " + "(mod time is " << modTime << + " which is outside sync window, " + << rParams.mSyncPeriodStart << " to " + << rParams.mSyncPeriodEnd << ")"); + } } + bool fileSynced = true; + if (doUpload) { + // Upload needed, don't mark sync success until + // we've actually done it + fileSynced = false; + // Make sure we're connected -- must connect here so we know whether // the storage limit has been exceeded, and hence whether or not // to actually upload the file. - rParams.mrContext.GetConnection(); + rContext.GetConnection(); // Only do this step if there is room on the server. // This step will be repeated later when there is space available - if(!rParams.mrContext.StorageLimitExceeded()) + if(!rContext.StorageLimitExceeded()) { - // Upload the file to the server, recording the object ID it returns - bool noPreviousVersionOnServer = ((pDirOnStore != 0) && (en == 0)); + // Upload the file to the server, recording the + // object ID it returns + bool noPreviousVersionOnServer = + ((pDirOnStore != 0) && (en == 0)); - // Surround this in a try/catch block, to catch errrors, but still continue + // Surround this in a try/catch block, to + // catch errors, but still continue bool uploadSuccess = false; try { - latestObjectID = UploadFile(rParams, filename, storeFilename, fileSize, modTime, attributesHash, noPreviousVersionOnServer); - uploadSuccess = true; + latestObjectID = UploadFile(rParams, + filename, storeFilename, + fileSize, modTime, + attributesHash, + noPreviousVersionOnServer); + + if (latestObjectID == 0) + { + // storage limit exceeded + rParams.mrContext.SetStorageLimitExceeded(); + uploadSuccess = false; + allUpdatedSuccessfully = false; + } + else + { + uploadSuccess = true; + } } catch(ConnectionException &e) { - // Connection errors should just be passed on to the main handler, retries - // would probably just cause more problems. - rParams.GetProgressNotifier() - .NotifyFileUploadException( - this, filename, e); + // Connection errors should just be + // passed on to the main handler, + // retries would probably just cause + // more problems. + rNotifier.NotifyFileUploadException( + this, filename, e); throw; } catch(BoxException &e) { - // an error occured -- make return code false, to show error in directory + if (e.GetType() == BackupStoreException::ExceptionType && + e.GetSubType() == BackupStoreException::SignalReceived) + { + // abort requested, pass the + // exception on up. + throw; + } + + // an error occured -- make return + // code false, to show error in directory allUpdatedSuccessfully = false; // Log it. SetErrorWhenReadingFilesystemObject(rParams, filename.c_str()); - rParams.GetProgressNotifier() - .NotifyFileUploadException( - this, filename, e); + rNotifier.NotifyFileUploadException( + this, filename, e); } - // Update structures if the file was uploaded successfully. + // Update structures if the file was uploaded + // successfully. if(uploadSuccess) { + fileSynced = true; + // delete from pending entries if(pendingFirstSeenTime != 0 && mpPendingEntries != 0) { @@ -919,28 +1020,41 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP } else { - rParams.GetProgressNotifier().NotifyFileSkippedServerFull(this, + rNotifier.NotifyFileSkippedServerFull(this, filename); } } else if(en != 0 && en->GetAttributesHash() != attributesHash) { // Attributes have probably changed, upload them again. - // If the attributes have changed enough, the directory hash will have changed too, - // and so the dir will have been downloaded, and the entry will be available. + // If the attributes have changed enough, the directory + // hash will have changed too, and so the dir will have + // been downloaded, and the entry will be available. // Get connection - BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + BackupProtocolClient &connection(rContext.GetConnection()); // Only do this step if there is room on the server. - // This step will be repeated later when there is space available - if(!rParams.mrContext.StorageLimitExceeded()) + // This step will be repeated later when there is + // space available + if(!rContext.StorageLimitExceeded()) { - // Update store - BackupClientFileAttributes attr; - attr.ReadAttributes(filename.c_str(), false /* put mod times in the attributes, please */); - MemBlockStream attrStream(attr); - connection.QuerySetReplacementFileAttributes(mObjectID, attributesHash, storeFilename, attrStream); + try + { + // Update store + BackupClientFileAttributes attr; + attr.ReadAttributes(filename.c_str(), false /* put mod times in the attributes, please */); + MemBlockStream attrStream(attr); + connection.QuerySetReplacementFileAttributes(mObjectID, attributesHash, storeFilename, attrStream); + fileSynced = true; + } + catch (BoxException &e) + { + BOX_ERROR("Failed to read or store " + "file attributes for '" << + filename << "', will try " + "again later"); + } } } @@ -976,36 +1090,56 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP if(fileSize >= rParams.mFileTrackingSizeThreshold) { // Get the map - BackupClientInodeToIDMap &idMap(rParams.mrContext.GetNewIDMap()); + BackupClientInodeToIDMap &idMap(rContext.GetNewIDMap()); // Need to get an ID from somewhere... if(latestObjectID != 0) { // Use this one + BOX_TRACE("Storing uploaded file ID " << + inodeNum << " (" << filename << ") " + "in ID map as object " << + latestObjectID << " with parent " << + mObjectID); idMap.AddToMap(inodeNum, latestObjectID, mObjectID /* containing directory */); } else { // Don't know it -- haven't sent anything to the store, and didn't get a listing. // Look it up in the current map, and if it's there, use that. - const BackupClientInodeToIDMap ¤tIDMap(rParams.mrContext.GetCurrentIDMap()); + const BackupClientInodeToIDMap ¤tIDMap(rContext.GetCurrentIDMap()); int64_t objid = 0, dirid = 0; if(currentIDMap.Lookup(inodeNum, objid, dirid)) { // Found + if (dirid != mObjectID) + { + BOX_WARNING("Found conflicting parent ID for file ID " << inodeNum << " (" << filename << "): expected " << mObjectID << " but found " << dirid << " (same directory used in two different locations?)"); + } + ASSERT(dirid == mObjectID); + // NOTE: If the above assert fails, an inode number has been reused by the OS, // or there is a problem somewhere. If this happened on a short test run, look // into it. However, in a long running process this may happen occasionally and - // not indiciate anything wrong. + // not indicate anything wrong. // Run the release version for real life use, where this check is not made. - idMap.AddToMap(inodeNum, objid, mObjectID /* containing directory */); + BOX_TRACE("Storing found file ID " << + inodeNum << " (" << filename << + ") in ID map as object " << + objid << " with parent " << + mObjectID); + idMap.AddToMap(inodeNum, objid, + mObjectID /* containing directory */); } } } - rParams.GetProgressNotifier().NotifyFileSynchronised(this, - filename, fileSize); + if (fileSynced) + { + rNotifier.NotifyFileSynchronised(this, filename, + fileSize); + } } // Erase contents of files to save space when recursing @@ -1014,7 +1148,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // Delete the pending entries, if the map is entry if(mpPendingEntries != 0 && mpPendingEntries->size() == 0) { - TRACE1("Deleting mpPendingEntries from dir ID %lld\n", mObjectID); + BOX_TRACE("Deleting mpPendingEntries from dir ID " << + BOX_FORMAT_OBJECTID(mObjectID)); delete mpPendingEntries; mpPendingEntries = 0; } @@ -1024,7 +1159,7 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP d != rDirs.end(); ++d) { // Send keep-alive message if needed - rParams.mrContext.DoKeepAlive(); + rContext.DoKeepAlive(); // Get the local filename std::string dirname(MakeFullPath(rLocalPath, *d)); @@ -1044,21 +1179,27 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // Check that the entry which might have been found is in fact a directory if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == 0)) { - // Entry exists, but is not a directory. Bad. Get rid of it. - BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + // Entry exists, but is not a directory. Bad. + // Get rid of it. + BackupProtocolClient &connection(rContext.GetConnection()); connection.QueryDeleteFile(mObjectID /* in directory */, storeFilename); + rNotifier.NotifyFileDeleted(en->GetObjectID(), + storeFilename.GetClearFilename()); // Nothing found en = 0; } - // Flag for having created directory, so can optimise the recusive call not to - // read it again, because we know it's empty. + // Flag for having created directory, so can optimise the + // recursive call not to read it again, because we know + // it's empty. bool haveJustCreatedDirOnServer = false; // Next, see if it's in the list of sub directories BackupClientDirectoryRecord *psubDirRecord = 0; - std::map<std::string, BackupClientDirectoryRecord *>::iterator e(mSubDirectories.find(*d)); + std::map<std::string, BackupClientDirectoryRecord *>::iterator + e(mSubDirectories.find(*d)); + if(e != mSubDirectories.end()) { // In the list, just use this pointer @@ -1080,7 +1221,7 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // No. Exists on the server, and we know about it from the listing. subDirObjectID = en->GetObjectID(); } - else if(rParams.mrContext.StorageLimitExceeded()) + else if(rContext.StorageLimitExceeded()) // know we've got a connection if we get this far, // as dir will have been modified. { @@ -1098,29 +1239,47 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP box_time_t attrModTime = 0; InodeRefType inodeNum = 0; BackupClientFileAttributes attr; - attr.ReadAttributes(dirname.c_str(), true /* directories have zero mod times */, - 0 /* not interested in mod time */, &attrModTime, 0 /* not file size */, - &inodeNum); + bool failedToReadAttributes = false; + + try + { + attr.ReadAttributes(dirname.c_str(), + true /* directories have zero mod times */, + 0 /* not interested in mod time */, + &attrModTime, 0 /* not file size */, + &inodeNum); + } + catch (BoxException &e) + { + BOX_WARNING("Failed to read attributes " + "of directory, cannot check " + "for rename, assuming new: '" + << dirname << "'"); + failedToReadAttributes = true; + } // Check to see if the directory been renamed // First, do we have a record in the ID map? int64_t renameObjectID = 0, renameInDirectory = 0; bool renameDir = false; - const BackupClientInodeToIDMap &idMap(rParams.mrContext.GetCurrentIDMap()); - if(idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) + const BackupClientInodeToIDMap &idMap( + rContext.GetCurrentIDMap()); + + if(!failedToReadAttributes && idMap.Lookup(inodeNum, + renameObjectID, renameInDirectory)) { // Look up on the server to get the name, to build the local filename std::string localPotentialOldName; bool isDir = false; bool isCurrentVersion = false; - if(rParams.mrContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion)) + if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion)) { // Only interested if it's a directory if(isDir && isCurrentVersion) { // Check that the object doesn't exist already - struct stat st; - if(::stat(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) + EMU_STRUCT_STAT st; + if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) { // Doesn't exist locally, but does exist on the server. // Therefore we can safely rename it. @@ -1131,7 +1290,7 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP } // Get connection - BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + BackupProtocolClient &connection(rContext.GetConnection()); // Don't do a check for storage limit exceeded here, because if we get to this // stage, a connection will have been opened, and the status known, so the check @@ -1151,7 +1310,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP connection.QueryChangeDirAttributes(renameObjectID, attrModTime, attrStream); // Stop it being deleted later - BackupClientDeleteList &rdelList(rParams.mrContext.GetDeleteList()); + BackupClientDeleteList &rdelList( + rContext.GetDeleteList()); rdelList.StopDirectoryDeletion(renameObjectID); // This is the ID for the renamed directory @@ -1188,12 +1348,14 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP } } - ASSERT(psubDirRecord != 0 || rParams.mrContext.StorageLimitExceeded()); + ASSERT(psubDirRecord != 0 || rContext.StorageLimitExceeded()); if(psubDirRecord) { // Sync this sub directory too - psubDirRecord->SyncDirectory(rParams, mObjectID, dirname, haveJustCreatedDirOnServer); + psubDirRecord->SyncDirectory(rParams, mObjectID, + dirname, rRemotePath + "/" + *d, + haveJustCreatedDirOnServer); } // Zero pointer in rEntriesLeftOver, if we have a pointer to zero @@ -1222,20 +1384,27 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // to a list, which is actually deleted at the very end of the session. // If there's an error during the process, it doesn't matter if things // aren't actually deleted, as the whole state will be reset anyway. - BackupClientDeleteList &rdel(rParams.mrContext.GetDeleteList()); + BackupClientDeleteList &rdel(rContext.GetDeleteList()); + + BackupStoreFilenameClear clear(en->GetName()); + std::string localName = MakeFullPath(rLocalPath, + clear.GetClearFilename()); // Delete this entry -- file or directory? if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0) { // Set a pending deletion for the file - rdel.AddFileDelete(mObjectID, en->GetName()); + rdel.AddFileDelete(mObjectID, en->GetName(), + localName); } else if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0) { // Set as a pending deletion for the directory - rdel.AddDirectoryDelete(en->GetObjectID()); + rdel.AddDirectoryDelete(en->GetObjectID(), + localName); - // If there's a directory record for it in the sub directory map, delete it now + // If there's a directory record for it in + // the sub directory map, delete it now BackupStoreFilenameClear dirname(en->GetName()); std::map<std::string, BackupClientDirectoryRecord *>::iterator e(mSubDirectories.find(dirname.GetClearFilename())); if(e != mSubDirectories.end()) @@ -1249,8 +1418,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP rLocalPath, dirname.GetClearFilename()); - TRACE1("Deleted directory record for " - "%s\n", name.c_str()); + BOX_TRACE("Deleted directory record " + "for " << name); } } } @@ -1270,14 +1439,24 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP // Created: 9/7/04 // // -------------------------------------------------------------------------- -void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile(SyncParams &rParams, BackupStoreDirectory *pDirOnStore, int64_t ObjectID, const std::string &rFilename) +void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile( + SyncParams &rParams, + BackupStoreDirectory* pDirOnStore, + BackupStoreDirectory::Entry* pEntry, + const std::string &rFilename) { // First, delete the directory BackupProtocolClient &connection(rParams.mrContext.GetConnection()); - connection.QueryDeleteDirectory(ObjectID); + connection.QueryDeleteDirectory(pEntry->GetObjectID()); + + BackupStoreFilenameClear clear(pEntry->GetName()); + rParams.mrContext.GetProgressNotifier().NotifyDirectoryDeleted( + pEntry->GetObjectID(), clear.GetClearFilename()); // Then, delete any directory record - std::map<std::string, BackupClientDirectoryRecord *>::iterator e(mSubDirectories.find(rFilename)); + std::map<std::string, BackupClientDirectoryRecord *>::iterator + e(mSubDirectories.find(rFilename)); + if(e != mSubDirectories.end()) { // A record exists for this, remove it @@ -1294,16 +1473,30 @@ void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile(SyncParams &rPara // -------------------------------------------------------------------------- // // Function -// Name: BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::SyncParams &, const std::string &, const BackupStoreFilename &, int64_t, box_time_t, box_time_t, bool) -// Purpose: Private. Upload a file to the server -- may send a patch instead of the whole thing +// Name: BackupClientDirectoryRecord::UploadFile( +// BackupClientDirectoryRecord::SyncParams &, +// const std::string &, +// const BackupStoreFilename &, +// int64_t, box_time_t, box_time_t, bool) +// Purpose: Private. Upload a file to the server. May send +// a patch instead of the whole thing // Created: 20/1/04 // // -------------------------------------------------------------------------- -int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::SyncParams &rParams, const std::string &rFilename, const BackupStoreFilename &rStoreFilename, - int64_t FileSize, box_time_t ModificationTime, box_time_t AttributesHash, bool NoPreviousVersionOnServer) +int64_t BackupClientDirectoryRecord::UploadFile( + BackupClientDirectoryRecord::SyncParams &rParams, + const std::string &rFilename, + const BackupStoreFilename &rStoreFilename, + int64_t FileSize, + box_time_t ModificationTime, + box_time_t AttributesHash, + bool NoPreviousVersionOnServer) { + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + // Get the connection - BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + BackupProtocolClient &connection(rContext.GetConnection()); // Info int64_t objID = 0; @@ -1312,8 +1505,10 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn // Use a try block to catch store full errors try { - // Might an old version be on the server, and is the file size over the diffing threshold? - if(!NoPreviousVersionOnServer && FileSize >= rParams.mDiffingUploadSizeThreshold) + // Might an old version be on the server, and is the file + // size over the diffing threshold? + if(!NoPreviousVersionOnServer && + FileSize >= rParams.mDiffingUploadSizeThreshold) { // YES -- try to do diff, if possible // First, query the server to see if there's an old version available @@ -1323,7 +1518,7 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn if(diffFromID != 0) { // Found an old version - rParams.GetProgressNotifier().NotifyFileUploadingPatch(this, + rNotifier.NotifyFileUploadingPatch(this, rFilename); // Get the index @@ -1333,7 +1528,7 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn // Diff the file // - rParams.mrContext.ManageDiffProcess(); + rContext.ManageDiffProcess(); bool isCompletelyDifferent = false; std::auto_ptr<IOStream> patchStream( @@ -1342,11 +1537,11 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn mObjectID, /* containing directory */ rStoreFilename, diffFromID, *blockIndexStream, connection.GetTimeout(), - &rParams.mrContext, // DiffTimer implementation + &rContext, // DiffTimer implementation 0 /* not interested in the modification time */, &isCompletelyDifferent)); - rParams.mrContext.UnManageDiffProcess(); + rContext.UnManageDiffProcess(); // // Upload the patch to the store @@ -1354,6 +1549,9 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn std::auto_ptr<BackupProtocolClientSuccess> stored(connection.QueryStoreFile(mObjectID, ModificationTime, AttributesHash, isCompletelyDifferent?(0):(diffFromID), rStoreFilename, *patchStream)); + // Get object ID from the result + objID = stored->GetObjectID(); + // Don't attempt to upload it again! doNormalUpload = false; } @@ -1362,13 +1560,14 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn if(doNormalUpload) { // below threshold or nothing to diff from, so upload whole - rParams.GetProgressNotifier().NotifyFileUploading(this, - rFilename); + rNotifier.NotifyFileUploading(this, rFilename); // Prepare to upload, getting a stream which will encode the file as we go along std::auto_ptr<IOStream> upload( BackupStoreFile::EncodeFile(rFilename.c_str(), - mObjectID, rStoreFilename)); + mObjectID, rStoreFilename, NULL, + &rParams, + &(rParams.mrRunStatusProvider))); // Send to store std::auto_ptr<BackupProtocolClientSuccess> stored( @@ -1384,9 +1583,10 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn } catch(BoxException &e) { - rParams.mrContext.UnManageDiffProcess(); + rContext.UnManageDiffProcess(); - if(e.GetType() == ConnectionException::ExceptionType && e.GetSubType() == ConnectionException::Protocol_UnexpectedReply) + if(e.GetType() == ConnectionException::ExceptionType && + e.GetSubType() == ConnectionException::Protocol_UnexpectedReply) { // Check and see what error the protocol has, // this is more useful to users than the exception. @@ -1397,11 +1597,15 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn && subtype == BackupProtocolClientError::Err_StorageLimitExceeded) { // The hard limit was exceeded on the server, notify! - rParams.mrDaemon.NotifySysadmin(BackupDaemon::NotifyEvent_StoreFull); + rParams.mrSysadminNotifier.NotifySysadmin( + SysadminNotifier::StoreFull); + // return an error code instead of + // throwing an exception that we + // can't debug. + return 0; } - rParams.GetProgressNotifier() - .NotifyFileUploadServerError( - this, rFilename, type, subtype); + rNotifier.NotifyFileUploadServerError(this, + rFilename, type, subtype); } } @@ -1409,7 +1613,7 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn throw; } - rParams.GetProgressNotifier().NotifyFileUploaded(this, rFilename, FileSize); + rNotifier.NotifyFileUploaded(this, rFilename, FileSize); // Return the new object ID of this file return objID; @@ -1450,16 +1654,20 @@ void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(BackupClie // Created: 8/3/04 // // -------------------------------------------------------------------------- -BackupClientDirectoryRecord::SyncParams::SyncParams(BackupDaemon &rDaemon, - ProgressNotifier &rProgressNotifier, BackupClientContext &rContext) - : mrProgressNotifier(rProgressNotifier), - mSyncPeriodStart(0), +BackupClientDirectoryRecord::SyncParams::SyncParams( + RunStatusProvider &rRunStatusProvider, + SysadminNotifier &rSysadminNotifier, + ProgressNotifier &rProgressNotifier, + BackupClientContext &rContext) + : mSyncPeriodStart(0), mSyncPeriodEnd(0), mMaxUploadWait(0), mMaxFileTimeInFuture(99999999999999999LL), mFileTrackingSizeThreshold(16*1024), mDiffingUploadSizeThreshold(16*1024), - mrDaemon(rDaemon), + mrRunStatusProvider(rRunStatusProvider), + mrSysadminNotifier(rSysadminNotifier), + mrProgressNotifier(rProgressNotifier), mrContext(rContext), mReadErrorsOnFilesystemObjects(false), mUploadAfterThisTimeInTheFuture(99999999999999999LL), diff --git a/bin/bbackupd/BackupClientDirectoryRecord.h b/bin/bbackupd/BackupClientDirectoryRecord.h index 9e4dda7a..fce3fc04 100644 --- a/bin/bbackupd/BackupClientDirectoryRecord.h +++ b/bin/bbackupd/BackupClientDirectoryRecord.h @@ -13,10 +13,13 @@ #include <string> #include <map> -#include "BoxTime.h" #include "BackupClientFileAttributes.h" +#include "BackupDaemonInterface.h" #include "BackupStoreDirectory.h" +#include "BoxTime.h" #include "MD5Digest.h" +#include "ReadLoggingStream.h" +#include "RunStatusProvider.h" class Archive; class BackupClientContext; @@ -25,82 +28,6 @@ class BackupDaemon; // -------------------------------------------------------------------------- // // Class -// Name: ProgressNotifier -// Purpose: Provides methods for the backup library to inform the user -// interface about its progress with the backup -// Created: 2005/11/20 -// -// -------------------------------------------------------------------------- -class BackupClientDirectoryRecord; - -class ProgressNotifier -{ - public: - virtual ~ProgressNotifier() { } - virtual void NotifyScanDirectory( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyDirStatFailed( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - const std::string& rErrorMsg) = 0; - virtual void NotifyFileStatFailed( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - const std::string& rErrorMsg) = 0; - virtual void NotifyDirListFailed( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - const std::string& rErrorMsg) = 0; - virtual void NotifyMountPointSkipped( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyFileExcluded( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyDirExcluded( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyUnsupportedFileType( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyFileReadFailed( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - const std::string& rErrorMsg) = 0; - virtual void NotifyFileModifiedInFuture( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyFileSkippedServerFull( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyFileUploadException( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - const BoxException& rException) = 0; - virtual void NotifyFileUploadServerError( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - int type, int subtype) = 0; - virtual void NotifyFileUploading( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyFileUploadingPatch( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath) = 0; - virtual void NotifyFileUploaded( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - int64_t FileSize) = 0; - virtual void NotifyFileSynchronised( - const BackupClientDirectoryRecord* pDirRecord, - const std::string& rLocalPath, - int64_t FileSize) = 0; -}; - -// -------------------------------------------------------------------------- -// -// Class // Name: BackupClientDirectoryRecord // Purpose: Implementation of record about directory for backup client // Created: 2003/10/08 @@ -132,11 +59,12 @@ public: // Created: 8/3/04 // // -------------------------------------------------------------------------- - class SyncParams + class SyncParams : public ReadLoggingStream::Logger { public: SyncParams( - BackupDaemon &rDaemon, + RunStatusProvider &rRunStatusProvider, + SysadminNotifier &rSysadminNotifier, ProgressNotifier &rProgressNotifier, BackupClientContext &rContext); ~SyncParams(); @@ -144,7 +72,6 @@ public: // No copying SyncParams(const SyncParams&); SyncParams &operator=(const SyncParams&); - ProgressNotifier &mrProgressNotifier; public: // Data members are public, as accessors are not justified here @@ -154,7 +81,9 @@ public: box_time_t mMaxFileTimeInFuture; int32_t mFileTrackingSizeThreshold; int32_t mDiffingUploadSizeThreshold; - BackupDaemon &mrDaemon; + RunStatusProvider &mrRunStatusProvider; + SysadminNotifier &mrSysadminNotifier; + ProgressNotifier &mrProgressNotifier; BackupClientContext &mrContext; bool mReadErrorsOnFilesystemObjects; @@ -162,41 +91,79 @@ public: box_time_t mUploadAfterThisTimeInTheFuture; bool mHaveLoggedWarningAboutFutureFileTimes; + bool StopRun() { return mrRunStatusProvider.StopRun(); } + void NotifySysadmin(SysadminNotifier::EventCode Event) + { + mrSysadminNotifier.NotifySysadmin(Event); + } ProgressNotifier& GetProgressNotifier() const { return mrProgressNotifier; } + + /* ReadLoggingStream::Logger implementation */ + virtual void Log(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset, + length, elapsed, finish); + } + virtual void Log(int64_t readSize, int64_t offset, + int64_t length) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset, + length); + } + virtual void Log(int64_t readSize, int64_t offset) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset); + } }; - void SyncDirectory(SyncParams &rParams, int64_t ContainingDirectoryID, const std::string &rLocalPath, + void SyncDirectory(SyncParams &rParams, + int64_t ContainingDirectoryID, + const std::string &rLocalPath, + const std::string &rRemotePath, bool ThisDirHasJustBeenCreated = false); private: void DeleteSubDirectories(); BackupStoreDirectory *FetchDirectoryListing(SyncParams &rParams); - void UpdateAttributes(SyncParams &rParams, BackupStoreDirectory *pDirOnStore, const std::string &rLocalPath); - bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath, BackupStoreDirectory *pDirOnStore, + void UpdateAttributes(SyncParams &rParams, + BackupStoreDirectory *pDirOnStore, + const std::string &rLocalPath); + bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath, + const std::string &rRemotePath, + BackupStoreDirectory *pDirOnStore, std::vector<BackupStoreDirectory::Entry *> &rEntriesLeftOver, - std::vector<std::string> &rFiles, const std::vector<std::string> &rDirs); - int64_t UploadFile(SyncParams &rParams, const std::string &rFilename, const BackupStoreFilename &rStoreFilename, - int64_t FileSize, box_time_t ModificationTime, box_time_t AttributesHash, bool NoPreviousVersionOnServer); - void SetErrorWhenReadingFilesystemObject(SyncParams &rParams, const char *Filename); - void RemoveDirectoryInPlaceOfFile(SyncParams &rParams, BackupStoreDirectory *pDirOnStore, int64_t ObjectID, const std::string &rFilename); + std::vector<std::string> &rFiles, + const std::vector<std::string> &rDirs); + int64_t UploadFile(SyncParams &rParams, + const std::string &rFilename, + const BackupStoreFilename &rStoreFilename, + int64_t FileSize, box_time_t ModificationTime, + box_time_t AttributesHash, bool NoPreviousVersionOnServer); + void SetErrorWhenReadingFilesystemObject(SyncParams &rParams, + const char *Filename); + void RemoveDirectoryInPlaceOfFile(SyncParams &rParams, + BackupStoreDirectory* pDirOnStore, + BackupStoreDirectory::Entry* pEntry, + const std::string &rFilename); private: - int64_t mObjectID; + int64_t mObjectID; std::string mSubDirName; - bool mInitialSyncDone; - bool mSyncDone; + bool mInitialSyncDone; + bool mSyncDone; // Checksum of directory contents and attributes, used to detect changes uint8_t mStateChecksum[MD5Digest::DigestLength]; - std::map<std::string, box_time_t> *mpPendingEntries; - std::map<std::string, BackupClientDirectoryRecord *> mSubDirectories; + std::map<std::string, box_time_t> *mpPendingEntries; + std::map<std::string, BackupClientDirectoryRecord *> mSubDirectories; // mpPendingEntries is a pointer rather than simple a member - // variables, because most of the time it'll be empty. This would waste a lot - // of memory because of STL allocation policies. + // variable, because most of the time it'll be empty. This would + // waste a lot of memory because of STL allocation policies. }; #endif // BACKUPCLIENTDIRECTORYRECORD__H diff --git a/bin/bbackupd/BackupClientInodeToIDMap.cpp b/bin/bbackupd/BackupClientInodeToIDMap.cpp index 0d4fd507..b9f56c5a 100644 --- a/bin/bbackupd/BackupClientInodeToIDMap.cpp +++ b/bin/bbackupd/BackupClientInodeToIDMap.cpp @@ -217,13 +217,16 @@ void BackupClientInodeToIDMap::AddToMap(InodeRefType InodeRef, int64_t ObjectID, // -------------------------------------------------------------------------- // // Function -// Name: BackupClientInodeToIDMap::Lookup(InodeRefType, int64_t &, int64_t &) const -// Purpose: Looks up an inode in the map, returning true if it exists, and the object -// ids of it and the directory it's in the reference arguments. +// Name: BackupClientInodeToIDMap::Lookup(InodeRefType, +// int64_t &, int64_t &) const +// Purpose: Looks up an inode in the map, returning true if it +// exists, and the object ids of it and the directory +// it's in the reference arguments. // Created: 11/11/03 // // -------------------------------------------------------------------------- -bool BackupClientInodeToIDMap::Lookup(InodeRefType InodeRef, int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const +bool BackupClientInodeToIDMap::Lookup(InodeRefType InodeRef, + int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const { #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION std::map<InodeRefType, std::pair<int64_t, int64_t> >::const_iterator i(mMap.find(InodeRef)); diff --git a/bin/bbackupd/BackupDaemon.cpp b/bin/bbackupd/BackupDaemon.cpp index e762bbdc..3615b848 100644 --- a/bin/bbackupd/BackupDaemon.cpp +++ b/bin/bbackupd/BackupDaemon.cpp @@ -10,6 +10,7 @@ #include "Box.h" #include <stdio.h> +#include <stdlib.h> #include <string.h> #ifdef HAVE_UNISTD_H @@ -47,36 +48,34 @@ #include "BoxPortsAndFiles.h" #include "SSLLib.h" -#include "TLSContext.h" -#include "BackupDaemon.h" -#include "BackupDaemonConfigVerify.h" +#include "autogen_BackupProtocolClient.h" +#include "autogen_ClientException.h" +#include "autogen_ConversionException.h" +#include "Archive.h" #include "BackupClientContext.h" +#include "BackupClientCryptoKeys.h" #include "BackupClientDirectoryRecord.h" -#include "BackupStoreDirectory.h" #include "BackupClientFileAttributes.h" -#include "BackupStoreFilenameClear.h" #include "BackupClientInodeToIDMap.h" -#include "autogen_BackupProtocolClient.h" -#include "autogen_ConversionException.h" -#include "BackupClientCryptoKeys.h" -#include "BannerText.h" +#include "BackupClientMakeExcludeList.h" +#include "BackupDaemon.h" +#include "BackupDaemonConfigVerify.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" #include "BackupStoreFile.h" -#include "Random.h" +#include "BackupStoreFilenameClear.h" +#include "BannerText.h" +#include "Conversion.h" #include "ExcludeList.h" -#include "BackupClientMakeExcludeList.h" -#include "IOStreamGetLine.h" -#include "Utils.h" #include "FileStream.h" -#include "BackupStoreException.h" -#include "BackupStoreConstants.h" -#include "LocalProcessStream.h" #include "IOStreamGetLine.h" -#include "Conversion.h" -#include "Archive.h" -#include "Timer.h" +#include "LocalProcessStream.h" #include "Logging.h" -#include "autogen_ClientException.h" +#include "Random.h" +#include "Timer.h" +#include "Utils.h" #ifdef WIN32 #include "Win32ServiceFunctions.h" @@ -93,25 +92,6 @@ static const time_t MAX_SLEEP_TIME = 1024; // This prevents repetative cycles of load on the server #define SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY 6 -#ifdef WIN32 -// -------------------------------------------------------------------------- -// -// Function -// Name: HelperThread() -// Purpose: Background thread function, called by Windows, -// calls the BackupDaemon's RunHelperThread method -// to listen for and act on control communications -// Created: 18/2/04 -// -// -------------------------------------------------------------------------- -unsigned int WINAPI HelperThread(LPVOID lpParam) -{ - ((BackupDaemon *)lpParam)->RunHelperThread(); - - return 0; -} -#endif - // -------------------------------------------------------------------------- // // Function @@ -122,9 +102,23 @@ unsigned int WINAPI HelperThread(LPVOID lpParam) // -------------------------------------------------------------------------- BackupDaemon::BackupDaemon() : mState(BackupDaemon::State_Initialising), - mpCommandSocketInfo(0), + mDeleteRedundantLocationsAfter(0), + mLastNotifiedEvent(SysadminNotifier::MAX), mDeleteUnusedRootDirEntriesAfter(0), - mLogAllFileAccess(false) + mClientStoreMarker(BackupClientContext::ClientStoreMarker_NotKnown), + mStorageLimitExceeded(false), + mReadErrorsOnFilesystemObjects(false), + mLastSyncTime(0), + mNextSyncTime(0), + mCurrentSyncStartTime(0), + mUpdateStoreInterval(0), + mDeleteStoreObjectInfoFile(false), + mDoSyncForcedByPreviousSyncError(false), + mLogAllFileAccess(false), + mpProgressNotifier(this), + mpLocationResolver(this), + mpRunStatusProvider(this), + mpSysadminNotifier(this) #ifdef WIN32 , mInstallService(false), mRemoveService(false), @@ -134,40 +128,6 @@ BackupDaemon::BackupDaemon() { // Only ever one instance of a daemon SSLLib::Initialise(); - - // Initialise notification sent status - for(int l = 0; l < NotifyEvent__MAX; ++l) - { - mNotificationsSent[l] = false; - } - - #ifdef WIN32 - // Create the event object to signal from main thread to - // worker when new messages are queued to be sent to the - // command socket. - - mhMessageToSendEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if(mhMessageToSendEvent == INVALID_HANDLE_VALUE) - { - BOX_ERROR("Failed to create event object: error " << - GetLastError()); - exit(1); - } - - // Create the event object to signal from worker to main thread - // when a command has been received on the command socket. - - mhCommandReceivedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if(mhCommandReceivedEvent == INVALID_HANDLE_VALUE) - { - BOX_ERROR("Failed to create event object: error " << - GetLastError()); - exit(1); - } - - // Create the critical section to protect the message queue - InitializeCriticalSection(&mMessageQueueLock); - #endif } // -------------------------------------------------------------------------- @@ -182,12 +142,6 @@ BackupDaemon::~BackupDaemon() { DeleteAllLocations(); DeleteAllIDMaps(); - - if(mpCommandSocketInfo != 0) - { - delete mpCommandSocketInfo; - mpCommandSocketInfo = 0; - } } // -------------------------------------------------------------------------- @@ -262,12 +216,12 @@ void BackupDaemon::SetupInInitialProcess() if(GetConfiguration().KeyExists("CommandSocket")) { BOX_WARNING( - "==============================================================================\n" - "SECURITY WARNING: This platform cannot check the credentials of connections to\n" - "the command socket. This is a potential DoS security problem.\n" - "Remove the CommandSocket directive from the bbackupd.conf file if bbackupctl\n" - "is not used.\n" - "==============================================================================\n" + "==============================================================================\n" + "SECURITY WARNING: This platform cannot check the credentials of connections to\n" + "the command socket. This is a potential DoS security problem.\n" + "Remove the CommandSocket directive from the bbackupd.conf file if bbackupctl\n" + "is not used.\n" + "==============================================================================\n" ); } } @@ -294,7 +248,7 @@ void BackupDaemon::DeleteAllLocations() // Clear the contents of the map, so it is empty mLocations.clear(); - // And delete everything from the assoicated mount vector + // And delete everything from the associated mount vector mIDMapMounts.clear(); } @@ -322,6 +276,7 @@ int BackupDaemon::ProcessOption(signed int option) case 'S': { mServiceName = optarg; + Logging::SetProgramName(mServiceName); return 0; } @@ -356,8 +311,6 @@ int BackupDaemon::Main(const std::string &rConfigFileName) return RemoveService(mServiceName); } - Logging::SetProgramName("Box Backup (" + mServiceName + ")"); - int returnCode; if (mRunAsService) @@ -377,220 +330,6 @@ int BackupDaemon::Main(const std::string &rConfigFileName) return returnCode; } - -void BackupDaemon::RunHelperThread(void) -{ - const Configuration &conf(GetConfiguration()); - mpCommandSocketInfo = new CommandSocketInfo; - WinNamedPipeStream& rSocket(mpCommandSocketInfo->mListeningSocket); - - // loop until the parent process exits, or we decide - // to kill the thread ourselves - while (!IsTerminateWanted()) - { - try - { - std::string socket = conf.GetKeyValue("CommandSocket"); - rSocket.Accept(socket); - } - catch (BoxException &e) - { - BOX_ERROR("Failed to open command socket: " << - e.what()); - SetTerminateWanted(); - break; // this is fatal to listening thread - } - catch(std::exception &e) - { - BOX_ERROR("Failed to open command socket: " << - e.what()); - SetTerminateWanted(); - break; // this is fatal to listening thread - } - catch(...) - { - BOX_ERROR("Failed to open command socket: " - "unknown error"); - SetTerminateWanted(); - break; // this is fatal to listening thread - } - - try - { - // Errors here do not kill the thread, - // only the current connection. - - // This next section comes from Ben's original function - // Log - BOX_INFO("Connection from command socket"); - - // Send a header line summarising the configuration - // and current state - char summary[256]; - size_t summarySize = sprintf(summary, - "bbackupd: %d %d %d %d\nstate %d\n", - conf.GetKeyValueBool("AutomaticBackup"), - conf.GetKeyValueInt("UpdateStoreInterval"), - conf.GetKeyValueInt("MinimumFileAge"), - conf.GetKeyValueInt("MaxUploadWait"), - mState); - - rSocket.Write(summary, summarySize); - rSocket.Write("ping\n", 5); - - // old queued messages are not useful - EnterCriticalSection(&mMessageQueueLock); - mMessageList.clear(); - ResetEvent(mhMessageToSendEvent); - LeaveCriticalSection(&mMessageQueueLock); - - IOStreamGetLine readLine(rSocket); - std::string command; - - while (rSocket.IsConnected() && !IsTerminateWanted()) - { - HANDLE handles[2]; - handles[0] = mhMessageToSendEvent; - handles[1] = rSocket.GetReadableEvent(); - - BOX_TRACE("Received command '" << command - << "' over command socket"); - - DWORD result = WaitForMultipleObjects( - sizeof(handles)/sizeof(*handles), - handles, FALSE, 1000); - - if(result == 0) - { - ResetEvent(mhMessageToSendEvent); - - EnterCriticalSection(&mMessageQueueLock); - try - { - while (mMessageList.size() > 0) - { - std::string message = *(mMessageList.begin()); - mMessageList.erase(mMessageList.begin()); - printf("Sending '%s' to waiting client... ", message.c_str()); - message += "\n"; - rSocket.Write(message.c_str(), - message.length()); - - printf("done.\n"); - } - } - catch (...) - { - LeaveCriticalSection(&mMessageQueueLock); - throw; - } - LeaveCriticalSection(&mMessageQueueLock); - continue; - } - else if(result == WAIT_TIMEOUT) - { - continue; - } - else if(result != 1) - { - BOX_ERROR("WaitForMultipleObjects returned invalid result " << result); - continue; - } - - if(!readLine.GetLine(command)) - { - BOX_ERROR("Failed to read line"); - continue; - } - - BOX_INFO("Received command " << command << - " from client"); - - bool sendOK = false; - bool sendResponse = true; - bool disconnect = false; - - // Command to process! - if(command == "quit" || command == "") - { - // Close the socket. - disconnect = true; - sendResponse = false; - } - else if(command == "sync") - { - // Sync now! - this->mDoSyncFlagOut = true; - this->mSyncIsForcedOut = false; - sendOK = true; - SetEvent(mhCommandReceivedEvent); - } - else if(command == "force-sync") - { - // Sync now (forced -- overrides any SyncAllowScript) - this->mDoSyncFlagOut = true; - this->mSyncIsForcedOut = true; - sendOK = true; - SetEvent(mhCommandReceivedEvent); - } - else if(command == "reload") - { - // Reload the configuration - SetReloadConfigWanted(); - sendOK = true; - SetEvent(mhCommandReceivedEvent); - } - else if(command == "terminate") - { - // Terminate the daemon cleanly - SetTerminateWanted(); - sendOK = true; - SetEvent(mhCommandReceivedEvent); - } - else - { - BOX_ERROR("Received unknown command " - "'" << command << "' " - "from client"); - sendResponse = true; - sendOK = false; - } - - // Send a response back? - if(sendResponse) - { - const char* response = sendOK ? "ok\n" : "error\n"; - rSocket.Write( - response, strlen(response)); - } - - if(disconnect) - { - break; - } - } - - rSocket.Close(); - } - catch(BoxException &e) - { - BOX_ERROR("Communication error with " - "control client: " << e.what()); - } - catch(std::exception &e) - { - BOX_ERROR("Internal error in command socket " - "thread: " << e.what()); - } - catch(...) - { - BOX_ERROR("Communication error with control client"); - } - } - - CloseHandle(mhCommandReceivedEvent); - CloseHandle(mhMessageToSendEvent); -} #endif // -------------------------------------------------------------------------- @@ -606,36 +345,29 @@ void BackupDaemon::Run() // initialise global timer mechanism Timers::Init(); - #ifdef WIN32 - // Create a thread to handle the named pipe - HANDLE hThread; - unsigned int dwThreadId; - - hThread = (HANDLE) _beginthreadex( - NULL, // default security attributes - 0, // use default stack size - HelperThread, // thread function - this, // argument to thread function - 0, // use default creation flags - &dwThreadId); // returns the thread identifier - #else + #ifndef WIN32 // Ignore SIGPIPE so that if a command connection is broken, // the daemon doesn't terminate. ::signal(SIGPIPE, SIG_IGN); + #endif - // Create a command socket? - const Configuration &conf(GetConfiguration()); - if(conf.KeyExists("CommandSocket")) - { - // Yes, create a local UNIX socket - mpCommandSocketInfo = new CommandSocketInfo; - const char *socketName = - conf.GetKeyValue("CommandSocket").c_str(); + // Create a command socket? + const Configuration &conf(GetConfiguration()); + if(conf.KeyExists("CommandSocket")) + { + // Yes, create a local UNIX socket + mapCommandSocketInfo.reset(new CommandSocketInfo); + const char *socketName = + conf.GetKeyValue("CommandSocket").c_str(); + #ifdef WIN32 + mapCommandSocketInfo->mListeningSocket.Listen( + socketName); + #else ::unlink(socketName); - mpCommandSocketInfo->mListeningSocket.Listen( + mapCommandSocketInfo->mListeningSocket.Listen( Socket::TypeUNIX, socketName); - } - #endif // !WIN32 + #endif + } // Handle things nicely on exceptions try @@ -644,16 +376,11 @@ void BackupDaemon::Run() } catch(...) { - #ifdef WIN32 - // Don't delete the socket, as the helper thread - // is probably still using it. Let Windows clean - // up after us. - #else - if(mpCommandSocketInfo != 0) + if(mapCommandSocketInfo.get()) { try { - delete mpCommandSocketInfo; + mapCommandSocketInfo.reset(); } catch(std::exception &e) { @@ -666,91 +393,63 @@ void BackupDaemon::Run() BOX_WARNING("Error closing command socket " "after exception, ignored."); } - mpCommandSocketInfo = 0; } - #endif // WIN32 Timers::Cleanup(); throw; } - #ifndef WIN32 - // Clean up - if(mpCommandSocketInfo != 0) - { - delete mpCommandSocketInfo; - mpCommandSocketInfo = 0; - } - #endif - + // Clean up + mapCommandSocketInfo.reset(); Timers::Cleanup(); } -// -------------------------------------------------------------------------- -// -// Function -// Name: BackupDaemon::Run2() -// Purpose: Run function for daemon (second stage) -// Created: 2003/10/08 -// -// -------------------------------------------------------------------------- -void BackupDaemon::Run2() +void BackupDaemon::InitCrypto() { // Read in the certificates creating a TLS context - TLSContext tlsContext; const Configuration &conf(GetConfiguration()); std::string certFile(conf.GetKeyValue("CertificateFile")); std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); std::string caFile(conf.GetKeyValue("TrustedCAsFile")); - tlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); + mTlsContext.Initialise(false /* as client */, certFile.c_str(), + keyFile.c_str(), caFile.c_str()); // Set up the keys for various things BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); +} - // Setup various timings - int maximumDiffingTime = 600; - int keepAliveTime = 60; +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::Run2() +// Purpose: Run function for daemon (second stage) +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupDaemon::Run2() +{ + InitCrypto(); - // max diffing time, keep-alive time - if(conf.KeyExists("MaximumDiffingTime")) - { - maximumDiffingTime = conf.GetKeyValueInt("MaximumDiffingTime"); - } - if(conf.KeyExists("KeepAliveTime")) - { - keepAliveTime = conf.GetKeyValueInt("KeepAliveTime"); - } + const Configuration &conf(GetConfiguration()); // How often to connect to the store (approximate) - box_time_t updateStoreInterval = SecondsToBoxTime(conf.GetKeyValueInt("UpdateStoreInterval")); + mUpdateStoreInterval = SecondsToBoxTime( + conf.GetKeyValueInt("UpdateStoreInterval")); // But are we connecting automatically? bool automaticBackup = conf.GetKeyValueBool("AutomaticBackup"); - // The minimum age a file needs to be before it will be considered for uploading - box_time_t minimumFileAge = SecondsToBoxTime(conf.GetKeyValueInt("MinimumFileAge")); - - // The maximum time we'll wait to upload a file, regardless of how often it's modified - box_time_t maxUploadWait = SecondsToBoxTime(conf.GetKeyValueInt("MaxUploadWait")); - // Adjust by subtracting the minimum file age, so is relative to sync period end in comparisons - maxUploadWait = (maxUploadWait > minimumFileAge)?(maxUploadWait - minimumFileAge):(0); - // When the next sync should take place -- which is ASAP - box_time_t nextSyncTime = 0; + mNextSyncTime = 0; // When the last sync started (only updated if the store was not full when the sync ended) - box_time_t lastSyncTime = 0; + mLastSyncTime = 0; // -------------------------------------------------------------------------------------------- - // And what's the current client store marker? - int64_t clientStoreMarker = - BackupClientContext::ClientStoreMarker_NotKnown; - // haven't contacted the store yet - - bool deleteStoreObjectInfoFile = DeserializeStoreObjectInfo( - clientStoreMarker, lastSyncTime, nextSyncTime); + mDeleteStoreObjectInfoFile = DeserializeStoreObjectInfo( + mLastSyncTime, mNextSyncTime); // -------------------------------------------------------------------------------------------- @@ -758,91 +457,98 @@ void BackupDaemon::Run2() // Set state SetState(State_Idle); + mDoSyncForcedByPreviousSyncError = false; + // Loop around doing backups do { // Flags used below bool storageLimitExceeded = false; bool doSync = false; - bool doSyncForcedByCommand = false; + bool mDoSyncForcedByCommand = false; // Is a delay necessary? + box_time_t currentTime; + + do { - box_time_t currentTime; - do + // Check whether we should be stopping, + // and don't run a sync if so. + if(StopRun()) break; + + currentTime = GetCurrentBoxTime(); + + // Pause a while, but no more than + // MAX_SLEEP_TIME seconds (use the conditional + // because times are unsigned) + box_time_t requiredDelay = + (mNextSyncTime < currentTime) + ? (0) + : (mNextSyncTime - currentTime); + + // If there isn't automatic backup happening, + // set a long delay. And limit delays at the + // same time. + if(!automaticBackup && !mDoSyncForcedByPreviousSyncError) { - // Check whether we should be stopping, - // and don't run a sync if so. - if(StopRun()) break; - - currentTime = GetCurrentBoxTime(); - - // Pause a while, but no more than - // MAX_SLEEP_TIME seconds (use the conditional - // because times are unsigned) - box_time_t requiredDelay = - (nextSyncTime < currentTime) - ? (0) - : (nextSyncTime - currentTime); - - // If there isn't automatic backup happening, - // set a long delay. And limit delays at the - // same time. - if(!automaticBackup || requiredDelay > - SecondsToBoxTime(MAX_SLEEP_TIME)) + requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); + } + else if(requiredDelay > SecondsToBoxTime(MAX_SLEEP_TIME)) + { + requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); + } + + // Only delay if necessary + if(requiredDelay > 0) + { + // Sleep somehow. There are choices + // on how this should be done, + // depending on the state of the + // control connection + if(mapCommandSocketInfo.get() != 0) { - requiredDelay = SecondsToBoxTime( - MAX_SLEEP_TIME); + // A command socket exists, + // so sleep by waiting on it + WaitOnCommandSocket(requiredDelay, + doSync, mDoSyncForcedByCommand); } - - // Only delay if necessary - if(requiredDelay > 0) + else { - // Sleep somehow. There are choices - // on how this should be done, - // depending on the state of the - // control connection - if(mpCommandSocketInfo != 0) - { - // A command socket exists, - // so sleep by waiting on it - WaitOnCommandSocket( - requiredDelay, doSync, - doSyncForcedByCommand); - } - else - { - // No command socket or - // connection, just do a - // normal sleep - time_t sleepSeconds = - BoxTimeToSeconds( - requiredDelay); - ::sleep((sleepSeconds <= 0) - ? 1 - : sleepSeconds); - } + // No command socket or + // connection, just do a + // normal sleep + time_t sleepSeconds = + BoxTimeToSeconds(requiredDelay); + ::sleep((sleepSeconds <= 0) + ? 1 : sleepSeconds); } - - } while((!automaticBackup || (currentTime < nextSyncTime)) && !doSync && !StopRun()); + } + + if ((automaticBackup || mDoSyncForcedByPreviousSyncError) + && currentTime >= mNextSyncTime) + { + doSync = true; + } } + while(!doSync && !StopRun()); // Time of sync start, and if it's time for another sync // (and we're doing automatic syncs), set the flag - box_time_t currentSyncStartTime = GetCurrentBoxTime(); - if(automaticBackup && currentSyncStartTime >= nextSyncTime) + mCurrentSyncStartTime = GetCurrentBoxTime(); + if((automaticBackup || mDoSyncForcedByPreviousSyncError) && + mCurrentSyncStartTime >= mNextSyncTime) { doSync = true; } // Use a script to see if sync is allowed now? - if(!doSyncForcedByCommand && doSync && !StopRun()) + if(!mDoSyncForcedByCommand && doSync && !StopRun()) { int d = UseScriptToSeeIfSyncAllowed(); if(d > 0) { // Script has asked for a delay - nextSyncTime = GetCurrentBoxTime() + + mNextSyncTime = GetCurrentBoxTime() + SecondsToBoxTime(d); doSync = false; } @@ -852,383 +558,448 @@ void BackupDaemon::Run2() // to be stopping) if(doSync && !StopRun()) { - // Touch a file to record times in filesystem - TouchFileInWorkingDir("last_sync_start"); + RunSyncNowWithExceptionHandling(); + } - // Tell anything connected to the command socket - SendSyncStartOrFinish(true /* start */); - - // Reset statistics on uploads - BackupStoreFile::ResetStats(); - - // Calculate the sync period of files to examine - box_time_t syncPeriodStart = lastSyncTime; - box_time_t syncPeriodEnd = currentSyncStartTime - - minimumFileAge; + // Set state + SetState(storageLimitExceeded?State_StorageLimitExceeded:State_Idle); - if(syncPeriodStart >= syncPeriodEnd && - syncPeriodStart - syncPeriodEnd < minimumFileAge) - { - // This can happen if we receive a force-sync - // command less than minimumFileAge after - // the last sync. Deal with it by moving back - // syncPeriodStart, which should not do any - // damage. - syncPeriodStart = syncPeriodEnd - - SecondsToBoxTime(1); - } + } while(!StopRun()); + + // Make sure we have a clean start next time round (if restart) + DeleteAllLocations(); + DeleteAllIDMaps(); +} - if(syncPeriodStart >= syncPeriodEnd) - { - BOX_ERROR("Invalid (negative) sync period: " - "perhaps your clock is going " - "backwards (" << syncPeriodStart << - " to " << syncPeriodEnd << ")"); - THROW_EXCEPTION(ClientException, - ClockWentBackwards); - } +void BackupDaemon::RunSyncNowWithExceptionHandling() +{ + OnBackupStart(); - // Check logic - ASSERT(syncPeriodEnd > syncPeriodStart); - // Paranoid check on sync times - if(syncPeriodStart >= syncPeriodEnd) continue; - - // Adjust syncPeriodEnd to emulate snapshot - // behaviour properly - box_time_t syncPeriodEndExtended = syncPeriodEnd; - // Using zero min file age? - if(minimumFileAge == 0) - { - // Add a year on to the end of the end time, - // to make sure we sync files which are - // modified after the scan run started. - // Of course, they may be eligible to be - // synced again the next time round, - // but this should be OK, because the changes - // only upload should upload no data. - syncPeriodEndExtended += SecondsToBoxTime( - (time_t)(356*24*3600)); - } + // Do sync + bool errorOccurred = false; + int errorCode = 0, errorSubCode = 0; + const char* errorString = "unknown"; - // Delete the serialised store object file, - // so that we don't try to reload it after a - // partially completed backup - if(deleteStoreObjectInfoFile && - !DeleteStoreObjectInfo()) - { - BOX_ERROR("Failed to delete the " - "StoreObjectInfoFile, backup cannot " - "continue safely."); - THROW_EXCEPTION(ClientException, - FailedToDeleteStoreObjectInfoFile); - } + try + { + RunSyncNow(); + } + catch(BoxException &e) + { + errorOccurred = true; + errorString = e.what(); + errorCode = e.GetType(); + errorSubCode = e.GetSubType(); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error during backup run: " << e.what()); + errorOccurred = true; + errorString = e.what(); + } + catch(...) + { + // TODO: better handling of exceptions here... + // need to be very careful + errorOccurred = true; + } - // In case the backup throws an exception, - // we should not try to delete the store info - // object file again. - deleteStoreObjectInfoFile = false; - - // Do sync - bool errorOccurred = false; - int errorCode = 0, errorSubCode = 0; - const char* errorString = "unknown"; + // do not retry immediately without a good reason + mDoSyncForcedByPreviousSyncError = false; + + if(errorOccurred) + { + // Is it a berkely db failure? + bool isBerkelyDbFailure = false; - try - { - // Set state and log start - SetState(State_Connected); - BOX_NOTICE("Beginning scan of local files"); + if (errorCode == BackupStoreException::ExceptionType + && errorSubCode == BackupStoreException::BerkelyDBFailure) + { + isBerkelyDbFailure = true; + } - std::string extendedLogFile; - if (conf.KeyExists("ExtendedLogFile")) - { - extendedLogFile = conf.GetKeyValue( - "ExtendedLogFile"); - } - - if (conf.KeyExists("LogAllFileAccess")) - { - mLogAllFileAccess = - conf.GetKeyValueBool( - "LogAllFileAccess"); - } - - // Then create a client context object (don't - // just connect, as this may be unnecessary) - BackupClientContext clientContext - ( - *this, - tlsContext, - conf.GetKeyValue("StoreHostname"), - conf.GetKeyValueInt("AccountNumber"), - conf.GetKeyValueBool("ExtendedLogging"), - conf.KeyExists("ExtendedLogFile"), - extendedLogFile - ); - - // Set up the sync parameters - BackupClientDirectoryRecord::SyncParams params( - *this, *this, clientContext); - params.mSyncPeriodStart = syncPeriodStart; - params.mSyncPeriodEnd = syncPeriodEndExtended; - // use potentially extended end time - params.mMaxUploadWait = maxUploadWait; - params.mFileTrackingSizeThreshold = - conf.GetKeyValueInt( - "FileTrackingSizeThreshold"); - params.mDiffingUploadSizeThreshold = - conf.GetKeyValueInt( - "DiffingUploadSizeThreshold"); - params.mMaxFileTimeInFuture = - SecondsToBoxTime( - conf.GetKeyValueInt( - "MaxFileTimeInFuture")); - mDeleteRedundantLocationsAfter = - conf.GetKeyValueInt( - "DeleteRedundantLocationsAfter"); - - clientContext.SetMaximumDiffingTime(maximumDiffingTime); - clientContext.SetKeepAliveTime(keepAliveTime); - - // Set store marker - clientContext.SetClientStoreMarker(clientStoreMarker); - - // Set up the locations, if necessary -- - // need to do it here so we have a - // (potential) connection to use - if(mLocations.empty()) - { - const Configuration &locations( - conf.GetSubConfiguration( - "BackupLocations")); - - // Make sure all the directory records - // are set up - SetupLocations(clientContext, locations); - } - - // Get some ID maps going - SetupIDMapsForSync(); - - // Delete any unused directories? - DeleteUnusedRootDirEntries(clientContext); - - // Notify administrator - NotifySysadmin(NotifyEvent_BackupStart); - - // Go through the records, syncing them - for(std::vector<Location *>::const_iterator - i(mLocations.begin()); - i != mLocations.end(); ++i) - { - // Set current and new ID map pointers - // in the context - clientContext.SetIDMaps(mCurrentIDMaps[(*i)->mIDMapIndex], mNewIDMaps[(*i)->mIDMapIndex]); - - // Set exclude lists (context doesn't - // take ownership) - clientContext.SetExcludeLists( - (*i)->mpExcludeFiles, - (*i)->mpExcludeDirs); - - // Sync the directory - (*i)->mpDirectoryRecord->SyncDirectory( - params, - BackupProtocolClientListDirectory::RootDirectory, - (*i)->mPath); + if(isBerkelyDbFailure) + { + // Delete corrupt files + DeleteCorruptBerkelyDbFiles(); + } - // Unset exclude lists (just in case) - clientContext.SetExcludeLists(0, 0); - } - - // Errors reading any files? - if(params.mReadErrorsOnFilesystemObjects) - { - // Notify administrator - NotifySysadmin(NotifyEvent_ReadError); - } - else - { - // Unset the read error flag, so the // error is reported again if it - // happens again - mNotificationsSent[NotifyEvent_ReadError] = false; - } - - // Perform any deletions required -- these are - // delayed until the end to allow renaming to - // happen neatly. - clientContext.PerformDeletions(); + // Clear state data + // Go back to beginning of time + mLastSyncTime = 0; + mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything + DeleteAllLocations(); + DeleteAllIDMaps(); - // Close any open connection - clientContext.CloseAnyOpenConnection(); - - // Get the new store marker - clientStoreMarker = clientContext.GetClientStoreMarker(); - - // Check the storage limit - if(clientContext.StorageLimitExceeded()) - { - // Tell the sysadmin about this - NotifySysadmin(NotifyEvent_StoreFull); - } - else - { - // The start time of the next run is - // the end time of this run. - // This is only done if the storage - // limit wasn't exceeded (as things - // won't have been done properly if - // it was) - lastSyncTime = syncPeriodEnd; - - // unflag the storage full notify flag - // so that next time the store is full, - // an alert will be sent - mNotificationsSent[NotifyEvent_StoreFull] = false; - } - - // Calculate when the next sync run should be - nextSyncTime = currentSyncStartTime + - updateStoreInterval + - Random::RandomInt(updateStoreInterval >> + // Handle restart? + if(StopRun()) + { + BOX_NOTICE("Exception (" << errorCode + << "/" << errorSubCode + << ") due to signal"); + OnBackupFinish(); + return; + } + + NotifySysadmin(SysadminNotifier::BackupError); + + // If the Berkely db files get corrupted, + // delete them and try again immediately. + if(isBerkelyDbFailure) + { + BOX_ERROR("Berkely db inode map files corrupted, " + "deleting and restarting scan. Renamed files " + "and directories will not be tracked until " + "after this scan."); + ::sleep(1); + } + else + { + // Not restart/terminate, pause and retry + // Notify administrator + SetState(State_Error); + BOX_ERROR("Exception caught (" << errorString << + " " << errorCode << "/" << errorSubCode << + "), reset state and waiting to retry..."); + ::sleep(10); + mNextSyncTime = mCurrentSyncStartTime + + SecondsToBoxTime(100) + + Random::RandomInt(mUpdateStoreInterval >> SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); - - // Commit the ID Maps - CommitIDMapsAfterSync(); + } + } + // Notify system administrator about the final state of the backup + else if(mReadErrorsOnFilesystemObjects) + { + NotifySysadmin(SysadminNotifier::ReadError); + } + else if(mStorageLimitExceeded) + { + NotifySysadmin(SysadminNotifier::StoreFull); + } + else + { + NotifySysadmin(SysadminNotifier::BackupOK); + } + + // If we were retrying after an error, and this backup succeeded, + // then now would be a good time to stop :-) + mDoSyncForcedByPreviousSyncError = errorOccurred; - // Log - BOX_NOTICE("Finished scan of local files"); + OnBackupFinish(); +} - // Notify administrator - NotifySysadmin(NotifyEvent_BackupFinish); +void BackupDaemon::RunSyncNow() +{ + // Delete the serialised store object file, + // so that we don't try to reload it after a + // partially completed backup + if(mDeleteStoreObjectInfoFile && + !DeleteStoreObjectInfo()) + { + BOX_ERROR("Failed to delete the StoreObjectInfoFile, " + "backup cannot continue safely."); + THROW_EXCEPTION(ClientException, + FailedToDeleteStoreObjectInfoFile); + } - // -------------------------------------------------------------------------------------------- + // In case the backup throws an exception, + // we should not try to delete the store info + // object file again. + mDeleteStoreObjectInfoFile = false; - // We had a successful backup, save the store - // info. If we save successfully, we must - // delete the file next time we start a backup + const Configuration &conf(GetConfiguration()); - deleteStoreObjectInfoFile = - SerializeStoreObjectInfo( - clientStoreMarker, - lastSyncTime, nextSyncTime); + std::auto_ptr<FileLogger> fileLogger; - // -------------------------------------------------------------------------------------------- - } - catch(BoxException &e) - { - errorOccurred = true; - errorString = e.what(); - errorCode = e.GetType(); - errorSubCode = e.GetSubType(); - } - catch(std::exception &e) - { - BOX_ERROR("Internal error during " - "backup run: " << e.what()); - errorOccurred = true; - errorString = e.what(); - } - catch(...) - { - // TODO: better handling of exceptions here... - // need to be very careful - errorOccurred = true; - } - - if(errorOccurred) - { - // Is it a berkely db failure? - bool isBerkelyDbFailure = false; + if (conf.KeyExists("LogFile")) + { + Log::Level level = Log::INFO; + if (conf.KeyExists("LogFileLevel")) + { + level = Logging::GetNamedLevel( + conf.GetKeyValue("LogFileLevel")); + } + fileLogger.reset(new FileLogger(conf.GetKeyValue("LogFile"), + level)); + } - if (errorCode == BackupStoreException::ExceptionType - && errorSubCode == BackupStoreException::BerkelyDBFailure) - { - isBerkelyDbFailure = true; - } + std::string extendedLogFile; + if (conf.KeyExists("ExtendedLogFile")) + { + extendedLogFile = conf.GetKeyValue("ExtendedLogFile"); + } + + if (conf.KeyExists("LogAllFileAccess")) + { + mLogAllFileAccess = conf.GetKeyValueBool("LogAllFileAccess"); + } + + // Then create a client context object (don't + // just connect, as this may be unnecessary) + BackupClientContext clientContext + ( + *mpLocationResolver, + mTlsContext, + conf.GetKeyValue("StoreHostname"), + conf.GetKeyValueInt("StorePort"), + conf.GetKeyValueInt("AccountNumber"), + conf.GetKeyValueBool("ExtendedLogging"), + conf.KeyExists("ExtendedLogFile"), + extendedLogFile, *mpProgressNotifier + ); + + // The minimum age a file needs to be before it will be + // considered for uploading + box_time_t minimumFileAge = SecondsToBoxTime( + conf.GetKeyValueInt("MinimumFileAge")); - if(isBerkelyDbFailure) - { - // Delete corrupt files - DeleteCorruptBerkelyDbFiles(); - } + // The maximum time we'll wait to upload a file, regardless + // of how often it's modified + box_time_t maxUploadWait = SecondsToBoxTime( + conf.GetKeyValueInt("MaxUploadWait")); + // Adjust by subtracting the minimum file age, so is relative + // to sync period end in comparisons + if (maxUploadWait > minimumFileAge) + { + maxUploadWait -= minimumFileAge; + } + else + { + maxUploadWait = 0; + } - // Clear state data - syncPeriodStart = 0; - // go back to beginning of time - clientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything - DeleteAllLocations(); - DeleteAllIDMaps(); + // Calculate the sync period of files to examine + box_time_t syncPeriodStart = mLastSyncTime; + box_time_t syncPeriodEnd = GetCurrentBoxTime() - minimumFileAge; - // Handle restart? - if(StopRun()) - { - BOX_NOTICE("Exception (" << errorCode - << "/" << errorSubCode - << ") due to signal"); - return; - } + if(syncPeriodStart >= syncPeriodEnd && + syncPeriodStart - syncPeriodEnd < minimumFileAge) + { + // This can happen if we receive a force-sync command less + // than minimumFileAge after the last sync. Deal with it by + // moving back syncPeriodStart, which should not do any + // damage. + syncPeriodStart = syncPeriodEnd - + SecondsToBoxTime(1); + } - // If the Berkely db files get corrupted, delete them and try again immediately - if(isBerkelyDbFailure) - { - BOX_ERROR("Berkely db inode map files corrupted, deleting and restarting scan. Renamed files and directories will not be tracked until after this scan."); - ::sleep(1); - } - else - { - // Not restart/terminate, pause and retry - // Notify administrator - NotifySysadmin(NotifyEvent_BackupError); - SetState(State_Error); - BOX_ERROR("Exception caught (" - << errorString - << " " << errorCode - << "/" << errorSubCode - << "), reset state and " - "waiting to retry..."); - ::sleep(10); - nextSyncTime = currentSyncStartTime + - SecondsToBoxTime(90) + - Random::RandomInt( - updateStoreInterval >> - SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); - } - } + if(syncPeriodStart >= syncPeriodEnd) + { + BOX_ERROR("Invalid (negative) sync period: " + "perhaps your clock is going " + "backwards (" << syncPeriodStart << + " to " << syncPeriodEnd << ")"); + THROW_EXCEPTION(ClientException, + ClockWentBackwards); + } - // Log the stats - BOX_NOTICE("File statistics: total file size uploaded " - << BackupStoreFile::msStats.mBytesInEncodedFiles - << ", bytes already on server " - << BackupStoreFile::msStats.mBytesAlreadyOnServer - << ", encoded size " - << BackupStoreFile::msStats.mTotalFileStreamSize); - BackupStoreFile::ResetStats(); + // Check logic + ASSERT(syncPeriodEnd > syncPeriodStart); + // Paranoid check on sync times + if(syncPeriodStart >= syncPeriodEnd) return; + + // Adjust syncPeriodEnd to emulate snapshot + // behaviour properly + box_time_t syncPeriodEndExtended = syncPeriodEnd; + + // Using zero min file age? + if(minimumFileAge == 0) + { + // Add a year on to the end of the end time, + // to make sure we sync files which are + // modified after the scan run started. + // Of course, they may be eligible to be + // synced again the next time round, + // but this should be OK, because the changes + // only upload should upload no data. + syncPeriodEndExtended += SecondsToBoxTime( + (time_t)(356*24*3600)); + } + + // Set up the sync parameters + BackupClientDirectoryRecord::SyncParams params(*mpRunStatusProvider, + *mpSysadminNotifier, *mpProgressNotifier, clientContext); + params.mSyncPeriodStart = syncPeriodStart; + params.mSyncPeriodEnd = syncPeriodEndExtended; + // use potentially extended end time + params.mMaxUploadWait = maxUploadWait; + params.mFileTrackingSizeThreshold = + conf.GetKeyValueInt("FileTrackingSizeThreshold"); + params.mDiffingUploadSizeThreshold = + conf.GetKeyValueInt("DiffingUploadSizeThreshold"); + params.mMaxFileTimeInFuture = + SecondsToBoxTime(conf.GetKeyValueInt("MaxFileTimeInFuture")); + mDeleteRedundantLocationsAfter = + conf.GetKeyValueInt("DeleteRedundantLocationsAfter"); + mStorageLimitExceeded = false; + mReadErrorsOnFilesystemObjects = false; - // Tell anything connected to the command socket - SendSyncStartOrFinish(false /* finish */); + // Setup various timings + int maximumDiffingTime = 600; + int keepAliveTime = 60; - // Touch a file to record times in filesystem - TouchFileInWorkingDir("last_sync_finish"); - } + // max diffing time, keep-alive time + if(conf.KeyExists("MaximumDiffingTime")) + { + maximumDiffingTime = conf.GetKeyValueInt("MaximumDiffingTime"); + } + if(conf.KeyExists("KeepAliveTime")) + { + keepAliveTime = conf.GetKeyValueInt("KeepAliveTime"); + } + + clientContext.SetMaximumDiffingTime(maximumDiffingTime); + clientContext.SetKeepAliveTime(keepAliveTime); + + // Set store marker + clientContext.SetClientStoreMarker(mClientStoreMarker); + + // Set up the locations, if necessary -- + // need to do it here so we have a + // (potential) connection to use + if(mLocations.empty()) + { + const Configuration &locations( + conf.GetSubConfiguration( + "BackupLocations")); - // Set state - SetState(storageLimitExceeded?State_StorageLimitExceeded:State_Idle); + // Make sure all the directory records + // are set up + SetupLocations(clientContext, locations); + } + + mpProgressNotifier->NotifyIDMapsSetup(clientContext); + + // Get some ID maps going + SetupIDMapsForSync(); - } while(!StopRun()); + // Delete any unused directories? + DeleteUnusedRootDirEntries(clientContext); + + // Go through the records, syncing them + for(std::vector<Location *>::const_iterator + i(mLocations.begin()); + i != mLocations.end(); ++i) + { + // Set current and new ID map pointers + // in the context + clientContext.SetIDMaps(mCurrentIDMaps[(*i)->mIDMapIndex], + mNewIDMaps[(*i)->mIDMapIndex]); - // Make sure we have a clean start next time round (if restart) - DeleteAllLocations(); - DeleteAllIDMaps(); + // Set exclude lists (context doesn't + // take ownership) + clientContext.SetExcludeLists( + (*i)->mpExcludeFiles, + (*i)->mpExcludeDirs); + + // Sync the directory + (*i)->mpDirectoryRecord->SyncDirectory( + params, + BackupProtocolClientListDirectory::RootDirectory, + (*i)->mPath, std::string("/") + (*i)->mName); + + // Unset exclude lists (just in case) + clientContext.SetExcludeLists(0, 0); + } + + // Perform any deletions required -- these are + // delayed until the end to allow renaming to + // happen neatly. + clientContext.PerformDeletions(); + + // Close any open connection + clientContext.CloseAnyOpenConnection(); + + // Get the new store marker + mClientStoreMarker = clientContext.GetClientStoreMarker(); + mStorageLimitExceeded = clientContext.StorageLimitExceeded(); + mReadErrorsOnFilesystemObjects = + params.mReadErrorsOnFilesystemObjects; + + if(!mStorageLimitExceeded) + { + // The start time of the next run is the end time of this + // run. This is only done if the storage limit wasn't + // exceeded (as things won't have been done properly if + // it was) + mLastSyncTime = syncPeriodEnd; + } + + // Commit the ID Maps + CommitIDMapsAfterSync(); + + // Calculate when the next sync run should be + mNextSyncTime = mCurrentSyncStartTime + + mUpdateStoreInterval + + Random::RandomInt(mUpdateStoreInterval >> + SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); + + // -------------------------------------------------------------------------------------------- + + // We had a successful backup, save the store + // info. If we save successfully, we must + // delete the file next time we start a backup + + mDeleteStoreObjectInfoFile = + SerializeStoreObjectInfo(mLastSyncTime, + mNextSyncTime); + + // -------------------------------------------------------------------------------------------- } +void BackupDaemon::OnBackupStart() +{ + // Touch a file to record times in filesystem + TouchFileInWorkingDir("last_sync_start"); + + // Reset statistics on uploads + BackupStoreFile::ResetStats(); + + // Tell anything connected to the command socket + SendSyncStartOrFinish(true /* start */); + + // Notify administrator + NotifySysadmin(SysadminNotifier::BackupStart); + + // Set state and log start + SetState(State_Connected); + BOX_NOTICE("Beginning scan of local files"); +} + +void BackupDaemon::OnBackupFinish() +{ + // Log + BOX_NOTICE("Finished scan of local files"); + + // Log the stats + BOX_NOTICE("File statistics: total file size uploaded " + << BackupStoreFile::msStats.mBytesInEncodedFiles + << ", bytes already on server " + << BackupStoreFile::msStats.mBytesAlreadyOnServer + << ", encoded size " + << BackupStoreFile::msStats.mTotalFileStreamSize); + + // Reset statistics again + BackupStoreFile::ResetStats(); + + // Notify administrator + NotifySysadmin(SysadminNotifier::BackupFinish); + + // Tell anything connected to the command socket + SendSyncStartOrFinish(false /* finish */); + + // Touch a file to record times in filesystem + TouchFileInWorkingDir("last_sync_finish"); +} // -------------------------------------------------------------------------- // // Function // Name: BackupDaemon::UseScriptToSeeIfSyncAllowed() -// Purpose: Private. Use a script to see if the sync should be allowed (if configured) -// Returns -1 if it's allowed, time in seconds to wait otherwise. +// Purpose: Private. Use a script to see if the sync should be +// allowed now (if configured). Returns -1 if it's +// allowed, time in seconds to wait otherwise. // Created: 21/6/04 // // -------------------------------------------------------------------------- @@ -1250,7 +1021,8 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed() pid_t pid = 0; try { - std::auto_ptr<IOStream> pscript(LocalProcessStream(conf.GetKeyValue("SyncAllowScript").c_str(), pid)); + std::auto_ptr<IOStream> pscript(LocalProcessStream( + conf.GetKeyValue("SyncAllowScript").c_str(), pid)); // Read in the result IOStreamGetLine getLine(*pscript); @@ -1323,33 +1095,13 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed() // -------------------------------------------------------------------------- void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut) { -#ifdef WIN32 - DWORD requiredDelayMs = BoxTimeToMilliSeconds(RequiredDelay); - - DWORD result = WaitForSingleObject(mhCommandReceivedEvent, - (DWORD)requiredDelayMs); - - if(result == WAIT_OBJECT_0) - { - DoSyncFlagOut = this->mDoSyncFlagOut; - SyncIsForcedOut = this->mSyncIsForcedOut; - ResetEvent(mhCommandReceivedEvent); - } - else if(result == WAIT_TIMEOUT) + ASSERT(mapCommandSocketInfo.get()); + if(!mapCommandSocketInfo.get()) { - DoSyncFlagOut = false; - SyncIsForcedOut = false; - } - else - { - BOX_ERROR("Unexpected result from WaitForSingleObject: " - "error " << GetLastError()); + // failure case isn't too bad + ::sleep(1); + return; } - - return; -#else // ! WIN32 - ASSERT(mpCommandSocketInfo != 0); - if(mpCommandSocketInfo == 0) {::sleep(1); return;} // failure case isn't too bad BOX_TRACE("Wait on command socket, delay = " << RequiredDelay); @@ -1362,12 +1114,12 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla if(timeout == INFTIM) timeout = 100000; // Wait for socket connection, or handle a command? - if(mpCommandSocketInfo->mpConnectedSocket.get() == 0) + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { // No connection, listen for a new one - mpCommandSocketInfo->mpConnectedSocket.reset(mpCommandSocketInfo->mListeningSocket.Accept(timeout).release()); + mapCommandSocketInfo->mpConnectedSocket.reset(mapCommandSocketInfo->mListeningSocket.Accept(timeout).release()); - if(mpCommandSocketInfo->mpConnectedSocket.get() == 0) + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { // If a connection didn't arrive, there was a timeout, which means we've // waited long enough and it's time to go. @@ -1386,7 +1138,7 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla { uid_t remoteEUID = 0xffff; gid_t remoteEGID = 0xffff; - if(mpCommandSocketInfo->mpConnectedSocket->GetPeerCredentials(remoteEUID, remoteEGID)) + if(mapCommandSocketInfo->mpConnectedSocket->GetPeerCredentials(remoteEUID, remoteEGID)) { // Credentials are available -- check UID if(remoteEUID == ::getuid()) @@ -1403,7 +1155,7 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla { // Dump the connection BOX_ERROR("Incoming command connection from peer had different user ID than this process, or security check could not be completed."); - mpCommandSocketInfo->mpConnectedSocket.reset(); + mapCommandSocketInfo->mpConnectedSocket.reset(); return; } else @@ -1420,7 +1172,7 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla conf.GetKeyValueInt("MinimumFileAge"), conf.GetKeyValueInt("MaxUploadWait"), mState); - mpCommandSocketInfo->mpConnectedSocket->Write(summary, summarySize); + mapCommandSocketInfo->mpConnectedSocket->Write(summary, summarySize); // Set the timeout to something very small, so we don't wait too long on waiting // for any incoming data @@ -1430,22 +1182,22 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla } // So there must be a connection now. - ASSERT(mpCommandSocketInfo->mpConnectedSocket.get() != 0); + ASSERT(mapCommandSocketInfo->mpConnectedSocket.get() != 0); // Is there a getline object ready? - if(mpCommandSocketInfo->mpGetLine == 0) + if(mapCommandSocketInfo->mpGetLine == 0) { // Create a new one - mpCommandSocketInfo->mpGetLine = new IOStreamGetLine(*(mpCommandSocketInfo->mpConnectedSocket.get())); + mapCommandSocketInfo->mpGetLine = new IOStreamGetLine(*(mapCommandSocketInfo->mpConnectedSocket.get())); } // Ping the remote side, to provide errors which will mean the socket gets closed - mpCommandSocketInfo->mpConnectedSocket->Write("ping\n", 5); + mapCommandSocketInfo->mpConnectedSocket->Write("ping\n", 5); // Wait for a command or something on the socket std::string command; - while(mpCommandSocketInfo->mpGetLine != 0 && !mpCommandSocketInfo->mpGetLine->IsEOF() - && mpCommandSocketInfo->mpGetLine->GetLine(command, false /* no preprocessing */, timeout)) + while(mapCommandSocketInfo->mpGetLine != 0 && !mapCommandSocketInfo->mpGetLine->IsEOF() + && mapCommandSocketInfo->mpGetLine->GetLine(command, false /* no preprocessing */, timeout)) { BOX_TRACE("Receiving command '" << command << "' over command socket"); @@ -1490,7 +1242,7 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla // Send a response back? if(sendResponse) { - mpCommandSocketInfo->mpConnectedSocket->Write(sendOK?"ok\n":"error\n", sendOK?3:6); + mapCommandSocketInfo->mpConnectedSocket->Write(sendOK?"ok\n":"error\n", sendOK?3:6); } // Set timeout to something very small, so this just checks for data which is waiting @@ -1498,18 +1250,39 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla } // Close on EOF? - if(mpCommandSocketInfo->mpGetLine != 0 && mpCommandSocketInfo->mpGetLine->IsEOF()) + if(mapCommandSocketInfo->mpGetLine != 0 && mapCommandSocketInfo->mpGetLine->IsEOF()) { CloseCommandConnection(); } } + catch(ConnectionException &ce) + { + BOX_NOTICE("Failed to write to command socket: " << ce.what()); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } catch(std::exception &e) { - BOX_ERROR("Internal error in command socket thread: " - << e.what()); - // If an error occurs, and there is a connection active, just close that - // connection and continue. Otherwise, let the error propagate. - if(mpCommandSocketInfo->mpConnectedSocket.get() == 0) + BOX_ERROR("Failed to write to command socket: " << + e.what()); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { throw; // thread will die } @@ -1521,9 +1294,13 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla } catch(...) { - // If an error occurs, and there is a connection active, just close that - // connection and continue. Otherwise, let the error propagate. - if(mpCommandSocketInfo->mpConnectedSocket.get() == 0) + BOX_ERROR("Failed to write to command socket: unknown error"); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { throw; // thread will die } @@ -1533,7 +1310,6 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla CloseCommandConnection(); } } -#endif // WIN32 } @@ -1547,17 +1323,16 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla // -------------------------------------------------------------------------- void BackupDaemon::CloseCommandConnection() { -#ifndef WIN32 try { BOX_TRACE("Closing command connection"); - if(mpCommandSocketInfo->mpGetLine) + if(mapCommandSocketInfo->mpGetLine) { - delete mpCommandSocketInfo->mpGetLine; - mpCommandSocketInfo->mpGetLine = 0; + delete mapCommandSocketInfo->mpGetLine; + mapCommandSocketInfo->mpGetLine = 0; } - mpCommandSocketInfo->mpConnectedSocket.reset(); + mapCommandSocketInfo->mpConnectedSocket.reset(); } catch(std::exception &e) { @@ -1568,7 +1343,6 @@ void BackupDaemon::CloseCommandConnection() { // Ignore any errors } -#endif } @@ -1586,27 +1360,15 @@ void BackupDaemon::SendSyncStartOrFinish(bool SendStart) // The bbackupctl program can't rely on a state change, because it // may never change if the server doesn't need to be contacted. - if(mpCommandSocketInfo != NULL && -#ifdef WIN32 - mpCommandSocketInfo->mListeningSocket.IsConnected() -#else - mpCommandSocketInfo->mpConnectedSocket.get() != 0 -#endif - ) + if(mapCommandSocketInfo.get() && + mapCommandSocketInfo->mpConnectedSocket.get() != 0) { std::string message = SendStart ? "start-sync" : "finish-sync"; try { -#ifdef WIN32 - EnterCriticalSection(&mMessageQueueLock); - mMessageList.push_back(message); - SetEvent(mhMessageToSendEvent); - LeaveCriticalSection(&mMessageQueueLock); -#else message += "\n"; - mpCommandSocketInfo->mpConnectedSocket->Write( + mapCommandSocketInfo->mpConnectedSocket->Write( message.c_str(), message.size()); -#endif } catch(std::exception &e) { @@ -1668,14 +1430,20 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con // Just a check to make sure it's right. DeleteAllLocations(); - // Going to need a copy of the root directory. Get a connection, and fetch it. + // Going to need a copy of the root directory. Get a connection, + // and fetch it. BackupProtocolClient &connection(rClientContext.GetConnection()); - // Ask server for a list of everything in the root directory, which is a directory itself - std::auto_ptr<BackupProtocolClientSuccess> dirreply(connection.QueryListDirectory( + // Ask server for a list of everything in the root directory, + // which is a directory itself + std::auto_ptr<BackupProtocolClientSuccess> dirreply( + connection.QueryListDirectory( BackupProtocolClientListDirectory::RootDirectory, - BackupProtocolClientListDirectory::Flags_Dir, // only directories - BackupProtocolClientListDirectory::Flags_Deleted | BackupProtocolClientListDirectory::Flags_OldVersion, // exclude old/deleted stuff + // only directories + BackupProtocolClientListDirectory::Flags_Dir, + // exclude old/deleted stuff + BackupProtocolClientListDirectory::Flags_Deleted | + BackupProtocolClientListDirectory::Flags_OldVersion, false /* no attributes */)); // Retrieve the directory from the stream following @@ -1755,33 +1523,41 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con #endif // HAVE_STRUCT_MNTENT_MNT_DIR // Check sorting and that things are as we expect ASSERT(mountPoints.size() > 0); -#ifndef NDEBUG +#ifndef BOX_RELEASE_BUILD { std::set<std::string, mntLenCompare>::reverse_iterator i(mountPoints.rbegin()); ASSERT(*i == "/"); } -#endif // n NDEBUG +#endif // n BOX_RELEASE_BUILD #endif // n HAVE_STRUCT_STATFS_F_MNTONNAME || n HAVE_STRUCT_STATVFS_F_MNTONNAME #endif // HAVE_MOUNTS // Then... go through each of the entries in the configuration, // making sure there's a directory created for it. - for(std::list<std::pair<std::string, Configuration> >::const_iterator i = rLocationsConf.mSubConfigurations.begin(); - i != rLocationsConf.mSubConfigurations.end(); ++i) - { - BOX_TRACE("new location: " << i->first); + std::vector<std::string> locNames = + rLocationsConf.GetSubConfigurationNames(); + + for(std::vector<std::string>::iterator + pLocName = locNames.begin(); + pLocName != locNames.end(); + pLocName++) + { + const Configuration& rConfig( + rLocationsConf.GetSubConfiguration(*pLocName)); + BOX_TRACE("new location: " << *pLocName); + // Create a record for it std::auto_ptr<Location> apLoc(new Location); try { // Setup names in the location record - apLoc->mName = i->first; - apLoc->mPath = i->second.GetKeyValue("Path"); + apLoc->mName = *pLocName; + apLoc->mPath = rConfig.GetKeyValue("Path"); // Read the exclude lists from the Configuration - apLoc->mpExcludeFiles = BackupClientMakeExcludeList_Files(i->second); - apLoc->mpExcludeDirs = BackupClientMakeExcludeList_Dirs(i->second); + apLoc->mpExcludeFiles = BackupClientMakeExcludeList_Files(rConfig); + apLoc->mpExcludeDirs = BackupClientMakeExcludeList_Dirs(rConfig); // Does this exist on the server? // Remove from dir object early, so that if we fail @@ -1814,10 +1590,9 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con if(::statfs(apLoc->mPath.c_str(), &s) != 0) #endif // HAVE_STRUCT_STATVFS_F_MNTONNAME { - BOX_WARNING("Failed to stat location " + BOX_LOG_SYS_WARNING("Failed to stat location " "path '" << apLoc->mPath << - "' (" << strerror(errno) << - "), skipping location '" << + "', skipping location '" << apLoc->mName << "'"); continue; } @@ -1929,7 +1704,8 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con // Create and store the directory object for the root of this location ASSERT(oid != 0); - BackupClientDirectoryRecord *precord = new BackupClientDirectoryRecord(oid, i->first); + BackupClientDirectoryRecord *precord = + new BackupClientDirectoryRecord(oid, *pLocName); apLoc->mpDirectoryRecord.reset(precord); // Push it back on the vector of locations @@ -2007,8 +1783,8 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con // -------------------------------------------------------------------------- void BackupDaemon::SetupIDMapsForSync() { - // Need to do different things depending on whether it's an in memory implementation, - // or whether it's all stored on disc. + // Need to do different things depending on whether it's an + // in memory implementation, or whether it's all stored on disc. #ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION @@ -2016,8 +1792,8 @@ void BackupDaemon::SetupIDMapsForSync() DeleteIDMapVector(mNewIDMaps); FillIDMapVector(mNewIDMaps, true /* new maps */); - // Then make sure that the current maps have objects, even if they are empty - // (for the very first run) + // Then make sure that the current maps have objects, + // even if they are empty (for the very first run) if(mCurrentIDMaps.empty()) { FillIDMapVector(mCurrentIDMaps, false /* current maps */); @@ -2191,9 +1967,8 @@ void BackupDaemon::CommitIDMapsAfterSync() #endif if(::rename(newmap.c_str(), target.c_str()) != 0) { - BOX_ERROR("failed to rename ID map: " << newmap - << " to " << target << ": " - << strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to rename ID map: " << + newmap << " to " << target); THROW_EXCEPTION(CommonException, OSFileError) } } @@ -2281,20 +2056,14 @@ void BackupDaemon::SetState(int State) sprintf(newState, "state %d", State); std::string message = newState; -#ifdef WIN32 - EnterCriticalSection(&mMessageQueueLock); - mMessageList.push_back(newState); - SetEvent(mhMessageToSendEvent); - LeaveCriticalSection(&mMessageQueueLock); -#else message += "\n"; - if(mpCommandSocketInfo == 0) + if(!mapCommandSocketInfo.get()) { return; } - if(mpCommandSocketInfo->mpConnectedSocket.get() == 0) + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) { return; } @@ -2302,22 +2071,27 @@ void BackupDaemon::SetState(int State) // Something connected to the command socket, tell it about the new state try { - mpCommandSocketInfo->mpConnectedSocket->Write(message.c_str(), + mapCommandSocketInfo->mpConnectedSocket->Write(message.c_str(), message.length()); } + catch(ConnectionException &ce) + { + BOX_NOTICE("Failed to write state to command socket: " << + ce.what()); + CloseCommandConnection(); + } catch(std::exception &e) { - BOX_ERROR("Internal error while writing state " - "to command socket: " << e.what()); + BOX_ERROR("Failed to write state to command socket: " << + e.what()); CloseCommandConnection(); } catch(...) { - BOX_ERROR("Internal error while writing state " - "to command socket: unknown error"); + BOX_ERROR("Failed to write state to command socket: " + "unknown error"); CloseCommandConnection(); } -#endif } @@ -2351,7 +2125,7 @@ void BackupDaemon::TouchFileInWorkingDir(const char *Filename) // Created: 25/2/04 // // -------------------------------------------------------------------------- -void BackupDaemon::NotifySysadmin(int Event) +void BackupDaemon::NotifySysadmin(SysadminNotifier::EventCode Event) { static const char *sEventNames[] = { @@ -2360,31 +2134,47 @@ void BackupDaemon::NotifySysadmin(int Event) "backup-error", "backup-start", "backup-finish", + "backup-ok", 0 }; - BOX_TRACE("sizeof(sEventNames) == " << sizeof(sEventNames)); - BOX_TRACE("sizeof(*sEventNames) == " << sizeof(*sEventNames)); - BOX_TRACE("NotifyEvent__MAX == " << NotifyEvent__MAX); - ASSERT((sizeof(sEventNames)/sizeof(*sEventNames)) == NotifyEvent__MAX + 1); + // BOX_TRACE("sizeof(sEventNames) == " << sizeof(sEventNames)); + // BOX_TRACE("sizeof(*sEventNames) == " << sizeof(*sEventNames)); + // BOX_TRACE("NotifyEvent__MAX == " << NotifyEvent__MAX); + ASSERT((sizeof(sEventNames)/sizeof(*sEventNames)) == SysadminNotifier::MAX + 1); - BOX_TRACE("BackupDaemon::NotifySysadmin() called, event = " << - sEventNames[Event]); - - if(Event < 0 || Event >= NotifyEvent__MAX) + if(Event < 0 || Event >= SysadminNotifier::MAX) { + BOX_ERROR("BackupDaemon::NotifySysadmin() called for " + "invalid event code " << Event); THROW_EXCEPTION(BackupStoreException, BadNotifySysadminEventCode); } - // Don't send lots of repeated messages - if(mNotificationsSent[Event] && - Event != NotifyEvent_BackupStart && - Event != NotifyEvent_BackupFinish) + BOX_TRACE("BackupDaemon::NotifySysadmin() called, event = " << + sEventNames[Event]); + + if(!GetConfiguration().KeyExists("NotifyAlways") || + !GetConfiguration().GetKeyValueBool("NotifyAlways")) { - BOX_WARNING("Suppressing duplicate notification about " << - sEventNames[Event]); - return; + // Don't send lots of repeated messages + // Note: backup-start and backup-finish will always be + // logged, because mLastNotifiedEvent is never set to + // these values and therefore they are never "duplicates". + if(mLastNotifiedEvent == Event) + { + if(Event == SysadminNotifier::BackupOK) + { + BOX_INFO("Suppressing duplicate notification " + "about " << sEventNames[Event]); + } + else + { + BOX_WARNING("Suppressing duplicate notification " + "about " << sEventNames[Event]); + } + return; + } } // Is there a notification script? @@ -2392,10 +2182,10 @@ void BackupDaemon::NotifySysadmin(int Event) if(!conf.KeyExists("NotifyScript")) { // Log, and then return - if(Event != NotifyEvent_BackupStart && - Event != NotifyEvent_BackupFinish) + if(Event != SysadminNotifier::BackupStart && + Event != SysadminNotifier::BackupFinish) { - BOX_ERROR("Not notifying administrator about event " + BOX_INFO("Not notifying administrator about event " << sEventNames[Event] << " -- set NotifyScript " "to do this in future"); } @@ -2407,20 +2197,22 @@ void BackupDaemon::NotifySysadmin(int Event) sEventNames[Event]); // Log what we're about to do - BOX_NOTICE("About to notify administrator about event " + BOX_INFO("About to notify administrator about event " << sEventNames[Event] << ", running script '" << script << "'"); // Then do it - if(::system(script.c_str()) != 0) + int returnCode = ::system(script.c_str()); + if(returnCode != 0) { - BOX_ERROR("Notify script returned an error code. ('" - << script << "')"); + BOX_WARNING("Notify script returned error code: " << + returnCode << " ('" << script << "')"); + } + else if(Event != SysadminNotifier::BackupStart && + Event != SysadminNotifier::BackupFinish) + { + mLastNotifiedEvent = Event; } - - // Flag that this is done so the administrator isn't constantly - // bombarded with lots of errors - mNotificationsSent[Event] = true; } @@ -2461,13 +2253,13 @@ void BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &rContext) // Entries to delete, and it's the right time to do so... BOX_NOTICE("Deleting unused locations from store root..."); BackupProtocolClient &connection(rContext.GetConnection()); - for(std::vector<std::pair<int64_t,std::string> >::iterator i(mUnusedRootDirEntries.begin()); i != mUnusedRootDirEntries.end(); ++i) + for(std::vector<std::pair<int64_t,std::string> >::iterator + i(mUnusedRootDirEntries.begin()); + i != mUnusedRootDirEntries.end(); ++i) { connection.QueryDeleteDirectory(i->first); - - // Log this - BOX_NOTICE("Deleted " << i->second << " (ID " << i->first - << ") from store root"); + rContext.GetProgressNotifier().NotifyFileDeleted( + i->first, i->second); } // Reset state @@ -2738,9 +2530,12 @@ BackupDaemon::CommandSocketInfo::~CommandSocketInfo() // -------------------------------------------------------------------------- // // Function -// Name: BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time_t theLastSyncTime, box_time_t theNextSyncTime) -// Purpose: Serializes remote directory and file information into a stream of bytes, using an Archive abstraction. -// +// Name: BackupDaemon::SerializeStoreObjectInfo( +// box_time_t theLastSyncTime, +// box_time_t theNextSyncTime) +// Purpose: Serializes remote directory and file information +// into a stream of bytes, using an Archive +// abstraction. // Created: 2005/04/11 // // -------------------------------------------------------------------------- @@ -2749,7 +2544,8 @@ static const int STOREOBJECTINFO_MAGIC_ID_VALUE = 0x7777525F; static const std::string STOREOBJECTINFO_MAGIC_ID_STRING = "BBACKUPD-STATE"; static const int STOREOBJECTINFO_VERSION = 2; -bool BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time_t theLastSyncTime, box_time_t theNextSyncTime) const +bool BackupDaemon::SerializeStoreObjectInfo(box_time_t theLastSyncTime, + box_time_t theNextSyncTime) const { if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) { @@ -2778,7 +2574,7 @@ bool BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time anArchive.Write(STOREOBJECTINFO_MAGIC_ID_STRING); anArchive.Write(STOREOBJECTINFO_VERSION); anArchive.Write(GetLoadedConfigModifiedTime()); - anArchive.Write(aClientStoreMarker); + anArchive.Write(mClientStoreMarker); anArchive.Write(theLastSyncTime); anArchive.Write(theNextSyncTime); @@ -2830,15 +2626,13 @@ bool BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time } catch(std::exception &e) { - BOX_ERROR("Internal error writing store object " - "info file (" << StoreObjectInfoFile << "): " - << e.what()); + BOX_ERROR("Failed to write StoreObjectInfoFile: " << + StoreObjectInfoFile << ": " << e.what()); } catch(...) { - BOX_ERROR("Internal error writing store object " - "info file (" << StoreObjectInfoFile << "): " - "unknown error"); + BOX_ERROR("Failed to write StoreObjectInfoFile: " << + StoreObjectInfoFile << ": unknown error"); } return created; @@ -2847,13 +2641,17 @@ bool BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time // -------------------------------------------------------------------------- // // Function -// Name: BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_time_t & theLastSyncTime, box_time_t & theNextSyncTime) -// Purpose: Deserializes remote directory and file information from a stream of bytes, using an Archive abstraction. -// +// Name: BackupDaemon::DeserializeStoreObjectInfo( +// box_time_t & theLastSyncTime, +// box_time_t & theNextSyncTime) +// Purpose: Deserializes remote directory and file information +// from a stream of bytes, using an Archive +// abstraction. // Created: 2005/04/11 // // -------------------------------------------------------------------------- -bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_time_t & theLastSyncTime, box_time_t & theNextSyncTime) +bool BackupDaemon::DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, + box_time_t & theNextSyncTime) { // // @@ -2945,7 +2743,7 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ // // this is it, go at it // - anArchive.Read(aClientStoreMarker); + anArchive.Read(mClientStoreMarker); anArchive.Read(theLastSyncTime); anArchive.Read(theNextSyncTime); @@ -3023,7 +2821,7 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ DeleteAllLocations(); - aClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; + mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; theLastSyncTime = 0; theNextSyncTime = 0; @@ -3057,9 +2855,10 @@ bool BackupDaemon::DeleteStoreObjectInfo() const // Check to see if the file exists if(!FileExists(storeObjectInfoFile.c_str())) { - // File doesn't exist -- so can't be deleted. But something isn't quite right, so log a message - BOX_WARNING("Store object info file did not exist when it " - "was supposed to. (" << storeObjectInfoFile << ")"); + // File doesn't exist -- so can't be deleted. But something + // isn't quite right, so log a message + BOX_WARNING("StoreObjectInfoFile did not exist when it " + "was supposed to: " << storeObjectInfoFile); // Return true to stop things going around in a loop return true; @@ -3068,8 +2867,8 @@ bool BackupDaemon::DeleteStoreObjectInfo() const // Actually delete it if(::unlink(storeObjectInfoFile.c_str()) != 0) { - BOX_ERROR("Failed to delete the old store object info file: " - << storeObjectInfoFile << ": "<< strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to delete the old " + "StoreObjectInfoFile: " << storeObjectInfoFile); return false; } diff --git a/bin/bbackupd/BackupDaemon.h b/bin/bbackupd/BackupDaemon.h index 62f9c393..0c864abd 100644 --- a/bin/bbackupd/BackupDaemon.h +++ b/bin/bbackupd/BackupDaemon.h @@ -14,16 +14,20 @@ #include <string> #include <memory> +#include "BackupClientContext.h" +#include "BackupClientDirectoryRecord.h" #include "BoxTime.h" #include "Daemon.h" -#include "BackupClientDirectoryRecord.h" +#include "Logging.h" #include "Socket.h" #include "SocketListen.h" #include "SocketStream.h" -#include "Logging.h" +#include "TLSContext.h" + #include "autogen_BackupProtocolClient.h" #ifdef WIN32 + #include "WinNamedPipeListener.h" #include "WinNamedPipeStream.h" #endif @@ -43,7 +47,8 @@ class Archive; // Created: 2003/10/08 // // -------------------------------------------------------------------------- -class BackupDaemon : public Daemon, ProgressNotifier +class BackupDaemon : public Daemon, ProgressNotifier, LocationResolver, +RunStatusProvider, SysadminNotifier { public: BackupDaemon(); @@ -52,10 +57,10 @@ public: private: // methods below do partial (specialized) serialization of // client state only - bool SerializeStoreObjectInfo(int64_t aClientStoreMarker, - box_time_t theLastSyncTime, box_time_t theNextSyncTime) const; - bool DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, - box_time_t & theLastSyncTime, box_time_t & theNextSyncTime); + bool SerializeStoreObjectInfo(box_time_t theLastSyncTime, + box_time_t theNextSyncTime) const; + bool DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, + box_time_t & theNextSyncTime); bool DeleteStoreObjectInfo() const; BackupDaemon(const BackupDaemon &); @@ -65,6 +70,14 @@ public: std::string GetOptionString(); int ProcessOption(signed int option); int Main(const std::string &rConfigFileName); + + // This shouldn't be here, but apparently gcc on + // Windows has no idea about inherited methods... + virtual int Main(const char *DefaultConfigFile, int argc, + const char *argv[]) + { + return Daemon::Main(DefaultConfigFile, argc, argv); + } #endif void Run(); @@ -88,21 +101,22 @@ public: int GetState() {return mState;} // Allow other classes to call this too - enum - { - NotifyEvent_StoreFull = 0, - NotifyEvent_ReadError, - NotifyEvent_BackupError, - NotifyEvent_BackupStart, - NotifyEvent_BackupFinish, - NotifyEvent__MAX - // When adding notifications, remember to add strings to NotifySysadmin() - }; - void NotifySysadmin(int Event); + void NotifySysadmin(SysadminNotifier::EventCode Event); private: void Run2(); +public: + void InitCrypto(); + void RunSyncNowWithExceptionHandling(); + void RunSyncNow(); + void OnBackupStart(); + void OnBackupFinish(); + // TouchFileInWorkingDir is only here for use by Boxi. + // This does NOT constitute an API! + void TouchFileInWorkingDir(const char *Filename); + +private: void DeleteAllLocations(); void SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf); @@ -126,8 +140,6 @@ private: void CloseCommandConnection(); void SendSyncStartOrFinish(bool SendStart); - void TouchFileInWorkingDir(const char *Filename); - void DeleteUnusedRootDirEntries(BackupClientContext &rContext); #ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET @@ -137,7 +149,7 @@ private: int UseScriptToSeeIfSyncAllowed(); -private: +public: class Location { public: @@ -157,7 +169,11 @@ private: ExcludeList *mpExcludeFiles; ExcludeList *mpExcludeDirs; }; - + + typedef const std::vector<Location *> Locations; + Locations GetLocations() { return mLocations; } + +private: int mState; // what the daemon is currently doing std::vector<Location *> mLocations; @@ -179,7 +195,8 @@ private: CommandSocketInfo &operator=(const CommandSocketInfo &); public: #ifdef WIN32 - WinNamedPipeStream mListeningSocket; + WinNamedPipeListener<1 /* listen backlog */> mListeningSocket; + std::auto_ptr<WinNamedPipeStream> mpConnectedSocket; #else SocketListen<SocketStream, 1 /* listen backlog */> mListeningSocket; std::auto_ptr<SocketStream> mpConnectedSocket; @@ -188,23 +205,51 @@ private: }; // Using a socket? - CommandSocketInfo *mpCommandSocketInfo; + std::auto_ptr<CommandSocketInfo> mapCommandSocketInfo; // Stop notifications being repeated. - bool mNotificationsSent[NotifyEvent__MAX]; + SysadminNotifier::EventCode mLastNotifiedEvent; // Unused entries in the root directory wait a while before being deleted box_time_t mDeleteUnusedRootDirEntriesAfter; // time to delete them std::vector<std::pair<int64_t,std::string> > mUnusedRootDirEntries; + int64_t mClientStoreMarker; + bool mStorageLimitExceeded; + bool mReadErrorsOnFilesystemObjects; + box_time_t mLastSyncTime, mNextSyncTime; + box_time_t mCurrentSyncStartTime, mUpdateStoreInterval; + TLSContext mTlsContext; + bool mDeleteStoreObjectInfoFile; + bool mDoSyncForcedByPreviousSyncError; + public: bool StopRun() { return this->Daemon::StopRun(); } + bool StorageLimitExceeded() { return mStorageLimitExceeded; } private: bool mLogAllFileAccess; +public: + ProgressNotifier* GetProgressNotifier() { return mpProgressNotifier; } + LocationResolver* GetLocationResolver() { return mpLocationResolver; } + RunStatusProvider* GetRunStatusProvider() { return mpRunStatusProvider; } + SysadminNotifier* GetSysadminNotifier() { return mpSysadminNotifier; } + void SetProgressNotifier (ProgressNotifier* p) { mpProgressNotifier = p; } + void SetLocationResolver (LocationResolver* p) { mpLocationResolver = p; } + void SetRunStatusProvider(RunStatusProvider* p) { mpRunStatusProvider = p; } + void SetSysadminNotifier (SysadminNotifier* p) { mpSysadminNotifier = p; } + +private: + ProgressNotifier* mpProgressNotifier; + LocationResolver* mpLocationResolver; + RunStatusProvider* mpRunStatusProvider; + SysadminNotifier* mpSysadminNotifier; + /* ProgressNotifier implementation */ public: + virtual void NotifyIDMapsSetup(BackupClientContext& rContext) { } + virtual void NotifyScanDirectory( const BackupClientDirectoryRecord* pDirRecord, const std::string& rLocalPath) @@ -387,7 +432,7 @@ public: { if (mLogAllFileAccess) { - BOX_INFO("Uploading complete file: " << rLocalPath); + BOX_NOTICE("Uploading complete file: " << rLocalPath); } } virtual void NotifyFileUploadingPatch( @@ -396,7 +441,7 @@ public: { if (mLogAllFileAccess) { - BOX_INFO("Uploading patch to file: " << rLocalPath); + BOX_NOTICE("Uploading patch to file: " << rLocalPath); } } virtual void NotifyFileUploaded( @@ -406,7 +451,7 @@ public: { if (mLogAllFileAccess) { - BOX_INFO("Uploaded file: " << rLocalPath); + BOX_NOTICE("Uploaded file: " << rLocalPath); } } virtual void NotifyFileSynchronised( @@ -419,18 +464,51 @@ public: BOX_INFO("Synchronised file: " << rLocalPath); } } + virtual void NotifyDirectoryDeleted( + int64_t ObjectID, + const std::string& rRemotePath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Deleted directory: " << rRemotePath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + ")"); + } + } + virtual void NotifyFileDeleted( + int64_t ObjectID, + const std::string& rRemotePath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Deleted file: " << rRemotePath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + ")"); + } + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", " << (length - offset) << " remain, eta " << + BoxTimeToSeconds(finish - elapsed) << "s"); + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", " << (length - offset) << " remain"); + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", unknown bytes remaining"); + } #ifdef WIN32 - public: - void RunHelperThread(void); - private: - bool mDoSyncFlagOut, mSyncIsForcedOut; bool mInstallService, mRemoveService, mRunAsService; std::string mServiceName; - HANDLE mhMessageToSendEvent, mhCommandReceivedEvent; - CRITICAL_SECTION mMessageQueueLock; - std::vector<std::string> mMessageList; #endif }; diff --git a/bin/bbackupd/BackupDaemonInterface.h b/bin/bbackupd/BackupDaemonInterface.h new file mode 100644 index 00000000..5bbdd427 --- /dev/null +++ b/bin/bbackupd/BackupDaemonInterface.h @@ -0,0 +1,164 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonInterface.h +// Purpose: Interfaces for managing a BackupDaemon +// Created: 2008/12/30 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMONINTERFACE__H +#define BACKUPDAEMONINTERFACE__H + +#include <string> +// #include <map> + +// #include "BackupClientFileAttributes.h" +// #include "BackupStoreDirectory.h" +#include "BoxTime.h" +// #include "MD5Digest.h" +// #include "ReadLoggingStream.h" +// #include "RunStatusProvider.h" + +class Archive; +class BackupClientContext; +class BackupDaemon; + +// -------------------------------------------------------------------------- +// +// Class +// Name: SysadminNotifier +// Purpose: Provides a NotifySysadmin() method to send mail to the sysadmin +// Created: 2005/11/15 +// +// -------------------------------------------------------------------------- +class SysadminNotifier +{ + public: + virtual ~SysadminNotifier() { } + + typedef enum + { + StoreFull = 0, + ReadError, + BackupError, + BackupStart, + BackupFinish, + BackupOK, + MAX + // When adding notifications, remember to add + // strings to NotifySysadmin() + } + EventCode; + + virtual void NotifySysadmin(EventCode Event) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: ProgressNotifier +// Purpose: Provides methods for the backup library to inform the user +// interface about its progress with the backup +// Created: 2005/11/20 +// +// -------------------------------------------------------------------------- + +class BackupClientContext; +class BackupClientDirectoryRecord; + +class ProgressNotifier +{ + public: + virtual ~ProgressNotifier() { } + virtual void NotifyIDMapsSetup(BackupClientContext& rContext) = 0; + virtual void NotifyScanDirectory( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyDirStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyFileStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyDirListFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyMountPointSkipped( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyDirExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyUnsupportedFileType( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileReadFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyFileModifiedInFuture( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileSkippedServerFull( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadException( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const BoxException& rException) = 0; + virtual void NotifyFileUploadServerError( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int type, int subtype) = 0; + virtual void NotifyFileUploading( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadingPatch( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploaded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) = 0; + virtual void NotifyFileSynchronised( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) = 0; + virtual void NotifyDirectoryDeleted( + int64_t ObjectID, + const std::string& rRemotePath) = 0; + virtual void NotifyFileDeleted( + int64_t ObjectID, + const std::string& rRemotePath) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: LocationResolver +// Purpose: Interface for classes that can resolve locations to paths, +// like BackupDaemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class LocationResolver +{ +public: + virtual ~LocationResolver() { } + virtual bool FindLocationPathName(const std::string &rLocationName, + std::string &rPathOut) const = 0; +}; + +#endif // BACKUPDAEMONINTERFACE__H diff --git a/bin/bbackupd/Win32ServiceFunctions.cpp b/bin/bbackupd/Win32ServiceFunctions.cpp index a7bf6bd9..2df914a7 100644 --- a/bin/bbackupd/Win32ServiceFunctions.cpp +++ b/bin/bbackupd/Win32ServiceFunctions.cpp @@ -203,12 +203,12 @@ int InstallService(const char* pConfigFileName, const std::string& rServiceName) { if (pConfigFileName != NULL) { - struct stat st; + EMU_STRUCT_STAT st; if (emu_stat(pConfigFileName, &st) != 0) { - BOX_ERROR("Failed to open configuration file '" << - pConfigFileName << "': " << strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to open configuration file " + "'" << pConfigFileName << "'"); return 1; } @@ -221,7 +221,7 @@ int InstallService(const char* pConfigFileName, const std::string& rServiceName) } } - SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); + SC_HANDLE scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); if (!scm) { diff --git a/bin/bbackupd/bbackupd-config.in b/bin/bbackupd/bbackupd-config.in index 16ddb75c..925dcc3e 100755 --- a/bin/bbackupd/bbackupd-config.in +++ b/bin/bbackupd/bbackupd-config.in @@ -26,7 +26,7 @@ Parameters: explicitly, using bbackupctl sync account-num (hexdecimal) and server-hostname are supplied by the server administrator - working-dir is usually @localstatedir_expanded@ + working-dir is usually @localstatedir_expanded@/bbackupd backup directories is list of directories to back up __E @@ -227,7 +227,7 @@ SUBJECT="BACKUP PROBLEM on host $hostname" SENDTO="$current_username" if [ "\$1" = "" ]; then - echo "Usage: $0 <store-full|read-error|backup-error|backup-start|backup-finish>" >&2 + echo "Usage: \$0 <store-full|read-error|backup-error|backup-start|backup-finish>" >&2 exit 2 elif [ "\$1" = store-full ]; then $sendmail \$SENDTO <<EOM @@ -577,7 +577,7 @@ What you need to do now... more files will be backed up. You want to know about this. 6) Start the backup daemon with the command - @bindir_expanded@/bbackupd$daemon_args + @sbindir_expanded@/bbackupd$daemon_args in /etc/rc.local, or your local equivalent. Note that bbackupd must run as root. __E diff --git a/bin/bbackupd/bbackupd.cpp b/bin/bbackupd/bbackupd.cpp index a0f275b3..d334a2df 100644 --- a/bin/bbackupd/bbackupd.cpp +++ b/bin/bbackupd/bbackupd.cpp @@ -29,7 +29,7 @@ int main(int argc, const char *argv[]) MAINHELPER_START - Logging::SetProgramName("Box Backup (bbackupd)"); + Logging::SetProgramName("bbackupd"); Logging::ToConsole(true); Logging::ToSyslog (true); diff --git a/bin/bbackupd/win32/NotifySysAdmin.vbs b/bin/bbackupd/win32/NotifySysAdmin.vbs index 49082887..712d92da 100644 --- a/bin/bbackupd/win32/NotifySysAdmin.vbs +++ b/bin/bbackupd/win32/NotifySysAdmin.vbs @@ -10,44 +10,62 @@ Dim smtpserver Set WshNet = CreateObject("WScript.Network") hostname = WshNet.ComputerName -account = "0a1" +account = "0x1" from = "boxbackup@" & hostname sendto = "admin@example.com" -subjtmpl = "BACKUP PROBLEM on host " & hostname smtpserver = "smtp.example.com" +subjtmpl = "BACKUP PROBLEM on host " & hostname Set args = WScript.Arguments If args(0) = "store-full" Then subject = subjtmpl & " (store full)" - body = "The store account for "&hostname&" is full." & vbCrLf & vbCrLf & _ - "=============================" & vbCrLf & _ - "FILES ARE NOT BEING BACKED UP" & vbCrLf & _ - "=============================" & vbCrLf & vbCrLf & _ - "Please adjust the limits on account "&account&" on server "&hostname&"." _ - & vbCrLf + body = "The store account for "&hostname&" is full." & vbCrLf & _ + vbCrLf & _ + "=============================" & vbCrLf & _ + "FILES ARE NOT BEING BACKED UP" & vbCrLf & _ + "=============================" & vbCrLf & _ + vbCrLf & _ + "Please adjust the limits on account "&account&" on server "&hostname&"." _ + & vbCrLf SendMail from,sendto,subject,body ElseIf args(0) = "read-error" Then subject = subjtmpl & " (read errors)" - body = "Errors occured reading some files or directories for backup on "&hostname&"." _ - & vbCrLf & vbCrLf & _ - "===================================" & vbCrLf & _ - "THESE FILES ARE NOT BEING BACKED UP" & vbCrLf & _ - "===================================" & vbCrLf & vbCrLf & _ - "Check the logs on "&hostname&" for the files and directories which caused" & _ - "these errors, and take appropraite action." & vbCrLf & vbCrLf & _ - "Other files are being backed up." & vbCrLf + body = "Errors occurred reading some files or directories " & _ + "for backup on " & hostname & "." & vbCrLf & _ + vbCrLf & _ + "===================================" & vbCrLf & _ + "THESE FILES ARE NOT BEING BACKED UP" & vbCrLf & _ + "===================================" & vbCrLf & vbCrLf & _ + "Check the logs on "&hostname&" for the files and " & _ + "directories which caused" & vbCrLf & _ + "these errors, and take appropriate action." & vbCrLf & _ + vbCrLf & _ + "Other files are being backed up." & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "backup-error" Then + subject = subjtmpl & " (read errors)" + body = "An error occurred during the backup on "&hostname&"." _ + & vbCrLf & vbCrLf & _ + "==========================" & vbCrLf & _ + "FILES MAY NOT BE BACKED UP" & vbCrLf & _ + "==========================" & vbCrLf & _ + vbCrLf & _ + "Check the logs on "&hostname&" for more " & _ + "information about the error, " & vbCrLf & _ + "and take appropriate action." & vbCrLf SendMail from,sendto,subject,body -ElseIf args(0) = "backup-start" Or args(0) = "backup-finish" Then +ElseIf args(0) = "backup-start" Or args(0) = "backup-finish" _ + Or args(0) = "backup-ok" Then ' do nothing for these messages by default Else subject = subjtmpl & " (unknown)" body = "The backup daemon on "&hostname&" reported an unknown error." _ - & vbCrLf & vbCrLf & _ - "==========================" & vbCrLf & _ - "FILES MAY NOT BE BACKED UP" & vbCrLf & _ - "==========================" & vbCrLf & vbCrLf & _ - "Please check the logs on "&hostname&"." & vbCrLf + & vbCrLf & vbCrLf & _ + "==========================" & vbCrLf & _ + "FILES MAY NOT BE BACKED UP" & vbCrLf & _ + "==========================" & vbCrLf & vbCrLf & _ + "Please check the logs on "&hostname&"." & vbCrLf SendMail from,sendto,subject,body End If diff --git a/bin/bbackupd/win32/bbackupd.conf b/bin/bbackupd/win32/bbackupd.conf index 6c987f7d..b0793b29 100644 --- a/bin/bbackupd/win32/bbackupd.conf +++ b/bin/bbackupd/win32/bbackupd.conf @@ -173,7 +173,7 @@ Server # If a directive ends in Regex, then it is a regular expression rather than a # explicit full pathname. See: # -# http://bbdev.fluffy.co.uk/trac/wiki/Win32Regex +# http://www.boxbackup.org/trac/wiki/Win32Regex # # for more information about regular expressions on Windows. # diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp index b6984641..687dcb05 100644 --- a/bin/bbackupquery/BackupQueries.cpp +++ b/bin/bbackupquery/BackupQueries.cpp @@ -13,7 +13,6 @@ #include <unistd.h> #endif -#include <string.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> @@ -25,29 +24,33 @@ #include <dirent.h> #endif -#include <set> +#include <cstring> #include <limits> +#include <iostream> +#include <ostream> +#include <set> +#include "BackupClientFileAttributes.h" +#include "BackupClientMakeExcludeList.h" +#include "BackupClientRestore.h" #include "BackupQueries.h" -#include "Utils.h" -#include "Configuration.h" -#include "autogen_BackupProtocolClient.h" -#include "BackupStoreFilenameClear.h" #include "BackupStoreDirectory.h" -#include "IOStream.h" -#include "BoxTimeToText.h" -#include "FileStream.h" +#include "BackupStoreException.h" #include "BackupStoreFile.h" -#include "TemporaryDirectory.h" -#include "FileModificationTime.h" -#include "BackupClientFileAttributes.h" +#include "BackupStoreFilenameClear.h" +#include "BoxTimeToText.h" #include "CommonException.h" -#include "BackupClientRestore.h" -#include "BackupStoreException.h" +#include "Configuration.h" #include "ExcludeList.h" -#include "BackupClientMakeExcludeList.h" -#include "PathUtils.h" +#include "FileModificationTime.h" +#include "FileStream.h" +#include "IOStream.h" #include "Logging.h" +#include "PathUtils.h" +#include "SelfFlushingStream.h" +#include "TemporaryDirectory.h" +#include "Utils.h" +#include "autogen_BackupProtocolClient.h" #include "MemLeakFindOn.h" @@ -68,8 +71,10 @@ // Created: 2003/10/10 // // -------------------------------------------------------------------------- -BackupQueries::BackupQueries(BackupProtocolClient &rConnection, const Configuration &rConfiguration) - : mrConnection(rConnection), +BackupQueries::BackupQueries(BackupProtocolClient &rConnection, + const Configuration &rConfiguration, bool readWrite) + : mReadWrite(readWrite), + mrConnection(rConnection), mrConfiguration(rConfiguration), mQuitNow(false), mRunningAsRoot(false), @@ -115,7 +120,13 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0') { // Yes, run shell command - ::system(Command + 3); + int result = ::system(Command + 3); + if(result != 0) + { + BOX_WARNING("System command returned error code " << + result); + SetReturnCode(ReturnCode::Command_Error); + } return; } @@ -209,28 +220,36 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) { "getobject", "" }, { "get", "i" }, { "compare", "alcqAEQ" }, - { "restore", "dri" }, + { "restore", "drif" }, { "help", "" }, - { "usage", "" }, + { "usage", "m" }, { "undelete", "" }, + { "delete", "" }, { NULL, NULL } }; - #define COMMAND_Quit 0 - #define COMMAND_Exit 1 - #define COMMAND_List 2 - #define COMMAND_pwd 3 - #define COMMAND_cd 4 - #define COMMAND_lcd 5 - #define COMMAND_sh 6 - #define COMMAND_GetObject 7 - #define COMMAND_Get 8 - #define COMMAND_Compare 9 - #define COMMAND_Restore 10 - #define COMMAND_Help 11 - #define COMMAND_Usage 12 - #define COMMAND_Undelete 13 - static const char *alias[] = {"ls", 0}; - static const int aliasIs[] = {COMMAND_List, 0}; + + typedef enum + { + Command_Quit = 0, + Command_Exit, + Command_List, + Command_pwd, + Command_cd, + Command_lcd, + Command_sh, + Command_GetObject, + Command_Get, + Command_Compare, + Command_Restore, + Command_Help, + Command_Usage, + Command_Undelete, + Command_Delete, + } + CommandType; + + static const char *alias[] = {"ls", 0}; + static const int aliasIs[] = {Command_List, 0}; // Work out which command it is... int cmd = 0; @@ -284,25 +303,25 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) } } - if(cmd != COMMAND_Quit && cmd != COMMAND_Exit) + if(cmd != Command_Quit && cmd != Command_Exit) { // If not a quit command, set the return code to zero - SetReturnCode(0); + SetReturnCode(ReturnCode::Command_OK); } // Handle command switch(cmd) { - case COMMAND_Quit: - case COMMAND_Exit: + case Command_Quit: + case Command_Exit: mQuitNow = true; break; - case COMMAND_List: + case Command_List: CommandList(args, opts); break; - case COMMAND_pwd: + case Command_pwd: { // Simple implementation, so do it here BOX_INFO(GetCurrentDirectoryName() << " (" << @@ -310,47 +329,52 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) } break; - case COMMAND_cd: + case Command_cd: CommandChangeDir(args, opts); break; - case COMMAND_lcd: + case Command_lcd: CommandChangeLocalDir(args); break; - case COMMAND_sh: + case Command_sh: BOX_ERROR("The command to run must be specified as an argument."); break; - case COMMAND_GetObject: + case Command_GetObject: CommandGetObject(args, opts); break; - case COMMAND_Get: + case Command_Get: CommandGet(args, opts); break; - case COMMAND_Compare: + case Command_Compare: CommandCompare(args, opts); break; - case COMMAND_Restore: + case Command_Restore: CommandRestore(args, opts); break; - case COMMAND_Usage: - CommandUsage(); + case Command_Usage: + CommandUsage(opts); break; - case COMMAND_Help: + case Command_Help: CommandHelp(args); break; - case COMMAND_Undelete: + case Command_Undelete: CommandUndelete(args, opts); break; + case Command_Delete: + CommandDelete(args, opts); + break; + default: + BOX_ERROR("Unknown command: " << Command); break; } } @@ -402,6 +426,7 @@ void BackupQueries::CommandList(const std::vector<std::string> &args, const bool { BOX_ERROR("Directory '" << args[0] << "' not found " "on store."); + SetReturnCode(ReturnCode::Command_Error); return; } } @@ -427,11 +452,28 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted; // Do communication - mrConnection.QueryListDirectory( - DirID, - BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories - excludeFlags, - true /* want attributes */); + try + { + mrConnection.QueryListDirectory( + DirID, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + // both files and directories + excludeFlags, + true /* want attributes */); + } + catch (std::exception &e) + { + BOX_ERROR("Failed to list directory: " << e.what()); + SetReturnCode(ReturnCode::Command_Error); + return; + } + catch (...) + { + BOX_ERROR("Failed to list directory: unknown error"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + // Retrieve the directory from the stream following BackupStoreDirectory dir; @@ -574,15 +616,18 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool // -------------------------------------------------------------------------- // // Function -// Name: BackupQueries::FindDirectoryObjectID(const std::string &) -// Purpose: Find the object ID of a directory on the store, or return 0 for not found. -// If pStack != 0, the object is set to the stack of directories. -// Will start from the current directory stack. +// Name: BackupQueries::FindDirectoryObjectID(const +// std::string &) +// Purpose: Find the object ID of a directory on the store, +// or return 0 for not found. If pStack != 0, the +// object is set to the stack of directories. +// Will start from the current directory stack. // Created: 2003/10/10 // // -------------------------------------------------------------------------- -int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, bool AllowOldVersion, - bool AllowDeletedDirs, std::vector<std::pair<std::string, int64_t> > *pStack) +int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, + bool AllowOldVersion, bool AllowDeletedDirs, + std::vector<std::pair<std::string, int64_t> > *pStack) { // Split up string into elements std::vector<std::string> dirElements; @@ -747,6 +792,7 @@ void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const if(args.size() != 1 || args[0].size() == 0) { BOX_ERROR("Incorrect usage. cd [-o] [-d] <directory>"); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -764,6 +810,7 @@ void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const if(id == 0) { BOX_ERROR("Directory '" << args[0] << "' not found."); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -785,7 +832,7 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) if(args.size() != 1 || args[0].size() == 0) { BOX_ERROR("Incorrect usage. lcd <local-directory>"); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -795,7 +842,7 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) { BOX_ERROR("Failed to convert path from console encoding."); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } int result = ::chdir(dirName.c_str()); @@ -810,11 +857,11 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) } else { - BOX_ERROR("Error changing to directory '" << - args[0] << ": " << strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to change to directory " + "'" << args[0] << "'"); } - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -822,9 +869,8 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) char wd[PATH_MAX]; if(::getcwd(wd, PATH_MAX) == 0) { - BOX_ERROR("Error getting current directory: " << - strerror(errno)); - SetReturnCode(COMMAND_RETURN_ERROR); + BOX_LOG_SYS_ERROR("Error getting current directory"); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -832,7 +878,7 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) if(!ConvertUtf8ToConsole(wd, dirName)) { BOX_ERROR("Failed to convert new path from console encoding."); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } BOX_INFO("Local current directory is now '" << dirName << "'."); @@ -868,8 +914,8 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const } // Does file exist? - struct stat st; - if(::stat(args[1].c_str(), &st) == 0 || errno != ENOENT) + EMU_STRUCT_STAT st; + if(EMU_STAT(args[1].c_str(), &st) == 0 || errno != ENOENT) { BOX_ERROR("The local file '" << args[1] << " already exists."); return; @@ -907,6 +953,117 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const } +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::FindFileID(const std::string& +// rNameOrIdString, const bool *options, +// int64_t *pDirIdOut, std::string* pFileNameOut) +// Purpose: Locate a file on the store (either by name or by +// object ID, depending on opts['i'], where name can +// include a path) and return the file ID, placing the +// directory ID in *pDirIdOut and the filename part +// of the path (if not looking up by ID and not NULL) +// in *pFileNameOut. +// Created: 2008-09-12 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString, + const bool *opts, int64_t *pDirIdOut, std::string* pFileNameOut, + int16_t flagsInclude, int16_t flagsExclude, int16_t* pFlagsOut) +{ + // Find object ID somehow + int64_t fileId; + int64_t dirId = GetCurrentDirectoryID(); + std::string fileName = rNameOrIdString; + + if(!opts['i']) + { + // does this remote filename include a path? + std::string::size_type index = fileName.rfind('/'); + if(index != std::string::npos) + { + std::string dirName(fileName.substr(0, index)); + fileName = fileName.substr(index + 1); + + dirId = FindDirectoryObjectID(dirName); + if(dirId == 0) + { + BOX_ERROR("Directory '" << dirName << + "' not found."); + return 0; + } + } + + if(pFileNameOut) + { + *pFileNameOut = fileName; + } + } + + BackupStoreFilenameClear fn(fileName); + + // Need to look it up in the current directory + mrConnection.QueryListDirectory( + dirId, flagsInclude, flagsExclude, + true /* do want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + BackupStoreDirectory::Entry *en; + + if(opts['i']) + { + // Specified as ID. + fileId = ::strtoll(rNameOrIdString.c_str(), 0, 16); + if(fileId == std::numeric_limits<long long>::min() || + fileId == std::numeric_limits<long long>::max() || + fileId == 0) + { + BOX_ERROR("Not a valid object ID (specified in hex)."); + return 0; + } + + // Check that the item is actually in the directory + en = dir.FindEntryByID(fileId); + if(en == 0) + { + BOX_ERROR("File ID " << + BOX_FORMAT_OBJECTID(fileId) << + " not found in current directory on store.\n" + "(You can only access files by ID from the " + "current directory.)"); + return 0; + } + } + else + { + // Specified by name, find the object in the directory to get the ID + BackupStoreDirectory::Iterator i(dir); + en = i.FindMatchingClearName(fn); + if(en == 0) + { + BOX_ERROR("Filename '" << rNameOrIdString << "' " + "not found in current directory on store.\n" + "(Subdirectories in path not searched.)"); + return 0; + } + + fileId = en->GetObjectID(); + } + + *pDirIdOut = dirId; + + if(pFlagsOut) + { + *pFlagsOut = en->GetFlags(); + } + + return fileId; +} + // -------------------------------------------------------------------------- // @@ -929,120 +1086,72 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) } // Find object ID somehow - int64_t fileId; - int64_t dirId = GetCurrentDirectoryID(); + int64_t fileId, dirId; std::string localName; - // BLOCK - { #ifdef WIN32 - for (std::vector<std::string>::iterator - i = args.begin(); i != args.end(); i++) + for (std::vector<std::string>::iterator + i = args.begin(); i != args.end(); i++) + { + std::string out; + if(!ConvertConsoleToUtf8(i->c_str(), out)) { - std::string out; - if(!ConvertConsoleToUtf8(i->c_str(), out)) - { - BOX_ERROR("Failed to convert encoding."); - return; - } - *i = out; + BOX_ERROR("Failed to convert encoding."); + return; } + *i = out; + } #endif - std::string fileName(args[0]); + int16_t flagsExclude; - if(!opts['i']) - { - // does this remote filename include a path? - std::string::size_type index = fileName.rfind('/'); - if(index != std::string::npos) - { - std::string dirName(fileName.substr(0, index)); - fileName = fileName.substr(index + 1); - - dirId = FindDirectoryObjectID(dirName); - if(dirId == 0) - { - BOX_ERROR("Directory '" << dirName << - "' not found."); - return; - } - } - } + if(opts['i']) + { + // can retrieve anything by ID + flagsExclude = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING; + } + else + { + // only current versions by name + flagsExclude = + BackupProtocolClientListDirectory::Flags_OldVersion | + BackupProtocolClientListDirectory::Flags_Deleted; + } - BackupStoreFilenameClear fn(fileName); - // Need to look it up in the current directory - mrConnection.QueryListDirectory( - dirId, - BackupProtocolClientListDirectory::Flags_File, // just files - (opts['i'])?(BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING):(BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted), // only current versions - false /* don't want attributes */); + fileId = FindFileID(args[0], opts, &dirId, &localName, + BackupProtocolClientListDirectory::Flags_File, // just files + flagsExclude, NULL /* don't care about flags found */); - // Retrieve the directory from the stream following - BackupStoreDirectory dir; - std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream()); - dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + if (fileId == 0) + { + // error already reported + return; + } - if(opts['i']) + if(opts['i']) + { + // Specified as ID. Must have a local name in the arguments + // (check at beginning of function ensures this) + localName = args[1]; + } + else + { + // Specified by name. Local name already set by FindFileID, + // but may be overridden by user supplying a second argument. + if(args.size() == 2) { - // Specified as ID. - fileId = ::strtoll(args[0].c_str(), 0, 16); - if(fileId == std::numeric_limits<long long>::min() || - fileId == std::numeric_limits<long long>::max() || - fileId == 0) - { - BOX_ERROR("Not a valid object ID (specified in hex)."); - return; - } - - // Check that the item is actually in the directory - if(dir.FindEntryByID(fileId) == 0) - { - BOX_ERROR("File ID " << - BOX_FORMAT_OBJECTID(fileId) << - " not found in current " - "directory on store.\n" - "(You can only download files by ID " - "from the current directory.)"); - return; - } - - // Must have a local name in the arguments (check at beginning of function ensures this) localName = args[1]; } - else - { - // Specified by name, find the object in the directory to get the ID - BackupStoreDirectory::Iterator i(dir); - BackupStoreDirectory::Entry *en = i.FindMatchingClearName(fn); - - if(en == 0) - { - BOX_ERROR("Filename '" << args[0] << "' " - "not found in current " - "directory on store.\n" - "(Subdirectories in path not " - "searched.)"); - return; - } - - fileId = en->GetObjectID(); - - // Local name is the last argument, which is either - // the looked up filename, or a filename specified - // by the user. - localName = args[args.size() - 1]; - } } // Does local file already exist? (don't want to overwrite) - struct stat st; - if(::stat(localName.c_str(), &st) == 0 || errno != ENOENT) + EMU_STRUCT_STAT st; + if(EMU_STAT(localName.c_str(), &st) == 0 || errno != ENOENT) { BOX_ERROR("The local file " << localName << " already exists, " "will not overwrite it."); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -1081,7 +1190,6 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) } } - // -------------------------------------------------------------------------- // // Function @@ -1090,58 +1198,17 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) // Created: 29/1/04 // // -------------------------------------------------------------------------- -BackupQueries::CompareParams::CompareParams() - : mQuickCompare(false), - mIgnoreExcludes(false), - mIgnoreAttributes(false), - mDifferences(0), - mDifferencesExplainedByModTime(0), - mUncheckedFiles(0), - mExcludedDirs(0), - mExcludedFiles(0), - mpExcludeFiles(0), - mpExcludeDirs(0), - mLatestFileUploadTime(0) -{ -} - - -// -------------------------------------------------------------------------- -// -// Function -// Name: BackupQueries::CompareParams::~CompareParams() -// Purpose: Destructor -// Created: 29/1/04 -// -// -------------------------------------------------------------------------- -BackupQueries::CompareParams::~CompareParams() -{ - DeleteExcludeLists(); -} - - -// -------------------------------------------------------------------------- -// -// Function -// Name: BackupQueries::CompareParams::DeleteExcludeLists() -// Purpose: Delete the include lists contained -// Created: 29/1/04 -// -// -------------------------------------------------------------------------- -void BackupQueries::CompareParams::DeleteExcludeLists() -{ - if(mpExcludeFiles != 0) - { - delete mpExcludeFiles; - mpExcludeFiles = 0; - } - if(mpExcludeDirs != 0) - { - delete mpExcludeDirs; - mpExcludeDirs = 0; - } -} - +BackupQueries::CompareParams::CompareParams(bool QuickCompare, + bool IgnoreExcludes, bool IgnoreAttributes, + box_time_t LatestFileUploadTime) +: BoxBackupCompareParams(QuickCompare, IgnoreExcludes, IgnoreAttributes, + LatestFileUploadTime), + mDifferences(0), + mDifferencesExplainedByModTime(0), + mUncheckedFiles(0), + mExcludedDirs(0), + mExcludedFiles(0) +{ } // -------------------------------------------------------------------------- // @@ -1153,24 +1220,19 @@ void BackupQueries::CompareParams::DeleteExcludeLists() // -------------------------------------------------------------------------- void BackupQueries::CommandCompare(const std::vector<std::string> &args, const bool *opts) { - // Parameters, including count of differences - BackupQueries::CompareParams params; - params.mQuickCompare = opts['q']; - params.mQuietCompare = opts['Q']; - params.mIgnoreExcludes = opts['E']; - params.mIgnoreAttributes = opts['A']; + box_time_t LatestFileUploadTime = GetCurrentBoxTime(); // Try and work out the time before which all files should be on the server { std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); syncTimeFilename += "last_sync_start"; // Stat it to get file time - struct stat st; - if(::stat(syncTimeFilename.c_str(), &st) == 0) + EMU_STRUCT_STAT st; + if(EMU_STAT(syncTimeFilename.c_str(), &st) == 0) { // Files modified after this time shouldn't be on the server, so report errors slightly differently - params.mLatestFileUploadTime = FileModificationTime(st) - - SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge")); + LatestFileUploadTime = FileModificationTime(st) - + SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge")); } else { @@ -1179,8 +1241,16 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b } } + // Parameters, including count of differences + BackupQueries::CompareParams params(opts['q'], // quick compare? + opts['E'], // ignore excludes + opts['A'], // ignore attributes + LatestFileUploadTime); + + params.mQuietCompare = opts['Q']; + // Quick compare? - if(params.mQuickCompare) + if(params.QuickCompare()) { BOX_WARNING("Quick compare used -- file attributes are not " "checked."); @@ -1189,11 +1259,16 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b if(!opts['l'] && opts['a'] && args.size() == 0) { // Compare all locations - const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations")); - for(std::list<std::pair<std::string, Configuration> >::const_iterator i = locations.mSubConfigurations.begin(); - i != locations.mSubConfigurations.end(); ++i) + const Configuration &rLocations( + mrConfiguration.GetSubConfiguration("BackupLocations")); + std::vector<std::string> locNames = + rLocations.GetSubConfigurationNames(); + for(std::vector<std::string>::iterator + pLocName = locNames.begin(); + pLocName != locNames.end(); + pLocName++) { - CompareLocation(i->first, params); + CompareLocation(*pLocName, params); } } else if(opts['l'] && !opts['a'] && args.size() == 1) @@ -1206,7 +1281,7 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b // Compare directory to directory // Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list - if(!params.mIgnoreExcludes) + if(!params.IgnoreExcludes()) { BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes."); return; @@ -1241,15 +1316,15 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b { if (params.mUncheckedFiles != 0) { - SetReturnCode(COMPARE_RETURN_ERROR); + SetReturnCode(ReturnCode::Compare_Error); } else if (params.mDifferences != 0) { - SetReturnCode(COMPARE_RETURN_DIFFERENT); + SetReturnCode(ReturnCode::Compare_Different); } else { - SetReturnCode(COMPARE_RETURN_SAME); + SetReturnCode(ReturnCode::Compare_Same); } } } @@ -1263,7 +1338,8 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b // Created: 2003/10/13 // // -------------------------------------------------------------------------- -void BackupQueries::CompareLocation(const std::string &rLocation, BackupQueries::CompareParams &rParams) +void BackupQueries::CompareLocation(const std::string &rLocation, + BoxBackupCompareParams &rParams) { // Find the location's sub configuration const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations")); @@ -1287,45 +1363,36 @@ void BackupQueries::CompareLocation(const std::string &rLocation, BackupQueries: } #endif - try - { - // Generate the exclude lists - if(!rParams.mIgnoreExcludes) - { - rParams.mpExcludeFiles = BackupClientMakeExcludeList_Files(loc); - rParams.mpExcludeDirs = BackupClientMakeExcludeList_Dirs(loc); - } - - // Then get it compared - Compare(std::string("/") + rLocation, - loc.GetKeyValue("Path"), rParams); - } - catch(...) + // Generate the exclude lists + if(!rParams.IgnoreExcludes()) { - // Clean up - rParams.DeleteExcludeLists(); - throw; + rParams.LoadExcludeLists(loc); } - - // Delete exclude lists - rParams.DeleteExcludeLists(); + + // Then get it compared + Compare(std::string("/") + rLocation, loc.GetKeyValue("Path"), rParams); } // -------------------------------------------------------------------------- // // Function -// Name: BackupQueries::Compare(const std::string &, const std::string &, BackupQueries::CompareParams &) +// Name: BackupQueries::Compare(const std::string &, +// const std::string &, BackupQueries::CompareParams &) // Purpose: Compare a store directory against a local directory // Created: 2003/10/13 // // -------------------------------------------------------------------------- -void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLocalDir, BackupQueries::CompareParams &rParams) +void BackupQueries::Compare(const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams) { #ifdef WIN32 + std::string localDirEncoded; std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return; if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return; #else + const std::string& localDirEncoded(rLocalDir); const std::string& storeDirEncoded(rStoreDir); #endif @@ -1335,19 +1402,22 @@ void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLo // Found? if(dirID == 0) { - BOX_WARNING("Local directory '" << rLocalDir << "' exists, " - "but server directory '" << rStoreDir << "' does not " - "exist."); - rParams.mDifferences ++; + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(rLocalDir.c_str(), &st) == 0) + { + if(FileAttrModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyRemoteFileMissing(localDirEncoded, + storeDirEncoded, modifiedAfterLastSync); return; } - -#ifdef WIN32 - std::string localDirEncoded; - if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return; -#else - std::string localDirEncoded(rLocalDir); -#endif // Go! Compare(dirID, storeDirEncoded, localDirEncoded, rParams); @@ -1363,56 +1433,36 @@ void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLo // Created: 2003/10/13 // // -------------------------------------------------------------------------- -void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const std::string &rLocalDir, BackupQueries::CompareParams &rParams) +void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams) { -#ifdef WIN32 - // By this point, rStoreDir and rLocalDir should be in UTF-8 encoding - - std::string localDirDisplay; - std::string storeDirDisplay; - - if(!ConvertUtf8ToConsole(rLocalDir.c_str(), localDirDisplay)) return; - if(!ConvertUtf8ToConsole(rStoreDir.c_str(), storeDirDisplay)) return; -#else - const std::string& localDirDisplay(rLocalDir); - const std::string& storeDirDisplay(rStoreDir); -#endif + rParams.NotifyDirComparing(rLocalDir, rStoreDir); // Get info on the local directory - struct stat st; - if(::lstat(rLocalDir.c_str(), &st) != 0) + EMU_STRUCT_STAT st; + if(EMU_LSTAT(rLocalDir.c_str(), &st) != 0) { // What kind of error? - if(errno == ENOTDIR) - { - BOX_WARNING("Local object '" << localDirDisplay << "' " - "is a file, server object '" << - storeDirDisplay << "' is a directory."); - rParams.mDifferences ++; - } - else if(errno == ENOENT) + if(errno == ENOTDIR || errno == ENOENT) { - BOX_WARNING("Local directory '" << localDirDisplay << - "' does not exist (compared to server " - "directory '" << storeDirDisplay << "')."); - rParams.mDifferences ++; + rParams.NotifyLocalDirMissing(rLocalDir, rStoreDir); } else { - BOX_WARNING("Failed to access local directory '" << - localDirDisplay << ": " << strerror(errno) << - "'."); - rParams.mUncheckedFiles ++; + rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); } return; } // Get the directory listing from the store mrConnection.QueryListDirectory( - DirID, - BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // get everything - BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted, // except for old versions and deleted files - true /* want attributes */); + DirID, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + // get everything + BackupProtocolClientListDirectory::Flags_OldVersion | + BackupProtocolClientListDirectory::Flags_Deleted, + // except for old versions and deleted files + true /* want attributes */); // Retrieve the directory from the stream following BackupStoreDirectory dir; @@ -1422,8 +1472,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // Test out the attributes if(!dir.HasAttributes()) { - BOX_WARNING("Store directory '" << storeDirDisplay << "' " - "doesn't have attributes."); + rParams.NotifyStoreDirMissingAttributes(rLocalDir, rStoreDir); } else { @@ -1436,12 +1485,27 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s localAttr.ReadAttributes(rLocalDir.c_str(), true /* directories have zero mod times */); - if(!(attr.Compare(localAttr, true, true /* ignore modification times */))) + if(attr.Compare(localAttr, true, true /* ignore modification times */)) { - BOX_WARNING("Local directory '" << localDirDisplay << - "' has different attributes to store " - "directory '" << storeDirDisplay << "'."); - rParams.mDifferences ++; + rParams.NotifyDirCompared(rLocalDir, rStoreDir, + false, false /* actually we didn't check :) */); + } + else + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(rLocalDir.c_str(), &st) == 0) + { + if(FileAttrModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyDirCompared(rLocalDir, rStoreDir, + true, modifiedAfterLastSync); } } @@ -1449,11 +1513,10 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s DIR *dirhandle = ::opendir(rLocalDir.c_str()); if(dirhandle == 0) { - BOX_WARNING("Failed to open local directory '" << - localDirDisplay << "': " << strerror(errno)); - rParams.mUncheckedFiles ++; + rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); return; } + try { // Read the files and directories into sets @@ -1484,8 +1547,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s #ifndef HAVE_VALID_DIRENT_D_TYPE std::string fn(MakeFullPath (rLocalDir, localDirEn->d_name)); - struct stat st; - if(::lstat(fn.c_str(), &st) != 0) + EMU_STRUCT_STAT st; + if(EMU_LSTAT(fn.c_str(), &st) != 0) { THROW_EXCEPTION(CommonException, OSFileError) } @@ -1518,8 +1581,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // Close directory if(::closedir(dirhandle) != 0) { - BOX_ERROR("Failed to close local directory '" << - localDirDisplay << "': " << strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to close local directory " + "'" << rLocalDir << "'"); } dirhandle = 0; @@ -1557,36 +1620,30 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i) { const std::string& fileName(i->first); -#ifdef WIN32 - // File name is also in UTF-8 encoding, - // need to convert to console - std::string fileNameDisplay; - if(!ConvertUtf8ToConsole(i->first.c_str(), - fileNameDisplay)) return; -#else - const std::string& fileNameDisplay(i->first); -#endif - std::string localPath(MakeFullPath - (rLocalDir, fileName)); - std::string localPathDisplay(MakeFullPath - (localDirDisplay, fileNameDisplay)); - std::string storePathDisplay - (storeDirDisplay + "/" + fileNameDisplay); + std::string localPath(MakeFullPath(rLocalDir, fileName)); + std::string storePath(rStoreDir + "/" + fileName); + rParams.NotifyFileComparing(localPath, storePath); + // Does the file exist locally? string_set_iter_t local(localFiles.find(fileName)); if(local == localFiles.end()) { // Not found -- report - BOX_WARNING("Local file '" << - localPathDisplay << "' does not exist, " - "but store file '" << - storePathDisplay << "' does."); - rParams.mDifferences ++; + rParams.NotifyLocalFileMissing(localPath, + storePath); } else - { + { + int64_t fileSize = 0; + + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0) + { + fileSize = st.st_size; + } + try { // Files the same flag? @@ -1594,8 +1651,10 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // File modified after last sync flag bool modifiedAfterLastSync = false; + + bool hasDifferentAttribs = false; - if(rParams.mQuickCompare) + if(rParams.QuickCompare()) { // Compare file -- fetch it mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID()); @@ -1640,7 +1699,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s BackupClientFileAttributes localAttr; box_time_t fileModTime = 0; localAttr.ReadAttributes(localPath.c_str(), false /* don't zero mod times */, &fileModTime); - modifiedAfterLastSync = (fileModTime > rParams.mLatestFileUploadTime); + modifiedAfterLastSync = (fileModTime > rParams.LatestFileUploadTime()); bool ignoreAttrModTime = true; #ifdef WIN32 @@ -1649,7 +1708,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s ignoreAttrModTime = false; #endif - if(!rParams.mIgnoreAttributes && + if(!rParams.IgnoreAttributes() && #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE !fileOnServerStream->IsSymLink() && #endif @@ -1657,128 +1716,43 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s ignoreAttrModTime, fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */)) { - BOX_WARNING("Local file '" << - localPathDisplay << - "' has different attributes " - "to store file '" << - storePathDisplay << - "'."); - rParams.mDifferences ++; - if(modifiedAfterLastSync) - { - rParams.mDifferencesExplainedByModTime ++; - BOX_INFO("(the file above was modified after the last sync time -- might be reason for difference)"); - } - else if(i->second->HasAttributes()) - { - BOX_INFO("(the file above has had new attributes applied)\n"); - } + hasDifferentAttribs = true; } // Compare contents, if it's a regular file not a link // Remember, we MUST read the entire stream from the server. + SelfFlushingStream flushObject(*objectStream); + if(!fileOnServerStream->IsSymLink()) { + SelfFlushingStream flushFile(*fileOnServerStream); // Open the local file FileStream l(localPath.c_str()); - - // Size - IOStream::pos_type fileSizeLocal = l.BytesLeftToRead(); - IOStream::pos_type fileSizeServer = 0; - - // Test the contents - char buf1[2048]; - char buf2[2048]; - while(fileOnServerStream->StreamDataLeft() && l.StreamDataLeft()) - { - int size = fileOnServerStream->Read(buf1, sizeof(buf1), mrConnection.GetTimeout()); - fileSizeServer += size; - - if(l.Read(buf2, size) != size - || ::memcmp(buf1, buf2, size) != 0) - { - equal = false; - break; - } - } - - // Check read all the data from the server and file -- can't be equal if local and remote aren't the same length - // Can't use StreamDataLeft() test on file, because if it's the same size, it won't know - // it's EOF yet. - if(fileOnServerStream->StreamDataLeft() || fileSizeServer != fileSizeLocal) - { - equal = false; - } - - // Must always read the entire decoded stream, if it's not a symlink - if(fileOnServerStream->StreamDataLeft()) - { - // Absorb all the data remaining - char buffer[2048]; - while(fileOnServerStream->StreamDataLeft()) - { - fileOnServerStream->Read(buffer, sizeof(buffer), mrConnection.GetTimeout()); - } - } - - // Must always read the entire encoded stream - if(objectStream->StreamDataLeft()) - { - // Absorb all the data remaining - char buffer[2048]; - while(objectStream->StreamDataLeft()) - { - objectStream->Read(buffer, sizeof(buffer), mrConnection.GetTimeout()); - } - } + equal = l.CompareWith(*fileOnServerStream, + mrConnection.GetTimeout()); } } - // Report if not equal. - if(!equal) - { - BOX_WARNING("Local file '" << - localPathDisplay << "' " - "has different contents " - "to store file '" << - storePathDisplay << - "'."); - rParams.mDifferences ++; - if(modifiedAfterLastSync) - { - rParams.mDifferencesExplainedByModTime ++; - BOX_INFO("(the file above was modified after the last sync time -- might be reason for difference)"); - } - else if(i->second->HasAttributes()) - { - BOX_INFO("(the file above has had new attributes applied)\n"); - } - } + rParams.NotifyFileCompared(localPath, + storePath, fileSize, + hasDifferentAttribs, !equal, + modifiedAfterLastSync, + i->second->HasAttributes()); } catch(BoxException &e) { - BOX_ERROR("Failed to fetch and compare " - "'" << - storePathDisplay.c_str() << - "': error " << e.what() << - " (" << e.GetType() << - "/" << e.GetSubType() << ")"); - rParams.mUncheckedFiles ++; + rParams.NotifyDownloadFailed(localPath, + storePath, fileSize, e); } catch(std::exception &e) { - BOX_ERROR("Failed to fetch and compare " - "'" << - storePathDisplay.c_str() << - "': " << e.what()); + rParams.NotifyDownloadFailed(localPath, + storePath, fileSize, e); } catch(...) { - BOX_ERROR("Failed to fetch and compare " - "'" << - storePathDisplay.c_str() << - "': unknown error"); - rParams.mUncheckedFiles ++; + rParams.NotifyDownloadFailed(localPath, + storePath, fileSize); } // Remove from set so that we know it's been compared @@ -1786,53 +1760,34 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s } } - // Report any files which exist on the locally, but not on the store + // Report any files which exist locally, but not on the store for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i) { -#ifdef WIN32 - // File name is also in UTF-8 encoding, - // need to convert to console - std::string fileNameDisplay; - if(!ConvertUtf8ToConsole(i->c_str(), fileNameDisplay)) - return; -#else - const std::string& fileNameDisplay(*i); -#endif - - std::string localPath(MakeFullPath - (rLocalDir, *i)); - std::string localPathDisplay(MakeFullPath - (localDirDisplay, fileNameDisplay)); - std::string storePathDisplay - (storeDirDisplay + "/" + fileNameDisplay); + std::string localPath(MakeFullPath(rLocalDir, *i)); + std::string storePath(rStoreDir + "/" + *i); // Should this be ignored (ie is excluded)? - if(rParams.mpExcludeFiles == 0 || - !(rParams.mpExcludeFiles->IsExcluded(localPath))) + if(!rParams.IsExcludedFile(localPath)) { - BOX_WARNING("Local file '" << - localPathDisplay << - "' exists, but store file '" << - storePathDisplay << - "' does not."); - rParams.mDifferences ++; + bool modifiedAfterLastSync = false; - // Check the file modification time + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0) { - struct stat st; - if(::stat(localPath.c_str(), &st) == 0) + if(FileModificationTime(st) > + rParams.LatestFileUploadTime()) { - if(FileModificationTime(st) > rParams.mLatestFileUploadTime) - { - rParams.mDifferencesExplainedByModTime ++; - BOX_INFO("(the file above was modified after the last sync time -- might be reason for difference)"); - } + modifiedAfterLastSync = true; } } + + rParams.NotifyRemoteFileMissing(localPath, + storePath, modifiedAfterLastSync); } else { - rParams.mExcludedFiles ++; + rParams.NotifyExcludedFile(localPath, + storePath); } } @@ -1843,99 +1798,69 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // Now do the directories, recursively to check subdirectories for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i) { -#ifdef WIN32 - // Directory name is also in UTF-8 encoding, - // need to convert to console - std::string subdirNameDisplay; - if(!ConvertUtf8ToConsole(i->first.c_str(), - subdirNameDisplay)) - return; -#else - const std::string& subdirNameDisplay(i->first); -#endif - - std::string localPath(MakeFullPath - (rLocalDir, i->first)); - std::string localPathDisplay(MakeFullPath - (localDirDisplay, subdirNameDisplay)); - std::string storePathDisplay - (storeDirDisplay + "/" + subdirNameDisplay); + std::string localPath(MakeFullPath(rLocalDir, i->first)); + std::string storePath(rLocalDir + "/" + i->first); // Does the directory exist locally? string_set_iter_t local(localDirs.find(i->first)); if(local == localDirs.end() && - rParams.mpExcludeDirs != NULL && - rParams.mpExcludeDirs->IsExcluded(localPath)) + rParams.IsExcludedDir(localPath)) { - // Not found -- report - BOX_WARNING("Local directory '" << - localPathDisplay << "' is excluded, " - "but store directory '" << - storePathDisplay << "' still exists."); - rParams.mDifferences ++; + rParams.NotifyExcludedFileNotDeleted(localPath, + storePath); } else if(local == localDirs.end()) { // Not found -- report - BOX_WARNING("Local directory '" << - localPathDisplay << "' does not exist, " - "but store directory '" << - storePathDisplay << "' does."); - rParams.mDifferences ++; + rParams.NotifyRemoteFileMissing(localPath, + storePath, false); } - else if(rParams.mpExcludeDirs != NULL && - rParams.mpExcludeDirs->IsExcluded(localPath)) + else if(rParams.IsExcludedDir(localPath)) { // don't recurse into excluded directories } else { // Compare directory - Compare(i->second->GetObjectID(), rStoreDir + "/" + i->first, localPath, rParams); + Compare(i->second->GetObjectID(), + rStoreDir + "/" + i->first, + localPath, rParams); // Remove from set so that we know it's been compared localDirs.erase(local); } } - // Report any files which exist on the locally, but not on the store - for(std::set<std::string>::const_iterator i = localDirs.begin(); i != localDirs.end(); ++i) + // Report any directories which exist locally, but not on the store + for(std::set<std::string>::const_iterator + i = localDirs.begin(); + i != localDirs.end(); ++i) { -#ifdef WIN32 - // File name is also in UTF-8 encoding, - // need to convert to console - std::string fileNameDisplay; - if(!ConvertUtf8ToConsole(i->c_str(), fileNameDisplay)) - return; -#else - const std::string& fileNameDisplay(*i); -#endif - - std::string localPath(MakeFullPath - (rLocalDir, *i)); - std::string localPathDisplay(MakeFullPath - (localDirDisplay, fileNameDisplay)); - - std::string storePath - (rStoreDir + "/" + *i); - std::string storePathDisplay - (storeDirDisplay + "/" + fileNameDisplay); + std::string localPath(MakeFullPath(rLocalDir, *i)); + std::string storePath(rStoreDir + "/" + *i); // Should this be ignored (ie is excluded)? - if(rParams.mpExcludeDirs == 0 || !(rParams.mpExcludeDirs->IsExcluded(localPath))) + if(!rParams.IsExcludedDir(localPath)) { - BOX_WARNING("Local directory '" << - localPathDisplay << "' exists, but " - "store directory '" << - storePathDisplay << "' does not."); - rParams.mDifferences ++; + bool modifiedAfterLastSync = false; + + // Check the dir modification time + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0 && + FileModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + + rParams.NotifyRemoteFileMissing(localPath, + storePath, modifiedAfterLastSync); } else { - rParams.mExcludedDirs ++; + rParams.NotifyExcludedDir(localPath, storePath); } } - } catch(...) { @@ -1943,6 +1868,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s { ::closedir(dirhandle); } + throw; } } @@ -1960,7 +1886,7 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b // Check arguments if(args.size() != 2) { - BOX_ERROR("Incorrect usage. restore [-d] [-r] [-i] <remote-name> <local-name>"); + BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> <local-name>"); return; } @@ -2023,18 +1949,19 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b localName.c_str(), true /* print progress dots */, restoreDeleted, false /* don't undelete after restore! */, - opts['r'] /* resume? */); + opts['r'] /* resume? */, + opts['f'] /* force continue after errors */); } catch(std::exception &e) { BOX_ERROR("Failed to restore: " << e.what()); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } catch(...) { BOX_ERROR("Failed to restore: unknown exception"); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); return; } @@ -2044,33 +1971,38 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b BOX_INFO("Restore complete."); break; + case Restore_CompleteWithErrors: + BOX_WARNING("Restore complete, but some files could not be " + "restored."); + break; + case Restore_ResumePossible: - BOX_ERROR("Resume possible -- repeat command with -r flag to resume"); - SetReturnCode(COMMAND_RETURN_ERROR); + BOX_ERROR("Resume possible -- repeat command with -r flag " + "to resume."); + SetReturnCode(ReturnCode::Command_Error); break; case Restore_TargetExists: - BOX_ERROR("The target directory exists. You cannot restore over an existing directory."); - SetReturnCode(COMMAND_RETURN_ERROR); + BOX_ERROR("The target directory exists. You cannot restore " + "over an existing directory."); + SetReturnCode(ReturnCode::Command_Error); break; - #ifdef WIN32 case Restore_TargetPathNotFound: BOX_ERROR("The target directory path does not exist.\n" "To restore to a directory whose parent " "does not exist, create the parent first."); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); break; - #endif case Restore_UnknownError: BOX_ERROR("Unknown error during restore."); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); break; default: BOX_ERROR("Unknown restore result " << result << "."); - SetReturnCode(COMMAND_RETURN_ERROR); + SetReturnCode(ReturnCode::Command_Error); break; } } @@ -2131,49 +2063,46 @@ void BackupQueries::CommandHelp(const std::vector<std::string> &args) // Created: 19/4/04 // // -------------------------------------------------------------------------- -void BackupQueries::CommandUsage() +void BackupQueries::CommandUsage(const bool *opts) { + bool MachineReadable = opts['m']; + // Request full details from the server std::auto_ptr<BackupProtocolClientAccountUsage> usage(mrConnection.QueryGetAccountUsage()); // Display each entry in turn int64_t hardLimit = usage->GetBlocksHardLimit(); int32_t blockSize = usage->GetBlockSize(); - CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, blockSize); - CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), hardLimit, blockSize); - CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), hardLimit, blockSize); - CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), hardLimit, blockSize); - CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), hardLimit, blockSize); - CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize); + CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, + blockSize, MachineReadable); + CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize, + MachineReadable); } // -------------------------------------------------------------------------- // // Function -// Name: BackupQueries::CommandUsageDisplayEntry(const char *, int64_t, int64_t, int32_t) +// Name: BackupQueries::CommandUsageDisplayEntry(const char *, +// int64_t, int64_t, int32_t, bool) // Purpose: Display an entry in the usage table // Created: 19/4/04 // // -------------------------------------------------------------------------- -void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, int64_t HardLimit, int32_t BlockSize) +void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, +int64_t HardLimit, int32_t BlockSize, bool MachineReadable) { - // Calculate size in Mb - double mb = (((double)Size) * ((double)BlockSize)) / ((double)(1024*1024)); - int64_t percent = (Size * 100) / HardLimit; - - // Bar graph - char bar[41]; - unsigned int b = (int)((Size * (sizeof(bar)-1)) / HardLimit); - if(b > sizeof(bar)-1) {b = sizeof(bar)-1;} - for(unsigned int l = 0; l < b; l++) - { - bar[l] = '*'; - } - bar[b] = '\0'; - - // Print the entryj - ::printf("%14s %10.1fMb %3d%% %s\n", Name, mb, (int32_t)percent, bar); + std::cout << FormatUsageLineStart(Name, MachineReadable) << + FormatUsageBar(Size, Size * BlockSize, HardLimit * BlockSize, + MachineReadable) << std::endl; } @@ -2187,10 +2116,17 @@ void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, int // -------------------------------------------------------------------------- void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const bool *opts) { + if (!mReadWrite) + { + BOX_ERROR("This command requires a read-write connection. " + "Please reconnect with the -w option."); + return; + } + // Check arguments if(args.size() != 1) { - BOX_ERROR("Incorrect usage. undelete <directory-name>"); + BOX_ERROR("Incorrect usage. undelete <name> or undelete -i <object-id>"); return; } @@ -2200,23 +2136,133 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const #else const std::string& storeDirEncoded(args[0]); #endif - - // Get directory ID - int64_t dirID = FindDirectoryObjectID(storeDirEncoded, - false /* no old versions */, true /* find deleted dirs */); - - // Allowable? - if(dirID == 0) + + // Find object ID somehow + int64_t fileId, parentId; + std::string fileName; + int16_t flagsOut; + + fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, + /* include files and directories */ + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + /* include old and deleted files */ + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + &flagsOut); + + if (fileId == 0) { - BOX_ERROR("Directory '" << args[0] << "' not found on server."); + // error already reported return; } - if(dirID == BackupProtocolClientListDirectory::RootDirectory) + + // Undelete it on the store + try + { + // Undelete object + if(flagsOut & BackupProtocolClientListDirectory::Flags_File) + { + mrConnection.QueryUndeleteFile(parentId, fileId); + } + else + { + mrConnection.QueryUndeleteDirectory(fileId); + } + } + catch (BoxException &e) + { + BOX_ERROR("Failed to undelete object: " << + e.what()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to undelete object: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Failed to undelete object: unknown error"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandDelete(const +// std::vector<std::string> &, const bool *) +// Purpose: Deletes a file +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandDelete(const std::vector<std::string> &args, + const bool *opts) +{ + if (!mReadWrite) + { + BOX_ERROR("This command requires a read-write connection. " + "Please reconnect with the -w option."); + return; + } + + // Check arguments + if(args.size() != 1) { - BOX_ERROR("Cannot undelete the root directory."); + BOX_ERROR("Incorrect usage. delete <name>"); return; } - // Undelete - mrConnection.QueryUndeleteDirectory(dirID); +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Find object ID somehow + int64_t fileId, parentId; + std::string fileName; + int16_t flagsOut; + + fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, + /* include files and directories */ + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + /* exclude old and deleted files */ + BackupProtocolClientListDirectory::Flags_OldVersion | + BackupProtocolClientListDirectory::Flags_Deleted, + &flagsOut); + + if (fileId == 0) + { + // error already reported + return; + } + + BackupStoreFilenameClear fn(fileName); + + // Delete it on the store + try + { + // Delete object + if(flagsOut & BackupProtocolClientListDirectory::Flags_File) + { + mrConnection.QueryDeleteFile(parentId, fn); + } + else + { + mrConnection.QueryDeleteDirectory(fileId); + } + } + catch (BoxException &e) + { + BOX_ERROR("Failed to delete object: " << + e.what()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to delete object: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Failed to delete object: unknown error"); + } } diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h index b2ef8cc2..392aa428 100644 --- a/bin/bbackupquery/BackupQueries.h +++ b/bin/bbackupquery/BackupQueries.h @@ -14,6 +14,7 @@ #include <string> #include "BoxTime.h" +#include "BoxBackupCompareParams.h" class BackupProtocolClient; class Configuration; @@ -30,7 +31,9 @@ class ExcludeList; class BackupQueries { public: - BackupQueries(BackupProtocolClient &rConnection, const Configuration &rConfiguration); + BackupQueries(BackupProtocolClient &rConnection, + const Configuration &rConfiguration, + bool readWrite); ~BackupQueries(); private: BackupQueries(const BackupQueries &); @@ -54,43 +57,282 @@ private: void CommandCompare(const std::vector<std::string> &args, const bool *opts); void CommandRestore(const std::vector<std::string> &args, const bool *opts); void CommandUndelete(const std::vector<std::string> &args, const bool *opts); - void CommandUsage(); - void CommandUsageDisplayEntry(const char *Name, int64_t Size, int64_t HardLimit, int32_t BlockSize); + void CommandDelete(const std::vector<std::string> &args, + const bool *opts); + void CommandUsage(const bool *opts); + void CommandUsageDisplayEntry(const char *Name, int64_t Size, + int64_t HardLimit, int32_t BlockSize, bool MachineReadable); void CommandHelp(const std::vector<std::string> &args); // Implementations - void List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel); - class CompareParams + void List(int64_t DirID, const std::string &rListRoot, const bool *opts, + bool FirstLevel); + +public: + class CompareParams : public BoxBackupCompareParams { public: - CompareParams(); - ~CompareParams(); - void DeleteExcludeLists(); - bool mQuickCompare; + CompareParams(bool QuickCompare, bool IgnoreExcludes, + bool IgnoreAttributes, box_time_t LatestFileUploadTime); + bool mQuietCompare; - bool mIgnoreExcludes; - bool mIgnoreAttributes; int mDifferences; int mDifferencesExplainedByModTime; int mUncheckedFiles; int mExcludedDirs; int mExcludedFiles; - const ExcludeList *mpExcludeFiles; - const ExcludeList *mpExcludeDirs; - box_time_t mLatestFileUploadTime; + + std::string ConvertForConsole(const std::string& rUtf8String) + { + #ifdef WIN32 + std::string output; + + if(!ConvertUtf8ToConsole(rUtf8String.c_str(), output)) + { + BOX_WARNING("Character set conversion failed " + "on string: " << rUtf8String); + return rUtf8String; + } + + return output; + #else + return rUtf8String; + #endif + } + + virtual void NotifyLocalDirMissing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Local directory '" << + ConvertForConsole(rLocalPath) << "' " + "does not exist, but remote directory does."); + mDifferences ++; + } + + virtual void NotifyLocalDirAccessFailed( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_LOG_SYS_WARNING("Failed to access local directory " + "'" << ConvertForConsole(rLocalPath) << "'"); + mUncheckedFiles ++; + } + + virtual void NotifyStoreDirMissingAttributes( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Store directory '" << + ConvertForConsole(rRemotePath) << "' " + "doesn't have attributes."); + } + + virtual void NotifyRemoteFileMissing( + const std::string& rLocalPath, + const std::string& rRemotePath, + bool modifiedAfterLastSync) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "exists, but remote file '" << + ConvertForConsole(rRemotePath) << "' " + "does not."); + mDifferences ++; + + if(modifiedAfterLastSync) + { + mDifferencesExplainedByModTime ++; + BOX_INFO("(the file above was modified after " + "the last sync time -- might be " + "reason for difference)"); + } + } + + virtual void NotifyLocalFileMissing( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Remote file '" << + ConvertForConsole(rRemotePath) << "' " + "exists, but local file '" << + ConvertForConsole(rLocalPath) << "' does not."); + mDifferences ++; + } + + virtual void NotifyExcludedFileNotDeleted( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "is excluded, but remote file '" << + ConvertForConsole(rRemotePath) << "' " + "still exists."); + mDifferences ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + BoxException& rException) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath) << "': " << + rException.what() << " (" << + rException.GetType() << "/" << + rException.GetSubType() << ")"); + mUncheckedFiles ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + std::exception& rException) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath) << "': " << + rException.what()); + mUncheckedFiles ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath)); + mUncheckedFiles ++; + } + + virtual void NotifyExcludedFile(const std::string& rLocalPath, + const std::string& rRemotePath) + { + mExcludedFiles ++; + } + + virtual void NotifyExcludedDir(const std::string& rLocalPath, + const std::string& rRemotePath) + { + mExcludedDirs ++; + } + + virtual void NotifyDirComparing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + } + + virtual void NotifyDirCompared( + const std::string& rLocalPath, + const std::string& rRemotePath, + bool HasDifferentAttributes, + bool modifiedAfterLastSync) + { + if(HasDifferentAttributes) + { + BOX_WARNING("Local directory '" << + ConvertForConsole(rLocalPath) << "' " + "has different attributes to " + "store directory '" << + ConvertForConsole(rRemotePath) << "'."); + mDifferences ++; + + if(modifiedAfterLastSync) + { + mDifferencesExplainedByModTime ++; + BOX_INFO("(the directory above was " + "modified after the last sync " + "time -- might be reason for " + "difference)"); + } + } + } + + virtual void NotifyFileComparing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + } + + virtual void NotifyFileCompared(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + bool HasDifferentAttributes, bool HasDifferentContents, + bool ModifiedAfterLastSync, bool NewAttributesApplied) + { + int NewDifferences = 0; + + if(HasDifferentAttributes) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "has different attributes to " + "store file '" << + ConvertForConsole(rRemotePath) << "'."); + NewDifferences ++; + } + + if(HasDifferentContents) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "has different contents to " + "store file '" << + ConvertForConsole(rRemotePath) << "'."); + NewDifferences ++; + } + + if(HasDifferentAttributes || HasDifferentContents) + { + if(ModifiedAfterLastSync) + { + mDifferencesExplainedByModTime += + NewDifferences; + BOX_INFO("(the file above was modified " + "after the last sync time -- " + "might be reason for difference)"); + } + else if(NewAttributesApplied) + { + BOX_INFO("(the file above has had new " + "attributes applied)\n"); + } + } + + mDifferences += NewDifferences; + } }; - void CompareLocation(const std::string &rLocation, CompareParams &rParams); - void Compare(const std::string &rStoreDir, const std::string &rLocalDir, CompareParams &rParams); - void Compare(int64_t DirID, const std::string &rStoreDir, const std::string &rLocalDir, CompareParams &rParams); + void CompareLocation(const std::string &rLocation, + BoxBackupCompareParams &rParams); + void Compare(const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams); + void Compare(int64_t DirID, const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams); + +public: + + class ReturnCode + { + public: + enum { + Command_OK = 0, + Compare_Same = 1, + Compare_Different, + Compare_Error, + Command_Error, + } Type; + }; + +private: // Utility functions - int64_t FindDirectoryObjectID(const std::string &rDirName, bool AllowOldVersion = false, - bool AllowDeletedDirs = false, std::vector<std::pair<std::string, int64_t> > *pStack = 0); + int64_t FindDirectoryObjectID(const std::string &rDirName, + bool AllowOldVersion = false, bool AllowDeletedDirs = false, + std::vector<std::pair<std::string, int64_t> > *pStack = 0); + int64_t FindFileID(const std::string& rNameOrIdString, + const bool *opts, int64_t *pDirIdOut, + std::string* pFileNameOut, int16_t flagsInclude, + int16_t flagsExclude, int16_t* pFlagsOut); int64_t GetCurrentDirectoryID(); std::string GetCurrentDirectoryName(); void SetReturnCode(int code) {mReturnCode = code;} private: + bool mReadWrite; BackupProtocolClient &mrConnection; const Configuration &mrConfiguration; bool mQuitNow; diff --git a/bin/bbackupquery/BoxBackupCompareParams.h b/bin/bbackupquery/BoxBackupCompareParams.h new file mode 100644 index 00000000..c58759a2 --- /dev/null +++ b/bin/bbackupquery/BoxBackupCompareParams.h @@ -0,0 +1,107 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxBackupCompareParams.h +// Purpose: Parameters and notifiers for a compare operation +// Created: 2008/12/30 +// +// -------------------------------------------------------------------------- + +#ifndef BOXBACKUPCOMPAREPARAMS__H +#define BOXBACKUPCOMPAREPARAMS__H + +#include <memory> +#include <string> + +#include "BoxTime.h" +#include "ExcludeList.h" +#include "BackupClientMakeExcludeList.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: BoxBackupCompareParams +// Purpose: Parameters and notifiers for a compare operation +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +class BoxBackupCompareParams +{ +private: + std::auto_ptr<const ExcludeList> mapExcludeFiles, mapExcludeDirs; + bool mQuickCompare; + bool mIgnoreExcludes; + bool mIgnoreAttributes; + box_time_t mLatestFileUploadTime; + +public: + BoxBackupCompareParams(bool QuickCompare, bool IgnoreExcludes, + bool IgnoreAttributes, box_time_t LatestFileUploadTime) + : mQuickCompare(QuickCompare), + mIgnoreExcludes(IgnoreExcludes), + mIgnoreAttributes(IgnoreAttributes), + mLatestFileUploadTime(LatestFileUploadTime) + { } + + virtual ~BoxBackupCompareParams() { } + + bool QuickCompare() { return mQuickCompare; } + bool IgnoreExcludes() { return mIgnoreExcludes; } + bool IgnoreAttributes() { return mIgnoreAttributes; } + box_time_t LatestFileUploadTime() { return mLatestFileUploadTime; } + + void LoadExcludeLists(const Configuration& rLoc) + { + mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rLoc)); + mapExcludeDirs.reset(BackupClientMakeExcludeList_Dirs(rLoc)); + } + bool IsExcludedFile(const std::string& rLocalPath) + { + if (!mapExcludeFiles.get()) return false; + return mapExcludeFiles->IsExcluded(rLocalPath); + } + bool IsExcludedDir(const std::string& rLocalPath) + { + if (!mapExcludeDirs.get()) return false; + return mapExcludeDirs->IsExcluded(rLocalPath); + } + + virtual void NotifyLocalDirMissing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyLocalDirAccessFailed(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyStoreDirMissingAttributes(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyRemoteFileMissing(const std::string& rLocalPath, + const std::string& rRemotePath, + bool modifiedAfterLastSync) = 0; + virtual void NotifyLocalFileMissing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyExcludedFileNotDeleted(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + BoxException& rException) = 0; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + std::exception& rException) = 0; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes) = 0; + virtual void NotifyExcludedFile(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyExcludedDir(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDirComparing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDirCompared(const std::string& rLocalPath, + const std::string& rRemotePath, bool HasDifferentAttributes, + bool modifiedAfterLastSync) = 0; + virtual void NotifyFileComparing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyFileCompared(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + bool HasDifferentAttributes, bool HasDifferentContents, + bool modifiedAfterLastSync, bool newAttributesApplied) = 0; +}; + +#endif // BOXBACKUPCOMPAREPARAMS__H diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp index c9d6b715..33860dcf 100644 --- a/bin/bbackupquery/bbackupquery.cpp +++ b/bin/bbackupquery/bbackupquery.cpp @@ -37,6 +37,8 @@ #endif #endif +#include <cstdlib> + #include "MainHelper.h" #include "BoxPortsAndFiles.h" #include "BackupDaemonConfigVerify.h" @@ -61,10 +63,13 @@ void PrintUsageAndExit() #ifdef WIN32 "[-u] " #endif - "\n\t[-c config_file] [-l log_file] [commands]\n" + "\n" + "\t[-c config_file] [-o log_file] [-O log_file_level]\n" + "\t[-l protocol_log_file] [commands]\n" + "\n" "As many commands as you require.\n" "If commands are multiple words, remember to enclose the command in quotes.\n" - "Remember to use quit command if you don't want to drop into interactive mode.\n"); + "Remember to use the quit command unless you want to end up in interactive mode.\n"); exit(1); } @@ -90,7 +95,7 @@ int main(int argc, const char *argv[]) #endif // Really don't want trace statements happening, even in debug mode - #ifndef NDEBUG + #ifndef BOX_RELEASE_BUILD BoxDebugTraceOn = false; #endif @@ -106,24 +111,26 @@ int main(int argc, const char *argv[]) #endif // Flags - bool quiet = false; bool readWrite = false; - Logging::SetProgramName("Box Backup (bbackupquery)"); + Logging::SetProgramName("bbackupquery"); - #ifdef NDEBUG + #ifdef BOX_RELEASE_BUILD int masterLevel = Log::NOTICE; // need an int to do math with #else int masterLevel = Log::INFO; // need an int to do math with #endif #ifdef WIN32 - const char* validOpts = "qvwuc:l:"; + const char* validOpts = "qvwuc:l:o:O:W:"; bool unicodeConsole = false; #else - const char* validOpts = "qvwc:l:"; + const char* validOpts = "qvwc:l:o:O:W:"; #endif + std::string fileLogFile; + Log::Level fileLogLevel = Log::INVALID; + // See if there's another entry on the command line int c; while((c = getopt(argc, (char * const *)argv, validOpts)) != -1) @@ -132,9 +139,6 @@ int main(int argc, const char *argv[]) { case 'q': { - // Quiet mode - quiet = true; - if(masterLevel == Log::NOTHING) { BOX_FATAL("Too many '-q': " @@ -159,6 +163,17 @@ int main(int argc, const char *argv[]) } break; + case 'W': + { + masterLevel = Logging::GetNamedLevel(optarg); + if (masterLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level"); + return 2; + } + } + break; + case 'w': // Read/write mode readWrite = true; @@ -174,8 +189,24 @@ int main(int argc, const char *argv[]) logFile = ::fopen(optarg, "w"); if(logFile == 0) { - BOX_ERROR("Failed to open log file '" << - optarg << "': " << strerror(errno)); + BOX_LOG_SYS_ERROR("Failed to open log file " + "'" << optarg << "'"); + } + break; + + case 'o': + fileLogFile = optarg; + fileLogLevel = Log::EVERYTHING; + break; + + case 'O': + { + fileLogLevel = Logging::GetNamedLevel(optarg); + if (fileLogLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level"); + return 2; + } } break; @@ -196,6 +227,19 @@ int main(int argc, const char *argv[]) Logging::SetGlobalLevel((Log::Level)masterLevel); + std::auto_ptr<FileLogger> fileLogger; + if (fileLogLevel != Log::INVALID) + { + fileLogger.reset(new FileLogger(fileLogFile, fileLogLevel)); + } + + bool quiet = false; + if (masterLevel < Log::NOTICE) + { + // Quiet mode + quiet = true; + } + // Print banner? if(!quiet) { @@ -260,7 +304,9 @@ int main(int argc, const char *argv[]) // 2. Connect to server if(!quiet) BOX_INFO("Connecting to store..."); SocketStreamTLS socket; - socket.Open(tlsContext, Socket::TypeINET, conf.GetKeyValue("StoreHostname").c_str(), BOX_PORT_BBSTORED); + socket.Open(tlsContext, Socket::TypeINET, + conf.GetKeyValue("StoreHostname").c_str(), + conf.GetKeyValueInt("StorePort")); // 3. Make a protocol, and handshake if(!quiet) BOX_INFO("Handshake with store..."); @@ -291,7 +337,7 @@ int main(int argc, const char *argv[]) if(!quiet) printf("Login complete.\n\nType \"help\" for a list of commands.\n\n"); // Set up a context for our work - BackupQueries context(connection, conf); + BackupQueries context(connection, conf, readWrite); // Start running commands... first from the command line { @@ -377,7 +423,8 @@ int main(int argc, const char *argv[]) #ifdef WIN32 // Clean up our sockets - WSACleanup(); + // FIXME we should do this, but I get an abort() when I try + // WSACleanup(); #endif MAINHELPER_END diff --git a/bin/bbackupquery/documentation.txt b/bin/bbackupquery/documentation.txt index 42217edc..d32bf200 100644 --- a/bin/bbackupquery/documentation.txt +++ b/bin/bbackupquery/documentation.txt @@ -116,7 +116,7 @@ compare <store-dir-name> <local-dir-name> This can be used for automated tests. < -> restore [-d] [-r] [-i] <directory-name> <local-directory-name> +> restore [-drif] <directory-name> <local-directory-name> Restores a directory to the local disc. The local directory specified must not exist (unless a previous restore is being restarted). @@ -126,6 +126,7 @@ compare <store-dir-name> <local-dir-name> -d -- restore a deleted directory or deleted files inside -r -- resume an interrupted restoration -i -- directory name is actually an ID + -f -- force restore to continue if errors are encountered If a restore operation is interrupted for any reason, it can be restarted using the -r switch. Restore progress information is saved in a file at @@ -141,10 +142,12 @@ compare <store-dir-name> <local-dir-name> stored format, which is encrypted and compressed. < -> usage +> usage [-m] Show space used on the server for this account. + -m -- display the output in machine-readable form + Used: Total amount of space used on the server. Old files: Space used by old files Deleted files: Space used by deleted files @@ -158,6 +161,21 @@ compare <store-dir-name> <local-dir-name> files is near zero. < +> undelete <directory-name> +undelete -i <object-id> + + Removes the deleted flag from the specified directory name (in the + current directory) or hex object ID. Be careful not to use this + command where a directory already exists with the same name which is + not marked as deleted. +< + +> delete <file-name> + + Sets the deleted flag on the specified file name (in the current + directory, or with a relative path). +< + > quit End session and exit. diff --git a/bin/bbstoreaccounts/bbstoreaccounts.cpp b/bin/bbstoreaccounts/bbstoreaccounts.cpp index 6f079d30..71114ef4 100644 --- a/bin/bbstoreaccounts/bbstoreaccounts.cpp +++ b/bin/bbstoreaccounts/bbstoreaccounts.cpp @@ -9,12 +9,17 @@ #include "Box.h" -#include <unistd.h> +#include <limits.h> #include <stdio.h> +#include <unistd.h> + #include <sys/types.h> -#include <limits.h> -#include <vector> + #include <algorithm> +#include <cstring> +#include <iostream> +#include <ostream> +#include <vector> #include "BoxPortsAndFiles.h" #include "BackupStoreConfigVerify.h" @@ -27,12 +32,15 @@ #include "NamedLock.h" #include "UnixUser.h" #include "BackupStoreCheck.h" +#include "Utils.h" #include "MemLeakFindOn.h" // max size of soft limit as percent of hard limit #define MAX_SOFT_LIMIT_SIZE 97 +bool sMachineReadableOutput = false; + void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit) { if(SoftLimit >= HardLimit) @@ -62,22 +70,11 @@ int BlockSizeOfDiscSet(int DiscSet) return controller.GetDiscSet(DiscSet).GetBlockSize(); } -const char *BlockSizeToString(int64_t Blocks, int DiscSet) +std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int DiscSet) { - // Not reentrant, nor can be used in the same function call twice, etc. - static char string[256]; - - // Work out size in Mb. - double mb = (Blocks * BlockSizeOfDiscSet(DiscSet)) / (1024.0*1024.0); - - // Format string -#ifdef WIN32 - sprintf(string, "%I64d (%.2fMb)", Blocks, mb); -#else - sprintf(string, "%lld (%.2fMb)", Blocks, mb); -#endif - - return string; + return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(DiscSet), + MaxBlocks * BlockSizeOfDiscSet(DiscSet), + sMachineReadableOutput); } int64_t SizeStringToBlocks(const char *string, int DiscSet) @@ -118,7 +115,7 @@ int64_t SizeStringToBlocks(const char *string, int DiscSet) default: BOX_FATAL(string << " has an invalid units specifier " - "(use B for blocks, M for Mb, G for Gb, eg 2Gb)"); + "(use B for blocks, M for MB, G for GB, eg 2GB)"); exit(1); break; } @@ -211,7 +208,9 @@ int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, c int AccountInfo(Configuration &rConfig, int32_t ID) { // Load in the account database - std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str())); + std::auto_ptr<BackupStoreAccountDatabase> db( + BackupStoreAccountDatabase::Read( + rConfig.GetKeyValue("AccountDatabase").c_str())); // Exists? if(!db->EntryExists(ID)) @@ -226,18 +225,34 @@ int AccountInfo(Configuration &rConfig, int32_t ID) std::string rootDir; int discSet; acc.GetAccountRoot(ID, rootDir, discSet); - std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, rootDir, discSet, true /* ReadOnly */)); + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, + rootDir, discSet, true /* ReadOnly */)); // Then print out lots of info - printf(" Account ID: %08x\n", ID); - printf(" Last object ID: %lld\n", info->GetLastObjectIDUsed()); - printf(" Blocks used: %s\n", BlockSizeToString(info->GetBlocksUsed(), discSet)); - printf(" Blocks used by old files: %s\n", BlockSizeToString(info->GetBlocksInOldFiles(), discSet)); - printf("Blocks used by deleted files: %s\n", BlockSizeToString(info->GetBlocksInDeletedFiles(), discSet)); - printf(" Blocks used by directories: %s\n", BlockSizeToString(info->GetBlocksInDirectories(), discSet)); - printf(" Block soft limit: %s\n", BlockSizeToString(info->GetBlocksSoftLimit(), discSet)); - printf(" Block hard limit: %s\n", BlockSizeToString(info->GetBlocksHardLimit(), discSet)); - printf(" Client store marker: %lld\n", info->GetClientStoreMarker()); + std::cout << FormatUsageLineStart("Account ID", sMachineReadableOutput) << + BOX_FORMAT_ACCOUNT(ID) << std::endl; + std::cout << FormatUsageLineStart("Last object ID", sMachineReadableOutput) << + BOX_FORMAT_OBJECTID(info->GetLastObjectIDUsed()) << std::endl; + std::cout << FormatUsageLineStart("Used", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksUsed(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Old files", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInOldFiles(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Deleted files", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInDeletedFiles(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInDirectories(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Soft limit", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksSoftLimit(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Hard limit", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksHardLimit(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Client store marker", sMachineReadableOutput) << + info->GetLastObjectIDUsed() << std::endl; return 0; } @@ -409,8 +424,32 @@ int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t void PrintUsageAndExit() { - printf("Usage: bbstoreaccounts [-c config_file] action account_id [args]\nAccount ID is integer specified in hex\n"); - exit(1); + printf( +"Usage: bbstoreaccounts [-c config_file] action account_id [args]\n" +"Account ID is integer specified in hex\n" +"\n" +"Commands (and arguments):\n" +" create <account> <discnum> <softlimit> <hardlimit>\n" +" Creates the specified account number (in hex with no 0x) on the\n" +" specified raidfile disc set number (see raidfile.conf for valid\n" +" set numbers) with the specified soft and hard limits (in blocks\n" +" if suffixed with B, MB with M, GB with G)\n" +" info [-m] <account>\n" +" Prints information about the specified account including number\n" +" of blocks used. The -m option enable machine-readable output.\n" +" setlimit <accounts> <softlimit> <hardlimit>\n" +" Changes the limits of the account as specified. Numbers are\n" +" interpreted as for the 'create' command (suffixed with B, M or G)\n" +" delete <account> [yes]\n" +" Deletes the specified account. Prompts for confirmation unless\n" +" the optional 'yes' parameter is provided.\n" +" check <account> [fix] [quiet]\n" +" Checks the specified account for errors. If the 'fix' option is\n" +" provided, any errors discovered that can be fixed automatically\n" +" will be fixed. If the 'quiet' option is provided, less output is\n" +" produced.\n" + ); + exit(2); } int main(int argc, const char *argv[]) @@ -420,6 +459,8 @@ int main(int argc, const char *argv[]) MAINHELPER_START + Logging::SetProgramName("bbstoreaccounts"); + // Filename for configuration file? std::string configFilename; @@ -431,7 +472,7 @@ int main(int argc, const char *argv[]) // See if there's another entry on the command line int c; - while((c = getopt(argc, (char * const *)argv, "c:")) != -1) + while((c = getopt(argc, (char * const *)argv, "c:m")) != -1) { switch(c) { @@ -440,6 +481,11 @@ int main(int argc, const char *argv[]) configFilename = optarg; break; + case 'm': + // enable machine readable output + sMachineReadableOutput = true; + break; + case '?': default: PrintUsageAndExit(); diff --git a/bin/bbstored/BBStoreDHousekeeping.cpp b/bin/bbstored/BBStoreDHousekeeping.cpp index 16a1432a..1c1767ca 100644 --- a/bin/bbstored/BBStoreDHousekeeping.cpp +++ b/bin/bbstored/BBStoreDHousekeeping.cpp @@ -46,6 +46,12 @@ void BackupStoreDaemon::HousekeepingProcess() { RunHousekeepingIfNeeded(); + // Stop early? + if(StopRun()) + { + break; + } + // Calculate how long should wait before doing the next // housekeeping run int64_t timeNow = GetCurrentBoxTime(); @@ -155,7 +161,11 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded() void BackupStoreDaemon::OnIdle() { - #ifdef WIN32 + if (!IsSingleProcess()) + { + return; + } + if (!mHousekeepingInited) { HousekeepingInit(); @@ -163,7 +173,6 @@ void BackupStoreDaemon::OnIdle() } RunHousekeepingIfNeeded(); - #endif } // -------------------------------------------------------------------------- @@ -193,7 +202,8 @@ bool BackupStoreDaemon::CheckForInterProcessMsg(int AccountNum, int MaximumWaitT std::string line; if(mInterProcessComms.GetLine(line, false /* no pre-processing */, MaximumWaitTime)) { - TRACE1("Housekeeping received command '%s' over interprocess comms\n", line.c_str()); + BOX_TRACE("Housekeeping received command '" << line << + "' over interprocess comms"); int account = 0; diff --git a/bin/bbstored/BackupCommands.cpp b/bin/bbstored/BackupCommands.cpp index bca52c04..38cda234 100644 --- a/bin/bbstored/BackupCommands.cpp +++ b/bin/bbstored/BackupCommands.cpp @@ -13,25 +13,25 @@ #include <sstream> #include "autogen_BackupProtocolServer.h" +#include "autogen_RaidFileException.h" #include "BackupConstants.h" -#include "BackupContext.h" -#include "CollectInBufferStream.h" +#include "BackupStoreContext.h" +#include "BackupStoreConstants.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreFile.h" -#include "StreamableMemBlock.h" -#include "BackupStoreConstants.h" -#include "RaidFileController.h" #include "BackupStoreInfo.h" -#include "RaidFileController.h" +#include "BufferedStream.h" +#include "CollectInBufferStream.h" #include "FileStream.h" #include "InvisibleTempFileStream.h" -#include "BufferedStream.h" +#include "RaidFileController.h" +#include "StreamableMemBlock.h" #include "MemLeakFindOn.h" #define CHECK_PHASE(phase) \ - if(rContext.GetPhase() != BackupContext::phase) \ + if(rContext.GetPhase() != BackupStoreContext::phase) \ { \ return std::auto_ptr<ProtocolObject>(new BackupProtocolServerError( \ BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_NotInRightProtocolPhase)); \ @@ -47,12 +47,12 @@ // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerVersion::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerVersion::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Return the current version, or an error if the requested version isn't allowed // Created: 2003/08/20 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerVersion::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerVersion::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Version) @@ -64,7 +64,7 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerVersion::DoCommand(BackupProto } // Mark the next phase - rContext.SetPhase(BackupContext::Phase_Login); + rContext.SetPhase(BackupStoreContext::Phase_Login); // Return our version return std::auto_ptr<ProtocolObject>(new BackupProtocolServerVersion(BACKUP_STORE_SERVER_VERSION)); @@ -73,12 +73,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerVersion::DoCommand(BackupProto // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerLogin::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerLogin::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Return the current version, or an error if the requested version isn't allowed // Created: 2003/08/20 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Login) @@ -131,7 +131,7 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtoco int64_t clientStoreMarker = rContext.GetClientStoreMarker(); // Mark the next phase - rContext.SetPhase(BackupContext::Phase_Commands); + rContext.SetPhase(BackupStoreContext::Phase_Commands); // Log login BOX_NOTICE("Login from Client ID " << @@ -151,12 +151,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtoco // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerFinished::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerFinished::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Marks end of conversation (Protocol framework handles this) // Created: 2003/08/20 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerFinished::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerFinished::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { BOX_NOTICE("Session finished for Client ID " << BOX_FORMAT_ACCOUNT(rContext.GetClientID())); @@ -172,47 +172,72 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerFinished::DoCommand(BackupProt // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerListDirectory::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerListDirectory::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to list a directory // Created: 2003/09/02 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerListDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerListDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) - // Ask the context for a directory - const BackupStoreDirectory &rdir(rContext.GetDirectory(mObjectID)); - // Store the listing to a stream std::auto_ptr<CollectInBufferStream> stream(new CollectInBufferStream); - rdir.WriteToStream(*stream, mFlagsMustBeSet, mFlagsNotToBeSet, mSendAttributes, - false /* never send dependency info to the client */); + + try + { + // Ask the context for a directory + const BackupStoreDirectory &rdir( + rContext.GetDirectory(mObjectID)); + rdir.WriteToStream(*stream, mFlagsMustBeSet, + mFlagsNotToBeSet, mSendAttributes, + false /* never send dependency info to the client */); + } + catch (RaidFileException &e) + { + if (e.GetSubType() == RaidFileException::RaidFileDoesntExist) + { + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_DoesNotExist)); + } + throw; + } + stream->SetForReading(); // Get the protocol to send the stream rProtocol.SendStreamAfterCommand(stream.release()); - return std::auto_ptr<ProtocolObject>(new BackupProtocolServerSuccess(mObjectID)); + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerSuccess(mObjectID)); } // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerStoreFile::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerStoreFile::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to store a file on the server // Created: 2003/09/02 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerStoreFile::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerStoreFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION + + std::auto_ptr<ProtocolObject> hookResult = + rContext.StartCommandHook(*this); + if(hookResult.get()) + { + return hookResult; + } // Check that the diff from file actually exists, if it's specified if(mDiffFromFileID != 0) { - if(!rContext.ObjectExists(mDiffFromFileID, BackupContext::ObjectExists_File)) + if(!rContext.ObjectExists(mDiffFromFileID, BackupStoreContext::ObjectExists_File)) { return std::auto_ptr<ProtocolObject>(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DiffFromFileDoesNotExist)); @@ -257,12 +282,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerStoreFile::DoCommand(BackupPro // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetObject::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerGetObject::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to get an arbitary object from the server // Created: 2003/09/03 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetObject::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetObject::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) @@ -285,13 +310,13 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetObject::DoCommand(BackupPro // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetFile::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerGetFile::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Command to get an file object from the server -- may have to do a bit of // work to get the object. // Created: 2003/09/03 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) @@ -457,12 +482,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProto // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerCreateDirectory::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerCreateDirectory::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Create directory command // Created: 2003/09/04 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerCreateDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerCreateDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -500,12 +525,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerCreateDirectory::DoCommand(Bac // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerChangeDirAttributes::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerChangeDirAttributes::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Change attributes on directory // Created: 2003/09/06 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerChangeDirAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerChangeDirAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -528,12 +553,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerChangeDirAttributes::DoCommand // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerSetReplacementFileAttributes::DoCommand(Protocol &, BackupContext &) +// Name: BackupProtocolServerSetReplacementFileAttributes::DoCommand(Protocol &, BackupStoreContext &) // Purpose: Change attributes on directory // Created: 2003/09/06 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerSetReplacementFileAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerSetReplacementFileAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -563,12 +588,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerSetReplacementFileAttributes:: // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Delete a file // Created: 2003/10/21 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -585,12 +610,36 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerDeleteFile::DoCommand(BackupPr // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerUndeleteFile::DoCommand( +// BackupProtocolServer &, BackupStoreContext &) +// Purpose: Undelete a file +// Created: 2008-09-12 +// +// -------------------------------------------------------------------------- +std::auto_ptr<ProtocolObject> BackupProtocolServerUndeleteFile::DoCommand( + BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Context handles this + bool result = rContext.UndeleteFile(mObjectID, mInDirectory); + + // return the object ID or zero for not found + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerSuccess(result ? mObjectID : 0)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Delete a directory // Created: 2003/10/21 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -613,12 +662,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerDeleteDirectory::DoCommand(Bac // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Undelete a directory // Created: 23/11/03 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -640,12 +689,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerUndeleteDirectory::DoCommand(B // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Command to set the client's store marker // Created: 2003/10/29 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -661,12 +710,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerSetClientStoreMarker::DoComman // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Command to move an object from one directory to another // Created: 2003/11/12 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) CHECK_WRITEABLE_SESSION @@ -704,12 +753,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerMoveObject::DoCommand(BackupPr // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Command to find the name of an object // Created: 12/11/03 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) @@ -730,7 +779,7 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetObjectName::DoCommand(Backu do { // Check the directory really exists - if(!rContext.ObjectExists(dirID, BackupContext::ObjectExists_Directory)) + if(!rContext.ObjectExists(dirID, BackupStoreContext::ObjectExists_Directory)) { return std::auto_ptr<ProtocolObject>(new BackupProtocolServerObjectName(BackupProtocolServerObjectName::NumNameElements_ObjectDoesntExist, 0, 0, 0)); } @@ -795,12 +844,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetObjectName::DoCommand(Backu // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Get the block index from a file, by ID // Created: 19/1/04 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) @@ -821,12 +870,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetBlockIndexByID::DoCommand(B // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Get the block index from a file, by name within a directory // Created: 19/1/04 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) @@ -873,12 +922,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetBlockIndexByName::DoCommand // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Return the amount of disc space used // Created: 19/4/04 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) @@ -904,12 +953,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetAccountUsage::DoCommand(Bac // -------------------------------------------------------------------------- // // Function -// Name: BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &, BackupContext &) +// Name: BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &, BackupStoreContext &) // Purpose: Return the amount of disc space used // Created: 19/4/04 // // -------------------------------------------------------------------------- -std::auto_ptr<ProtocolObject> BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) +std::auto_ptr<ProtocolObject> BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) { CHECK_PHASE(Phase_Commands) diff --git a/bin/bbstored/BackupConstants.h b/bin/bbstored/BackupConstants.h index 515b3bcd..19d06a15 100644 --- a/bin/bbstored/BackupConstants.h +++ b/bin/bbstored/BackupConstants.h @@ -10,8 +10,6 @@ #ifndef BACKUPCONSTANTS__H #define BACKUPCONSTANTS__H -#define BACKUP_STORE_DEFAULT_ACCOUNT_DATABASE_FILE "/etc/box/backupstoreaccounts" - // 15 minutes to timeout (milliseconds) #define BACKUP_STORE_TIMEOUT (15*60*1000) diff --git a/bin/bbstored/BackupContext.cpp b/bin/bbstored/BackupStoreContext.cpp index 16388099..990be05d 100644 --- a/bin/bbstored/BackupContext.cpp +++ b/bin/bbstored/BackupStoreContext.cpp @@ -1,7 +1,7 @@ // -------------------------------------------------------------------------- // // File -// Name: BackupContext.cpp +// Name: BackupStoreContext.cpp // Purpose: Context for backup store server // Created: 2003/08/20 // @@ -11,7 +11,7 @@ #include <stdio.h> -#include "BackupContext.h" +#include "BackupStoreContext.h" #include "RaidFileWrite.h" #include "RaidFileRead.h" #include "BackupStoreDirectory.h" @@ -33,7 +33,7 @@ // Maximum number of directories to keep in the cache // When the cache is bigger than this, everything gets // deleted. -#ifdef NDEBUG +#ifdef BOX_RELEASE_BUILD #define MAX_CACHE_SIZE 32 #else #define MAX_CACHE_SIZE 2 @@ -48,31 +48,33 @@ // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::BackupContext() +// Name: BackupStoreContext::BackupStoreContext() // Purpose: Constructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- -BackupContext::BackupContext(int32_t ClientID, BackupStoreDaemon &rDaemon) +BackupStoreContext::BackupStoreContext(int32_t ClientID, + HousekeepingInterface &rDaemon) : mClientID(ClientID), mrDaemon(rDaemon), mProtocolPhase(Phase_START), mClientHasAccount(false), mStoreDiscSet(-1), mReadOnly(true), - mSaveStoreInfoDelay(STORE_INFO_SAVE_DELAY) + mSaveStoreInfoDelay(STORE_INFO_SAVE_DELAY), + mpTestHook(NULL) { } // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::~BackupContext() +// Name: BackupStoreContext::~BackupStoreContext() // Purpose: Destructor // Created: 2003/08/20 // // -------------------------------------------------------------------------- -BackupContext::~BackupContext() +BackupStoreContext::~BackupStoreContext() { // Delete the objects in the cache for(std::map<int64_t, BackupStoreDirectory*>::iterator i(mDirectoryCache.begin()); i != mDirectoryCache.end(); ++i) @@ -85,12 +87,12 @@ BackupContext::~BackupContext() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::CleanUp() +// Name: BackupStoreContext::CleanUp() // Purpose: Clean up after a connection // Created: 16/12/03 // // -------------------------------------------------------------------------- -void BackupContext::CleanUp() +void BackupStoreContext::CleanUp() { // Make sure the store info is saved, if it has been loaded, isn't read only and has been modified if(mpStoreInfo.get() && !(mpStoreInfo->IsReadOnly()) && mpStoreInfo->IsModified()) @@ -102,12 +104,12 @@ void BackupContext::CleanUp() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::ReceivedFinishCommand() +// Name: BackupStoreContext::ReceivedFinishCommand() // Purpose: Called when the finish command is received by the protocol // Created: 16/12/03 // // -------------------------------------------------------------------------- -void BackupContext::ReceivedFinishCommand() +void BackupStoreContext::ReceivedFinishCommand() { if(!mReadOnly && mpStoreInfo.get()) { @@ -120,12 +122,12 @@ void BackupContext::ReceivedFinishCommand() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::AttemptToGetWriteLock() +// Name: BackupStoreContext::AttemptToGetWriteLock() // Purpose: Attempt to get a write lock for the store, and if so, unset the read only flags // Created: 2003/09/02 // // -------------------------------------------------------------------------- -bool BackupContext::AttemptToGetWriteLock() +bool BackupStoreContext::AttemptToGetWriteLock() { // Make the filename of the write lock file std::string writeLockFile; @@ -166,12 +168,12 @@ bool BackupContext::AttemptToGetWriteLock() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::LoadStoreInfo() +// Name: BackupStoreContext::LoadStoreInfo() // Purpose: Load the store info from disc // Created: 2003/09/03 // // -------------------------------------------------------------------------- -void BackupContext::LoadStoreInfo() +void BackupStoreContext::LoadStoreInfo() { if(mpStoreInfo.get() != 0) { @@ -195,12 +197,12 @@ void BackupContext::LoadStoreInfo() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::SaveStoreInfo(bool) +// Name: BackupStoreContext::SaveStoreInfo(bool) // Purpose: Potentially delayed saving of the store info // Created: 16/12/03 // // -------------------------------------------------------------------------- -void BackupContext::SaveStoreInfo(bool AllowDelay) +void BackupStoreContext::SaveStoreInfo(bool AllowDelay) { if(mpStoreInfo.get() == 0) { @@ -233,13 +235,13 @@ void BackupContext::SaveStoreInfo(bool AllowDelay) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::MakeObjectFilename(int64_t, std::string &, bool) +// Name: BackupStoreContext::MakeObjectFilename(int64_t, std::string &, bool) // Purpose: Create the filename of an object in the store, optionally creating the // containing directory if it doesn't already exist. // Created: 2003/09/02 // // -------------------------------------------------------------------------- -void BackupContext::MakeObjectFilename(int64_t ObjectID, std::string &rOutput, bool EnsureDirectoryExists) +void BackupStoreContext::MakeObjectFilename(int64_t ObjectID, std::string &rOutput, bool EnsureDirectoryExists) { // Delegate to utility function StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, mStoreDiscSet, rOutput, EnsureDirectoryExists); @@ -249,7 +251,7 @@ void BackupContext::MakeObjectFilename(int64_t ObjectID, std::string &rOutput, b // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::GetDirectoryInternal(int64_t) +// Name: BackupStoreContext::GetDirectoryInternal(int64_t) // Purpose: Return a reference to a directory. Valid only until the // next time a function which affects directories is called. // Mainly this funciton, and creation of files. @@ -257,7 +259,7 @@ void BackupContext::MakeObjectFilename(int64_t ObjectID, std::string &rOutput, b // Created: 2003/09/02 // // -------------------------------------------------------------------------- -BackupStoreDirectory &BackupContext::GetDirectoryInternal(int64_t ObjectID) +BackupStoreDirectory &BackupStoreContext::GetDirectoryInternal(int64_t ObjectID) { // Get the filename std::string filename; @@ -277,9 +279,17 @@ BackupStoreDirectory &BackupContext::GetDirectoryInternal(int64_t ObjectID) if(revID == item->second->GetRevisionID()) { // Looks good... return the cached object + BOX_TRACE("Returning object " << + BOX_FORMAT_OBJECTID(ObjectID) << + " from cache, modtime = " << revID); return *(item->second); } + BOX_TRACE("Refreshing object " << + BOX_FORMAT_OBJECTID(ObjectID) << + " in cache, modtime changed from " << + item->second->GetRevisionID() << " to " << revID); + // Delete this cached object delete item->second; mDirectoryCache.erase(item); @@ -335,12 +345,12 @@ BackupStoreDirectory &BackupContext::GetDirectoryInternal(int64_t ObjectID) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::AllocateObjectID() +// Name: BackupStoreContext::AllocateObjectID() // Purpose: Allocate a new object ID, tolerant of failures to save store info // Created: 16/12/03 // // -------------------------------------------------------------------------- -int64_t BackupContext::AllocateObjectID() +int64_t BackupStoreContext::AllocateObjectID() { if(mpStoreInfo.get() == 0) { @@ -374,7 +384,8 @@ int64_t BackupContext::AllocateObjectID() // Mark that the store info should be saved as soon as possible mSaveStoreInfoDelay = 0; - TRACE1("When allocating object ID, found that %lld is already in use\n", id); + BOX_WARNING("When allocating object ID, found that " << + BOX_FORMAT_OBJECTID(id) << " is already in use"); } THROW_EXCEPTION(BackupStoreException, CouldNotFindUnusedIDDuringAllocation) @@ -384,13 +395,13 @@ int64_t BackupContext::AllocateObjectID() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::AddFile(IOStream &, int64_t, int64_t, int64_t, const BackupStoreFilename &, bool) +// Name: BackupStoreContext::AddFile(IOStream &, int64_t, int64_t, int64_t, const BackupStoreFilename &, bool) // Purpose: Add a file to the store, from a given stream, into a specified directory. // Returns object ID of the new file. // Created: 2003/09/03 // // -------------------------------------------------------------------------- -int64_t BackupContext::AddFile(IOStream &rFile, int64_t InDirectory, int64_t ModificationTime, +int64_t BackupStoreContext::AddFile(IOStream &rFile, int64_t InDirectory, int64_t ModificationTime, int64_t AttributesHash, int64_t DiffFromFileID, const BackupStoreFilename &rFilename, bool MarkFileWithSameNameAsOldVersions) { @@ -672,18 +683,19 @@ int64_t BackupContext::AddFile(IOStream &rFile, int64_t InDirectory, int64_t Mod // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &) +// Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &) // Purpose: Deletes a file, returning true if the file existed. Object ID returned too, set to zero if not found. // Created: 2003/10/21 // // -------------------------------------------------------------------------- -bool BackupContext::DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut) +bool BackupStoreContext::DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut) { // Essential checks! if(mpStoreInfo.get() == 0) { THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) } + if(mReadOnly) { THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) @@ -748,23 +760,102 @@ bool BackupContext::DeleteFile(const BackupStoreFilename &rFilename, int64_t InD RemoveDirectoryFromCache(InDirectory); throw; } - return fileExisted; } +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &) +// Purpose: Deletes a file, returning true if the file existed. Object ID returned too, set to zero if not found. +// Created: 2003/10/21 +// +// -------------------------------------------------------------------------- +bool BackupStoreContext::UndeleteFile(int64_t ObjectID, int64_t InDirectory) +{ + // Essential checks! + if(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) + } + + // Find the directory the file is in (will exception if it fails) + BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory)); + + // Setup flags + bool fileExisted = false; + bool madeChanges = false; + + // Count of deleted blocks + int64_t blocksDel = 0; + + try + { + // Iterate through directory, only looking at files which have been deleted + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *e = 0; + while((e = i.Next(BackupStoreDirectory::Entry::Flags_File | + BackupStoreDirectory::Entry::Flags_Deleted, 0)) != 0) + { + // Compare name + if(e->GetObjectID() == ObjectID) + { + // Check that it's definitely already deleted + ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0); + // Clear deleted flag + e->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted); + // Mark as made a change + madeChanges = true; + blocksDel -= e->GetSizeInBlocks(); + + // Is this the last version? + if((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0) + { + // Yes. It's been found. + fileExisted = true; + } + } + } + + // Save changes? + if(madeChanges) + { + // Save the directory back + SaveDirectory(dir, InDirectory); + + // Modify the store info, and write + mpStoreInfo->ChangeBlocksInDeletedFiles(blocksDel); + + // Maybe postponed save of store info + SaveStoreInfo(); + } + } + catch(...) + { + RemoveDirectoryFromCache(InDirectory); + throw; + } + + return fileExisted; +} // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::RemoveDirectoryFromCache(int64_t) +// Name: BackupStoreContext::RemoveDirectoryFromCache(int64_t) // Purpose: Remove directory from cache // Created: 2003/09/04 // // -------------------------------------------------------------------------- -void BackupContext::RemoveDirectoryFromCache(int64_t ObjectID) +void BackupStoreContext::RemoveDirectoryFromCache(int64_t ObjectID) { std::map<int64_t, BackupStoreDirectory*>::iterator item(mDirectoryCache.find(ObjectID)); if(item != mDirectoryCache.end()) @@ -780,12 +871,12 @@ void BackupContext::RemoveDirectoryFromCache(int64_t ObjectID) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::SaveDirectory(BackupStoreDirectory &, int64_t) +// Name: BackupStoreContext::SaveDirectory(BackupStoreDirectory &, int64_t) // Purpose: Save directory back to disc, update time in cache // Created: 2003/09/04 // // -------------------------------------------------------------------------- -void BackupContext::SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID) +void BackupStoreContext::SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID) { if(mpStoreInfo.get() == 0) { @@ -842,12 +933,12 @@ void BackupContext::SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::AddDirectory(int64_t, const BackupStoreFilename &, bool &) +// Name: BackupStoreContext::AddDirectory(int64_t, const BackupStoreFilename &, bool &) // Purpose: Creates a directory (or just returns the ID of an existing one). rAlreadyExists set appropraitely. // Created: 2003/09/04 // // -------------------------------------------------------------------------- -int64_t BackupContext::AddDirectory(int64_t InDirectory, const BackupStoreFilename &rFilename, const StreamableMemBlock &Attributes, int64_t AttributesModTime, bool &rAlreadyExists) +int64_t BackupStoreContext::AddDirectory(int64_t InDirectory, const BackupStoreFilename &rFilename, const StreamableMemBlock &Attributes, int64_t AttributesModTime, bool &rAlreadyExists) { if(mpStoreInfo.get() == 0) { @@ -936,12 +1027,12 @@ int64_t BackupContext::AddDirectory(int64_t InDirectory, const BackupStoreFilena // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &, bool) +// Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &, bool) // Purpose: Recusively deletes a directory (or undeletes if Undelete = true) // Created: 2003/10/21 // // -------------------------------------------------------------------------- -void BackupContext::DeleteDirectory(int64_t ObjectID, bool Undelete) +void BackupStoreContext::DeleteDirectory(int64_t ObjectID, bool Undelete) { // Essential checks! if(mpStoreInfo.get() == 0) @@ -1018,12 +1109,12 @@ void BackupContext::DeleteDirectory(int64_t ObjectID, bool Undelete) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::DeleteDirectoryRecurse(BackupStoreDirectory &, int64_t) +// Name: BackupStoreContext::DeleteDirectoryRecurse(BackupStoreDirectory &, int64_t) // Purpose: Private. Deletes a directory depth-first recusively. // Created: 2003/10/21 // // -------------------------------------------------------------------------- -void BackupContext::DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDeletedOut, bool Undelete) +void BackupStoreContext::DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDeletedOut, bool Undelete) { try { @@ -1120,12 +1211,12 @@ void BackupContext::DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDel // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::ChangeDirAttributes(int64_t, const StreamableMemBlock &, int64_t) +// Name: BackupStoreContext::ChangeDirAttributes(int64_t, const StreamableMemBlock &, int64_t) // Purpose: Change the attributes of a directory // Created: 2003/09/06 // // -------------------------------------------------------------------------- -void BackupContext::ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime) +void BackupStoreContext::ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime) { if(mpStoreInfo.get() == 0) { @@ -1157,12 +1248,12 @@ void BackupContext::ChangeDirAttributes(int64_t Directory, const StreamableMemBl // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::ChangeFileAttributes(int64_t, int64_t, const StreamableMemBlock &, int64_t) +// Name: BackupStoreContext::ChangeFileAttributes(int64_t, int64_t, const StreamableMemBlock &, int64_t) // Purpose: Sets the attributes on a directory entry. Returns true if the object existed, false if it didn't. // Created: 2003/09/06 // // -------------------------------------------------------------------------- -bool BackupContext::ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut) +bool BackupStoreContext::ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut) { if(mpStoreInfo.get() == 0) { @@ -1222,12 +1313,12 @@ bool BackupContext::ChangeFileAttributes(const BackupStoreFilename &rFilename, i // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::ObjectExists(int64_t) +// Name: BackupStoreContext::ObjectExists(int64_t) // Purpose: Test to see if an object of this ID exists in the store // Created: 2003/09/03 // // -------------------------------------------------------------------------- -bool BackupContext::ObjectExists(int64_t ObjectID, int MustBe) +bool BackupStoreContext::ObjectExists(int64_t ObjectID, int MustBe) { if(mpStoreInfo.get() == 0) { @@ -1293,12 +1384,12 @@ bool BackupContext::ObjectExists(int64_t ObjectID, int MustBe) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::OpenObject(int64_t) +// Name: BackupStoreContext::OpenObject(int64_t) // Purpose: Opens an object // Created: 2003/09/03 // // -------------------------------------------------------------------------- -std::auto_ptr<IOStream> BackupContext::OpenObject(int64_t ObjectID) +std::auto_ptr<IOStream> BackupStoreContext::OpenObject(int64_t ObjectID) { if(mpStoreInfo.get() == 0) { @@ -1315,12 +1406,12 @@ std::auto_ptr<IOStream> BackupContext::OpenObject(int64_t ObjectID) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::GetClientStoreMarker() +// Name: BackupStoreContext::GetClientStoreMarker() // Purpose: Retrieve the client store marker // Created: 2003/10/29 // // -------------------------------------------------------------------------- -int64_t BackupContext::GetClientStoreMarker() +int64_t BackupStoreContext::GetClientStoreMarker() { if(mpStoreInfo.get() == 0) { @@ -1334,12 +1425,12 @@ int64_t BackupContext::GetClientStoreMarker() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::GetStoreDiscUsageInfo(int64_t &, int64_t &, int64_t &) +// Name: BackupStoreContext::GetStoreDiscUsageInfo(int64_t &, int64_t &, int64_t &) // Purpose: Get disc usage info from store info // Created: 1/1/04 // // -------------------------------------------------------------------------- -void BackupContext::GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocksSoftLimit, int64_t &rBlocksHardLimit) +void BackupStoreContext::GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocksSoftLimit, int64_t &rBlocksHardLimit) { if(mpStoreInfo.get() == 0) { @@ -1355,12 +1446,12 @@ void BackupContext::GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocks // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::HardLimitExceeded() +// Name: BackupStoreContext::HardLimitExceeded() // Purpose: Returns true if the hard limit has been exceeded // Created: 1/1/04 // // -------------------------------------------------------------------------- -bool BackupContext::HardLimitExceeded() +bool BackupStoreContext::HardLimitExceeded() { if(mpStoreInfo.get() == 0) { @@ -1374,12 +1465,12 @@ bool BackupContext::HardLimitExceeded() // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::SetClientStoreMarker(int64_t) +// Name: BackupStoreContext::SetClientStoreMarker(int64_t) // Purpose: Sets the client store marker, and commits it to disc // Created: 2003/10/29 // // -------------------------------------------------------------------------- -void BackupContext::SetClientStoreMarker(int64_t ClientStoreMarker) +void BackupStoreContext::SetClientStoreMarker(int64_t ClientStoreMarker) { if(mpStoreInfo.get() == 0) { @@ -1398,12 +1489,12 @@ void BackupContext::SetClientStoreMarker(int64_t ClientStoreMarker) // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::MoveObject(int64_t, int64_t, int64_t, const BackupStoreFilename &, bool) +// Name: BackupStoreContext::MoveObject(int64_t, int64_t, int64_t, const BackupStoreFilename &, bool) // Purpose: Move an object (and all objects with the same name) from one directory to another // Created: 12/11/03 // // -------------------------------------------------------------------------- -void BackupContext::MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject) +void BackupStoreContext::MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject) { if(mReadOnly) { @@ -1643,12 +1734,12 @@ void BackupContext::MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int6 // -------------------------------------------------------------------------- // // Function -// Name: BackupContext::GetBackupStoreInfo() +// Name: BackupStoreContext::GetBackupStoreInfo() // Purpose: Return the backup store info object, exception if it isn't loaded // Created: 19/4/04 // // -------------------------------------------------------------------------- -const BackupStoreInfo &BackupContext::GetBackupStoreInfo() const +const BackupStoreInfo &BackupStoreContext::GetBackupStoreInfo() const { if(mpStoreInfo.get() == 0) { diff --git a/bin/bbstored/BackupContext.h b/bin/bbstored/BackupStoreContext.h index 18f2f25c..4cfdb601 100644 --- a/bin/bbstored/BackupContext.h +++ b/bin/bbstored/BackupStoreContext.h @@ -1,7 +1,7 @@ // -------------------------------------------------------------------------- // // File -// Name: BackupContext.h +// Name: BackupStoreContext.h // Purpose: Context for backup store server // Created: 2003/08/20 // @@ -15,6 +15,7 @@ #include <memory> #include "NamedLock.h" +#include "ProtocolObject.h" #include "Utils.h" class BackupStoreDirectory; @@ -22,23 +23,31 @@ class BackupStoreFilename; class BackupStoreDaemon; class BackupStoreInfo; class IOStream; +class BackupProtocolObject; class StreamableMemBlock; +class HousekeepingInterface +{ + public: + virtual ~HousekeepingInterface() { } + virtual void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) = 0; +}; + // -------------------------------------------------------------------------- // // Class -// Name: BackupContext +// Name: BackupStoreContext // Purpose: Context for backup store server // Created: 2003/08/20 // // -------------------------------------------------------------------------- -class BackupContext +class BackupStoreContext { public: - BackupContext(int32_t ClientID, BackupStoreDaemon &rDaemon); - ~BackupContext(); + BackupStoreContext(int32_t ClientID, HousekeepingInterface &rDaemon); + ~BackupStoreContext(); private: - BackupContext(const BackupContext &rToCopy); + BackupStoreContext(const BackupStoreContext &rToCopy); public: void ReceivedFinishCommand(); @@ -83,7 +92,7 @@ public: // -------------------------------------------------------------------------- // // Function - // Name: BackupContext::GetDirectory(int64_t) + // Name: BackupStoreContext::GetDirectory(int64_t) // Purpose: Return a reference to a directory. Valid only until the // next time a function which affects directories is called. // Mainly this funciton, and creation of files. @@ -103,6 +112,7 @@ public: void ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime); bool ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut); bool DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut); + bool UndeleteFile(int64_t ObjectID, int64_t InDirectory); void DeleteDirectory(int64_t ObjectID, bool Undelete = false); void MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject); @@ -129,7 +139,7 @@ private: private: int32_t mClientID; - BackupStoreDaemon &mrDaemon; + HousekeepingInterface &mrDaemon; int mProtocolPhase; bool mClientHasAccount; std::string mStoreRoot; // has final directory separator @@ -143,6 +153,30 @@ private: // Directory cache std::map<int64_t, BackupStoreDirectory*> mDirectoryCache; + +public: + class TestHook + { + public: + virtual std::auto_ptr<ProtocolObject> StartCommand(BackupProtocolObject& + rCommand) = 0; + virtual ~TestHook() { } + }; + void SetTestHook(TestHook& rTestHook) + { + mpTestHook = &rTestHook; + } + std::auto_ptr<ProtocolObject> StartCommandHook(BackupProtocolObject& rCommand) + { + if(mpTestHook) + { + return mpTestHook->StartCommand(rCommand); + } + return std::auto_ptr<ProtocolObject>(); + } + +private: + TestHook* mpTestHook; }; #endif // BACKUPCONTEXT__H diff --git a/bin/bbstored/BackupStoreDaemon.cpp b/bin/bbstored/BackupStoreDaemon.cpp index 28e28176..4de0a078 100644 --- a/bin/bbstored/BackupStoreDaemon.cpp +++ b/bin/bbstored/BackupStoreDaemon.cpp @@ -17,7 +17,7 @@ #include <syslog.h> #endif -#include "BackupContext.h" +#include "BackupStoreContext.h" #include "BackupStoreDaemon.h" #include "BackupStoreConfigVerify.h" #include "autogen_BackupProtocolServer.h" @@ -43,7 +43,8 @@ BackupStoreDaemon::BackupStoreDaemon() mHaveForkedHousekeeping(false), mIsHousekeepingProcess(false), mHousekeepingInited(false), - mInterProcessComms(mInterProcessCommsSocket) + mInterProcessComms(mInterProcessCommsSocket), + mpTestHook(NULL) { } @@ -171,11 +172,12 @@ void BackupStoreDaemon::Run() const Configuration &config(GetConfiguration()); mExtendedLogging = config.GetKeyValueBool("ExtendedLogging"); -#ifdef WIN32 - // Housekeeping runs synchronously on Win32 -#else - // Fork off housekeeping daemon -- must only do this the first time Run() is called - if(!mHaveForkedHousekeeping) + // Fork off housekeeping daemon -- must only do this the first + // time Run() is called. Housekeeping runs synchronously on Win32 + // because IsSingleProcess() is always true + +#ifndef WIN32 + if(!IsSingleProcess() && !mHaveForkedHousekeeping) { // Open a socket pair for communication int sv[2] = {-1,-1}; @@ -205,7 +207,9 @@ void BackupStoreDaemon::Run() // Log that housekeeping started BOX_INFO("Housekeeping process started"); // Ignore term and hup - // Parent will handle these and alert the child via the socket, don't want to randomly die + // Parent will handle these and alert the + // child via the socket, don't want to + // randomly die! ::signal(SIGHUP, SIG_IGN); ::signal(SIGTERM, SIG_IGN); } @@ -317,10 +321,18 @@ void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream) } // Make ps listings clearer - SetProcessTitle("client %08x", id); + std::ostringstream tag; + tag << "client=" << BOX_FORMAT_ACCOUNT(id); + SetProcessTitle(tag.str().c_str()); + Logging::Tagger tagWithClientID(tag.str()); // Create a context, using this ID - BackupContext context(id, *this); + BackupStoreContext context(id, *this); + + if (mpTestHook) + { + context.SetTestHook(*mpTestHook); + } // See if the client has an account? if(mpAccounts && mpAccounts->AccountExists(id)) @@ -352,7 +364,7 @@ void BackupStoreDaemon::LogConnectionStats(const char *commonName, const SocketStreamTLS &s) { // Log the amount of data transferred - BOX_INFO("Connection statistics for " << commonName << ":" + BOX_NOTICE("Connection statistics for " << commonName << ":" " IN=" << s.GetBytesRead() << " OUT=" << s.GetBytesWritten() << " TOTAL=" << (s.GetBytesRead() + s.GetBytesWritten())); diff --git a/bin/bbstored/BackupStoreDaemon.h b/bin/bbstored/BackupStoreDaemon.h index 387a1d5b..a5d216f4 100644 --- a/bin/bbstored/BackupStoreDaemon.h +++ b/bin/bbstored/BackupStoreDaemon.h @@ -13,6 +13,7 @@ #include "ServerTLS.h" #include "BoxPortsAndFiles.h" #include "BackupConstants.h" +#include "BackupStoreContext.h" #include "IOStreamGetLine.h" class BackupStoreAccounts; @@ -27,7 +28,8 @@ class HousekeepStoreAccount; // Created: 2003/08/20 // // -------------------------------------------------------------------------- -class BackupStoreDaemon : public ServerTLS<BOX_PORT_BBSTORED> +class BackupStoreDaemon : public ServerTLS<BOX_PORT_BBSTORED>, + HousekeepingInterface { friend class HousekeepStoreAccount; @@ -38,7 +40,7 @@ private: BackupStoreDaemon(const BackupStoreDaemon &rToCopy); public: - // For BackupContext to communicate with housekeeping process + // For BackupStoreContext to communicate with housekeeping process void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) { #ifndef WIN32 @@ -81,6 +83,15 @@ private: void HousekeepingInit(); void RunHousekeepingIfNeeded(); int64_t mLastHousekeepingRun; + +public: + void SetTestHook(BackupStoreContext::TestHook& rTestHook) + { + mpTestHook = &rTestHook; + } + +private: + BackupStoreContext::TestHook* mpTestHook; }; diff --git a/bin/bbstored/HousekeepStoreAccount.cpp b/bin/bbstored/HousekeepStoreAccount.cpp index 9f4239e7..dbb9b544 100644 --- a/bin/bbstored/HousekeepStoreAccount.cpp +++ b/bin/bbstored/HousekeepStoreAccount.cpp @@ -85,16 +85,19 @@ void HousekeepStoreAccount::DoHousekeeping() { // Attempt to lock the account std::string writeLockFilename; - StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet, writeLockFilename); + StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet, + writeLockFilename); NamedLock writeLock; - if(!writeLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */)) + if(!writeLock.TryAndGetLock(writeLockFilename.c_str(), + 0600 /* restrictive file permissions */)) { // Couldn't lock the account -- just stop now return; } // Load the store info to find necessary info for the housekeeping - std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(mAccountID, mStoreRoot, mStoreDiscSet, false /* Read/Write */)); + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(mAccountID, + mStoreRoot, mStoreDiscSet, false /* Read/Write */)); // Calculate how much should be deleted mDeletionSizeTarget = info->GetBlocksUsed() - info->GetBlocksSoftLimit(); @@ -104,14 +107,18 @@ void HousekeepStoreAccount::DoHousekeeping() } // Scan the directory for potential things to delete - // This will also remove elegiable items marked with RemoveASAP + // This will also remove eligible items marked with RemoveASAP bool continueHousekeeping = ScanDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID); - // If scan directory stopped for some reason, probably parent instructed to teminate, stop now. + // If scan directory stopped for some reason, probably parent + // instructed to terminate, stop now. if(!continueHousekeeping) { - // If any files were marked "delete now", then update the size of the store. - if(mBlocksUsedDelta != 0 || mBlocksInOldFilesDelta != 0 || mBlocksInDeletedFilesDelta != 0) + // If any files were marked "delete now", then update + // the size of the store. + if(mBlocksUsedDelta != 0 || + mBlocksInOldFilesDelta != 0 || + mBlocksInDeletedFilesDelta != 0) { info->ChangeBlocksUsed(mBlocksUsedDelta); info->ChangeBlocksInOldFiles(mBlocksInOldFilesDelta); @@ -124,8 +131,8 @@ void HousekeepStoreAccount::DoHousekeeping() return; } - // Log any difference in opinion between the values recorded in the store info, and - // the values just calculated for space usage. + // Log any difference in opinion between the values recorded in + // the store info, and the values just calculated for space usage. // BLOCK { int64_t used = info->GetBlocksUsed(); @@ -133,9 +140,12 @@ void HousekeepStoreAccount::DoHousekeeping() int64_t usedDeleted = info->GetBlocksInDeletedFiles(); int64_t usedDirectories = info->GetBlocksInDirectories(); - // If the counts were wrong, taking into account RemoveASAP items deleted, log a message - if((used + mBlocksUsedDelta) != mBlocksUsed || (usedOld + mBlocksInOldFilesDelta) != mBlocksInOldFiles - || (usedDeleted + mBlocksInDeletedFilesDelta) != mBlocksInDeletedFiles || usedDirectories != mBlocksInDirectories) + // If the counts were wrong, taking into account RemoveASAP + // items deleted, log a message + if((used + mBlocksUsedDelta) != mBlocksUsed + || (usedOld + mBlocksInOldFilesDelta) != mBlocksInOldFiles + || (usedDeleted + mBlocksInDeletedFilesDelta) != mBlocksInDeletedFiles + || usedDirectories != mBlocksInDirectories) { // Log this BOX_ERROR("Housekeeping on account " << @@ -153,18 +163,25 @@ void HousekeepStoreAccount::DoHousekeeping() } // If the current values don't match, store them - if(used != mBlocksUsed || usedOld != mBlocksInOldFiles - || usedDeleted != mBlocksInDeletedFiles || usedDirectories != (mBlocksInDirectories + mBlocksInDirectoriesDelta)) + if(used != mBlocksUsed + || usedOld != mBlocksInOldFiles + || usedDeleted != mBlocksInDeletedFiles + || usedDirectories != (mBlocksInDirectories + mBlocksInDirectoriesDelta)) { // Set corrected values in store info - info->CorrectAllUsedValues(mBlocksUsed, mBlocksInOldFiles, mBlocksInDeletedFiles, mBlocksInDirectories + mBlocksInDirectoriesDelta); + info->CorrectAllUsedValues(mBlocksUsed, + mBlocksInOldFiles, mBlocksInDeletedFiles, + mBlocksInDirectories + mBlocksInDirectoriesDelta); info->Save(); } } - // Reset the delta counts for files, as they will include RemoveASAP flagged files deleted - // during the initial scan. - int64_t removeASAPBlocksUsedDelta = mBlocksUsedDelta; // keep for reporting + // Reset the delta counts for files, as they will include + // RemoveASAP flagged files deleted during the initial scan. + + // keep for reporting + int64_t removeASAPBlocksUsedDelta = mBlocksUsedDelta; + mBlocksUsedDelta = 0; mBlocksInOldFilesDelta = 0; mBlocksInDeletedFilesDelta = 0; @@ -172,7 +189,8 @@ void HousekeepStoreAccount::DoHousekeeping() // Go and delete items from the accounts bool deleteInterrupted = DeleteFiles(); - // If that wasn't interrupted, remove any empty directories which are also marked as deleted in their containing directory + // If that wasn't interrupted, remove any empty directories which + // are also marked as deleted in their containing directory if(!deleteInterrupted) { deleteInterrupted = DeleteEmptyDirectories(); @@ -190,8 +208,9 @@ void HousekeepStoreAccount::DoHousekeeping() (deleteInterrupted?" and was interrupted":"")); } - // Make sure the delta's won't cause problems if the counts are really wrong, and - // it wasn't fixed because the store was updated during the scan. + // Make sure the delta's won't cause problems if the counts are + // really wrong, and it wasn't fixed because the store was + // updated during the scan. if(mBlocksUsedDelta < (0 - info->GetBlocksUsed())) { mBlocksUsedDelta = (0 - info->GetBlocksUsed()); @@ -218,7 +237,8 @@ void HousekeepStoreAccount::DoHousekeeping() // Save the store info back info->Save(); - // Explicity release the lock (would happen automatically on going out of scope, included for code clarity) + // Explicity release the lock (would happen automatically on + // going out of scope, included for code clarity) writeLock.ReleaseLock(); } @@ -243,8 +263,9 @@ void HousekeepStoreAccount::MakeObjectFilename(int64_t ObjectID, std::string &rF // // Function // Name: HousekeepStoreAccount::ScanDirectory(int64_t) -// Purpose: Private. Scan a directory for potenitally deleteable items, and -// add them to the list. Returns true if the scan should continue. +// Purpose: Private. Scan a directory for potentially deleteable +// items, and add them to the list. Returns true if the +// scan should continue. // Created: 11/12/03 // // -------------------------------------------------------------------------- @@ -253,9 +274,12 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) #ifndef WIN32 if((--mCountUntilNextInterprocessMsgCheck) <= 0) { - mCountUntilNextInterprocessMsgCheck = POLL_INTERPROCESS_MSG_CHECK_FREQUENCY; + mCountUntilNextInterprocessMsgCheck = + POLL_INTERPROCESS_MSG_CHECK_FREQUENCY; + // Check for having to stop - if(mrDaemon.CheckForInterProcessMsg(mAccountID)) // include account ID here as the specified account is locked + // Include account ID here as the specified account is locked + if(mrDaemon.CheckForInterProcessMsg(mAccountID)) { // Need to abort now return false; @@ -268,7 +292,8 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) MakeObjectFilename(ObjectID, objectFilename); // Open it. - std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(mStoreDiscSet, objectFilename)); + std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(mStoreDiscSet, + objectFilename)); // Add the size of the directory on disc to the size being calculated int64_t originalDirSizeInBlocks = dirStream->GetDiscUsageInBlocks(); @@ -290,8 +315,8 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) // BLOCK { - // Remove any files which are marked for removal as soon as they become old - // or deleted. + // Remove any files which are marked for removal as soon + // as they become old or deleted. bool deletedSomething = false; do { @@ -324,7 +349,8 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) // Add files to the list of potential deletions // map to count the distance from the mark - std::map<std::pair<BackupStoreFilename, int32_t>, int32_t> markVersionAges; + typedef std::pair<std::string, int32_t> version_t; + std::map<version_t, int32_t> markVersionAges; // map of pair (filename, mark number) -> version age // NOTE: use a reverse iterator to allow the distance from mark stuff to work @@ -342,7 +368,10 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) // Work out ages of this version from the last mark int32_t enVersionAge = 0; - std::map<std::pair<BackupStoreFilename, int32_t>, int32_t>::iterator enVersionAgeI(markVersionAges.find(std::pair<BackupStoreFilename, int32_t>(en->GetName(), en->GetMarkNumber()))); + std::map<version_t, int32_t>::iterator enVersionAgeI( + markVersionAges.find( + version_t(en->GetName().GetEncodedFilename(), + en->GetMarkNumber()))); if(enVersionAgeI != markVersionAges.end()) { enVersionAge = enVersionAgeI->second + 1; @@ -350,7 +379,7 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) } else { - markVersionAges[std::pair<BackupStoreFilename, int32_t>(en->GetName(), en->GetMarkNumber())] = enVersionAge; + markVersionAges[version_t(en->GetName().GetEncodedFilename(), en->GetMarkNumber())] = enVersionAge; } // enVersionAge is now the age of this version. @@ -364,6 +393,9 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) d.mSizeInBlocks = en->GetSizeInBlocks(); d.mMarkNumber = en->GetMarkNumber(); d.mVersionAgeWithinMark = enVersionAge; + d.mIsFlagDeleted = (enFlags & + BackupStoreDirectory::Entry::Flags_Deleted) + ? true : false; // Add it to the list mPotentialDeletions.insert(d); @@ -541,6 +573,10 @@ bool HousekeepStoreAccount::DeleteFiles() // Delete the file DeleteFile(i->mInDirectory, i->mObjectID, dir, dirFilename, dirSizeInBlocksOrig); + BOX_INFO("Housekeeping removed " << + (i->mIsFlagDeleted ? "deleted" : "old") << + " file " << BOX_FORMAT_OBJECTID(i->mObjectID) << + " from dir " << BOX_FORMAT_OBJECTID(i->mInDirectory)); // Stop if the deletion target has been matched or exceeded // (checking here rather than at the beginning will tend to reduce the @@ -786,99 +822,115 @@ bool HousekeepStoreAccount::DeleteEmptyDirectories() continue; } - // Load up the directory to potentially delete - std::string dirFilename; - BackupStoreDirectory dir; - int64_t dirSizeInBlocks = 0; - { - MakeObjectFilename(*i, dirFilename); - // Check it actually exists (just in case it gets added twice to the list) - if(!RaidFileRead::FileExists(mStoreDiscSet, dirFilename)) - { - // doesn't exist, next! - continue; - } - // load - std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(mStoreDiscSet, dirFilename)); - dirSizeInBlocks = dirStream->GetDiscUsageInBlocks(); - dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); - } + DeleteEmptyDirectory(*i, toExamine); + } - // Make sure this directory is actually empty - if(dir.GetNumberOfEntries() != 0) - { - // Not actually empty, try next one - continue; - } + // Remove contents of empty directories + mEmptyDirectories.clear(); + // Swap in new, so it's examined next time round + mEmptyDirectories.swap(toExamine); + } + + // Not interrupted + return false; +} - // Candiate for deletion... open containing directory - std::string containingDirFilename; - BackupStoreDirectory containingDir; - int64_t containingDirSizeInBlocksOrig = 0; - { - MakeObjectFilename(dir.GetContainerID(), containingDirFilename); - std::auto_ptr<RaidFileRead> containingDirStream(RaidFileRead::Open(mStoreDiscSet, containingDirFilename)); - containingDirSizeInBlocksOrig = containingDirStream->GetDiscUsageInBlocks(); - containingDir.ReadFromStream(*containingDirStream, IOStream::TimeOutInfinite); - } +void HousekeepStoreAccount::DeleteEmptyDirectory(int64_t dirId, + std::vector<int64_t>& rToExamine) +{ + // Load up the directory to potentially delete + std::string dirFilename; + BackupStoreDirectory dir; + int64_t dirSizeInBlocks = 0; - // Find the entry - BackupStoreDirectory::Entry *pdirentry = containingDir.FindEntryByID(dir.GetObjectID()); - if((pdirentry != 0) && ((pdirentry->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0)) - { - // Should be deleted - containingDir.DeleteEntry(dir.GetObjectID()); + // BLOCK + { + MakeObjectFilename(dirId, dirFilename); + // Check it actually exists (just in case it gets + // added twice to the list) + if(!RaidFileRead::FileExists(mStoreDiscSet, dirFilename)) + { + // doesn't exist, next! + return; + } + // load + std::auto_ptr<RaidFileRead> dirStream( + RaidFileRead::Open(mStoreDiscSet, dirFilename)); + dirSizeInBlocks = dirStream->GetDiscUsageInBlocks(); + dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); + } - // Is the containing dir now a candidate for deletion? - if(containingDir.GetNumberOfEntries() == 0) - { - toExamine.push_back(containingDir.GetObjectID()); - } + // Make sure this directory is actually empty + if(dir.GetNumberOfEntries() != 0) + { + // Not actually empty, try next one + return; + } - // Write revised parent directory - RaidFileWrite writeDir(mStoreDiscSet, containingDirFilename); - writeDir.Open(true /* allow overwriting */); - containingDir.WriteToStream(writeDir); + // Candidate for deletion... open containing directory + std::string containingDirFilename; + BackupStoreDirectory containingDir; + int64_t containingDirSizeInBlocksOrig = 0; + { + MakeObjectFilename(dir.GetContainerID(), containingDirFilename); + std::auto_ptr<RaidFileRead> containingDirStream( + RaidFileRead::Open(mStoreDiscSet, + containingDirFilename)); + containingDirSizeInBlocksOrig = + containingDirStream->GetDiscUsageInBlocks(); + containingDir.ReadFromStream(*containingDirStream, + IOStream::TimeOutInfinite); + } - // get the disc usage (must do this before commiting it) - int64_t dirSize = writeDir.GetDiscUsageInBlocks(); + // Find the entry + BackupStoreDirectory::Entry *pdirentry = + containingDir.FindEntryByID(dir.GetObjectID()); + if((pdirentry != 0) && ((pdirentry->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0)) + { + // Should be deleted + containingDir.DeleteEntry(dir.GetObjectID()); - // Commit directory - writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); + // Is the containing dir now a candidate for deletion? + if(containingDir.GetNumberOfEntries() == 0) + { + rToExamine.push_back(containingDir.GetObjectID()); + } - // adjust usage counts for this directory - if(dirSize > 0) - { - int64_t adjust = dirSize - containingDirSizeInBlocksOrig; - mBlocksUsedDelta += adjust; - mBlocksInDirectoriesDelta += adjust; - } + // Write revised parent directory + RaidFileWrite writeDir(mStoreDiscSet, containingDirFilename); + writeDir.Open(true /* allow overwriting */); + containingDir.WriteToStream(writeDir); - // Delete the directory itself - { - RaidFileWrite del(mStoreDiscSet, dirFilename); - del.Delete(); - } + // get the disc usage (must do this before commiting it) + int64_t dirSize = writeDir.GetDiscUsageInBlocks(); - // And adjust usage counts for the directory that's just been deleted - mBlocksUsedDelta -= dirSizeInBlocks; - mBlocksInDirectoriesDelta -= dirSizeInBlocks; + // Commit directory + writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); - // Update count - ++mEmptyDirectoriesDeleted; - } + // adjust usage counts for this directory + if(dirSize > 0) + { + int64_t adjust = dirSize - containingDirSizeInBlocksOrig; + mBlocksUsedDelta += adjust; + mBlocksInDirectoriesDelta += adjust; } - // Remove contents of empty directories - mEmptyDirectories.clear(); - // Swap in new, so it's examined next time round - mEmptyDirectories.swap(toExamine); - } - - // Not interrupted - return false; -} + // Delete the directory itself + { + RaidFileWrite del(mStoreDiscSet, dirFilename); + del.Delete(); + } + BOX_INFO("Housekeeping removed empty deleted dir " << + BOX_FORMAT_OBJECTID(dirId)); + // And adjust usage counts for the directory that's + // just been deleted + mBlocksUsedDelta -= dirSizeInBlocks; + mBlocksInDirectoriesDelta -= dirSizeInBlocks; + // Update count + ++mEmptyDirectoriesDeleted; + } +} diff --git a/bin/bbstored/HousekeepStoreAccount.h b/bin/bbstored/HousekeepStoreAccount.h index 6c8f251d..5c2a9885 100644 --- a/bin/bbstored/HousekeepStoreAccount.h +++ b/bin/bbstored/HousekeepStoreAccount.h @@ -42,6 +42,8 @@ private: bool ScanDirectory(int64_t ObjectID); bool DeleteFiles(); bool DeleteEmptyDirectories(); + void DeleteEmptyDirectory(int64_t dirId, + std::vector<int64_t>& rToExamine); void DeleteFile(int64_t InDirectory, int64_t ObjectID, BackupStoreDirectory &rDirectory, const std::string &rDirectoryFilename, int64_t OriginalDirSizeInBlocks); private: @@ -52,6 +54,7 @@ private: int64_t mSizeInBlocks; int32_t mMarkNumber; int32_t mVersionAgeWithinMark; // 0 == current, 1 latest old version, etc + bool mIsFlagDeleted; // false for files flagged "Old" } DelEn; struct DelEnCompare diff --git a/bin/bbstored/backupprotocol.txt b/bin/bbstored/backupprotocol.txt index 62d837ff..3eca514a 100644 --- a/bin/bbstored/backupprotocol.txt +++ b/bin/bbstored/backupprotocol.txt @@ -4,7 +4,7 @@ Name Backup IdentString Box-Backup:v=C -ServerContextClass BackupContext BackupContext.h +ServerContextClass BackupStoreContext BackupStoreContext.h ClientType Filename BackupStoreFilenameClear BackupStoreFilenameClear.h ServerType Filename BackupStoreFilename BackupStoreFilename.h @@ -204,6 +204,12 @@ GetBlockIndexByName 35 Command(Success) # stream of the block index follows the reply if found ID != 0 +UndeleteFile 36 Command(Success) + int64 InDirectory + int64 ObjectID + # will return 0 if the object couldn't be found in the specified directory + + # ------------------------------------------------------------------------------------- # Information commands # ------------------------------------------------------------------------------------- diff --git a/bin/bbstored/bbstored-config.in b/bin/bbstored/bbstored-config.in index 33cfb39a..5ad96d50 100755 --- a/bin/bbstored/bbstored-config.in +++ b/bin/bbstored/bbstored-config.in @@ -196,7 +196,7 @@ TimeBetweenHousekeeping = 900 Server { - PidFile = @localstatedir_expanded@/bbstored.pid + PidFile = @localstatedir_expanded@/run/bbstored.pid User = $username ListenAddresses = inet:$server CertificateFile = $certificate @@ -234,7 +234,7 @@ What you need to do now... 4) Create accounts with bbstoreaccounts 5) Start the backup store daemon with the command - @bindir_expanded@/bbstored$daemon_args + @sbindir_expanded@/bbstored$daemon_args in /etc/rc.local, or your local equivalent. =================================================================== diff --git a/bin/bbstored/bbstored.cpp b/bin/bbstored/bbstored.cpp index 54858dd4..21a9e5f1 100644 --- a/bin/bbstored/bbstored.cpp +++ b/bin/bbstored/bbstored.cpp @@ -18,7 +18,7 @@ int main(int argc, const char *argv[]) { MAINHELPER_START - Logging::SetProgramName("Box Backup (bbstored)"); + Logging::SetProgramName("bbstored"); Logging::ToConsole(true); Logging::ToSyslog (true); |