// distribution boxbackup-0.11.1 (svn version: 2821_2827)
// Box Backup, http://www.boxbackup.org/
// 
// Copyright (c) 2003-2010, Ben Summers and contributors.
// All rights reserved.
// 
// Note that this project uses mixed licensing. Any file with this license
// attached, or where the code LICENSE-GPL appears on the first line, falls
// under the "Box Backup GPL" license. See the file COPYING.txt for more
// information about this license.
// 
// ---------------------------------------------------------------------
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
// 
// [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4]
// 
// As a special exception to the GPLv2, the Box Backup Project gives
// permission to link any code falling under this license (the Box Backup
// GPL) with any software that can be downloaded from
// the OpenSSL website [http://www.openssl.org] under either the
// "OpenSSL License" or the "Original SSLeay License", and to distribute
// the linked executables under the terms of the "Box Backup GPL" license.
// 
// As a special exception to the GPLv2, the Box Backup Project gives
// permission to link any code falling under this license (the Box Backup
// GPL) with any version of Microsoft's Volume Shadow Copy Service 7.2 SDK
// or Microsoft Windows Software Development Kit (SDK), including
// vssapi.lib, that can be downloaded from the Microsoft website
// [*.microsoft.com], and to distribute the linked executables under the
// terms of the "Box Backup GPL" license.
// --------------------------------------------------------------------------
//
// File
//		Name:    testbbackupd.cpp
//		Purpose: test backup daemon (and associated client bits)
//		Created: 2003/10/07
//
// --------------------------------------------------------------------------

#include "Box.h"

// do not include MinGW's dirent.h on Win32, 
// as we override some of it in lib/win32.

#ifndef WIN32
	#include <dirent.h>
#endif

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>

#ifdef HAVE_SYS_WAIT_H
	#include <sys/wait.h>
#endif

#ifdef HAVE_SYS_XATTR_H
	#include <cerrno>
	#include <sys/xattr.h>
#endif

#ifdef HAVE_SIGNAL_H
	#include <signal.h>
#endif

#include <map>

#ifdef HAVE_SYSCALL
	#include <sys/syscall.h>
#endif

#include "autogen_BackupProtocolServer.h"
#include "BackupClientCryptoKeys.h"
#include "BackupClientFileAttributes.h"
#include "BackupClientRestore.h"
#include "BackupDaemon.h"
#include "BackupDaemonConfigVerify.h"
#include "BackupQueries.h"
#include "BackupStoreConstants.h"
#include "BackupStoreContext.h"
#include "BackupStoreDaemon.h"
#include "BackupStoreDirectory.h"
#include "BackupStoreException.h"
#include "BoxPortsAndFiles.h"
#include "BoxTime.h"
#include "BoxTimeToUnix.h"
#include "CollectInBufferStream.h"
#include "CommonException.h"
#include "Configuration.h"
#include "FileModificationTime.h"
#include "FileStream.h"
#include "IOStreamGetLine.h"
#include "LocalProcessStream.h"
#include "SSLLib.h"
#include "ServerControl.h"
#include "Socket.h"
#include "SocketStreamTLS.h"
#include "TLSContext.h"
#include "Test.h"
#include "Timer.h"
#include "Utils.h"

#include "autogen_BackupProtocolClient.h"
#include "intercept.h"
#include "ServerControl.h"

#include "MemLeakFindOn.h"

// ENOATTR may be defined in a separate header file which we may not have
#ifndef ENOATTR
#define ENOATTR ENODATA
#endif

// two cycles and a bit
#define TIME_TO_WAIT_FOR_BACKUP_OPERATION	12

void wait_for_backup_operation(const char* message)
{
	wait_for_operation(TIME_TO_WAIT_FOR_BACKUP_OPERATION, message);
}

int bbstored_pid = 0;
int bbackupd_pid = 0;

#ifdef HAVE_SYS_XATTR_H
bool readxattr_into_map(const char *filename, std::map<std::string,std::string> &rOutput)
{
	rOutput.clear();
	
	ssize_t xattrNamesBufferSize = llistxattr(filename, NULL, 0);
	if(xattrNamesBufferSize < 0)
	{
		return false;
	}
	else if(xattrNamesBufferSize > 0)
	{
		// There is some data there to look at
		char *xattrNamesBuffer = (char*)malloc(xattrNamesBufferSize + 4);
		if(xattrNamesBuffer == NULL) return false;
		char *xattrDataBuffer = 0;
		int xattrDataBufferSize = 0;
		// note: will leak these buffers if a read error occurs. (test code, so doesn't matter)
		
		ssize_t ns = llistxattr(filename, xattrNamesBuffer, xattrNamesBufferSize);
		if(ns < 0)
		{
			return false;
		}
		else if(ns > 0)
		{
			// Read all the attribute values
			const char *xattrName = xattrNamesBuffer;
			while(xattrName < (xattrNamesBuffer + ns))
			{
				// Store size of name
				int xattrNameSize = strlen(xattrName);
				
				bool ok = true;
					
				ssize_t dataSize = lgetxattr(filename, xattrName, NULL, 0);
				if(dataSize < 0)
				{
					if(errno == ENOATTR)
					{
						// Deleted from under us
						ok = false;
					}
					else
					{
						return false;
					}
				}
				else if(dataSize == 0)
				{
					// something must have removed all the data from under us
					ok = false;
				}
				else
				{
					// Make sure there's enough space in the buffer to get the attribute
					if(xattrDataBuffer == 0)
					{
						xattrDataBuffer = (char*)malloc(dataSize + 4);
						xattrDataBufferSize = dataSize + 4;
					}
					else if(xattrDataBufferSize < (dataSize + 4))
					{
						char *resized = (char*)realloc(xattrDataBuffer, dataSize + 4);
						if(resized == NULL) return false;
						xattrDataBuffer = resized;
						xattrDataBufferSize = dataSize + 4;
					}
				}

				// Read the data!
				dataSize = 0;
				if(ok)
				{
					dataSize = lgetxattr(filename, xattrName, xattrDataBuffer,
						xattrDataBufferSize - 1 /*for terminator*/);
					if(dataSize < 0)
					{
						if(errno == ENOATTR)
						{
							// Deleted from under us
							ok = false;
						}
						else
						{
							return false;
						}
					}
					else if(dataSize == 0)
					{
						// something must have deleted this from under us
						ok = false;
					}
					else
					{
						// Terminate the data
						xattrDataBuffer[dataSize] = '\0';
					}
					// Got the data in the buffer
				}
				
				// Store in map
				if(ok)
				{
					rOutput[std::string(xattrName)] = std::string(xattrDataBuffer, dataSize);
				}

				// Next attribute
				xattrName += xattrNameSize + 1;
			}
		}
		
		if(xattrNamesBuffer != 0) ::free(xattrNamesBuffer);
		if(xattrDataBuffer != 0) ::free(xattrDataBuffer);
	}

	return true;
}

static FILE *xattrTestDataHandle = 0;
bool write_xattr_test(const char *filename, const char *attrName, unsigned int length, bool *pNotSupported = 0)
{
	if(xattrTestDataHandle == 0)
	{
		xattrTestDataHandle = ::fopen("testfiles/test3.tgz", "rb");	// largest test file
	}
	if(xattrTestDataHandle == 0)
	{
		return false;
	}
	else
	{
		char data[1024];
		if(length > sizeof(data)) length = sizeof(data);
		
		if(::fread(data, length, 1, xattrTestDataHandle) != 1)
		{
			return false;
		}
		
		if(::lsetxattr(filename, attrName, data, length, 0) != 0)
		{
			if(pNotSupported != 0)
			{
				*pNotSupported = (errno == ENOTSUP);
			}
			return false;
		}
	}

	return true;
}
void finish_with_write_xattr_test()
{
	if(xattrTestDataHandle != 0)
	{
		::fclose(xattrTestDataHandle);
	}
}
#endif // HAVE_SYS_XATTR_H

bool attrmatch(const char *f1, const char *f2)
{
	EMU_STRUCT_STAT s1, s2;
	TEST_THAT(EMU_LSTAT(f1, &s1) == 0);
	TEST_THAT(EMU_LSTAT(f2, &s2) == 0);

#ifdef HAVE_SYS_XATTR_H
	{
		std::map<std::string,std::string> xattr1, xattr2;
		if(!readxattr_into_map(f1, xattr1)
			|| !readxattr_into_map(f2, xattr2))
		{
			/*return false*/;
		}
		else if(!(xattr1 == xattr2))
		{
			return false;
		}
	}
#endif // HAVE_SYS_XATTR_H

	// if link, just make sure other file is a link too, and that the link to names match
	if((s1.st_mode & S_IFMT) == S_IFLNK)
	{
#ifdef WIN32
		TEST_FAIL_WITH_MESSAGE("No symlinks on win32!")
#else
		if((s2.st_mode & S_IFMT) != S_IFLNK) return false;
		
		char p1[PATH_MAX], p2[PATH_MAX];
		int p1l = ::readlink(f1, p1, PATH_MAX);
		int p2l = ::readlink(f2, p2, PATH_MAX);
		TEST_THAT(p1l != -1 && p2l != -1);
		// terminate strings properly
		p1[p1l] = '\0';
		p2[p2l] = '\0';
		return strcmp(p1, p2) == 0;
#endif
	}

	// modification times
	if(FileModificationTime(s1) != FileModificationTime(s2))
	{
		return false;
	}

	// compare the rest
	return (s1.st_mode == s2.st_mode && s1.st_uid == s2.st_uid && s1.st_gid == s2.st_gid);
}

int test_basics()
{
	// Read attributes from files
	BackupClientFileAttributes t1;
	t1.ReadAttributes("testfiles/test1");
	TEST_THAT(!t1.IsSymLink());

#ifndef WIN32
	BackupClientFileAttributes t2;
	t2.ReadAttributes("testfiles/test2");
	TEST_THAT(t2.IsSymLink());
	// Check that it's actually been encrypted (search for symlink name encoded in it)
	void *te = ::memchr(t2.GetBuffer(), 't', t2.GetSize() - 3);
	TEST_THAT(te == 0 || ::memcmp(te, "test", 4) != 0);
#endif
	
	BackupClientFileAttributes t3;
	{
		Logging::Guard guard(Log::ERROR);
		TEST_CHECK_THROWS(t3.ReadAttributes("doesn't exist"),
			CommonException, OSFileError);
	}

	// Create some more files
	FILE *f = fopen("testfiles/test1_n", "w");
	fclose(f);
	f = fopen("testfiles/test2_n", "w");
	fclose(f);
	
	// Apply attributes to these new files
	t1.WriteAttributes("testfiles/test1_n");
#ifdef WIN32
	t1.WriteAttributes("testfiles/test2_n");
#else
	t2.WriteAttributes("testfiles/test2_n");
#endif

#ifndef WIN32
	{
		Logging::Guard guard(Log::ERROR);
		TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"),
			CommonException, OSFileError);
		TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"),
			BackupStoreException, AttributesNotLoaded);
	}

	// Test that attributes are vaguely similar
	TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_n"));
	TEST_THAT(attrmatch("testfiles/test2", "testfiles/test2_n"));
#endif
	
	// Check encryption, and recovery from encryption
	// First, check that two attributes taken from the same thing have different encrypted values (think IV)
	BackupClientFileAttributes t1b;
	t1b.ReadAttributes("testfiles/test1");
	TEST_THAT(::memcmp(t1.GetBuffer(), t1b.GetBuffer(), t1.GetSize()) != 0);
	// But that comparing them works OK.
	TEST_THAT(t1 == t1b);
	// Then store them both to a stream
	CollectInBufferStream stream;
	t1.WriteToStream(stream);
	t1b.WriteToStream(stream);
	// Read them back again
	stream.SetForReading();
	BackupClientFileAttributes t1_r, t1b_r;
	t1_r.ReadFromStream(stream, 1000);
	t1b_r.ReadFromStream(stream, 1000);
	TEST_THAT(::memcmp(t1_r.GetBuffer(), t1b_r.GetBuffer(), t1_r.GetSize()) != 0);
	TEST_THAT(t1_r == t1b_r);
	TEST_THAT(t1 == t1_r);
	TEST_THAT(t1b == t1b_r);
	TEST_THAT(t1_r == t1b);
	TEST_THAT(t1b_r == t1);

#ifdef HAVE_SYS_XATTR_H
	// Write some attributes to the file, checking for ENOTSUP
	bool xattrNotSupported = false;
	if(!write_xattr_test("testfiles/test1", "user.attr_1", 1000, &xattrNotSupported) && xattrNotSupported)
	{
		::printf("***********\nYour platform supports xattr, but your filesystem does not.\nSkipping tests.\n***********\n");
	}
	else
	{
		BackupClientFileAttributes x1, x2, x3, x4;

		// Write more attributes
		TEST_THAT(write_xattr_test("testfiles/test1", "user.attr_2", 947));
		TEST_THAT(write_xattr_test("testfiles/test1", "user.sadfohij39998.3hj", 123));
	
		// Read file attributes
		x1.ReadAttributes("testfiles/test1");
		
		// Write file attributes
		FILE *f = fopen("testfiles/test1_nx", "w");
		fclose(f);
		x1.WriteAttributes("testfiles/test1_nx");
		
		// Compare to see if xattr copied
		TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_nx"));
		
		// Add more attributes to a file
		x2.ReadAttributes("testfiles/test1");
		TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23));
		
		// Read them again, and check that the Compare() function detects that they're different
		x3.ReadAttributes("testfiles/test1");
		TEST_THAT(x1.Compare(x2, true, true));
		TEST_THAT(!x1.Compare(x3, true, true));
		
		// Change the value of one of them, leaving the size the same.
		TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23));
		x4.ReadAttributes("testfiles/test1");
		TEST_THAT(!x1.Compare(x4, true, true));
	}
	finish_with_write_xattr_test();
