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");
		}
	}
}
Beispiel #6
0
// --------------------------------------------------------------------------
//
// 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");
	}
}
Beispiel #7
0
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", &currentState) != 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;
}