int BackupStoreAccountsControl::SetAccountName(int32_t ID, const std::string& rNewAccountName) { std::string rootDir; int discSetNum; std::auto_ptr<UnixUser> user; // used to reset uid when we return NamedLock writeLock; if(!OpenAccount(ID, rootDir, discSetNum, user, &writeLock)) { BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) << " to change name."); return 1; } // Load the info std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, rootDir, discSetNum, false /* Read/Write */)); info->SetAccountName(rNewAccountName); // Save info->Save(); BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " name changed to " << rNewAccountName); return 0; }
int BackupStoreAccountsControl::CreateAccount(int32_t ID, int32_t DiscNumber, int32_t SoftLimit, int32_t HardLimit) { // Load in the account database std::auto_ptr<BackupStoreAccountDatabase> db( BackupStoreAccountDatabase::Read( mConfig.GetKeyValue("AccountDatabase"))); // Already exists? if(db->EntryExists(ID)) { BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << " already exists."); return 1; } // Get the user under which the daemon runs std::string username; { const Configuration &rserverConfig(mConfig.GetSubConfiguration("Server")); if(rserverConfig.KeyExists("User")) { username = rserverConfig.GetKeyValue("User"); } } // Create it. BackupStoreAccounts acc(*db); acc.Create(ID, DiscNumber, SoftLimit, HardLimit, username); BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created."); return 0; }
int BackupStoreAccountsControl::SetLimit(int32_t ID, const char *SoftLimitStr, const char *HardLimitStr) { std::string rootDir; int discSetNum; std::auto_ptr<UnixUser> user; // used to reset uid when we return NamedLock writeLock; if(!OpenAccount(ID, rootDir, discSetNum, user, &writeLock)) { BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) << " to change limits."); return 1; } // Load the info std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, rootDir, discSetNum, false /* Read/Write */)); // Change the limits int blocksize = BlockSizeOfDiscSet(discSetNum); int64_t softlimit = SizeStringToBlocks(SoftLimitStr, blocksize); int64_t hardlimit = SizeStringToBlocks(HardLimitStr, blocksize); CheckSoftHardLimits(softlimit, hardlimit); info->ChangeLimits(softlimit, hardlimit); // Save info->Save(); BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) << " changed to " << softlimit << " soft, " << hardlimit << " hard."); return 0; }
int BackupStoreAccountsControl::DeleteAccount(int32_t ID, bool AskForConfirmation) { std::string rootDir; int discSetNum; std::auto_ptr<UnixUser> user; // used to reset uid when we return NamedLock writeLock; // Obtain a write lock, as the daemon user if(!OpenAccount(ID, rootDir, discSetNum, user, &writeLock)) { BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) << " for deletion."); return 1; } // Check user really wants to do this if(AskForConfirmation) { 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) { BOX_NOTICE("Deletion cancelled."); return 0; } } // Back to original user, but write lock is maintained user.reset(); std::auto_ptr<BackupStoreAccountDatabase> db( BackupStoreAccountDatabase::Read( mConfig.GetKeyValue("AccountDatabase"))); // Delete from account database db->DeleteEntry(ID); // Write back to disc db->Write(); // Remove the store files... // First, become the user specified in the config file std::string username; { const Configuration &rserverConfig(mConfig.GetSubConfiguration("Server")); if(rserverConfig.KeyExists("User")) { username = rserverConfig.GetKeyValue("User"); } } // Become the right user if(!username.empty()) { // Username specified, change... user.reset(new UnixUser(username)); user->ChangeProcessUser(true /* temporary */); // Change will be undone when user goes out of scope } // Secondly, work out which directories need wiping std::vector<std::string> toDelete; RaidFileController &rcontroller(RaidFileController::GetController()); RaidFileDiscSet discSet(rcontroller.GetDiscSet(discSetNum)); for(RaidFileDiscSet::const_iterator i(discSet.begin()); i != discSet.end(); ++i) { if(std::find(toDelete.begin(), toDelete.end(), *i) == toDelete.end()) { toDelete.push_back((*i) + DIRECTORY_SEPARATOR + rootDir); } } // NamedLock will throw an exception if it can't delete the lockfile, // which it can't if it doesn't exist. Now that we've deleted the account, // nobody can open it anyway, so it's safe to unlock. writeLock.ReleaseLock(); int retcode = 0; // Thirdly, delete the directories... for(std::vector<std::string>::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) { 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) { BOX_ERROR("Failed to delete files in " << (*d) << ", delete them manually."); retcode = 1; } } // Success! return retcode; }
int main(int argc, char * const * argv) { // Start memory leak testing MEMLEAKFINDER_START Logging::SetProgramName(BOX_MODULE); struct option longopts[] = { { "bbackupd-args", required_argument, NULL, 'c' }, { "bbstored-args", required_argument, NULL, 's' }, { "test-daemon-args", required_argument, NULL, 'd' }, { "execute-only", required_argument, NULL, 'e' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; int c; std::string options("c:d:e:hs:"); options += Logging::OptionParser::GetOptionString(); Logging::OptionParser LogLevel; while ((c = getopt_long(argc, argv, options.c_str(), longopts, NULL)) != -1) { switch(c) { case 'c': { if (bbackupd_args.length() > 0) { bbackupd_args += " "; } bbackupd_args += optarg; } break; case 'd': { if (test_args.length() > 0) { test_args += " "; } test_args += optarg; } break; case 'e': { run_only_named_tests.push_back(optarg); } break; case 'h': { return Usage(argv[0]); } break; case 's': { bbstored_args += " "; bbstored_args += optarg; } break; default: { int ret = LogLevel.ProcessOption(c); if(ret != 0) { fprintf(stderr, "Unknown option code " "'%c'\n", c); exit(2); } } } } Logging::FilterSyslog(Log::NOTHING); Logging::FilterConsole(LogLevel.GetCurrentLevel()); argc -= optind - 1; argv += optind - 1; // If there is more than one argument, then the test is doing something advanced, so leave it alone bool fulltestmode = (argc == 1); if(fulltestmode) { // banner BOX_NOTICE("Running test TEST_NAME in " MODE_TEXT " mode..."); // Count open file descriptors for a very crude "files left open" test Logging::GetSyslog().Shutdown(); // On NetBSD, gethostbyname() appears to open a kqueue socket // and it's not clear how to close it again. So let's just do // it once, before counting fds for the first time, so that it's // already open and doesn't count as a leak. ::gethostbyname("nonexistent"); check_filedes(false); #ifdef WIN32 // Under win32 we must initialise the Winsock library // before using sockets WSADATA info; TEST_THAT(WSAStartup(0x0101, &info) != SOCKET_ERROR) #endif } try { #ifdef BOX_MEMORY_LEAK_TESTING memleakfinder_init(); #endif Timers::Init(); int returncode = test(argc, (const char **)argv); Timers::Cleanup(false); fflush(stdout); fflush(stderr); // check for memory leaks, if enabled #ifdef BOX_MEMORY_LEAK_TESTING if(memleakfinder_numleaks() != 0) { num_failures++; printf("FAILURE: Memory leaks detected in test code\n"); printf("==== MEMORY LEAKS =================================\n"); memleakfinder_reportleaks(); printf("===================================================\n"); } #endif if(fulltestmode) { Logging::GetSyslog().Shutdown(); bool filesleftopen = !checkfilesleftopen(); fflush(stdout); fflush(stderr); if(filesleftopen) { num_failures++; printf("IMPLICIT TEST FAILED: Something left files open\n"); } if(num_failures > 0) { printf("FAILED: %d tests failed (first at " "%s:%d)\n", num_failures, first_fail_file.c_str(), first_fail_line); } else { printf("PASSED\n"); } } return returncode; } catch(std::exception &e) { printf("FAILED: Exception caught: %s\n", e.what()); return 1; } catch(...) { printf("FAILED: Unknown exception caught\n"); return 1; } if(fulltestmode) { if(!checkfilesleftopen()) { printf("WARNING: Files were left open\n"); } } }
// -------------------------------------------------------------------------- // // Function // Name: BackupStoreCheck::Check() // Purpose: Perform the check on the given account. You need to // hold a lock on the account before calling this! // Created: 21/4/04 // // -------------------------------------------------------------------------- void BackupStoreCheck::Check() { if(mFixErrors) { std::string writeLockFilename; StoreStructure::MakeWriteLockFilename(mStoreRoot, mDiscSetNumber, writeLockFilename); ASSERT(FileExists(writeLockFilename)); } if(!mQuiet && mFixErrors) { BOX_INFO("Will fix errors encountered during checking."); } BackupStoreAccountDatabase::Entry account(mAccountID, mDiscSetNumber); mapNewRefs = BackupStoreRefCountDatabase::Create(account); // Phase 1, check objects if(!mQuiet) { BOX_INFO("Checking store account ID " << BOX_FORMAT_ACCOUNT(mAccountID) << "..."); BOX_INFO("Phase 1, check objects..."); } CheckObjects(); // Phase 2, check directories if(!mQuiet) { BOX_INFO("Phase 2, check directories..."); } CheckDirectories(); // Phase 3, check root if(!mQuiet) { BOX_INFO("Phase 3, check root..."); } CheckRoot(); // Phase 4, check unattached objects if(!mQuiet) { BOX_INFO("Phase 4, fix unattached objects..."); } CheckUnattachedObjects(); // Phase 5, fix bad info if(!mQuiet) { BOX_INFO("Phase 5, fix unrecovered inconsistencies..."); } FixDirsWithWrongContainerID(); FixDirsWithLostDirs(); // Phase 6, regenerate store info if(!mQuiet) { BOX_INFO("Phase 6, regenerate store info..."); } WriteNewStoreInfo(); try { std::auto_ptr<BackupStoreRefCountDatabase> apOldRefs = BackupStoreRefCountDatabase::Load(account, false); mNumberErrorsFound += mapNewRefs->ReportChangesTo(*apOldRefs); } catch(BoxException &e) { BOX_WARNING("Reference count database was missing or " "corrupted, cannot check it for errors."); mNumberErrorsFound++; } // force file to be saved and closed before releasing the lock below if(mFixErrors) { mapNewRefs->Commit(); } else { mapNewRefs->Discard(); } mapNewRefs.reset(); if(mNumberErrorsFound > 0) { BOX_WARNING("Finished checking store account ID " << BOX_FORMAT_ACCOUNT(mAccountID) << ": " << mNumberErrorsFound << " errors found"); if(!mFixErrors) { BOX_WARNING("No changes to the store account " "have been made."); } if(!mFixErrors && mNumberErrorsFound > 0) { BOX_WARNING("Run again with fix option to " "fix these errors"); } if(mFixErrors && mNumberErrorsFound > 0) { BOX_WARNING("You should now use bbackupquery " "on the client machine to examine the store."); if(mLostAndFoundDirectoryID != 0) { BOX_WARNING("A lost+found directory was " "created in the account root.\n" "This contains files and directories " "which could not be matched to " "existing directories.\n"\ "bbackupd will delete this directory " "in a few days time."); } } } else { BOX_NOTICE("Finished checking store account ID " << BOX_FORMAT_ACCOUNT(mAccountID) << ": " "no errors found"); } }
int main(int argc, const char *argv[]) { int returnCode = 0; MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupctl.memleaks", "bbackupctl") MAINHELPER_START Logging::SetProgramName("bbackupctl"); // Filename for configuration file? std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; // See if there's another entry on the command line int c; std::string options("c:"); options += Logging::OptionParser::GetOptionString(); Logging::OptionParser LogLevel; while((c = getopt(argc, (char * const *)argv, options.c_str())) != -1) { switch(c) { case 'c': // store argument configFilename = optarg; break; default: int ret = LogLevel.ProcessOption(c); if(ret != 0) { PrintUsageAndExit(ret); } } } // Adjust arguments argc -= optind; argv += optind; // Check there's a command if(argc != 1) { PrintUsageAndExit(2); } Logging::FilterConsole(LogLevel.GetCurrentLevel()); // Read in the configuration file BOX_INFO("Using configuration file " << configFilename); std::string errs; std::auto_ptr<Configuration> config( Configuration::LoadAndVerify (configFilename, &BackupDaemonConfigVerify, errs)); if(config.get() == 0 || !errs.empty()) { BOX_ERROR("Invalid configuration file: " << errs); return 1; } // Easier coding const Configuration &conf(*config); // Check there's a socket defined in the config file if(!conf.KeyExists("CommandSocket")) { BOX_ERROR("Daemon isn't using a control socket, " "could not execute command.\n" "Add a CommandSocket declaration to the " "bbackupd.conf file."); return 1; } // Connect to socket #ifndef WIN32 SocketStream connection; #else /* WIN32 */ WinNamedPipeStream connection; #endif /* ! WIN32 */ try { #ifdef WIN32 std::string socket = conf.GetKeyValue("CommandSocket"); connection.Connect(socket); #else connection.Open(Socket::TypeUNIX, conf.GetKeyValue("CommandSocket").c_str()); #endif } catch(...) { 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" ); return 1; } // For receiving data IOStreamGetLine getLine(connection); // Wait for the configuration summary std::string configSummary; if(!getLine.GetLine(configSummary, false, PROTOCOL_DEFAULT_TIMEOUT)) { BOX_ERROR("Failed to receive configuration summary " "from daemon"); return 1; } // Was the connection rejected by the server? if(getLine.IsEOF()) { BOX_ERROR("Server rejected the connection. Are you running " "bbackupctl as the same user as the daemon?"); return 1; } // Decode it int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", &autoBackup, &updateStoreInterval, &minimumFileAge, &maxUploadWait) != 4) { BOX_ERROR("Config summary didn't decode."); return 1; } // Print summary? BOX_TRACE("Daemon configuration summary:\n" " AutomaticBackup = " << (autoBackup?"true":"false") << "\n" " UpdateStoreInterval = " << updateStoreInterval << " seconds\n" " MinimumFileAge = " << minimumFileAge << " seconds\n" " MaxUploadWait = " << maxUploadWait << " seconds"); std::string stateLine; if(!getLine.GetLine(stateLine, false, PROTOCOL_DEFAULT_TIMEOUT) || getLine.IsEOF()) { BOX_ERROR("Failed to receive state line from daemon"); return 1; } // Decode it int currentState; if(::sscanf(stateLine.c_str(), "state %d", ¤tState) != 1) { BOX_ERROR("Received invalid state line from daemon"); return 1; } BOX_TRACE("Current state: " << BackupDaemon::GetStateName(currentState)); Command command = Default; std::string commandName(argv[0]); if(commandName == "wait-for-sync") { command = WaitForSyncStart; } else if(commandName == "wait-for-end") { command = WaitForSyncEnd; } else if(commandName == "sync-and-wait") { command = SyncAndWaitForEnd; } else if(commandName == "status") { BOX_NOTICE("state " << BackupDaemon::GetStateName(currentState)); command = NoCommand; } switch(command) { case WaitForSyncStart: case WaitForSyncEnd: { // Check that it's in automatic mode, // because otherwise it'll never start if(!autoBackup) { BOX_ERROR("Daemon is not in automatic mode, " "sync will never start!"); return 1; } } break; case SyncAndWaitForEnd: { // send a sync command commandName = "force-sync"; std::string cmd = commandName + "\n"; connection.Write(cmd, PROTOCOL_DEFAULT_TIMEOUT); connection.WriteAllBuffered(); if(currentState != 0) { BOX_INFO("Waiting for current sync/error state " "to finish..."); } } break; default: { // Normal case, just send the command given, plus a // quit command. std::string cmd = commandName + "\n"; connection.Write(cmd, PROTOCOL_DEFAULT_TIMEOUT); } // fall through case NoCommand: { // Normal case, just send the command given plus a // quit command. std::string cmd = "quit\n"; connection.Write(cmd, PROTOCOL_DEFAULT_TIMEOUT); } } // Read the response std::string line; bool syncIsRunning = false; bool finished = false; while(command != NoCommand && !finished && !getLine.IsEOF() && getLine.GetLine(line, false, PROTOCOL_DEFAULT_TIMEOUT)) { BOX_TRACE("Received line: " << line); if(line.substr(0, 6) == "state ") { std::string state_str = line.substr(6); int state_num; if(sscanf(state_str.c_str(), "%d", &state_num) == 1) { BOX_INFO("Daemon state changed to: " << BackupDaemon::GetStateName(state_num)); } else { BOX_WARNING("Failed to parse line: " << line); } } switch(command) { case WaitForSyncStart: { // Need to wait for the state change... if(line == "start-sync") { // And we're done finished = true; } } break; case WaitForSyncEnd: case SyncAndWaitForEnd: { if(line == "start-sync") { BOX_TRACE("Sync started..."); syncIsRunning = true; } else if(line == "finish-sync") { if (syncIsRunning) { // And we're done BOX_TRACE("Sync finished."); finished = true; } else { BOX_TRACE("Previous sync finished."); } // daemon must still be busy } } break; default: { // Is this an OK or error line? if(line == "ok") { BOX_TRACE("Control command " "sent: " << commandName); finished = true; } else if(line == "error") { BOX_ERROR("Control command failed: " << commandName << ". Check " "command spelling."); returnCode = 1; finished = true; } } } } // Send a quit command to finish nicely connection.Write("quit\n", 5, PROTOCOL_DEFAULT_TIMEOUT); MAINHELPER_END #if defined WIN32 && ! defined BOX_RELEASE_BUILD closelog(); #endif return returnCode; }