#endif // HAVE_SYS_XATTR_H

	return 0;
}

int test_setupaccount()
{
	TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c "
		"testfiles/bbstored.conf create 01234567 0 1000B 2000B") == 0);
	TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
	return 0;
}

int test_run_bbstored()
{
	std::string cmd = BBSTORED " " + bbstored_args + 
		" testfiles/bbstored.conf";
	bbstored_pid = LaunchServer(cmd, "testfiles/bbstored.pid");

	TEST_THAT(bbstored_pid != -1 && bbstored_pid != 0);

	if(bbstored_pid > 0)
	{
		::safe_sleep(1);
		TEST_THAT(ServerIsAlive(bbstored_pid));
		return 0;	// success
	}
	
	return 1;
}

int test_kill_bbstored(bool wait_for_process = false)
{
	TEST_THAT(KillServer(bbstored_pid, wait_for_process));
	::safe_sleep(1);
	TEST_THAT(!ServerIsAlive(bbstored_pid));
	if (!ServerIsAlive(bbstored_pid))
	{
		bbstored_pid = 0;
	}

	#ifdef WIN32
		TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
	#else
		TestRemoteProcessMemLeaks("bbstored.memleaks");
	#endif
	
	return 0;
}

int64_t GetDirID(BackupProtocolClient &protocol, const char *name, int64_t InDirectory)
{
	protocol.QueryListDirectory(
			InDirectory,
			BackupProtocolClientListDirectory::Flags_Dir,
			BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
			true /* want attributes */);
	
	// Retrieve the directory from the stream following
	BackupStoreDirectory dir;
	std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
	dir.ReadFromStream(*dirstream, protocol.GetTimeout());
	
	BackupStoreDirectory::Iterator i(dir);
	BackupStoreDirectory::Entry *en = 0;
	int64_t dirid = 0;
	BackupStoreFilenameClear dirname(name);
	while((en = i.Next()) != 0)
	{
		if(en->GetName() == dirname)
		{
			dirid = en->GetObjectID();
		}
	}
	return dirid;
}

void terminate_on_alarm(int sigraised)
{
	abort();
}

#ifndef WIN32
void do_interrupted_restore(const TLSContext &context, int64_t restoredirid)
{
	int pid = 0;
	switch((pid = fork()))
	{
	case 0:
		// child process
		{
			// connect and log in
			SocketStreamTLS conn;
			conn.Open(context, Socket::TypeINET, "localhost",
				22011);
			BackupProtocolClient protocol(conn);
			protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
			std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
			
			// Test the restoration
			TEST_THAT(BackupClientRestore(protocol, restoredirid,
				"Test1", "testfiles/restore-interrupt",
				true /* print progress dots */)
				== Restore_Complete);

			// Log out
			protocol.QueryFinished();
		}
		exit(0);
		break;
	
	case -1:
		{
			printf("Fork failed\n");
			exit(1);
		}
	
	default:
		{
			// Wait until a resume file is written, then terminate the child
			while(true)
			{
				// Test for existence of the result file
				int64_t resumesize = 0;
				if(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize) && resumesize > 16)
				{
					// It's done something. Terminate it.	
					::kill(pid, SIGTERM);
					break;
				}

				// Process finished?
				int status = 0;
				if(waitpid(pid, &status, WNOHANG) != 0)
				{
					// child has finished anyway.
					return;
				}
				
				// Give up timeslot so as not to hog the processor
				::sleep(0);
			}
			
			// Just wait until the child has completed
			int status = 0;
			waitpid(pid, &status, 0);
		}
	}
}
#endif // !WIN32

#ifdef WIN32
bool set_file_time(const char* filename, FILETIME creationTime, 
	FILETIME lastModTime, FILETIME lastAccessTime)
{
	HANDLE handle = openfile(filename, O_RDWR, 0);
	TEST_THAT(handle != INVALID_HANDLE_VALUE);
	if (handle == INVALID_HANDLE_VALUE) return false;
		
	BOOL success = SetFileTime(handle, &creationTime, &lastAccessTime,
		&lastModTime);
	TEST_THAT(success);

	TEST_THAT(CloseHandle(handle));
	return success;
}
#endif

void intercept_setup_delay(const char *filename, unsigned int delay_after, 
	int delay_ms, int syscall_to_delay);
bool intercept_triggered();

int64_t SearchDir(BackupStoreDirectory& rDir,
	const std::string& rChildName)
{
	BackupStoreDirectory::Iterator i(rDir);
	BackupStoreFilenameClear child(rChildName.c_str());
	BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child);
	if (en == 0) return 0;
	int64_t id = en->GetObjectID();
	TEST_THAT(id > 0);
	TEST_THAT(id != BackupProtocolClientListDirectory::RootDirectory);
	return id;
}

SocketStreamTLS sSocket;

std::auto_ptr<BackupProtocolClient> Connect(TLSContext& rContext)
{
	sSocket.Open(rContext, Socket::TypeINET, 
		"localhost", 22011);
	std::auto_ptr<BackupProtocolClient> connection;
	connection.reset(new BackupProtocolClient(sSocket));
	connection->Handshake();
	std::auto_ptr<BackupProtocolClientVersion> 
		serverVersion(connection->QueryVersion(
			BACKUP_STORE_SERVER_VERSION));
	if(serverVersion->GetVersion() != 
		BACKUP_STORE_SERVER_VERSION)
	{
		THROW_EXCEPTION(BackupStoreException, 
			WrongServerVersion);
	}
	return connection;
}

std::auto_ptr<BackupProtocolClient> ConnectAndLogin(TLSContext& rContext,
	int flags)
{
	std::auto_ptr<BackupProtocolClient> connection(Connect(rContext));
	connection->QueryLogin(0x01234567, flags);
	return connection;
}
	
std::auto_ptr<BackupStoreDirectory> ReadDirectory
(
	BackupProtocolClient& rClient,
	int64_t id
)
{
	std::auto_ptr<BackupProtocolClientSuccess> dirreply(
		rClient.QueryListDirectory(id, false, 0, false));
	std::auto_ptr<IOStream> dirstream(rClient.ReceiveStream());
	std::auto_ptr<BackupStoreDirectory> apDir(new BackupStoreDirectory());
	apDir->ReadFromStream(*dirstream, rClient.GetTimeout());
	return apDir;
}
	
int start_internal_daemon()
{
	// ensure that no child processes end up running tests!
	int own_pid = getpid();
	BOX_TRACE("Test PID is " << own_pid);
		
	// this is a quick hack to allow passing some options to the daemon
	const char* argv[] = {
		"dummy",
		bbackupd_args.c_str(),
	};

	BackupDaemon daemon;
	int result;

	if (bbackupd_args.size() > 0)
	{
		result = daemon.Main("testfiles/bbackupd.conf", 2, argv);
	}
	else
	{
		result = daemon.Main("testfiles/bbackupd.conf", 1, argv);
	}
	
	TEST_EQUAL_LINE(0, result, "Daemon exit code");
	
	// ensure that no child processes end up running tests!
	if (getpid() != own_pid)
	{
		// abort!
		BOX_INFO("Daemon child finished, exiting now.");
		_exit(0);
	}

	TEST_THAT(TestFileExists("testfiles/bbackupd.pid"));
	
	printf("Waiting for backup daemon to start: ");
	int pid = -1;
	
	for (int i = 0; i < 30; i++)
	{
		printf(".");
		fflush(stdout);
		safe_sleep(1);

		if (TestFileExists("testfiles/bbackupd.pid"))
		{
			pid = ReadPidFile("testfiles/bbackupd.pid");
		}

		if (pid > 0)
		{
			break;
		}		
	}
	
	printf(" done.\n");
	fflush(stdout);

	TEST_THAT(pid > 0);
	return pid;
}

bool stop_internal_daemon(int pid)
{
	bool killed_server = KillServer(pid, false);
	TEST_THAT(killed_server);
	return killed_server;
}

static struct dirent readdir_test_dirent;
static int readdir_test_counter = 0;
static int readdir_stop_time = 0;
static char stat_hook_filename[512];

// First test hook, during the directory scanning stage, returns empty.
// This will not match the directory on the store, so a sync will start.
// We set up the next intercept for the same directory by passing NULL.

extern "C" struct dirent *readdir_test_hook_2(DIR *dir);

#ifdef LINUX_WEIRD_LSTAT
extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf);
#else
extern "C" int lstat_test_hook(const char *file_name, struct stat *buf);
#endif

extern "C" struct dirent *readdir_test_hook_1(DIR *dir)
{
#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
	intercept_setup_readdir_hook(NULL, readdir_test_hook_2);
#endif
	return NULL;
}

// Second test hook, during the directory sync stage, keeps returning 
// new filenames until the timer expires, then disables the intercept.

extern "C" struct dirent *readdir_test_hook_2(DIR *dir)
{
	if (time(NULL) >= readdir_stop_time)
	{
#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
		intercept_setup_readdir_hook(NULL, NULL);
		intercept_setup_lstat_hook  (NULL, NULL);
		// we will not be called again.
#endif
	}

	// fill in the struct dirent appropriately
	memset(&readdir_test_dirent, 0, sizeof(readdir_test_dirent));

	#ifdef HAVE_STRUCT_DIRENT_D_INO
		readdir_test_dirent.d_ino = ++readdir_test_counter;
	#endif

	snprintf(readdir_test_dirent.d_name, 
		sizeof(readdir_test_dirent.d_name),
		"test.%d", readdir_test_counter);

	// ensure that when bbackupd stats the file, it gets the 
	// right answer
	snprintf(stat_hook_filename, sizeof(stat_hook_filename),
		"testfiles/TestDir1/spacetest/d1/test.%d", 
		readdir_test_counter);

#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
	intercept_setup_lstat_hook(stat_hook_filename, lstat_test_hook);
#endif

	return &readdir_test_dirent;
}

#ifdef LINUX_WEIRD_LSTAT
extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf)
#else
extern "C" int lstat_test_hook(const char *file_name, struct stat *buf)
#endif
{
	// TRACE1("lstat hook triggered for %s", file_name);
	memset(buf, 0, sizeof(*buf));
	buf->st_mode = S_IFREG;
	return 0;
}

// Simulate a symlink that is on a different device than the file
// that it points to.
int lstat_test_post_hook(int old_ret, const char *file_name, struct stat *buf)
{
	BOX_TRACE("lstat post hook triggered for " << file_name);
	if (old_ret == 0 &&
		strcmp(file_name, "testfiles/symlink-to-TestDir1") == 0)
	{
		buf->st_dev ^= 0xFFFF;
	}
	return old_ret;
}

bool test_entry_deleted(BackupStoreDirectory& rDir, 
	const std::string& rName)
{
	BackupStoreDirectory::Iterator i(rDir);

	BackupStoreDirectory::Entry *en = i.FindMatchingClearName(
		BackupStoreFilenameClear(rName));
	TEST_THAT(en != 0);
	if (en == 0) return false;

	int16_t flags = en->GetFlags();
	TEST_THAT(flags && BackupStoreDirectory::Entry::Flags_Deleted);
	return flags && BackupStoreDirectory::Entry::Flags_Deleted;
}

int test_bbackupd()
{
	// First, wait for a normal period to make sure the last changes 
	// attributes are within a normal backup timeframe.
	// wait_for_backup_operation();

	// Connection gubbins
	TLSContext context;
	context.Initialise(false /* client */,
			"testfiles/clientCerts.pem",
			"testfiles/clientPrivKey.pem",
			"testfiles/clientTrustedCAs.pem");

	printf("\n==== Testing that ReadDirectory on nonexistent directory "
		"does not crash\n");
	{
		std::auto_ptr<BackupProtocolClient> client = ConnectAndLogin(
			context, 0 /* read-write */);
		
		{
			Logging::Guard guard(Log::ERROR);
			TEST_CHECK_THROWS(ReadDirectory(*client, 0x12345678),
				ConnectionException,
				Conn_Protocol_UnexpectedReply);
		}

		client->QueryFinished();
		sSocket.Close();
	}

	// unpack the files for the initial test
	TEST_THAT(::system("rm -rf testfiles/TestDir1") == 0);
	TEST_THAT(::mkdir("testfiles/TestDir1", 0777) == 0);

	#ifdef WIN32
		TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz "
			"-C testfiles/TestDir1") == 0);
	#else
		TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz "
			"| ( cd testfiles/TestDir1 && tar xf - )") == 0);
	#endif

#ifdef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
	printf("\n==== Skipping intercept-based KeepAlive tests "
		"on this platform.\n");
