summaryrefslogtreecommitdiff
path: root/test/bbackupd/testbbackupd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/bbackupd/testbbackupd.cpp')
-rw-r--r--test/bbackupd/testbbackupd.cpp4077
1 files changed, 4077 insertions, 0 deletions
diff --git a/test/bbackupd/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp
new file mode 100644
index 00000000..77c463ba
--- /dev/null
+++ b/test/bbackupd/testbbackupd.cpp
@@ -0,0 +1,4077 @@
+// --------------------------------------------------------------------------
+//
+// 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;
+ }
+ 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;
+}