diff options
author | Chris Wilson <chris+github@qwirx.com> | 2014-02-07 15:44:07 +0000 |
---|---|---|
committer | Chris Wilson <chris+github@qwirx.com> | 2014-02-07 15:44:07 +0000 |
commit | e370cf08364620cac777affb07088b59260025b2 (patch) | |
tree | a408fc0f8bb5b7313316707e6e8fa984a7219637 /lib/backupstore | |
parent | 40b353bbe608ba440287650d6c276887e1e7c2f0 (diff) |
Split bbstoreaccounts commands out into a separate class.
Allows us to call them from tests without shelling out, making debugging
easier.
Diffstat (limited to 'lib/backupstore')
-rw-r--r-- | lib/backupstore/BackupStoreAccounts.cpp | 491 | ||||
-rw-r--r-- | lib/backupstore/BackupStoreAccounts.h | 34 |
2 files changed, 520 insertions, 5 deletions
diff --git a/lib/backupstore/BackupStoreAccounts.cpp b/lib/backupstore/BackupStoreAccounts.cpp index 18500fc1..5aed4a6d 100644 --- a/lib/backupstore/BackupStoreAccounts.cpp +++ b/lib/backupstore/BackupStoreAccounts.cpp @@ -9,19 +9,29 @@ #include "Box.h" -#include <stdio.h> +#include <algorithm> +#include <climits> +#include <cstdio> +#include <cstring> +#include <iostream> #include "BackupStoreAccounts.h" #include "BackupStoreAccountDatabase.h" +#include "BackupStoreCheck.h" +#include "BackupStoreConfigVerify.h" #include "BackupStoreConstants.h" #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreInfo.h" #include "BackupStoreRefCountDatabase.h" #include "BoxPortsAndFiles.h" +#include "HousekeepStoreAccount.h" +#include "NamedLock.h" +#include "RaidFileController.h" #include "RaidFileWrite.h" #include "StoreStructure.h" #include "UnixUser.h" +#include "Utils.h" #include "MemLeakFindOn.h" @@ -110,10 +120,7 @@ void BackupStoreAccounts::Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, info->Save(); // Create the refcount database - BackupStoreRefCountDatabase::CreateNew(Entry); - std::auto_ptr<BackupStoreRefCountDatabase> refcount( - BackupStoreRefCountDatabase::Load(Entry, false)); - refcount->AddReference(BACKUPSTORE_ROOT_DIRECTORY_ID); + BackupStoreRefCountDatabase::Create(Entry)->Commit(); } // As the original user... @@ -201,3 +208,477 @@ void BackupStoreAccounts::LockAccount(int32_t ID, NamedLock& rNamedLock) "lock on account " << ID); } } + +BackupStoreAccountsControl::BackupStoreAccountsControl( + const Configuration& config, bool machineReadableOutput) +: mConfig(config), + mMachineReadableOutput(machineReadableOutput) +{ } + +void BackupStoreAccountsControl::CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit) +{ + if(SoftLimit > HardLimit) + { + BOX_FATAL("Soft limit must be less than the hard limit."); + exit(1); + } + if(SoftLimit > ((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100)) + { + BOX_WARNING("We recommend setting the soft limit below " << + MAX_SOFT_LIMIT_SIZE << "% of the hard limit, or " << + HumanReadableSize((HardLimit * MAX_SOFT_LIMIT_SIZE) + / 100) << " in this case."); + } +} + +int BackupStoreAccountsControl::BlockSizeOfDiscSet(int discSetNum) +{ + // Get controller, check disc set number + RaidFileController &controller(RaidFileController::GetController()); + if(discSetNum < 0 || discSetNum >= controller.GetNumDiscSets()) + { + BOX_FATAL("Disc set " << discSetNum << " does not exist."); + exit(1); + } + + // Return block size + return controller.GetDiscSet(discSetNum).GetBlockSize(); +} + +std::string BackupStoreAccountsControl::BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int discSetNum) +{ + return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(discSetNum), + MaxBlocks * BlockSizeOfDiscSet(discSetNum), + mMachineReadableOutput); +} + +int64_t BackupStoreAccountsControl::SizeStringToBlocks(const char *string, int discSetNum) +{ + // Find block size + int blockSize = BlockSizeOfDiscSet(discSetNum); + + // Get number + char *endptr = (char*)string; + int64_t number = strtol(string, &endptr, 0); + if(endptr == string || number == LONG_MIN || number == LONG_MAX) + { + BOX_FATAL("'" << string << "' is not a valid number."); + exit(1); + } + + // Check units + switch(*endptr) + { + case 'M': + case 'm': + // Units: Mb + return (number * 1024*1024) / blockSize; + break; + + case 'G': + case 'g': + // Units: Gb + return (number * 1024*1024*1024) / blockSize; + break; + + case 'B': + case 'b': + // Units: Blocks + // Easy! Just return the number specified. + return number; + break; + + default: + BOX_FATAL(string << " has an invalid units specifier " + "(use B for blocks, M for MB, G for GB, eg 2GB)"); + exit(1); + break; + } +} + +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 + int64_t softlimit = SizeStringToBlocks(SoftLimitStr, discSetNum); + int64_t hardlimit = SizeStringToBlocks(HardLimitStr, discSetNum); + 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::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::PrintAccountInfo(int32_t ID) +{ + std::string rootDir; + int discSetNum; + std::auto_ptr<UnixUser> user; // used to reset uid when we return + + if(!OpenAccount(ID, rootDir, discSetNum, user, + NULL /* no write lock needed for this read-only operation */)) + { + BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) + << " to display info."); + return 1; + } + + // Load it in + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, + rootDir, discSetNum, true /* ReadOnly */)); + + // Then print out lots of info + std::cout << FormatUsageLineStart("Account ID", mMachineReadableOutput) << + BOX_FORMAT_ACCOUNT(ID) << std::endl; + std::cout << FormatUsageLineStart("Account Name", mMachineReadableOutput) << + info->GetAccountName() << std::endl; + std::cout << FormatUsageLineStart("Last object ID", mMachineReadableOutput) << + BOX_FORMAT_OBJECTID(info->GetLastObjectIDUsed()) << std::endl; + std::cout << FormatUsageLineStart("Used", mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksUsed(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Current files", + mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInCurrentFiles(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Old files", mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInOldFiles(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Deleted files", mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInDeletedFiles(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Directories", mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInDirectories(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Soft limit", mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksSoftLimit(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Hard limit", mMachineReadableOutput) << + BlockSizeToString(info->GetBlocksHardLimit(), + info->GetBlocksHardLimit(), discSetNum) << std::endl; + std::cout << FormatUsageLineStart("Client store marker", mMachineReadableOutput) << + info->GetLastObjectIDUsed() << std::endl; + std::cout << FormatUsageLineStart("Current Files", mMachineReadableOutput) << + info->GetNumCurrentFiles() << std::endl; + std::cout << FormatUsageLineStart("Old Files", mMachineReadableOutput) << + info->GetNumOldFiles() << std::endl; + std::cout << FormatUsageLineStart("Deleted Files", mMachineReadableOutput) << + info->GetNumDeletedFiles() << std::endl; + std::cout << FormatUsageLineStart("Directories", mMachineReadableOutput) << + info->GetNumDirectories() << std::endl; + std::cout << FormatUsageLineStart("Enabled", mMachineReadableOutput) << + (info->IsAccountEnabled() ? "yes" : "no") << std::endl; + + return 0; +} + +int BackupStoreAccountsControl::SetAccountEnabled(int32_t ID, bool enabled) +{ + 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 enabled flag."); + return 1; + } + + // Load it in + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, + rootDir, discSetNum, false /* ReadOnly */)); + info->SetAccountEnabled(enabled); + info->Save(); + 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); + } + } + +#ifdef WIN32 + // Cannot remove files while holding a lock on them + writeLock.ReleaseLock(); +#endif + + 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; +} + +bool BackupStoreAccountsControl::OpenAccount(int32_t ID, std::string &rRootDirOut, + int &rDiscSetOut, std::auto_ptr<UnixUser> apUser, NamedLock* pLock) +{ + // Load in the account database + std::auto_ptr<BackupStoreAccountDatabase> db( + BackupStoreAccountDatabase::Read( + mConfig.GetKeyValue("AccountDatabase"))); + + // Exists? + if(!db->EntryExists(ID)) + { + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " does not exist."); + return false; + } + + // Get info from the database + BackupStoreAccounts acc(*db); + acc.GetAccountRoot(ID, rRootDirOut, rDiscSetOut); + + // 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"); + } + } + + // Become the right user + if(!username.empty()) + { + // Username specified, change... + apUser.reset(new UnixUser(username)); + apUser->ChangeProcessUser(true /* temporary */); + // Change will be undone when apUser goes out of scope + // in the caller. + } + + if(pLock) + { + acc.LockAccount(ID, *pLock); + } + + return true; +} + +int BackupStoreAccountsControl::CheckAccount(int32_t ID, bool FixErrors, bool Quiet) +{ + 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) + << " for checking."); + return 1; + } + + // Check it + BackupStoreCheck check(rootDir, discSetNum, ID, FixErrors, Quiet); + check.Check(); + + return check.ErrorsFound()?1: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::HousekeepAccountNow(int32_t ID) +{ + std::string rootDir; + int discSetNum; + std::auto_ptr<UnixUser> user; // used to reset uid when we return + + if(!OpenAccount(ID, rootDir, discSetNum, user, + NULL /* housekeeping locks the account itself */)) + { + BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) + << " for housekeeping."); + return 1; + } + + HousekeepStoreAccount housekeeping(ID, rootDir, discSetNum, NULL); + bool success = housekeeping.DoHousekeeping(); + + if(!success) + { + BOX_ERROR("Failed to lock account " << BOX_FORMAT_ACCOUNT(ID) + << " for housekeeping: perhaps a client is " + "still connected?"); + return 1; + } + else + { + BOX_TRACE("Finished housekeeping on account " << + BOX_FORMAT_ACCOUNT(ID)); + return 0; + } +} + diff --git a/lib/backupstore/BackupStoreAccounts.h b/lib/backupstore/BackupStoreAccounts.h index 3163f15c..5ee67312 100644 --- a/lib/backupstore/BackupStoreAccounts.h +++ b/lib/backupstore/BackupStoreAccounts.h @@ -51,5 +51,39 @@ private: BackupStoreAccountDatabase &mrDatabase; }; +class Configuration; +class UnixUser; + +class BackupStoreAccountsControl +{ +private: + const Configuration& mConfig; + bool mMachineReadableOutput; + +public: + BackupStoreAccountsControl(const Configuration& config, + bool machineReadableOutput = false); + + void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit); + int BlockSizeOfDiscSet(int discSetNum); + std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int discSetNum); + int64_t SizeStringToBlocks(const char *string, int discSetNum); + bool OpenAccount(int32_t ID, std::string &rRootDirOut, + int &rDiscSetOut, std::auto_ptr<UnixUser> apUser, NamedLock* pLock); + int SetLimit(int32_t ID, const char *SoftLimitStr, + const char *HardLimitStr); + int SetAccountName(int32_t ID, const std::string& rNewAccountName); + int PrintAccountInfo(int32_t ID); + int SetAccountEnabled(int32_t ID, bool enabled); + int DeleteAccount(int32_t ID, bool AskForConfirmation); + int CheckAccount(int32_t ID, bool FixErrors, bool Quiet); + int CreateAccount(int32_t ID, int32_t DiscNumber, int32_t SoftLimit, + int32_t HardLimit); + int HousekeepAccountNow(int32_t ID); +}; + +// max size of soft limit as percent of hard limit +#define MAX_SOFT_LIMIT_SIZE 97 + #endif // BACKUPSTOREACCOUNTS__H |