#else
	printf("\n==== Testing SSL KeepAlive messages\n");

	{
		#ifdef WIN32
		#error TODO: implement threads on Win32, or this test \
			will not finish properly
		#endif

		// bbackupd daemon will try to initialise timers itself
		Timers::Cleanup();
		
		// something to diff against (empty file doesn't work)
		int fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
		TEST_THAT(fd > 0);

		char buffer[10000];
		memset(buffer, 0, sizeof(buffer));

		TEST_EQUAL_LINE(sizeof(buffer),
			write(fd, buffer, sizeof(buffer)),
			"Buffer write");
		TEST_THAT(close(fd) == 0);
		
		int pid = start_internal_daemon();
		wait_for_backup_operation("internal daemon to run a sync");
		TEST_THAT(stop_internal_daemon(pid));

		// two-second delay on the first read() of f1
		// should mean that a single keepalive is sent,
		// and diff does not abort.
		intercept_setup_delay("testfiles/TestDir1/spacetest/f1", 
			0, 2000, SYS_read, 1);
		TEST_THAT(unlink("testfiles/bbackupd.log") == 0);

		pid = start_internal_daemon();
		intercept_clear_setup();
		
		fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
		TEST_THAT(fd > 0);
		// write again, to update the file's timestamp
		TEST_EQUAL_LINE(sizeof(buffer),
			write(fd, buffer, sizeof(buffer)),
			"Buffer write");
		TEST_THAT(close(fd) == 0);	

		wait_for_backup_operation("internal daemon to sync "
			"spacetest/f1");
		// can't test whether intercept was triggered, because
		// it's in a different process.
		// TEST_THAT(intercept_triggered());
		TEST_THAT(stop_internal_daemon(pid));

		// check that keepalive was written to logs, and
		// diff was not aborted, i.e. upload was a diff
		FileStream fs("testfiles/bbackupd.log", O_RDONLY);
		IOStreamGetLine reader(fs);
		bool found1 = false;

		while (!reader.IsEOF())
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			if (line == "Send GetBlockIndexByName(0x3,\"f1\")")
			{
				found1 = true;
				break;
			}
		}

		TEST_THAT(found1);
		if (found1)
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			std::string comp = "Receive Success(0x";
			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
				line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receiving stream, size 124", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Send GetIsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receive IsAlive()", line);

			TEST_THAT(reader.GetLine(line));
			comp = "Send StoreFile(0x3,";
			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
				line);
			comp = ",\"f1\")";
			std::string sub = line.substr(line.size() - comp.size());
			TEST_EQUAL_LINE(comp, sub, line);
			std::string comp2 = ",0x0,";
			sub = line.substr(line.size() - comp.size() -
				comp2.size() + 1, comp2.size());
			TEST_LINE(comp2 != sub, line);
		}
		
		if (failures > 0)
		{
			// stop early to make debugging easier
			Timers::Init();
			return 1;
		}

		// four-second delay on first read() of f1
		// should mean that no keepalives were sent,
		// because diff was immediately aborted
		// before any matching blocks could be found.
		intercept_setup_delay("testfiles/TestDir1/spacetest/f1", 
			0, 4000, SYS_read, 1);
		pid = start_internal_daemon();
		intercept_clear_setup();
		
		fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
		TEST_THAT(fd > 0);
		// write again, to update the file's timestamp
		TEST_EQUAL_LINE(sizeof(buffer),
			write(fd, buffer, sizeof(buffer)),
			"Buffer write");
		TEST_THAT(close(fd) == 0);	

		wait_for_backup_operation("internal daemon to sync "
			"spacetest/f1 again");
		// can't test whether intercept was triggered, because
		// it's in a different process.
		// TEST_THAT(intercept_triggered());
		TEST_THAT(stop_internal_daemon(pid));

		// check that the diff was aborted, i.e. upload was not a diff
		found1 = false;

		while (!reader.IsEOF())
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			if (line == "Send GetBlockIndexByName(0x3,\"f1\")")
			{
				found1 = true;
				break;
			}
		}

		TEST_THAT(found1);
		if (found1)
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			std::string comp = "Receive Success(0x";
			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
				line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receiving stream, size 124", line);

			// delaying for 4 seconds in one step means that
			// the diff timer and the keepalive timer will
			// both expire, and the diff timer is honoured first,
			// so there will be no keepalives.

			TEST_THAT(reader.GetLine(line));
			comp = "Send StoreFile(0x3,";
			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
				line);
			comp = ",0x0,\"f1\")";
			std::string sub = line.substr(line.size() - comp.size());
			TEST_EQUAL_LINE(comp, sub, line);
		}

		if (failures > 0)
		{
			// stop early to make debugging easier
			Timers::Init();
			return 1;
		}

		intercept_setup_delay("testfiles/TestDir1/spacetest/f1", 
			0, 1000, SYS_read, 3);
		pid = start_internal_daemon();
		intercept_clear_setup();
		
		fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
		TEST_THAT(fd > 0);
		// write again, to update the file's timestamp
		TEST_EQUAL_LINE(sizeof(buffer),
			write(fd, buffer, sizeof(buffer)),
			"Buffer write");
		TEST_THAT(close(fd) == 0);	

		wait_for_backup_operation("internal daemon to sync "
			"spacetest/f1 again");
		// can't test whether intercept was triggered, because
		// it's in a different process.
		// TEST_THAT(intercept_triggered());
		TEST_THAT(stop_internal_daemon(pid));

		// check that the diff was aborted, i.e. upload was not a diff
		found1 = false;

		while (!reader.IsEOF())
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			if (line == "Send GetBlockIndexByName(0x3,\"f1\")")
			{
				found1 = true;
				break;
			}
		}

		TEST_THAT(found1);
		if (found1)
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			std::string comp = "Receive Success(0x";
			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
				line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receiving stream, size 124", line);

			// delaying for 3 seconds in steps of 1 second
			// means that the keepalive timer will expire 3 times,
			// and on the 3rd time the diff timer will expire too.
			// The diff timer is honoured first, so there will be 
			// only two keepalives.
			
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Send GetIsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receive IsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Send GetIsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receive IsAlive()", line);

			// but two matching blocks should have been found
			// already, so the upload should be a diff.

			TEST_THAT(reader.GetLine(line));
			comp = "Send StoreFile(0x3,";
			TEST_EQUAL_LINE(comp, line.substr(0, comp.size()),
				line);
			comp = ",\"f1\")";
			std::string sub = line.substr(line.size() - comp.size());
			TEST_EQUAL_LINE(comp, sub, line);
			std::string comp2 = ",0x0,";
			sub = line.substr(line.size() - comp.size() -
				comp2.size() + 1, comp2.size());
			TEST_LINE(comp2 != sub, line);
		}

		if (failures > 0)
		{
			// stop early to make debugging easier
			Timers::Init();
			return 1;
		}

		intercept_setup_readdir_hook("testfiles/TestDir1/spacetest/d1", 
			readdir_test_hook_1);
		
		// time for at least two keepalives
		readdir_stop_time = time(NULL) + 12 + 2;

		pid = start_internal_daemon();
		intercept_clear_setup();
		
		std::string touchfile = 
			"testfiles/TestDir1/spacetest/d1/touch-me";

		fd = open(touchfile.c_str(), O_CREAT | O_WRONLY);
		TEST_THAT(fd > 0);
		// write again, to update the file's timestamp
		TEST_EQUAL_LINE(sizeof(buffer),
			write(fd, buffer, sizeof(buffer)),
			"Buffer write");
		TEST_THAT(close(fd) == 0);	

		wait_for_backup_operation("internal daemon to scan "
			"spacetest/d1");
		// can't test whether intercept was triggered, because
		// it's in a different process.
		// TEST_THAT(intercept_triggered());
		TEST_THAT(stop_internal_daemon(pid));

		// check that keepalives were sent during the dir search
		found1 = false;

		// skip to next login
		while (!reader.IsEOF())
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			if (line == "Send ListDirectory(0x3,0xffff,0xc,true)")
			{
				found1 = true;
				break;
			}
		}

		TEST_THAT(found1);
		if (found1)
		{
			found1 = false;

			while (!reader.IsEOF())
			{
				std::string line;
				TEST_THAT(reader.GetLine(line));
				if (line == "Send ListDirectory(0x3,0xffffffff,0xc,true)")
				{
					found1 = true;
					break;
				}
			}
		}

		if (found1)
		{
			std::string line;
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receive Success(0x3)", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receiving stream, size 425", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Send GetIsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receive IsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Send GetIsAlive()", line);
			TEST_THAT(reader.GetLine(line));
			TEST_EQUAL("Receive IsAlive()", line);
		}

		if (failures > 0)
		{
			// stop early to make debugging easier
			Timers::Init();
			return 1;
		}

		TEST_THAT(unlink(touchfile.c_str()) == 0);

		// restore timers for rest of tests
		Timers::Init();
	}
