diff options
author | Chris Wilson <chris+github@qwirx.com> | 2007-09-19 21:58:24 +0000 |
---|---|---|
committer | Chris Wilson <chris+github@qwirx.com> | 2007-09-19 21:58:24 +0000 |
commit | 867fbf737760a7764f6095a1b9b7554047c47eb3 (patch) | |
tree | 1bc17e74cfef9352857f62b3ee842fa95a8547a4 /bin | |
parent | 41f3230a75e965254ab47e3609f68c8634266d37 (diff) | |
parent | 2ff87143551e6882c90ceaba940a34779b922882 (diff) |
Replace trunk with chris/merge.
Diffstat (limited to 'bin')
27 files changed, 2246 insertions, 1018 deletions
diff --git a/bin/bbackupctl/bbackupctl.cpp b/bin/bbackupctl/bbackupctl.cpp index 30b5def8..edbc252f 100644 --- a/bin/bbackupctl/bbackupctl.cpp +++ b/bin/bbackupctl/bbackupctl.cpp @@ -56,17 +56,23 @@ int main(int argc, const char *argv[]) { int returnCode = 0; -#if defined WIN32 && ! defined NDEBUG - ::openlog("Box Backup (bbackupctl)", 0, 0); -#endif - MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupctl.memleaks", "bbackupctl") MAINHELPER_START +#if defined WIN32 && ! defined NDEBUG + ::openlog("Box Backup (bbackupctl)", 0, 0); +#endif + // Filename for configuration file? - const char *configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG; + std::string configFilename; + + #ifdef WIN32 + configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; + #else + configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG; + #endif // Quiet? bool quiet = false; @@ -103,12 +109,16 @@ int main(int argc, const char *argv[]) } // Read in the configuration file - if(!quiet) printf("Using configuration file %s\n", configFilename); + if(!quiet) BOX_NOTICE("Using configuration file " << configFilename); + std::string errs; - std::auto_ptr<Configuration> config(Configuration::LoadAndVerify(configFilename, &BackupDaemonConfigVerify, errs)); + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + (configFilename, &BackupDaemonConfigVerify, errs)); + if(config.get() == 0 || !errs.empty()) { - printf("Invalid configuration file:\n%s", errs.c_str()); + BOX_ERROR("Invalid configuration file: " << errs); return 1; } // Easier coding @@ -117,10 +127,10 @@ int main(int argc, const char *argv[]) // Check there's a socket defined in the config file if(!conf.KeyExists("CommandSocket")) { - printf("Daemon isn't using a control socket, " + BOX_ERROR("Daemon isn't using a control socket, " "could not execute command.\n" "Add a CommandSocket declaration to the " - "bbackupd.conf file.\n"); + "bbackupd.conf file."); return 1; } @@ -142,18 +152,14 @@ int main(int argc, const char *argv[]) } catch(...) { - printf("Failed to connect to daemon control socket.\n" + BOX_ERROR("Failed to connect to daemon control socket.\n" "Possible causes:\n" " * Daemon not running\n" " * Daemon busy syncing with store server\n" " * Another bbackupctl process is communicating with the daemon\n" - " * Daemon is waiting to recover from an error\n" + " * Daemon is waiting to recover from an error" ); -#if defined WIN32 && ! defined NDEBUG - syslog(LOG_ERR,"Failed to connect to the command socket"); -#endif - return 1; } @@ -164,29 +170,16 @@ int main(int argc, const char *argv[]) std::string configSummary; if(!getLine.GetLine(configSummary)) { -#if defined WIN32 && ! defined NDEBUG - syslog(LOG_ERR, "Failed to receive configuration summary " + BOX_ERROR("Failed to receive configuration summary " "from daemon"); -#else - printf("Failed to receive configuration summary from daemon\n"); -#endif - return 1; } // Was the connection rejected by the server? if(getLine.IsEOF()) { -#if defined WIN32 && ! defined NDEBUG - syslog(LOG_ERR, "Server rejected the connection. " - "Are you running bbackupctl as the same user " - "as the daemon?"); -#else - printf("Server rejected the connection. " - "Are you running bbackupctl as the same user " - "as the daemon?\n"); -#endif - + BOX_ERROR("Server rejected the connection. Are you running " + "bbackupctl as the same user as the daemon?"); return 1; } @@ -195,29 +188,25 @@ int main(int argc, const char *argv[]) if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", &autoBackup, &updateStoreInterval, &minimumFileAge, &maxUploadWait) != 4) { - printf("Config summary didn't decode\n"); + BOX_ERROR("Config summary didn't decode."); return 1; } // Print summary? if(!quiet) { - printf("Daemon configuration summary:\n" - " AutomaticBackup = %s\n" - " UpdateStoreInterval = %d seconds\n" - " MinimumFileAge = %d seconds\n" - " MaxUploadWait = %d seconds\n", - autoBackup?"true":"false", updateStoreInterval, - minimumFileAge, maxUploadWait); + BOX_INFO("Daemon configuration summary:\n" + " AutomaticBackup = " << + (autoBackup?"true":"false") << "\n" + " UpdateStoreInterval = " << updateStoreInterval << + " seconds\n" + " MinimumFileAge = " << minimumFileAge << " seconds\n" + " MaxUploadWait = " << maxUploadWait << " seconds\n"); } std::string stateLine; if(!getLine.GetLine(stateLine) || getLine.IsEOF()) { -#if defined WIN32 && ! defined NDEBUG - syslog(LOG_ERR, "Failed to receive state line from daemon"); -#else - printf("Failed to receive state line from daemon\n"); -#endif + BOX_ERROR("Failed to receive state line from daemon"); return 1; } @@ -225,7 +214,7 @@ int main(int argc, const char *argv[]) int currentState; if(::sscanf(stateLine.c_str(), "state %d", ¤tState) != 1) { - printf("State line didn't decode\n"); + BOX_ERROR("Received invalid state line from daemon"); return 1; } @@ -255,8 +244,8 @@ int main(int argc, const char *argv[]) if(!autoBackup) { - printf("ERROR: Daemon is not in automatic mode -- " - "sync will never start!\n"); + BOX_ERROR("Daemon is not in automatic mode, " + "sync will never start!"); return 1; } @@ -272,8 +261,8 @@ int main(int argc, const char *argv[]) if (currentState != 0) { - printf("Waiting for current sync/error state " - "to finish...\n"); + BOX_INFO("Waiting for current sync/error state " + "to finish..."); } } break; @@ -316,14 +305,14 @@ int main(int argc, const char *argv[]) { if(line == "start-sync") { - if (!quiet) printf("Sync started...\n"); + if (!quiet) BOX_INFO("Sync started..."); syncIsRunning = true; } else if(line == "finish-sync") { if (syncIsRunning) { - if (!quiet) printf("Sync finished.\n"); + if (!quiet) BOX_INFO("Sync finished.\n"); // Send a quit command to finish nicely connection.Write("quit\n", 5); @@ -332,7 +321,7 @@ int main(int argc, const char *argv[]) } else { - if (!quiet) printf("Previous sync finished.\n"); + if (!quiet) BOX_INFO("Previous sync finished."); } // daemon must still be busy } @@ -346,13 +335,13 @@ int main(int argc, const char *argv[]) { if(!quiet) { - printf("Succeeded.\n"); + BOX_INFO("Succeeded.\n"); } finished = true; } else if(line == "error") { - printf("ERROR. (Check command spelling)\n"); + BOX_ERROR("Check command spelling"); returnCode = 1; finished = true; } diff --git a/bin/bbackupd/BackupClientContext.cpp b/bin/bbackupd/BackupClientContext.cpp index 4b84fecb..4b4efd90 100644 --- a/bin/bbackupd/BackupClientContext.cpp +++ b/bin/bbackupd/BackupClientContext.cpp @@ -9,12 +9,10 @@ #include "Box.h" -#ifdef HAVE_SYSLOG_H - #include <syslog.h> -#endif #ifdef HAVE_SIGNAL_H #include <signal.h> #endif + #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif @@ -29,19 +27,28 @@ #include "BackupDaemon.h" #include "autogen_BackupProtocolClient.h" #include "BackupStoreFile.h" +#include "Logging.h" #include "MemLeakFindOn.h" // -------------------------------------------------------------------------- // // Function -// Name: BackupClientContext::BackupClientContext(BackupDaemon &, TLSContext &, const std::string &, int32_t, bool) +// Name: BackupClientContext::BackupClientContext(BackupDaemon &, TLSContext &, const std::string &, int32_t, bool, bool, std::string) // Purpose: Constructor // Created: 2003/10/08 // // -------------------------------------------------------------------------- -BackupClientContext::BackupClientContext(BackupDaemon &rDaemon, TLSContext &rTLSContext, const std::string &rHostname, - int32_t AccountNumber, bool ExtendedLogging) +BackupClientContext::BackupClientContext +( + BackupDaemon &rDaemon, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile +) : mrDaemon(rDaemon), mrTLSContext(rTLSContext), mHostname(rHostname), @@ -49,6 +56,9 @@ BackupClientContext::BackupClientContext(BackupDaemon &rDaemon, TLSContext &rTLS mpSocket(0), mpConnection(0), mExtendedLogging(ExtendedLogging), + mExtendedLogToFile(ExtendedLogToFile), + mExtendedLogFile(ExtendedLogFile), + mpExtendedLogFileHandle(NULL), mClientStoreMarker(ClientStoreMarker_NotKnown), mpDeleteList(0), mpCurrentIDMap(0), @@ -56,8 +66,8 @@ BackupClientContext::BackupClientContext(BackupDaemon &rDaemon, TLSContext &rTLS mStorageLimitExceeded(false), mpExcludeFiles(0), mpExcludeDirs(0), - mbIsManaged(false), - mTimeMgmtEpoch(0) + mKeepAliveTimer(0), + mbIsManaged(false) { } @@ -115,7 +125,8 @@ BackupProtocolClient &BackupClientContext::GetConnection() } // Log intention - ::syslog(LOG_INFO, "Opening connection to server %s...", mHostname.c_str()); + BOX_INFO("Opening connection to server '" << + mHostname << "'..."); // Connect! mpSocket->Open(mrTLSContext, Socket::TypeINET, mHostname.c_str(), BOX_PORT_BBSTORED); @@ -126,6 +137,24 @@ BackupProtocolClient &BackupClientContext::GetConnection() // Set logging option mpConnection->SetLogToSysLog(mExtendedLogging); + if (mExtendedLogToFile) + { + ASSERT(mpExtendedLogFileHandle == NULL); + + mpExtendedLogFileHandle = fopen( + mExtendedLogFile.c_str(), "a+"); + + if (!mpExtendedLogFileHandle) + { + BOX_ERROR("Failed to open extended log " + "file: " << strerror(errno)); + } + else + { + mpConnection->SetLogToFile(mpExtendedLogFileHandle); + } + } + // Handshake mpConnection->Handshake(); @@ -164,19 +193,16 @@ BackupProtocolClient &BackupClientContext::GetConnection() } // Log success - ::syslog(LOG_INFO, "Connection made, login successful"); + BOX_INFO("Connection made, login successful"); // Check to see if there is any space available on the server - int64_t softLimit = loginConf->GetBlocksSoftLimit(); - int64_t hardLimit = loginConf->GetBlocksHardLimit(); - // Threshold for uploading new stuff - int64_t stopUploadThreshold = softLimit + ((hardLimit - softLimit) / 3); - if(loginConf->GetBlocksUsed() > stopUploadThreshold) + if(loginConf->GetBlocksUsed() >= loginConf->GetBlocksHardLimit()) { // no -- flag so only things like deletions happen mStorageLimitExceeded = true; // Log - ::syslog(LOG_WARNING, "Exceeded storage limits on server -- not uploading changes to files"); + BOX_WARNING("Exceeded storage hard-limit on server, " + "not uploading changes to files"); } } catch(...) @@ -256,6 +282,12 @@ void BackupClientContext::CloseAnyOpenConnection() delete mpDeleteList; mpDeleteList = 0; } + + if (mpExtendedLogFileHandle != NULL) + { + fclose(mpExtendedLogFileHandle); + mpExtendedLogFileHandle = NULL; + } } @@ -303,8 +335,8 @@ BackupClientDeleteList &BackupClientContext::GetDeleteList() // -------------------------------------------------------------------------- // // Function -// Name: -// Purpose: +// Name: BackupClientContext::PerformDeletions() +// Purpose: Perform any pending file deletions. // Created: 10/11/03 // // -------------------------------------------------------------------------- @@ -461,35 +493,18 @@ bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirec return true; } - -// maximum time to spend diffing -static int sMaximumDiffTime = 600; -// maximum time of SSL inactivity (keep-alive interval) -static int sKeepAliveTime = 0; - void BackupClientContext::SetMaximumDiffingTime(int iSeconds) { - sMaximumDiffTime = iSeconds < 0 ? 0 : iSeconds; - TRACE1("Set maximum diffing time to %d seconds\n", sMaximumDiffTime); + mMaximumDiffingTime = iSeconds < 0 ? 0 : iSeconds; + BOX_TRACE("Set maximum diffing time to " << mMaximumDiffingTime << + " seconds"); } void BackupClientContext::SetKeepAliveTime(int iSeconds) { - sKeepAliveTime = iSeconds < 0 ? 0 : iSeconds; - TRACE1("Set keep-alive time to %d seconds\n", sKeepAliveTime); -} - -// -------------------------------------------------------------------------- -// -// Function -// Name: static TimerSigHandler(int) -// Purpose: Signal handler -// Created: 19/3/04 -// -// -------------------------------------------------------------------------- -static void TimerSigHandler(int iUnused) -{ - BackupStoreFile::DiffTimerExpired(); + mKeepAliveTime = iSeconds < 0 ? 0 : iSeconds; + BOX_TRACE("Set keep-alive time to " << mKeepAliveTime << " seconds"); + mKeepAliveTimer = Timer(mKeepAliveTime); } // -------------------------------------------------------------------------- @@ -502,59 +517,8 @@ static void TimerSigHandler(int iUnused) // -------------------------------------------------------------------------- void BackupClientContext::ManageDiffProcess() { - if (mbIsManaged || !mpConnection) - return; - - ASSERT(mTimeMgmtEpoch == 0); - -#ifdef PLATFORM_CYGWIN - ::signal(SIGALRM, TimerSigHandler); -#elif defined WIN32 - // no support for SIGVTALRM - SetTimerHandler(TimerSigHandler); -#else - ::signal(SIGVTALRM, TimerSigHandler); -#endif // PLATFORM_CYGWIN - - struct itimerval timeout; - memset(&timeout, 0, sizeof(timeout)); - - // - // - // - if (sMaximumDiffTime <= 0 && sKeepAliveTime <= 0) - { - TRACE0("Diff control not requested - letting things run wild\n"); - return; - } - else if (sMaximumDiffTime > 0 && sKeepAliveTime > 0) - { - timeout.it_value.tv_sec = sKeepAliveTime < sMaximumDiffTime ? sKeepAliveTime : sMaximumDiffTime; - timeout.it_interval.tv_sec = sKeepAliveTime < sMaximumDiffTime ? sKeepAliveTime : sMaximumDiffTime; - } - else - { - timeout.it_value.tv_sec = sKeepAliveTime > 0 ? sKeepAliveTime : sMaximumDiffTime; - timeout.it_interval.tv_sec = sKeepAliveTime > 0 ? sKeepAliveTime : sMaximumDiffTime; - } - - // avoid race - mTimeMgmtEpoch = time(NULL); - -#ifdef PLATFORM_CYGWIN - if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) -#else - if(::setitimer(ITIMER_VIRTUAL, &timeout, NULL) != 0) -#endif // PLATFORM_CYGWIN - { - mTimeMgmtEpoch = 0; - - TRACE0("WARNING: couldn't set file diff control timeout\n"); - THROW_EXCEPTION(BackupStoreException, Internal) - } - + ASSERT(!mbIsManaged); mbIsManaged = true; - TRACE0("Initiated timer for file diff control\n"); } // -------------------------------------------------------------------------- @@ -567,33 +531,16 @@ void BackupClientContext::ManageDiffProcess() // -------------------------------------------------------------------------- void BackupClientContext::UnManageDiffProcess() { - if (!mbIsManaged /* don't test for active connection, just do it */) - return; - - struct itimerval timeout; - memset(&timeout, 0, sizeof(timeout)); - -#ifdef PLATFORM_CYGWIN - if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) -#else - if(::setitimer(ITIMER_VIRTUAL, &timeout, NULL) != 0) -#endif // PLATFORM_CYGWIN - { - TRACE0("WARNING: couldn't clear file diff control timeout\n"); - THROW_EXCEPTION(BackupStoreException, Internal) - } - + // ASSERT(mbIsManaged); mbIsManaged = false; - mTimeMgmtEpoch = 0; - - TRACE0("Suspended timer for file diff control\n"); } // -------------------------------------------------------------------------- // // Function // Name: BackupClientContext::DoKeepAlive() -// Purpose: Does something inconsequential over the SSL link to keep it up +// Purpose: Check whether it's time to send a KeepAlive +// message over the SSL link, and if so, send it. // Created: 04/19/2005 // // -------------------------------------------------------------------------- @@ -601,33 +548,26 @@ void BackupClientContext::DoKeepAlive() { if (!mpConnection) { - ::syslog(LOG_ERR, "DoKeepAlive() called with no connection!"); + return; + } + + if (mKeepAliveTime == 0) + { return; } + if (!mKeepAliveTimer.HasExpired()) + { + return; + } + + BOX_TRACE("KeepAliveTime reached, sending keep-alive message"); mpConnection->QueryGetIsAlive(); -} - -// -------------------------------------------------------------------------- -// -// Function -// Name: BackupClientContext::GetTimeMgmtEpoch() -// Purpose: Returns the unix time when the diff was started, or zero -// if the diff process is unmanaged. -// Created: 04/19/2005 -// -// -------------------------------------------------------------------------- -time_t BackupClientContext::GetTimeMgmtEpoch() -{ - return mTimeMgmtEpoch; + + mKeepAliveTimer = Timer(mKeepAliveTime); } int BackupClientContext::GetMaximumDiffingTime() { - return sMaximumDiffTime; -} - -int BackupClientContext::GetKeepaliveTime() -{ - return sKeepAliveTime; + return mMaximumDiffingTime; } diff --git a/bin/bbackupd/BackupClientContext.h b/bin/bbackupd/BackupClientContext.h index a0cf6e1f..152d8556 100644 --- a/bin/bbackupd/BackupClientContext.h +++ b/bin/bbackupd/BackupClientContext.h @@ -14,6 +14,7 @@ #include "BackupClientDeleteList.h" #include "BackupStoreFile.h" #include "ExcludeList.h" +#include "Timer.h" class TLSContext; class BackupProtocolClient; @@ -35,8 +36,16 @@ class BackupStoreFilenameClear; class BackupClientContext : public DiffTimer { public: - BackupClientContext(BackupDaemon &rDaemon, TLSContext &rTLSContext, const std::string &rHostname, - int32_t AccountNumber, bool ExtendedLogging); + BackupClientContext + ( + BackupDaemon &rDaemon, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile + ); virtual ~BackupClientContext(); private: BackupClientContext(const BackupClientContext &); @@ -143,7 +152,7 @@ public: // Created: 04/19/2005 // // -------------------------------------------------------------------------- - static void SetMaximumDiffingTime(int iSeconds); + void SetMaximumDiffingTime(int iSeconds); // -------------------------------------------------------------------------- // @@ -153,7 +162,7 @@ public: // Created: 04/19/2005 // // -------------------------------------------------------------------------- - static void SetKeepAliveTime(int iSeconds); + void SetKeepAliveTime(int iSeconds); // -------------------------------------------------------------------------- // @@ -175,19 +184,18 @@ public: // -------------------------------------------------------------------------- void UnManageDiffProcess(); - // -------------------------------------------------------------------------- + // ------------------------------------------------------------------- // // Function // Name: BackupClientContext::DoKeepAlive() - // Purpose: Does something inconsequential over the SSL link to - // keep it up, implements DiffTimer interface + // Purpose: Check whether it's time to send a KeepAlive + // message over the SSL link, and if so, send it. // Created: 04/19/2005 // - // -------------------------------------------------------------------------- + // ------------------------------------------------------------------- virtual void DoKeepAlive(); - virtual time_t GetTimeMgmtEpoch(); virtual int GetMaximumDiffingTime(); - virtual int GetKeepaliveTime(); + virtual bool IsManaged() { return mbIsManaged; } private: BackupDaemon &mrDaemon; @@ -197,6 +205,9 @@ private: SocketStreamTLS *mpSocket; BackupProtocolClient *mpConnection; bool mExtendedLogging; + bool mExtendedLogToFile; + std::string mExtendedLogFile; + FILE* mpExtendedLogFileHandle; int64_t mClientStoreMarker; BackupClientDeleteList *mpDeleteList; const BackupClientInodeToIDMap *mpCurrentIDMap; @@ -204,11 +215,10 @@ private: bool mStorageLimitExceeded; ExcludeList *mpExcludeFiles; ExcludeList *mpExcludeDirs; - + Timer mKeepAliveTimer; bool mbIsManaged; - // unix time when diff was started - time_t mTimeMgmtEpoch; + int mKeepAliveTime; + int mMaximumDiffingTime; }; - #endif // BACKUPCLIENTCONTEXT__H diff --git a/bin/bbackupd/BackupClientDirectoryRecord.cpp b/bin/bbackupd/BackupClientDirectoryRecord.cpp index 1db6cbe5..e5a57bc1 100644 --- a/bin/bbackupd/BackupClientDirectoryRecord.cpp +++ b/bin/bbackupd/BackupClientDirectoryRecord.cpp @@ -29,6 +29,9 @@ #include "BackupDaemon.h" #include "BackupStoreException.h" #include "Archive.h" +#include "PathUtils.h" +#include "Logging.h" +#include "ReadLoggingStream.h" #include "MemLeakFindOn.h" @@ -97,33 +100,6 @@ void BackupClientDirectoryRecord::DeleteSubDirectories() // -------------------------------------------------------------------------- // // Function -// Name: MakeFullPath(const std::string& rDir, const std::string& rFile) -// Purpose: Combine directory and file name -// Created: 2006/08/10 -// -// -------------------------------------------------------------------------- -static std::string MakeFullPath(const std::string& rDir, - const std::string& rFile) -{ - std::string result; - - if (rDir.size() > 0 && - rDir[rDir.size()-1] == DIRECTORY_SEPARATOR_ASCHAR) - { - result = rDir + rFile; - } - else - { - result = rDir + DIRECTORY_SEPARATOR + rFile; - } - - return result; -} - - -// -------------------------------------------------------------------------- -// -// Function // Name: BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::SyncParams &, int64_t, const std::string &, bool) // Purpose: Syncronise, recusively, a local directory with the server. // Created: 2003/10/08 @@ -164,8 +140,8 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn { // 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. - TRACE1("Stat failed for '%s' (directory)\n", - rLocalPath.c_str()); + rParams.GetProgressNotifier().NotifyDirStatFailed( + this, rLocalPath, strerror(errno)); return; } // Store inode number in map so directories are tracked in case they're renamed @@ -193,15 +169,48 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn std::vector<std::string> dirs; std::vector<std::string> files; bool downloadDirectoryRecordBecauseOfFutureFiles = false; + + struct stat dir_st; + if(::lstat(rLocalPath.c_str(), &dir_st) != 0) + { + // Report the error (logs and + // eventual email to administrator) + rParams.GetProgressNotifier().NotifyFileStatFailed(this, + rLocalPath, strerror(errno)); + + // FIXME move to NotifyFileStatFailed() + SetErrorWhenReadingFilesystemObject(rParams, + rLocalPath.c_str()); + + // This shouldn't happen, so we'd better not continue + THROW_EXCEPTION(CommonException, OSFileError) + } + // BLOCK { // read the contents... DIR *dirHandle = 0; try { + rParams.GetProgressNotifier().NotifyScanDirectory( + this, rLocalPath); + dirHandle = ::opendir(rLocalPath.c_str()); if(dirHandle == 0) { + // Report the error (logs and + // eventual email to administrator) + if (errno == EACCES) + { + rParams.GetProgressNotifier().NotifyDirListFailed( + this, rLocalPath, "Access denied"); + } + else + { + rParams.GetProgressNotifier().NotifyDirListFailed(this, + rLocalPath, strerror(errno)); + } + // Report the error (logs and eventual email to administrator) SetErrorWhenReadingFilesystemObject(rParams, rLocalPath.c_str()); // Ignore this directory for now. @@ -223,6 +232,8 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn 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 @@ -252,6 +263,10 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn { // Report the error (logs and // eventual email to administrator) + rParams.GetProgressNotifier().NotifyFileStatFailed(this, + filename, strerror(errno)); + + // FIXME move to NotifyFileStatFailed() SetErrorWhenReadingFilesystemObject( rParams, filename.c_str()); @@ -259,6 +274,14 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn continue; } + if(st.st_dev != dir_st.st_dev) + { + rParams.GetProgressNotifier() + .NotifyMountPointSkipped(this, + filename); + continue; + } + int type = st.st_mode & S_IFMT; #endif @@ -269,6 +292,11 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Exclude it? if(rParams.mrContext.ExcludeFile(filename)) { + rParams.GetProgressNotifier() + .NotifyFileExcluded( + this, + filename); + // Next item! continue; } @@ -283,6 +311,11 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Exclude it? if(rParams.mrContext.ExcludeDir(filename)) { + rParams.GetProgressNotifier() + .NotifyDirExcluded( + this, + filename); + // Next item! continue; } @@ -292,13 +325,22 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn } else { - #ifdef WIN32 - ::syslog(LOG_ERR, "Unknown file type: " - "%d (%s)", type, - filename.c_str()); - #endif - SetErrorWhenReadingFilesystemObject( - rParams, filename.c_str()); + if(rParams.mrContext.ExcludeFile(filename)) + { + rParams.GetProgressNotifier() + .NotifyFileExcluded( + this, + filename); + } + else + { + rParams.GetProgressNotifier() + .NotifyUnsupportedFileType( + this, filename); + SetErrorWhenReadingFilesystemObject( + rParams, filename.c_str()); + } + continue; } @@ -310,6 +352,11 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // but now we need the information. if(::lstat(filename.c_str(), &st) != 0) { + rParams.GetProgressNotifier() + .NotifyFileStatFailed(this, + filename, + strerror(errno)); + // Report the error (logs and // eventual email to administrator) SetErrorWhenReadingFilesystemObject( @@ -318,6 +365,14 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Ignore this entry for now. continue; } + + if(st.st_dev != dir_st.st_dev) + { + rParams.GetProgressNotifier() + .NotifyMountPointSkipped(this, + filename); + continue; + } #endif checksum_info.mModificationTime = FileModificationTime(st); @@ -335,8 +390,8 @@ void BackupClientDirectoryRecord::SyncDirectory(BackupClientDirectoryRecord::Syn // Log that this has happened if(!rParams.mHaveLoggedWarningAboutFutureFileTimes) { - ::syslog(LOG_ERR, "Some files have modification times excessively in the future. Check clock syncronisation.\n"); - ::syslog(LOG_ERR, "Example file (only one shown) : %s\n", filename.c_str()); + rParams.GetProgressNotifier().NotifyFileModifiedInFuture( + this, filename); rParams.mHaveLoggedWarningAboutFutureFileTimes = true; } } @@ -574,6 +629,9 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP for(std::vector<std::string>::const_iterator f = rFiles.begin(); f != rFiles.end(); ++f) { + // Send keep-alive message if needed + rParams.mrContext.DoKeepAlive(); + // Filename of this file std::string filename(MakeFullPath(rLocalPath, *f)); @@ -589,7 +647,16 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP struct stat st; if(::lstat(filename.c_str(), &st) != 0) { - THROW_EXCEPTION(CommonException, OSFileError) + rParams.GetProgressNotifier().NotifyFileStatFailed(this, + filename, strerror(errno)); + + // Report the error (logs and + // eventual email to administrator) + SetErrorWhenReadingFilesystemObject(rParams, + filename.c_str()); + + // Ignore this entry for now. + continue; } // Extract required data @@ -728,10 +795,14 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP if (pDirOnStore != 0 && en == 0) { doUpload = true; + BOX_TRACE(filename << ": will upload " + "(not on server)"); } else if (modTime >= rParams.mSyncPeriodStart) { doUpload = true; + BOX_TRACE(filename << ": will upload " + "(modified since last sync)"); } } @@ -748,6 +819,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP > rParams.mMaxUploadWait) { doUpload = true; + BOX_TRACE(filename << ": will upload " + "(continually modified)"); } // Then make sure that if files are added with a @@ -763,6 +836,8 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP en->GetModificationTime() != modTime) { doUpload = true; + BOX_TRACE(filename << ": will upload " + "(mod time changed)"); } // And just to catch really badly off clocks in @@ -773,9 +848,20 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP rParams.mUploadAfterThisTimeInTheFuture) { doUpload = true; + BOX_TRACE(filename << ": will upload " + "(mod time in the future)"); } } + if (!doUpload) + { + BOX_TRACE(filename << ": will not upload " + "(no reason to upload, mod time is " + << modTime << " versus sync period " + << rParams.mSyncPeriodStart << " to " + << rParams.mSyncPeriodEnd << ")"); + } + if (doUpload) { // Make sure we're connected -- must connect here so we know whether @@ -801,6 +887,9 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP { // Connection errors should just be passed on to the main handler, retries // would probably just cause more problems. + rParams.GetProgressNotifier() + .NotifyFileUploadException( + this, filename, e); throw; } catch(BoxException &e) @@ -809,8 +898,9 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP allUpdatedSuccessfully = false; // Log it. SetErrorWhenReadingFilesystemObject(rParams, filename.c_str()); - // Log error. - ::syslog(LOG_ERR, "Error code when uploading was (%d/%d), %s", e.GetType(), e.GetSubType(), e.what()); + rParams.GetProgressNotifier() + .NotifyFileUploadException( + this, filename, e); } // Update structures if the file was uploaded successfully. @@ -823,6 +913,11 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP } } } + else + { + rParams.GetProgressNotifier().NotifyFileSkippedServerFull(this, + filename); + } } else if(en != 0 && en->GetAttributesHash() != attributesHash) { @@ -904,6 +999,9 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP } } } + + rParams.GetProgressNotifier().NotifyFileSynchronised(this, + filename, fileSize); } // Erase contents of files to save space when recursing @@ -921,6 +1019,9 @@ bool BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncP for(std::vector<std::string>::const_iterator d = rDirs.begin(); d != rDirs.end(); ++d) { + // Send keep-alive message if needed + rParams.mrContext.DoKeepAlive(); + // Get the local filename std::string dirname(MakeFullPath(rLocalPath, *d)); @@ -1217,7 +1318,11 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn if(diffFromID != 0) { - // Found an old version -- get the index + // Found an old version + rParams.GetProgressNotifier().NotifyFileUploadingPatch(this, + rFilename); + + // Get the index std::auto_ptr<IOStream> blockIndexStream(connection.ReceiveStream()); // @@ -1253,9 +1358,13 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn if(doNormalUpload) { // below threshold or nothing to diff from, so upload whole + rParams.GetProgressNotifier().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)); + std::auto_ptr<IOStream> upload( + BackupStoreFile::EncodeFile(rFilename.c_str(), + mObjectID, rStoreFilename)); // Send to store std::auto_ptr<BackupProtocolClientSuccess> stored( @@ -1275,14 +1384,20 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn if(e.GetType() == ConnectionException::ExceptionType && e.GetSubType() == ConnectionException::Protocol_UnexpectedReply) { - // Check and see what error the protocol has -- as it might be an error... + // Check and see what error the protocol has, + // this is more useful to users than the exception. int type, subtype; - if(connection.GetLastError(type, subtype) - && type == BackupProtocolClientError::ErrorType - && subtype == BackupProtocolClientError::Err_StorageLimitExceeded) + if(connection.GetLastError(type, subtype)) { - // The hard limit was exceeded on the server, notify! - rParams.mrDaemon.NotifySysadmin(BackupDaemon::NotifyEvent_StoreFull); + if(type == BackupProtocolClientError::ErrorType + && subtype == BackupProtocolClientError::Err_StorageLimitExceeded) + { + // The hard limit was exceeded on the server, notify! + rParams.mrDaemon.NotifySysadmin(BackupDaemon::NotifyEvent_StoreFull); + } + rParams.GetProgressNotifier() + .NotifyFileUploadServerError( + this, rFilename, type, subtype); } } @@ -1290,6 +1405,8 @@ int64_t BackupClientDirectoryRecord::UploadFile(BackupClientDirectoryRecord::Syn throw; } + rParams.GetProgressNotifier().NotifyFileUploaded(this, rFilename, FileSize); + // Return the new object ID of this file return objID; } @@ -1309,8 +1426,11 @@ void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(BackupClie // Zero hash, so it gets synced properly next time round. ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); - // Log the error - ::syslog(LOG_ERR, "Backup object failed, error when reading %s", Filename); + // Log the error - already done by caller + /* + rParams.GetProgressNotifier().NotifyFileReadFailed(this, + Filename, strerror(errno)); + */ // Mark that an error occured in the parameters object rParams.mReadErrorsOnFilesystemObjects = true; @@ -1326,8 +1446,10 @@ void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(BackupClie // Created: 8/3/04 // // -------------------------------------------------------------------------- -BackupClientDirectoryRecord::SyncParams::SyncParams(BackupDaemon &rDaemon, BackupClientContext &rContext) - : mSyncPeriodStart(0), +BackupClientDirectoryRecord::SyncParams::SyncParams(BackupDaemon &rDaemon, + ProgressNotifier &rProgressNotifier, BackupClientContext &rContext) + : mrProgressNotifier(rProgressNotifier), + mSyncPeriodStart(0), mSyncPeriodEnd(0), mMaxUploadWait(0), mMaxFileTimeInFuture(99999999999999999LL), diff --git a/bin/bbackupd/BackupClientDirectoryRecord.h b/bin/bbackupd/BackupClientDirectoryRecord.h index b7b84984..9e4dda7a 100644 --- a/bin/bbackupd/BackupClientDirectoryRecord.h +++ b/bin/bbackupd/BackupClientDirectoryRecord.h @@ -25,6 +25,82 @@ 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 @@ -59,14 +135,18 @@ public: class SyncParams { public: - SyncParams(BackupDaemon &rDaemon, BackupClientContext &rContext); + SyncParams( + BackupDaemon &rDaemon, + ProgressNotifier &rProgressNotifier, + BackupClientContext &rContext); ~SyncParams(); private: // No copying SyncParams(const SyncParams&); SyncParams &operator=(const SyncParams&); + ProgressNotifier &mrProgressNotifier; + public: - // Data members are public, as accessors are not justified here box_time_t mSyncPeriodStart; box_time_t mSyncPeriodEnd; @@ -81,6 +161,11 @@ public: // Member variables modified by syncing process box_time_t mUploadAfterThisTimeInTheFuture; bool mHaveLoggedWarningAboutFutureFileTimes; + + ProgressNotifier& GetProgressNotifier() const + { + return mrProgressNotifier; + } }; void SyncDirectory(SyncParams &rParams, int64_t ContainingDirectoryID, const std::string &rLocalPath, diff --git a/bin/bbackupd/BackupDaemon.cpp b/bin/bbackupd/BackupDaemon.cpp index 7d508743..cfeb320e 100644 --- a/bin/bbackupd/BackupDaemon.cpp +++ b/bin/bbackupd/BackupDaemon.cpp @@ -18,9 +18,6 @@ #ifdef HAVE_SIGNAL_H #include <signal.h> #endif -#ifdef HAVE_SYSLOG_H - #include <syslog.h> -#endif #ifdef HAVE_SYS_PARAM_H #include <sys/param.h> #endif @@ -75,6 +72,9 @@ #include "IOStreamGetLine.h" #include "Conversion.h" #include "Archive.h" +#include "Timer.h" +#include "Logging.h" +#include "autogen_ClientException.h" #include "MemLeakFindOn.h" @@ -114,18 +114,42 @@ unsigned int WINAPI HelperThread(LPVOID lpParam) BackupDaemon::BackupDaemon() : mState(BackupDaemon::State_Initialising), mpCommandSocketInfo(0), - mDeleteUnusedRootDirEntriesAfter(0) + mDeleteUnusedRootDirEntriesAfter(0), + mLogAllFileAccess(false) { // Only ever one instance of a daemon SSLLib::Initialise(); - // Initialise notifcation sent status - for(int l = 0; l <= NotifyEvent__MAX; ++l) + // 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); + // Create a thread to handle the named pipe HANDLE hThread; unsigned int dwThreadId; @@ -223,7 +247,7 @@ void BackupDaemon::SetupInInitialProcess() // Print a warning on this platform if the CommandSocket is used. if(GetConfiguration().KeyExists("CommandSocket")) { - printf( + 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" @@ -276,29 +300,21 @@ void BackupDaemon::RunHelperThread(void) } catch (BoxException &e) { - ::syslog(LOG_ERR, "Failed to open command socket: %s", + BOX_ERROR("Failed to open command socket: " << e.what()); SetTerminateWanted(); break; // this is fatal to listening thread } - catch (...) - { - ::syslog(LOG_ERR, "Failed to open command socket: " - "unknown error"); - SetTerminateWanted(); - break; // this is fatal to listening thread - } - } catch(std::exception &e) { - ::syslog(LOG_ERR, "Failed to open command socket: " - "%s", e.what()); + BOX_ERROR("Failed to open command socket: " << + e.what()); SetTerminateWanted(); break; // this is fatal to listening thread } catch(...) { - ::syslog(LOG_ERR, "Failed to open command socket: " + BOX_ERROR("Failed to open command socket: " "unknown error"); SetTerminateWanted(); break; // this is fatal to listening thread @@ -311,7 +327,7 @@ void BackupDaemon::RunHelperThread(void) // This next section comes from Ben's original function // Log - ::syslog(LOG_INFO, "Connection from command socket"); + BOX_INFO("Connection from command socket"); // Send a header line summarising the configuration // and current state @@ -328,15 +344,73 @@ void BackupDaemon::RunHelperThread(void) 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() && - readLine.GetLine(command) && - !IsTerminateWanted()) + while (rSocket.IsConnected() && !IsTerminateWanted()) { - TRACE1("Received command '%s' over " - "command socket\n", command.c_str()); + 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; @@ -355,6 +429,7 @@ void BackupDaemon::RunHelperThread(void) this->mDoSyncFlagOut = true; this->mSyncIsForcedOut = false; sendOK = true; + SetEvent(mhCommandReceivedEvent); } else if(command == "force-sync") { @@ -362,22 +437,27 @@ void BackupDaemon::RunHelperThread(void) 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 { - ::syslog(LOG_ERR, "Received unknown command '%s' from client", command.c_str()); + BOX_ERROR("Received unknown command " + "'" << command << "' " + "from client"); sendResponse = true; sendOK = false; } @@ -394,27 +474,28 @@ void BackupDaemon::RunHelperThread(void) { break; } - - this->mReceivedCommandConn = true; } rSocket.Close(); } catch(BoxException &e) { - ::syslog(LOG_ERR, "Communication error with " - "control client: %s", e.what()); + BOX_ERROR("Communication error with " + "control client: " << e.what()); } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error in command socket " - "thread: %s", e.what()); + BOX_ERROR("Internal error in command socket " + "thread: " << e.what()); } catch(...) { - ::syslog(LOG_ERR, "Communication error with control client"); + BOX_ERROR("Communication error with control client"); } } + + CloseHandle(mhCommandReceivedEvent); + CloseHandle(mhMessageToSendEvent); } #endif @@ -428,21 +509,19 @@ void BackupDaemon::RunHelperThread(void) // -------------------------------------------------------------------------- void BackupDaemon::Run() { + // initialise global timer mechanism + Timers::Init(); + #ifdef WIN32 - // init our own timer for file diff timeouts - InitTimer(); - try { Run2(); } catch(...) { - FiniTimer(); + Timers::Cleanup(); throw; } - - FiniTimer(); #else // ! WIN32 // Ignore SIGPIPE (so that if a command connection is broken, the daemon doesn't terminate) ::signal(SIGPIPE, SIG_IGN); @@ -473,19 +552,20 @@ void BackupDaemon::Run() } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error while " + BOX_WARNING("Internal error while " "closing command socket after " - "another exception: %s", e.what()); + "another exception: " << e.what()); } catch(...) { - ::syslog(LOG_WARNING, - "Error closing command socket " + BOX_WARNING("Error closing command socket " "after exception, ignored."); } mpCommandSocketInfo = 0; } + Timers::Cleanup(); + throw; } @@ -496,6 +576,8 @@ void BackupDaemon::Run() mpCommandSocketInfo = 0; } #endif + + Timers::Cleanup(); } // -------------------------------------------------------------------------- @@ -519,18 +601,20 @@ void BackupDaemon::Run2() // Set up the keys for various things BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); + // Setup various timings + int maximumDiffingTime = 600; + int keepAliveTime = 60; + // max diffing time, keep-alive time if(conf.KeyExists("MaximumDiffingTime")) { - BackupClientContext::SetMaximumDiffingTime(conf.GetKeyValueInt("MaximumDiffingTime")); + maximumDiffingTime = conf.GetKeyValueInt("MaximumDiffingTime"); } if(conf.KeyExists("KeepAliveTime")) { - BackupClientContext::SetKeepAliveTime(conf.GetKeyValueInt("KeepAliveTime")); + keepAliveTime = conf.GetKeyValueInt("KeepAliveTime"); } - // Setup various timings - // How often to connect to the store (approximate) box_time_t updateStoreInterval = SecondsToBoxTime(conf.GetKeyValueInt("UpdateStoreInterval")); @@ -580,38 +664,64 @@ void BackupDaemon::Run2() box_time_t currentTime; do { - // Need to check the stop run thing here too, so this loop isn't run if we should be stopping + // 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); + // 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); + } - // Only do the delay if there is a delay required + // 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 + // 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 handling connections with it - WaitOnCommandSocket(requiredDelay, doSync, doSyncForcedByCommand); + // 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()); } - // Time of sync start, and if it's time for another sync (and we're doing automatic syncs), set the flag + // 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) { @@ -625,12 +735,14 @@ void BackupDaemon::Run2() if(d > 0) { // Script has asked for a delay - nextSyncTime = GetCurrentBoxTime() + SecondsToBoxTime(d); + nextSyncTime = GetCurrentBoxTime() + + SecondsToBoxTime(d); doSync = false; } } - // Ready to sync? (but only if we're not supposed to be stopping) + // Ready to sync? (but only if we're not supposed + // to be stopping) if(doSync && !StopRun()) { // Touch a file to record times in filesystem @@ -644,22 +756,51 @@ void BackupDaemon::Run2() // Calculate the sync period of files to examine box_time_t syncPeriodStart = lastSyncTime; - box_time_t syncPeriodEnd = currentSyncStartTime - minimumFileAge; + box_time_t syncPeriodEnd = currentSyncStartTime - + minimumFileAge; + + 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(syncPeriodStart >= syncPeriodEnd) + { + BOX_ERROR("Invalid (negative) sync period: " + "perhaps your clock is going " + "backwards (" << syncPeriodStart << + " to " << syncPeriodEnd << ")"); + THROW_EXCEPTION(ClientException, + ClockWentBackwards); + } + // Check logic ASSERT(syncPeriodEnd > syncPeriodStart); // Paranoid check on sync times if(syncPeriodStart >= syncPeriodEnd) continue; - // Adjust syncPeriodEnd to emulate snapshot behaviour properly + // 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 eligable 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)); + // 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)); } // Delete the serialised store object file, @@ -668,13 +809,11 @@ void BackupDaemon::Run2() if(deleteStoreObjectInfoFile && !DeleteStoreObjectInfo()) { - ::syslog(LOG_ERR, "Failed to delete the " + BOX_ERROR("Failed to delete the " "StoreObjectInfoFile, backup cannot " "continue safely."); - // prevent runaway process where the logs fill up -- without this - // the log message will be emitted in a tight loop. - ::sleep(60); - continue; + THROW_EXCEPTION(ClientException, + FailedToDeleteStoreObjectInfoFile); } // In case the backup throws an exception, @@ -691,30 +830,70 @@ void BackupDaemon::Run2() { // Set state and log start SetState(State_Connected); - ::syslog(LOG_INFO, "Beginning scan of local files"); + BOX_NOTICE("Beginning scan of local files"); - // 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")); + 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, clientContext); + BackupClientDirectoryRecord::SyncParams params( + *this, *this, clientContext); params.mSyncPeriodStart = syncPeriodStart; - params.mSyncPeriodEnd = syncPeriodEndExtended; // use potentially extended end time + 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")); + params.mFileTrackingSizeThreshold = + conf.GetKeyValueInt( + "FileTrackingSizeThreshold"); + params.mDiffingUploadSizeThreshold = + conf.GetKeyValueInt( + "DiffingUploadSizeThreshold"); + params.mMaxFileTimeInFuture = + SecondsToBoxTime( + conf.GetKeyValueInt( + "MaxFileTimeInFuture")); + + 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 + // 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")); + const Configuration &locations( + conf.GetSubConfiguration( + "BackupLocations")); - // Make sure all the directory records are set up + // Make sure all the directory records + // are set up SetupLocations(clientContext, locations); } @@ -724,17 +903,29 @@ void BackupDaemon::Run2() // 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) + for(std::vector<Location *>::const_iterator + i(mLocations.begin()); + i != mLocations.end(); ++i) { - // Set current and new ID map pointers in the context + // 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); + // 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); + (*i)->mpDirectoryRecord->SyncDirectory( + params, + BackupProtocolClientListDirectory::RootDirectory, + (*i)->mPath); // Unset exclude lists (just in case) clientContext.SetExcludeLists(0, 0); @@ -748,13 +939,14 @@ void BackupDaemon::Run2() } else { - // Unset the read error flag, so the error is - // reported again in the future + // 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. + // Perform any deletions required -- these are + // delayed until the end to allow renaming to + // happen neatly. clientContext.PerformDeletions(); // Close any open connection @@ -771,21 +963,34 @@ void BackupDaemon::Run2() } 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) + // 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, and alert will be sent + + // 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 >> SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); + nextSyncTime = currentSyncStartTime + + updateStoreInterval + + Random::RandomInt(updateStoreInterval >> + SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); // Commit the ID Maps CommitIDMapsAfterSync(); // Log - ::syslog(LOG_INFO, "Finished scan of local files"); + BOX_NOTICE("Finished scan of local files"); + + // Notify administrator + NotifySysadmin(NotifyEvent_BackupFinish); // -------------------------------------------------------------------------------------------- @@ -809,21 +1014,29 @@ void BackupDaemon::Run2() } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error during " - "backup run: %s", e.what()); + 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 + // TODO: better handling of exceptions here... + // need to be very careful errorOccurred = true; } if(errorOccurred) { // Is it a berkely db failure? - bool isBerkelyDbFailure = (errorCode == BackupStoreException::ExceptionType - && errorSubCode == BackupStoreException::BerkelyDBFailure); + bool isBerkelyDbFailure = false; + + if (errorCode == BackupStoreException::ExceptionType + && errorSubCode == BackupStoreException::BerkelyDBFailure) + { + isBerkelyDbFailure = true; + } + if(isBerkelyDbFailure) { // Delete corrupt files @@ -831,7 +1044,8 @@ void BackupDaemon::Run2() } // Clear state data - syncPeriodStart = 0; // go back to beginning of time + syncPeriodStart = 0; + // go back to beginning of time clientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything DeleteAllLocations(); DeleteAllIDMaps(); @@ -839,26 +1053,30 @@ void BackupDaemon::Run2() // Handle restart? if(StopRun()) { - ::syslog(LOG_INFO, "Exception (%d/%d) due to signal", errorCode, errorSubCode); + BOX_NOTICE("Exception (" << errorCode + << "/" << errorSubCode + << ") due to signal"); return; } // If the Berkely db files get corrupted, delete them and try again immediately if(isBerkelyDbFailure) { - ::syslog(LOG_ERR, "Berkely db inode map files corrupted, deleting and restarting scan. Renamed files and directories will not be tracked until after this scan.\n"); + 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); - ::syslog(LOG_ERR, - "Exception caught (%s %d/%d), " - "reset state and waiting " - "to retry...", - errorString, errorCode, - errorSubCode); + BOX_ERROR("Exception caught (" + << errorString + << " " << errorCode + << "/" << errorSubCode + << "), reset state and " + "waiting to retry..."); ::sleep(10); nextSyncTime = currentSyncStartTime + SecondsToBoxTime(90) + @@ -869,9 +1087,12 @@ void BackupDaemon::Run2() } // Log the stats - ::syslog(LOG_INFO, "File statistics: total file size uploaded %lld, bytes already on server %lld, encoded size %lld", - BackupStoreFile::msStats.mBytesInEncodedFiles, BackupStoreFile::msStats.mBytesAlreadyOnServer, - BackupStoreFile::msStats.mTotalFileStreamSize); + 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(); // Tell anything connected to the command socket @@ -926,7 +1147,7 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed() std::string line; if(getLine.GetLine(line, true, 30000)) // 30 seconds should be enough { - // Got a string, intepret + // Got a string, interpret if(line == "now") { // Script says do it now. Obey. @@ -941,44 +1162,39 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed() } catch(ConversionException &e) { - ::syslog(LOG_ERR, "Invalid output " - "from SyncAllowScript '%s': " - "'%s'", - conf.GetKeyValue("SyncAllowScript").c_str(), - line.c_str()); + BOX_ERROR("Invalid output " + "from SyncAllowScript '" + << conf.GetKeyValue("SyncAllowScript") + << "': '" << line << "'"); throw; } - ::syslog(LOG_INFO, "Delaying sync by %d seconds (SyncAllowScript '%s')", waitInSeconds, conf.GetKeyValue("SyncAllowScript").c_str()); + BOX_NOTICE("Delaying sync by " << waitInSeconds + << " seconds (SyncAllowScript '" + << conf.GetKeyValue("SyncAllowScript") + << "')"); } } - // Wait and then cleanup child process - int status = 0; - ::waitpid(pid, &status, 0); } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error running SyncAllowScript: " - "%s", e.what()); - // Clean up - if(pid != 0) - { - int status = 0; - ::waitpid(pid, &status, 0); - } + BOX_ERROR("Internal error running SyncAllowScript: " + << e.what()); } catch(...) { // Ignore any exceptions // Log that something bad happened - ::syslog(LOG_ERR, "Error running SyncAllowScript '%s'", conf.GetKeyValue("SyncAllowScript").c_str()); - // Clean up though - if(pid != 0) - { - int status = 0; - ::waitpid(pid, &status, 0); - } + BOX_ERROR("Error running SyncAllowScript '" + << conf.GetKeyValue("SyncAllowScript") << "'"); + } + + // Wait and then cleanup child process, if any + if(pid != 0) + { + int status = 0; + ::waitpid(pid, &status, 0); } return waitInSeconds; @@ -998,32 +1214,34 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed() void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut) { #ifdef WIN32 - // Really could use some interprocess protection, mutex etc - // any side effect should be too bad???? :) - DWORD timeout = (DWORD)BoxTimeToMilliSeconds(RequiredDelay); + DWORD requiredDelayMs = BoxTimeToMilliSeconds(RequiredDelay); - while ( this->mReceivedCommandConn == false ) - { - Sleep(1); + DWORD result = WaitForSingleObject(mhCommandReceivedEvent, + (DWORD)requiredDelayMs); - if( timeout == 0 ) - { - DoSyncFlagOut = false; - SyncIsForcedOut = false; - return; - } - timeout--; + if(result == WAIT_OBJECT_0) + { + DoSyncFlagOut = this->mDoSyncFlagOut; + SyncIsForcedOut = this->mSyncIsForcedOut; + ResetEvent(mhCommandReceivedEvent); + } + else if(result == WAIT_TIMEOUT) + { + DoSyncFlagOut = false; + SyncIsForcedOut = false; + } + else + { + BOX_ERROR("Unexpected result from WaitForSingleObject: " + "error " << GetLastError()); } - this->mReceivedCommandConn = false; - DoSyncFlagOut = this->mDoSyncFlagOut; - SyncIsForcedOut = this->mSyncIsForcedOut; return; #else // ! WIN32 ASSERT(mpCommandSocketInfo != 0); if(mpCommandSocketInfo == 0) {::sleep(1); return;} // failure case isn't too bad - TRACE1("Wait on command socket, delay = %lld\n", RequiredDelay); + BOX_TRACE("Wait on command socket, delay = " << RequiredDelay); try { @@ -1049,7 +1267,7 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla { #ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET bool uidOK = true; - ::syslog(LOG_WARNING, "On this platform, no security check can be made on the credentials of peers connecting to the command socket. (bbackupctl)"); + BOX_WARNING("On this platform, no security check can be made on the credentials of peers connecting to the command socket. (bbackupctl)"); #else // Security check -- does the process connecting to this socket have // the same UID as this process? @@ -1074,14 +1292,14 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla if(!uidOK) { // Dump the connection - ::syslog(LOG_ERR, "Incoming command connection from peer had different user ID than this process, or security check could not be completed."); + BOX_ERROR("Incoming command connection from peer had different user ID than this process, or security check could not be completed."); mpCommandSocketInfo->mpConnectedSocket.reset(); return; } else { // Log - ::syslog(LOG_INFO, "Connection from command socket"); + BOX_INFO("Connection from command socket"); // Send a header line summarising the configuration and current state const Configuration &conf(GetConfiguration()); @@ -1119,7 +1337,8 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla while(mpCommandSocketInfo->mpGetLine != 0 && !mpCommandSocketInfo->mpGetLine->IsEOF() && mpCommandSocketInfo->mpGetLine->GetLine(command, false /* no preprocessing */, timeout)) { - TRACE1("Receiving command '%s' over command socket\n", command.c_str()); + BOX_TRACE("Receiving command '" << command + << "' over command socket"); bool sendOK = false; bool sendResponse = true; @@ -1176,9 +1395,19 @@ void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFla } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error in command socket thread: " - "%s", e.what()); - throw; // thread will die + 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) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } } catch(...) { @@ -1211,7 +1440,7 @@ void BackupDaemon::CloseCommandConnection() #ifndef WIN32 try { - TRACE0("Closing command connection\n"); + BOX_TRACE("Closing command connection"); if(mpCommandSocketInfo->mpGetLine) { @@ -1222,8 +1451,8 @@ void BackupDaemon::CloseCommandConnection() } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error while closing command " - "socket: %s", e.what()); + BOX_ERROR("Internal error while closing command " + "socket: " << e.what()); } catch(...) { @@ -1247,10 +1476,6 @@ 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. -#ifdef __MINGW32__ -#warning race condition: what happens if socket is closed? -#endif - if(mpCommandSocketInfo != NULL && #ifdef WIN32 mpCommandSocketInfo->mListeningSocket.IsConnected() @@ -1259,21 +1484,24 @@ void BackupDaemon::SendSyncStartOrFinish(bool SendStart) #endif ) { - const char* message = SendStart ? "start-sync\n" : "finish-sync\n"; + std::string message = SendStart ? "start-sync" : "finish-sync"; try { #ifdef WIN32 - mpCommandSocketInfo->mListeningSocket.Write(message, - (int)strlen(message)); + EnterCriticalSection(&mMessageQueueLock); + mMessageList.push_back(message); + SetEvent(mhMessageToSendEvent); + LeaveCriticalSection(&mMessageQueueLock); #else - mpCommandSocketInfo->mpConnectedSocket->Write(message, - strlen(message)); + message += "\n"; + mpCommandSocketInfo->mpConnectedSocket->Write( + message.c_str(), message.size()); #endif } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error while sending to " - "command socket client: %s", e.what()); + BOX_ERROR("Internal error while sending to " + "command socket client: " << e.what()); CloseCommandConnection(); } catch(...) @@ -1376,7 +1604,7 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con struct mntent *entry = 0; while((entry = ::getmntent(mountPointsFile)) != 0) { - TRACE1("Found mount point at %s\n", entry->mnt_dir); + BOX_TRACE("Found mount point at " << entry->mnt_dir); mountPoints.insert(std::string(entry->mnt_dir)); } @@ -1398,12 +1626,11 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con try { - // Read all the entries, and put them in the set struct mnttab entry; while(getmntent(mountPointsFile, &entry) == 0) { - TRACE1("Found mount point at %s\n", entry.mnt_mountp); + BOX_TRACE("Found mount point at " << entry.mnt_mountp); mountPoints.insert(std::string(entry.mnt_mountp)); } @@ -1432,7 +1659,7 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con for(std::list<std::pair<std::string, Configuration> >::const_iterator i = rLocationsConf.mSubConfigurations.begin(); i != rLocationsConf.mSubConfigurations.end(); ++i) { -TRACE0("new location\n"); + BOX_TRACE("new location"); // Create a record for it Location *ploc = new Location; try @@ -1444,7 +1671,23 @@ TRACE0("new location\n"); // Read the exclude lists from the Configuration ploc->mpExcludeFiles = BackupClientMakeExcludeList_Files(i->second); ploc->mpExcludeDirs = BackupClientMakeExcludeList_Dirs(i->second); - + // Does this exist on the server? + // Remove from dir object early, so that if we fail + // to stat the local directory, we still don't + // consider to remote one for deletion. + BackupStoreDirectory::Iterator iter(dir); + BackupStoreFilenameClear dirname(ploc->mName); // generate the filename + BackupStoreDirectory::Entry *en = iter.FindMatchingClearName(dirname); + int64_t oid = 0; + if(en != 0) + { + oid = en->GetObjectID(); + + // Delete the entry from the directory, so we get a list of + // unused root directories at the end of this. + dir.DeleteEntry(oid); + } + // Do a fsstat on the pathname to find out which mount it's on { @@ -1459,7 +1702,11 @@ TRACE0("new location\n"); if(::statfs(ploc->mPath.c_str(), &s) != 0) #endif // HAVE_STRUCT_STATVFS_F_MNTONNAME { - THROW_EXCEPTION(CommonException, OSFileError) + BOX_WARNING("Failed to stat location: " + << ploc->mPath + << ": " << strerror(errno)); + THROW_EXCEPTION(CommonException, + OSFileError) } // Where the filesystem is mounted @@ -1470,19 +1717,22 @@ TRACE0("new location\n"); // Warn in logs if the directory isn't absolute if(ploc->mPath[0] != '/') { - ::syslog(LOG_ERR, "Location path '%s' isn't absolute", ploc->mPath.c_str()); + BOX_WARNING("Location path '" + << ploc->mPath + << "' is not absolute"); } // Go through the mount points found, and find a suitable one std::string mountName("/"); { std::set<std::string, mntLenCompare>::const_iterator i(mountPoints.begin()); - TRACE1("%d potential mount points\n", mountPoints.size()); + BOX_TRACE(mountPoints.size() + << " potential mount points"); for(; i != mountPoints.end(); ++i) { // Compare first n characters with the filename // If it matches, the file belongs in that mount point // (sorting order ensures this) - TRACE1("checking against mount point %s\n", i->c_str()); + BOX_TRACE("checking against mount point " << *i); if(::strncmp(i->c_str(), ploc->mPath.c_str(), i->size()) == 0) { // Match @@ -1490,7 +1740,9 @@ TRACE0("new location\n"); break; } } - TRACE2("mount point chosen for %s is %s\n", ploc->mPath.c_str(), mountName.c_str()); + BOX_TRACE("mount point chosen for " + << ploc->mPath << " is " + << mountName); } #endif @@ -1517,35 +1769,46 @@ TRACE0("new location\n"); } // Does this exist on the server? - BackupStoreDirectory::Iterator iter(dir); - BackupStoreFilenameClear dirname(ploc->mName); // generate the filename - BackupStoreDirectory::Entry *en = iter.FindMatchingClearName(dirname); - int64_t oid = 0; - if(en != 0) - { - oid = en->GetObjectID(); - - // Delete the entry from the directory, so we get a list of - // unused root directories at the end of this. - dir.DeleteEntry(oid); - } - else + if(en == 0) { // Doesn't exist, so it has to be created on the server. Let's go! // First, get the directory's attributes and modification time box_time_t attrModTime = 0; BackupClientFileAttributes attr; - attr.ReadAttributes(ploc->mPath.c_str(), true /* directories have zero mod times */, - 0 /* not interested in mod time */, &attrModTime /* get the attribute modification time */); + try + { + attr.ReadAttributes(ploc->mPath.c_str(), + true /* directories have zero mod times */, + 0 /* not interested in mod time */, + &attrModTime /* get the attribute modification time */); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to get attributes " + "for path '" << ploc->mPath + << "', skipping."); + continue; + } // Execute create directory command - MemBlockStream attrStream(attr); - std::auto_ptr<BackupProtocolClientSuccess> dirCreate(connection.QueryCreateDirectory( - BackupProtocolClientListDirectory::RootDirectory, - attrModTime, dirname, attrStream)); - - // Object ID for later creation - oid = dirCreate->GetObjectID(); + try + { + MemBlockStream attrStream(attr); + std::auto_ptr<BackupProtocolClientSuccess> dirCreate(connection.QueryCreateDirectory( + BackupProtocolClientListDirectory::RootDirectory, + attrModTime, dirname, attrStream)); + + // Object ID for later creation + oid = dirCreate->GetObjectID(); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to create remote " + "directory '/" << ploc->mName << + "', skipping location."); + continue; + } + } // Create and store the directory object for the root of this location @@ -1556,10 +1819,26 @@ TRACE0("new location\n"); // Push it back on the vector of locations mLocations.push_back(ploc); } - catch(...) + catch (std::exception &e) { delete ploc; ploc = 0; + BOX_ERROR("Failed to configure location '" + << ploc->mName << "' path '" + << ploc->mPath << "': " << e.what() << + ": please check for previous errors"); + throw; + } + catch(...) + { + BOX_ERROR("Failed to configure location '" + << ploc->mName << "' path '" + << ploc->mPath << "': please check for " + "previous errors"); + + delete ploc; + ploc = NULL; + throw; } } @@ -1567,8 +1846,27 @@ TRACE0("new location\n"); // Any entries in the root directory which need deleting? if(dir.GetNumberOfEntries() > 0) { - ::syslog(LOG_INFO, "%d redundant locations in root directory found, will delete from store after %d seconds.", - dir.GetNumberOfEntries(), BACKUP_DELETE_UNUSED_ROOT_ENTRIES_AFTER); + box_time_t now = GetCurrentBoxTime(); + + // This should reset the timer if the list of unused + // locations changes, but it will not if the number of + // unused locations does not change, but the locations + // do change, e.g. one mysteriously appears and another + // mysteriously appears. (FIXME) + if (dir.GetNumberOfEntries() != mUnusedRootDirEntries.size() || + mDeleteUnusedRootDirEntriesAfter == 0) + { + mDeleteUnusedRootDirEntriesAfter = now + + SecondsToBoxTime( + BACKUP_DELETE_UNUSED_ROOT_ENTRIES_AFTER); + } + + int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter + - now); + + BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " + "in root directory found, will delete from store " + "after " << secs << " seconds."); // Store directories in list of things to delete mUnusedRootDirEntries.clear(); @@ -1579,14 +1877,13 @@ TRACE0("new location\n"); // Add name to list BackupStoreFilenameClear clear(en->GetName()); const std::string &name(clear.GetClearFilename()); - mUnusedRootDirEntries.push_back(std::pair<int64_t,std::string>(en->GetObjectID(), name)); + mUnusedRootDirEntries.push_back( + std::pair<int64_t,std::string> + (en->GetObjectID(), name)); // Log this - ::syslog(LOG_INFO, "Unused location in root: %s", name.c_str()); + BOX_INFO("Unused location in root: " << name); } ASSERT(mUnusedRootDirEntries.size() > 0); - // Time to delete them - mDeleteUnusedRootDirEntriesAfter = - GetCurrentBoxTime() + SecondsToBoxTime(BACKUP_DELETE_UNUSED_ROOT_ENTRIES_AFTER); } } @@ -1698,14 +1995,14 @@ void BackupDaemon::DeleteCorruptBerkelyDbFiles() MakeMapBaseName(l, filename); // Delete the file - TRACE1("Deleting %s\n", filename.c_str()); + BOX_TRACE("Deleting " << filename); ::unlink(filename.c_str()); // Add a suffix for the new map filename += ".n"; // Delete that too - TRACE1("Deleting %s\n", filename.c_str()); + BOX_TRACE("Deleting " << filename); ::unlink(filename.c_str()); } } @@ -1785,6 +2082,9 @@ 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)); THROW_EXCEPTION(CommonException, OSFileError) } } @@ -1869,51 +2169,44 @@ void BackupDaemon::SetState(int State) // command socket if there's an error char newState[64]; - char newStateSize = sprintf(newState, "state %d\n", State); + sprintf(newState, "state %d", State); + std::string message = newState; #ifdef WIN32 - #ifndef _MSC_VER - #warning FIX ME: race condition - #endif + EnterCriticalSection(&mMessageQueueLock); + mMessageList.push_back(newState); + SetEvent(mhMessageToSendEvent); + LeaveCriticalSection(&mMessageQueueLock); +#else + message += "\n"; - // what happens if the socket is closed by the other thread before - // we can write to it? Null pointer deref at best. - if(mpCommandSocketInfo && - mpCommandSocketInfo->mListeningSocket.IsConnected()) + if(mpCommandSocketInfo == 0) { - try - { - mpCommandSocketInfo->mListeningSocket.Write(newState, newStateSize); - } - catch(std::exception &e) - { - ::syslog(LOG_ERR, "Internal error while writing state " - "to command socket: %s", e.what()); - CloseCommandConnection(); - } - catch(...) - { - CloseCommandConnection(); - } + return; } -#else - if(mpCommandSocketInfo != 0 && mpCommandSocketInfo->mpConnectedSocket.get() != 0) + + if(mpCommandSocketInfo->mpConnectedSocket.get() == 0) { - // Something connected to the command socket, tell it about the new state - try - { - mpCommandSocketInfo->mpConnectedSocket->Write(newState, newStateSize); - } - catch(std::exception &e) - { - ::syslog(LOG_ERR, "Internal error while writing state " - "to command socket: %s", e.what()); - CloseCommandConnection(); - } - catch(...) - { - CloseCommandConnection(); - } + return; + } + + // Something connected to the command socket, tell it about the new state + try + { + mpCommandSocketInfo->mpConnectedSocket->Write(message.c_str(), + message.length()); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error while writing state " + "to command socket: " << e.what()); + CloseCommandConnection(); + } + catch(...) + { + BOX_ERROR("Internal error while writing state " + "to command socket: unknown error"); + CloseCommandConnection(); } #endif } @@ -1944,49 +2237,78 @@ void BackupDaemon::TouchFileInWorkingDir(const char *Filename) // // Function // Name: BackupDaemon::NotifySysadmin(int) -// Purpose: Run the script to tell the sysadmin about events which need attention. +// Purpose: Run the script to tell the sysadmin about events +// which need attention. // Created: 25/2/04 // // -------------------------------------------------------------------------- void BackupDaemon::NotifySysadmin(int Event) { - static const char *sEventNames[] = {"store-full", "read-error", 0}; + static const char *sEventNames[] = + { + "store-full", + "read-error", + "backup-error", + "backup-start", + "backup-finish", + 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); - TRACE1("BackupDaemon::NotifySysadmin() called, event = %d\n", Event); + BOX_TRACE("BackupDaemon::NotifySysadmin() called, event = " << + sEventNames[Event]); - if(Event < 0 || Event > NotifyEvent__MAX) + if(Event < 0 || Event >= NotifyEvent__MAX) { - THROW_EXCEPTION(BackupStoreException, BadNotifySysadminEventCode); + THROW_EXCEPTION(BackupStoreException, + BadNotifySysadminEventCode); } // Don't send lots of repeated messages - if(mNotificationsSent[Event]) + if(mNotificationsSent[Event] && + Event != NotifyEvent_BackupStart && + Event != NotifyEvent_BackupFinish) { + BOX_WARNING("Suppressing duplicate notification about " << + sEventNames[Event]); return; } // Is there a notifation script? const Configuration &conf(GetConfiguration()); - if(!conf.KeyExists("NotifyScript")) + if(!conf.KeyExists("NotifyScript") && + Event != NotifyEvent_BackupStart && + Event != NotifyEvent_BackupFinish) { // Log, and then return - ::syslog(LOG_ERR, "Not notifying administrator about event %s -- set NotifyScript to do this in future", sEventNames[Event]); + BOX_ERROR("Not notifying administrator about event " + << sEventNames[Event] << " -- set NotifyScript " + "to do this in future"); return; } // Script to run - std::string script(conf.GetKeyValue("NotifyScript") + ' ' + sEventNames[Event]); + std::string script(conf.GetKeyValue("NotifyScript") + ' ' + + sEventNames[Event]); // Log what we're about to do - ::syslog(LOG_INFO, "About to notify administrator about event %s, running script '%s'", sEventNames[Event], script.c_str()); + BOX_NOTICE("About to notify administrator about event " + << sEventNames[Event] << ", running script '" + << script << "'"); // Then do it if(::system(script.c_str()) != 0) { - ::syslog(LOG_ERR, "Notify script returned an error code. ('%s')", script.c_str()); + BOX_ERROR("Notify script returned an error code. ('" + << script << "')"); } - // Flag that this is done so the administrator isn't constantly bombarded with lots of errors + // Flag that this is done so the administrator isn't constantly + // bombarded with lots of errors mNotificationsSent[Event] = true; } @@ -2001,28 +2323,40 @@ void BackupDaemon::NotifySysadmin(int Event) // -------------------------------------------------------------------------- void BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &rContext) { - if(mUnusedRootDirEntries.empty() || mDeleteUnusedRootDirEntriesAfter == 0) + if(mUnusedRootDirEntries.empty()) { - // Nothing to do. + BOX_INFO("Not deleting unused entries - none in list"); return; } + if(mDeleteUnusedRootDirEntriesAfter == 0) + { + BOX_INFO("Not deleting unused entries - " + "zero delete time (bad)"); + return; + } + // Check time - if(GetCurrentBoxTime() < mDeleteUnusedRootDirEntriesAfter) + box_time_t now = GetCurrentBoxTime(); + if(now < mDeleteUnusedRootDirEntriesAfter) { - // Too early to delete files + int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter + - now); + BOX_INFO("Not deleting unused entries - too early (" + << secs << " seconds remaining)"); return; } // Entries to delete, and it's the right time to do so... - ::syslog(LOG_INFO, "Deleting unused locations from store root..."); + 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) { connection.QueryDeleteDirectory(i->first); // Log this - ::syslog(LOG_INFO, "Deleted %s (ID %08llx) from store root", i->second.c_str(), i->first); + BOX_NOTICE("Deleted " << i->second << " (ID " << i->first + << ") from store root"); } // Reset state @@ -2138,7 +2472,7 @@ void BackupDaemon::Location::Deserialize(Archive &rArchive) else { // there is something going on here - THROW_EXCEPTION(CommonException, Internal) + THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); } // @@ -2163,7 +2497,7 @@ void BackupDaemon::Location::Deserialize(Archive &rArchive) else { // there is something going on here - THROW_EXCEPTION(CommonException, Internal) + THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); } // @@ -2188,7 +2522,7 @@ void BackupDaemon::Location::Deserialize(Archive &rArchive) else { // there is something going on here - THROW_EXCEPTION(CommonException, Internal) + THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); } } @@ -2302,7 +2636,7 @@ BackupDaemon::CommandSocketInfo::~CommandSocketInfo() static const int STOREOBJECTINFO_MAGIC_ID_VALUE = 0x7777525F; static const std::string STOREOBJECTINFO_MAGIC_ID_STRING = "BBACKUPD-STATE"; -static const int STOREOBJECTINFO_VERSION = 1; +static const int STOREOBJECTINFO_VERSION = 2; bool BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time_t theLastSyncTime, box_time_t theNextSyncTime) const { @@ -2361,15 +2695,39 @@ bool BackupDaemon::SerializeStoreObjectInfo(int64_t aClientStoreMarker, box_time // // // + iCount = mUnusedRootDirEntries.size(); + anArchive.Write(iCount); + + for(int v = 0; v < iCount; v++) + { + anArchive.Write(mUnusedRootDirEntries[v].first); + anArchive.Write(mUnusedRootDirEntries[v].second); + } + + if (iCount > 0) + { + anArchive.Write(mDeleteUnusedRootDirEntriesAfter); + } + + // + // + // aFile.Close(); - ::syslog(LOG_INFO, "Saved store object info file '%s'", - StoreObjectInfoFile.c_str()); + BOX_INFO("Saved store object info file version " << + STOREOBJECTINFO_VERSION << " (" << + StoreObjectInfoFile << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error writing store object " + "info file (" << StoreObjectInfoFile << "): " + << e.what()); } catch(...) { - ::syslog(LOG_WARNING, "Requested store object info file '%s' " - "not accessible or could not be created", - StoreObjectInfoFile.c_str()); + BOX_ERROR("Internal error writing store object " + "info file (" << StoreObjectInfoFile << "): " + "unknown error"); } return created; @@ -2420,10 +2778,10 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ if(iMagicValue != STOREOBJECTINFO_MAGIC_ID_VALUE) { - ::syslog(LOG_WARNING, "Store object info file '%s' " + BOX_WARNING("Store object info file " "is not a valid or compatible serialised " - "archive. Will re-cache from store.", - StoreObjectInfoFile.c_str()); + "archive. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); return false; } @@ -2435,10 +2793,10 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ if(strMagicValue != STOREOBJECTINFO_MAGIC_ID_STRING) { - ::syslog(LOG_WARNING, "Store object info file '%s' " + BOX_WARNING("Store object info file " "is not a valid or compatible serialised " - "archive. Will re-cache from store.", - StoreObjectInfoFile.c_str()); + "archive. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); return false; } @@ -2451,11 +2809,10 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ if(iVersion != STOREOBJECTINFO_VERSION) { - ::syslog(LOG_WARNING, "Store object info file '%s' " - "version %d unsupported. " - "Will re-cache from store.", - StoreObjectInfoFile.c_str(), - iVersion); + BOX_WARNING("Store object info file " + "version " << iVersion << " unsupported. " + "Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); return false; } @@ -2468,9 +2825,9 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ if(lastKnownConfigModTime != GetLoadedConfigModifiedTime()) { - ::syslog(LOG_WARNING, "Store object info file '%s' " - "out of date. Will re-cache from store", - StoreObjectInfoFile.c_str()); + BOX_WARNING("Store object info file " + "out of date. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); return false; } @@ -2516,22 +2873,41 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ // // // - aFile.Close(); - ::syslog(LOG_INFO, "Loaded store object info file '%s', " - "version [%d]", StoreObjectInfoFile.c_str(), - iVersion); + iCount = 0; + anArchive.Read(iCount); + for(int v = 0; v < iCount; v++) + { + int64_t anId; + anArchive.Read(anId); + + std::string aName; + anArchive.Read(aName); + + mUnusedRootDirEntries.push_back(std::pair<int64_t, std::string>(anId, aName)); + } + + if (iCount > 0) + anArchive.Read(mDeleteUnusedRootDirEntriesAfter); + + // + // + // + aFile.Close(); + BOX_INFO("Loaded store object info file version " << iVersion + << " (" << StoreObjectInfoFile << ")"); + return true; } catch(std::exception &e) { - ::syslog(LOG_ERR, "Internal error reading store object " - "info file: %s", e.what()); + BOX_ERROR("Internal error reading store object info file: " + << StoreObjectInfoFile << ": " << e.what()); } catch(...) { - ::syslog(LOG_ERR, "Internal error reading store object " - "info file: unknown error"); + BOX_ERROR("Internal error reading store object info file: " + << StoreObjectInfoFile << ": unknown error"); } DeleteAllLocations(); @@ -2540,11 +2916,10 @@ bool BackupDaemon::DeserializeStoreObjectInfo(int64_t & aClientStoreMarker, box_ theLastSyncTime = 0; theNextSyncTime = 0; - ::syslog(LOG_WARNING, "Requested store object info file '%s' " - "does not exist, not accessible, or inconsistent. " - "Will re-cache from store.", - StoreObjectInfoFile.c_str()); - + BOX_WARNING("Store object info file is missing, not accessible, " + "or inconsistent. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); + return false; } @@ -2572,9 +2947,9 @@ bool BackupDaemon::DeleteStoreObjectInfo() const if(!FileExists(storeObjectInfoFile.c_str())) { // File doesn't exist -- so can't be deleted. But something isn't quite right, so log a message - ::syslog(LOG_ERR, "Expected to be able to delete " - "store object info file '%s', but the file did not exist.", - storeObjectInfoFile.c_str()); + BOX_WARNING("Store object info file did not exist when it " + "was supposed to. (" << storeObjectInfoFile << ")"); + // Return true to stop things going around in a loop return true; } @@ -2582,9 +2957,8 @@ bool BackupDaemon::DeleteStoreObjectInfo() const // Actually delete it if(::unlink(storeObjectInfoFile.c_str()) != 0) { - ::syslog(LOG_ERR, "Failed to delete the old " - "store object info file '%s': %s", - storeObjectInfoFile.c_str(), strerror(errno)); + BOX_ERROR("Failed to delete the old store object info file: " + << storeObjectInfoFile << ": "<< strerror(errno)); return false; } diff --git a/bin/bbackupd/BackupDaemon.h b/bin/bbackupd/BackupDaemon.h index 347c205b..ab64bbfa 100644 --- a/bin/bbackupd/BackupDaemon.h +++ b/bin/bbackupd/BackupDaemon.h @@ -16,9 +16,13 @@ #include "BoxTime.h" #include "Daemon.h" +#include "BackupClientDirectoryRecord.h" #include "Socket.h" #include "SocketListen.h" #include "SocketStream.h" +#include "Logging.h" +#include "autogen_BackupProtocolClient.h" + #ifdef WIN32 #include "WinNamedPipeStream.h" #endif @@ -39,7 +43,7 @@ class Archive; // Created: 2003/10/08 // // -------------------------------------------------------------------------- -class BackupDaemon : public Daemon +class BackupDaemon : public Daemon, ProgressNotifier { public: BackupDaemon(); @@ -79,8 +83,11 @@ public: enum { NotifyEvent_StoreFull = 0, - NotifyEvent_ReadError = 1, - NotifyEvent__MAX = 1 + NotifyEvent_ReadError, + NotifyEvent_BackupError, + NotifyEvent_BackupStart, + NotifyEvent_BackupFinish, + NotifyEvent__MAX // When adding notifications, remember to add strings to NotifySysadmin() }; void NotifySysadmin(int Event); @@ -174,18 +181,244 @@ private: CommandSocketInfo *mpCommandSocketInfo; // Stop notifications being repeated. - bool mNotificationsSent[NotifyEvent__MAX + 1]; + bool mNotificationsSent[NotifyEvent__MAX]; // 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; +public: + bool StopRun() { return this->Daemon::StopRun(); } + +private: + bool mLogAllFileAccess; + + /* ProgressNotifier implementation */ +public: + virtual void NotifyScanDirectory( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Scanning directory: " << rLocalPath); + } + } + virtual void NotifyDirStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to access directory: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyFileStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to access file: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyDirListFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to list directory: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyMountPointSkipped( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + #ifdef WIN32 + BOX_WARNING("Ignored directory: " << rLocalPath << + ": is an NTFS junction/reparse point; create " + "a new location if you want to back it up"); + #else + BOX_WARNING("Ignored directory: " << rLocalPath << + ": is a mount point; create a new location " + "if you want to back it up"); + #endif + } + virtual void NotifyFileExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Skipping excluded file: " << rLocalPath); + } + } + virtual void NotifyDirExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Skipping excluded directory: " << rLocalPath); + } + } + virtual void NotifyUnsupportedFileType( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Ignoring file of unknown type: " << rLocalPath); + } + virtual void NotifyFileReadFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Error reading file: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyFileModifiedInFuture( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Some files have modification times excessively " + "in the future. Check clock synchronisation. " + "Example file (only one shown): " << rLocalPath); + } + virtual void NotifyFileSkippedServerFull( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Skipped file: server is full: " << rLocalPath); + } + virtual void NotifyFileUploadException( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const BoxException& rException) + { + if (rException.GetType() == CommonException::ExceptionType && + rException.GetSubType() == CommonException::AccessDenied) + { + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": Access denied"); + } + else + { + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": caught exception: " << rException.what() + << " (" << rException.GetType() + << "/" << rException.GetSubType() << ")"); + } + } + virtual void NotifyFileUploadServerError( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int type, int subtype) + { + std::ostringstream msgs; + if (type != BackupProtocolClientError::ErrorType) + { + msgs << "unknown error type " << type; + } + else + { + switch(subtype) + { + case BackupProtocolClientError::Err_WrongVersion: + msgs << "WrongVersion"; + break; + case BackupProtocolClientError::Err_NotInRightProtocolPhase: + msgs << "NotInRightProtocolPhase"; + break; + case BackupProtocolClientError::Err_BadLogin: + msgs << "BadLogin"; + break; + case BackupProtocolClientError::Err_CannotLockStoreForWriting: + msgs << "CannotLockStoreForWriting"; + break; + case BackupProtocolClientError::Err_SessionReadOnly: + msgs << "SessionReadOnly"; + break; + case BackupProtocolClientError::Err_FileDoesNotVerify: + msgs << "FileDoesNotVerify"; + break; + case BackupProtocolClientError::Err_DoesNotExist: + msgs << "DoesNotExist"; + break; + case BackupProtocolClientError::Err_DirectoryAlreadyExists: + msgs << "DirectoryAlreadyExists"; + break; + case BackupProtocolClientError::Err_CannotDeleteRoot: + msgs << "CannotDeleteRoot"; + break; + case BackupProtocolClientError::Err_TargetNameExists: + msgs << "TargetNameExists"; + break; + case BackupProtocolClientError::Err_StorageLimitExceeded: + msgs << "StorageLimitExceeded"; + break; + case BackupProtocolClientError::Err_DiffFromFileDoesNotExist: + msgs << "DiffFromFileDoesNotExist"; + break; + case BackupProtocolClientError::Err_DoesNotExistInDirectory: + msgs << "DoesNotExistInDirectory"; + break; + case BackupProtocolClientError::Err_PatchConsistencyError: + msgs << "PatchConsistencyError"; + break; + default: + msgs << "unknown error subtype " << subtype; + } + } + + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": server error: " << msgs.str()); + } + virtual void NotifyFileUploading( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Uploading complete file: " << rLocalPath); + } + } + virtual void NotifyFileUploadingPatch( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Uploading patch to file: " << rLocalPath); + } + } + virtual void NotifyFileUploaded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) + { + if (mLogAllFileAccess) + { + BOX_INFO("Uploaded file: " << rLocalPath); + } + } + virtual void NotifyFileSynchronised( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) + { + if (mLogAllFileAccess) + { + BOX_INFO("Synchronised file: " << rLocalPath); + } + } + #ifdef WIN32 public: void RunHelperThread(void); private: - bool mDoSyncFlagOut, mSyncIsForcedOut, mReceivedCommandConn; + bool mDoSyncFlagOut, mSyncIsForcedOut; + HANDLE mhMessageToSendEvent, mhCommandReceivedEvent; + CRITICAL_SECTION mMessageQueueLock; + std::vector<std::string> mMessageList; #endif }; diff --git a/bin/bbackupd/ClientException.txt b/bin/bbackupd/ClientException.txt new file mode 100644 index 00000000..04f88620 --- /dev/null +++ b/bin/bbackupd/ClientException.txt @@ -0,0 +1,11 @@ + +# NOTE: Exception descriptions are for public distributions of Box Backup only -- do not rely for other applications. + + +EXCEPTION Client 13 + +Internal 0 +AssertFailed 1 +ClockWentBackwards 2 Invalid (negative) sync period: perhaps your clock is going backwards? +FailedToDeleteStoreObjectInfoFile 3 Failed to delete the StoreObjectInfoFile, backup cannot continue safely. +CorruptStoreObjectInfoFile 4 The store object info file contained an invalid value and is probably corrupt. Try deleting it. diff --git a/bin/bbackupd/Makefile.extra b/bin/bbackupd/Makefile.extra new file mode 100644 index 00000000..52e21366 --- /dev/null +++ b/bin/bbackupd/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_ClientException.h autogen_ClientException.cpp: $(MAKEEXCEPTION) ClientException.txt + $(PERL) $(MAKEEXCEPTION) ClientException.txt + diff --git a/bin/bbackupd/Win32BackupService.cpp b/bin/bbackupd/Win32BackupService.cpp index 7cbf4828..9f0ac867 100644 --- a/bin/bbackupd/Win32BackupService.cpp +++ b/bin/bbackupd/Win32BackupService.cpp @@ -29,33 +29,17 @@ void TerminateService(void) DWORD Win32BackupService::WinService(const char* pConfigFileName) { - char exepath[MAX_PATH]; - GetModuleFileName(NULL, exepath, sizeof(exepath)); + DWORD ret; - std::string configfile; - if (pConfigFileName != NULL) { - configfile = pConfigFileName; + ret = this->Main(pConfigFileName); } else { - // make the default config file name, - // based on the program path - configfile = exepath; - configfile = configfile.substr(0, - configfile.rfind(DIRECTORY_SEPARATOR_ASCHAR)); - configfile += DIRECTORY_SEPARATOR "bbackupd.conf"; + ret = this->Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE); } - const char *argv[] = {exepath, "-c", configfile.c_str()}; - int argc = sizeof(argv) / sizeof(*argv); - DWORD ret; - - MAINHELPER_START - ret = this->Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); - MAINHELPER_END - return ret; } diff --git a/bin/bbackupd/Win32ServiceFunctions.cpp b/bin/bbackupd/Win32ServiceFunctions.cpp index 3010cf3f..5acf5f67 100644 --- a/bin/bbackupd/Win32ServiceFunctions.cpp +++ b/bin/bbackupd/Win32ServiceFunctions.cpp @@ -44,7 +44,7 @@ void ErrorHandler(char *s, DWORD err) char buf[256]; memset(buf, 0, sizeof(buf)); _snprintf(buf, sizeof(buf)-1, "%s (%d)", s, err); - ::syslog(LOG_ERR, "%s", buf); + BOX_ERROR(buf); MessageBox(0, buf, "Error", MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); ExitProcess(err); @@ -161,7 +161,7 @@ VOID ServiceMain(DWORD argc, LPTSTR *argv) } } -void OurService(char* pConfigFileName) +int OurService(char* pConfigFileName) { spConfigFileName = pConfigFileName; @@ -180,7 +180,10 @@ void OurService(char* pConfigFileName) ErrorHandler("Failed to start service. Did you start " "Box Backup from the Service Control Manager? " "(StartServiceCtrlDispatcher)", GetLastError()); + return 1; } + + return 0; } int InstallService(const char* pConfigFileName) @@ -191,16 +194,16 @@ int InstallService(const char* pConfigFileName) if (emu_stat(pConfigFileName, &st) != 0) { - syslog(LOG_ERR, "Failed to open configuration file: " - "%s: %s", pConfigFileName, strerror(errno)); + BOX_ERROR("Failed to open configuration file '" << + pConfigFileName << "': " << strerror(errno)); return 1; } if (!(st.st_mode & S_IFREG)) { - syslog(LOG_ERR, "Failed to open configuration file: " - "%s: not a file", pConfigFileName); + BOX_ERROR("Failed to open configuration file '" << + pConfigFileName << "': not a file"); return 1; } } @@ -209,8 +212,8 @@ int InstallService(const char* pConfigFileName) if (!scm) { - syslog(LOG_ERR, "Failed to open service control manager: " - "error %d", GetLastError()); + BOX_ERROR("Failed to open service control manager: " << + GetErrorMessage(GetLastError())); return 1; } @@ -248,21 +251,21 @@ int InstallService(const char* pConfigFileName) { case ERROR_SERVICE_EXISTS: { - ::syslog(LOG_ERR, "Failed to create Box Backup " + BOX_ERROR("Failed to create Box Backup " "service: it already exists"); } break; case ERROR_SERVICE_MARKED_FOR_DELETE: { - ::syslog(LOG_ERR, "Failed to create Box Backup " + BOX_ERROR("Failed to create Box Backup " "service: it is waiting to be deleted"); } break; case ERROR_DUPLICATE_SERVICE_NAME: { - ::syslog(LOG_ERR, "Failed to create Box Backup " + BOX_ERROR("Failed to create Box Backup " "service: a service with this name " "already exists"); } @@ -270,15 +273,16 @@ int InstallService(const char* pConfigFileName) default: { - ::syslog(LOG_ERR, "Failed to create Box Backup " - "service: error %d", err); + BOX_ERROR("Failed to create Box Backup " + "service: error " << + GetErrorMessage(GetLastError())); } } return 1; } - ::syslog(LOG_INFO, "Created Box Backup service"); + BOX_INFO("Created Box Backup service"); SERVICE_DESCRIPTION desc; desc.lpDescription = "Backs up your data files over the Internet"; @@ -286,8 +290,8 @@ int InstallService(const char* pConfigFileName) if (!ChangeServiceConfig2(newService, SERVICE_CONFIG_DESCRIPTION, &desc)) { - ::syslog(LOG_WARNING, "Failed to set description for " - "Box Backup service: error %d", GetLastError()); + BOX_WARNING("Failed to set description for Box Backup " + "service: " << GetErrorMessage(GetLastError())); } CloseServiceHandle(newService); @@ -301,8 +305,8 @@ int RemoveService(void) if (!scm) { - syslog(LOG_ERR, "Failed to open service control manager: " - "error %d", GetLastError()); + BOX_ERROR("Failed to open service control manager: " << + GetErrorMessage(GetLastError())); return 1; } @@ -317,13 +321,13 @@ int RemoveService(void) err == ERROR_IO_PENDING) // hello microsoft? anyone home? { - syslog(LOG_ERR, "Failed to open Box Backup service: " + BOX_ERROR("Failed to open Box Backup service: " "not installed or not found"); } else { - syslog(LOG_ERR, "Failed to open Box Backup service: " - "error %d", err); + BOX_ERROR("Failed to open Box Backup service: " << + GetErrorMessage(err)); } return 1; } @@ -334,8 +338,8 @@ int RemoveService(void) err = GetLastError(); if (err != ERROR_SERVICE_NOT_ACTIVE) { - syslog(LOG_WARNING, "Failed to stop Box Backup " - "service: error %d", err); + BOX_WARNING("Failed to stop Box Backup service: " << + GetErrorMessage(err)); } } @@ -345,18 +349,18 @@ int RemoveService(void) if (deleted) { - syslog(LOG_INFO, "Box Backup service deleted"); + BOX_INFO("Box Backup service deleted"); return 0; } else if (err == ERROR_SERVICE_MARKED_FOR_DELETE) { - syslog(LOG_ERR, "Failed to remove Box Backup service: " + BOX_ERROR("Failed to remove Box Backup service: " "it is already being deleted"); } else { - syslog(LOG_ERR, "Failed to remove Box Backup service: " - "error %d", err); + BOX_ERROR("Failed to remove Box Backup service: " << + GetErrorMessage(err)); } return 1; diff --git a/bin/bbackupd/Win32ServiceFunctions.h b/bin/bbackupd/Win32ServiceFunctions.h index 70e1f085..cecd5c7b 100644 --- a/bin/bbackupd/Win32ServiceFunctions.h +++ b/bin/bbackupd/Win32ServiceFunctions.h @@ -12,8 +12,8 @@ #ifndef WIN32SERVICEFUNCTIONS_H #define WIN32SERVICEFUNCTIONS_H -int RemoveService (void); -int InstallService (const char* pConfigFilePath); -void OurService (char* pConfigFileName); +int RemoveService (void); +int InstallService (const char* pConfigFilePath); +int OurService (char* pConfigFileName); #endif diff --git a/bin/bbackupd/bbackupd-config.in b/bin/bbackupd/bbackupd-config.in index c06b818d..b5dc8be8 100755 --- a/bin/bbackupd/bbackupd-config.in +++ b/bin/bbackupd/bbackupd-config.in @@ -123,9 +123,9 @@ __E print ' ',$_,"\n" for(@tobackup); print <<__E; -Note: If other file systems are mounted inside these directories, then problems may occur -with files on the store server being renamed incorrectly. This will cause efficiency -problems, but not affect the integrity of the backups. +Note: If other file systems are mounted inside these directories, then +they will NOT be backed up. You will have to create separate locations for +any mounted filesystems inside your backup locations. WARNING: Directories not checked against mountpoints. Check mounted filesystems manually. @@ -211,12 +211,24 @@ $sendmail = 'sendmail' if $sendmail !~ m/\S/; print NOTIFY <<__EOS; #!/bin/sh +# This script is run whenever bbackupd changes state or encounters a +# problem which requires the system administrator to assist: +# +# 1) The store is full, and no more data can be uploaded. +# 2) Some files or directories were not readable. +# 3) A backup run starts or finishes. +# +# The default script emails the system administrator, except for backups +# starting and stopping, where it does nothing. + SUBJECT="BACKUP PROBLEM on host $hostname" SENDTO="$current_username" -if [ \$1 = store-full ] -then -$sendmail \$SENDTO <<EOM +if [ "\$1" = "" ]; then + echo "Usage: $0 <store-full|read-error|backup-error|backup-start|backup-finish>" >&2 + exit 2 +elif [ "\$1" = store-full ]; then + $sendmail \$SENDTO <<EOM Subject: \$SUBJECT (store full) To: \$SENDTO @@ -230,8 +242,7 @@ FILES ARE NOT BEING BACKED UP Please adjust the limits on account $account_num on server $server. EOM -elif [ \$1 = read-error ] -then +elif [ "\$1" = read-error ]; then $sendmail \$SENDTO <<EOM Subject: \$SUBJECT (read errors) To: \$SENDTO @@ -244,11 +255,14 @@ THESE FILES ARE NOT BEING BACKED UP =================================== Check the logs on $hostname for the files and directories which caused -these errors, and take appropraite action. +these errors, and take appropriate action. Other files are being backed up. EOM +elif [ "\$1" = backup-start -o "\$1" = backup-finish ]; then + # do nothing by default + true else $sendmail \$SENDTO <<EOM Subject: \$SUBJECT (unknown) @@ -287,11 +301,15 @@ TrustedCAsFile = $ca_root_cert DataDirectory = $working_dir -# This script is run whenever bbackupd encounters a problem which requires -# the system administrator to assist: +# This script is run whenever bbackupd changes state or encounters a +# problem which requires the system administrator to assist: +# # 1) The store is full, and no more data can be uploaded. # 2) Some files or directories were not readable. -# The default script emails the system administrator. +# 3) A backup run starts or finishes. +# +# The default script emails the system administrator, except for backups +# starting and stopping, where it does nothing. NotifyScript = $notify_script @@ -302,24 +320,46 @@ if($backup_mode eq 'lazy') # lazy mode configuration print CONFIG <<__E; -# A scan of the local discs will be made once an hour (approximately). -# To avoid cycles of load on the server, this time is randomly adjusted by a small +# The number of seconds between backup runs under normal conditions. To avoid +# cycles of load on the server, this time is randomly adjusted by a small # percentage as the daemon runs. UpdateStoreInterval = 3600 -# A file must have been modified at least 6 hours ago before it will be uploaded. +# The minimum age of a file, in seconds, that will be uploaded. Avoids +# repeated uploads of a file which is constantly being modified. MinimumFileAge = 21600 -# If a file is modified repeated, it won't be uploaded immediately in case it's modified again. -# However, it should be uploaded eventually. This is how long we should wait after first noticing -# a change. (1 day) +# If a file is modified repeated, it won't be uploaded immediately in case +# it's modified again, due to the MinimumFileAge specified above. However, it +# should be uploaded eventually even if it is being modified repeatedly. This +# is how long we should wait, in seconds, after first noticing a change. +# (86400 seconds = 1 day) MaxUploadWait = 86400 +# If the connection is idle for some time (e.g. over 10 minutes or 600 +# seconds, not sure exactly how long) then the server will give up and +# disconnect the client, resulting in Connection Protocol_Timeout errors +# on the server and TLSReadFailed or TLSWriteFailed errors on the client. +# Also, some firewalls and NAT gateways will kill idle connections after +# similar lengths of time. +# +# This can happen for example when most files are backed up already and +# don't need to be sent to the store again, while scanning a large +# directory, or while calculating diffs of a large file. To avoid this, +# KeepAliveTime specifies that special keep-alive messages should be sent +# when the connection is otherwise idle for a certain length of time, +# specified here in seconds. +# +# The default is that these messages are never sent, equivalent to setting +# this option to zero, but we recommend that all users enable this. + +KeepAliveTime = 120 + __E } else @@ -352,13 +392,17 @@ FileTrackingSizeThreshold = 65535 DiffingUploadSizeThreshold = 8192 -# The limit on how much time is spent diffing files. Most files shouldn't take very long, -# but if you have really big files you can use this to limit the time spent diffing them. +# The limit on how much time is spent diffing files, in seconds. Most files +# shouldn't take very long, but if you have really big files you can use this +# to limit the time spent diffing them. +# # * Reduce if you are having problems with processor usage. -# * Increase if you have large files, and think the upload of changes is too large and want -# to spend more time searching for unchanged blocks. +# +# * Increase if you have large files, and think the upload of changes is too +# large and you want bbackupd to spend more time searching for unchanged +# blocks. -MaximumDiffingTime = 20 +MaximumDiffingTime = 120 # Uncomment this line to see exactly what the daemon is going when it's connected to the server. @@ -366,14 +410,20 @@ MaximumDiffingTime = 20 # ExtendedLogging = yes -# Use this to temporarily stop bbackupd from syncronising or connecting to the store. -# This specifies a program or script script which is run just before each sync, and ideally -# the full path to the interpreter. It will be run as the same user bbackupd is running as, -# usually root. -# The script prints either "now" or a number to STDOUT (and a terminating newline, no quotes). -# If the result was "now", then the sync will happen. If it's a number, then the script will -# be asked again in that number of seconds. -# For example, you could use this on a laptop to only backup when on a specific network. +# This specifies a program or script script which is run just before each +# sync, and ideally the full path to the interpreter. It will be run as the +# same user bbackupd is running as, usually root. +# +# The script must output (print) either "now" or a number to STDOUT (and a +# terminating newline, no quotes). +# +# If the result was "now", then the sync will happen. If it's a number, then +# no backup will happen for that number of seconds (bbackupd will pause) and +# then the script will be run again. +# +# Use this to temporarily stop bbackupd from syncronising or connecting to the +# store. For example, you could use this on a laptop to only backup when on a +# specific network, or when it has a working Internet connection. # SyncAllowScript = /path/to/intepreter/or/exe script-name parameters etc @@ -396,7 +446,7 @@ Server PidFile = /var/run/bbackupd.pid } -# + # BackupLocations specifies which locations on disc should be backed up. Each # directory is in the format # @@ -425,15 +475,31 @@ Server # files, except one MP3 file in particular. # # In general, Exclude excludes a file or directory, unless the directory is -# explicitly mentioned in a AlwaysInclude directive. +# explicitly mentioned in a AlwaysInclude directive. However, Box Backup +# does NOT scan inside excluded directories and will never back up an +# AlwaysIncluded file or directory inside an excluded directory or any +# subdirectory thereof. +# +# To back up a directory inside an excluded directory, use a configuration +# like this, to ensure that each directory in the path to the important +# files is included, but none of their contents will be backed up except +# the directories futher down that path to the important one. +# +# ExcludeDirsRegex = /home/user/bigfiles/.* +# ExcludeFilesRegex = /home/user/bigfiles/.* +# AlwaysIncludeDir = /home/user/bigfiles/path +# AlwaysIncludeDir = /home/user/bigfiles/path/to +# AlwaysIncludeDir = /home/user/bigfiles/path/important +# AlwaysIncludeDir = /home/user/bigfiles/path/important/files +# AlwaysIncludeDirsRegex = /home/user/bigfiles/path/important/files/.* +# AlwaysIncludeFilesRegex = /home/user/bigfiles/path/important/files/.* # -# If a directive ends in Regex, then it is a regular expression rather than a +# If a directive ends in Regex, then it is a regular expression rather than a # explicit full pathname. See # # man 7 re_format # # for the regex syntax on your platform. -# BackupLocations { diff --git a/bin/bbackupd/bbackupd.cpp b/bin/bbackupd/bbackupd.cpp index e00d3628..e094d499 100644 --- a/bin/bbackupd/bbackupd.cpp +++ b/bin/bbackupd/bbackupd.cpp @@ -12,6 +12,7 @@ #include "MainHelper.h" #include "BoxPortsAndFiles.h" #include "BackupStoreException.h" +#include "Logging.h" #include "MemLeakFindOn.h" @@ -24,12 +25,16 @@ int main(int argc, const char *argv[]) { + int ExitCode = 0; + MAINHELPER_START + Logging::SetProgramName("Box Backup (bbackupd)"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + #ifdef WIN32 - ::openlog("Box Backup (bbackupd)", LOG_PID, LOG_LOCAL6); - if(argc == 2 && (::strcmp(argv[1], "--help") == 0 || ::strcmp(argv[1], "-h") == 0)) @@ -62,11 +67,9 @@ int main(int argc, const char *argv[]) EnableBackupRights(); - int ExitCode = 0; - if (runAsWin32Service) { - syslog(LOG_INFO, "Box Backup service starting"); + BOX_INFO("Box Backup service starting"); char* config = NULL; if (argc >= 3) @@ -74,33 +77,31 @@ int main(int argc, const char *argv[]) config = strdup(argv[2]); } - OurService(config); + ExitCode = OurService(config); if (config) { free(config); } - syslog(LOG_INFO, "Box Backup service shut down"); + BOX_INFO("Box Backup service shut down"); } else { ExitCode = gpDaemonService->Main( - BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); + BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE, argc, argv); } - ::closelog(); - delete gpDaemonService; - return ExitCode; - #else // !WIN32 BackupDaemon daemon; - return daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); + ExitCode = daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); #endif // WIN32 MAINHELPER_END + + return ExitCode; } diff --git a/bin/bbackupd/win32/ReadMe.txt b/bin/bbackupd/win32/ReadMe.txt deleted file mode 100644 index 3d260750..00000000 --- a/bin/bbackupd/win32/ReadMe.txt +++ /dev/null @@ -1,24 +0,0 @@ -Upgrade instructions - -Version 0.09g to 0.09h - -This version included patches to the server as well. The server for this -upgrade can be found at http://home.earthlink.net/~gniemcew/ but hopefully -will be merged into the core in the next release. - -New values in the bbackupd.conf can now be added: - -StoreObjectInfoFile = C:\Program Files\Box Backup\bbackupd\bbackupd.dat - -This stores the state when a backup daemon is shutdown. - -KeepAliveTime = 250 - -This is imperative if MaximumDiffingTime is larger than 300, this stops the ssl -layer timing out when a diff is performed. It is wise to set MaximumDiffingTime -long enough for the largest file you may have. If you do not wish to upgrade your -server then make KeepAliveTime greater than MaximumDiffingTime. - -Have fun - -Nick diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp index be5eeb72..09003d4d 100644 --- a/bin/bbackupquery/BackupQueries.cpp +++ b/bin/bbackupquery/BackupQueries.cpp @@ -46,6 +46,8 @@ #include "BackupStoreException.h" #include "ExcludeList.h" #include "BackupClientMakeExcludeList.h" +#include "PathUtils.h" +#include "Logging.h" #include "MemLeakFindOn.h" @@ -53,10 +55,10 @@ #undef min #undef max -#define COMPARE_RETURN_SAME 1 +#define COMPARE_RETURN_SAME 1 #define COMPARE_RETURN_DIFFERENT 2 #define COMPARE_RETURN_ERROR 3 - +#define COMMAND_RETURN_ERROR 4 // -------------------------------------------------------------------------- // @@ -179,7 +181,7 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) if (!ConvertEncoding(*i, CP_ACP, converted, GetConsoleCP())) { - printf("Failed to convert encoding"); + BOX_ERROR("Failed to convert encoding"); return; } *i = converted; @@ -206,7 +208,7 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) { "sh", "" }, { "getobject", "" }, { "get", "i" }, - { "compare", "alcqAE" }, + { "compare", "alcqAEQ" }, { "restore", "dri" }, { "help", "" }, { "usage", "" }, @@ -253,7 +255,7 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) // No such command if(alias[a] == 0) { - printf("Unrecognised command: %s\n", Command); + BOX_ERROR("Unrecognised command: " << Command); return; } } @@ -273,8 +275,8 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) // Valid option? if(::strchr(commands[cmd].opts, *c) == NULL) { - printf("Invalid option '%c' for command %s\n", - *c, commands[cmd].name); + BOX_ERROR("Invalid option '" << *c << "' for " + "command " << commands[cmd].name); return; } opts[(int)*c] = true; @@ -303,9 +305,8 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) case COMMAND_pwd: { // Simple implementation, so do it here - printf("%s (%08llx)\n", - GetCurrentDirectoryName().c_str(), - (long long)GetCurrentDirectoryID()); + BOX_INFO(GetCurrentDirectoryName() << " (" << + BOX_FORMAT_OBJECTID(GetCurrentDirectoryID())); } break; @@ -318,7 +319,7 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) break; case COMMAND_sh: - printf("The command to run must be specified as an argument.\n"); + BOX_ERROR("The command to run must be specified as an argument."); break; case COMMAND_GetObject: @@ -399,8 +400,8 @@ void BackupQueries::CommandList(const std::vector<std::string> &args, const bool if(rootDir == 0) { - printf("Directory '%s' not found on store\n", - args[0].c_str()); + BOX_ERROR("Directory '" << args[0] << "' not found " + "on store."); return; } } @@ -745,7 +746,7 @@ void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const { if(args.size() != 1 || args[0].size() == 0) { - printf("Incorrect usage.\ncd [-o] [-d] <directory>\n"); + BOX_ERROR("Incorrect usage. cd [-o] [-d] <directory>"); return; } @@ -762,7 +763,7 @@ void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const if(id == 0) { - printf("Directory '%s' not found\n", args[0].c_str()); + BOX_ERROR("Directory '" << args[0] << "' not found."); return; } @@ -783,22 +784,37 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) { if(args.size() != 1 || args[0].size() == 0) { - printf("Incorrect usage.\nlcd <local-directory>\n"); + BOX_ERROR("Incorrect usage. lcd <local-directory>"); + SetReturnCode(COMMAND_RETURN_ERROR); return; } // Try changing directory #ifdef WIN32 std::string dirName; - if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return; + if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) + { + BOX_ERROR("Failed to convert path from console encoding."); + SetReturnCode(COMMAND_RETURN_ERROR); + return; + } int result = ::chdir(dirName.c_str()); #else int result = ::chdir(args[0].c_str()); #endif if(result != 0) { - printf((errno == ENOENT || errno == ENOTDIR)?"Directory '%s' does not exist\n":"Error changing dir to '%s'\n", - args[0].c_str()); + if(errno == ENOENT || errno == ENOTDIR) + { + BOX_ERROR("Directory '" << args[0] << "' does not exist."); + } + else + { + BOX_ERROR("Error changing to directory '" << + args[0] << ": " << strerror(errno)); + } + + SetReturnCode(COMMAND_RETURN_ERROR); return; } @@ -806,15 +822,22 @@ void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) char wd[PATH_MAX]; if(::getcwd(wd, PATH_MAX) == 0) { - printf("Error getting current directory\n"); + BOX_ERROR("Error getting current directory: " << + strerror(errno)); + SetReturnCode(COMMAND_RETURN_ERROR); return; } #ifdef WIN32 - if(!ConvertUtf8ToConsole(wd, dirName)) return; - printf("Local current directory is now '%s'\n", dirName.c_str()); + if(!ConvertUtf8ToConsole(wd, dirName)) + { + BOX_ERROR("Failed to convert new path from console encoding."); + SetReturnCode(COMMAND_RETURN_ERROR); + return; + } + BOX_INFO("Local current directory is now '" << dirName << "'."); #else - printf("Local current directory is now '%s'\n", wd); + BOX_INFO("Local current directory is now '" << wd << "'."); #endif } @@ -832,14 +855,15 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const // Check args if(args.size() != 2) { - printf("Incorrect usage.\ngetobject <object-id> <local-filename>\n"); + BOX_ERROR("Incorrect usage. getobject <object-id> " + "<local-filename>"); return; } int64_t id = ::strtoll(args[0].c_str(), 0, 16); if(id == std::numeric_limits<long long>::min() || id == std::numeric_limits<long long>::max() || id == 0) { - printf("Not a valid object ID (specified in hex)\n"); + BOX_ERROR("Not a valid object ID (specified in hex)."); return; } @@ -847,7 +871,7 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const struct stat st; if(::stat(args[1].c_str(), &st) == 0 || errno != ENOENT) { - printf("The local file %s already exists\n", args[1].c_str()); + BOX_ERROR("The local file '" << args[1] << " already exists."); return; } @@ -865,18 +889,20 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream()); objectStream->CopyStreamTo(out); - printf("Object ID %08llx fetched successfully.\n", id); + BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(id) << + " fetched successfully."); } else { - printf("Object does not exist on store.\n"); + BOX_ERROR("Object ID " << BOX_FORMAT_OBJECTID(id) << + " does not exist on store."); ::unlink(args[1].c_str()); } } catch(...) { ::unlink(args[1].c_str()); - printf("Error occured fetching object.\n"); + BOX_ERROR("Error occured fetching object."); } } @@ -896,15 +922,17 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) // Check args if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2) { - printf("Incorrect usage.\n" + BOX_ERROR("Incorrect usage.\n" "get <remote-filename> [<local-filename>] or\n" - "get -i <object-id> <local-filename>\n"); + "get -i <object-id> <local-filename>"); return; } // Find object ID somehow - int64_t id; + int64_t fileId; + int64_t dirId = GetCurrentDirectoryID(); std::string localName; + // BLOCK { #ifdef WIN32 @@ -914,7 +942,7 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) std::string out; if(!ConvertConsoleToUtf8(i->c_str(), out)) { - fprintf(stderr, "failed to convert encoding\n"); + BOX_ERROR("Failed to convert encoding."); return; } *i = out; @@ -923,11 +951,30 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) std::string fileName(args[0]); + 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; + } + } + } + BackupStoreFilenameClear fn(fileName); // Need to look it up in the current directory mrConnection.QueryListDirectory( - GetCurrentDirectoryID(), + 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 */); @@ -940,17 +987,24 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) if(opts['i']) { // Specified as ID. - id = ::strtoll(args[0].c_str(), 0, 16); - if(id == std::numeric_limits<long long>::min() || id == std::numeric_limits<long long>::max() || id == 0) + 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) { - printf("Not a valid object ID (specified in hex)\n"); + BOX_ERROR("Not a valid object ID (specified in hex)."); return; } // Check that the item is actually in the directory - if(dir.FindEntryByID(id) == 0) + if(dir.FindEntryByID(fileId) == 0) { - printf("ID '%08llx' not found in current directory on store.\n(You can only download objects by ID from the current directory.)\n", id); + 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; } @@ -965,14 +1019,19 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) if(en == 0) { - printf("Filename '%s' not found in current directory on store.\n(Subdirectories in path not searched.)\n", args[0].c_str()); + BOX_ERROR("Filename '" << args[0] << "' " + "not found in current " + "directory on store.\n" + "(Subdirectories in path not " + "searched.)"); return; } - id = en->GetObjectID(); + fileId = en->GetObjectID(); - // Local name is the last argument, which is either the looked up filename, or - // a filename specified by the user. + // 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]; } } @@ -981,7 +1040,9 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) struct stat st; if(::stat(localName.c_str(), &st) == 0 || errno != ENOENT) { - printf("The local file %s already exists, will not overwrite it.\n", localName.c_str()); + BOX_ERROR("The local file " << localName << " already exists, " + "will not overwrite it."); + SetReturnCode(COMMAND_RETURN_ERROR); return; } @@ -989,7 +1050,7 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) try { // Request object - mrConnection.QueryGetFile(GetCurrentDirectoryID(), id); + mrConnection.QueryGetFile(dirId, fileId); // Stream containing encoded file std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream()); @@ -998,12 +1059,25 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout()); // Done. - printf("Object ID %08llx fetched sucessfully.\n", id); + BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(fileId) << + " fetched successfully."); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to fetch file: " << + e.what()); + ::unlink(localName.c_str()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to fetch file: " << + e.what()); + ::unlink(localName.c_str()); } catch(...) { + BOX_ERROR("Failed to fetch file: unknown error"); ::unlink(localName.c_str()); - printf("Error occured fetching file.\n"); } } @@ -1022,6 +1096,7 @@ BackupQueries::CompareParams::CompareParams() mIgnoreAttributes(false), mDifferences(0), mDifferencesExplainedByModTime(0), + mUncheckedFiles(0), mExcludedDirs(0), mExcludedFiles(0), mpExcludeFiles(0), @@ -1081,6 +1156,7 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b // Parameters, including count of differences BackupQueries::CompareParams params; params.mQuickCompare = opts['q']; + params.mQuietCompare = opts['Q']; params.mIgnoreExcludes = opts['E']; params.mIgnoreAttributes = opts['A']; @@ -1098,14 +1174,16 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b } else { - printf("Warning: couldn't determine the time of the last synchronisation -- checks not performed.\n"); + BOX_WARNING("Failed to determine the time of the last " + "synchronisation -- checks not performed."); } } // Quick compare? if(params.mQuickCompare) { - printf("WARNING: Quick compare used -- file attributes are not checked.\n"); + BOX_WARNING("Quick compare used -- file attributes are not " + "checked."); } if(!opts['l'] && opts['a'] && args.size() == 0) @@ -1130,7 +1208,7 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b // 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) { - printf("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes\n"); + BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes."); return; } else @@ -1141,17 +1219,38 @@ void BackupQueries::CommandCompare(const std::vector<std::string> &args, const b } else { - printf("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>\n"); + BOX_ERROR("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>"); return; } - - printf("\n[ %d (of %d) differences probably due to file modifications after the last upload ]\nDifferences: %d (%d dirs excluded, %d files excluded)\n", - params.mDifferencesExplainedByModTime, params.mDifferences, params.mDifferences, params.mExcludedDirs, params.mExcludedFiles); + + if (!params.mQuietCompare) + { + BOX_INFO("[ " << + params.mDifferencesExplainedByModTime << " (of " << + params.mDifferences << ") differences probably " + "due to file modifications after the last upload ]"); + } + + BOX_INFO("Differences: " << params.mDifferences << " (" << + params.mExcludedDirs << " dirs excluded, " << + params.mExcludedFiles << " files excluded, " << + params.mUncheckedFiles << " files not checked)"); // Set return code? if(opts['c']) { - SetReturnCode((params.mDifferences == 0)?COMPARE_RETURN_SAME:COMPARE_RETURN_DIFFERENT); + if (params.mUncheckedFiles != 0) + { + SetReturnCode(COMPARE_RETURN_ERROR); + } + else if (params.mDifferences != 0) + { + SetReturnCode(COMPARE_RETURN_DIFFERENT); + } + else + { + SetReturnCode(COMPARE_RETURN_SAME); + } } } @@ -1170,10 +1269,23 @@ void BackupQueries::CompareLocation(const std::string &rLocation, BackupQueries: const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations")); if(!locations.SubConfigurationExists(rLocation.c_str())) { - printf("Location %s does not exist.\n", rLocation.c_str()); + BOX_ERROR("Location " << rLocation << " does not exist."); return; } const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str())); + + #ifdef WIN32 + { + std::string path = loc.GetKeyValue("Path"); + if (path.size() > 0 && path[path.size()-1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + BOX_WARNING("Location '" << rLocation << "' path ends " + "with '" DIRECTORY_SEPARATOR "', " + "compare may fail!"); + } + } + #endif try { @@ -1223,9 +1335,9 @@ void BackupQueries::Compare(const std::string &rStoreDir, const std::string &rLo // Found? if(dirID == 0) { - printf("Local directory '%s' exists, but " - "server directory '%s' does not exist\n", - rLocalDir.c_str(), rStoreDir.c_str()); + BOX_WARNING("Local directory '" << rLocalDir << "' exists, " + "but server directory '" << rStoreDir << "' does not " + "exist."); rParams.mDifferences ++; return; } @@ -1273,23 +1385,24 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // What kind of error? if(errno == ENOTDIR) { - printf("Local object '%s' is a file, " - "server object '%s' is a directory\n", - localDirDisplay.c_str(), - storeDirDisplay.c_str()); + BOX_WARNING("Local object '" << localDirDisplay << "' " + "is a file, server object '" << + storeDirDisplay << "' is a directory."); rParams.mDifferences ++; } else if(errno == ENOENT) { - printf("Local directory '%s' does not exist " - "(compared to server directory '%s')\n", - localDirDisplay.c_str(), - storeDirDisplay.c_str()); + BOX_WARNING("Local directory '" << localDirDisplay << + "' does not exist (compared to server " + "directory '" << storeDirDisplay << "')."); + rParams.mDifferences ++; } else { - printf("ERROR: stat on local dir '%s'\n", - localDirDisplay.c_str()); + BOX_WARNING("Failed to access local directory '" << + localDirDisplay << ": " << strerror(errno) << + "'."); + rParams.mUncheckedFiles ++; } return; } @@ -1309,8 +1422,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // Test out the attributes if(!dir.HasAttributes()) { - printf("Store directory '%s' doesn't have attributes.\n", - storeDirDisplay.c_str()); + BOX_WARNING("Store directory '" << storeDirDisplay << "' " + "doesn't have attributes."); } else { @@ -1325,10 +1438,9 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s if(!(attr.Compare(localAttr, true, true /* ignore modification times */))) { - printf("Local directory '%s' has different attributes " - "to store directory '%s'.\n", - localDirDisplay.c_str(), - storeDirDisplay.c_str()); + BOX_WARNING("Local directory '" << localDirDisplay << + "' has different attributes to store " + "directory '" << storeDirDisplay << "'."); rParams.mDifferences ++; } } @@ -1337,8 +1449,9 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s DIR *dirhandle = ::opendir(rLocalDir.c_str()); if(dirhandle == 0) { - printf("ERROR: opendir on local dir '%s'\n", - localDirDisplay.c_str()); + BOX_WARNING("Failed to open local directory '" << + localDirDisplay << "': " << strerror(errno)); + rParams.mUncheckedFiles ++; return; } try @@ -1358,9 +1471,9 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s #ifdef HAVE_VALID_DIRENT_D_TYPE if (localDirEn->d_type != DT_DIR) { - fprintf(stderr, "ERROR: d_type does " - "not really work on your " - "platform. Reconfigure Box!\n"); + BOX_ERROR("d_type does not really " + "work on your platform. " + "Reconfigure Box!"); return; } #endif @@ -1369,9 +1482,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s } #ifndef HAVE_VALID_DIRENT_D_TYPE - std::string fn(rLocalDir); - fn += DIRECTORY_SEPARATOR_ASCHAR; - fn += localDirEn->d_name; + std::string fn(MakeFullPath + (rLocalDir, localDirEn->d_name)); struct stat st; if(::lstat(fn.c_str(), &st) != 0) { @@ -1406,8 +1518,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // Close directory if(::closedir(dirhandle) != 0) { - printf("ERROR: closedir on local dir '%s'\n", - localDirDisplay.c_str()); + BOX_ERROR("Failed to close local directory '" << + localDirDisplay << "': " << strerror(errno)); } dirhandle = 0; @@ -1455,29 +1567,28 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s const std::string& fileNameDisplay(i->first); #endif - std::string localPathDisplay = localDirDisplay + - DIRECTORY_SEPARATOR + fileNameDisplay; - std::string storePathDisplay = storeDirDisplay + - "/" + fileNameDisplay; + std::string localPath(MakeFullPath + (rLocalDir, fileName)); + std::string localPathDisplay(MakeFullPath + (localDirDisplay, fileNameDisplay)); + std::string storePathDisplay + (storeDirDisplay + "/" + fileNameDisplay); // Does the file exist locally? string_set_iter_t local(localFiles.find(fileName)); if(local == localFiles.end()) { // Not found -- report - printf("Local file '%s' does not exist, " - "but store file '%s' does.\n", - localPathDisplay.c_str(), - storePathDisplay.c_str()); + BOX_WARNING("Local file '" << + localDirDisplay << "' does not exist, " + "but store file '" << + storePathDisplay << "' does."); rParams.mDifferences ++; } else { try { - // make local name of file for comparison - std::string localPath(rLocalDir + DIRECTORY_SEPARATOR + fileName); - // Files the same flag? bool equal = true; @@ -1505,7 +1616,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s // Decode it std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream; - // Got additional attibutes? + // Got additional attributes? if(i->second->HasAttributes()) { // Use these attributes @@ -1539,24 +1650,28 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s #endif if(!rParams.mIgnoreAttributes && + #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE + !fileOnServerStream->IsSymLink() && + #endif !localAttr.Compare(fileOnServerStream->GetAttributes(), ignoreAttrModTime, fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */)) { - printf("Local file '%s' " - "has different attributes " - "to store file '%s'.\n", - localPathDisplay.c_str(), - storePathDisplay.c_str()); + BOX_WARNING("Local file '" << + localPathDisplay << + "' has different attributes " + "to store file '" << + storePathDisplay << + "'."); rParams.mDifferences ++; if(modifiedAfterLastSync) { rParams.mDifferencesExplainedByModTime ++; - printf("(the file above was modified after the last sync time -- might be reason for difference)\n"); + BOX_INFO("(the file above was modified after the last sync time -- might be reason for difference)"); } else if(i->second->HasAttributes()) { - printf("(the file above has had new attributes applied)\n"); + BOX_INFO("(the file above has had new attributes applied)\n"); } } @@ -1595,7 +1710,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s equal = false; } - // Must always read the entire decoded string, if it's not a symlink + // Must always read the entire decoded stream, if it's not a symlink if(fileOnServerStream->StreamDataLeft()) { // Absorb all the data remaining @@ -1605,39 +1720,65 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s 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()); + } + } } } // Report if not equal. if(!equal) { - printf("Local file '%s' " + BOX_WARNING("Local file '" << + localPathDisplay << "' " "has different contents " - "to store file '%s'.\n", - localPathDisplay.c_str(), - storePathDisplay.c_str()); + "to store file '" << + storePathDisplay << + "'."); rParams.mDifferences ++; if(modifiedAfterLastSync) { rParams.mDifferencesExplainedByModTime ++; - printf("(the file above was modified after the last sync time -- might be reason for difference)\n"); + BOX_INFO("(the file above was modified after the last sync time -- might be reason for difference)"); } else if(i->second->HasAttributes()) { - printf("(the file above has had new attributes applied)\n"); + BOX_INFO("(the file above has had new attributes applied)\n"); } } } catch(BoxException &e) { - printf("ERROR: (%d/%d) during file fetch and comparison for '%s'\n", - e.GetType(), - e.GetSubType(), - storePathDisplay.c_str()); + BOX_ERROR("Failed to fetch and compare " + "'" << + storePathDisplay.c_str() << + "': error " << e.what() << + " (" << e.GetType() << + "/" << e.GetSubType() << ")"); + rParams.mUncheckedFiles ++; } - catch(...) + catch(std::exception &e) { - printf("ERROR: (unknown) during file fetch and comparison for '%s'\n", storePathDisplay.c_str()); + BOX_ERROR("Failed to fetch and compare " + "'" << + storePathDisplay.c_str() << + "': " << e.what()); + } + catch(...) + { + BOX_ERROR("Failed to fetch and compare " + "'" << + storePathDisplay.c_str() << + "': unknown error"); + rParams.mUncheckedFiles ++; } // Remove from set so that we know it's been compared @@ -1658,22 +1799,22 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s const std::string& fileNameDisplay(*i); #endif - std::string localPath(rLocalDir + - DIRECTORY_SEPARATOR + *i); - std::string localPathDisplay(localDirDisplay + - DIRECTORY_SEPARATOR + fileNameDisplay); - std::string storePathDisplay(storeDirDisplay + - "/" + fileNameDisplay); + std::string localPath(MakeFullPath + (rLocalDir, *i)); + std::string localPathDisplay(MakeFullPath + (localDirDisplay, fileNameDisplay)); + std::string storePathDisplay + (storeDirDisplay + "/" + fileNameDisplay); // Should this be ignored (ie is excluded)? if(rParams.mpExcludeFiles == 0 || !(rParams.mpExcludeFiles->IsExcluded(localPath))) { - printf("Local file '%s' exists, " - "but store file '%s' " - "does not exist.\n", - localPathDisplay.c_str(), - storePathDisplay.c_str()); + BOX_WARNING("Local file '" << + localPathDisplay << + "' exists, but store file '" << + storePathDisplay << + "' does not."); rParams.mDifferences ++; // Check the file modification time @@ -1684,7 +1825,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s if(FileModificationTime(st) > rParams.mLatestFileUploadTime) { rParams.mDifferencesExplainedByModTime ++; - printf("(the file above was modified after the last sync time -- might be reason for difference)\n"); + BOX_INFO("(the file above was modified after the last sync time -- might be reason for difference)"); } } } @@ -1699,7 +1840,7 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s localFiles.clear(); storeFiles.clear(); - // Now do the directories, recusively to check subdirectories + // 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 @@ -1713,26 +1854,44 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s const std::string& subdirNameDisplay(i->first); #endif - std::string localPathDisplay = localDirDisplay + - DIRECTORY_SEPARATOR + subdirNameDisplay; - std::string storePathDisplay = storeDirDisplay + - "/" + subdirNameDisplay; + std::string localPath(MakeFullPath + (rLocalDir, i->first)); + std::string localPathDisplay(MakeFullPath + (localDirDisplay, subdirNameDisplay)); + std::string storePathDisplay + (storeDirDisplay + "/" + subdirNameDisplay); // Does the directory exist locally? string_set_iter_t local(localDirs.find(i->first)); - if(local == localDirs.end()) + if(local == localDirs.end() && + rParams.mpExcludeDirs != NULL && + rParams.mpExcludeDirs->IsExcluded(localPath)) + { + // Not found -- report + BOX_WARNING("Local directory '" << + localPathDisplay << "' is excluded, " + "but store directory '" << + storePathDisplay << "' still exists."); + rParams.mDifferences ++; + } + else if(local == localDirs.end()) { // Not found -- report - printf("Local directory '%s' does not exist, " - "but store directory '%s' does.\n", - localPathDisplay.c_str(), - storePathDisplay.c_str()); + BOX_WARNING("Local directory '" << + localPathDisplay << "' does not exist, " + "but store directory '" << + storePathDisplay << "' does."); rParams.mDifferences ++; } + else if(rParams.mpExcludeDirs != NULL && + rParams.mpExcludeDirs->IsExcluded(localPath)) + { + // don't recurse into excluded directories + } else { // Compare directory - Compare(i->second->GetObjectID(), rStoreDir + "/" + i->first, rLocalDir + DIRECTORY_SEPARATOR + i->first, rParams); + Compare(i->second->GetObjectID(), rStoreDir + "/" + i->first, localPath, rParams); // Remove from set so that we know it's been compared localDirs.erase(local); @@ -1752,23 +1911,23 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, const s const std::string& fileNameDisplay(*i); #endif - std::string localPath = rLocalDir + - DIRECTORY_SEPARATOR + *i; - std::string storePath = rStoreDir + - "/" + *i; + std::string localPath(MakeFullPath + (rLocalDir, *i)); + std::string localPathDisplay(MakeFullPath + (localDirDisplay, fileNameDisplay)); - std::string localPathDisplay = localDirDisplay + - DIRECTORY_SEPARATOR + fileNameDisplay; - std::string storePathDisplay = storeDirDisplay + - "/" + fileNameDisplay; + std::string storePath + (rStoreDir + "/" + *i); + std::string storePathDisplay + (storeDirDisplay + "/" + fileNameDisplay); // Should this be ignored (ie is excluded)? if(rParams.mpExcludeDirs == 0 || !(rParams.mpExcludeDirs->IsExcluded(localPath))) { - printf("Local directory '%s' exists, but " - "store directory '%s' does not exist.\n", - localPathDisplay.c_str(), - storePathDisplay.c_str()); + BOX_WARNING("Local directory '" << + localPathDisplay << "' exists, but " + "store directory '" << + storePathDisplay << "' does not."); rParams.mDifferences ++; } else @@ -1801,7 +1960,7 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b // Check arguments if(args.size() != 2) { - printf("Incorrect usage.\nrestore [-d] [-r] [-i] <directory-name> <local-directory-name>\n"); + BOX_ERROR("Incorrect usage. restore [-d] [-r] [-i] <remote-name> <local-name>"); return; } @@ -1816,7 +1975,7 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b dirID = ::strtoll(args[0].c_str(), 0, 16); if(dirID == std::numeric_limits<long long>::min() || dirID == std::numeric_limits<long long>::max() || dirID == 0) { - printf("Not a valid object ID (specified in hex)\n"); + BOX_ERROR("Not a valid object ID (specified in hex)"); return; } } @@ -1839,12 +1998,12 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b // Allowable? if(dirID == 0) { - printf("Directory '%s' not found on server\n", args[0].c_str()); + BOX_ERROR("Directory '" << args[0] << "' not found on server"); return; } if(dirID == BackupProtocolClientListDirectory::RootDirectory) { - printf("Cannot restore the root directory -- restore locations individually.\n"); + BOX_ERROR("Cannot restore the root directory -- restore locations individually."); return; } @@ -1856,25 +2015,55 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b #endif // Go and restore... - switch(BackupClientRestore(mrConnection, dirID, localName.c_str(), - true /* print progress dots */, restoreDeleted, - false /* don't undelete after restore! */, - opts['r'] /* resume? */)) + int result; + + try + { + result = BackupClientRestore(mrConnection, dirID, + localName.c_str(), + true /* print progress dots */, restoreDeleted, + false /* don't undelete after restore! */, + opts['r'] /* resume? */); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore: " << e.what()); + return; + } + catch(...) + { + BOX_ERROR("Failed to restore: unknown exception"); + return; + } + + switch(result) { case Restore_Complete: - printf("Restore complete\n"); + BOX_INFO("Restore complete."); break; case Restore_ResumePossible: - printf("Resume possible -- repeat command with -r flag to resume\n"); + BOX_ERROR("Resume possible -- repeat command with -r flag to resume"); break; case Restore_TargetExists: - printf("The target directory exists. You cannot restore over an existing directory.\n"); + BOX_ERROR("The target directory exists. You cannot restore over an existing directory."); 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."); + break; + #endif + + case Restore_UnknownError: + BOX_ERROR("Unknown error during restore."); + break; + default: - printf("ERROR: Unknown restore result.\n"); + BOX_ERROR("Unknown restore result " << result << "."); break; } } @@ -1994,7 +2183,7 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const // Check arguments if(args.size() != 1) { - printf("Incorrect usage.\nundelete <directory-name>\n"); + BOX_ERROR("Incorrect usage. undelete <directory-name>"); return; } @@ -2012,12 +2201,12 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const // Allowable? if(dirID == 0) { - printf("Directory '%s' not found on server\n", args[0].c_str()); + BOX_ERROR("Directory '" << args[0] << "' not found on server."); return; } if(dirID == BackupProtocolClientListDirectory::RootDirectory) { - printf("Cannot undelete the root directory.\n"); + BOX_ERROR("Cannot undelete the root directory."); return; } diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h index 17043b61..b2ef8cc2 100644 --- a/bin/bbackupquery/BackupQueries.h +++ b/bin/bbackupquery/BackupQueries.h @@ -67,10 +67,12 @@ private: ~CompareParams(); void DeleteExcludeLists(); bool mQuickCompare; + bool mQuietCompare; bool mIgnoreExcludes; bool mIgnoreAttributes; int mDifferences; int mDifferencesExplainedByModTime; + int mUncheckedFiles; int mExcludedDirs; int mExcludedFiles; const ExcludeList *mpExcludeFiles; diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp index ef271af1..98726843 100644 --- a/bin/bbackupquery/bbackupquery.cpp +++ b/bin/bbackupquery/bbackupquery.cpp @@ -12,8 +12,14 @@ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif + +#include <errno.h> #include <stdio.h> -#include <sys/types.h> + +#ifdef HAVE_SYS_TYPES_H + #include <sys/types.h> +#endif + #ifdef HAVE_LIBREADLINE #ifdef HAVE_READLINE_READLINE_H #include <readline/readline.h> @@ -45,6 +51,7 @@ #include "FdGetLine.h" #include "BackupClientCryptoKeys.h" #include "BannerText.h" +#include "Logging.h" #include "MemLeakFindOn.h" @@ -63,7 +70,11 @@ void PrintUsageAndExit() int main(int argc, const char *argv[]) { - MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks", "bbackupquery") + int returnCode = 0; + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks", + "bbackupquery") + MAINHELPER_START #ifdef WIN32 WSADATA info; @@ -73,7 +84,7 @@ int main(int argc, const char *argv[]) if (WSAStartup(0x0101, &info) == SOCKET_ERROR) { - // throw error? perhaps give it its own id in the furture + // throw error? perhaps give it its own id in the future THROW_EXCEPTION(BackupStoreException, Internal) } #endif @@ -83,24 +94,34 @@ int main(int argc, const char *argv[]) BoxDebugTraceOn = false; #endif - int returnCode = 0; - - MAINHELPER_START - FILE *logFile = 0; // Filename for configuration file? - const char *configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG; + std::string configFilename; + + #ifdef WIN32 + configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; + #else + configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG; + #endif // Flags bool quiet = false; bool readWrite = false; + Logging::SetProgramName("Box Backup (bbackupquery)"); + + #ifdef NDEBUG + 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 = "qwuc:l:"; + const char* validOpts = "qvwuc:l:"; bool unicodeConsole = false; #else - const char* validOpts = "qwc:l:"; + const char* validOpts = "qvwc:l:"; #endif // See if there's another entry on the command line @@ -109,11 +130,35 @@ int main(int argc, const char *argv[]) { switch(c) { - case 'q': - // Quiet mode - quiet = true; + case 'q': + { + // Quiet mode + quiet = true; + + if(masterLevel == Log::NOTHING) + { + BOX_FATAL("Too many '-q': " + "Cannot reduce logging " + "level any more"); + return 2; + } + masterLevel--; + } break; - + + case 'v': + { + if(masterLevel == Log::EVERYTHING) + { + BOX_FATAL("Too many '-v': " + "Cannot increase logging " + "level any more"); + return 2; + } + masterLevel++; + } + break; + case 'w': // Read/write mode readWrite = true; @@ -129,7 +174,8 @@ int main(int argc, const char *argv[]) logFile = ::fopen(optarg, "w"); if(logFile == 0) { - printf("Can't open log file '%s'\n", optarg); + BOX_ERROR("Failed to open log file '" << + optarg << "': " << strerror(errno)); } break; @@ -148,11 +194,13 @@ int main(int argc, const char *argv[]) argc -= optind; argv += optind; + Logging::SetGlobalLevel((Log::Level)masterLevel); + // Print banner? if(!quiet) { const char *banner = BANNER_TEXT("Backup Query Tool"); - printf(banner); + BOX_NOTICE(banner); } #ifdef WIN32 @@ -160,14 +208,14 @@ int main(int argc, const char *argv[]) { if (!SetConsoleCP(CP_UTF8)) { - fprintf(stderr, "Failed to set input codepage: " - "error %d\n", GetLastError()); + BOX_ERROR("Failed to set input codepage: " << + GetErrorMessage(GetLastError())); } if (!SetConsoleOutputCP(CP_UTF8)) { - fprintf(stderr, "Failed to set output codepage: " - "error %d\n", GetLastError()); + BOX_ERROR("Failed to set output codepage: " << + GetErrorMessage(GetLastError())); } // enable input of Unicode characters @@ -181,12 +229,16 @@ int main(int argc, const char *argv[]) #endif // WIN32 // Read in the configuration file - if(!quiet) printf("Using configuration file %s\n", configFilename); + if(!quiet) BOX_INFO("Using configuration file " << configFilename); + std::string errs; - std::auto_ptr<Configuration> config(Configuration::LoadAndVerify(configFilename, &BackupDaemonConfigVerify, errs)); + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + (configFilename, &BackupDaemonConfigVerify, errs)); + if(config.get() == 0 || !errs.empty()) { - printf("Invalid configuration file:\n%s", errs.c_str()); + BOX_FATAL("Invalid configuration file: " << errs); return 1; } // Easier coding @@ -206,12 +258,12 @@ int main(int argc, const char *argv[]) BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); // 2. Connect to server - if(!quiet) printf("Connecting to store...\n"); + if(!quiet) BOX_INFO("Connecting to store..."); SocketStreamTLS socket; socket.Open(tlsContext, Socket::TypeINET, conf.GetKeyValue("StoreHostname").c_str(), BOX_PORT_BBSTORED); // 3. Make a protocol, and handshake - if(!quiet) printf("Handshake with store...\n"); + if(!quiet) BOX_INFO("Handshake with store..."); BackupProtocolClient connection(socket); connection.Handshake(); @@ -222,7 +274,7 @@ int main(int argc, const char *argv[]) } // 4. Log in to server - if(!quiet) printf("Login to store...\n"); + if(!quiet) BOX_INFO("Login to store..."); // Check the version of the server { std::auto_ptr<BackupProtocolClientVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION)); @@ -299,9 +351,9 @@ int main(int argc, const char *argv[]) #endif // Done... stop nicely - if(!quiet) printf("Logging off...\n"); + if(!quiet) BOX_INFO("Logging off..."); connection.QueryFinished(); - if(!quiet) printf("Session finished.\n"); + if(!quiet) BOX_INFO("Session finished."); // Return code returnCode = context.GetReturnCode(); @@ -314,13 +366,13 @@ int main(int argc, const char *argv[]) // Let everything be cleaned up on exit. - MAINHELPER_END - #ifdef WIN32 // Clean up our sockets WSACleanup(); #endif + MAINHELPER_END + return returnCode; } diff --git a/bin/bbackupquery/documentation.txt b/bin/bbackupquery/documentation.txt index 0fb0bacb..42217edc 100644 --- a/bin/bbackupquery/documentation.txt +++ b/bin/bbackupquery/documentation.txt @@ -123,7 +123,7 @@ compare <store-dir-name> <local-dir-name> The root cannot be restored -- restore locations individually. - -d -- restore a deleted directory. + -d -- restore a deleted directory or deleted files inside -r -- resume an interrupted restoration -i -- directory name is actually an ID diff --git a/bin/bbstoreaccounts/bbstoreaccounts.cpp b/bin/bbstoreaccounts/bbstoreaccounts.cpp index dd42458b..8a00e3b9 100644 --- a/bin/bbstoreaccounts/bbstoreaccounts.cpp +++ b/bin/bbstoreaccounts/bbstoreaccounts.cpp @@ -37,12 +37,13 @@ void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit) { if(SoftLimit >= HardLimit) { - printf("ERROR: Soft limit must be less than the hard limit.\n"); + BOX_FATAL("Soft limit must be less than the hard limit."); exit(1); } if(SoftLimit > ((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100)) { - printf("ERROR: Soft limit must be no more than %d%% of the hard limit.\n", MAX_SOFT_LIMIT_SIZE); + BOX_FATAL("Soft limit must be no more than " << + MAX_SOFT_LIMIT_SIZE << "% of the hard limit."); exit(1); } } @@ -53,7 +54,7 @@ int BlockSizeOfDiscSet(int DiscSet) RaidFileController &controller(RaidFileController::GetController()); if(DiscSet < 0 || DiscSet >= controller.GetNumDiscSets()) { - printf("Disc set %d does not exist\n", DiscSet); + BOX_FATAL("Disc set " << DiscSet << " does not exist."); exit(1); } @@ -89,7 +90,7 @@ int64_t SizeStringToBlocks(const char *string, int DiscSet) int64_t number = strtol(string, &endptr, 0); if(endptr == string || number == LONG_MIN || number == LONG_MAX) { - printf("%s is an invalid number\n", string); + BOX_FATAL("'" << string << "' is not a valid number."); exit(1); } @@ -116,7 +117,8 @@ int64_t SizeStringToBlocks(const char *string, int DiscSet) break; default: - printf("%s has an invalid units specifier\nUse B for blocks, M for Mb, G for Gb, eg 2Gb\n", string); + BOX_FATAL(string << " has an invalid units specifier " + "(use B for blocks, M for Mb, G for Gb, eg 2Gb)"); exit(1); break; } @@ -143,7 +145,8 @@ bool GetWriteLockOnAccount(NamedLock &rLock, const std::string rRootDir, int Dis if(!gotLock) { // Couldn't lock the account -- just stop now - printf("Couldn't lock the account -- did not change the limits\nTry again later.\n"); + BOX_ERROR("Failed to lock the account, did not change limits. " + "Try again later."); return 1; } @@ -168,7 +171,8 @@ int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, c // Already exists? if(!db->EntryExists(ID)) { - printf("Account %x does not exist\n", ID); + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " does not exist."); return 1; } @@ -198,7 +202,9 @@ int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, c // Save info->Save(); - printf("Limits on account 0x%08x changed to %lld soft, %lld hard\n", ID, softlimit, hardlimit); + BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) << + " changed to " << softlimit << " soft, " << + hardlimit << " hard."); return 0; } @@ -211,7 +217,8 @@ int AccountInfo(Configuration &rConfig, int32_t ID) // Exists? if(!db->EntryExists(ID)) { - printf("Account %x does not exist\n", ID); + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " does not exist."); return 1; } @@ -241,11 +248,12 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t // Check user really wants to do this if(AskForConfirmation) { - ::printf("Really delete account %08x?\n(type 'yes' to confirm)\n", ID); + BOX_WARNING("Really delete account " << + BOX_FORMAT_ACCOUNT(ID) << "? (type 'yes' to confirm)"); char response[256]; if(::fgets(response, sizeof(response), stdin) == 0 || ::strcmp(response, "yes\n") != 0) { - printf("Deletion cancelled\n"); + BOX_NOTICE("Deletion cancelled."); return 0; } } @@ -256,7 +264,8 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t // Exists? if(!db->EntryExists(ID)) { - printf("Account %x does not exist\n", ID); + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " does not exist."); return 1; } @@ -318,24 +327,27 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t toDelete.push_back((*i) + DIRECTORY_SEPARATOR + rootDir); } } - + + int retcode = 0; + // Thirdly, delete the directories... for(std::vector<std::string>::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) { - ::printf("Deleting store directory %s...\n", (*d).c_str()); + BOX_NOTICE("Deleting store directory " << (*d) << "..."); // Just use the rm command to delete the files std::string cmd("rm -rf "); cmd += *d; // Run command if(::system(cmd.c_str()) != 0) { - ::printf("ERROR: Deletion of %s failed.\n(when cleaning up, remember to delete all raid directories)\n", (*d).c_str()); - return 1; + BOX_ERROR("Failed to delete files in " << (*d) << + ", delete them manually."); + retcode = 1; } } // Success! - return 0; + return retcode; } int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool FixErrors, bool Quiet) @@ -346,7 +358,8 @@ int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t I // Exists? if(!db->EntryExists(ID)) { - printf("Account %x does not exist\n", ID); + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " does not exist."); return 1; } @@ -381,7 +394,8 @@ int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t // Already exists? if(db->EntryExists(ID)) { - printf("Account %x already exists\n", ID); + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " already exists."); return 1; } @@ -389,7 +403,7 @@ int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t BackupStoreAccounts acc(*db); acc.Create(ID, DiscNumber, SoftLimit, HardLimit, rUsername); - printf("Account %x created\n", ID); + BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created."); return 0; } @@ -402,12 +416,19 @@ void PrintUsageAndExit() int main(int argc, const char *argv[]) { - MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbstoreaccounts.memleaks", "bbstoreaccounts") + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbstoreaccounts.memleaks", + "bbstoreaccounts") MAINHELPER_START - // Filename for configuraiton file? - const char *configFilename = BOX_FILE_BBSTORED_DEFAULT_CONFIG; + // Filename for configuration file? + std::string configFilename; + + #ifdef WIN32 + configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; + #else + configFilename = BOX_FILE_BBSTORED_DEFAULT_CONFIG; + #endif // See if there's another entry on the command line int c; @@ -431,10 +452,14 @@ int main(int argc, const char *argv[]) // Read in the configuration file std::string errs; - std::auto_ptr<Configuration> config(Configuration::LoadAndVerify(configFilename, &BackupConfigFileVerify, errs)); + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + (configFilename, &BackupConfigFileVerify, errs)); + if(config.get() == 0 || !errs.empty()) { - printf("Invalid configuration file:\n%s", errs.c_str()); + BOX_ERROR("Invalid configuration file " << configFilename << + ":" << errs); } // Get the user under which the daemon runs @@ -474,7 +499,8 @@ int main(int argc, const char *argv[]) if(argc < 5 || ::sscanf(argv[2], "%d", &discnum) != 1) { - printf("create requires raid file disc number, soft and hard limits\n"); + BOX_ERROR("create requires raid file disc number, " + "soft and hard limits."); return 1; } @@ -496,7 +522,7 @@ int main(int argc, const char *argv[]) // Change the limits on this account if(argc < 4) { - printf("setlimit requires soft and hard limits\n"); + BOX_ERROR("setlimit requires soft and hard limits."); return 1; } @@ -530,7 +556,7 @@ int main(int argc, const char *argv[]) } else { - ::printf("Unknown option %s.\n", argv[o]); + BOX_ERROR("Unknown option " << argv[o] << "."); return 2; } } @@ -540,7 +566,7 @@ int main(int argc, const char *argv[]) } else { - printf("Unknown command '%s'\n", argv[0]); + BOX_ERROR("Unknown command '" << argv[0] << "'."); return 1; } diff --git a/bin/bbstored/BBStoreDHousekeeping.cpp b/bin/bbstored/BBStoreDHousekeeping.cpp index 8c725b0f..16a1432a 100644 --- a/bin/bbstored/BBStoreDHousekeeping.cpp +++ b/bin/bbstored/BBStoreDHousekeeping.cpp @@ -11,10 +11,6 @@ #include <stdio.h> -#ifdef HAVE_SYSLOG_H - #include <syslog.h> -#endif - #include "BackupStoreDaemon.h" #include "BackupStoreAccountDatabase.h" #include "BackupStoreAccounts.h" @@ -50,7 +46,8 @@ void BackupStoreDaemon::HousekeepingProcess() { RunHousekeepingIfNeeded(); - // Calculate how long should wait before doing the next housekeeping run + // Calculate how long should wait before doing the next + // housekeeping run int64_t timeNow = GetCurrentBoxTime(); time_t secondsToGo = BoxTimeToSeconds( (mLastHousekeepingRun + housekeepingInterval) - @@ -72,6 +69,7 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded() // Time now int64_t timeNow = GetCurrentBoxTime(); + // Do housekeeping if the time interval has elapsed since the last check if((timeNow - mLastHousekeepingRun) < housekeepingInterval) { @@ -80,7 +78,7 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded() // Store the time mLastHousekeepingRun = timeNow; - ::syslog(LOG_INFO, "Starting housekeeping"); + BOX_INFO("Starting housekeeping"); // Get the list of accounts std::vector<int32_t> accounts; @@ -110,18 +108,25 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded() } catch(BoxException &e) { - ::syslog(LOG_ERR, "while housekeeping account %08X, exception %s (%d/%d) -- aborting housekeeping run for this account", - *i, e.what(), e.GetType(), e.GetSubType()); + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " << + e.what() << " (" << + e.GetType() << "/" << e.GetSubType() << ")"); } catch(std::exception &e) { - ::syslog(LOG_ERR, "while housekeeping account %08X, exception %s -- aborting housekeeping run for this account", - *i, e.what()); + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " << + e.what()); } catch(...) { - ::syslog(LOG_ERR, "while housekeeping account %08X, unknown exception -- aborting housekeeping run for this account", - *i); + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " + "unknown exception"); } int64_t timeNow = GetCurrentBoxTime(); @@ -142,12 +147,25 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded() } } - ::syslog(LOG_INFO, "Finished housekeeping"); + BOX_INFO("Finished housekeeping"); // Placed here for accuracy, if StopRun() is true, for example. SetProcessTitle("housekeeping, idle"); } +void BackupStoreDaemon::OnIdle() +{ + #ifdef WIN32 + if (!mHousekeepingInited) + { + HousekeepingInit(); + mHousekeepingInited = true; + } + + RunHousekeepingIfNeeded(); + #endif +} + // -------------------------------------------------------------------------- // // Function @@ -159,6 +177,11 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded() // -------------------------------------------------------------------------- bool BackupStoreDaemon::CheckForInterProcessMsg(int AccountNum, int MaximumWaitTime) { + if(!mInterProcessCommsSocket.IsOpened()) + { + return false; + } + // First, check to see if it's EOF -- this means something has gone wrong, and the housekeeping should terminate. if(mInterProcessComms.IsEOF()) { @@ -170,7 +193,7 @@ 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()); + TRACE1("Housekeeping received command '%s' over interprocess comms\n", line.c_str()); int account = 0; @@ -192,7 +215,9 @@ bool BackupStoreDaemon::CheckForInterProcessMsg(int AccountNum, int MaximumWaitT if(account == AccountNum) { // Yes! -- need to stop now so when it retries to get the lock, it will succeed - ::syslog(LOG_INFO, "Housekeeping giving way to connection for account 0x%08x", AccountNum); + BOX_INFO("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(AccountNum) << + "giving way to client connection"); return true; } } diff --git a/bin/bbstored/BackupCommands.cpp b/bin/bbstored/BackupCommands.cpp index b199edbe..bca52c04 100644 --- a/bin/bbstored/BackupCommands.cpp +++ b/bin/bbstored/BackupCommands.cpp @@ -9,7 +9,7 @@ #include "Box.h" -#include <syslog.h> +#include <set> #include <sstream> #include "autogen_BackupProtocolServer.h" @@ -25,6 +25,8 @@ #include "BackupStoreInfo.h" #include "RaidFileController.h" #include "FileStream.h" +#include "InvisibleTempFileStream.h" +#include "BufferedStream.h" #include "MemLeakFindOn.h" @@ -82,11 +84,26 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtoco // Check given client ID against the ID in the certificate certificate // and that the client actually has an account on this machine - if(mClientID != rContext.GetClientID() || !rContext.GetClientHasAccount()) + if(mClientID != rContext.GetClientID()) { - ::syslog(LOG_INFO, "Failed login: Client ID presented was %08X", mClientID); - return std::auto_ptr<ProtocolObject>(new BackupProtocolServerError( - BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_BadLogin)); + BOX_WARNING("Failed login from client ID " << + BOX_FORMAT_ACCOUNT(mClientID) << + ": wrong certificate for this account"); + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_BadLogin)); + } + + if(!rContext.GetClientHasAccount()) + { + BOX_WARNING("Failed login from client ID " << + BOX_FORMAT_ACCOUNT(mClientID) << + ": no such account on this server"); + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_BadLogin)); } // If we need to write, check that nothing else has got a write lock @@ -95,9 +112,12 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtoco // See if the context will get the lock if(!rContext.AttemptToGetWriteLock()) { - ::syslog(LOG_INFO, "Failed to get write lock (for Client ID %08X)", mClientID); - return std::auto_ptr<ProtocolObject>(new BackupProtocolServerError( - BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_CannotLockStoreForWriting)); + BOX_WARNING("Failed to get write lock for Client ID " << + BOX_FORMAT_ACCOUNT(mClientID)); + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_CannotLockStoreForWriting)); } // Debug: check we got the lock @@ -114,7 +134,11 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtoco rContext.SetPhase(BackupContext::Phase_Commands); // Log login - ::syslog(LOG_INFO, "Login: Client ID %08X, %s", mClientID, ((mFlags & Flags_ReadOnly) != Flags_ReadOnly)?"Read/Write":"Read-only"); + BOX_NOTICE("Login from Client ID " << + BOX_FORMAT_ACCOUNT(mClientID) << + " " << + (((mFlags & Flags_ReadOnly) != Flags_ReadOnly) + ?"Read/Write":"Read-only")); // Get the usage info for reporting to the client int64_t blocksUsed = 0, blocksSoftLimit = 0, blocksHardLimit = 0; @@ -134,7 +158,8 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerLogin::DoCommand(BackupProtoco // -------------------------------------------------------------------------- std::auto_ptr<ProtocolObject> BackupProtocolServerFinished::DoCommand(BackupProtocolServer &rProtocol, BackupContext &rContext) { - ::syslog(LOG_INFO, "Session finished"); + BOX_NOTICE("Session finished for Client ID " << + BOX_FORMAT_ACCOUNT(rContext.GetClientID())); // Let the context know about it rContext.ReceivedFinishCommand(); @@ -305,13 +330,23 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProto en = rdir.FindEntryByID(id); if(en == 0) { - ::syslog(LOG_ERR, "Object %llx in dir %llx for account %x references object %llx which does not exist in dir", - mObjectID, mInDirectory, rContext.GetClientID(), id); - return std::auto_ptr<ProtocolObject>(new BackupProtocolServerError( - BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_PatchConsistencyError)); + BOX_ERROR("Object " << + BOX_FORMAT_OBJECTID(mObjectID) << + " in dir " << + BOX_FORMAT_OBJECTID(mInDirectory) << + " for account " << + BOX_FORMAT_ACCOUNT(rContext.GetClientID()) << + " references object " << + BOX_FORMAT_OBJECTID(id) << + " which does not exist in dir"); + return std::auto_ptr<ProtocolObject>( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_PatchConsistencyError)); } id = en->GetDependsNewer(); - } while(en != 0 && id != 0); + } + while(en != 0 && id != 0); // OK! The last entry in the chain is the full file, the others are patches back from it. // Open the last one, which is the current from file @@ -340,14 +375,14 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProto { { // Write nastily to allow this to work with gcc 2.x - std::auto_ptr<IOStream> t(new FileStream(tempFn.c_str(), O_RDWR | O_CREAT | O_EXCL)); + std::auto_ptr<IOStream> t( + new InvisibleTempFileStream( + tempFn.c_str(), + O_RDWR | O_CREAT | + O_EXCL | O_BINARY | + O_TRUNC)); combined = t; } - // Unlink immediately as it's a temporary file - if(::unlink(tempFn.c_str()) != 0) - { - THROW_EXCEPTION(CommonException, OSFileError); - } } catch(...) { @@ -383,9 +418,10 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProto // Open the object std::auto_ptr<IOStream> object(rContext.OpenObject(mObjectID)); + BufferedStream buf(*object); // Verify it - if(!BackupStoreFile::VerifyEncodedFileFormat(*object)) + if(!BackupStoreFile::VerifyEncodedFileFormat(buf)) { return std::auto_ptr<ProtocolObject>(new BackupProtocolServerError( BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_FileDoesNotVerify)); @@ -401,8 +437,9 @@ std::auto_ptr<ProtocolObject> BackupProtocolServerGetFile::DoCommand(BackupProto stream = t; } - // Object will be deleted when the stream is deleted, so can release the object auto_ptr here to - // avoid premature deletiong + // Object will be deleted when the stream is deleted, + // so can release the object auto_ptr here to avoid + // premature deletion object.release(); } diff --git a/bin/bbstored/BackupContext.cpp b/bin/bbstored/BackupContext.cpp index a3f614a5..16388099 100644 --- a/bin/bbstored/BackupContext.cpp +++ b/bin/bbstored/BackupContext.cpp @@ -24,6 +24,8 @@ #include "BackupStoreDaemon.h" #include "RaidFileController.h" #include "FileStream.h" +#include "InvisibleTempFileStream.h" +#include "BufferedStream.h" #include "MemLeakFindOn.h" @@ -125,7 +127,6 @@ void BackupContext::ReceivedFinishCommand() // -------------------------------------------------------------------------- bool BackupContext::AttemptToGetWriteLock() { -#ifndef WIN32 // Make the filename of the write lock file std::string writeLockFile; StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet, writeLockFile); @@ -151,7 +152,7 @@ bool BackupContext::AttemptToGetWriteLock() } while(!gotLock && tries > 0); } - + if(gotLock) { // Got the lock, mark as not read only @@ -159,10 +160,6 @@ bool BackupContext::AttemptToGetWriteLock() } return gotLock; -#else // WIN32 - // no housekeeping process, we do have the lock - return true; -#endif // !WIN32 } @@ -310,7 +307,8 @@ BackupStoreDirectory &BackupContext::GetDirectoryInternal(int64_t ObjectID) std::auto_ptr<BackupStoreDirectory> dir(new BackupStoreDirectory); // Read it from the stream, then set it's revision ID - dir->ReadFromStream(*objectFile, IOStream::TimeOutInfinite); + BufferedStream buf(*objectFile); + dir->ReadFromStream(buf, IOStream::TimeOutInfinite); dir->SetRevisionID(revID); // Make sure the size of the directory is available for writing the dir back @@ -459,9 +457,9 @@ int64_t BackupContext::AddFile(IOStream &rFile, int64_t InDirectory, int64_t Mod { // Open it twice #ifdef WIN32 - FileStream diff(tempFn.c_str(), + InvisibleTempFileStream diff(tempFn.c_str(), O_RDWR | O_CREAT | O_BINARY); - FileStream diff2(tempFn.c_str(), + InvisibleTempFileStream diff2(tempFn.c_str(), O_RDWR | O_BINARY); #else FileStream diff(tempFn.c_str(), O_RDWR | O_CREAT | O_EXCL); @@ -521,14 +519,6 @@ int64_t BackupContext::AddFile(IOStream &rFile, int64_t InDirectory, int64_t Mod ::unlink(tempFn.c_str()); throw; } - -#ifdef WIN32 - // we can't delete the file while it's open, above - if(::unlink(tempFn.c_str()) != 0) - { - THROW_EXCEPTION(CommonException, OSFileError); - } -#endif } // Get the blocks used diff --git a/bin/bbstored/BackupStoreDaemon.cpp b/bin/bbstored/BackupStoreDaemon.cpp index ce7263da..c5d5fe40 100644 --- a/bin/bbstored/BackupStoreDaemon.cpp +++ b/bin/bbstored/BackupStoreDaemon.cpp @@ -42,6 +42,7 @@ BackupStoreDaemon::BackupStoreDaemon() mExtendedLogging(false), mHaveForkedHousekeeping(false), mIsHousekeepingProcess(false), + mHousekeepingInited(false), mInterProcessComms(mInterProcessCommsSocket) { } @@ -131,7 +132,23 @@ void BackupStoreDaemon::SetupInInitialProcess() // Initialise the raid files controller RaidFileController &rcontroller = RaidFileController::GetController(); - rcontroller.Initialise(config.GetKeyValue("RaidFileConf").c_str()); + + std::string raidFileConfig; + + #ifdef WIN32 + if (!config.KeyExists("RaidFileConf")) + { + raidFileConfig = BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE; + } + else + { + raidFileConfig = config.GetKeyValue("RaidFileConf"); + } + #else + raidFileConfig = config.GetKeyValue("RaidFileConf"); + #endif + + rcontroller.Initialise(raidFileConfig); // Load the account database std::auto_ptr<BackupStoreAccountDatabase> pdb(BackupStoreAccountDatabase::Read(config.GetKeyValue("AccountDatabase").c_str())); @@ -159,6 +176,9 @@ 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) { @@ -188,7 +208,7 @@ void BackupStoreDaemon::Run() // Change the log name ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); // Log that housekeeping started - ::syslog(LOG_INFO, "Housekeeping process 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 ::signal(SIGHUP, SIG_IGN); @@ -214,6 +234,7 @@ void BackupStoreDaemon::Run() THROW_EXCEPTION(ServerException, SocketCloseError) } } +#endif // WIN32 if(mIsHousekeepingProcess) { @@ -224,12 +245,18 @@ void BackupStoreDaemon::Run() { // In server process -- use the base class to do the magic ServerTLS<BOX_PORT_BBSTORED>::Run(); - + + if (!mInterProcessCommsSocket.IsOpened()) + { + return; + } + // Why did it stop? Tell the housekeeping process to do the same if(IsReloadConfigWanted()) { mInterProcessCommsSocket.Write("h\n", 2); } + if(IsTerminateWanted()) { mInterProcessCommsSocket.Write("t\n", 2); @@ -237,22 +264,54 @@ void BackupStoreDaemon::Run() } } - // -------------------------------------------------------------------------- // // Function // Name: BackupStoreDaemon::Connection(SocketStreamTLS &) -// Purpose: Handles a connection +// Purpose: Handles a connection, by catching exceptions and +// delegating to Connection2 // Created: 2003/08/20 // // -------------------------------------------------------------------------- void BackupStoreDaemon::Connection(SocketStreamTLS &rStream) { + try + { + Connection2(rStream); + } + catch(BoxException &e) + { + BOX_ERROR("Error in child process, terminating connection: " << + e.what() << " (" << e.GetType() << "/" << + e.GetSubType() << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Error in child process, terminating connection: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Error in child process, terminating connection: " << + "unknown exception"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Connection2(SocketStreamTLS &) +// Purpose: Handles a connection from bbackupd +// Created: 2006/11/12 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream) +{ // Get the common name from the certificate std::string clientCommonName(rStream.GetPeerCommonName()); // Log the name - ::syslog(LOG_INFO, "Certificate CN: %s\n", clientCommonName.c_str()); + BOX_INFO("Client certificate CN: " << clientCommonName); // Check it int32_t id; @@ -298,10 +357,8 @@ void BackupStoreDaemon::LogConnectionStats(const char *commonName, const SocketStreamTLS &s) { // Log the amount of data transferred - ::syslog(LOG_INFO, "Connection statistics for %s: " - "IN=%lld OUT=%lld TOTAL=%lld\n", commonName, - (long long)s.GetBytesRead(), - (long long)s.GetBytesWritten(), - (long long)s.GetBytesRead() + - (long long)s.GetBytesWritten()); + BOX_INFO("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 0ce6f21f..eb665440 100644 --- a/bin/bbstored/BackupStoreDaemon.h +++ b/bin/bbstored/BackupStoreDaemon.h @@ -52,7 +52,8 @@ protected: virtual void Run(); - void Connection(SocketStreamTLS &rStream); + virtual void Connection(SocketStreamTLS &rStream); + void Connection2(SocketStreamTLS &rStream); virtual const char *DaemonName() const; virtual const char *DaemonBanner() const; @@ -71,10 +72,12 @@ private: bool mExtendedLogging; bool mHaveForkedHousekeeping; bool mIsHousekeepingProcess; + bool mHousekeepingInited; SocketStream mInterProcessCommsSocket; IOStreamGetLine mInterProcessComms; + virtual void OnIdle(); void HousekeepingInit(); void RunHousekeepingIfNeeded(); int64_t mLastHousekeepingRun; diff --git a/bin/bbstored/HousekeepStoreAccount.cpp b/bin/bbstored/HousekeepStoreAccount.cpp index 91945306..9f4239e7 100644 --- a/bin/bbstored/HousekeepStoreAccount.cpp +++ b/bin/bbstored/HousekeepStoreAccount.cpp @@ -9,9 +9,10 @@ #include "Box.h" -#include <map> #include <stdio.h> +#include <map> + #include "HousekeepStoreAccount.h" #include "BackupStoreDaemon.h" #include "StoreStructure.h" @@ -23,6 +24,7 @@ #include "NamedLock.h" #include "autogen_BackupStoreException.h" #include "BackupStoreFile.h" +#include "BufferedStream.h" #include "MemLeakFindOn.h" @@ -136,11 +138,18 @@ void HousekeepStoreAccount::DoHousekeeping() || (usedDeleted + mBlocksInDeletedFilesDelta) != mBlocksInDeletedFiles || usedDirectories != mBlocksInDirectories) { // Log this - ::syslog(LOG_ERR, "On housekeeping, sizes in store do not match calculated sizes, correcting"); - ::syslog(LOG_ERR, "different (store,calc): acc 0x%08x, used (%lld,%lld), old (%lld,%lld), deleted (%lld,%lld), dirs (%lld,%lld)", - mAccountID, - (used + mBlocksUsedDelta), mBlocksUsed, (usedOld + mBlocksInOldFilesDelta), mBlocksInOldFiles, - (usedDeleted + mBlocksInDeletedFilesDelta), mBlocksInDeletedFiles, usedDirectories, mBlocksInDirectories); + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(mAccountID) << " found " + "and fixed wrong block counts: " + "used (" << + (used + mBlocksUsedDelta) << "," << + mBlocksUsed << "), old (" << + (usedOld + mBlocksInOldFilesDelta) << "," << + mBlocksInOldFiles << "), deleted (" << + (usedDeleted + mBlocksInDeletedFilesDelta) << + "," << mBlocksInDeletedFiles << "), dirs (" << + usedDirectories << "," << mBlocksInDirectories + << ")"); } // If the current values don't match, store them @@ -172,17 +181,33 @@ void HousekeepStoreAccount::DoHousekeeping() // Log deletion if anything was deleted if(mFilesDeleted > 0 || mEmptyDirectoriesDeleted > 0) { - ::syslog(LOG_INFO, "Account 0x%08x, removed %lld blocks (%lld files, %lld dirs)%s", mAccountID, 0 - (mBlocksUsedDelta + removeASAPBlocksUsedDelta), - mFilesDeleted, mEmptyDirectoriesDeleted, - deleteInterrupted?" was interrupted":""); + BOX_INFO("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(mAccountID) << " " + "removed " << + (0 - (mBlocksUsedDelta + removeASAPBlocksUsedDelta)) << + " blocks (" << mFilesDeleted << " files, " << + mEmptyDirectoriesDeleted << " dirs)" << + (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. - if(mBlocksUsedDelta < (0 - info->GetBlocksUsed())) mBlocksUsedDelta = (0 - info->GetBlocksUsed()); - if(mBlocksInOldFilesDelta < (0 - info->GetBlocksInOldFiles())) mBlocksInOldFilesDelta = (0 - info->GetBlocksInOldFiles()); - if(mBlocksInDeletedFilesDelta < (0 - info->GetBlocksInDeletedFiles())) mBlocksInDeletedFilesDelta =(0 - info->GetBlocksInDeletedFiles()); - if(mBlocksInDirectoriesDelta < (0 - info->GetBlocksInDirectories())) mBlocksInDirectoriesDelta = (0 - info->GetBlocksInDirectories()); + if(mBlocksUsedDelta < (0 - info->GetBlocksUsed())) + { + mBlocksUsedDelta = (0 - info->GetBlocksUsed()); + } + if(mBlocksInOldFilesDelta < (0 - info->GetBlocksInOldFiles())) + { + mBlocksInOldFilesDelta = (0 - info->GetBlocksInOldFiles()); + } + if(mBlocksInDeletedFilesDelta < (0 - info->GetBlocksInDeletedFiles())) + { + mBlocksInDeletedFilesDelta = (0 - info->GetBlocksInDeletedFiles()); + } + if(mBlocksInDirectoriesDelta < (0 - info->GetBlocksInDirectories())) + { + mBlocksInDirectoriesDelta = (0 - info->GetBlocksInDirectories()); + } // Update the usage counts in the store info->ChangeBlocksUsed(mBlocksUsedDelta); @@ -252,7 +277,8 @@ bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID) // Read the directory in BackupStoreDirectory dir; - dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); + BufferedStream buf(*dirStream); + dir.ReadFromStream(buf, IOStream::TimeOutInfinite); dirStream->Close(); // Is it empty? @@ -552,7 +578,14 @@ void HousekeepStoreAccount::DeleteFile(int64_t InDirectory, int64_t ObjectID, Ba BackupStoreDirectory::Entry *pentry = rDirectory.FindEntryByID(ObjectID); if(pentry == 0) { - ::syslog(LOG_ERR, "acc 0x%08x, object %lld not found in dir %lld, logic error/corruption? Run bbstoreaccounts check <accid> fix", mAccountID, ObjectID, InDirectory); + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(mAccountID) << " " + "found error: object " << + BOX_FORMAT_OBJECTID(ObjectID) << " " + "not found in dir " << + BOX_FORMAT_OBJECTID(InDirectory) << ", " + "indicates logic error/corruption? Run " + "bbstoreaccounts check <accid> fix"); return; } diff --git a/bin/bbstored/bbstored.cpp b/bin/bbstored/bbstored.cpp index 3eaf2639..54858dd4 100644 --- a/bin/bbstored/bbstored.cpp +++ b/bin/bbstored/bbstored.cpp @@ -10,6 +10,7 @@ #include "Box.h" #include "BackupStoreDaemon.h" #include "MainHelper.h" +#include "Logging.h" #include "MemLeakFindOn.h" @@ -17,8 +18,19 @@ int main(int argc, const char *argv[]) { MAINHELPER_START + Logging::SetProgramName("Box Backup (bbstored)"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + BackupStoreDaemon daemon; - return daemon.Main(BOX_FILE_BBSTORED_DEFAULT_CONFIG, argc, argv); + + #ifdef WIN32 + return daemon.Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE, + argc, argv); + #else + return daemon.Main(BOX_FILE_BBSTORED_DEFAULT_CONFIG, + argc, argv); + #endif MAINHELPER_END } |