diff options
Diffstat (limited to 'test/bbackupd/testbbackupd.cpp')
-rw-r--r-- | test/bbackupd/testbbackupd.cpp | 1096 |
1 files changed, 1096 insertions, 0 deletions
diff --git a/test/bbackupd/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp new file mode 100644 index 00000000..a085a96c --- /dev/null +++ b/test/bbackupd/testbbackupd.cpp @@ -0,0 +1,1096 @@ +// distribution boxbackup-0.10 (svn version: 494) +// +// Copyright (c) 2003 - 2006 +// Ben Summers and contributors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. All use of this software and associated advertising materials must +// display the following acknowledgment: +// This product includes software developed by Ben Summers. +// 4. The names of the Authors may not be used to endorse or promote +// products derived from this software without specific prior written +// permission. +// +// [Where legally impermissible the Authors do not disclaim liability for +// direct physical injury or death caused solely by defects in the software +// unless it is modified by a third party.] +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// +// +// -------------------------------------------------------------------------- +// +// File +// Name: testbbackupd.cpp +// Purpose: test backup daemon (and associated client bits) +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <limits.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> +#ifdef HAVE_SYS_XATTR_H +#include <cerrno> +#include <sys/xattr.h> +#endif +#include <map> + +#include "Test.h" +#include "BackupClientFileAttributes.h" +#include "CommonException.h" +#include "BackupStoreException.h" +#include "FileModificationTime.h" +#include "autogen_BackupProtocolClient.h" +#include "SSLLib.h" +#include "TLSContext.h" +#include "SocketStreamTLS.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreConstants.h" +#include "Socket.h" +#include "BackupClientRestore.h" +#include "BackupStoreDirectory.h" +#include "BackupClientCryptoKeys.h" +#include "CollectInBufferStream.h" +#include "Utils.h" +#include "BoxTime.h" +#include "BoxTimeToUnix.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(int seconds = TIME_TO_WAIT_FOR_BACKUP_OPERATION) +{ + printf("waiting: "); + fflush(stdout); + for(int l = 0; l < seconds; ++l) + { + sleep(1); + printf("."); + fflush(stdout); + } + printf("\n"); +} + +int bbstored_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) +{ + struct stat s1, s2; + TEST_THAT(::lstat(f1, &s1) == 0); + TEST_THAT(::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) + { + 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; + } + + // 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()); + 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); + + BackupClientFileAttributes t3; + 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"); + t2.WriteAttributes("testfiles/test2_n"); + TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"), CommonException, OSFileError); + TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"), BackupStoreException, AttributesNotLoaded); + + // Test that atttributes are vaguely similar + TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_n")); + TEST_THAT(attrmatch("testfiles/test2", "testfiles/test2_n")); + + // 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("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf create 01234567 0 1000B 2000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + return 0; +} + +int test_run_bbstored() +{ + bbstored_pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid"); + TEST_THAT(bbstored_pid != -1 && bbstored_pid != 0); + if(bbstored_pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(bbstored_pid)); + return 0; // success + } + + return 1; +} + +int test_kill_bbstored() +{ + TEST_THAT(KillServer(bbstored_pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(bbstored_pid)); + TestRemoteProcessMemLeaks("bbstored.memleaks"); + 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(); +} + +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", BOX_PORT_BBSTORED); + 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, "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); + } + } +} + + +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"); + + // unpack the files for the initial test + TEST_THAT(::system("rm -rf testfiles/TestDir1") == 0); + TEST_THAT(::system("mkdir testfiles/TestDir1") == 0); + TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz | ( cd testfiles/TestDir1 && tar xf - )") == 0); + + int pid = LaunchServer("../../bin/bbackupd/bbackupd testfiles/bbackupd.conf", "testfiles/bbackupd.pid"); + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + + // First, check storage space handling -- wait for file to be uploaded + wait_for_backup_operation(); + //TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf info 01234567") == 0); + // Set limit to something very small + // About 28 blocks will be used at this point. bbackupd will only pause if the size used is + // greater than soft limit + 1/3 of (hard - soft). Set small values for limits accordingly. + TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 40B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Unpack some more files + TEST_THAT(::system("gzip -d < testfiles/spacetest2.tgz | ( cd testfiles/TestDir1 && tar xf - )") == 0); + // Delete a file and a directory + TEST_THAT(::unlink("testfiles/TestDir1/spacetest/d1/f3") == 0); + TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d3/d4") == 0); + wait_for_backup_operation(); + + // Make sure there are some differences + int compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query0a.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 2*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Put the limit back + TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 1000B 2000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // 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")); + + // unpack the initial files again + TEST_THAT(::system("gzip -d < testfiles/test_base.tgz | ( cd testfiles && tar xf - )") == 0); + + // wait for it to do it's stuff + wait_for_backup_operation(); + + // Check that the contents of the store are the same as the contents + // of the disc (-a = all, -c = give result in return code) + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query1.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("Delete file and update another, create symlink.\n"); + + // Delete a file + TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + // New symlink + TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/symlink-to-dir") == 0); + + // Update a file (will be uploaded as a diff) + { + // Check that the file is over the diffing threshold in the bbstored.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 for backup daemon to do it's stuff, and compare again + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query2.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + // Try a quick compare, just for fun + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query2q.log \"compare -acq\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Bad case: delete a file/symlink, replace it with a directory + printf("Replace symlink with directory, add new directory\n"); + TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir") == 0); + 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 + TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file/contents") == 0); + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // And the inverse, replace a directory with a file/symlink + printf("Replace directory with symlink\n"); + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file/contents") == 0); + TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); + TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file") == 0); + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // And then, put it back to how it was before. + printf("Replace symlink with directory (which was a symlink)\n"); + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file") == 0); + TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) == 0); + TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file/contents2") == 0); + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // 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("Put it all back to how it was\n"); + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file/contents2") == 0); + TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); + TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file") == 0); + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // case which went wrong: rename a tracked file over a deleted file + printf("Rename an existing file over a deleted file\n"); + TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", "testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("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! + TEST_THAT(::system("gzip -d < testfiles/test2.tgz | ( cd testfiles && tar xf - )") == 0); + ::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415); + + // Wait and test + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Check that modifying files with old timestamps still get added + printf("Modify existing file, but change timestamp to rather old\n"); + // Time critical, so sync + TEST_THAT(::system("../../bin/bbackupctl/bbackupctl -q -c testfiles/bbackupd.conf wait-for-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + // Then wait a second, to make sure the scan is complete + ::sleep(1); + // Then modify an existing file + { + chmod("testfiles/TestDir1/sub23/rand.h", 0777); // in the archive, it's read only + FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", "w+"); + TEST_THAT(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_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3e.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Add some files and directories which are marked as excluded + printf("Add files and dirs for exclusion test\n"); + TEST_THAT(::system("gzip -d < testfiles/testexclude.tgz | ( cd testfiles && tar xf - )") == 0); + // Wait and test + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3c.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3d.log \"compare -acE\" quit"); + TEST_THAT(compareReturnValue == 2*256); // should find differences + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // These tests only work as non-root users. + if(::getuid() != 0) + { + // Check that read errors are reported neatly + printf("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(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3e.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 2*256); // should find differences + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + // Check that it was reported correctly + TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1")); + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + // Set permissions on file and dir to stop errors in the future + ::chmod("testfiles/TestDir1/sub23/read-fail-test-dir", 0770); + ::chmod("testfiles/TestDir1/read-fail-test-file", 0770); + } + + printf("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 + TEST_THAT(::system("../../bin/bbackupctl/bbackupctl -c testfiles/bbackupd.conf wait-for-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + // Then wait a second, to make sure the scan is complete + ::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); + sleep(1); + printf("."); + fflush(stdout); + ::fclose(f); + } + printf("\n"); + + // Check there's a difference + compareReturnValue = ::system("testfiles/extcheck1.pl"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("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); + sleep(1); + printf("."); + fflush(stdout); + ::fclose(f); + } + printf("\n"); + + compareReturnValue = ::system("testfiles/extcheck2.pl"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + + printf("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(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query4.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("Restore files and directories\n"); + int64_t deldirid = 0; + int64_t restoredirid = 0; + { + // connect and log in + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED); + BackupProtocolClient protocol(conn); + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); + std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); + + // Find the ID of the Test1 directory + restoredirid = GetDirID(protocol, "Test1", BackupProtocolClientListDirectory::RootDirectory); + TEST_THAT(restoredirid != 0); + + // Test the restoration + TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-Test1", true /* print progress dots */) == Restore_Complete); + + // Compare it + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query10.log \"compare -cE Test1 testfiles/restore-Test1\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Make sure you can't restore a restored directory + TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-Test1", true /* print progress dots */) == Restore_TargetExists); + + // Find ID of the deleted directory + deldirid = GetDirID(protocol, "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(protocol, deldirid, "testfiles/restore-Test1-x1", true /* print progress dots */, true /* deleted files */) == Restore_Complete); + + // Log out + protocol.QueryFinished(); + } + + printf("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 + TEST_THAT(::system("gzip -d < testfiles/test3.tgz | ( cd testfiles && tar xmf - )") == 0); + + // Wait and test + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query5.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Rename directory + printf("Rename directory\n"); + TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", "testfiles/TestDir1/renamed-dir") == 0); + wait_for_backup_operation(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + // and again, but with quick flag + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6q.log \"compare -acq\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Rename some files -- one under the threshold, others above + printf("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(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Check that modifying files with madly in the future timestamps still get added + printf("Create a file with timestamp to way ahead in the future\n"); + // Time critical, so sync + TEST_THAT(::system("../../bin/bbackupctl/bbackupctl -q -c testfiles/bbackupd.conf wait-for-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + // Then wait a second, to make sure the scan is complete + ::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(); + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3e.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("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 + { + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED); + BackupProtocolClient protocol(conn); + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); + std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)); // read-write + // Make sure the marker isn't zero, because that's the default, and it should have changed + TEST_THAT(loginConf->GetClientStoreMarker() != 0); + + // Change it to something else + protocol.QuerySetClientStoreMarker(12); + + // Success! + done = true; + + // Log out + protocol.QueryFinished(); + } + catch(...) + { + tries--; + } + } + TEST_THAT(done); + } + + printf("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 and test that there *are* differences + wait_for_backup_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2); // little bit longer than usual + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 2*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("Interrupted restore\n"); + { + do_interrupted_restore(context, restoredirid); + int64_t resumesize = 0; + TEST_THAT(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize)); + TEST_THAT(resumesize > 16); // make sure it has recorded something to resume + + printf("\nResume restore\n"); + + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED); + BackupProtocolClient protocol(conn); + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); + std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)); // read-write + + // Check that the restore fn returns resume possible, rather than doing anything + TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-interrupt", true /* print progress dots */) == Restore_ResumePossible); + + // Then resume it + TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-interrupt", true /* print progress dots */, false /* deleted files */, false /* undelete server */, true /* resume */) == Restore_Complete); + + protocol.QueryFinished(); + + // Then check it has restored the correct stuff + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query14.log \"compare -cE Test1 testfiles/restore-interrupt\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + + printf("Check restore deleted files\n"); + { + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED); + BackupProtocolClient protocol(conn); + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); + std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)); // read-write + + // Do restore and undelete + TEST_THAT(BackupClientRestore(protocol, deldirid, "testfiles/restore-Test1-x1-2", true /* print progress dots */, true /* deleted files */, true /* undelete on server */) == Restore_Complete); + + protocol.QueryFinished(); + + // Do a compare with the now undeleted files + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query11.log \"compare -cE Test1/x1 testfiles/restore-Test1-x1-2\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + + // Final check on notifications + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + + // Kill the daemon + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + TestRemoteProcessMemLeaks("bbackupd.memleaks"); + + // Start it again + pid = LaunchServer("../../bin/bbackupd/bbackupd testfiles/bbackupd.conf", "testfiles/bbackupd.pid"); + TEST_THAT(pid != -1 && pid != 0); + if(pid != -1 && pid != 0) + { + // Wait and comapre + wait_for_backup_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2); // little bit longer than usual + compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query4.log \"compare -ac\" quit"); + TEST_THAT(compareReturnValue == 1*256); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Kill it again + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + TestRemoteProcessMemLeaks("bbackupd.memleaks"); + } + } + + // List the files on the server + ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/queryLIST.log \"list -rotdh\" quit"); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + if(::getuid() == 0) + { + ::printf("WARNING: This test was run as root. Some tests have been omitted.\n"); + } + + return 0; +} + +int test(int argc, const char *argv[]) +{ + // SSL library + SSLLib::Initialise(); + + // Keys for subsystems + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // Initial files + TEST_THAT(::system("gzip -d < testfiles/test_base.tgz | ( cd testfiles && tar xf - )") == 0); + + // Do the tests + + int r = test_basics(); + if(r != 0) return r; + + r = test_setupaccount(); + if(r != 0) return r; + + r = test_run_bbstored(); + if(r != 0) return r; + + r = test_bbackupd(); + if(r != 0) return r; + + test_kill_bbstored(); + + return 0; +} + |