#endif // PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE

	// Check that no read error has been reported yet
	TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

	std::string cmd = BBACKUPD " " + bbackupd_args + 
		" testfiles/bbackupd.conf";

	bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
	TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
	::safe_sleep(1);

	TEST_THAT(ServerIsAlive(bbackupd_pid));
	TEST_THAT(ServerIsAlive(bbstored_pid));
	if (!ServerIsAlive(bbackupd_pid)) return 1;
	if (!ServerIsAlive(bbstored_pid)) return 1;

	if(bbackupd_pid > 0)
	{
		printf("\n==== Testing that backup pauses when "
			"store is full\n");

		// wait for files to be uploaded
		BOX_TRACE("Waiting for all outstanding files to be uploaded")
		wait_for_sync_end();
		BOX_TRACE("done.")

		// Set limit to something very small
		// 26 blocks will be used at this point.
		// (12 files + location * 2 for raidfile)
		// 20 is what we'll need in a minute
		// set soft limit to 0 to ensure that all deleted files
		// are deleted immediately by housekeeping
		TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c "
			"testfiles/bbstored.conf setlimit 01234567 0B 20B") 
			== 0);
		TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");

		// Unpack some more files
		#ifdef WIN32
			TEST_THAT(::system("tar xzvf testfiles/spacetest2.tgz "
				"-C testfiles/TestDir1") == 0);
		#else
			TEST_THAT(::system("gzip -d < testfiles/spacetest2.tgz "
				"| ( cd testfiles/TestDir1 && tar xf - )") == 0);
		#endif

		// Delete a file and a directory
		TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0);
		TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0);

		// The following files should be on the server:
		// 00000001 -d---- 00002 (root)
		// 00000002 -d---- 00002 Test1
		// 00000003 -d---- 00002 Test1/spacetest
		// 00000004 f-X--- 00002 Test1/spacetest/f1
		// 00000005 f----- 00002 Test1/spacetest/f2
		// 00000006 -d---- 00002 Test1/spacetest/d1
		// 00000007 f----- 00002 Test1/spacetest/d1/f3
		// 00000008 f----- 00002 Test1/spacetest/d1/f4
		// 00000009 -d---- 00002 Test1/spacetest/d2
		// 0000000a -d---- 00002 Test1/spacetest/d3
		// 0000000b -d---- 00002 Test1/spacetest/d3/d4
		// 0000000c f----- 00002 Test1/spacetest/d3/d4/f5
		// 0000000d -d---- 00002 Test1/spacetest/d6
		// 0000000e -dX--- 00002 Test1/spacetest/d7
		// This is 28 blocks total, of which 2 in deleted files
		// and 18 in directories. Note that f1 and d7 may or may
		// not be deleted yet.
		//
		// spacetest1 + spacetest2 = 16 files = 32 blocks with raidfile
		// minus one file and one dir is 28 blocks
		//
		// d2/f6, d6/d8 and d6/d8/f7 are new
		// even if the client marks f1 and d7 as deleted, and
		// housekeeping deleted them, the backup cannot complete
		// if the limit is 20 blocks.

		BOX_TRACE("Waiting for sync for bbackupd to notice that the "
			"store is full");
		wait_for_sync_end();
		BOX_TRACE("Sync finished.");

		BOX_TRACE("Compare to check that there are differences");
		int compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query0a.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		BOX_TRACE("Compare finished.");

		// Check that the notify script was run
		TEST_THAT(TestFileExists("testfiles/notifyran.store-full.1"));
		// But only once!
		TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2"));
		
		// Kill the daemon
		terminate_bbackupd(bbackupd_pid);

		wait_for_operation(5, "housekeeping to remove the "
			"deleted files");

		// This removes f1 and d7, which were previously marked
		// as deleted, so total usage drops by 4 blocks to 24.

		// BLOCK
		{
			std::auto_ptr<BackupProtocolClient> client =
				ConnectAndLogin(context, 0 /* read-write */);
		
			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
				client->QueryGetAccountUsage());
			TEST_EQUAL_LINE(24, usage->GetBlocksUsed(),
				"blocks used");
			TEST_EQUAL_LINE(0,  usage->GetBlocksInDeletedFiles(),
				"deleted blocks");
			TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(),
				"directory blocks");

			client->QueryFinished();
			sSocket.Close();
		}

		if (failures > 0)
		{
			// stop early to make debugging easier
			return 1;
		}

		// ensure time is different to refresh the cache
		::safe_sleep(1);

		BOX_TRACE("Restart bbackupd with more exclusions");
		// Start again with a new config that excludes d3 and f2,
		// and hence also d3/d4 and d3/d4/f5. bbackupd should mark
		// them as deleted and housekeeping should clean up,
		// making space to upload the new files.
		// total required: (13-2-4+3)*2 = 20 blocks
		/*
		cmd = BBACKUPD " " + bbackupd_args +
			" testfiles/bbackupd-exclude.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;
		*/

		BackupDaemon bbackupd;
		bbackupd.Configure("testfiles/bbackupd-exclude.conf");
		bbackupd.InitCrypto();
		BOX_TRACE("done.");

		// Should be marked as deleted by this run
		// wait_for_sync_end();
		{
			// Logging::Guard guard(Log::ERROR);
			bbackupd.RunSyncNow();
		}

		TEST_THAT(bbackupd.StorageLimitExceeded());

		// Check that the notify script was run
		// TEST_THAT(TestFileExists("testfiles/notifyran.store-full.2"));
		// But only twice!
		// TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.3"));

		// All these should be marked as deleted but hopefully
		// not removed by housekeeping yet:
		// f1		deleted
		// f2		excluded
		// d1		excluded (why?)
		// d1/f3	excluded (why?)
		// d3		excluded
		// d3/d4	excluded
		// d3/d4/f5	excluded
		// d7		deleted
		// Careful with timing here, these files will be removed by
		// housekeeping the next time it runs. On Win32, housekeeping
		// runs immediately after disconnect, but only if enough time
		// has elapsed since the last housekeeping. Since the
		// backup run closely follows the last one, housekeeping
		// should not run afterwards. On other platforms, we want to
		// get in immediately after the backup and hope that
		// housekeeping doesn't beat us to it.

		BOX_TRACE("Find out whether bbackupd marked files as deleted");
		{
			std::auto_ptr<BackupProtocolClient> client =
				ConnectAndLogin(context, 0 /* read-write */);
		
			std::auto_ptr<BackupStoreDirectory> rootDir = 
				ReadDirectory(*client,
				BackupProtocolClientListDirectory::RootDirectory);

			int64_t testDirId = SearchDir(*rootDir, "Test1");
			TEST_THAT(testDirId != 0);

			std::auto_ptr<BackupStoreDirectory> Test1_dir =
				ReadDirectory(*client, testDirId);

			int64_t spacetestDirId = SearchDir(*Test1_dir, 
				"spacetest");
			TEST_THAT(spacetestDirId != 0);

			std::auto_ptr<BackupStoreDirectory> spacetest_dir =
				ReadDirectory(*client, spacetestDirId);

			// these files were deleted before, they should be
			// long gone by now

			TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0);
			TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0);

			// these files have just been deleted, because
			// they are excluded by the new configuration.
			// but housekeeping should not have run yet

			TEST_THAT(test_entry_deleted(*spacetest_dir, "f2"));
			TEST_THAT(test_entry_deleted(*spacetest_dir, "d3"));

			int64_t d3_id = SearchDir(*spacetest_dir, "d3");
			TEST_THAT(d3_id != 0);

			std::auto_ptr<BackupStoreDirectory> d3_dir =
				ReadDirectory(*client, d3_id);
			TEST_THAT(test_entry_deleted(*d3_dir, "d4"));

			int64_t d4_id = SearchDir(*d3_dir, "d4");
			TEST_THAT(d4_id != 0);

			std::auto_ptr<BackupStoreDirectory> d4_dir =
				ReadDirectory(*client, d4_id);
			TEST_THAT(test_entry_deleted(*d4_dir, "f5"));

			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
				client->QueryGetAccountUsage());
			TEST_EQUAL_LINE(24, usage->GetBlocksUsed(),
				"blocks used");
			TEST_EQUAL_LINE(4, usage->GetBlocksInDeletedFiles(),
				"deleted blocks");
			TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(),
				"directory blocks");
			// d1/f3 and d1/f4 are the only two files on the
			// server which are not deleted, they use 2 blocks
			// each, the rest is directories and 2 deleted files
			// (f1 and d3/d4/f5)

			// Log out.
			client->QueryFinished();
			sSocket.Close();
		}
		BOX_TRACE("done.");

		if (failures > 0)
		{
			// stop early to make debugging easier
			return 1;
		}

		wait_for_operation(5, "housekeeping to remove the "
			"deleted files");

		BOX_TRACE("Check that the files were removed");
		{
			std::auto_ptr<BackupProtocolClient> client = 
				ConnectAndLogin(context, 0 /* read-write */);
			
			std::auto_ptr<BackupStoreDirectory> rootDir = 
				ReadDirectory(*client,
				BackupProtocolClientListDirectory::RootDirectory);

			int64_t testDirId = SearchDir(*rootDir, "Test1");
			TEST_THAT(testDirId != 0);

			std::auto_ptr<BackupStoreDirectory> Test1_dir =
				ReadDirectory(*client, testDirId);

			int64_t spacetestDirId = SearchDir(*Test1_dir, 
				"spacetest");
			TEST_THAT(spacetestDirId != 0);

			std::auto_ptr<BackupStoreDirectory> spacetest_dir =
				ReadDirectory(*client, spacetestDirId);

			TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0);
			TEST_THAT(SearchDir(*spacetest_dir, "f2") == 0);
			TEST_THAT(SearchDir(*spacetest_dir, "d3") == 0);
			TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0);

			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
				client->QueryGetAccountUsage());
			TEST_EQUAL_LINE(16, usage->GetBlocksUsed(),
				"blocks used");
			TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(),
				"deleted blocks");
			TEST_EQUAL_LINE(12, usage->GetBlocksInDirectories(),
				"directory blocks");
			// d1/f3 and d1/f4 are the only two files on the
			// server, they use 2 blocks each, the rest is
			// directories.

			// Log out.
			client->QueryFinished();
			sSocket.Close();
		}

		if (failures > 0)
		{
			// stop early to make debugging easier
			return 1;
		}

		// Need 22 blocks free to upload everything
		TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c "
			"testfiles/bbstored.conf setlimit 01234567 0B 22B") 
			== 0);
		TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");

		// Run another backup, now there should be enough space
		// for everything we want to upload.
		{
			Logging::Guard guard(Log::ERROR);
			bbackupd.RunSyncNow();
		}
		TEST_THAT(!bbackupd.StorageLimitExceeded());

		// Check that the contents of the store are the same 
		// as the contents of the disc 
		// (-a = all, -c = give result in return code)
		BOX_TRACE("Check that all files were uploaded successfully");
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd-exclude.conf "
			"-l testfiles/query1.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		BOX_TRACE("done.");

		// BLOCK
		{
			std::auto_ptr<BackupProtocolClient> client = 
				ConnectAndLogin(context, 0 /* read-write */);

			std::auto_ptr<BackupProtocolClientAccountUsage> usage(
				client->QueryGetAccountUsage());
			TEST_EQUAL_LINE(22, usage->GetBlocksUsed(),
				"blocks used");
			TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(),
				"deleted blocks");
			TEST_EQUAL_LINE(14, usage->GetBlocksInDirectories(),
				"directory blocks");
			// d2/f6, d6/d8 and d6/d8/f7 are new
			// i.e. 2 new files, 1 new directory

			client->QueryFinished();
			sSocket.Close();
		}

		if (failures > 0)
		{
			// stop early to make debugging easier
			return 1;
		}

		// Put the limit back
		TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c "
			"testfiles/bbstored.conf setlimit 01234567 "
			"1000B 2000B") == 0);
		TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
	
		// Start again with the old config
		BOX_TRACE("Restart bbackupd with original configuration");
		// terminate_bbackupd();
		cmd = BBACKUPD " " + bbackupd_args +
			" testfiles/bbackupd.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		::safe_sleep(1);
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;
		BOX_TRACE("done.");

		// unpack the initial files again
		#ifdef WIN32
			TEST_THAT(::system("tar xzvf testfiles/test_base.tgz "
				"-C testfiles") == 0);
		#else
			TEST_THAT(::system("gzip -d < testfiles/test_base.tgz "
				"| ( cd testfiles && tar xf - )") == 0);
		#endif

		wait_for_backup_operation("bbackupd to upload more files");
		
		// Check that the contents of the store are the same 
		// as the contents of the disc 
		// (-a = all, -c = give result in return code)
		BOX_TRACE("Check that all files were uploaded successfully");
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query1.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue, 
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		BOX_TRACE("done.");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if (failures > 0)
		{
			// stop early to make debugging easier
			return 1;
		}
	}

	// Check that no read error has been reported yet
	TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

	#ifndef WIN32 // requires fork
	printf("\n==== Testing that bbackupd responds correctly to "
		"connection failure\n");

	{
		// Kill the daemons
		terminate_bbackupd(bbackupd_pid);
		test_kill_bbstored();

		// create a new file to force an upload

		const char* new_file = "testfiles/TestDir1/force-upload-2";
		int fd = open(new_file, 
			O_CREAT | O_EXCL | O_WRONLY, 0700);
		if (fd <= 0)
		{
			perror(new_file);
		}
		TEST_THAT(fd > 0);
	
		const char* control_string = "whee!\n";
		TEST_THAT(write(fd, control_string, 
			strlen(control_string)) ==
			(int)strlen(control_string));
		close(fd);

		// sleep to make it old enough to upload
		safe_sleep(4);

		class MyHook : public BackupStoreContext::TestHook
		{
			virtual std::auto_ptr<ProtocolObject> StartCommand(
				BackupProtocolObject& rCommand)
			{
				if (rCommand.GetType() ==
					BackupProtocolServerStoreFile::TypeID)
				{
					// terminate badly
					THROW_EXCEPTION(CommonException,
						Internal);
				}
				return std::auto_ptr<ProtocolObject>();
			}
		};
		MyHook hook;

		bbstored_pid = fork();

		if (bbstored_pid < 0)
		{
			BOX_LOG_SYS_ERROR("failed to fork()");
			return 1;
		}

		if (bbstored_pid == 0)
		{
			// in fork child
			TEST_THAT(setsid() != -1);

			if (!Logging::IsEnabled(Log::TRACE))
			{
				Logging::SetGlobalLevel(Log::NOTHING);
			}

			// BackupStoreDaemon must be destroyed before exit(),
			// to avoid memory leaks being reported.
			{
				BackupStoreDaemon bbstored;
				bbstored.SetTestHook(hook);
				bbstored.SetRunInForeground(true);
				bbstored.Main("testfiles/bbstored.conf");
			}

			Timers::Cleanup(); // avoid memory leaks
			exit(0);
		}

		// in fork parent
		bbstored_pid = WaitForServerStartup("testfiles/bbstored.pid",
			bbstored_pid);

		TEST_THAT(::system("rm -f testfiles/notifyran.store-full.*") == 0);

		// Ignore SIGPIPE so that when the connection is broken,
		// the daemon doesn't terminate.
		::signal(SIGPIPE, SIG_IGN);

		{
			Log::Level newLevel = Logging::GetGlobalLevel();

			if (!Logging::IsEnabled(Log::TRACE))
			{
				newLevel = Log::NOTHING;
			}

			Logging::Guard guard(newLevel);

			BackupDaemon bbackupd;
			bbackupd.Configure("testfiles/bbackupd.conf");
			bbackupd.InitCrypto();
			bbackupd.RunSyncNowWithExceptionHandling();
		}

		::signal(SIGPIPE, SIG_DFL);

		TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1"));
		TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2"));
		TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.1"));

		test_kill_bbstored(true);

		if (failures > 0)
		{
			// stop early to make debugging easier
			return 1;
		}

		TEST_THAT(test_run_bbstored() == 0);

		cmd = BBACKUPD " " + bbackupd_args +
			" testfiles/bbackupd.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		::safe_sleep(1);
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;
	}
	#endif // !WIN32

	#ifndef WIN32
	printf("\n==== Testing that absolute symlinks are not followed "
		"during restore\n");

	{
		#define SYM_DIR "testfiles" DIRECTORY_SEPARATOR "TestDir1" \
			DIRECTORY_SEPARATOR "symlink_test"

		TEST_THAT(::mkdir(SYM_DIR, 0777) == 0);
		TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a", 0777) == 0);
		TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a"
			DIRECTORY_SEPARATOR "subdir", 0777) == 0);
		TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "b", 0777) == 0);

		FILE* fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a"
			DIRECTORY_SEPARATOR "subdir"
			DIRECTORY_SEPARATOR "content", "w");
		TEST_THAT(fp != NULL);
		fputs("before\n", fp);
		fclose(fp);

		char buf[PATH_MAX];
		TEST_THAT(getcwd(buf, sizeof(buf)) == buf);
		std::string path = buf;
		path += DIRECTORY_SEPARATOR SYM_DIR 
			DIRECTORY_SEPARATOR "a"
			DIRECTORY_SEPARATOR "subdir";
		TEST_THAT(symlink(path.c_str(), SYM_DIR 
			DIRECTORY_SEPARATOR "b"
			DIRECTORY_SEPARATOR "link") == 0);

		// also test symlink-to-self loop does not break restore
		TEST_THAT(symlink("self", SYM_DIR "/self") == 0);

		wait_for_operation(4, "symlinks to be old enough");
		sync_and_wait();

		// Check that the backup was successful, i.e. no differences
		int compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query1.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// now stop bbackupd and update the test file,
		// make the original directory unreadable
		terminate_bbackupd(bbackupd_pid);

		fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a"
			DIRECTORY_SEPARATOR "subdir"
			DIRECTORY_SEPARATOR "content", "w");
		TEST_THAT(fp != NULL);
		fputs("after\n", fp);
		fclose(fp);

		TEST_THAT(chmod(SYM_DIR, 0) == 0);

		// check that we can restore it
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-Wwarning \"restore Test1 testfiles/restore-symlink\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);

		// make it accessible again
		TEST_THAT(chmod(SYM_DIR, 0755) == 0);

		// check that the original file was not overwritten
		FileStream fs(SYM_DIR "/a/subdir/content");
		IOStreamGetLine gl(fs);
		std::string line;
		TEST_THAT(gl.GetLine(line));
		TEST_THAT(line != "before");
		TEST_EQUAL("after", line);

		#undef SYM_DIR

		/*
 		// This is not worth testing or fixing.
 		//
		#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
		printf("\n==== Testing that symlinks to other filesystems "
			"can be backed up as roots\n");

		intercept_setup_lstat_post_hook(lstat_test_post_hook);
		TEST_THAT(symlink("TestDir1", "testfiles/symlink-to-TestDir1")
			== 0);

		struct stat stat_st, lstat_st;
		TEST_THAT(stat("testfiles/symlink-to-TestDir1", &stat_st) == 0);
		TEST_THAT(lstat("testfiles/symlink-to-TestDir1", &lstat_st) == 0);
		TEST_EQUAL_LINE((stat_st.st_dev ^ 0xFFFF), lstat_st.st_dev,
			"stat vs lstat");

		BackupDaemon bbackupd;
		bbackupd.Configure("testfiles/bbackupd-symlink.conf");
		bbackupd.InitCrypto();
		bbackupd.RunSyncNow();
		intercept_clear_setup();

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query0a.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// and again using the symlink during compare
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd-symlink.conf "
			"-l testfiles/query0a.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		#endif
		*/

		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		::safe_sleep(1);

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;
	}
	#endif // !WIN32

	// Check that no read error has been reported yet
	TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

	printf("\n==== Testing that redundant locations are deleted on time\n");

	// unpack the files for the redundant location test
	TEST_THAT(::system("rm -rf testfiles/TestDir2") == 0);
	TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0);

	#ifdef WIN32
		TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz "
			"-C testfiles/TestDir2") == 0);
	#else
		TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz "
			"| ( cd testfiles/TestDir2 && tar xf - )") == 0);
	#endif

	// BLOCK
	{
		// Kill the daemon
		terminate_bbackupd(bbackupd_pid);

		// Start it with a config that has a temporary location
		// that will be created on the server
		std::string cmd = BBACKUPD " " + bbackupd_args + 
			" testfiles/bbackupd-temploc.conf";

		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		::safe_sleep(1);

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		sync_and_wait();

		{
			std::auto_ptr<BackupProtocolClient> client =
				ConnectAndLogin(context,
				BackupProtocolClientLogin::Flags_ReadOnly);
			
			std::auto_ptr<BackupStoreDirectory> dir = 
				ReadDirectory(*client,
				BackupProtocolClientListDirectory::RootDirectory);
			int64_t testDirId = SearchDir(*dir, "Test2");
			TEST_THAT(testDirId != 0);

			client->QueryFinished();
			sSocket.Close();
		}

		// Kill the daemon
		terminate_bbackupd(bbackupd_pid);

		// Start it again with the normal config (no Test2)
		cmd = BBACKUPD " " + bbackupd_args +
			" testfiles/bbackupd.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");

		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);

		::safe_sleep(1);

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Test2 should be deleted after 10 seconds (4 runs)
		wait_for_sync_end();
		wait_for_sync_end();
		wait_for_sync_end();

		// not yet! should still be there

		{
			std::auto_ptr<BackupProtocolClient> client =
				ConnectAndLogin(context,
				BackupProtocolClientLogin::Flags_ReadOnly);
			
			std::auto_ptr<BackupStoreDirectory> dir = 
				ReadDirectory(*client,
				BackupProtocolClientListDirectory::RootDirectory);
			int64_t testDirId = SearchDir(*dir, "Test2");
			TEST_THAT(testDirId != 0);

			client->QueryFinished();
			sSocket.Close();
		}

		wait_for_sync_end();

		// NOW it should be gone

		{
			std::auto_ptr<BackupProtocolClient> client =
				ConnectAndLogin(context,
				BackupProtocolClientLogin::Flags_ReadOnly);
			
			std::auto_ptr<BackupStoreDirectory> root_dir = 
				ReadDirectory(*client,
				BackupProtocolClientListDirectory::RootDirectory);

			TEST_THAT(test_entry_deleted(*root_dir, "Test2"));

			client->QueryFinished();
			sSocket.Close();
		}
	}

	TEST_THAT(ServerIsAlive(bbackupd_pid));
	TEST_THAT(ServerIsAlive(bbstored_pid));
	if (!ServerIsAlive(bbackupd_pid)) return 1;
	if (!ServerIsAlive(bbstored_pid)) return 1;

	if(bbackupd_pid > 0)
	{
		// Check that no read error has been reported yet
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

		printf("\n==== Check that read-only directories and "
			"their contents can be restored.\n");

		int compareReturnValue;

		{
			#ifdef WIN32
				TEST_THAT(::system("chmod 0555 testfiles/"
					"TestDir1/x1") == 0);
			#else
				TEST_THAT(chmod("testfiles/TestDir1/x1",
					0555) == 0);
			#endif

			wait_for_sync_end(); // too new
			wait_for_sync_end(); // should be backed up now

			compareReturnValue = ::system(BBACKUPQUERY " "
				"-Wwarning "
				"-c testfiles/bbackupd.conf "
				"\"compare -cEQ Test1 testfiles/TestDir1\" " 
				"quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			// check that we can restore it
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-Wwarning "
				"-c testfiles/bbackupd.conf "
				"\"restore Test1 testfiles/restore1\" "
				"quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Command_OK);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			// check that it restored properly
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-Wwarning "
				"-c testfiles/bbackupd.conf "
				"\"compare -cEQ Test1 testfiles/restore1\" " 
				"quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			// put the permissions back to sensible values
			#ifdef WIN32
				TEST_THAT(::system("chmod 0755 testfiles/"
					"TestDir1/x1") == 0);
				TEST_THAT(::system("chmod 0755 testfiles/"
					"restore1/x1") == 0);
			#else
				TEST_THAT(chmod("testfiles/TestDir1/x1",
					0755) == 0);
				TEST_THAT(chmod("testfiles/restore1/x1",
					0755) == 0);
			#endif

		}

#ifdef WIN32
		printf("\n==== Check that filenames in UTF-8 "
			"can be backed up\n");

		// We have no guarantee that a random Unicode string can be
		// represented in the user's character set, so we go the 
		// other way, taking three random characters from the 
		// character set and converting them to Unicode. 
		//
		// We hope that these characters are valid in most 
		// character sets, but they probably are not in multibyte 
		// character sets such as Shift-JIS, GB2312, etc. This test 
		// will probably fail if your system locale is set to 
		// Chinese, Japanese, etc. where one of these character
		// sets is used by default. You can check the character
		// set for your system in Control Panel -> Regional 
		// Options -> General -> Language Settings -> Set Default
		// (System Locale). Because bbackupquery converts from
		// system locale to UTF-8 via the console code page
		// (which you can check from the Command Prompt with "chcp")
		// they must also be valid in your code page (850 for
		// Western Europe).
		//
		// In ISO-8859-1 (Danish locale) they are three Danish 
		// accented characters, which are supported in code page
		// 850. Depending on your locale, YYMV (your yak may vomit).

		std::string foreignCharsNative("\x91\x9b\x86");
		std::string foreignCharsUnicode;
		TEST_THAT(ConvertConsoleToUtf8(foreignCharsNative.c_str(),
			foreignCharsUnicode));

		std::string basedir("testfiles/TestDir1");
		std::string dirname("test" + foreignCharsUnicode + "testdir");
		std::string dirpath(basedir + "/" + dirname);
		TEST_THAT(mkdir(dirpath.c_str(), 0) == 0);

		std::string filename("test" + foreignCharsUnicode + "testfile");
		std::string filepath(dirpath + "/" + filename);

		char cwdbuf[1024];
		TEST_THAT(getcwd(cwdbuf, sizeof(cwdbuf)) == cwdbuf);
		std::string cwd = cwdbuf;

		// Test that our emulated chdir() works properly
		// with relative and absolute paths
		TEST_THAT(::chdir(dirpath.c_str()) == 0);
		TEST_THAT(::chdir("../../..") == 0);
		TEST_THAT(::chdir(cwd.c_str()) == 0);

		// Check that it can be converted to the system encoding
		// (which is what is needed on the command line)
		std::string systemDirName;
		TEST_THAT(ConvertEncoding(dirname.c_str(), CP_UTF8,
			systemDirName, CP_ACP));

		std::string systemFileName;
		TEST_THAT(ConvertEncoding(filename.c_str(), CP_UTF8,
			systemFileName, CP_ACP));

		// Check that it can be converted to the console encoding
		// (which is what we will see in the output)
		std::string consoleDirName;
		TEST_THAT(ConvertUtf8ToConsole(dirname.c_str(),
			consoleDirName));

		std::string consoleFileName;
		TEST_THAT(ConvertUtf8ToConsole(filename.c_str(),
			consoleFileName));

		// test that bbackupd will let us lcd into the local 
		// directory using a relative path
		std::string command = BBACKUPQUERY " "
			"-Wwarning "
			"-c testfiles/bbackupd.conf "
			"\"lcd testfiles/TestDir1/" + systemDirName + "\" "
			"quit";
		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);

		// and back out again
		command = BBACKUPQUERY " "
			"-Wwarning "
			"-c testfiles/bbackupd.conf "
			"\"lcd testfiles/TestDir1/" + systemDirName + "\" "
			"\"lcd ..\" quit";
		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);

		// and using an absolute path
		command = BBACKUPQUERY " "
			"-Wwarning "
			"-c testfiles/bbackupd.conf "
			"\"lcd " + cwd + "/testfiles/TestDir1/" + 
			systemDirName + "\" quit";
		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);

		// and back out again
		command = BBACKUPQUERY " "
			"-Wwarning "
			"-c testfiles/bbackupd.conf "
			"\"lcd " + cwd + "/testfiles/TestDir1/" + 
			systemDirName + "\" "
			"\"lcd ..\" quit";
		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);

		{
			FileStream fs(filepath.c_str(), O_CREAT | O_RDWR);

			std::string data("hello world\n");
			fs.Write(data.c_str(), data.size());
			TEST_EQUAL_LINE(12, fs.GetPosition(),
				"FileStream position");
			fs.Close();
		}

		wait_for_backup_operation("upload of file with unicode name");

		// Compare to check that the file was uploaded
		compareReturnValue = ::system(BBACKUPQUERY " -Wwarning "
			"-c testfiles/bbackupd.conf \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// Check that we can find it in directory listing
		{
			std::auto_ptr<BackupProtocolClient> client =
				ConnectAndLogin(context, 0);

			std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory(
				*client, 
				BackupProtocolClientListDirectory::RootDirectory);

			int64_t baseDirId = SearchDir(*dir, "Test1");
			TEST_THAT(baseDirId != 0);
			dir = ReadDirectory(*client, baseDirId);

			int64_t testDirId = SearchDir(*dir, dirname.c_str());
			TEST_THAT(testDirId != 0);
			dir = ReadDirectory(*client, testDirId);
		
			TEST_THAT(SearchDir(*dir, filename.c_str()) != 0);
			// Log out
			client->QueryFinished();
			sSocket.Close();
		}

		// Check that bbackupquery shows the dir in console encoding
		command = BBACKUPQUERY " -Wwarning "
			"-c testfiles/bbackupd.conf "
			"-q \"list Test1\" quit";
		pid_t bbackupquery_pid;
		std::auto_ptr<IOStream> queryout;
		queryout = LocalProcessStream(command.c_str(), 
			bbackupquery_pid);
		TEST_THAT(queryout.get() != NULL);
		TEST_THAT(bbackupquery_pid != -1);

		IOStreamGetLine reader(*queryout);
		std::string line;
		bool found = false;
		while (!reader.IsEOF())
		{
			TEST_THAT(reader.GetLine(line));
			if (line.find(consoleDirName) != std::string::npos)
			{
				found = true;
			}
		}
		TEST_THAT(!(queryout->StreamDataLeft()));
		TEST_THAT(reader.IsEOF());
		TEST_THAT(found);
		queryout->Close();

		// Check that bbackupquery can list the dir when given
		// on the command line in system encoding, and shows
		// the file in console encoding
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf "
			"-Wwarning \"list Test1/" + systemDirName + "\" quit";
		queryout = LocalProcessStream(command.c_str(), 
			bbackupquery_pid);
		TEST_THAT(queryout.get() != NULL);
		TEST_THAT(bbackupquery_pid != -1);

		IOStreamGetLine reader2(*queryout);
		found = false;
		while (!reader2.IsEOF())
		{
			TEST_THAT(reader2.GetLine(line));
			if (line.find(consoleFileName) != std::string::npos)
			{
				found = true;
			}
		}
		TEST_THAT(!(queryout->StreamDataLeft()));
		TEST_THAT(reader2.IsEOF());
		TEST_THAT(found);
		queryout->Close();

		// Check that bbackupquery can compare the dir when given
		// on the command line in system encoding.
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf "
			"-Wwarning \"compare -cEQ Test1/" + systemDirName +
			" testfiles/TestDir1/" + systemDirName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);

		// Check that bbackupquery can restore the dir when given
		// on the command line in system encoding.
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf "
			"-Wwarning \"restore Test1/" + systemDirName +
			" testfiles/restore-" + systemDirName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);

		// Compare to make sure it was restored properly.
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf "
			"-Wwarning \"compare -cEQ Test1/" + systemDirName +
			" testfiles/restore-" + systemDirName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);

		std::string fileToUnlink = "testfiles/restore-" + 
			dirname + "/" + filename;
		TEST_THAT(::unlink(fileToUnlink.c_str()) == 0);

		// Check that bbackupquery can get the file when given
		// on the command line in system encoding.
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf "
			"-Wwarning \"get Test1/" + systemDirName + "/" + 
			systemFileName + " " + "testfiles/restore-" + 
			systemDirName + "/" + systemFileName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// And after changing directory to a relative path
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf "
			"-Wwarning "
			"\"lcd testfiles\" "
			"\"cd Test1/" + systemDirName + "\" " + 
			"\"get " + systemFileName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);
		TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks");

		// cannot overwrite a file that exists, so delete it
		std::string tmp = "testfiles/" + filename;
		TEST_THAT(::unlink(tmp.c_str()) == 0);

		// And after changing directory to an absolute path
		command = BBACKUPQUERY " -c testfiles/bbackupd.conf -Wwarning "
			"\"lcd " + cwd + "/testfiles\" "
			"\"cd Test1/" + systemDirName + "\" " + 
			"\"get " + systemFileName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Command_OK);
		TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks");

		// Compare to make sure it was restored properly.
		// The Get command does not restore attributes, so
		// we must compare without them (-A) to succeed.
		command = BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-Wwarning \"compare -cAEQ Test1/" + systemDirName +
			" testfiles/restore-" + systemDirName + "\" quit";

		compareReturnValue = ::system(command.c_str());
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);

		// Compare without attributes. This should fail.
		command = BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-Werror \"compare -cEQ Test1/" + systemDirName +
			" testfiles/restore-" + systemDirName + "\" quit";
		compareReturnValue = ::system(command.c_str());
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
#endif // WIN32

		// Check that no read error has been reported yet
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Check that SyncAllowScript is executed and can "
			"pause backup\n");
		fflush(stdout);

		{
			wait_for_sync_end();
			// we now have 3 seconds before bbackupd
			// runs the SyncAllowScript again.

			const char* sync_control_file = "testfiles" 
				DIRECTORY_SEPARATOR "syncallowscript.control";
			int fd = open(sync_control_file, 
				O_CREAT | O_EXCL | O_WRONLY, 0700);
			if (fd <= 0)
			{
				perror(sync_control_file);
			}
			TEST_THAT(fd > 0);
		
			const char* control_string = "10\n";
			TEST_THAT(write(fd, control_string, 
				strlen(control_string)) ==
				(int)strlen(control_string));
			close(fd);

			// this will pause backups, bbackupd will check
			// every 10 seconds to see if they are allowed again.

			const char* new_test_file = "testfiles"
				DIRECTORY_SEPARATOR "TestDir1"
				DIRECTORY_SEPARATOR "Added_During_Pause";
			fd = open(new_test_file,
				O_CREAT | O_EXCL | O_WRONLY, 0700);
			if (fd <= 0)
			{
				perror(new_test_file);
			}
			TEST_THAT(fd > 0);
			close(fd);

			struct stat st;

			// next poll should happen within the next
			// 5 seconds (normally about 3 seconds)

			wait_for_operation(1, "2 seconds before next run");
			TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR 
				"syncallowscript.notifyran.1", &st) != 0);
			wait_for_operation(4, "2 seconds after run");
			TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR 
				"syncallowscript.notifyran.1", &st) == 0);
			TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR 
				"syncallowscript.notifyran.2", &st) != 0);

			// next poll should happen within the next
			// 10 seconds (normally about 8 seconds)

			wait_for_operation(6, "2 seconds before next run");
			TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR 
				"syncallowscript.notifyran.2", &st) != 0);
			wait_for_operation(4, "2 seconds after run");
			TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR 
				"syncallowscript.notifyran.2", &st) == 0);

			// bbackupquery compare might take a while
			// on slow machines, so start the timer now
			long start_time = time(NULL);

			// check that no backup has run (compare fails)
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-Werror "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query3.log "
				"\"compare -acQ\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Different);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			TEST_THAT(unlink(sync_control_file) == 0);
			wait_for_sync_start();
			long end_time = time(NULL);

			long wait_time = end_time - start_time + 2;

			// should be about 10 seconds
			if (wait_time < 8 || wait_time > 12)
			{
				printf("Waited for %ld seconds, should have "
					"been %s", wait_time, control_string);
			}

			TEST_THAT(wait_time >= 8);
			TEST_THAT(wait_time <= 12);

			wait_for_sync_end();
			// check that backup has run (compare succeeds)
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-Wwarning "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query3a.log "
				"\"compare -acQ\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			if (failures > 0)
			{
				// stop early to make debugging easier
				return 1;
			}
		}

		// Check that no read error has been reported yet
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Delete file and update another, "
			"create symlink.\n");
		
		// Delete a file
		TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0);

		#ifndef WIN32
			// New symlink
			TEST_THAT(::symlink("does-not-exist", 
				"testfiles/TestDir1/symlink-to-dir") == 0);
		#endif		

		// Update a file (will be uploaded as a diff)
		{
			// Check that the file is over the diffing 
			// threshold in the bbackupd.conf file
			TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") 
				> 1024);
			
			// Add a bit to the end
			FILE *f = ::fopen("testfiles/TestDir1/f45.df", "a");
			TEST_THAT(f != 0);
			::fprintf(f, "EXTRA STUFF");
			::fclose(f);
			TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") 
				> 1024);
		}

		// wait long enough for new files to be old enough to backup
		wait_for_operation(5, "new files to be old enough");

		// wait for backup daemon to do it's stuff
		sync_and_wait();

		// compare to make sure that it worked
		compareReturnValue = ::system(BBACKUPQUERY " -Wwarning "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query2.log "
			"\"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// Try a quick compare, just for fun
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query2q.log "
			"-Wwarning \"compare -acqQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Check that store errors are reported neatly
		printf("\n==== Create store error\n");
		TEST_THAT(system("rm -f testfiles/notifyran.backup-error.*")
			== 0);

		// break the store
		TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf",
			"testfiles/0_0/backup/01234567/info.rf.bak") == 0);
		TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf",
			"testfiles/0_1/backup/01234567/info.rf.bak") == 0);
		TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf",
			"testfiles/0_2/backup/01234567/info.rf.bak") == 0);

		// Create a file to trigger an upload
		{
			int fd1 = open("testfiles/TestDir1/force-upload", 
				O_CREAT | O_EXCL | O_WRONLY, 0700);
			TEST_THAT(fd1 > 0);
			TEST_THAT(write(fd1, "just do it", 10) == 10);
			TEST_THAT(close(fd1) == 0);
		}

		wait_for_operation(4, "bbackupd to try to access the store");

		// Check that an error was reported just once
		TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1"));
		TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2"));
		// Now kill bbackupd and start one that's running in
		// snapshot mode, check that it automatically syncs after
		// an error, without waiting for another sync command.
		terminate_bbackupd(bbackupd_pid);
		std::string cmd = BBACKUPD " " + bbackupd_args + 
			" testfiles/bbackupd-snapshot.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		::safe_sleep(1);
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		sync_and_wait();

		// Check that the error was reported once more
		TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.2"));
		TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.3"));

		// Fix the store (so that bbackupquery compare works)
		TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak",
			"testfiles/0_0/backup/01234567/info.rf") == 0);
		TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak",
			"testfiles/0_1/backup/01234567/info.rf") == 0);
		TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak",
			"testfiles/0_2/backup/01234567/info.rf") == 0);

		int store_fixed_time = time(NULL);

		// Check that we DO get errors on compare (cannot do this
		// until after we fix the store, which creates a race)
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3b.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		// Test initial state
		TEST_THAT(!TestFileExists("testfiles/"
			"notifyran.backup-start.wait-snapshot.1"));

		// Set a tag for the notify script to distinguish from
		// previous runs.
		{
			int fd1 = open("testfiles/notifyscript.tag", 
				O_CREAT | O_EXCL | O_WRONLY, 0700);
			TEST_THAT(fd1 > 0);
			TEST_THAT(write(fd1, "wait-snapshot", 13) == 13);
			TEST_THAT(close(fd1) == 0);
		}

		// bbackupd should pause for about 90 seconds from
		// store_fixed_time, so check that it hasn't run after
		// 85 seconds after store_fixed_time
		wait_for_operation(85 - time(NULL) + store_fixed_time,
			"just before bbackupd recovers");
		TEST_THAT(!TestFileExists("testfiles/"
			"notifyran.backup-start.wait-snapshot.1"));

		// Should not have backed up, should still get errors
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3b.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		// wait another 10 seconds, bbackup should have run
		wait_for_operation(10, "bbackupd to recover");
		TEST_THAT(TestFileExists("testfiles/"
			"notifyran.backup-start.wait-snapshot.1"));
	
		// Check that it did get uploaded, and we have no more errors
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3b.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0);

		// Stop the snapshot bbackupd
		terminate_bbackupd(bbackupd_pid);

		// Break the store again
		TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf",
			"testfiles/0_0/backup/01234567/info.rf.bak") == 0);
		TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf",
			"testfiles/0_1/backup/01234567/info.rf.bak") == 0);
		TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf",
			"testfiles/0_2/backup/01234567/info.rf.bak") == 0);

		// Modify a file to trigger an upload
		{
			int fd1 = open("testfiles/TestDir1/force-upload", 
				O_WRONLY, 0700);
			TEST_THAT(fd1 > 0);
			TEST_THAT(write(fd1, "and again", 9) == 9);
			TEST_THAT(close(fd1) == 0);
		}

		// Restart the old bbackupd, in automatic mode
		cmd = BBACKUPD " " + bbackupd_args + 
			" testfiles/bbackupd.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
		::safe_sleep(1);
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		sync_and_wait();

		// Fix the store again
		TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak",
			"testfiles/0_0/backup/01234567/info.rf") == 0);
		TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak",
			"testfiles/0_1/backup/01234567/info.rf") == 0);
		TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak",
			"testfiles/0_2/backup/01234567/info.rf") == 0);

		store_fixed_time = time(NULL);

		// Check that we DO get errors on compare (cannot do this
		// until after we fix the store, which creates a race)
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3b.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		// Test initial state
		TEST_THAT(!TestFileExists("testfiles/"
			"notifyran.backup-start.wait-automatic.1"));

		// Set a tag for the notify script to distinguist from
		// previous runs.
		{
			int fd1 = open("testfiles/notifyscript.tag", 
				O_CREAT | O_EXCL | O_WRONLY, 0700);
			TEST_THAT(fd1 > 0);
			TEST_THAT(write(fd1, "wait-automatic", 14) == 14);
			TEST_THAT(close(fd1) == 0);
		}

		// bbackupd should pause for about 90 seconds from
		// store_fixed_time, so check that it hasn't run after
		// 85 seconds from store_fixed_time
		wait_for_operation(85 - time(NULL) + store_fixed_time,
			"just before bbackupd recovers");
		TEST_THAT(!TestFileExists("testfiles/"
			"notifyran.backup-start.wait-automatic.1"));

		// Should not have backed up, should still get errors
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3b.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		// wait another 10 seconds, bbackup should have run
		wait_for_operation(10, "bbackupd to recover");
		TEST_THAT(TestFileExists("testfiles/"
			"notifyran.backup-start.wait-automatic.1"));
	
		// Check that it did get uploaded, and we have no more errors
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3b.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0);

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Bad case: delete a file/symlink, replace it with a directory
		printf("\n==== Replace symlink with directory, "
			"add new directory\n");

		#ifndef WIN32
			TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir")
				== 0);
		#endif

		TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) 
			== 0);
		TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) 
			== 0);

		// NOTE: create a file within the directory to 
		// avoid deletion by the housekeeping process later

		#ifndef WIN32
			TEST_THAT(::symlink("does-not-exist", 
				"testfiles/TestDir1/x1/dir-to-file/contents") 
				== 0);
		#endif

		wait_for_backup_operation("bbackupd to sync the changes");
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3c.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");		

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// And the inverse, replace a directory with a file/symlink
		printf("\n==== Replace directory with symlink\n");

		#ifndef WIN32
			TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file"
				"/contents") == 0);
		#endif

		TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0);

		#ifndef WIN32
			TEST_THAT(::symlink("does-not-exist", 
				"testfiles/TestDir1/x1/dir-to-file") == 0);
		#endif

		wait_for_backup_operation("bbackupd to sync the changes");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3d.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// And then, put it back to how it was before.
		printf("\n==== Replace symlink with directory "
			"(which was a symlink)\n");

		#ifndef WIN32
			TEST_THAT(::unlink("testfiles/TestDir1/x1"
				"/dir-to-file") == 0);
		#endif

		TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 
			0755) == 0);

		#ifndef WIN32
			TEST_THAT(::symlink("does-not-exist", 
				"testfiles/TestDir1/x1/dir-to-file/contents2")
				== 0);
		#endif

		wait_for_backup_operation("bbackupd to sync the changes");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3e.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// And finally, put it back to how it was before 
		// it was put back to how it was before
		// This gets lots of nasty things in the store with 
		// directories over other old directories.
		printf("\n==== Put it all back to how it was\n");

		#ifndef WIN32
			TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file"
				"/contents2") == 0);
		#endif

		TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0);

		#ifndef WIN32
			TEST_THAT(::symlink("does-not-exist", 
				"testfiles/TestDir1/x1/dir-to-file") == 0);
		#endif

		wait_for_backup_operation("bbackupd to sync the changes");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3f.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// rename an untracked file over an 
		// existing untracked file
		printf("\n==== Rename over existing untracked file\n");
		int fd1 = open("testfiles/TestDir1/untracked-1", 
			O_CREAT | O_EXCL | O_WRONLY, 0700);
		int fd2 = open("testfiles/TestDir1/untracked-2",
			O_CREAT | O_EXCL | O_WRONLY, 0700);
		TEST_THAT(fd1 > 0);
		TEST_THAT(fd2 > 0);
		TEST_THAT(write(fd1, "hello", 5) == 5);
		TEST_THAT(close(fd1) == 0);
		safe_sleep(1);
		TEST_THAT(write(fd2, "world", 5) == 5);
		TEST_THAT(close(fd2) == 0);
		TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-1"));
		TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-2"));

		// back up both files
		wait_for_operation(5, "untracked files to be old enough");
		wait_for_backup_operation("bbackupd to sync the "
			"untracked files");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3g.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		#ifdef WIN32
			TEST_THAT(::unlink("testfiles/TestDir1/untracked-2")
				== 0);
		#endif

		TEST_THAT(::rename("testfiles/TestDir1/untracked-1", 
			"testfiles/TestDir1/untracked-2") == 0);
		TEST_THAT(!TestFileExists("testfiles/TestDir1/untracked-1"));
		TEST_THAT( TestFileExists("testfiles/TestDir1/untracked-2"));

		wait_for_backup_operation("bbackupd to sync the untracked "
			"files again");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3g.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// case which went wrong: rename a tracked file over an
		// existing tracked file
		printf("\n==== Rename over existing tracked file\n");
		fd1 = open("testfiles/TestDir1/tracked-1", 
			O_CREAT | O_EXCL | O_WRONLY, 0700);
		fd2 = open("testfiles/TestDir1/tracked-2",
			O_CREAT | O_EXCL | O_WRONLY, 0700);
		TEST_THAT(fd1 > 0);
		TEST_THAT(fd2 > 0);
		char buffer[1024];
		TEST_THAT(write(fd1, "hello", 5) == 5);
		TEST_THAT(write(fd1, buffer, sizeof(buffer)) == sizeof(buffer));
		TEST_THAT(close(fd1) == 0);
		safe_sleep(1);
		TEST_THAT(write(fd2, "world", 5) == 5);
		TEST_THAT(write(fd2, buffer, sizeof(buffer)) == sizeof(buffer));
		TEST_THAT(close(fd2) == 0);
		TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-1"));
		TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-2"));

		// wait for them to be old enough to back up
		wait_for_operation(5, "tracked files to be old enough");
		
		// back up both files
		sync_and_wait();

		// compare to make sure that it worked
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3h.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		#ifdef WIN32
			TEST_THAT(::unlink("testfiles/TestDir1/tracked-2")
				== 0);
		#endif

		TEST_THAT(::rename("testfiles/TestDir1/tracked-1", 
			"testfiles/TestDir1/tracked-2") == 0);
		TEST_THAT(!TestFileExists("testfiles/TestDir1/tracked-1"));
		TEST_THAT( TestFileExists("testfiles/TestDir1/tracked-2"));

		wait_for_backup_operation("bbackupd to sync the tracked "
			"files again");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3i.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
	
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// case which went wrong: rename a tracked file 
		// over a deleted file
		printf("\n==== Rename an existing file over a deleted file\n");
		TEST_THAT(!TestFileExists("testfiles/TestDir1/x1/dsfdsfs98.fd"));
		TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", 
			"testfiles/TestDir1/x1/dsfdsfs98.fd") == 0);
		
		wait_for_backup_operation("bbackupd to sync");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3j.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		// Check that no read error has been reported yet
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Add files with old times, update "
			"attributes of one to latest time\n");

		// Move that file back
		TEST_THAT(::rename("testfiles/TestDir1/x1/dsfdsfs98.fd", 
			"testfiles/TestDir1/df9834.dsf") == 0);
		
		// Add some more files
		// Because the 'm' option is not used, these files will 
		// look very old to the daemon.
		// Lucky it'll upload them then!
		#ifdef WIN32
			TEST_THAT(::system("tar xzvf testfiles/test2.tgz "
				"-C testfiles") == 0);
		#else
			TEST_THAT(::system("gzip -d < testfiles/test2.tgz "
				"| ( cd  testfiles && tar xf - )") == 0);
			::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415);
		#endif
		
		// Wait and test
		wait_for_backup_operation("bbackupd to sync old files");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3k.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Check that no read error has been reported yet
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

		// Check that modifying files with old timestamps
		// still get added
		printf("\n==== Modify existing file, but change timestamp "
			"to rather old\n");
		wait_for_sync_end();

		// Then modify an existing file
		{
			// in the archive, it's read only
			#ifdef WIN32
				TEST_THAT(::system("chmod 0777 testfiles"
					"/TestDir1/sub23/rand.h") == 0);
			#else
				TEST_THAT(chmod("testfiles/TestDir1/sub23"
					"/rand.h", 0777) == 0);
			#endif

			FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", 
				"w+");

			if (f == 0)
			{
				perror("Failed to open");
			}

			TEST_THAT(f != 0);

			if (f != 0)
			{
				fprintf(f, "MODIFIED!\n");
				fclose(f);
			}

			// and then move the time backwards!
			struct timeval times[2];
			BoxTimeToTimeval(SecondsToBoxTime(
				(time_t)(365*24*60*60)), times[1]);
			times[0] = times[1];
			TEST_THAT(::utimes("testfiles/TestDir1/sub23/rand.h", 
				times) == 0);
		}

		// Wait and test
		wait_for_sync_end(); // files too new
		wait_for_sync_end(); // should (not) be backed up this time

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3l.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Check that no read error has been reported yet
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

		// Add some files and directories which are marked as excluded
		printf("\n==== Add files and dirs for exclusion test\n");
		#ifdef WIN32
			TEST_THAT(::system("tar xzvf testfiles/testexclude.tgz "
				"-C testfiles") == 0);
		#else
			TEST_THAT(::system("gzip -d < "
				"testfiles/testexclude.tgz "
				"| ( cd testfiles && tar xf - )") == 0);
		#endif

		// Wait and test
		wait_for_sync_end();
		wait_for_sync_end();
		
		// compare with exclusions, should not find differences
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3m.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// compare without exclusions, should find differences
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3n.log "
			"-Werror \"compare -acEQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// check that the excluded files did not make it
		// into the store, and the included files did
		printf("\n==== Check that exclude/alwaysinclude commands "
			"actually work\n");

		{
			std::auto_ptr<BackupProtocolClient> client = 
				ConnectAndLogin(context,
				BackupProtocolClientLogin::Flags_ReadOnly);
			
			std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory(
				*client,
				BackupProtocolClientListDirectory::RootDirectory);

			int64_t testDirId = SearchDir(*dir, "Test1");
			TEST_THAT(testDirId != 0);
			dir = ReadDirectory(*client, testDirId);
				
			TEST_THAT(!SearchDir(*dir, "excluded_1"));
			TEST_THAT(!SearchDir(*dir, "excluded_2"));
			TEST_THAT(!SearchDir(*dir, "exclude_dir"));
			TEST_THAT(!SearchDir(*dir, "exclude_dir_2"));
			// xx_not_this_dir_22 should not be excluded by
			// ExcludeDirsRegex, because it's a file
			TEST_THAT(SearchDir (*dir, "xx_not_this_dir_22"));
			TEST_THAT(!SearchDir(*dir, "zEXCLUDEu"));
			TEST_THAT(SearchDir (*dir, "dont.excludethis"));
			TEST_THAT(SearchDir (*dir, "xx_not_this_dir_ALWAYSINCLUDE"));

			int64_t sub23id = SearchDir(*dir, "sub23");
			TEST_THAT(sub23id != 0);
			dir = ReadDirectory(*client, sub23id);

			TEST_THAT(!SearchDir(*dir, "xx_not_this_dir_22"));
			TEST_THAT(!SearchDir(*dir, "somefile.excludethis"));
			client->QueryFinished();
			sSocket.Close();
		}

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

#ifndef WIN32
		// These tests only work as non-root users.
		if(::getuid() != 0)
		{
			// Check that the error has not been reported yet
			TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));

			// Check that read errors are reported neatly
			printf("\n==== Add unreadable files\n");
			
			{
				// Dir and file which can't be read
				TEST_THAT(::mkdir("testfiles/TestDir1/sub23"
					"/read-fail-test-dir", 0000) == 0);
				int fd = ::open("testfiles/TestDir1"
					"/read-fail-test-file", 
					O_CREAT | O_WRONLY, 0000);
				TEST_THAT(fd != -1);
				::close(fd);
			}

			// Wait and test...
			wait_for_backup_operation("bbackupd to try to sync "
				"unreadable file");
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query3o.log "
				"-Werror \"compare -acQ\" quit");

			// should find differences
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Error);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			// Check that it was reported correctly
			TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1"));

			// Check that the error was only reported once
			TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));

			// Set permissions on file and dir to stop 
			// errors in the future
			TEST_THAT(::chmod("testfiles/TestDir1/sub23"
				"/read-fail-test-dir", 0770) == 0);
			TEST_THAT(::chmod("testfiles/TestDir1"
				"/read-fail-test-file", 0770) == 0);
		}
#endif

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Continuously update file, "
			"check isn't uploaded\n");
		
		// Make sure everything happens at the same point in the 
		// sync cycle: wait until exactly the start of a sync
		wait_for_sync_start();

		// Then wait a second, to make sure the scan is complete
		::safe_sleep(1);

		{
			// Open a file, then save something to it every second
			for(int l = 0; l < 12; ++l)
			{
				FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+");
				TEST_THAT(f != 0);
				fprintf(f, "Loop iteration %d\n", l);
				fflush(f);
				fclose(f);

				printf(".");
				fflush(stdout);
				safe_sleep(1);
			}
			printf("\n");
			fflush(stdout);
			
			// Check there's a difference
			compareReturnValue = ::system("perl testfiles/"
				"extcheck1.pl");

			TEST_RETURN(compareReturnValue, 1);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			printf("\n==== Keep on continuously updating file, "
				"check it is uploaded eventually\n");

			for(int l = 0; l < 28; ++l)
			{
				FILE *f = ::fopen("testfiles/TestDir1/"
					"continousupdate", "w+");
				TEST_THAT(f != 0);
				fprintf(f, "Loop 2 iteration %d\n", l);
				fflush(f);
				fclose(f);

				printf(".");
				fflush(stdout);
				safe_sleep(1);
			}
			printf("\n");
			fflush(stdout);

			compareReturnValue = ::system("perl testfiles/"
				"extcheck2.pl");

			TEST_RETURN(compareReturnValue, 1);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		}
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Delete directory, change attributes\n");
	
		// Delete a directory
		TEST_THAT(::system("rm -rf testfiles/TestDir1/x1") == 0);
		// Change attributes on an original file.
		::chmod("testfiles/TestDir1/df9834.dsf", 0423);
		
		// Wait and test
		wait_for_backup_operation("bbackupd to sync deletion "
			"of directory");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query4.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
	
		printf("\n==== Restore files and directories\n");
		int64_t deldirid = 0;
		int64_t restoredirid = 0;
		{
			// connect and log in
			std::auto_ptr<BackupProtocolClient> client = 
				ConnectAndLogin(context,
				BackupProtocolClientLogin::Flags_ReadOnly);

			// Find the ID of the Test1 directory
			restoredirid = GetDirID(*client, "Test1", 
				BackupProtocolClientListDirectory::RootDirectory);
			TEST_THAT(restoredirid != 0);

			// Test the restoration
			TEST_THAT(BackupClientRestore(*client, restoredirid, 
				"Test1", "testfiles/restore-Test1", 
				true /* print progress dots */) 
				== Restore_Complete);

			// On Win32 we can't open another connection
			// to the server, so we'll compare later.

			// Make sure you can't restore a restored directory
			TEST_THAT(BackupClientRestore(*client, restoredirid, 
				"Test1", "testfiles/restore-Test1", 
				true /* print progress dots */) 
				== Restore_TargetExists);
			
			// Find ID of the deleted directory
			deldirid = GetDirID(*client, "x1", restoredirid);
			TEST_THAT(deldirid != 0);

			// Just check it doesn't bomb out -- will check this 
			// properly later (when bbackupd is stopped)
			TEST_THAT(BackupClientRestore(*client, deldirid, 
				"Test1", "testfiles/restore-Test1-x1", 
				true /* print progress dots */, 
				true /* deleted files */) 
				== Restore_Complete);

			// Make sure you can't restore to a nonexistant path
			printf("\n==== Try to restore to a path "
				"that doesn't exist\n");
			fflush(stdout);

			{
				Logging::Guard guard(Log::FATAL);
				TEST_THAT(BackupClientRestore(*client,
					restoredirid, "Test1",
					"testfiles/no-such-path/subdir", 
					true /* print progress dots */) 
					== Restore_TargetPathNotFound);
			}

			// Log out
			client->QueryFinished();
			sSocket.Close();
		}

		// Compare the restored files
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query10.log "
			"-Wwarning "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

#ifdef WIN32
		// make one of the files read-only, expect a compare failure
		compareReturnValue = ::system("attrib +r "
			"testfiles\\restore-Test1\\f1.dat");
		TEST_RETURN(compareReturnValue, 0);

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query10a.log "
			"-Werror "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
	
		// set it back, expect no failures
		compareReturnValue = ::system("attrib -r "
			"testfiles\\restore-Test1\\f1.dat");
		TEST_RETURN(compareReturnValue, 0);

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf -l testfiles/query10a.log "
			"-Wwarning "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// change the timestamp on a file, expect a compare failure
		char* testfile = "testfiles\\restore-Test1\\f1.dat";
		HANDLE handle = openfile(testfile, O_RDWR, 0);
		TEST_THAT(handle != INVALID_HANDLE_VALUE);
		
		FILETIME creationTime, lastModTime, lastAccessTime;
		TEST_THAT(GetFileTime(handle, &creationTime, &lastAccessTime, 
			&lastModTime) != 0);
		TEST_THAT(CloseHandle(handle));

		FILETIME dummyTime = lastModTime;
		dummyTime.dwHighDateTime -= 100;

		// creation time is backed up, so changing it should cause
		// a compare failure
		TEST_THAT(set_file_time(testfile, dummyTime, lastModTime,
			lastAccessTime));
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query10a.log "
			"-Werror "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// last access time is not backed up, so it cannot be compared
		TEST_THAT(set_file_time(testfile, creationTime, lastModTime,
			dummyTime));
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query10a.log "
			"-Wwarning "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// last write time is backed up, so changing it should cause
		// a compare failure
		TEST_THAT(set_file_time(testfile, creationTime, dummyTime,
			lastAccessTime));
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query10a.log "
			"-Werror "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// set back to original values, check that compare succeeds
		TEST_THAT(set_file_time(testfile, creationTime, lastModTime,
			lastAccessTime));
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query10a.log "
			"-Wwarning "
			"\"compare -cEQ Test1 testfiles/restore-Test1\" "
			"quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
#endif // WIN32

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Add files with current time\n");
	
		// Add some more files and modify others
		// Use the m flag this time so they have a recent modification time
		#ifdef WIN32
			TEST_THAT(::system("tar xzvmf testfiles/test3.tgz "
				"-C testfiles") == 0);
		#else
			TEST_THAT(::system("gzip -d < testfiles/test3.tgz "
				"| ( cd testfiles && tar xmf - )") == 0);
		#endif
		
		// Wait and test
		wait_for_backup_operation("bbackupd to sync new files");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query5.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Rename directory
		printf("\n==== Rename directory\n");
		TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", 
			"testfiles/TestDir1/renamed-dir") == 0);

		wait_for_backup_operation("bbackupd to sync renamed directory");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query6.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// and again, but with quick flag
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query6q.log "
			"-Wwarning \"compare -acqQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		// Rename some files -- one under the threshold, others above
		printf("\n==== Rename files\n");
		TEST_THAT(rename("testfiles/TestDir1/continousupdate", 
			"testfiles/TestDir1/continousupdate-ren") == 0);
		TEST_THAT(rename("testfiles/TestDir1/df324", 
			"testfiles/TestDir1/df324-ren") == 0);
		TEST_THAT(rename("testfiles/TestDir1/sub23/find2perl", 
			"testfiles/TestDir1/find2perl-ren") == 0);

		wait_for_backup_operation("bbackupd to sync renamed files");

		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query6.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// Check that modifying files with madly in the future 
		// timestamps still get added
		printf("\n==== Create a file with timestamp way ahead "
			"in the future\n");

		// Time critical, so sync
		wait_for_sync_start();

		// Then wait a second, to make sure the scan is complete
		::safe_sleep(1);

		// Then modify an existing file
		{
			FILE *f = fopen("testfiles/TestDir1/sub23/"
				"in-the-future", "w");
			TEST_THAT(f != 0);
			fprintf(f, "Back to the future!\n");
			fclose(f);
			// and then move the time forwards!
			struct timeval times[2];
			BoxTimeToTimeval(GetCurrentBoxTime() + 
				SecondsToBoxTime((time_t)(365*24*60*60)), 
				times[1]);
			times[0] = times[1];
			TEST_THAT(::utimes("testfiles/TestDir1/sub23/"
				"in-the-future", times) == 0);
		}

		// Wait and test
		wait_for_backup_operation("bbackup to sync future file");
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query3e.log "
			"-Wwarning \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Same);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Change client store marker\n");

		// Then... connect to the server, and change the 
		// client store marker. See what that does!
		{
			bool done = false;
			int tries = 4;
			while(!done && tries > 0)
			{
				try
				{
					std::auto_ptr<BackupProtocolClient>
						protocol = Connect(context);
					// Make sure the marker isn't zero,
					// because that's the default, and
					// it should have changed
					std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol->QueryLogin(0x01234567, 0));
					TEST_THAT(loginConf->GetClientStoreMarker() != 0);
					
					// Change it to something else
					protocol->QuerySetClientStoreMarker(12);
					
					// Success!
					done = true;
					
					// Log out
					protocol->QueryFinished();
					sSocket.Close();
				}
				catch(...)
				{
					tries--;
				}
			}
			TEST_THAT(done);
		}
		
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Check change of store marker pauses daemon\n");
		
		// Make a change to a file, to detect whether or not 
		// it's hanging around waiting to retry.
		{
			FILE *f = ::fopen("testfiles/TestDir1/fileaftermarker", "w");
			TEST_THAT(f != 0);
			::fprintf(f, "Lovely file you got there.");
			::fclose(f);
		}

		// Wait a little bit longer than usual
		wait_for_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION * 
			3) / 2, "bbackupd to detect changed store marker");

		// Test that there *are* differences
		compareReturnValue = ::system(BBACKUPQUERY " "
			"-c testfiles/bbackupd.conf "
			"-l testfiles/query6.log "
			"-Werror \"compare -acQ\" quit");
		TEST_RETURN(compareReturnValue,
			BackupQueries::ReturnCode::Compare_Different);
		TestRemoteProcessMemLeaks("bbackupquery.memleaks");
	
		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		// 100 seconds - (12*3/2)
		wait_for_operation(82, "bbackupd to recover");

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

#ifndef WIN32
		printf("\n==== Interrupted restore\n");
		{
			do_interrupted_restore(context, restoredirid);
			int64_t resumesize = 0;
			TEST_THAT(FileExists("testfiles/"
				"restore-interrupt.boxbackupresume", 
				&resumesize));
			// make sure it has recorded something to resume
			TEST_THAT(resumesize > 16);	

			printf("\n==== Resume restore\n");

			std::auto_ptr<BackupProtocolClient> client = 
				ConnectAndLogin(context,
				BackupProtocolClientLogin::Flags_ReadOnly);

			// Check that the restore fn returns resume possible,
			// rather than doing anything
			TEST_THAT(BackupClientRestore(*client, restoredirid, 
				"Test1", "testfiles/restore-interrupt", 
				true /* print progress dots */) 
				== Restore_ResumePossible);

			// Then resume it
			TEST_THAT(BackupClientRestore(*client, restoredirid, 
				"Test1", "testfiles/restore-interrupt", 
				true /* print progress dots */, 
				false /* deleted files */, 
				false /* undelete server */, 
				true /* resume */) 
				== Restore_Complete);

			client->QueryFinished();
			sSocket.Close();

			// Then check it has restored the correct stuff
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query14.log "
				"-Wwarning \"compare -cEQ Test1 "
				"testfiles/restore-interrupt\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		}
#endif // !WIN32

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		printf("\n==== Check restore deleted files\n");

		{
			std::auto_ptr<BackupProtocolClient> client = 
				ConnectAndLogin(context, 0 /* read-write */);

			// Do restore and undelete
			TEST_THAT(BackupClientRestore(*client, deldirid, 
				"Test1", "testfiles/restore-Test1-x1-2", 
				true /* print progress dots */, 
				true /* deleted files */, 
				true /* undelete on server */) 
				== Restore_Complete);

			client->QueryFinished();
			sSocket.Close();

			// Do a compare with the now undeleted files
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query11.log "
				"-Wwarning "
				"\"compare -cEQ Test1/x1 "
				"testfiles/restore-Test1-x1-2\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		}
		
		// Final check on notifications
		TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2"));
		TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

#ifdef WIN32
		printf("\n==== Testing locked file behaviour:\n");

		// Test that locked files cannot be backed up,
		// and the appropriate error is reported.
		// Wait for the sync to finish, so that we have time to work
		wait_for_sync_end();
		// Now we have about three seconds to work

		handle = openfile("testfiles/TestDir1/lockedfile",
			O_CREAT | O_EXCL | O_LOCK, 0);
		TEST_THAT(handle != INVALID_HANDLE_VALUE);

		if (handle != 0)
		{
			// first sync will ignore the file, it's too new
			wait_for_sync_end();
			TEST_THAT(!TestFileExists("testfiles/"
				"notifyran.read-error.1"));
		}

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if (handle != 0)
		{
			// this sync should try to back up the file, 
			// and fail, because it's locked
			wait_for_sync_end();
			TEST_THAT(TestFileExists("testfiles/"
				"notifyran.read-error.1"));
			TEST_THAT(!TestFileExists("testfiles/"
				"notifyran.read-error.2"));
		}

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if (handle != 0)
		{
			// now close the file and check that it is
			// backed up on the next run.
			CloseHandle(handle);
			wait_for_sync_end();

			// still no read errors?
			TEST_THAT(!TestFileExists("testfiles/"
				"notifyran.read-error.2"));
		}

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if (handle != 0)
		{
			// compare, and check that it works
			// reports the correct error message (and finishes)
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query15a.log "
				"-Wwarning \"compare -acQ\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		}

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if (handle != 0)
		{
			// open the file again, compare and check that compare
			// reports the correct error message (and finishes)
			handle = openfile("testfiles/TestDir1/lockedfile",
				O_LOCK, 0);
			TEST_THAT(handle != INVALID_HANDLE_VALUE);

			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query15.log "
				"-Werror \"compare -acQ\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Error);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			// close the file again, check that compare
			// works again
			CloseHandle(handle);
		}

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if (handle != 0)
		{
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query15a.log "
				"-Wwarning \"compare -acQ\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");
		}
#endif

		// Kill the daemon
		terminate_bbackupd(bbackupd_pid);
		
		// Start it again
		cmd = BBACKUPD " " + bbackupd_args +
			" testfiles/bbackupd.conf";
		bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");

		TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);

		TEST_THAT(ServerIsAlive(bbackupd_pid));
		TEST_THAT(ServerIsAlive(bbstored_pid));
		if (!ServerIsAlive(bbackupd_pid)) return 1;
		if (!ServerIsAlive(bbstored_pid)) return 1;

		if(bbackupd_pid != -1 && bbackupd_pid != 0)
		{
			// Wait and compare (a little bit longer than usual)
			wait_for_operation(
				(TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2,
				"bbackupd to sync everything"); 
			compareReturnValue = ::system(BBACKUPQUERY " "
				"-c testfiles/bbackupd.conf "
				"-l testfiles/query4a.log "
				"-Wwarning \"compare -acQ\" quit");
			TEST_RETURN(compareReturnValue,
				BackupQueries::ReturnCode::Compare_Same);
			TestRemoteProcessMemLeaks("bbackupquery.memleaks");

			// Kill it again
			terminate_bbackupd(bbackupd_pid);
		}
	}

	/*
	// List the files on the server - why?
	::system(BBACKUPQUERY " -q -c testfiles/bbackupd.conf "
		"-l testfiles/queryLIST.log \"list -rotdh\" quit");
	TestRemoteProcessMemLeaks("bbackupquery.memleaks");
	*/

	#ifndef WIN32	
		if(::getuid() == 0)
		{
			::printf("WARNING: This test was run as root. "
				"Some tests have been omitted.\n");
		}
	#endif
	
	return 0;
}

int test(int argc, const char *argv[])
{
	// SSL library
	SSLLib::Initialise();

	// Keys for subsystems
	BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys");

	// Initial files
	#ifdef WIN32
		TEST_THAT(::system("tar xzvf testfiles/test_base.tgz "
			"-C testfiles") == 0);
	#else
		TEST_THAT(::system("gzip -d < testfiles/test_base.tgz "
			"| ( cd testfiles && tar xf - )") == 0);
	#endif

	// Do the tests

	int r = test_basics();
	if(r != 0) return r;
	
	r = test_setupaccount();
	if(r != 0) return r;

	r = test_run_bbstored();
	TEST_THAT(r == 0);
	if(r != 0) return r;
	
	r = test_bbackupd();
	if(r != 0)
	{
		if (bbackupd_pid)
		{
			KillServer(bbackupd_pid);
		}
		if (bbstored_pid)
		{
			KillServer(bbstored_pid);
		}
		return r;
	}
	
	test_kill_bbstored();

	return 0;
}
