summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorReinhard Tartler <siretart@tauware.de>2011-10-27 19:53:48 -0400
committerReinhard Tartler <siretart@tauware.de>2011-10-27 19:53:48 -0400
commit757294d769a7defe6a531a09fba9674cc6b388f7 (patch)
treeca47acead4e3676acd942de5f443d7bdc5fdd90c /test
Import boxbackup_0.11.1~r2837.orig.tar.gz
[dgit import orig boxbackup_0.11.1~r2837.orig.tar.gz]
Diffstat (limited to 'test')
-rw-r--r--test/backupdiff/difftestfiles.cpp295
-rw-r--r--test/backupdiff/testbackupdiff.cpp605
-rw-r--r--test/backupdiff/testextra2
-rw-r--r--test/backupstore/Makefile.extra1
-rw-r--r--test/backupstore/testbackupstore.cpp2178
-rw-r--r--test/backupstore/testextra4
-rw-r--r--test/backupstore/testfiles/accounts.txt0
-rw-r--r--test/backupstore/testfiles/bbackupd.keysbin0 -> 1024 bytes
-rw-r--r--test/backupstore/testfiles/bbstored.conf17
-rw-r--r--test/backupstore/testfiles/bbstored_multi.conf16
-rw-r--r--test/backupstore/testfiles/clientCerts.pem11
-rw-r--r--test/backupstore/testfiles/clientPrivKey.pem15
-rw-r--r--test/backupstore/testfiles/clientReq.pem10
-rw-r--r--test/backupstore/testfiles/clientTrustedCAs.pem11
-rw-r--r--test/backupstore/testfiles/query.conf34
-rw-r--r--test/backupstore/testfiles/raidfile.conf10
-rw-r--r--test/backupstore/testfiles/root.pem26
-rw-r--r--test/backupstore/testfiles/root.srl1
-rw-r--r--test/backupstore/testfiles/rootcert.pem11
-rw-r--r--test/backupstore/testfiles/rootkey.pem15
-rw-r--r--test/backupstore/testfiles/rootreq.pem10
-rw-r--r--test/backupstore/testfiles/serverCerts.pem11
-rw-r--r--test/backupstore/testfiles/serverPrivKey.pem15
-rw-r--r--test/backupstore/testfiles/serverReq.pem10
-rw-r--r--test/backupstore/testfiles/serverTrustedCAs.pem11
-rw-r--r--test/backupstorefix/testbackupstorefix.cpp614
-rw-r--r--test/backupstorefix/testextra5
-rwxr-xr-xtest/backupstorefix/testfiles/testbackupstorefix.pl.in221
-rw-r--r--test/backupstorepatch/testbackupstorepatch.cpp670
-rw-r--r--test/backupstorepatch/testextra6
-rw-r--r--test/basicserver/Makefile.extra21
-rw-r--r--test/basicserver/TestCommands.cpp101
-rw-r--r--test/basicserver/TestContext.cpp16
-rw-r--r--test/basicserver/TestContext.h7
-rw-r--r--test/basicserver/testbasicserver.cpp772
-rw-r--r--test/basicserver/testfiles/clientCerts.pem14
-rw-r--r--test/basicserver/testfiles/clientPrivKey.pem15
-rw-r--r--test/basicserver/testfiles/clientReq.pem11
-rw-r--r--test/basicserver/testfiles/clientTrustedCAs.pem14
-rw-r--r--test/basicserver/testfiles/key-creation.txt83
-rw-r--r--test/basicserver/testfiles/root.pem29
-rw-r--r--test/basicserver/testfiles/root.srl1
-rw-r--r--test/basicserver/testfiles/rootcert.pem14
-rw-r--r--test/basicserver/testfiles/rootkey.pem15
-rw-r--r--test/basicserver/testfiles/rootreq.pem11
-rw-r--r--test/basicserver/testfiles/serverCerts.pem14
-rw-r--r--test/basicserver/testfiles/serverPrivKey.pem15
-rw-r--r--test/basicserver/testfiles/serverReq.pem11
-rw-r--r--test/basicserver/testfiles/serverTrustedCAs.pem14
-rw-r--r--test/basicserver/testfiles/srv1.conf6
-rw-r--r--test/basicserver/testfiles/srv1b.conf6
-rw-r--r--test/basicserver/testfiles/srv2.conf6
-rw-r--r--test/basicserver/testfiles/srv3.conf9
-rw-r--r--test/basicserver/testfiles/srv4.conf6
-rw-r--r--test/basicserver/testprotocol.txt42
-rw-r--r--test/bbackupd/Makefile.extra14
-rw-r--r--test/bbackupd/testbbackupd.cpp4077
-rw-r--r--test/bbackupd/testextra4
-rw-r--r--test/bbackupd/testfiles/accounts.txt0
-rw-r--r--test/bbackupd/testfiles/bbackupd-exclude.conf.in47
-rw-r--r--test/bbackupd/testfiles/bbackupd-snapshot.conf.in56
-rw-r--r--test/bbackupd/testfiles/bbackupd-symlink.conf.in55
-rw-r--r--test/bbackupd/testfiles/bbackupd-temploc.conf55
-rw-r--r--test/bbackupd/testfiles/bbackupd.conf.in55
-rw-r--r--test/bbackupd/testfiles/bbackupd.keysbin0 -> 1024 bytes
-rw-r--r--test/bbackupd/testfiles/bbstored.conf17
-rw-r--r--test/bbackupd/testfiles/clientCerts.pem11
-rw-r--r--test/bbackupd/testfiles/clientPrivKey.pem15
-rw-r--r--test/bbackupd/testfiles/clientTrustedCAs.pem11
-rwxr-xr-xtest/bbackupd/testfiles/extcheck1.pl.in58
-rwxr-xr-xtest/bbackupd/testfiles/extcheck2.pl.in50
-rwxr-xr-xtest/bbackupd/testfiles/notifyscript.pl.in24
-rw-r--r--test/bbackupd/testfiles/raidfile.conf10
-rw-r--r--test/bbackupd/testfiles/serverCerts.pem11
-rw-r--r--test/bbackupd/testfiles/serverPrivKey.pem15
-rw-r--r--test/bbackupd/testfiles/serverTrustedCAs.pem11
-rw-r--r--test/bbackupd/testfiles/spacetest1.tgzbin0 -> 288 bytes
-rw-r--r--test/bbackupd/testfiles/spacetest2.tgzbin0 -> 203 bytes
-rwxr-xr-xtest/bbackupd/testfiles/syncallowscript.pl.in33
-rw-r--r--test/bbackupd/testfiles/test2.tgzbin0 -> 25190 bytes
-rw-r--r--test/bbackupd/testfiles/test3.tgzbin0 -> 44957 bytes
-rw-r--r--test/bbackupd/testfiles/test_base.tgzbin0 -> 14950 bytes
-rw-r--r--test/bbackupd/testfiles/testexclude.tgzbin0 -> 377 bytes
-rw-r--r--test/common/testcommon.cpp882
-rw-r--r--test/common/testfiles/config1.txt40
-rw-r--r--test/common/testfiles/config10.txt37
-rw-r--r--test/common/testfiles/config11.txt39
-rw-r--r--test/common/testfiles/config12.txt33
-rw-r--r--test/common/testfiles/config13.txt15
-rw-r--r--test/common/testfiles/config14.txt41
-rw-r--r--test/common/testfiles/config15.txt45
-rw-r--r--test/common/testfiles/config16.txt42
-rw-r--r--test/common/testfiles/config2.txt39
-rw-r--r--test/common/testfiles/config3.txt39
-rw-r--r--test/common/testfiles/config4.txt40
-rw-r--r--test/common/testfiles/config5.txt37
-rw-r--r--test/common/testfiles/config6.txt39
-rw-r--r--test/common/testfiles/config7.txt39
-rw-r--r--test/common/testfiles/config8.txt37
-rw-r--r--test/common/testfiles/config9.txt38
-rw-r--r--test/common/testfiles/config9b.txt38
-rw-r--r--test/common/testfiles/config9c.txt38
-rw-r--r--test/common/testfiles/config9d.txt38
-rw-r--r--test/common/testfiles/fdgetlinetest.txt20
-rw-r--r--test/compress/testcompress.cpp261
-rw-r--r--test/crypto/testcrypto.cpp314
-rw-r--r--test/httpserver/testfiles/httpserver.conf8
-rw-r--r--test/httpserver/testfiles/photos/puppy.jpg1
-rw-r--r--test/httpserver/testfiles/s3simulator.conf10
-rwxr-xr-xtest/httpserver/testfiles/testrequests.pl143
-rw-r--r--test/httpserver/testhttpserver.cpp480
-rw-r--r--test/raidfile/testextra7
-rw-r--r--test/raidfile/testfiles/raidfile.conf30
-rw-r--r--test/raidfile/testraidfile.cpp981
-rw-r--r--test/win32/Makefile5
-rw-r--r--test/win32/testlibwin32.cpp345
-rw-r--r--test/win32/timezone.cpp87
117 files changed, 15005 insertions, 0 deletions
diff --git a/test/backupdiff/difftestfiles.cpp b/test/backupdiff/difftestfiles.cpp
new file mode 100644
index 00000000..33690f6b
--- /dev/null
+++ b/test/backupdiff/difftestfiles.cpp
@@ -0,0 +1,295 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: createtestfiles.cpp
+// Purpose: Create the test files for the backupdiff test
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include "FileStream.h"
+#include "PartialReadStream.h"
+#include "Test.h"
+#include "RollingChecksum.h"
+
+#include "MemLeakFindOn.h"
+
+#define ACT_END 0
+#define ACT_COPY 1
+#define ACT_NEW 2
+#define ACT_SKIP 3
+#define ACT_COPYEND 4
+
+typedef struct
+{
+ int action, length, seed;
+} gen_action;
+
+#define INITIAL_FILE_LENGTH (128*1024 + 342)
+
+
+gen_action file1actions[] = {
+ {ACT_COPYEND, 0, 0},
+ {ACT_END, 0, 0} };
+
+gen_action file2actions[] = {
+ {ACT_COPY, 16*1024, 0},
+ // Do blocks on block boundaries, but swapped around a little
+ {ACT_SKIP, 4*1024, 0},
+ {ACT_COPY, 8*1024, 0},
+ {ACT_SKIP, -12*1024, 0},
+ {ACT_COPY, 4*1024, 0},
+ {ACT_SKIP, 8*1024, 0},
+ // Get rest of file with some new data inserted
+ {ACT_COPY, 37*1024 + 12, 0},
+ {ACT_NEW, 23*1024 + 129, 23990},
+ {ACT_COPYEND, 0, 0},
+ {ACT_END, 0, 0} };
+
+gen_action file3actions[] = {
+ {ACT_COPY, 12*1024 + 983, 0},
+ {ACT_SKIP, 37*1024 + 12, 0},
+ {ACT_COPYEND, 0, 0},
+ {ACT_END, 0, 0} };
+
+gen_action file4actions[] = {
+ {ACT_COPY, 20*1024 + 2385, 0},
+ {ACT_NEW, 12, 2334},
+ {ACT_COPY, 16*1024 + 385, 0},
+ {ACT_SKIP, 9*1024 + 42, 0},
+ {ACT_COPYEND, 0, 0},
+ {ACT_END, 0, 0} };
+
+// insert 1 byte a block into the file, between two other blocks
+gen_action file5actions[] = {
+ {ACT_COPY, 4*1024, 0},
+ {ACT_NEW, 1, 2334},
+ {ACT_COPYEND, 0, 0},
+ {ACT_END, 0, 0} };
+
+gen_action file6actions[] = {
+ {ACT_NEW, 6*1024, 12353452},
+ {ACT_COPYEND, 0, 0},
+ {ACT_END, 0, 0} };
+
+// but delete that one byte block, it's annoying
+gen_action file7actions[] = {
+ {ACT_COPY, 10*1024, 0},
+ {ACT_SKIP, 1, 0},
+ {ACT_COPYEND, 0, 0},
+ {ACT_NEW, 7*1024, 1235352},
+ {ACT_END, 0, 0} };
+
+gen_action file8actions[] = {
+ {ACT_NEW, 54*1024 + 9, 125352},
+ {ACT_END, 0, 0} };
+
+gen_action file9actions[] = {
+ {ACT_END, 0, 0} };
+
+gen_action *testfiles[] = {file1actions, file2actions, file3actions, file4actions,
+ file5actions, file6actions, file7actions, file8actions, file9actions, 0};
+
+
+// Nice random data for testing written files
+class R250 {
+public:
+ // Set up internal state table with 32-bit random numbers.
+ // The bizarre bit-twiddling is because rand() returns 16 bits of which
+ // the bottom bit is always zero! Hence, I use only some of the bits.
+ // You might want to do something better than this....
+
+ R250(int seed) : posn1(0), posn2(103)
+ {
+ // populate the state and incr tables
+ srand(seed);
+
+ for (int i = 0; i != stateLen; ++i) {
+ state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2);
+ incrTable[i] = i == stateLen - 1 ? 0 : i + 1;
+ }
+
+ // stir up the numbers to ensure they're random
+
+ for (int j = 0; j != stateLen * 4; ++j)
+ (void) next();
+ }
+
+ // Returns the next random number. Xor together two elements separated
+ // by 103 mod 250, replacing the first element with the result. Then
+ // increment the two indices mod 250.
+ inline int next()
+ {
+ int ret = (state[posn1] ^= state[posn2]); // xor and replace element
+
+ posn1 = incrTable[posn1]; // increment indices using lookup table
+ posn2 = incrTable[posn2];
+
+ return ret;
+ }
+private:
+ enum { stateLen = 250 }; // length of the state table
+ int state[stateLen]; // holds the random number state
+ int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen
+ int posn1, posn2; // indices into the state table
+};
+
+void make_random_data(void *buffer, int size, int seed)
+{
+ R250 rand(seed);
+
+ int n = size / sizeof(int);
+ int *b = (int*)buffer;
+ for(int l = 0; l < n; ++l)
+ {
+ b[l] = rand.next();
+ }
+}
+
+void write_test_data(IOStream &rstream, int size, int seed)
+{
+ R250 rand(seed);
+
+ while(size > 0)
+ {
+ // make a nice buffer of data
+ int buffer[2048/sizeof(int)];
+ for(unsigned int l = 0; l < (sizeof(buffer) / sizeof(int)); ++l)
+ {
+ buffer[l] = rand.next();
+ }
+
+ // Write out...
+ unsigned int w = size;
+ if(w > sizeof(buffer)) w = sizeof(buffer);
+ rstream.Write(buffer, w);
+
+ size -= w;
+ }
+}
+
+void gen_varient(IOStream &out, char *sourcename, gen_action *pact)
+{
+ // Open source
+ FileStream source(sourcename);
+
+ while(true)
+ {
+ switch(pact->action)
+ {
+ case ACT_END:
+ {
+ // all done
+ return;
+ }
+ case ACT_COPY:
+ {
+ PartialReadStream copy(source, pact->length);
+ copy.CopyStreamTo(out);
+ break;
+ }
+ case ACT_NEW:
+ {
+ write_test_data(out, pact->length, pact->seed);
+ break;
+ }
+ case ACT_SKIP:
+ {
+ source.Seek(pact->length, IOStream::SeekType_Relative);
+ break;
+ }
+ case ACT_COPYEND:
+ {
+ source.CopyStreamTo(out);
+ break;
+ }
+ }
+
+ ++pact;
+ }
+}
+
+void create_test_files()
+{
+ // First, the keys for the crypto
+ {
+ FileStream keys("testfiles/backup.keys", O_WRONLY | O_CREAT);
+ write_test_data(keys, 1024, 237);
+ }
+
+ // Create the initial file -- needs various special properties...
+ // 1) Two blocks much be the different, but have the same weak checksum
+ // 2) A block must exist twice, but at an offset which isn't a multiple of the block size.
+ {
+ FileStream f0("testfiles/f0", O_WRONLY | O_CREAT);
+ // Write first bit.
+ write_test_data(f0, (16*1024), 20012);
+ // Now repeated checksum blocks
+ uint8_t blk[4096];
+ make_random_data(blk, sizeof(blk), 12201);
+ // Three magic numbers which make the checksum work: Use this perl to find them:
+ /*
+ for($z = 1; $z < 4096; $z++)
+ {
+ for($n = 0; $n <= 255; $n++)
+ {
+ for($m = 0; $m <= 255; $m++)
+ {
+ if($n != $m && (($n*4096 + $m*(4096-$z)) % (64*1024) == ($n*(4096-$z) + $m*4096) % (64*1024)))
+ {
+ print "$z: $n $m\n";
+ }
+ }
+ }
+ }
+ */
+ blk[0] = 255;
+ blk[1024] = 191;
+ // Checksum to check
+ RollingChecksum c1(blk, sizeof(blk));
+ // Write
+ f0.Write(blk, sizeof(blk));
+ // Adjust block and write again
+ uint8_t blk2[4096];
+ memcpy(blk2, blk, sizeof(blk2));
+ blk2[1024] = 255;
+ blk2[0] = 191;
+ TEST_THAT(::memcmp(blk2, blk, sizeof(blk)) != 0);
+ RollingChecksum c2(blk2, sizeof(blk2));
+ f0.Write(blk2, sizeof(blk2));
+ // Check checksums
+ TEST_THAT(c1.GetChecksum() == c2.GetChecksum());
+
+ // Another 4k block
+ write_test_data(f0, (4*1024), 99209);
+ // Offset block
+ make_random_data(blk, 2048, 1234199);
+ f0.Write(blk, 2048);
+ f0.Write(blk, 2048);
+ f0.Write(blk, 2048);
+ make_random_data(blk, 2048, 1343278);
+ f0.Write(blk, 2048);
+
+ write_test_data(f0, INITIAL_FILE_LENGTH - (16*1024) - ((4*1024)*2) - (4*1024) - (2048*4), 202);
+
+ }
+
+ // Then... create the varients
+ for(int l = 0; testfiles[l] != 0; ++l)
+ {
+ char n1[256];
+ char n2[256];
+ sprintf(n1, "testfiles/f%d", l + 1);
+ sprintf(n2, "testfiles/f%d", l);
+
+ FileStream f1(n1, O_WRONLY | O_CREAT);
+ gen_varient(f1, n2, testfiles[l]);
+ }
+}
+
+
diff --git a/test/backupdiff/testbackupdiff.cpp b/test/backupdiff/testbackupdiff.cpp
new file mode 100644
index 00000000..816f50d1
--- /dev/null
+++ b/test/backupdiff/testbackupdiff.cpp
@@ -0,0 +1,605 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testbackupdiff.cpp
+// Purpose: Test diffing routines for backup store files
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "Test.h"
+#include "BackupClientCryptoKeys.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFilenameClear.h"
+#include "FileStream.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "BackupStoreFileCryptVar.h"
+#include "BackupStoreException.h"
+#include "CollectInBufferStream.h"
+
+#include "MemLeakFindOn.h"
+
+using namespace BackupStoreFileCryptVar;
+
+
+// from another file
+void create_test_files();
+
+bool files_identical(const char *file1, const char *file2)
+{
+ FileStream f1(file1);
+ FileStream f2(file2);
+
+ if(f1.BytesLeftToRead() != f2.BytesLeftToRead())
+ {
+ return false;
+ }
+
+ while(f1.StreamDataLeft())
+ {
+ char buffer1[2048];
+ char buffer2[2048];
+ int s = f1.Read(buffer1, sizeof(buffer1));
+ if(f2.Read(buffer2, s) != s)
+ {
+ return false;
+ }
+ if(::memcmp(buffer1, buffer2, s) != 0)
+ {
+ return false;
+ }
+ }
+
+ if(f2.StreamDataLeft())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool make_file_of_zeros(const char *filename, size_t size)
+{
+ #ifdef WIN32
+ HANDLE handle = openfile(filename, O_WRONLY | O_CREAT | O_EXCL, 0);
+ TEST_THAT(handle != INVALID_HANDLE_VALUE);
+ TEST_THAT(SetFilePointer(handle, size, NULL, FILE_BEGIN)
+ != INVALID_SET_FILE_POINTER);
+ TEST_THAT(GetLastError() == NO_ERROR);
+ BOOL result = SetEndOfFile(handle);
+ if (result != TRUE)
+ {
+ BOX_ERROR("Failed to create large file " << filename <<
+ " (" << (size >> 20) << " MB): " <<
+ GetErrorMessage(GetLastError()));
+ }
+ TEST_THAT(result == TRUE);
+ TEST_THAT(CloseHandle(handle) == TRUE);
+ #else
+ int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
+ if (fd < 0) perror(filename);
+ TEST_THAT(fd >= 0);
+ TEST_THAT(ftruncate(fd, size) == 0);
+ TEST_THAT(close(fd) == 0);
+ #endif
+
+ bool correct_size = ((size_t)TestGetFileSize(filename) == size);
+ TEST_THAT(correct_size);
+ if (!correct_size)
+ {
+ BOX_ERROR("Failed to create large file " << filename <<
+ " (" << (size >> 20) << " MB): " <<
+ "got " << (TestGetFileSize(filename) >> 20) <<
+ " MB instead");
+ }
+ return correct_size;
+}
+
+
+void check_encoded_file(const char *filename, int64_t OtherFileID, int new_blocks_expected, int old_blocks_expected)
+{
+ FileStream enc(filename);
+
+ // Use the interface verify routine
+ int64_t otherIDFromFile = 0;
+ TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc, &otherIDFromFile));
+ TEST_THAT(otherIDFromFile == OtherFileID);
+
+ // Now do our own reading
+ enc.Seek(0, IOStream::SeekType_Absolute);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(enc);
+ // Read in header to check magic value is as expected
+ file_BlockIndexHeader hdr;
+ TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0));
+ TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1));
+ TEST_THAT((uint64_t)box_ntoh64(hdr.mOtherFileID) == (uint64_t)OtherFileID);
+ // number of blocks
+ int64_t nblocks = box_ntoh64(hdr.mNumBlocks);
+ BOX_TRACE("Reading index from '" << filename << "', has " <<
+ nblocks << " blocks");
+ BOX_TRACE("======== ===== ========== ======== ========");
+ BOX_TRACE(" Index Where EncSz/Idx Size WChcksm");
+ // Read them all in
+ int64_t nnew = 0, nold = 0;
+ for(int64_t b = 0; b < nblocks; ++b)
+ {
+ file_BlockIndexEntry en;
+ TEST_THAT(enc.ReadFullBuffer(&en, sizeof(en), 0));
+ int64_t s = box_ntoh64(en.mEncodedSize);
+
+ // Decode the rest
+ uint64_t iv = box_ntoh64(hdr.mEntryIVBase);
+ iv += b;
+ sBlowfishDecryptBlockEntry.SetIV(&iv);
+ file_BlockIndexEntryEnc entryEnc;
+ sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc,
+ sizeof(entryEnc), en.mEnEnc, sizeof(en.mEnEnc));
+
+
+ if(s > 0)
+ {
+ nnew++;
+ BOX_TRACE(std::setw(8) << b << " this s=" <<
+ std::setw(8) << s << " " <<
+ std::setw(8) << ntohl(entryEnc.mSize) << " " <<
+ std::setw(8) << std::setfill('0') <<
+ std::hex << ntohl(entryEnc.mWeakChecksum));
+ }
+ else
+ {
+ nold++;
+ BOX_TRACE(std::setw(8) << b << " other i=" <<
+ std::setw(8) << (0-s) << " " <<
+ std::setw(8) << ntohl(entryEnc.mSize) << " " <<
+ std::setw(8) << std::setfill('0') <<
+ std::hex << ntohl(entryEnc.mWeakChecksum));
+ }
+ }
+ BOX_TRACE("======== ===== ========== ======== ========");
+ TEST_THAT(new_blocks_expected == nnew);
+ TEST_THAT(old_blocks_expected == nold);
+}
+
+void test_diff(int from, int to, int new_blocks_expected, int old_blocks_expected, bool expect_completely_different = false)
+{
+ // First, get the block index of the thing it's comparing against
+ char from_encoded[256];
+ sprintf(from_encoded, "testfiles/f%d.encoded", from);
+ FileStream blockindex(from_encoded);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
+
+ // make filenames
+ char from_orig[256];
+ sprintf(from_orig, "testfiles/f%d", from);
+ char to_encoded[256];
+ sprintf(to_encoded, "testfiles/f%d.encoded", to);
+ char to_diff[256];
+ sprintf(to_diff, "testfiles/f%d.diff", to);
+ char to_orig[256];
+ sprintf(to_orig, "testfiles/f%d", to);
+ char rev_diff[256];
+ sprintf(rev_diff, "testfiles/f%d.revdiff", to);
+ char from_rebuild[256];
+ sprintf(from_rebuild, "testfiles/f%d.rebuilt", to);
+ char from_rebuild_dec[256];
+ sprintf(from_rebuild_dec, "testfiles/f%d.rebuilt_dec", to);
+
+ // Then call the encode varient for diffing files
+ bool completelyDifferent = !expect_completely_different; // oposite of what we want
+ {
+ BackupStoreFilenameClear f1name("filename");
+ FileStream out(to_diff, O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(
+ BackupStoreFile::EncodeFileDiff(
+ to_orig,
+ 1 /* dir ID */,
+ f1name,
+ 1000 + from /* object ID of the file diffing from */,
+ blockindex,
+ IOStream::TimeOutInfinite,
+ NULL, // DiffTimer interface
+ 0,
+ &completelyDifferent));
+ encoded->CopyStreamTo(out);
+ }
+ TEST_THAT(completelyDifferent == expect_completely_different);
+
+ // Test that the number of blocks in the file match what's expected
+ check_encoded_file(to_diff, expect_completely_different?(0):(1000 + from), new_blocks_expected, old_blocks_expected);
+
+ // filename
+ char to_testdec[256];
+ sprintf(to_testdec, "testfiles/f%d.testdec", to);
+
+ if(!completelyDifferent)
+ {
+ // Then produce a combined file
+ {
+ FileStream diff(to_diff);
+ FileStream diff2(to_diff);
+ FileStream from(from_encoded);
+ FileStream out(to_encoded, O_WRONLY | O_CREAT | O_EXCL);
+ BackupStoreFile::CombineFile(diff, diff2, from, out);
+ }
+
+ // And check it
+ check_encoded_file(to_encoded, 0, new_blocks_expected + old_blocks_expected, 0);
+ }
+ else
+ {
+#ifdef WIN32
+ // Emulate the above stage!
+ char src[256], dst[256];
+ sprintf(src, "testfiles\\f%d.diff", to);
+ sprintf(dst, "testfiles\\f%d.encoded", to);
+ TEST_THAT(CopyFile(src, dst, FALSE) != 0)
+#else
+ // Emulate the above stage!
+ char cmd[256];
+ sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to);
+ ::system(cmd);
+#endif
+ }
+
+ // Decode it
+ {
+ FileStream enc(to_encoded);
+ BackupStoreFile::DecodeFile(enc, to_testdec, IOStream::TimeOutInfinite);
+ TEST_THAT(files_identical(to_orig, to_testdec));
+ }
+
+ // Then do some comparisons against the block index
+ {
+ FileStream index(to_encoded);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(index);
+ TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(to_orig, index, IOStream::TimeOutInfinite) == true);
+ }
+ {
+ char from_orig[256];
+ sprintf(from_orig, "testfiles/f%d", from);
+ FileStream index(to_encoded);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(index);
+ TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(from_orig, index, IOStream::TimeOutInfinite) == files_identical(from_orig, to_orig));
+ }
+
+ // Check that combined index creation works as expected
+ {
+ // Load a combined index into memory
+ FileStream diff(to_diff);
+ FileStream from(from_encoded);
+ std::auto_ptr<IOStream> indexCmbStr(BackupStoreFile::CombineFileIndices(diff, from));
+ CollectInBufferStream indexCmb;
+ indexCmbStr->CopyStreamTo(indexCmb);
+ // Then check that it's as expected!
+ FileStream result(to_encoded);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(result);
+ CollectInBufferStream index;
+ result.CopyStreamTo(index);
+ TEST_THAT(indexCmb.GetSize() == index.GetSize());
+ TEST_THAT(::memcmp(indexCmb.GetBuffer(), index.GetBuffer(), index.GetSize()) == 0);
+ }
+
+ // Check that reverse delta can be made, and that it decodes OK
+ {
+ // Create reverse delta
+ {
+ bool reversedCompletelyDifferent = !completelyDifferent;
+ FileStream diff(to_diff);
+ FileStream from(from_encoded);
+ FileStream from2(from_encoded);
+ FileStream reversed(rev_diff, O_WRONLY | O_CREAT);
+ BackupStoreFile::ReverseDiffFile(diff, from, from2, reversed, to, &reversedCompletelyDifferent);
+ TEST_THAT(reversedCompletelyDifferent == completelyDifferent);
+ }
+ // Use it to combine a file
+ {
+ FileStream diff(rev_diff);
+ FileStream diff2(rev_diff);
+ FileStream from(to_encoded);
+ FileStream out(from_rebuild, O_WRONLY | O_CREAT | O_EXCL);
+ BackupStoreFile::CombineFile(diff, diff2, from, out);
+ }
+ // And then confirm that this file is actually the one we want
+ {
+ FileStream enc(from_rebuild);
+ BackupStoreFile::DecodeFile(enc, from_rebuild_dec, IOStream::TimeOutInfinite);
+ TEST_THAT(files_identical(from_orig, from_rebuild_dec));
+ }
+ // Do some extra checking
+ {
+ TEST_THAT(files_identical(from_rebuild, from_encoded));
+ }
+ }
+}
+
+void test_combined_diff(int version1, int version2, int serial)
+{
+ char combined_file[256];
+ char last_diff[256];
+ sprintf(last_diff, "testfiles/f%d.diff", version1 + 1); // ie from version1 to version1 + 1
+
+ for(int v = version1 + 2; v <= version2; ++v)
+ {
+ FileStream diff1(last_diff);
+ char next_diff[256];
+ sprintf(next_diff, "testfiles/f%d.diff", v);
+ FileStream diff2(next_diff);
+ FileStream diff2b(next_diff);
+ sprintf(combined_file, "testfiles/comb%d_%d.cmbdiff", version1, v);
+ FileStream out(combined_file, O_WRONLY | O_CREAT);
+ BackupStoreFile::CombineDiffs(diff1, diff2, diff2b, out);
+ strcpy(last_diff, combined_file);
+ }
+
+ // Then do a combine on it, and check that it decodes to the right thing
+ char orig_enc[256];
+ sprintf(orig_enc, "testfiles/f%d.encoded", version1);
+ char combined_out[256];
+ sprintf(combined_out, "testfiles/comb%d_%d.out", version1, version2);
+
+ {
+ FileStream diff(combined_file);
+ FileStream diff2(combined_file);
+ FileStream from(orig_enc);
+ FileStream out(combined_out, O_WRONLY | O_CREAT);
+ BackupStoreFile::CombineFile(diff, diff2, from, out);
+ }
+
+ char combined_out_dec[256];
+ sprintf(combined_out_dec, "testfiles/comb%d_%d_s%d.dec", version1, version2, serial);
+ char to_orig[256];
+ sprintf(to_orig, "testfiles/f%d", version2);
+
+ {
+ FileStream enc(combined_out);
+ BackupStoreFile::DecodeFile(enc, combined_out_dec, IOStream::TimeOutInfinite);
+ TEST_THAT(files_identical(to_orig, combined_out_dec));
+ }
+
+}
+
+#define MAX_DIFF 9
+void test_combined_diffs()
+{
+ int serial = 0;
+
+ // Number of items to combine at once
+ for(int stages = 2; stages <= 4; ++stages)
+ {
+ // Offset to get complete coverage
+ for(int offset = 0; offset < stages; ++offset)
+ {
+ // And then actual end file number
+ for(int f = 0; f <= (MAX_DIFF - stages - offset); ++f)
+ {
+ // And finally, do something!
+ test_combined_diff(offset + f, offset + f + stages, ++serial);
+ }
+ }
+ }
+}
+
+int test(int argc, const char *argv[])
+{
+ // Want to trace out all the details
+ #ifndef BOX_RELEASE_BUILD
+ #ifndef WIN32
+ BackupStoreFile::TraceDetailsOfDiffProcess = true;
+ #endif
+ #endif
+
+ // Create all the test files
+ create_test_files();
+
+ // Setup the crypto
+ BackupClientCryptoKeys_Setup("testfiles/backup.keys");
+
+ // Encode the first file
+ {
+ BackupStoreFilenameClear f0name("f0");
+ FileStream out("testfiles/f0.encoded", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/f0", 1 /* dir ID */, f0name));
+ encoded->CopyStreamTo(out);
+ out.Close();
+ check_encoded_file("testfiles/f0.encoded", 0, 33, 0);
+ }
+
+ // Check the "seek to index" code
+ {
+ FileStream enc("testfiles/f0.encoded");
+ BackupStoreFile::MoveStreamPositionToBlockIndex(enc);
+ // Read in header to check magic value is as expected
+ file_BlockIndexHeader hdr;
+ TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0));
+ TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1));
+ }
+
+ // Diff some files -- parameters are from number, to number,
+ // then the number of new blocks expected, and the number of old blocks expected.
+
+ // Diff the original file to a copy of itself, and check that there is no data in the file
+ // This checks that the hash table is constructed properly, because two of the blocks share
+ // the same weak checksum.
+ test_diff(0, 1, 0, 33);
+
+ // Insert some new data
+ // Blocks from old file moved whole, but put in different order
+ test_diff(1, 2, 7, 32);
+
+ // Delete some data, but not on block boundaries
+ test_diff(2, 3, 1, 29);
+
+ // Add a very small amount of data, not on block boundary
+ // delete a little data
+ test_diff(3, 4, 3, 25);
+
+ // 1 byte insertion between two blocks
+ test_diff(4, 5, 1, 28);
+
+ // a file with some new content at the very beginning
+ // NOTE: You might expect the last numbers to be 2, 29, but the small 1 byte block isn't searched for
+ test_diff(5, 6, 3, 28);
+
+ // some new content at the very end
+ // NOTE: 1 byte block deleted, so number aren't what you'd initial expect.
+ test_diff(6, 7, 2, 30);
+
+ // a completely different file, with no blocks matching.
+ test_diff(7, 8, 14, 0, true /* completely different expected */);
+
+ // diff to zero sized file
+ test_diff(8, 9, 0, 0, true /* completely different expected */);
+
+ // Test that combining diffs works
+ test_combined_diffs();
+
+ // Check zero sized file works OK to encode on its own, using normal encoding
+ {
+ {
+ // Encode
+ BackupStoreFilenameClear fn("filename");
+ FileStream out("testfiles/f9.zerotest", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/f9", 1 /* dir ID */, fn));
+ encoded->CopyStreamTo(out);
+ out.Close();
+ check_encoded_file("testfiles/f9.zerotest", 0, 0, 0);
+ }
+ {
+ // Decode
+ FileStream enc("testfiles/f9.zerotest");
+ BackupStoreFile::DecodeFile(enc, "testfiles/f9.testdec.zero", IOStream::TimeOutInfinite);
+ TEST_THAT(files_identical("testfiles/f9", "testfiles/f9.testdec.zero"));
+ }
+ }
+
+#ifndef WIN32
+ // Check that symlinks aren't diffed
+ TEST_THAT(::symlink("f2", "testfiles/f2.symlink") == 0)
+ // And go and diff it against the previous encoded file
+ {
+ bool completelyDifferent = false;
+ {
+ FileStream blockindex("testfiles/f1.encoded");
+ BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
+
+ BackupStoreFilenameClear f1name("filename");
+ FileStream out("testfiles/f2.symlink.diff", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(
+ BackupStoreFile::EncodeFileDiff(
+ "testfiles/f2.symlink",
+ 1 /* dir ID */,
+ f1name,
+ 1001 /* object ID of the file diffing from */,
+ blockindex,
+ IOStream::TimeOutInfinite,
+ NULL, // DiffTimer interface
+ 0,
+ &completelyDifferent));
+ encoded->CopyStreamTo(out);
+ }
+ TEST_THAT(completelyDifferent == true);
+ check_encoded_file("testfiles/f2.symlink.diff", 0, 0, 0);
+ }
+#endif
+
+ // Check that diffing against a file which isn't "complete" and
+ // references another isn't allowed
+ {
+ FileStream blockindex("testfiles/f1.diff");
+ BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
+
+ BackupStoreFilenameClear f1name("filename");
+ FileStream out("testfiles/f2.testincomplete", O_WRONLY | O_CREAT | O_EXCL);
+ TEST_CHECK_THROWS(BackupStoreFile::EncodeFileDiff("testfiles/f2", 1 /* dir ID */, f1name,
+ 1001 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite,
+ 0, 0), BackupStoreException, CannotDiffAnIncompleteStoreFile);
+ }
+
+ // Found a nasty case where files of lots of the same thing
+ // suck up lots of processor time -- because of lots of matches
+ // found. Check this out!
+
+ #ifdef WIN32
+ BOX_WARNING("Testing diffing two large streams, may take a while!");
+ ::fflush(stderr);
+ #endif
+
+ if (!make_file_of_zeros("testfiles/zero.0", 20*1024*1024))
+ {
+ return 1;
+ }
+
+ if (!make_file_of_zeros("testfiles/zero.1", 200*1024*1024))
+ {
+ remove("testfiles/zero.0");
+ return 1;
+ }
+
+ // Generate a first encoded file
+ {
+ BackupStoreFilenameClear f0name("zero.0");
+ FileStream out("testfiles/zero.0.enc", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/zero.0", 1 /* dir ID */, f0name));
+ encoded->CopyStreamTo(out);
+ }
+ // Then diff from it -- time how long it takes...
+ {
+ int beginTime = time(0);
+ FileStream blockindex("testfiles/zero.0.enc");
+ BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
+
+ BackupStoreFilenameClear f1name("zero.1");
+ FileStream out("testfiles/zero.1.enc", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFileDiff("testfiles/zero.1", 1 /* dir ID */, f1name,
+ 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite,
+ 0, 0));
+ encoded->CopyStreamTo(out);
+
+ printf("Time taken: %d seconds\n", (int)(time(0) - beginTime));
+
+ #ifdef WIN32
+ TEST_THAT(time(0) < (beginTime + 300));
+ #else
+ TEST_THAT(time(0) < (beginTime + 40));
+ #endif
+ }
+ // Remove zero-files to save disk space
+ remove("testfiles/zero.0");
+ remove("testfiles/zero.1");
+
+#if 0
+ // Code for a nasty real world example! (16Mb files, won't include them in the distribution
+ // for obvious reasons...)
+ // Generate a first encoded file
+ {
+ BackupStoreFilenameClear f0name("0000000000000000.old");
+ FileStream out("testfiles/0000000000000000.enc.0", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("/Users/ben/Desktop/0000000000000000.old", 1 /* dir ID */, f0name));
+ encoded->CopyStreamTo(out);
+ }
+ // Then diff from it -- time how long it takes...
+ {
+ int beginTime = time(0);
+ FileStream blockindex("testfiles/0000000000000000.enc.0");
+ BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex);
+
+ BackupStoreFilenameClear f1name("0000000000000000.new");
+ FileStream out("testfiles/0000000000000000.enc.1", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFileDiff("/Users/ben/Desktop/0000000000000000.new", 1 /* dir ID */, f1name,
+ 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite,
+ 0, 0));
+ encoded->CopyStreamTo(out);
+ TEST_THAT(time(0) < (beginTime + 20));
+ }
+#endif // 0
+
+ return 0;
+}
+
+
diff --git a/test/backupdiff/testextra b/test/backupdiff/testextra
new file mode 100644
index 00000000..165cacb9
--- /dev/null
+++ b/test/backupdiff/testextra
@@ -0,0 +1,2 @@
+rm -rf testfiles
+mkdir testfiles
diff --git a/test/backupstore/Makefile.extra b/test/backupstore/Makefile.extra
new file mode 100644
index 00000000..e2e2d27c
--- /dev/null
+++ b/test/backupstore/Makefile.extra
@@ -0,0 +1 @@
+link-extra: ../../bin/bbstored/HousekeepStoreAccount.o
diff --git a/test/backupstore/testbackupstore.cpp b/test/backupstore/testbackupstore.cpp
new file mode 100644
index 00000000..646e6b45
--- /dev/null
+++ b/test/backupstore/testbackupstore.cpp
@@ -0,0 +1,2178 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testbackupstore.cpp
+// Purpose: Test backup store server
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "Test.h"
+#include "autogen_BackupProtocolClient.h"
+#include "SSLLib.h"
+#include "TLSContext.h"
+#include "SocketStreamTLS.h"
+#include "BoxPortsAndFiles.h"
+#include "BackupStoreConstants.h"
+#include "Socket.h"
+#include "BackupStoreFilenameClear.h"
+#include "CollectInBufferStream.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreFile.h"
+#include "FileStream.h"
+#include "RaidFileController.h"
+#include "RaidFileWrite.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreException.h"
+#include "RaidFileException.h"
+#include "MemBlockStream.h"
+#include "BackupClientFileAttributes.h"
+#include "BackupClientCryptoKeys.h"
+#include "ServerControl.h"
+#include "BackupStoreAccountDatabase.h"
+#include "BackupStoreRefCountDatabase.h"
+#include "BackupStoreAccounts.h"
+#include "HousekeepStoreAccount.h"
+
+#include "MemLeakFindOn.h"
+
+
+#define ENCFILE_SIZE 2765
+
+typedef struct
+{
+ BackupStoreFilenameClear fn;
+ box_time_t mod;
+ int64_t id;
+ int64_t size;
+ int16_t flags;
+ box_time_t attrmod;
+} dirtest;
+
+static dirtest ens[] =
+{
+ {BackupStoreFilenameClear(), 324324, 3432, 324, BackupStoreDirectory::Entry::Flags_File, 458763243422LL},
+ {BackupStoreFilenameClear(), 3432, 32443245645LL, 78, BackupStoreDirectory::Entry::Flags_Dir, 3248972347LL},
+ {BackupStoreFilenameClear(), 544435, 234234, 23324, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 2348974782LL},
+ {BackupStoreFilenameClear(), 234, 235436, 6523, BackupStoreDirectory::Entry::Flags_File, 32458923175634LL},
+ {BackupStoreFilenameClear(), 0x3242343532144LL, 8978979789LL, 21345, BackupStoreDirectory::Entry::Flags_File, 329483243432LL},
+ {BackupStoreFilenameClear(), 324265765734LL, 12312312321LL, 324987324329874LL, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 32489747234LL},
+ {BackupStoreFilenameClear(), 3452134, 7868578768LL, 324243, BackupStoreDirectory::Entry::Flags_Dir, 34786457432LL},
+ {BackupStoreFilenameClear(), 43543543, 324234, 21432, BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_Deleted, 3489723478327LL},
+ {BackupStoreFilenameClear(), 325654765874324LL, 4353543, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 32489734789237LL},
+ {BackupStoreFilenameClear(), 32144325, 436547657, 9, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 234897347234LL}
+};
+static const char *ens_filenames[] = {"obj1ertewt", "obj2", "obj3", "obj4dfedfg43", "obj5", "obj6dfgs", "obj7", "obj8xcvbcx", "obj9", "obj10fgjhfg"};
+#define DIR_NUM 10
+#define DIR_DIRS 3
+#define DIR_FILES 7
+#define DIR_OLD 2
+#define DIR_DELETED 3
+
+typedef struct
+{
+ const char *fnextra;
+ BackupStoreFilenameClear name;
+ int seed;
+ int size;
+ box_time_t mod_time;
+ int64_t allocated_objid;
+ bool should_be_old_version;
+ bool delete_file;
+} uploadtest;
+
+#define TEST_FILE_FOR_PATCHING "testfiles/test2"
+// a few bytes will be inserted at this point:
+#define TEST_FILE_FOR_PATCHING_PATCH_AT ((64*1024)-128)
+#define TEST_FILE_FOR_PATCHING_SIZE ((128*1024)+2564)
+#define UPLOAD_PATCH_EN 2
+
+uploadtest uploads[] =
+{
+ {"0", BackupStoreFilenameClear(), 324, 455, 0, 0, false, false},
+ {"1", BackupStoreFilenameClear(), 3232432, 2674, 0, 0, true, false}, // old ver
+ {"2", BackupStoreFilenameClear(), 234, TEST_FILE_FOR_PATCHING_SIZE, 0, 0, false, false},
+ {"3", BackupStoreFilenameClear(), 324324, 6763, 0, 0, false, false},
+ {"4", BackupStoreFilenameClear(), 23456, 124, 0, 0, true, false}, // old ver
+ {"5", BackupStoreFilenameClear(), 675745, 1, 0, 0, false, false}, // will upload new attrs for this one!
+ {"6", BackupStoreFilenameClear(), 345213, 0, 0, 0, false, false},
+ {"7", BackupStoreFilenameClear(), 12313, 3246, 0, 0, true, true}, // old ver, will get deleted
+ {"8", BackupStoreFilenameClear(), 457, 3434, 0, 0, false, false}, // overwrites
+ {"9", BackupStoreFilenameClear(), 12315, 446, 0, 0, false, false},
+ {"a", BackupStoreFilenameClear(), 3476, 2466, 0, 0, false, false},
+ {"b", BackupStoreFilenameClear(), 124334, 4562, 0, 0, false, false},
+ {"c", BackupStoreFilenameClear(), 45778, 234, 0, 0, false, false}, // overwrites
+ {"d", BackupStoreFilenameClear(), 2423425, 435, 0, 0, false, true} // overwrites, will be deleted
+};
+static const char *uploads_filenames[] = {"49587fds", "cvhjhj324", "sdfcscs324", "dsfdsvsdc3214", "XXsfdsdf2342", "dsfdsc232",
+ "sfdsdce2345", "YYstfbdtrdf76", "cvhjhj324", "fbfd098.ycy", "dfs98732hj", "svd987kjsad", "XXsfdsdf2342", "YYstfbdtrdf76"};
+#define UPLOAD_NUM 14
+#define UPLOAD_LATEST_FILES 12
+// file we'll upload some new attributes for
+#define UPLOAD_ATTRS_EN 5
+#define UPLOAD_DELETE_EN 13
+// file which will be moved (as well as it's old version)
+#define UPLOAD_FILE_TO_MOVE 8
+
+
+// Nice random data for testing written files
+class R250 {
+public:
+ // Set up internal state table with 32-bit random numbers.
+ // The bizarre bit-twiddling is because rand() returns 16 bits of which
+ // the bottom bit is always zero! Hence, I use only some of the bits.
+ // You might want to do something better than this....
+
+ R250(int seed) : posn1(0), posn2(103)
+ {
+ // populate the state and incr tables
+ srand(seed);
+
+ for (int i = 0; i != stateLen; ++i) {
+ state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2);
+ incrTable[i] = i == stateLen - 1 ? 0 : i + 1;
+ }
+
+ // stir up the numbers to ensure they're random
+
+ for (int j = 0; j != stateLen * 4; ++j)
+ (void) next();
+ }
+
+ // Returns the next random number. Xor together two elements separated
+ // by 103 mod 250, replacing the first element with the result. Then
+ // increment the two indices mod 250.
+ inline int next()
+ {
+ int ret = (state[posn1] ^= state[posn2]); // xor and replace element
+
+ posn1 = incrTable[posn1]; // increment indices using lookup table
+ posn2 = incrTable[posn2];
+
+ return ret;
+ }
+private:
+ enum { stateLen = 250 }; // length of the state table
+ int state[stateLen]; // holds the random number state
+ int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen
+ int posn1, posn2; // indices into the state table
+};
+
+
+int SkipEntries(int e, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet)
+{
+ if(e >= DIR_NUM) return e;
+
+ bool skip = false;
+ do
+ {
+ skip = false;
+
+ if(FlagsMustBeSet != BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING)
+ {
+ if((ens[e].flags & FlagsMustBeSet) != FlagsMustBeSet)
+ {
+ skip = true;
+ }
+ }
+ if((ens[e].flags & FlagsNotToBeSet) != 0)
+ {
+ skip = true;
+ }
+
+ if(skip)
+ {
+ ++e;
+ }
+ } while(skip && e < DIR_NUM);
+
+ return e;
+}
+
+void CheckEntries(BackupStoreDirectory &rDir, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet)
+{
+ int e = 0;
+
+ BackupStoreDirectory::Iterator i(rDir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ TEST_THAT(e < DIR_NUM);
+
+ // Skip to entry in the ens array which matches
+ e = SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet);
+
+ // Does it match?
+ TEST_THAT(en->GetName() == ens[e].fn && en->GetModificationTime() == ens[e].mod && en->GetObjectID() == ens[e].id && en->GetFlags() == ens[e].flags && en->GetSizeInBlocks() == ens[e].size);
+
+ // next
+ ++e;
+ }
+
+ // Got them all?
+ TEST_THAT(en == 0);
+ TEST_THAT(DIR_NUM == SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet));
+}
+
+int test1(int argc, const char *argv[])
+{
+ // Initialise the raid file controller
+ RaidFileController &rcontroller = RaidFileController::GetController();
+ rcontroller.Initialise("testfiles/raidfile.conf");
+
+ // test some basics -- encoding and decoding filenames
+ {
+ // Make some filenames in various ways
+ BackupStoreFilenameClear fn1;
+ fn1.SetClearFilename(std::string("filenameXYZ"));
+ BackupStoreFilenameClear fn2(std::string("filenameXYZ"));
+ BackupStoreFilenameClear fn3(fn1);
+ TEST_THAT(fn1 == fn2);
+ TEST_THAT(fn1 == fn3);
+
+ // Check that it's been encrypted
+ std::string name(fn2.GetEncodedFilename());
+ TEST_THAT(name.find("name") == name.npos);
+
+ // Bung it in a stream, get it out in a Clear filename
+ {
+ CollectInBufferStream stream;
+ fn1.WriteToStream(stream);
+ stream.SetForReading();
+ BackupStoreFilenameClear fn4;
+ fn4.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(fn4.GetClearFilename() == "filenameXYZ");
+ TEST_THAT(fn4 == fn1);
+ }
+ // Bung it in a stream, get it out in a server non-Clear filename (two of them into the same var)
+ {
+ BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf ");
+ CollectInBufferStream stream;
+ fn1.WriteToStream(stream);
+ fno.WriteToStream(stream);
+ stream.SetForReading();
+ BackupStoreFilename fn5;
+ fn5.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(fn5 == fn1);
+ fn5.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(fn5 == fno);
+ }
+ // Same again with clear strings
+ {
+ BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf ");
+ CollectInBufferStream stream;
+ fn1.WriteToStream(stream);
+ fno.WriteToStream(stream);
+ stream.SetForReading();
+ BackupStoreFilenameClear fn5;
+ fn5.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(fn5.GetClearFilename() == "filenameXYZ");
+ fn5.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(fn5.GetClearFilename() == "pinglet dksfnsf jksjdf ");
+ }
+ // Test a very big filename
+ {
+ const char *fnr = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789";
+ BackupStoreFilenameClear fnLong(fnr);
+ CollectInBufferStream stream;
+ fnLong.WriteToStream(stream);
+ stream.SetForReading();
+ BackupStoreFilenameClear fn9;
+ fn9.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(fn9.GetClearFilename() == fnr);
+ TEST_THAT(fn9 == fnLong);
+ }
+ // Test a filename which went wrong once
+ {
+ BackupStoreFilenameClear dodgy("content-negotiation.html");
+ }
+ }
+ return 0;
+}
+
+int test2(int argc, const char *argv[])
+{
+ {
+ // Now play with directories
+
+ // Fill in...
+ BackupStoreDirectory dir1(12, 98);
+ for(int e = 0; e < DIR_NUM; ++e)
+ {
+ dir1.AddEntry(ens[e].fn, ens[e].mod, ens[e].id, ens[e].size, ens[e].flags, ens[e].attrmod);
+ }
+ // Got the right number
+ TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM);
+
+ // Stick it into a stream and get it out again
+ {
+ CollectInBufferStream stream;
+ dir1.WriteToStream(stream);
+ stream.SetForReading();
+ BackupStoreDirectory dir2;
+ dir2.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir2.GetNumberOfEntries() == DIR_NUM);
+ TEST_THAT(dir2.GetObjectID() == 12);
+ TEST_THAT(dir2.GetContainerID() == 98);
+ CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING);
+ }
+
+ // Then do selective writes and reads
+ {
+ CollectInBufferStream stream;
+ dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File);
+ stream.SetForReading();
+ BackupStoreDirectory dir2;
+ dir2.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES);
+ CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING);
+ }
+ {
+ CollectInBufferStream stream;
+ dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_File);
+ stream.SetForReading();
+ BackupStoreDirectory dir2;
+ dir2.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir2.GetNumberOfEntries() == DIR_DIRS);
+ CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_Dir, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING);
+ }
+ {
+ CollectInBufferStream stream;
+ dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion);
+ stream.SetForReading();
+ BackupStoreDirectory dir2;
+ dir2.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - DIR_OLD);
+ CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion);
+ }
+
+ // Finally test deleting items
+ {
+ dir1.DeleteEntry(12312312321LL);
+ // Verify
+ TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM - 1);
+ CollectInBufferStream stream;
+ dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File);
+ stream.SetForReading();
+ BackupStoreDirectory dir2;
+ dir2.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - 1);
+ }
+
+ // Check attributes
+ {
+ int attrI[4] = {1, 2, 3, 4};
+ StreamableMemBlock attr(attrI, sizeof(attrI));
+ BackupStoreDirectory d1(16, 546);
+ d1.SetAttributes(attr, 56234987324232LL);
+ TEST_THAT(d1.GetAttributes() == attr);
+ TEST_THAT(d1.GetAttributesModTime() == 56234987324232LL);
+ CollectInBufferStream stream;
+ d1.WriteToStream(stream);
+ stream.SetForReading();
+ BackupStoreDirectory d2;
+ d2.ReadFromStream(stream, IOStream::TimeOutInfinite);
+ TEST_THAT(d2.GetAttributes() == attr);
+ TEST_THAT(d2.GetAttributesModTime() == 56234987324232LL);
+ }
+ }
+ return 0;
+}
+
+void write_test_file(int t)
+{
+ std::string filename("testfiles/test");
+ filename += uploads[t].fnextra;
+ printf("%s\n", filename.c_str());
+
+ FileStream write(filename.c_str(), O_WRONLY | O_CREAT);
+
+ R250 r(uploads[t].seed);
+
+ unsigned char *data = (unsigned char*)malloc(uploads[t].size);
+ for(int l = 0; l < uploads[t].size; ++l)
+ {
+ data[l] = r.next() & 0xff;
+ }
+ write.Write(data, uploads[t].size);
+
+ free(data);
+}
+
+void test_test_file(int t, IOStream &rStream)
+{
+ // Decode to a file
+ BackupStoreFile::DecodeFile(rStream, "testfiles/test_download", IOStream::TimeOutInfinite);
+
+ // Compare...
+ FileStream in("testfiles/test_download");
+ TEST_THAT(in.BytesLeftToRead() == uploads[t].size);
+
+ R250 r(uploads[t].seed);
+
+ unsigned char *data = (unsigned char*)malloc(uploads[t].size);
+ TEST_THAT(in.ReadFullBuffer(data, uploads[t].size, 0 /* not interested in bytes read if this fails */));
+
+ for(int l = 0; l < uploads[t].size; ++l)
+ {
+ TEST_THAT(data[l] == (r.next() & 0xff));
+ }
+
+ free(data);
+ in.Close();
+ TEST_THAT(unlink("testfiles/test_download") == 0);
+}
+
+void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)
+{
+ printf("Test for del: %llx\n", DirID);
+
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
+ DirID,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ int files = 0;
+ int dirs = 0;
+ while((en = i.Next()) != 0)
+ {
+ if(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir)
+ {
+ dirs++;
+ // Recurse
+ test_everything_deleted(protocol, en->GetObjectID());
+ }
+ else
+ {
+ files++;
+ }
+ // Check it's deleted
+ TEST_THAT(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Deleted);
+ }
+
+ // Check there were the right number of files and directories
+ TEST_THAT(files == 3);
+ TEST_THAT(dirs == 0 || dirs == 2);
+}
+
+std::vector<uint32_t> ExpectedRefCounts;
+
+void set_refcount(int64_t ObjectID, uint32_t RefCount = 1)
+{
+ if (ExpectedRefCounts.size() <= ObjectID);
+ {
+ ExpectedRefCounts.resize(ObjectID + 1, 0);
+ }
+ ExpectedRefCounts[ObjectID] = RefCount;
+}
+
+void create_file_in_dir(std::string name, std::string source, int64_t parentId,
+ BackupProtocolClient &protocol, BackupStoreRefCountDatabase& rRefCount)
+{
+ BackupStoreFilenameClear name_encoded("file_One");
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(
+ source.c_str(), parentId, name_encoded));
+ std::auto_ptr<BackupProtocolClientSuccess> stored(
+ protocol.QueryStoreFile(
+ parentId,
+ 0x123456789abcdefLL, /* modification time */
+ 0x7362383249872dfLL, /* attr hash */
+ 0, /* diff from ID */
+ name_encoded,
+ *upload));
+ int64_t objectId = stored->GetObjectID();
+ TEST_EQUAL(objectId, rRefCount.GetLastObjectIDUsed());
+ TEST_EQUAL(1, rRefCount.GetRefCount(objectId))
+ set_refcount(objectId, 1);
+}
+
+int64_t create_test_data_subdirs(BackupProtocolClient &protocol, int64_t indir,
+ const char *name, int depth, BackupStoreRefCountDatabase& rRefCount)
+{
+ // Create a directory
+ int64_t subdirid = 0;
+ BackupStoreFilenameClear dirname(name);
+ {
+ // Create with dummy attributes
+ int attrS = 0;
+ MemBlockStream attr(&attrS, sizeof(attrS));
+ std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
+ indir,
+ 9837429842987984LL, dirname, attr));
+ subdirid = dirCreate->GetObjectID();
+ }
+
+ printf("Create subdirs, depth = %d, dirid = %llx\n", depth, subdirid);
+
+ TEST_EQUAL(subdirid, rRefCount.GetLastObjectIDUsed());
+ TEST_EQUAL(1, rRefCount.GetRefCount(subdirid))
+ set_refcount(subdirid, 1);
+
+ // Put more directories in it, if we haven't gone down too far
+ if(depth > 0)
+ {
+ create_test_data_subdirs(protocol, subdirid, "dir_One",
+ depth - 1, rRefCount);
+ create_test_data_subdirs(protocol, subdirid, "dir_Two",
+ depth - 1, rRefCount);
+ }
+
+ // Stick some files in it
+ create_file_in_dir("file_One", "testfiles/file1", subdirid, protocol,
+ rRefCount);
+ create_file_in_dir("file_Two", "testfiles/file1", subdirid, protocol,
+ rRefCount);
+ create_file_in_dir("file_Three", "testfiles/file1", subdirid, protocol,
+ rRefCount);
+ return subdirid;
+}
+
+
+void check_dir_after_uploads(BackupProtocolClient &protocol, const StreamableMemBlock &Attributes)
+{
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ TEST_THAT(dirreply->GetObjectID() == BackupProtocolClientListDirectory::RootDirectory);
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == UPLOAD_NUM + 1 /* for the first test file */);
+ TEST_THAT(!dir.HasAttributes());
+
+ // Check them!
+ BackupStoreDirectory::Iterator i(dir);
+ // Discard first
+ BackupStoreDirectory::Entry *en = i.Next();
+ TEST_THAT(en != 0);
+
+ for(int t = 0; t < UPLOAD_NUM; ++t)
+ {
+ en = i.Next();
+ TEST_THAT(en != 0);
+ TEST_THAT(en->GetName() == uploads[t].name);
+ TEST_THAT(en->GetObjectID() == uploads[t].allocated_objid);
+ TEST_THAT(en->GetModificationTime() == uploads[t].mod_time);
+ int correct_flags = BackupProtocolClientListDirectory::Flags_File;
+ if(uploads[t].should_be_old_version) correct_flags |= BackupProtocolClientListDirectory::Flags_OldVersion;
+ if(uploads[t].delete_file) correct_flags |= BackupProtocolClientListDirectory::Flags_Deleted;
+ TEST_THAT(en->GetFlags() == correct_flags);
+ if(t == UPLOAD_ATTRS_EN)
+ {
+ TEST_THAT(en->HasAttributes());
+ TEST_THAT(en->GetAttributesHash() == 32498749832475LL);
+ TEST_THAT(en->GetAttributes() == Attributes);
+ }
+ else
+ {
+ // No attributes on this one
+ TEST_THAT(!en->HasAttributes());
+ }
+ }
+ en = i.Next();
+ TEST_THAT(en == 0);
+}
+
+
+typedef struct
+{
+ int objectsNotDel;
+ int deleted;
+ int old;
+} recursive_count_objects_results;
+
+void recursive_count_objects_r(BackupProtocolClient &protocol, int64_t id, recursive_count_objects_results &results)
+{
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
+ id,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+
+ // Check them!
+ BackupStoreDirectory::Iterator i(dir);
+ // Discard first
+ BackupStoreDirectory::Entry *en = 0;
+
+ while((en = i.Next()) != 0)
+ {
+ if((en->GetFlags() & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) == 0) results.objectsNotDel++;
+ if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) results.deleted++;
+ if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) results.old++;
+
+ if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir)
+ {
+ recursive_count_objects_r(protocol, en->GetObjectID(), results);
+ }
+ }
+}
+
+void recursive_count_objects(const char *hostname, int64_t id, recursive_count_objects_results &results)
+{
+ // Context
+ TLSContext context;
+ context.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+
+ // Get a connection
+ SocketStreamTLS connReadOnly;
+ connReadOnly.Open(context, Socket::TypeINET, hostname,
+ BOX_PORT_BBSTORED_TEST);
+ BackupProtocolClient protocolReadOnly(connReadOnly);
+
+ {
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
+ }
+
+ // Count objects
+ recursive_count_objects_r(protocolReadOnly, id, results);
+
+ // Close it
+ protocolReadOnly.QueryFinished();
+}
+
+bool check_block_index(const char *encoded_file, IOStream &rBlockIndex)
+{
+ // Open file, and move to the right position
+ FileStream enc(encoded_file);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(enc);
+
+ bool same = true;
+
+ // Now compare the two...
+ while(enc.StreamDataLeft())
+ {
+ char buffer1[2048];
+ char buffer2[2048];
+ int s = enc.Read(buffer1, sizeof(buffer1));
+ if(rBlockIndex.Read(buffer2, s) != s)
+ {
+ same = false;
+ break;
+ }
+ if(::memcmp(buffer1, buffer2, s) != 0)
+ {
+ same = false;
+ break;
+ }
+ }
+
+ if(rBlockIndex.StreamDataLeft())
+ {
+ same = false;
+
+ // Absorb all this excess data so procotol is in the first state
+ char buffer[2048];
+ while(rBlockIndex.StreamDataLeft())
+ {
+ rBlockIndex.Read(buffer, sizeof(buffer));
+ }
+ }
+
+ return same;
+}
+
+bool check_files_same(const char *f1, const char *f2)
+{
+ // Open file, and move to the right position
+ FileStream f1s(f1);
+ FileStream f2s(f2);
+
+ bool same = true;
+
+ // Now compare the two...
+ while(f1s.StreamDataLeft())
+ {
+ char buffer1[2048];
+ char buffer2[2048];
+ int s = f1s.Read(buffer1, sizeof(buffer1));
+ if(f2s.Read(buffer2, s) != s)
+ {
+ same = false;
+ break;
+ }
+ if(::memcmp(buffer1, buffer2, s) != 0)
+ {
+ same = false;
+ break;
+ }
+ }
+
+ if(f2s.StreamDataLeft())
+ {
+ same = false;
+ }
+
+ return same;
+}
+
+
+void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protocolReadOnly)
+{
+ int encfile[ENCFILE_SIZE];
+ {
+ for(int l = 0; l < ENCFILE_SIZE; ++l)
+ {
+ encfile[l] = l * 173;
+ }
+
+ // Write this to a file
+ {
+ FileStream f("testfiles/file1", O_WRONLY | O_CREAT | O_EXCL);
+ f.Write(encfile, sizeof(encfile));
+ }
+
+ }
+
+ // Read the root directory a few times (as it's cached, so make sure it doesn't hurt anything)
+ for(int l = 0; l < 3; ++l)
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == 0);
+ }
+
+ // Read the dir from the readonly connection (make sure it gets in the cache)
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == 0);
+ }
+
+ // Store a file -- first make the encoded file
+ BackupStoreFilenameClear store1name("testfiles/file1");
+ {
+ FileStream out("testfiles/file1_upload1", O_WRONLY | O_CREAT | O_EXCL);
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/file1", BackupProtocolClientListDirectory::RootDirectory, store1name));
+ encoded->CopyStreamTo(out);
+ }
+
+// printf("SKIPPING\n");
+// goto skip; {
+ // Then send it
+ int64_t store1objid = 0;
+ {
+ FileStream upload("testfiles/file1_upload1");
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolClientListDirectory::RootDirectory,
+ 0x123456789abcdefLL, /* modification time */
+ 0x7362383249872dfLL, /* attr hash */
+ 0, /* diff from ID */
+ store1name,
+ upload));
+ store1objid = stored->GetObjectID();
+ TEST_THAT(store1objid == 2);
+ }
+ set_refcount(store1objid, 1);
+ // And retrieve it
+ {
+ // Retrieve as object
+ std::auto_ptr<BackupProtocolClientSuccess> getfile(protocol.QueryGetObject(store1objid));
+ TEST_THAT(getfile->GetObjectID() == store1objid);
+ // BLOCK
+ {
+ // Get stream
+ std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
+ // Need to put it in another stream, because it's not in stream order
+ CollectInBufferStream f;
+ filestream->CopyStreamTo(f);
+ f.SetForReading();
+ // Get and decode
+ BackupStoreFile::DecodeFile(f, "testfiles/file1_upload_retrieved", IOStream::TimeOutInfinite);
+ }
+
+ // Retrieve as file
+ std::auto_ptr<BackupProtocolClientSuccess> getobj(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, store1objid));
+ TEST_THAT(getobj->GetObjectID() == store1objid);
+ // BLOCK
+ {
+ // Get stream
+ std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
+ // Get and decode
+ BackupStoreFile::DecodeFile(*filestream, "testfiles/file1_upload_retrieved_str", IOStream::TimeOutInfinite);
+ }
+
+ // Read in rebuilt original, and compare contents
+ {
+ FileStream in("testfiles/file1_upload_retrieved");
+ int encfile_i[ENCFILE_SIZE];
+ in.Read(encfile_i, sizeof(encfile_i));
+ TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0);
+ }
+ {
+ FileStream in("testfiles/file1_upload_retrieved_str");
+ int encfile_i[ENCFILE_SIZE];
+ in.Read(encfile_i, sizeof(encfile_i));
+ TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0);
+ }
+
+ // Retrieve the block index, by ID
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByID(store1objid));
+ TEST_THAT(getblockindex->GetObjectID() == store1objid);
+ std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+ // Check against uploaded file
+ TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream));
+ }
+ // and again, by name
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByName(BackupProtocolClientListDirectory::RootDirectory, store1name));
+ TEST_THAT(getblockindex->GetObjectID() == store1objid);
+ std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+ // Check against uploaded file
+ TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream));
+ }
+ }
+ // Get the directory again, and see if the entry is in it
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == 1);
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next();
+ TEST_THAT(en != 0);
+ TEST_THAT(i.Next() == 0);
+ if(en != 0)
+ {
+ TEST_THAT(en->GetName() == store1name);
+ TEST_THAT(en->GetModificationTime() == 0x123456789abcdefLL);
+ TEST_THAT(en->GetAttributesHash() == 0x7362383249872dfLL);
+ TEST_THAT(en->GetObjectID() == store1objid);
+ TEST_THAT(en->GetSizeInBlocks() < ((ENCFILE_SIZE * 4 * 3) / 2 / 2048)+2);
+ TEST_THAT(en->GetFlags() == BackupStoreDirectory::Entry::Flags_File);
+ }
+ }
+
+ // Try using GetFile on a directory
+ {
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientSuccess> getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::RootDirectory)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+ }
+}
+
+void init_context(TLSContext& rContext)
+{
+ rContext.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+}
+
+std::auto_ptr<SocketStreamTLS> open_conn(const char *hostname,
+ TLSContext& rContext)
+{
+ init_context(rContext);
+ std::auto_ptr<SocketStreamTLS> conn(new SocketStreamTLS);
+ conn->Open(rContext, Socket::TypeINET, hostname,
+ BOX_PORT_BBSTORED_TEST);
+ return conn;
+}
+
+std::auto_ptr<BackupProtocolClient> test_server_login(SocketStreamTLS& rConn)
+{
+ // Make a protocol
+ std::auto_ptr<BackupProtocolClient> protocol(new
+ BackupProtocolClient(rConn));
+
+ // Check the version
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(
+ protocol->QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(
+ protocol->QueryLogin(0x01234567, 0));
+
+ return protocol;
+}
+
+void run_housekeeping(BackupStoreAccountDatabase::Entry& rAccount)
+{
+ std::string rootDir = BackupStoreAccounts::GetAccountRoot(rAccount);
+ int discSet = rAccount.GetDiscSet();
+
+ // Do housekeeping on this account
+ HousekeepStoreAccount housekeeping(rAccount.GetID(), rootDir,
+ discSet, NULL);
+ housekeeping.DoHousekeeping(true /* keep trying forever */);
+}
+
+int test_server(const char *hostname)
+{
+ TLSContext context;
+ std::auto_ptr<SocketStreamTLS> conn = open_conn(hostname, context);
+ std::auto_ptr<BackupProtocolClient> apProtocol(
+ test_server_login(*conn));
+ BackupProtocolClient& protocol(*apProtocol);
+
+ // Make some test attributes
+ #define ATTR1_SIZE 245
+ #define ATTR2_SIZE 23
+ #define ATTR3_SIZE 122
+ int attr1[ATTR1_SIZE];
+ int attr2[ATTR2_SIZE];
+ int attr3[ATTR3_SIZE];
+ {
+ R250 r(3465657);
+ for(int l = 0; l < ATTR1_SIZE; ++l) {attr1[l] = r.next();}
+ for(int l = 0; l < ATTR2_SIZE; ++l) {attr2[l] = r.next();}
+ for(int l = 0; l < ATTR3_SIZE; ++l) {attr3[l] = r.next();}
+ }
+
+ // BLOCK
+ {
+ // Get it logging
+ FILE *protocolLog = ::fopen("testfiles/protocol.log", "w");
+ TEST_THAT(protocolLog != 0);
+ protocol.SetLogToFile(protocolLog);
+
+#ifndef WIN32
+ // Check that we can't open a new connection which requests write permissions
+ {
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, hostname,
+ BOX_PORT_BBSTORED_TEST);
+ BackupProtocolClient protocol(conn);
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+ protocol.QueryFinished();
+ }
+#endif
+
+ // Set the client store marker
+ protocol.QuerySetClientStoreMarker(0x8732523ab23aLL);
+
+#ifndef WIN32
+ // Open a new connection which is read only
+ SocketStreamTLS connReadOnly;
+ connReadOnly.Open(context, Socket::TypeINET, hostname,
+ BOX_PORT_BBSTORED_TEST);
+ BackupProtocolClient protocolReadOnly(connReadOnly);
+
+ // Get it logging
+ FILE *protocolReadOnlyLog = ::fopen("testfiles/protocolReadOnly.log", "w");
+ TEST_THAT(protocolReadOnlyLog != 0);
+ protocolReadOnly.SetLogToFile(protocolReadOnlyLog);
+
+ {
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
+
+ // Check client store marker
+ TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL);
+ }
+#else // WIN32
+ BackupProtocolClient& protocolReadOnly(protocol);
+#endif
+
+ test_server_1(protocol, protocolReadOnly);
+
+ // sleep to ensure that the timestamp on the file will change
+ ::safe_sleep(1);
+
+ // Create and upload some test files
+ int64_t maxID = 0;
+ for(int t = 0; t < UPLOAD_NUM; ++t)
+ {
+ write_test_file(t);
+
+ std::string filename("testfiles/test");
+ filename += uploads[t].fnextra;
+ int64_t modtime = 0;
+
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(filename.c_str(), BackupProtocolClientListDirectory::RootDirectory, uploads[t].name, &modtime));
+ TEST_THAT(modtime != 0);
+
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolClientListDirectory::RootDirectory,
+ modtime,
+ modtime, /* use it for attr hash too */
+ 0, /* diff from ID */
+ uploads[t].name,
+ *upload));
+ uploads[t].allocated_objid = stored->GetObjectID();
+ uploads[t].mod_time = modtime;
+ if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID();
+ set_refcount(stored->GetObjectID(), 1);
+ BOX_TRACE("wrote file " << filename << " to server "
+ "as object " <<
+ BOX_FORMAT_OBJECTID(stored->GetObjectID()));
+ }
+
+ // Add some attributes onto one of them
+ {
+ MemBlockStream attrnew(attr3, sizeof(attr3));
+ std::auto_ptr<BackupProtocolClientSuccess> set(protocol.QuerySetReplacementFileAttributes(
+ BackupProtocolClientListDirectory::RootDirectory,
+ 32498749832475LL,
+ uploads[UPLOAD_ATTRS_EN].name,
+ attrnew));
+ TEST_THAT(set->GetObjectID() == uploads[UPLOAD_ATTRS_EN].allocated_objid);
+ }
+
+ // Delete one of them (will implicitly delete an old version)
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> del(protocol.QueryDeleteFile(
+ BackupProtocolClientListDirectory::RootDirectory,
+ uploads[UPLOAD_DELETE_EN].name));
+ TEST_THAT(del->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid);
+ }
+ // Check that the block index can be obtained by name even though it's been deleted
+ {
+ // Fetch the raw object
+ {
+ FileStream out("testfiles/downloaddelobj", O_WRONLY | O_CREAT);
+ std::auto_ptr<BackupProtocolClientSuccess> getobj(protocol.QueryGetObject(uploads[UPLOAD_DELETE_EN].allocated_objid));
+ std::auto_ptr<IOStream> objstream(protocol.ReceiveStream());
+ objstream->CopyStreamTo(out);
+ }
+ // query index and test
+ std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByName(
+ BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_DELETE_EN].name));
+ TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid);
+ std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+ TEST_THAT(check_block_index("testfiles/downloaddelobj", *blockIndexStream));
+ }
+
+ // Download them all... (even deleted files)
+ for(int t = 0; t < UPLOAD_NUM; ++t)
+ {
+ printf("%d\n", t);
+ std::auto_ptr<BackupProtocolClientSuccess> getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, uploads[t].allocated_objid));
+ TEST_THAT(getFile->GetObjectID() == uploads[t].allocated_objid);
+ std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
+ test_test_file(t, *filestream);
+ }
+
+ {
+ StreamableMemBlock attrtest(attr3, sizeof(attr3));
+
+ // Use the read only connection to verify that the directory is as we expect
+ printf("\n\n==== Reading directory using read-only connection\n");
+ check_dir_after_uploads(protocolReadOnly, attrtest);
+ printf("done.\n\n");
+ // And on the read/write one
+ check_dir_after_uploads(protocol, attrtest);
+ }
+
+ // sleep to ensure that the timestamp on the file will change
+ ::safe_sleep(1);
+
+ // Check diffing and rsync like stuff...
+ // Build a modified file
+ {
+ // Basically just insert a bit in the middle
+ TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING) == TEST_FILE_FOR_PATCHING_SIZE);
+ FileStream in(TEST_FILE_FOR_PATCHING);
+ void *buf = ::malloc(TEST_FILE_FOR_PATCHING_SIZE);
+ FileStream out(TEST_FILE_FOR_PATCHING ".mod", O_WRONLY | O_CREAT | O_EXCL);
+ TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_PATCH_AT);
+ out.Write(buf, TEST_FILE_FOR_PATCHING_PATCH_AT);
+ char insert[13] = "INSERTINSERT";
+ out.Write(insert, sizeof(insert));
+ TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT);
+ out.Write(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT);
+ ::free(buf);
+ }
+ {
+ // Fetch the block index for this one
+ std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByName(
+ BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_PATCH_EN].name));
+ TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_PATCH_EN].allocated_objid);
+ std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+
+ // Do the patching
+ bool isCompletelyDifferent = false;
+ int64_t modtime;
+ std::auto_ptr<IOStream> patchstream(
+ BackupStoreFile::EncodeFileDiff(
+ TEST_FILE_FOR_PATCHING ".mod",
+ BackupProtocolClientListDirectory::RootDirectory,
+ uploads[UPLOAD_PATCH_EN].name,
+ uploads[UPLOAD_PATCH_EN].allocated_objid,
+ *blockIndexStream,
+ IOStream::TimeOutInfinite,
+ NULL, // pointer to DiffTimer impl
+ &modtime, &isCompletelyDifferent));
+ TEST_THAT(isCompletelyDifferent == false);
+ // Sent this to a file, so we can check the size, rather than uploading it directly
+ {
+ FileStream patch(TEST_FILE_FOR_PATCHING ".patch", O_WRONLY | O_CREAT | O_EXCL);
+ patchstream->CopyStreamTo(patch);
+ }
+ // Make sure the stream is a plausible size for a patch containing only one new block
+ TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING ".patch") < (8*1024));
+ // Upload it
+ int64_t patchedID = 0;
+ {
+ FileStream uploadpatch(TEST_FILE_FOR_PATCHING ".patch");
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolClientListDirectory::RootDirectory,
+ modtime,
+ modtime, /* use it for attr hash too */
+ uploads[UPLOAD_PATCH_EN].allocated_objid, /* diff from ID */
+ uploads[UPLOAD_PATCH_EN].name,
+ uploadpatch));
+ TEST_THAT(stored->GetObjectID() > 0);
+ if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID();
+ patchedID = stored->GetObjectID();
+ }
+
+ set_refcount(patchedID, 1);
+
+ // Then download it to check it's OK
+ std::auto_ptr<BackupProtocolClientSuccess> getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, patchedID));
+ TEST_THAT(getFile->GetObjectID() == patchedID);
+ std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
+ BackupStoreFile::DecodeFile(*filestream, TEST_FILE_FOR_PATCHING ".downloaded", IOStream::TimeOutInfinite);
+ // Check it's the same
+ TEST_THAT(check_files_same(TEST_FILE_FOR_PATCHING ".downloaded", TEST_FILE_FOR_PATCHING ".mod"));
+ }
+
+ // Create a directory
+ int64_t subdirid = 0;
+ BackupStoreFilenameClear dirname("lovely_directory");
+ {
+ // Attributes
+ MemBlockStream attr(attr1, sizeof(attr1));
+ std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ 9837429842987984LL, dirname, attr));
+ subdirid = dirCreate->GetObjectID();
+ TEST_THAT(subdirid == maxID + 1);
+ }
+
+ set_refcount(subdirid, 1);
+
+ // Stick a file in it
+ int64_t subdirfileid = 0;
+ {
+ std::string filename("testfiles/test0");
+ int64_t modtime;
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(filename.c_str(), subdirid, uploads[0].name, &modtime));
+
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ subdirid,
+ modtime,
+ modtime, /* use for attr hash too */
+ 0, /* diff from ID */
+ uploads[0].name,
+ *upload));
+ subdirfileid = stored->GetObjectID();
+ }
+
+ set_refcount(subdirfileid, 1);
+
+ printf("\n==== Checking upload using read-only connection\n");
+ // Check the directories on the read only connection
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == UPLOAD_NUM + 3 /* for the first test file, the patched upload, and this new dir */);
+
+ // Check the last one...
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ BackupStoreDirectory::Entry *t = 0;
+ while((t = i.Next()) != 0)
+ {
+ if(en != 0)
+ {
+ // here for all but last object
+ TEST_THAT(en->GetObjectID() != subdirid);
+ TEST_THAT(en->GetName() != dirname);
+ }
+ en = t;
+ }
+ // Does it look right?
+ TEST_THAT(en->GetName() == dirname);
+ TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir);
+ TEST_THAT(en->GetObjectID() == subdirid);
+ TEST_THAT(en->GetModificationTime() == 0); // dirs don't have modification times.
+ }
+
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, true /* get attributes */));
+ TEST_THAT(dirreply->GetObjectID() == subdirid);
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == 1);
+
+ // Check the last one...
+ BackupStoreDirectory::Iterator i(dir);
+ // Discard first
+ BackupStoreDirectory::Entry *en = i.Next();
+ TEST_THAT(en != 0);
+ // Does it look right?
+ TEST_THAT(en->GetName() == uploads[0].name);
+ TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_File);
+ TEST_THAT(en->GetObjectID() == subdirfileid);
+ TEST_THAT(en->GetModificationTime() != 0);
+
+ // Attributes
+ TEST_THAT(dir.HasAttributes());
+ TEST_THAT(dir.GetAttributesModTime() == 9837429842987984LL);
+ StreamableMemBlock attr(attr1, sizeof(attr1));
+ TEST_THAT(dir.GetAttributes() == attr);
+ }
+ printf("done.\n\n");
+
+ // Check that we don't get attributes if we don't ask for them
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(!dir.HasAttributes());
+ }
+
+ // sleep to ensure that the timestamp on the file will change
+ ::safe_sleep(1);
+
+ // Change attributes on the directory
+ {
+ MemBlockStream attrnew(attr2, sizeof(attr2));
+ std::auto_ptr<BackupProtocolClientSuccess> changereply(protocol.QueryChangeDirAttributes(
+ subdirid,
+ 329483209443598LL,
+ attrnew));
+ TEST_THAT(changereply->GetObjectID() == subdirid);
+ }
+ // Check the new attributes
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ 0, // no flags
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_EVERYTHING, true /* get attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.GetNumberOfEntries() == 0);
+
+ // Attributes
+ TEST_THAT(dir.HasAttributes());
+ TEST_THAT(dir.GetAttributesModTime() == 329483209443598LL);
+ StreamableMemBlock attrtest(attr2, sizeof(attr2));
+ TEST_THAT(dir.GetAttributes() == attrtest);
+ }
+
+ // sleep to ensure that the timestamp on the file will change
+ ::safe_sleep(1);
+
+ // Test moving a file
+ {
+ BackupStoreFilenameClear newName("moved-files");
+
+ std::auto_ptr<BackupProtocolClientSuccess> rep(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ BackupProtocolClientListDirectory::RootDirectory,
+ subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName));
+ TEST_THAT(rep->GetObjectID() == uploads[UPLOAD_FILE_TO_MOVE].allocated_objid);
+ }
+
+ // Try some dodgy renames
+ {
+ BackupStoreFilenameClear newName("moved-files");
+ TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ BackupProtocolClientListDirectory::RootDirectory,
+ subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+ TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ subdirid,
+ subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+ }
+
+ // Rename within a directory
+ {
+ BackupStoreFilenameClear newName("moved-files-x");
+ protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ subdirid,
+ subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName);
+ }
+
+ // Check it's all gone from the root directory...
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ // Read all entries
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ TEST_THAT(en->GetName() != uploads[UPLOAD_FILE_TO_MOVE].name);
+ }
+ }
+
+ // Check the old and new versions are in the other directory
+ {
+ BackupStoreFilenameClear lookFor("moved-files-x");
+
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+ // Check entries
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ bool foundCurrent = false;
+ bool foundOld = false;
+ while((en = i.Next()) != 0)
+ {
+ if(en->GetName() == lookFor)
+ {
+ if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File)) foundCurrent = true;
+ if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion)) foundOld = true;
+ }
+ }
+ TEST_THAT(foundCurrent);
+ TEST_THAT(foundOld);
+ }
+
+ // sleep to ensure that the timestamp on the file will change
+ ::safe_sleep(1);
+
+ // make a little bit more of a thing to look at
+ int64_t subsubdirid = 0;
+ int64_t subsubfileid = 0;
+ {
+ BackupStoreFilenameClear nd("sub2");
+ // Attributes
+ MemBlockStream attr(attr1, sizeof(attr1));
+ std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
+ subdirid,
+ 9837429842987984LL, nd, attr));
+ subsubdirid = dirCreate->GetObjectID();
+
+ FileStream upload("testfiles/file1_upload1");
+ BackupStoreFilenameClear nf("file2");
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ subsubdirid,
+ 0x123456789abcdefLL, /* modification time */
+ 0x7362383249872dfLL, /* attr hash */
+ 0, /* diff from ID */
+ nf,
+ upload));
+ subsubfileid = stored->GetObjectID();
+ }
+
+ set_refcount(subsubdirid, 1);
+ set_refcount(subsubfileid, 1);
+
+ // Query names -- test that invalid stuff returns not found OK
+ {
+ std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(3248972347823478927LL, subsubdirid));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+ {
+ std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(subsubfileid, 2342378424LL));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+ {
+ std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(38947234789LL, 2342378424LL));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+ {
+ std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, 2234342378424LL));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+
+ // Query names... first, get info for the file
+ {
+ std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(subsubfileid, subsubdirid));
+ std::auto_ptr<IOStream> namestream(protocol.ReceiveStream());
+
+ TEST_THAT(nameRep->GetNumNameElements() == 3);
+ TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_File);
+ TEST_THAT(nameRep->GetModificationTime() == 0x123456789abcdefLL);
+ TEST_THAT(nameRep->GetAttributesHash() == 0x7362383249872dfLL);
+ static const char *testnames[] = {"file2","sub2","lovely_directory"};
+ for(int l = 0; l < nameRep->GetNumNameElements(); ++l)
+ {
+ BackupStoreFilenameClear fn;
+ fn.ReadFromStream(*namestream, 10000);
+ TEST_THAT(fn.GetClearFilename() == testnames[l]);
+ }
+ }
+
+ // Query names... secondly, for the directory
+ {
+ std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, subsubdirid));
+ std::auto_ptr<IOStream> namestream(protocol.ReceiveStream());
+
+ TEST_THAT(nameRep->GetNumNameElements() == 2);
+ TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir);
+ static const char *testnames[] = {"sub2","lovely_directory"};
+ for(int l = 0; l < nameRep->GetNumNameElements(); ++l)
+ {
+ BackupStoreFilenameClear fn;
+ fn.ReadFromStream(*namestream, 10000);
+ TEST_THAT(fn.GetClearFilename() == testnames[l]);
+ }
+ }
+
+//} skip:
+
+ std::auto_ptr<BackupStoreAccountDatabase> apAccounts(
+ BackupStoreAccountDatabase::Read(
+ "testfiles/accounts.txt"));
+ std::auto_ptr<BackupStoreRefCountDatabase> apRefCount(
+ BackupStoreRefCountDatabase::Load(
+ apAccounts->GetEntry(0x1234567), true));
+
+ // Create some nice recursive directories
+ int64_t dirtodelete = create_test_data_subdirs(protocol,
+ BackupProtocolClientListDirectory::RootDirectory,
+ "test_delete", 6 /* depth */, *apRefCount);
+
+ // And delete them
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> dirdel(protocol.QueryDeleteDirectory(
+ dirtodelete));
+ TEST_THAT(dirdel->GetObjectID() == dirtodelete);
+ }
+
+ // Get the root dir, checking for deleted items
+ {
+ // Command
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_Dir | BackupProtocolClientListDirectory::Flags_Deleted,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+
+ // Check there's only that one entry
+ TEST_THAT(dir.GetNumberOfEntries() == 1);
+
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next();
+ TEST_THAT(en != 0);
+ if(en)
+ {
+ TEST_THAT(en->GetObjectID() == dirtodelete);
+ BackupStoreFilenameClear n("test_delete");
+ TEST_THAT(en->GetName() == n);
+ }
+
+ // Then... check everything's deleted
+ test_everything_deleted(protocolReadOnly, dirtodelete);
+ }
+
+ // Finish the connections
+#ifndef WIN32
+ protocolReadOnly.QueryFinished();
+#endif
+ protocol.QueryFinished();
+
+ // Close logs
+#ifndef WIN32
+ ::fclose(protocolReadOnlyLog);
+#endif
+ ::fclose(protocolLog);
+ }
+
+ return 0;
+}
+
+int test3(int argc, const char *argv[])
+{
+ // Now test encoded files
+ // TODO: This test needs to check failure situations as well as everything working,
+ // but this will be saved for the full implementation.
+ int encfile[ENCFILE_SIZE];
+ {
+ for(int l = 0; l < ENCFILE_SIZE; ++l)
+ {
+ encfile[l] = l * 173;
+ }
+
+ // Encode and decode a small block (shouldn't be compressed)
+ {
+ #define SMALL_BLOCK_SIZE 251
+ int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(SMALL_BLOCK_SIZE);
+ TEST_THAT(encBlockSize > SMALL_BLOCK_SIZE);
+ BackupStoreFile::EncodingBuffer encoded;
+ encoded.Allocate(encBlockSize / 8); // make sure reallocation happens
+
+ // Encode!
+ int encSize = BackupStoreFile::EncodeChunk(encfile, SMALL_BLOCK_SIZE, encoded);
+ // Check the header says it's not been compressed
+ TEST_THAT((encoded.mpBuffer[0] & 1) == 0);
+ // Check the output size has been inflated (no compression)
+ TEST_THAT(encSize > SMALL_BLOCK_SIZE);
+
+ // Decode it
+ int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(SMALL_BLOCK_SIZE);
+ TEST_THAT(decBlockSize > SMALL_BLOCK_SIZE);
+ uint8_t *decoded = (uint8_t*)malloc(decBlockSize);
+ int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize);
+ TEST_THAT(decSize < decBlockSize);
+ TEST_THAT(decSize == SMALL_BLOCK_SIZE);
+
+ // Check it came out of the wash the same
+ TEST_THAT(::memcmp(encfile, decoded, SMALL_BLOCK_SIZE) == 0);
+
+ free(decoded);
+ }
+
+ // Encode and decode a big block (should be compressed)
+ {
+ int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(ENCFILE_SIZE);
+ TEST_THAT(encBlockSize > ENCFILE_SIZE);
+ BackupStoreFile::EncodingBuffer encoded;
+ encoded.Allocate(encBlockSize / 8); // make sure reallocation happens
+
+ // Encode!
+ int encSize = BackupStoreFile::EncodeChunk(encfile, ENCFILE_SIZE, encoded);
+ // Check the header says it's compressed
+ TEST_THAT((encoded.mpBuffer[0] & 1) == 1);
+ // Check the output size make it likely that it's compressed (is very compressible data)
+ TEST_THAT(encSize < ENCFILE_SIZE);
+
+ // Decode it
+ int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(ENCFILE_SIZE);
+ TEST_THAT(decBlockSize > ENCFILE_SIZE);
+ uint8_t *decoded = (uint8_t*)malloc(decBlockSize);
+ int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize);
+ TEST_THAT(decSize < decBlockSize);
+ TEST_THAT(decSize == ENCFILE_SIZE);
+
+ // Check it came out of the wash the same
+ TEST_THAT(::memcmp(encfile, decoded, ENCFILE_SIZE) == 0);
+
+ free(decoded);
+ }
+
+ // The test block to a file
+ {
+ FileStream f("testfiles/testenc1", O_WRONLY | O_CREAT | O_EXCL);
+ f.Write(encfile, sizeof(encfile));
+ }
+
+ // Encode it
+ {
+ FileStream out("testfiles/testenc1_enc", O_WRONLY | O_CREAT | O_EXCL);
+ BackupStoreFilenameClear name("testfiles/testenc1");
+
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testenc1", 32, name));
+ encoded->CopyStreamTo(out);
+ }
+
+ // Verify it
+ {
+ FileStream enc("testfiles/testenc1_enc");
+ TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc) == true);
+ }
+
+ // Decode it
+ {
+ FileStream enc("testfiles/testenc1_enc");
+ BackupStoreFile::DecodeFile(enc, "testfiles/testenc1_orig", IOStream::TimeOutInfinite);
+ }
+
+ // Read in rebuilt original, and compare contents
+ {
+ TEST_THAT(TestGetFileSize("testfiles/testenc1_orig") == sizeof(encfile));
+ FileStream in("testfiles/testenc1_orig");
+ int encfile_i[ENCFILE_SIZE];
+ in.Read(encfile_i, sizeof(encfile_i));
+ TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0);
+ }
+
+ // Check how many blocks it had, and test the stream based interface
+ {
+ FileStream enc("testfiles/testenc1_enc");
+ std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(enc, IOStream::TimeOutInfinite));
+ CollectInBufferStream d;
+ decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */);
+ d.SetForReading();
+ TEST_THAT(d.GetSize() == sizeof(encfile));
+ TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0);
+
+ TEST_THAT(decoded->GetNumBlocks() == 3);
+ }
+
+ // Test that the last block in a file, if less than 256 bytes, gets put into the last block
+ {
+ #define FILE_SIZE_JUST_OVER ((4096*2)+58)
+ FileStream f("testfiles/testenc2", O_WRONLY | O_CREAT | O_EXCL);
+ f.Write(encfile + 2, FILE_SIZE_JUST_OVER);
+ BackupStoreFilenameClear name("testenc2");
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testenc2", 32, name));
+ CollectInBufferStream e;
+ encoded->CopyStreamTo(e);
+ e.SetForReading();
+ std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(e, IOStream::TimeOutInfinite));
+ CollectInBufferStream d;
+ decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 879 /* buffer block size */);
+ d.SetForReading();
+ TEST_THAT(d.GetSize() == FILE_SIZE_JUST_OVER);
+ TEST_THAT(memcmp(encfile + 2, d.GetBuffer(), FILE_SIZE_JUST_OVER) == 0);
+
+ TEST_THAT(decoded->GetNumBlocks() == 2);
+ }
+
+ // Test that reordered streams work too
+ {
+ FileStream enc("testfiles/testenc1_enc");
+ std::auto_ptr<IOStream> reordered(BackupStoreFile::ReorderFileToStreamOrder(&enc, false));
+ std::auto_ptr<BackupStoreFile::DecodedStream> decoded(BackupStoreFile::DecodeFileStream(*reordered, IOStream::TimeOutInfinite));
+ CollectInBufferStream d;
+ decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */);
+ d.SetForReading();
+ TEST_THAT(d.GetSize() == sizeof(encfile));
+ TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0);
+
+ TEST_THAT(decoded->GetNumBlocks() == 3);
+ }
+
+#ifndef WIN32 // no symlinks on Win32
+ // Try out doing this on a symlink
+ {
+ TEST_THAT(::symlink("does/not/exist", "testfiles/testsymlink") == 0);
+ BackupStoreFilenameClear name("testsymlink");
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/testsymlink", 32, name));
+ // Can't decode it from the stream, because it's in file order, and doesn't have the
+ // required properties to be able to reorder it. So buffer it...
+ CollectInBufferStream b;
+ encoded->CopyStreamTo(b);
+ b.SetForReading();
+ // Decode it
+ BackupStoreFile::DecodeFile(b, "testfiles/testsymlink_2", IOStream::TimeOutInfinite);
+ }
+#endif
+ }
+
+ // Store info
+ {
+ RaidFileWrite::CreateDirectory(0, "test-info");
+ BackupStoreInfo::CreateNew(76, "test-info/", 0, 3461231233455433LL, 2934852487LL);
+ TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, "test-info/", 0, 0, 0), RaidFileException, CannotOverwriteExistingFile);
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, true));
+ TEST_CHECK_THROWS(info->Save(), BackupStoreException, StoreInfoIsReadOnly);
+ TEST_CHECK_THROWS(info->ChangeBlocksUsed(1), BackupStoreException, StoreInfoIsReadOnly);
+ TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(1), BackupStoreException, StoreInfoIsReadOnly);
+ TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(1), BackupStoreException, StoreInfoIsReadOnly);
+ TEST_CHECK_THROWS(info->RemovedDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly);
+ TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly);
+ }
+ {
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, false));
+ info->ChangeBlocksUsed(8);
+ info->ChangeBlocksInOldFiles(9);
+ info->ChangeBlocksInDeletedFiles(10);
+ info->ChangeBlocksUsed(-1);
+ info->ChangeBlocksInOldFiles(-4);
+ info->ChangeBlocksInDeletedFiles(-9);
+ TEST_CHECK_THROWS(info->ChangeBlocksUsed(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative);
+ TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative);
+ TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative);
+ info->AddDeletedDirectory(2);
+ info->AddDeletedDirectory(3);
+ info->AddDeletedDirectory(4);
+ info->RemovedDeletedDirectory(3);
+ TEST_CHECK_THROWS(info->RemovedDeletedDirectory(9), BackupStoreException, StoreInfoDirNotInList);
+ info->Save();
+ }
+ {
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, true));
+ TEST_THAT(info->GetBlocksUsed() == 7);
+ TEST_THAT(info->GetBlocksInOldFiles() == 5);
+ TEST_THAT(info->GetBlocksInDeletedFiles() == 1);
+ TEST_THAT(info->GetBlocksSoftLimit() == 3461231233455433LL);
+ TEST_THAT(info->GetBlocksHardLimit() == 2934852487LL);
+ const std::vector<int64_t> &delfiles(info->GetDeletedDirectories());
+ TEST_THAT(delfiles.size() == 2);
+ TEST_THAT(delfiles[0] == 2);
+ TEST_THAT(delfiles[1] == 4);
+ }
+
+//printf("SKIPPINGTESTS---------\n");
+//return 0;
+
+ // Context
+ TLSContext context;
+ context.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+
+ // First, try logging in without an account having been created... just make sure login fails.
+
+ std::string cmd = BBSTORED " " + bbstored_args +
+ " testfiles/bbstored.conf";
+ int pid = LaunchServer(cmd.c_str(), "testfiles/bbstored.pid");
+
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // BLOCK
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Check the version
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+
+ // Finish the connection
+ protocol.QueryFinished();
+ }
+
+ // Create an account for the test client
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf create 01234567 0 "
+ "10000B 20000B") == 0);
+
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ TEST_THAT(TestDirExists("testfiles/0_0/backup/01234567"));
+ TEST_THAT(TestDirExists("testfiles/0_1/backup/01234567"));
+ TEST_THAT(TestDirExists("testfiles/0_2/backup/01234567"));
+ TEST_THAT(TestGetFileSize("testfiles/accounts.txt") > 8);
+ // make sure something is written to it
+
+ std::auto_ptr<BackupStoreAccountDatabase> apAccounts(
+ BackupStoreAccountDatabase::Read("testfiles/accounts.txt"));
+
+ std::auto_ptr<BackupStoreRefCountDatabase> apReferences(
+ BackupStoreRefCountDatabase::Load(
+ apAccounts->GetEntry(0x1234567), true));
+ TEST_EQUAL(BACKUPSTORE_ROOT_DIRECTORY_ID,
+ apReferences->GetLastObjectIDUsed());
+ TEST_EQUAL(1, apReferences->GetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID))
+ apReferences.reset();
+
+ // Delete the refcount database and log in again,
+ // check that it is recreated automatically but with
+ // no objects in it, to ensure seamless upgrade.
+ TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.db.rfw"));
+
+ TLSContext context;
+ std::auto_ptr<SocketStreamTLS> conn = open_conn("localhost",
+ context);
+ test_server_login(*conn)->QueryFinished();
+
+ BackupStoreAccountDatabase::Entry account =
+ apAccounts->GetEntry(0x1234567);
+ apReferences = BackupStoreRefCountDatabase::Load(account, true);
+ TEST_EQUAL(0, apReferences->GetLastObjectIDUsed());
+
+ TEST_THAT(ServerIsAlive(pid));
+
+ run_housekeeping(account);
+
+ // Check that housekeeping fixed the ref counts
+ TEST_EQUAL(BACKUPSTORE_ROOT_DIRECTORY_ID,
+ apReferences->GetLastObjectIDUsed());
+ TEST_EQUAL(1, apReferences->GetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID))
+
+ TEST_THAT(ServerIsAlive(pid));
+
+ set_refcount(BACKUPSTORE_ROOT_DIRECTORY_ID, 1);
+
+ TEST_THAT(test_server("localhost") == 0);
+
+ // test that all object reference counts have the
+ // expected values
+ TEST_EQUAL(ExpectedRefCounts.size() - 1,
+ apReferences->GetLastObjectIDUsed());
+ for (unsigned int i = BACKUPSTORE_ROOT_DIRECTORY_ID;
+ i < ExpectedRefCounts.size(); i++)
+ {
+ TEST_EQUAL_LINE(ExpectedRefCounts[i],
+ apReferences->GetRefCount(i),
+ "object " << BOX_FORMAT_OBJECTID(i));
+ }
+
+ // Delete the refcount database again, and let
+ // housekeeping recreate it and fix the ref counts.
+ // This could also happen after upgrade, if a housekeeping
+ // runs before the user logs in.
+ apReferences.reset();
+ TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.db.rfw"));
+ run_housekeeping(account);
+ apReferences = BackupStoreRefCountDatabase::Load(account, true);
+
+ TEST_EQUAL(ExpectedRefCounts.size() - 1,
+ apReferences->GetLastObjectIDUsed());
+ for (unsigned int i = BACKUPSTORE_ROOT_DIRECTORY_ID;
+ i < ExpectedRefCounts.size(); i++)
+ {
+ TEST_EQUAL_LINE(ExpectedRefCounts[i],
+ apReferences->GetRefCount(i),
+ "object " << BOX_FORMAT_OBJECTID(i));
+ }
+
+ // Test the deletion of objects by the housekeeping system
+ // First, things as they are now.
+ recursive_count_objects_results before = {0,0,0};
+
+ recursive_count_objects("localhost", BackupProtocolClientListDirectory::RootDirectory, before);
+
+ TEST_THAT(before.objectsNotDel != 0);
+ TEST_THAT(before.deleted != 0);
+ TEST_THAT(before.old != 0);
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+
+ // Set a new limit on the account -- leave the hard limit
+ // high to make sure the target for freeing space is the
+ // soft limit.
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf setlimit 01234567 "
+ "10B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Start things up
+ pid = LaunchServer(BBSTORED " testfiles/bbstored.conf",
+ "testfiles/bbstored.pid");
+
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // wait for housekeeping to happen
+ printf("waiting for housekeeping:\n");
+ for(int l = 0; l < 30; ++l)
+ {
+ ::sleep(1);
+ printf(".");
+ fflush(stdout);
+ }
+ printf("\n");
+
+ // Count the objects again
+ recursive_count_objects_results after = {0,0,0};
+ recursive_count_objects("localhost",
+ BackupProtocolClientListDirectory::RootDirectory,
+ after);
+
+ // If these tests fail then try increasing the timeout above
+ TEST_THAT(after.objectsNotDel == before.objectsNotDel);
+ TEST_THAT(after.deleted == 0);
+ TEST_THAT(after.old == 0);
+
+ // Set a really small hard limit
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf setlimit 01234567 "
+ "10B 20B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Try to upload a file and create a directory, and check an error is generated
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Check the version
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0));
+
+ int64_t modtime = 0;
+
+ BackupStoreFilenameClear fnx("exceed-limit");
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/test3", BackupProtocolClientListDirectory::RootDirectory, fnx, &modtime));
+ TEST_THAT(modtime != 0);
+
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolClientListDirectory::RootDirectory,
+ modtime,
+ modtime, /* use it for attr hash too */
+ 0, /* diff from ID */
+ fnx,
+ *upload)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+
+ MemBlockStream attr(&modtime, sizeof(modtime));
+ BackupStoreFilenameClear fnxd("exceed-limit-dir");
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ 9837429842987984LL, fnxd, attr)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+
+
+ // Finish the connection
+ protocol.QueryFinished();
+ }
+
+ // Kill it again
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+ }
+
+ return 0;
+}
+
+int multi_server()
+{
+ printf("Starting server for connection from remote machines...\n");
+
+ // Create an account for the test client
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf create 01234567 0 "
+ "30000B 40000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // First, try logging in without an account having been created... just make sure login fails.
+
+ int pid = LaunchServer(BBSTORED " testfiles/bbstored_multi.conf",
+ "testfiles/bbstored.pid");
+
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // Wait for a keypress
+ printf("Press ENTER to terminate the server\n");
+ char line[512];
+ fgets(line, 512, stdin);
+ printf("Terminating server...\n");
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+ }
+
+
+ return 0;
+}
+
+#ifdef WIN32
+WCHAR* ConvertUtf8ToWideString(const char* pString);
+std::string ConvertPathToAbsoluteUnicode(const char *pFileName);
+#endif
+
+int test(int argc, const char *argv[])
+{
+#ifdef WIN32
+ // this had better work, or bbstored will die when combining diffs
+ char* file = "foo";
+ std::string abs = ConvertPathToAbsoluteUnicode(file);
+ WCHAR* wfile = ConvertUtf8ToWideString(abs.c_str());
+
+ DWORD accessRights = FILE_READ_ATTRIBUTES |
+ FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_WRITE_ATTRIBUTES |
+ FILE_WRITE_DATA | FILE_WRITE_EA /*| FILE_ALL_ACCESS*/;
+ DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+
+ HANDLE h1 = CreateFileW(wfile, accessRights, shareMode,
+ NULL, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ assert(h1 != INVALID_HANDLE_VALUE);
+ TEST_THAT(h1 != INVALID_HANDLE_VALUE);
+
+ accessRights = FILE_READ_ATTRIBUTES |
+ FILE_LIST_DIRECTORY | FILE_READ_EA;
+
+ HANDLE h2 = CreateFileW(wfile, accessRights, shareMode,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ assert(h2 != INVALID_HANDLE_VALUE);
+ TEST_THAT(h2 != INVALID_HANDLE_VALUE);
+
+ CloseHandle(h2);
+ CloseHandle(h1);
+ delete [] wfile;
+
+ h1 = openfile("foo", O_CREAT | O_RDWR, 0);
+ TEST_THAT(h1 != INVALID_HANDLE_VALUE);
+ h2 = openfile("foo", O_RDWR, 0);
+ TEST_THAT(h2 != INVALID_HANDLE_VALUE);
+ CloseHandle(h2);
+ CloseHandle(h1);
+#endif
+
+ // SSL library
+ SSLLib::Initialise();
+
+ // Give a test key for the filenames
+// BackupStoreFilenameClear::SetBlowfishKey(FilenameEncodingKey, sizeof(FilenameEncodingKey));
+ // And set the encoding to blowfish
+// BackupStoreFilenameClear::SetEncodingMethod(BackupStoreFilename::Encoding_Blowfish);
+
+ // And for directory attributes -- need to set it, as used in file encoding
+// BackupClientFileAttributes::SetBlowfishKey(AttributesEncodingKey, sizeof(AttributesEncodingKey));
+
+ // And finally for file encoding
+// BackupStoreFile::SetBlowfishKeys(FileEncodingKey, sizeof(FileEncodingKey), FileBlockEntryEncodingKey, sizeof(FileBlockEntryEncodingKey));
+
+ // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used
+ // for seeing what's going on.
+ BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys");
+
+ // encode in some filenames -- can't do static initialisation
+ // because the key won't be set up when these are initialised
+ {
+ MEMLEAKFINDER_NO_LEAKS
+
+ for(unsigned int l = 0; l < sizeof(ens_filenames) / sizeof(ens_filenames[0]); ++l)
+ {
+ ens[l].fn = BackupStoreFilenameClear(ens_filenames[l]);
+ }
+ for(unsigned int l = 0; l < sizeof(uploads_filenames) / sizeof(uploads_filenames[0]); ++l)
+ {
+ uploads[l].name = BackupStoreFilenameClear(uploads_filenames[l]);
+ }
+ }
+
+ // Trace errors out
+ SET_DEBUG_SSLLIB_TRACE_ERRORS
+
+ if(argc == 2 && strcmp(argv[1], "server") == 0)
+ {
+ return multi_server();
+ }
+ if(argc == 3 && strcmp(argv[1], "client") == 0)
+ {
+ return test_server(argv[2]);
+ }
+// large file test
+/* {
+ int64_t modtime = 0;
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("/Users/ben/temp/large.tar",
+ BackupProtocolClientListDirectory::RootDirectory, uploads[0].name, &modtime));
+ TEST_THAT(modtime != 0);
+ FileStream write("testfiles/large.enc", O_WRONLY | O_CREAT);
+ upload->CopyStreamTo(write);
+ }
+printf("SKIPPING TESTS ------------------------------------------------------\n");
+return 0;*/
+ int r = 0;
+ r = test1(argc, argv);
+ if(r != 0) return r;
+ r = test2(argc, argv);
+ if(r != 0) return r;
+ r = test3(argc, argv);
+ if(r != 0) return r;
+ return 0;
+}
+
diff --git a/test/backupstore/testextra b/test/backupstore/testextra
new file mode 100644
index 00000000..798c8c67
--- /dev/null
+++ b/test/backupstore/testextra
@@ -0,0 +1,4 @@
+mkdir testfiles/0_0
+mkdir testfiles/0_1
+mkdir testfiles/0_2
+mkdir testfiles/bbackupd-data
diff --git a/test/backupstore/testfiles/accounts.txt b/test/backupstore/testfiles/accounts.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/backupstore/testfiles/accounts.txt
diff --git a/test/backupstore/testfiles/bbackupd.keys b/test/backupstore/testfiles/bbackupd.keys
new file mode 100644
index 00000000..4c58fc22
--- /dev/null
+++ b/test/backupstore/testfiles/bbackupd.keys
Binary files differ
diff --git a/test/backupstore/testfiles/bbstored.conf b/test/backupstore/testfiles/bbstored.conf
new file mode 100644
index 00000000..460b9d59
--- /dev/null
+++ b/test/backupstore/testfiles/bbstored.conf
@@ -0,0 +1,17 @@
+
+RaidFileConf = testfiles/raidfile.conf
+AccountDatabase = testfiles/accounts.txt
+
+ExtendedLogging = yes
+
+TimeBetweenHousekeeping = 10
+
+Server
+{
+ PidFile = testfiles/bbstored.pid
+ ListenAddresses = inet:localhost:22011
+ CertificateFile = testfiles/serverCerts.pem
+ PrivateKeyFile = testfiles/serverPrivKey.pem
+ TrustedCAsFile = testfiles/serverTrustedCAs.pem
+}
+
diff --git a/test/backupstore/testfiles/bbstored_multi.conf b/test/backupstore/testfiles/bbstored_multi.conf
new file mode 100644
index 00000000..73c70aa9
--- /dev/null
+++ b/test/backupstore/testfiles/bbstored_multi.conf
@@ -0,0 +1,16 @@
+
+RaidFileConf = testfiles/raidfile.conf
+AccountDatabase = testfiles/accounts.txt
+
+TimeBetweenHousekeeping = 5
+
+Server
+{
+ PidFile = testfiles/bbstored.pid
+ # 0.0.0.0 is the 'any' address, allowing connections from things other than localhost
+ ListenAddresses = inet:0.0.0.0
+ CertificateFile = testfiles/serverCerts.pem
+ PrivateKeyFile = testfiles/serverPrivKey.pem
+ TrustedCAsFile = testfiles/serverTrustedCAs.pem
+}
+
diff --git a/test/backupstore/testfiles/clientCerts.pem b/test/backupstore/testfiles/clientCerts.pem
new file mode 100644
index 00000000..c1f14fa7
--- /dev/null
+++ b/test/backupstore/testfiles/clientCerts.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w
+MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w
+MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92
+OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A
+LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k
+Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU
+knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx
+hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o
+EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ==
+-----END CERTIFICATE-----
diff --git a/test/backupstore/testfiles/clientPrivKey.pem b/test/backupstore/testfiles/clientPrivKey.pem
new file mode 100644
index 00000000..34b1af2a
--- /dev/null
+++ b/test/backupstore/testfiles/clientPrivKey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev
+ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G
+9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB
+AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N
+eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq
+iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp
+81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9
+qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU
+LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ
+V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B
+BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8
+eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26
+Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw=
+-----END RSA PRIVATE KEY-----
diff --git a/test/backupstore/testfiles/clientReq.pem b/test/backupstore/testfiles/clientReq.pem
new file mode 100644
index 00000000..8eee0b5f
--- /dev/null
+++ b/test/backupstore/testfiles/clientReq.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBWTCBwwIBADAaMRgwFgYDVQQDEw9CQUNLVVAtMDEyMzQ1NjcwgZ8wDQYJKoZI
+hvcNAQEBBQADgY0AMIGJAoGBAL6bTOgPvmXZMWDfdjiOndDt9oIkXcm0nVFTSZNP
+VdaXVSRYV6+KMIbVb+nfMg5z2jToEtS5cWfQh3EeQC7RcCPcJhhwXOWtrhQmRjkU
+2koIJ7QsrYb0Vdwxpv31O5kTBOy3tf7VlsY8nJ5NpEXs+nexX4+eklwxaMWNnSvD
+MEWrAgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQBtz10sGGYhbw9+7L8bOtOUV6j9
+46jnbHGXHmdBZsg8ZWgKBJQ61HwvKCNA+KAEeb9yMxWgpJRGqFk6yvPb62XXuRGl
+4RQN0/6rRc8GJh3Qi4oPV1GYnzyYg2+bjZAgeMoL6ro1YuH52CTHJpQ3Arg2Ortz
+xVxbWyMouzjc1g4gdw==
+-----END CERTIFICATE REQUEST-----
diff --git a/test/backupstore/testfiles/clientTrustedCAs.pem b/test/backupstore/testfiles/clientTrustedCAs.pem
new file mode 100644
index 00000000..2a065879
--- /dev/null
+++ b/test/backupstore/testfiles/clientTrustedCAs.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz
+MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e
+cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9
+I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6
+u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ
+hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM
+USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt
+SlL3iQzVXlF6NAhkzS54fQ==
+-----END CERTIFICATE-----
diff --git a/test/backupstore/testfiles/query.conf b/test/backupstore/testfiles/query.conf
new file mode 100644
index 00000000..984ace6c
--- /dev/null
+++ b/test/backupstore/testfiles/query.conf
@@ -0,0 +1,34 @@
+
+# this is a dummy config file so that bbackupquery can be used
+
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+AccountNumber = 0x01234567
+UpdateStoreInterval = 3
+MinimumFileAge = 4
+MaxUploadWait = 24
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+# this is just a dummy entry
+BackupLocations
+{
+ test_delete
+ {
+ Path = testfiles/test_delete
+ }
+}
+
diff --git a/test/backupstore/testfiles/raidfile.conf b/test/backupstore/testfiles/raidfile.conf
new file mode 100644
index 00000000..641872b0
--- /dev/null
+++ b/test/backupstore/testfiles/raidfile.conf
@@ -0,0 +1,10 @@
+
+disc0
+{
+ SetNumber = 0
+ BlockSize = 2048
+ Dir0 = testfiles/0_0
+ Dir1 = testfiles/0_1
+ Dir2 = testfiles/0_2
+}
+
diff --git a/test/backupstore/testfiles/root.pem b/test/backupstore/testfiles/root.pem
new file mode 100644
index 00000000..b7fa6a17
--- /dev/null
+++ b/test/backupstore/testfiles/root.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz
+MDgyMDExNTEyN1oXDTAzMDkxOTExNTEyN1owDzENMAsGA1UEAxMEUk9PVDCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e
+cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9
+I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6
+u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQCPbEXLzpItnnh1kUPy0vui
+atzeQoTFzgEybKLqgM4irWUjUnVdcSFEJFgddABpMOlGymu/6NuqqVQR8OUUOUrk
+BUlucY1m3BuCJBsADKWXVBOky4aQ7oo7BZZUh7e9NeKHfu7u1+0kvIQlTc+1Xnub
+uAQzwDRZ5vAFMWzzvh5BtA==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO
+cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2
+tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB
+AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe
+xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi
+wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0
+5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01
+TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe
+k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb
+Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO
+Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF
+Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe
+3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w==
+-----END RSA PRIVATE KEY-----
diff --git a/test/backupstore/testfiles/root.srl b/test/backupstore/testfiles/root.srl
new file mode 100644
index 00000000..eeee65ec
--- /dev/null
+++ b/test/backupstore/testfiles/root.srl
@@ -0,0 +1 @@
+05
diff --git a/test/backupstore/testfiles/rootcert.pem b/test/backupstore/testfiles/rootcert.pem
new file mode 100644
index 00000000..2a065879
--- /dev/null
+++ b/test/backupstore/testfiles/rootcert.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz
+MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e
+cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9
+I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6
+u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ
+hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM
+USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt
+SlL3iQzVXlF6NAhkzS54fQ==
+-----END CERTIFICATE-----
diff --git a/test/backupstore/testfiles/rootkey.pem b/test/backupstore/testfiles/rootkey.pem
new file mode 100644
index 00000000..7ce55861
--- /dev/null
+++ b/test/backupstore/testfiles/rootkey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO
+cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2
+tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB
+AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe
+xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi
+wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0
+5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01
+TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe
+k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb
+Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO
+Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF
+Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe
+3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w==
+-----END RSA PRIVATE KEY-----
diff --git a/test/backupstore/testfiles/rootreq.pem b/test/backupstore/testfiles/rootreq.pem
new file mode 100644
index 00000000..2ac6293c
--- /dev/null
+++ b/test/backupstore/testfiles/rootreq.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBTjCBuAIBADAPMQ0wCwYDVQQDEwRST09UMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDOcOtu
+HweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2tNI9
+W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQABoAAw
+DQYJKoZIhvcNAQEFBQADgYEAarbwMXzojqzCzQLakpX8hMDiBnGb80M4au+r8MXI
+g492CbH+PgpSus4g58na+1S1xAV2a7kDN6udss+OjHvukePybWUkkR6DAfXVJuxO
+FrchOTv6Pwj1p4FZGzocnJ2sIp4fe+2p2ge2oAHw7EIX+1IhQUObGI/q7zEVDctK
+5Fg=
+-----END CERTIFICATE REQUEST-----
diff --git a/test/backupstore/testfiles/serverCerts.pem b/test/backupstore/testfiles/serverCerts.pem
new file mode 100644
index 00000000..92467618
--- /dev/null
+++ b/test/backupstore/testfiles/serverCerts.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w
+MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw
+MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV
+I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92
+cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH
+RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/
+myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2
+SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1
+HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t
+-----END CERTIFICATE-----
diff --git a/test/backupstore/testfiles/serverPrivKey.pem b/test/backupstore/testfiles/serverPrivKey.pem
new file mode 100644
index 00000000..fd87607d
--- /dev/null
+++ b/test/backupstore/testfiles/serverPrivKey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP
+cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4
+bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB
+AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX
+P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT
+LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l
+4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN
+8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t
+Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX
+I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl
+XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48
+q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE
+xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn
+-----END RSA PRIVATE KEY-----
diff --git a/test/backupstore/testfiles/serverReq.pem b/test/backupstore/testfiles/serverReq.pem
new file mode 100644
index 00000000..7475d406
--- /dev/null
+++ b/test/backupstore/testfiles/serverReq.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBWDCBwgIBADAZMRcwFQYDVQQDEw5TVE9SRS0wMDAwMDAwODCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAzY9Xxkgmkpf9cNZUVSPKhOgao70+kdF1xnSFfnZP
+5h5hNzTgj3Aav4MWTmHr6fkJ8DHPp1Ekuz8f5/qvdnJd2vLbSJ32Yy6oPaP8KXen
+QXx+IC0xuGxLo4TLdceGxFCZP7ln55ORYbCVwwa5R0SWTYkvQLJnvuWb4389z4ct
+GFsCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIdlFo8gbik1K/+4Ra87cQDZzn0L
+wE9bZrxRMPXqGjCQ8HBCfvQMFa1Oc6fEczCJ/nmmd76j0HIXW7uYOELIT8L/Zvf5
+jw/z9/OvEOQal7H2JN2d6W4ZmYpQko5+e/bJmlrOxyBpcXk34BvyQen9pTmI6J4E
+pkBN/5XUUvVJSM67
+-----END CERTIFICATE REQUEST-----
diff --git a/test/backupstore/testfiles/serverTrustedCAs.pem b/test/backupstore/testfiles/serverTrustedCAs.pem
new file mode 100644
index 00000000..2a065879
--- /dev/null
+++ b/test/backupstore/testfiles/serverTrustedCAs.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz
+MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e
+cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9
+I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6
+u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ
+hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM
+USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt
+SlL3iQzVXlF6NAhkzS54fQ==
+-----END CERTIFICATE-----
diff --git a/test/backupstorefix/testbackupstorefix.cpp b/test/backupstorefix/testbackupstorefix.cpp
new file mode 100644
index 00000000..2d4ce052
--- /dev/null
+++ b/test/backupstorefix/testbackupstorefix.cpp
@@ -0,0 +1,614 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testbackupstorefix.cpp
+// Purpose: Test BackupStoreCheck functionality
+// Created: 23/4/04
+//
+// --------------------------------------------------------------------------
+
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <string>
+#include <map>
+
+#include "Test.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreFile.h"
+#include "FileStream.h"
+#include "RaidFileController.h"
+#include "RaidFileWrite.h"
+#include "RaidFileRead.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreException.h"
+#include "RaidFileException.h"
+#include "StoreStructure.h"
+#include "BackupStoreFileWire.h"
+#include "ServerControl.h"
+
+#include "MemLeakFindOn.h"
+
+/*
+
+Errors checked:
+
+make some BackupDirectoryStore objects, CheckAndFix(), then verify
+ - multiple objects with same ID
+ - wrong order of old flags
+ - all old flags
+
+delete store info
+add spurious file
+delete directory (should appear again)
+change container ID of directory
+delete a file
+double reference to a file inside a single dir
+modify the object ID of a directory
+delete directory, which has no members (will be removed)
+extra reference to a file in another dir (higher ID to allow consistency -- use something in subti)
+delete dir + dir2 in dir/dir2/file where nothing in dir2 except file, file should end up in lost+found
+similarly with a dir, but that should get a dirxxx name
+corrupt dir
+corrupt file
+delete root, copy a file to it instead (equivalent to deleting it too)
+
+*/
+
+std::string storeRoot("backup/01234567/");
+int discSetNum = 0;
+
+std::map<std::string, int32_t> nameToID;
+std::map<int32_t, bool> objectIsDir;
+
+#define RUN_CHECK \
+ ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567"); \
+ ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567 fix");
+
+// Get ID of an object given a filename
+int32_t getID(const char *name)
+{
+ std::map<std::string, int32_t>::iterator i(nameToID.find(std::string(name)));
+ TEST_THAT(i != nameToID.end());
+ if(i == nameToID.end()) return -1;
+
+ return i->second;
+}
+
+// Get the RAID filename of an object
+std::string getObjectName(int32_t id)
+{
+ std::string fn;
+ StoreStructure::MakeObjectFilename(id, storeRoot, discSetNum, fn, false);
+ return fn;
+}
+
+// Delete an object
+void DeleteObject(const char *name)
+{
+ RaidFileWrite del(discSetNum, getObjectName(getID(name)));
+ del.Delete();
+}
+
+// Load a directory
+void LoadDirectory(const char *name, BackupStoreDirectory &rDir)
+{
+ std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(discSetNum, getObjectName(getID(name))));
+ rDir.ReadFromStream(*file, IOStream::TimeOutInfinite);
+}
+// Save a directory back again
+void SaveDirectory(const char *name, const BackupStoreDirectory &rDir)
+{
+ RaidFileWrite d(discSetNum, getObjectName(getID(name)));
+ d.Open(true /* allow overwrite */);
+ rDir.WriteToStream(d);
+ d.Commit(true /* write now! */);
+}
+
+void CorruptObject(const char *name, int start, const char *rubbish)
+{
+ int rubbish_len = ::strlen(rubbish);
+ std::string fn(getObjectName(getID(name)));
+ std::auto_ptr<RaidFileRead> r(RaidFileRead::Open(discSetNum, fn));
+ RaidFileWrite w(discSetNum, fn);
+ w.Open(true /* allow overwrite */);
+ // Copy beginning
+ char buf[2048];
+ r->Read(buf, start, IOStream::TimeOutInfinite);
+ w.Write(buf, start);
+ // Write rubbish
+ r->Seek(rubbish_len, IOStream::SeekType_Relative);
+ w.Write(rubbish, rubbish_len);
+ // Copy rest of file
+ r->CopyStreamTo(w);
+ r->Close();
+ // Commit
+ w.Commit(true /* convert now */);
+}
+
+BackupStoreFilename fnames[3];
+
+typedef struct
+{
+ int name;
+ int64_t id;
+ int flags;
+} dir_en_check;
+
+void check_dir(BackupStoreDirectory &dir, dir_en_check *ck)
+{
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en;
+
+ while((en = i.Next()) != 0)
+ {
+ TEST_THAT(ck->name != -1);
+ if(ck->name == -1)
+ {
+ break;
+ }
+ TEST_THAT(en->GetName() == fnames[ck->name]);
+ TEST_THAT(en->GetObjectID() == ck->id);
+ TEST_THAT(en->GetFlags() == ck->flags);
+ ++ck;
+ }
+
+ TEST_THAT(en == 0);
+ TEST_THAT(ck->name == -1);
+}
+
+typedef struct
+{
+ int64_t id, depNewer, depOlder;
+} checkdepinfoen;
+
+void check_dir_dep(BackupStoreDirectory &dir, checkdepinfoen *ck)
+{
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en;
+
+ while((en = i.Next()) != 0)
+ {
+ TEST_THAT(ck->id != -1);
+ if(ck->id == -1)
+ {
+ break;
+ }
+ TEST_THAT(en->GetObjectID() == ck->id);
+ TEST_THAT(en->GetDependsNewer() == ck->depNewer);
+ TEST_THAT(en->GetDependsOlder() == ck->depOlder);
+ ++ck;
+ }
+
+ TEST_THAT(en == 0);
+ TEST_THAT(ck->id == -1);
+}
+
+void test_dir_fixing()
+{
+ {
+ MEMLEAKFINDER_NO_LEAKS;
+ fnames[0].SetAsClearFilename("x1");
+ fnames[1].SetAsClearFilename("x2");
+ fnames[2].SetAsClearFilename("x3");
+ }
+
+ {
+ BackupStoreDirectory dir;
+ dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[1], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+
+ dir_en_check ck[] = {
+ {1, 2, BackupStoreDirectory::Entry::Flags_File},
+ {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion},
+ {0, 5, BackupStoreDirectory::Entry::Flags_File},
+ {-1, 0, 0}
+ };
+
+ TEST_THAT(dir.CheckAndFix() == true);
+ TEST_THAT(dir.CheckAndFix() == false);
+ check_dir(dir, ck);
+ }
+ {
+ BackupStoreDirectory dir;
+ dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[1], 12, 10 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+
+ dir_en_check ck[] = {
+ {0, 2, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion},
+ {1, 10, BackupStoreDirectory::Entry::Flags_Dir},
+ {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion},
+ {0, 5, BackupStoreDirectory::Entry::Flags_File},
+ {-1, 0, 0}
+ };
+
+ TEST_THAT(dir.CheckAndFix() == true);
+ TEST_THAT(dir.CheckAndFix() == false);
+ check_dir(dir, ck);
+ }
+
+ // Test dependency fixing
+ {
+ BackupStoreDirectory dir;
+ BackupStoreDirectory::Entry *e2
+ = dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ TEST_THAT(e2 != 0);
+ e2->SetDependsNewer(3);
+ BackupStoreDirectory::Entry *e3
+ = dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ TEST_THAT(e3 != 0);
+ e3->SetDependsNewer(4); e3->SetDependsOlder(2);
+ BackupStoreDirectory::Entry *e4
+ = dir.AddEntry(fnames[0], 12, 4 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ TEST_THAT(e4 != 0);
+ e4->SetDependsNewer(5); e4->SetDependsOlder(3);
+ BackupStoreDirectory::Entry *e5
+ = dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+ TEST_THAT(e5 != 0);
+ e5->SetDependsOlder(4);
+
+ // This should all be nice and valid
+ TEST_THAT(dir.CheckAndFix() == false);
+ static checkdepinfoen c1[] = {{2, 3, 0}, {3, 4, 2}, {4, 5, 3}, {5, 0, 4}, {-1, 0, 0}};
+ check_dir_dep(dir, c1);
+
+ // Check that dependency forwards are restored
+ e4->SetDependsOlder(34343);
+ TEST_THAT(dir.CheckAndFix() == true);
+ TEST_THAT(dir.CheckAndFix() == false);
+ check_dir_dep(dir, c1);
+
+ // Check that a spurious depends older ref is undone
+ e2->SetDependsOlder(1);
+ TEST_THAT(dir.CheckAndFix() == true);
+ TEST_THAT(dir.CheckAndFix() == false);
+ check_dir_dep(dir, c1);
+
+ // Now delete an entry, and check it's cleaned up nicely
+ dir.DeleteEntry(3);
+ TEST_THAT(dir.CheckAndFix() == true);
+ TEST_THAT(dir.CheckAndFix() == false);
+ static checkdepinfoen c2[] = {{4, 5, 0}, {5, 0, 4}, {-1, 0, 0}};
+ check_dir_dep(dir, c2);
+ }
+}
+
+int test(int argc, const char *argv[])
+{
+ // Test the backupstore directory fixing
+ test_dir_fixing();
+
+ // Initialise the raidfile controller
+ RaidFileController &rcontroller = RaidFileController::GetController();
+ rcontroller.Initialise("testfiles/raidfile.conf");
+
+ // Create an account
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf "
+ "create 01234567 0 10000B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Start the bbstored server
+ int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf",
+ "testfiles/bbstored.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+
+ if(pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // Run the perl script to create the initial directories
+ TEST_THAT_ABORTONFAIL(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl init") == 0);
+
+ std::string cmd = BBACKUPD " " + bbackupd_args +
+ " testfiles/bbackupd.conf";
+ int bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
+ TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
+
+ if(bbackupd_pid > 0)
+ {
+ ::safe_sleep(1);
+ TEST_THAT(ServerIsAlive(bbackupd_pid));
+
+ // Wait 4 more seconds for the files to be old enough
+ // to upload
+ ::safe_sleep(4);
+
+ // Upload files to create a nice store directory
+ ::sync_and_wait();
+
+ // Stop bbackupd
+ #ifdef WIN32
+ terminate_bbackupd(bbackupd_pid);
+ // implicit check for memory leaks
+ #else
+ TEST_THAT(KillServer(bbackupd_pid));
+ TestRemoteProcessMemLeaks("bbackupd.memleaks");
+ #endif
+ }
+
+ // Generate a list of all the object IDs
+ TEST_THAT_ABORTONFAIL(::system(BBACKUPQUERY " -Wwarning "
+ "-c testfiles/bbackupd.conf \"list -r\" quit "
+ "> testfiles/initial-listing.txt") == 0);
+
+ // And load it in
+ {
+ FILE *f = ::fopen("testfiles/initial-listing.txt", "r");
+ TEST_THAT_ABORTONFAIL(f != 0);
+ char line[512];
+ int32_t id;
+ char flags[32];
+ char name[256];
+ while(::fgets(line, sizeof(line), f) != 0)
+ {
+ TEST_THAT(::sscanf(line, "%x %s %s", &id,
+ flags, name) == 3);
+ bool isDir = (::strcmp(flags, "-d---") == 0);
+ //TRACE3("%x,%d,%s\n", id, isDir, name);
+ MEMLEAKFINDER_NO_LEAKS;
+ nameToID[std::string(name)] = id;
+ objectIsDir[id] = isDir;
+ }
+ ::fclose(f);
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Delete store info, add random file\n");
+ {
+ // Delete store info
+ RaidFileWrite del(discSetNum, storeRoot + "info");
+ del.Delete();
+ }
+ {
+ // Add a spurious file
+ RaidFileWrite random(discSetNum,
+ storeRoot + "randomfile");
+ random.Open();
+ random.Write("test", 4);
+ random.Commit(true);
+ }
+
+ // Fix it
+ RUN_CHECK
+
+ // Check everything is as it was
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 0") == 0);
+ // Check the random file doesn't exist
+ {
+ TEST_THAT(!RaidFileRead::FileExists(discSetNum,
+ storeRoot + "01/randomfile"));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Delete an entry for an object from dir, change that object to be a patch, check it's deleted\n");
+ {
+ // Open dir and find entry
+ int64_t delID = getID("Test1/cannes/ict/metegoguered/oats");
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/cannes/ict/metegoguered", dir);
+ TEST_THAT(dir.FindEntryByID(delID) != 0);
+ dir.DeleteEntry(delID);
+ SaveDirectory("Test1/cannes/ict/metegoguered", dir);
+ }
+
+ // Adjust that entry
+ //
+ // IMPORTANT NOTE: There's a special hack in testbackupstorefix.pl to make sure that
+ // the file we're modifiying has at least two blocks so we can modify it and produce a valid file
+ // which will pass the verify checks.
+ //
+ std::string fn(getObjectName(delID));
+ {
+ std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(discSetNum, fn));
+ RaidFileWrite f(discSetNum, fn);
+ f.Open(true /* allow overwrite */);
+ // Make a copy of the original
+ file->CopyStreamTo(f);
+ // Move to header in both
+ file->Seek(0, IOStream::SeekType_Absolute);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(*file);
+ f.Seek(file->GetPosition(), IOStream::SeekType_Absolute);
+ // Read header
+ struct
+ {
+ file_BlockIndexHeader hdr;
+ file_BlockIndexEntry e[2];
+ } h;
+ TEST_THAT(file->Read(&h, sizeof(h)) == sizeof(h));
+ file->Close();
+
+ // Modify
+ TEST_THAT(box_ntoh64(h.hdr.mOtherFileID) == 0);
+ TEST_THAT(box_ntoh64(h.hdr.mNumBlocks) >= 2);
+ h.hdr.mOtherFileID = box_hton64(2345); // don't worry about endianness
+ h.e[0].mEncodedSize = box_hton64((box_ntoh64(h.e[0].mEncodedSize)) + (box_ntoh64(h.e[1].mEncodedSize)));
+ h.e[1].mOtherBlockIndex = box_hton64(static_cast<uint64_t>(-2));
+ // Write to modified file
+ f.Write(&h, sizeof(h));
+ // Commit new version
+ f.Commit(true /* write now! */);
+ }
+
+ // Fix it
+ RUN_CHECK
+ // Check
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 1")
+ == 0);
+
+ // Check the modified file doesn't exist
+ TEST_THAT(!RaidFileRead::FileExists(discSetNum, fn));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Delete directory, change container ID of another, duplicate entry in dir, spurious file size, delete file\n");
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ dir.SetContainerID(73773);
+ SaveDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ }
+ int64_t duplicatedID = 0;
+ int64_t notSpuriousFileSize = 0;
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/cannes/ict/peep", dir);
+ // Duplicate the second entry
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ i.Next();
+ BackupStoreDirectory::Entry *en = i.Next();
+ TEST_THAT(en != 0);
+ duplicatedID = en->GetObjectID();
+ dir.AddEntry(*en);
+ }
+ // Adjust file size of first file
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File);
+ TEST_THAT(en != 0);
+ notSpuriousFileSize = en->GetSizeInBlocks();
+ en->SetSizeInBlocks(3473874);
+ TEST_THAT(en->GetSizeInBlocks() == 3473874);
+ }
+ SaveDirectory("Test1/cannes/ict/peep", dir);
+ }
+ // Delete a directory
+ DeleteObject("Test1/pass/cacted/ming");
+ // Delete a file
+ DeleteObject("Test1/cannes/ict/scely");
+ // Fix it
+ RUN_CHECK
+ // Check everything is as it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 2") == 0);
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ TEST_THAT(dir.GetContainerID() == getID("Test1/foreomizes/stemptinevidate"));
+ }
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/cannes/ict/peep", dir);
+ BackupStoreDirectory::Iterator i(dir);
+ // Count the number of entries with the ID which was duplicated
+ int count = 0;
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ if(en->GetObjectID() == duplicatedID)
+ {
+ ++count;
+ }
+ }
+ TEST_THAT(count == 1);
+ // Check file size has changed
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File);
+ TEST_THAT(en != 0);
+ TEST_THAT(en->GetSizeInBlocks() == notSpuriousFileSize);
+ }
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Modify the obj ID of dir, delete dir with no members, add extra reference to a file\n");
+ // Set bad object ID
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ dir.TESTONLY_SetObjectID(73773);
+ SaveDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ }
+ // Delete dir with no members
+ DeleteObject("Test1/dir-no-members");
+ // Add extra reference
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/divel", dir);
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File);
+ TEST_THAT(en != 0);
+ BackupStoreDirectory dir2;
+ LoadDirectory("Test1/divel/torsines/cruishery", dir2);
+ dir2.AddEntry(*en);
+ SaveDirectory("Test1/divel/torsines/cruishery", dir2);
+ }
+ // Fix it
+ RUN_CHECK
+ // Check everything is as it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 3") == 0);
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ TEST_THAT(dir.GetObjectID() == getID("Test1/foreomizes/stemptinevidate/ict"));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Orphan files and dirs without being recoverable\n");
+ DeleteObject("Test1/dir1");
+ DeleteObject("Test1/dir1/dir2");
+ // Fix it
+ RUN_CHECK
+ // Check everything is where it is predicted to be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 4") == 0);
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Corrupt file and dir\n");
+ // File
+ CorruptObject("Test1/foreomizes/stemptinevidate/algoughtnerge",
+ 33, "34i729834298349283479233472983sdfhasgs");
+ // Dir
+ CorruptObject("Test1/cannes/imulatrougge/foreomizes",23,
+ "dsf32489sdnadf897fd2hjkesdfmnbsdfcsfoisufio2iofe2hdfkjhsf");
+ // Fix it
+ RUN_CHECK
+ // Check everything is where it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 5") == 0);
+
+ // ------------------------------------------------------------------------------------------------
+ ::printf(" === Overwrite root with a file\n");
+ {
+ std::auto_ptr<RaidFileRead> r(RaidFileRead::Open(discSetNum, getObjectName(getID("Test1/pass/shuted/brightinats/milamptimaskates"))));
+ RaidFileWrite w(discSetNum, getObjectName(1 /* root */));
+ w.Open(true /* allow overwrite */);
+ r->CopyStreamTo(w);
+ w.Commit(true /* convert now */);
+ }
+ // Fix it
+ RUN_CHECK
+ // Check everything is where it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl reroot 6") == 0);
+
+
+ // ---------------------------------------------------------
+ // Stop server
+ TEST_THAT(KillServer(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+ }
+
+ return 0;
+}
+
diff --git a/test/backupstorefix/testextra b/test/backupstorefix/testextra
new file mode 100644
index 00000000..bf5719f9
--- /dev/null
+++ b/test/backupstorefix/testextra
@@ -0,0 +1,5 @@
+mkdir testfiles/0_0
+mkdir testfiles/0_1
+mkdir testfiles/0_2
+mkdir testfiles/bbackupd-data
+cp ../../../test/bbackupd/testfiles/*.* testfiles/
diff --git a/test/backupstorefix/testfiles/testbackupstorefix.pl.in b/test/backupstorefix/testfiles/testbackupstorefix.pl.in
new file mode 100755
index 00000000..d27c1106
--- /dev/null
+++ b/test/backupstorefix/testfiles/testbackupstorefix.pl.in
@@ -0,0 +1,221 @@
+#!@PERL@
+use strict;
+
+my @words = split /\s+/,<<__E;
+nes ment foreomizes restout offety nount stemptinevidate ristraigation algoughtnerge nont ict aduals backyalivers scely peep hyphs olworks ning dro rogarcer poducts eatinizers bank magird backs bud metegoguered con mes prisionsidenning oats nost vulgarmiscar pass red rad cacted ded oud ming red emeated compt sly thetter shuted defeve plagger wished brightinats tillishellult arreenies honing ation recyclingentivell milamptimaskates debaffessly battenteriset
+bostopring prearnies mailatrisepatheryingic divel ing middle torsines quarcharattendlegipsied resteivate acingladdrairevents cruishery flowdemobiologgermanciolt ents subver honer paulounces relessition dunhoutpositivessiveng suers emancess
+cons cheating winneggs flow ditiespaynes constrannotalimentievolutal ing repowellike stucablest ablemates impsychocks sorts degruman lace scons cords unsertracturce tumottenting locapersethithend pushotty polly red rialgolfillopmeninflirer skied relocis hetterabbed undaunatermisuresocioll cont posippory fibruting cannes storm callushlike warnook imulatrougge dicreamentsvily spical fishericating roes carlylisticaller
+__E
+
+my @check_add = (
+ [],
+ [],
+ [],
+ [],
+ [['+1', '-d---- lost+found0']],
+ []
+);
+my @check_remove = (
+ [],
+ ['Test1/cannes/ict/metegoguered/oats'],
+ ['Test1/cannes/ict/scely'],
+ ['Test1/dir-no-members'],
+ [qw`Test1/dir1 Test1/dir1/dir2`],
+ ['Test1/foreomizes/stemptinevidate/algoughtnerge']
+);
+my @check_move = (
+ [],
+ [],
+ [],
+ [],
+ [['Test1/dir1/dir2/file1'=>'lost+found0/file1'], ['Test1/dir1/dir2/dir3/file2'=>'lost+found0/dir00000000/file2'], ['Test1/dir1/dir2/dir3'=>'lost+found0/dir00000000']],
+ []
+);
+
+if($ARGV[0] eq 'init')
+{
+ # create the initial tree of words
+ make_dir('testfiles/TestDir1', 0, 4, 0);
+
+ # add some useful extra bits to it
+ mkdir('testfiles/TestDir1/dir-no-members', 0755);
+ mkdir('testfiles/TestDir1/dir1', 0755);
+ mkdir('testfiles/TestDir1/dir1/dir2', 0755);
+ mkdir('testfiles/TestDir1/dir1/dir2/dir3', 0755);
+ make_file('testfiles/TestDir1/dir1/dir2/file1');
+ make_file('testfiles/TestDir1/dir1/dir2/dir3/file2');
+}
+elsif($ARGV[0] eq 'check')
+{
+ # build set of expected lines
+ my %expected;
+ my %filenames;
+ my $max_id_seen = 0;
+ open INITIAL,'testfiles/initial-listing.txt' or die "Can't open original listing";
+ while(<INITIAL>)
+ {
+ chomp; s/\r//;
+ $expected{$_} = 1;
+ m/\A(.+?) .+? (.+)\Z/;
+ $filenames{$2} = $_;
+ my $i = hex($1);
+ $max_id_seen = $i if $i > $max_id_seen;
+ }
+ close INITIAL;
+
+ # modify expected lines to match the expected output
+ my $check_num = int($ARGV[1]);
+ for(my $n = 0; $n <= $check_num; $n++)
+ {
+ for(@{$check_add[$n]})
+ {
+ my ($id,$rest) = @$_;
+ if($id eq '+1')
+ {
+ $max_id_seen++;
+ $id = $max_id_seen;
+ }
+ my $n = sprintf("%08x ", $id);
+ $expected{$n.$rest} = 1
+ }
+ for(@{$check_remove[$n]})
+ {
+ delete $expected{$filenames{$_}}
+ }
+ for(@{$check_move[$n]})
+ {
+ my ($from,$to) = @$_;
+ my $orig = $filenames{$from};
+ delete $expected{$filenames{$from}};
+ my ($id,$type) = split / /,$orig;
+ $expected{"$id $type $to"} = 1
+ }
+ }
+
+ # read in the new listing, and compare
+ open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " .
+ "-c testfiles/bbackupd.conf " .
+ "\"list -r\" quit |"
+ or die "Can't open list utility";
+ open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt'
+ or die "can't open copy listing file";
+ my $err = 0;
+ while(<LISTING>)
+ {
+ print LISTING_COPY;
+ chomp; s/\r//;
+ s/\[FILENAME NOT ENCRYPTED\]//;
+ if(exists $expected{$_})
+ {
+ delete $expected{$_}
+ }
+ else
+ {
+ $err = 1;
+ print "Unexpected object $_ in new output\n"
+ }
+ }
+ close LISTING_COPY;
+ close LISTING;
+
+ # check for anything which didn't appear but should have done
+ for(keys %expected)
+ {
+ $err = 1;
+ print "Expected object $_ not found in new output\n"
+ }
+
+ exit $err;
+}
+elsif($ARGV[0] eq 'reroot')
+{
+ open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " .
+ "-c testfiles/bbackupd.conf " .
+ "\"list -r\" quit |"
+ or die "Can't open list utility";
+ open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt'
+ or die "can't open copy listing file";
+ my $err = 0;
+ my $count = 0;
+ while(<LISTING>)
+ {
+ print LISTING_COPY;
+ chomp;
+ s/\[FILENAME NOT ENCRYPTED\]//;
+ my ($id,$type,$name) = split / /;
+ $count++;
+ if($name !~ /\Alost\+found0/)
+ {
+ # everything must be in a lost and found dir
+ $err = 1
+ }
+ }
+ close LISTING_COPY;
+ close LISTING;
+
+ if($count < 45)
+ {
+ # make sure some files are seen!
+ $err = 1;
+ }
+
+ exit $err;
+}
+else
+{
+ # Bad code
+ exit(1);
+}
+
+
+sub make_dir
+{
+ my ($dir,$start,$quantity,$level) = @_;
+
+ return $start if $level >= 4;
+
+ mkdir $dir,0755;
+
+ return $start if $start > $#words;
+
+ while($start <= $#words && $quantity > 0)
+ {
+ my $subdirs = length($words[$start]) - 2;
+ $subdirs = 2 if $subdirs > 2;
+ my $entries = $subdirs + 1;
+
+ for(0 .. ($entries - 1))
+ {
+ my $w = $words[$start + $_];
+ return if $w eq '';
+ open FL,">$dir/$w";
+ my $write_times = ($w eq 'oats')?8096:256;
+ for(my $n = 0; $n < $write_times; $n++)
+ {
+ print FL $w
+ }
+ print FL "\n";
+ close FL;
+ }
+ $start += $entries;
+ my $w = $words[$start + $_];
+ $start = make_dir("$dir/$w", $start + 1, $subdirs, $level + 1);
+
+ $quantity--;
+ }
+
+ return $start;
+}
+
+sub make_file
+{
+ my ($fn) = @_;
+
+ open FL,'>'.$fn or die "can't open $fn for writing";
+ for(0 .. 255)
+ {
+ print FL $fn
+ }
+ close FL;
+}
+
diff --git a/test/backupstorepatch/testbackupstorepatch.cpp b/test/backupstorepatch/testbackupstorepatch.cpp
new file mode 100644
index 00000000..2510da30
--- /dev/null
+++ b/test/backupstorepatch/testbackupstorepatch.cpp
@@ -0,0 +1,670 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testbackupstorepatch.cpp
+// Purpose: Test storage of patches on the backup store server
+// Created: 13/7/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include "autogen_BackupProtocolClient.h"
+#include "BackupClientCryptoKeys.h"
+#include "BackupClientFileAttributes.h"
+#include "BackupStoreAccountDatabase.h"
+#include "BackupStoreAccounts.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFilenameClear.h"
+#include "BackupStoreInfo.h"
+#include "BoxPortsAndFiles.h"
+#include "CollectInBufferStream.h"
+#include "FileStream.h"
+#include "MemBlockStream.h"
+#include "RaidFileController.h"
+#include "RaidFileException.h"
+#include "RaidFileRead.h"
+#include "RaidFileUtil.h"
+#include "RaidFileWrite.h"
+#include "SSLLib.h"
+#include "ServerControl.h"
+#include "Socket.h"
+#include "SocketStreamTLS.h"
+#include "StoreStructure.h"
+#include "TLSContext.h"
+#include "Test.h"
+
+#include "MemLeakFindOn.h"
+
+typedef struct
+{
+ int ChangePoint, InsertBytes, DeleteBytes;
+ int64_t IDOnServer;
+ bool IsCompletelyDifferent;
+ bool HasBeenDeleted;
+ int64_t DepNewer, DepOlder;
+} file_info;
+
+file_info test_files[] =
+{
+// ChPnt, Insert, Delete, ID, IsCDf, BeenDel
+ {0, 0, 0, 0, false, false}, // 0 dummy first entry
+ {32000, 2087, 0, 0, false, false}, // 1
+ {1000, 1998, 2976, 0, false, false}, // 2
+ {27800, 0, 288, 0, false, false}, // 3
+ {3208, 1087, 98, 0, false, false}, // 4
+ {56000, 23087, 98, 0, false, false}, // 5
+ {0, 98765, 9999999,0, false, false}, // 6 completely different, make a break in the storage
+ {9899, 9887, 2, 0, false, false}, // 7
+ {12984, 12345, 1234, 0, false, false}, // 8
+ {1209, 29885, 3498, 0, false, false} // 9
+};
+
+// Order in which the files will be removed from the server
+int test_file_remove_order[] = {0, 2, 3, 5, 8, 1, 4, -1};
+
+#define NUMBER_FILES ((sizeof(test_files) / sizeof(test_files[0])))
+#define FIRST_FILE_SIZE (64*1024+3)
+#define BUFFER_SIZE (256*1024)
+
+// Chunk of memory to use for copying files, etc
+static void *buffer = 0;
+
+int64_t ModificationTime = 7766333330000LL;
+#define MODIFICATION_TIME_INC 10000000;
+
+// Nice random data for testing written files
+class R250 {
+public:
+ // Set up internal state table with 32-bit random numbers.
+ // The bizarre bit-twiddling is because rand() returns 16 bits of which
+ // the bottom bit is always zero! Hence, I use only some of the bits.
+ // You might want to do something better than this....
+
+ R250(int seed) : posn1(0), posn2(103)
+ {
+ // populate the state and incr tables
+ srand(seed);
+
+ for (int i = 0; i != stateLen; ++i) {
+ state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2);
+ incrTable[i] = i == stateLen - 1 ? 0 : i + 1;
+ }
+
+ // stir up the numbers to ensure they're random
+
+ for (int j = 0; j != stateLen * 4; ++j)
+ (void) next();
+ }
+
+ // Returns the next random number. Xor together two elements separated
+ // by 103 mod 250, replacing the first element with the result. Then
+ // increment the two indices mod 250.
+ inline int next()
+ {
+ int ret = (state[posn1] ^= state[posn2]); // xor and replace element
+
+ posn1 = incrTable[posn1]; // increment indices using lookup table
+ posn2 = incrTable[posn2];
+
+ return ret;
+ }
+private:
+ enum { stateLen = 250 }; // length of the state table
+ int state[stateLen]; // holds the random number state
+ int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen
+ int posn1, posn2; // indices into the state table
+};
+
+// will overrun the buffer!
+void make_random_data(void *buffer, int size, int seed)
+{
+ R250 rand(seed);
+
+ int n = (size / sizeof(int)) + 1;
+ int *b = (int*)buffer;
+ for(int l = 0; l < n; ++l)
+ {
+ b[l] = rand.next();
+ }
+}
+
+bool files_identical(const char *file1, const char *file2)
+{
+ FileStream f1(file1);
+ FileStream f2(file2);
+
+ if(f1.BytesLeftToRead() != f2.BytesLeftToRead())
+ {
+ return false;
+ }
+
+ while(f1.StreamDataLeft())
+ {
+ char buffer1[2048];
+ char buffer2[2048];
+ int s = f1.Read(buffer1, sizeof(buffer1));
+ if(f2.Read(buffer2, s) != s)
+ {
+ return false;
+ }
+ if(::memcmp(buffer1, buffer2, s) != 0)
+ {
+ return false;
+ }
+ }
+
+ if(f2.StreamDataLeft())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+
+void create_test_files()
+{
+ // Create first file
+ {
+ make_random_data(buffer, FIRST_FILE_SIZE, 98);
+ FileStream out("testfiles/0.test", O_WRONLY | O_CREAT);
+ out.Write(buffer, FIRST_FILE_SIZE);
+ }
+
+ // Create other files
+ int seed = 987;
+ for(unsigned int f = 1; f < NUMBER_FILES; ++f)
+ {
+ // Open files
+ char fnp[64];
+ sprintf(fnp, "testfiles/%d.test", f - 1);
+ FileStream previous(fnp);
+ char fnt[64];
+ sprintf(fnt, "testfiles/%d.test", f);
+ FileStream out(fnt, O_WRONLY | O_CREAT);
+
+ // Copy up to the change point
+ int b = previous.Read(buffer, test_files[f].ChangePoint, IOStream::TimeOutInfinite);
+ out.Write(buffer, b);
+
+ // Add new bytes?
+ if(test_files[f].InsertBytes > 0)
+ {
+ make_random_data(buffer, test_files[f].InsertBytes, ++seed);
+ out.Write(buffer, test_files[f].InsertBytes);
+ }
+ // Delete bytes?
+ if(test_files[f].DeleteBytes > 0)
+ {
+ previous.Seek(test_files[f].DeleteBytes, IOStream::SeekType_Relative);
+ }
+ // Copy rest of data
+ b = previous.Read(buffer, BUFFER_SIZE, IOStream::TimeOutInfinite);
+ out.Write(buffer, b);
+ }
+}
+
+void test_depends_in_dirs()
+{
+ BackupStoreFilenameClear storeFilename("test");
+
+ {
+ // Save directory with no dependency info
+ BackupStoreDirectory dir(1000, 1001); // some random ids
+ dir.AddEntry(storeFilename, 1, 2, 3, BackupStoreDirectory::Entry::Flags_File, 4);
+ dir.AddEntry(storeFilename, 1, 3, 3, BackupStoreDirectory::Entry::Flags_File, 4);
+ dir.AddEntry(storeFilename, 1, 4, 3, BackupStoreDirectory::Entry::Flags_File, 4);
+ dir.AddEntry(storeFilename, 1, 5, 3, BackupStoreDirectory::Entry::Flags_File, 4);
+ {
+ FileStream out("testfiles/dir.0", O_WRONLY | O_CREAT);
+ dir.WriteToStream(out);
+ }
+ // Add some dependency info to one of them
+ BackupStoreDirectory::Entry *en = dir.FindEntryByID(3);
+ TEST_THAT(en != 0);
+ en->SetDependsNewer(4);
+ // Save again
+ {
+ FileStream out("testfiles/dir.1", O_WRONLY | O_CREAT);
+ dir.WriteToStream(out);
+ }
+ // Check that the file size increases as expected.
+ TEST_THAT(TestGetFileSize("testfiles/dir.1") == (TestGetFileSize("testfiles/dir.0") + (4*16)));
+ }
+ {
+ // Load the directory back in
+ BackupStoreDirectory dir2;
+ FileStream in("testfiles/dir.1");
+ dir2.ReadFromStream(in, IOStream::TimeOutInfinite);
+ // Check entries
+ TEST_THAT(dir2.GetNumberOfEntries() == 4);
+ for(int i = 2; i <= 5; ++i)
+ {
+ BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i);
+ TEST_THAT(en != 0);
+ TEST_THAT(en->GetDependsNewer() == ((i == 3)?4:0));
+ TEST_THAT(en->GetDependsOlder() == 0);
+ }
+ dir2.Dump(0, true);
+ // Test that numbers go in and out as required
+ for(int i = 2; i <= 5; ++i)
+ {
+ BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i);
+ TEST_THAT(en != 0);
+ en->SetDependsNewer(i + 1);
+ en->SetDependsOlder(i - 1);
+ }
+ // Save
+ {
+ FileStream out("testfiles/dir.2", O_WRONLY | O_CREAT);
+ dir2.WriteToStream(out);
+ }
+ // Load and check
+ {
+ BackupStoreDirectory dir3;
+ FileStream in("testfiles/dir.2");
+ dir3.ReadFromStream(in, IOStream::TimeOutInfinite);
+ dir3.Dump(0, true);
+ for(int i = 2; i <= 5; ++i)
+ {
+ BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i);
+ TEST_THAT(en != 0);
+ TEST_THAT(en->GetDependsNewer() == (i + 1));
+ TEST_THAT(en->GetDependsOlder() == (i - 1));
+ }
+ }
+ }
+}
+
+
+int test(int argc, const char *argv[])
+{
+ // Allocate a buffer
+ buffer = ::malloc(BUFFER_SIZE);
+ TEST_THAT(buffer != 0);
+
+ // SSL library
+ SSLLib::Initialise();
+
+ // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used
+ // for seeing what's going on.
+ BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys");
+
+ // Trace errors out
+ SET_DEBUG_SSLLIB_TRACE_ERRORS
+
+ // Initialise the raid file controller
+ RaidFileController &rcontroller = RaidFileController::GetController();
+ rcontroller.Initialise("testfiles/raidfile.conf");
+
+ // Context
+ TLSContext context;
+ context.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+
+ // Create an account
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf "
+ "create 01234567 0 30000B 40000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Create test files
+ create_test_files();
+
+ // Check the basic directory stuff works
+ test_depends_in_dirs();
+
+ std::string storeRootDir;
+ int discSet = 0;
+ {
+ std::auto_ptr<BackupStoreAccountDatabase> apDatabase(
+ BackupStoreAccountDatabase::Read("testfiles/accounts.txt"));
+ BackupStoreAccounts accounts(*apDatabase);
+ accounts.GetAccountRoot(0x1234567, storeRootDir, discSet);
+ }
+ RaidFileDiscSet rfd(rcontroller.GetDiscSet(discSet));
+
+ int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf",
+ "testfiles/bbstored.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ TEST_THAT(ServerIsAlive(pid));
+
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Login
+ {
+ // Check the version
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ protocol.QueryLogin(0x01234567, 0);
+ }
+
+ // Filename for server
+ BackupStoreFilenameClear storeFilename("test");
+
+ // Upload the first file
+ {
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/0.test",
+ BackupProtocolClientListDirectory::RootDirectory, storeFilename));
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolClientListDirectory::RootDirectory, ModificationTime,
+ ModificationTime, 0 /* no diff from file ID */, storeFilename, *upload));
+ test_files[0].IDOnServer = stored->GetObjectID();
+ test_files[0].IsCompletelyDifferent = true;
+ ModificationTime += MODIFICATION_TIME_INC;
+ }
+
+ // Upload the other files, using the diffing process
+ for(unsigned int f = 1; f < NUMBER_FILES; ++f)
+ {
+ // Get an index for the previous version
+ std::auto_ptr<BackupProtocolClientSuccess> getBlockIndex(protocol.QueryGetBlockIndexByName(
+ BackupProtocolClientListDirectory::RootDirectory, storeFilename));
+ int64_t diffFromID = getBlockIndex->GetObjectID();
+ TEST_THAT(diffFromID != 0);
+
+ if(diffFromID != 0)
+ {
+ // Found an old version -- get the index
+ std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+
+ // Diff the file
+ char filename[64];
+ ::sprintf(filename, "testfiles/%d.test", f);
+ bool isCompletelyDifferent = false;
+ std::auto_ptr<IOStream> patchStream(
+ BackupStoreFile::EncodeFileDiff(
+ filename,
+ BackupProtocolClientListDirectory::RootDirectory, /* containing directory */
+ storeFilename,
+ diffFromID,
+ *blockIndexStream,
+ protocol.GetTimeout(),
+ NULL, // DiffTimer impl
+ 0 /* not interested in the modification time */,
+ &isCompletelyDifferent));
+
+ // Upload the patch to the store
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolClientListDirectory::RootDirectory, ModificationTime,
+ ModificationTime, isCompletelyDifferent?(0):(diffFromID), storeFilename, *patchStream));
+ ModificationTime += MODIFICATION_TIME_INC;
+
+ // Store details
+ test_files[f].IDOnServer = stored->GetObjectID();
+ test_files[f].IsCompletelyDifferent = isCompletelyDifferent;
+
+#ifdef WIN32
+ printf("ID %I64d, completely different: %s\n",
+#else
+ printf("ID %lld, completely different: %s\n",
+#endif
+ test_files[f].IDOnServer,
+ test_files[f].IsCompletelyDifferent?"yes":"no");
+ }
+ else
+ {
+ ::printf("WARNING: Block index not obtained when diffing file %d!\n", f);
+ }
+ }
+
+ // List the directory from the server, and check that no dependency info is sent -- waste of bytes
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ // Stream
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
+
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ TEST_THAT(en->GetDependsNewer() == 0);
+ TEST_THAT(en->GetDependsOlder() == 0);
+ }
+ }
+
+ // Finish the connection
+ protocol.QueryFinished();
+ conn.Close();
+ }
+
+ // Fill in initial dependency information
+ for(unsigned int f = 0; f < NUMBER_FILES; ++f)
+ {
+ int64_t newer = (f < (NUMBER_FILES - 1))?test_files[f + 1].IDOnServer:0;
+ int64_t older = (f > 0)?test_files[f - 1].IDOnServer:0;
+ if(test_files[f].IsCompletelyDifferent)
+ {
+ older = 0;
+ }
+ if(f < (NUMBER_FILES - 1) && test_files[f + 1].IsCompletelyDifferent)
+ {
+ newer = 0;
+ }
+ test_files[f].DepNewer = newer;
+ test_files[f].DepOlder = older;
+ }
+
+ // Check the stuff on the server
+ int deleteIndex = 0;
+ while(true)
+ {
+ // Load up the root directory
+ BackupStoreDirectory dir;
+ {
+ std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(0, "backup/01234567/o01"));
+ dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite);
+ dir.Dump(0, true);
+
+ // Check that dependency info is correct
+ for(unsigned int f = 0; f < NUMBER_FILES; ++f)
+ {
+ //TRACE1("t f = %d\n", f);
+ BackupStoreDirectory::Entry *en = dir.FindEntryByID(test_files[f].IDOnServer);
+ if(en == 0)
+ {
+ TEST_THAT(test_files[f].HasBeenDeleted);
+ // check that unreferenced
+ // object was removed by
+ // housekeeping
+ std::string filenameOut;
+ int startDisc = 0;
+ StoreStructure::MakeObjectFilename(
+ test_files[f].IDOnServer,
+ storeRootDir, discSet,
+ filenameOut,
+ false /* don't bother ensuring the directory exists */);
+ TEST_EQUAL(RaidFileUtil::NoFile,
+ RaidFileUtil::RaidFileExists(
+ rfd, filenameOut));
+ }
+ else
+ {
+ TEST_THAT(!test_files[f].HasBeenDeleted);
+ TEST_THAT(en->GetDependsNewer() == test_files[f].DepNewer);
+ TEST_THAT(en->GetDependsOlder() == test_files[f].DepOlder);
+ // Test that size is plausible
+ if(en->GetDependsNewer() == 0)
+ {
+ // Should be a full file
+ TEST_THAT(en->GetSizeInBlocks() > 40);
+ }
+ else
+ {
+ // Should be a patch
+ TEST_THAT(en->GetSizeInBlocks() < 40);
+ }
+ }
+ }
+ }
+
+ // Open a connection to the server (need to do this each time, otherwise housekeeping won't delete files)
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+ BackupProtocolClient protocol(conn);
+ {
+ std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ protocol.QueryLogin(0x01234567, 0);
+ }
+
+ // Pull all the files down, and check that they match the files on disc
+ for(unsigned int f = 0; f < NUMBER_FILES; ++f)
+ {
+ ::printf("r=%d, f=%d\n", deleteIndex, f);
+
+ // Might have been deleted
+ if(test_files[f].HasBeenDeleted)
+ {
+ continue;
+ }
+
+ // Filenames
+ char filename[64], filename_fetched[64];
+ ::sprintf(filename, "testfiles/%d.test", f);
+ ::sprintf(filename_fetched, "testfiles/%d.test.fetched", f);
+ ::unlink(filename_fetched);
+
+ // Fetch the file
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> getobj(protocol.QueryGetFile(
+ BackupProtocolClientListDirectory::RootDirectory,
+ test_files[f].IDOnServer));
+ TEST_THAT(getobj->GetObjectID() == test_files[f].IDOnServer);
+ // BLOCK
+ {
+ // Get stream
+ std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
+ // Get and decode
+ BackupStoreFile::DecodeFile(*filestream, filename_fetched, IOStream::TimeOutInfinite);
+ }
+ }
+ // Test for identicalness
+ TEST_THAT(files_identical(filename_fetched, filename));
+
+ // Download the index, and check it looks OK
+ {
+ std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByID(test_files[f].IDOnServer));
+ TEST_THAT(getblockindex->GetObjectID() == test_files[f].IDOnServer);
+ std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+ TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(filename, *blockIndexStream, IOStream::TimeOutInfinite));
+ }
+ }
+
+ // Close the connection
+ protocol.QueryFinished();
+ conn.Close();
+
+ // Mark one of the elements as deleted
+ if(test_file_remove_order[deleteIndex] == -1)
+ {
+ // Nothing left to do
+ break;
+ }
+ int todel = test_file_remove_order[deleteIndex++];
+
+ // Modify the entry
+ BackupStoreDirectory::Entry *pentry = dir.FindEntryByID(test_files[todel].IDOnServer);
+ TEST_THAT(pentry != 0);
+ pentry->AddFlags(BackupStoreDirectory::Entry::Flags_RemoveASAP);
+ // Save it back
+ {
+ RaidFileWrite writedir(0, "backup/01234567/o01");
+ writedir.Open(true /* overwrite */);
+ dir.WriteToStream(writedir);
+ writedir.Commit(true);
+ }
+
+#ifdef WIN32
+ // Cannot signal bbstored to do housekeeping now,
+ // so just wait until we're sure it's done
+ wait_for_operation(12, "housekeeping to run");
+#else
+ // Send the server a restart signal, so it does
+ // housekeeping immediately, and wait for it to happen
+ // Wait for old connections to terminate
+ ::sleep(1);
+ ::kill(pid, SIGHUP);
+#endif
+
+ // Get the revision number of the info file
+ int64_t first_revision = 0;
+ RaidFileRead::FileExists(0, "backup/01234567/o01", &first_revision);
+ for(int l = 0; l < 32; ++l)
+ {
+ // Sleep a while, and print a dot
+ ::sleep(1);
+ ::printf(".");
+ ::fflush(stdout);
+
+ // Early end?
+ if(l > 2)
+ {
+ int64_t revid = 0;
+ RaidFileRead::FileExists(0, "backup/01234567/o01", &revid);
+ if(revid != first_revision)
+ {
+ break;
+ }
+ }
+ }
+ ::printf("\n");
+
+ // Flag for test
+ test_files[todel].HasBeenDeleted = true;
+ // Update dependency info
+ int z = todel;
+ while(z > 0 && test_files[z].HasBeenDeleted && test_files[z].DepOlder != 0)
+ {
+ --z;
+ }
+ if(z >= 0) test_files[z].DepNewer = test_files[todel].DepNewer;
+ z = todel;
+ while(z < (int)NUMBER_FILES && test_files[z].HasBeenDeleted && test_files[z].DepNewer != 0)
+ {
+ ++z;
+ }
+ if(z < (int)NUMBER_FILES) test_files[z].DepOlder = test_files[todel].DepOlder;
+ }
+
+ // Kill store server
+ TEST_THAT(KillServer(pid));
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifndef WIN32
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+ }
+
+ ::free(buffer);
+
+ return 0;
+}
diff --git a/test/backupstorepatch/testextra b/test/backupstorepatch/testextra
new file mode 100644
index 00000000..cacda911
--- /dev/null
+++ b/test/backupstorepatch/testextra
@@ -0,0 +1,6 @@
+rm -rf testfiles
+mkdir testfiles
+mkdir testfiles/0_0
+mkdir testfiles/0_1
+mkdir testfiles/0_2
+cp ../../../test/backupstore/testfiles/*.* testfiles/
diff --git a/test/basicserver/Makefile.extra b/test/basicserver/Makefile.extra
new file mode 100644
index 00000000..50120136
--- /dev/null
+++ b/test/basicserver/Makefile.extra
@@ -0,0 +1,21 @@
+
+MAKEPROTOCOL = ../../lib/server/makeprotocol.pl
+
+GEN_CMD_SRV = $(MAKEPROTOCOL) Server testprotocol.txt
+GEN_CMD_CLI = $(MAKEPROTOCOL) Client testprotocol.txt
+
+# AUTOGEN SEEDING
+autogen_TestProtocolServer.cpp: $(MAKEPROTOCOL) testprotocol.txt
+ $(_PERL) $(GEN_CMD_SRV)
+
+autogen_TestProtocolServer.h: $(MAKEPROTOCOL) testprotocol.txt
+ $(_PERL) $(GEN_CMD_SRV)
+
+
+# AUTOGEN SEEDING
+autogen_TestProtocolClient.cpp: $(MAKEPROTOCOL) testprotocol.txt
+ $(_PERL) $(GEN_CMD_CLI)
+
+autogen_TestProtocolClient.h: $(MAKEPROTOCOL) testprotocol.txt
+ $(_PERL) $(GEN_CMD_CLI)
+
diff --git a/test/basicserver/TestCommands.cpp b/test/basicserver/TestCommands.cpp
new file mode 100644
index 00000000..657e79ea
--- /dev/null
+++ b/test/basicserver/TestCommands.cpp
@@ -0,0 +1,101 @@
+
+#include "Box.h"
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+#include "autogen_TestProtocolServer.h"
+#include "CollectInBufferStream.h"
+
+#include "MemLeakFindOn.h"
+
+
+std::auto_ptr<ProtocolObject> TestProtocolServerHello::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ if(mNumber32 != 41 || mNumber16 != 87 || mNumber8 != 11 || mText != "pingu")
+ {
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerError(0, 0));
+ }
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerHello(12,89,22,std::string("Hello world!")));
+}
+
+std::auto_ptr<ProtocolObject> TestProtocolServerLists::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerListsReply(mLotsOfText.size()));
+}
+
+std::auto_ptr<ProtocolObject> TestProtocolServerQuit::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerQuit);
+}
+
+std::auto_ptr<ProtocolObject> TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerSimpleReply(mValue+1));
+}
+
+class UncertainBufferStream : public CollectInBufferStream
+{
+public:
+ // make the collect in buffer stream pretend not to know how many bytes are left
+ pos_type BytesLeftToRead()
+ {
+ return IOStream::SizeOfStreamUnknown;
+ }
+};
+
+std::auto_ptr<ProtocolObject> TestProtocolServerGetStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ // make a new stream object
+ CollectInBufferStream *pstream = mUncertainSize?(new UncertainBufferStream):(new CollectInBufferStream);
+
+ // Data.
+ int values[24273];
+ int v = mStartingValue;
+ for(int l = 0; l < 3; ++l)
+ {
+ for(int x = 0; x < 24273; ++x)
+ {
+ values[x] = v++;
+ }
+ pstream->Write(values, sizeof(values));
+ }
+
+ // Finished
+ pstream->SetForReading();
+
+ // Get it to be sent
+ rProtocol.SendStreamAfterCommand(pstream);
+
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerGetStream(mStartingValue, mUncertainSize));
+}
+
+std::auto_ptr<ProtocolObject> TestProtocolServerSendStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ if(mValue != 0x73654353298ffLL)
+ {
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerError(0, 0));
+ }
+
+ // Get a stream
+ std::auto_ptr<IOStream> stream(rProtocol.ReceiveStream());
+ bool uncertain = (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown);
+
+ // Count how many bytes in it
+ int bytes = 0;
+ char buffer[125];
+ while(stream->StreamDataLeft())
+ {
+ bytes += stream->Read(buffer, sizeof(buffer));
+ }
+
+ // tell the caller how many bytes there were
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerGetStream(bytes, uncertain));
+}
+
+std::auto_ptr<ProtocolObject> TestProtocolServerString::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+{
+ return std::auto_ptr<ProtocolObject>(new TestProtocolServerString(mTest));
+}
+
diff --git a/test/basicserver/TestContext.cpp b/test/basicserver/TestContext.cpp
new file mode 100644
index 00000000..4b92766e
--- /dev/null
+++ b/test/basicserver/TestContext.cpp
@@ -0,0 +1,16 @@
+
+#include "Box.h"
+#include "Test.h"
+#include "TestContext.h"
+
+#include "MemLeakFindOn.h"
+
+TestContext::TestContext()
+{
+}
+
+TestContext::~TestContext()
+{
+}
+
+
diff --git a/test/basicserver/TestContext.h b/test/basicserver/TestContext.h
new file mode 100644
index 00000000..d0c28349
--- /dev/null
+++ b/test/basicserver/TestContext.h
@@ -0,0 +1,7 @@
+
+class TestContext
+{
+public:
+ TestContext();
+ ~TestContext();
+};
diff --git a/test/basicserver/testbasicserver.cpp b/test/basicserver/testbasicserver.cpp
new file mode 100644
index 00000000..18bc0aa8
--- /dev/null
+++ b/test/basicserver/testbasicserver.cpp
@@ -0,0 +1,772 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testbasicserver.cpp
+// Purpose: Test basic server classes
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <typeinfo>
+
+#include "Test.h"
+#include "Daemon.h"
+#include "Configuration.h"
+#include "ServerStream.h"
+#include "SocketStream.h"
+#include "IOStreamGetLine.h"
+#include "ServerTLS.h"
+#include "CollectInBufferStream.h"
+
+#include "TestContext.h"
+#include "autogen_TestProtocolClient.h"
+#include "autogen_TestProtocolServer.h"
+#include "ServerControl.h"
+
+#include "MemLeakFindOn.h"
+
+#define SERVER_LISTEN_PORT 2003
+
+// in ms
+#define COMMS_READ_TIMEOUT 4
+#define COMMS_SERVER_WAIT_BEFORE_REPLYING 40
+
+class basicdaemon : public Daemon
+{
+public:
+basicdaemon() {};
+~basicdaemon() {}
+virtual void Run();
+};
+
+void basicdaemon::Run()
+{
+ // Write a file to check it's done...
+ const Configuration &c(GetConfiguration());
+
+ FILE *f = fopen(c.GetKeyValue("TestFile").c_str(), "w");
+ fclose(f);
+
+ while(!StopRun())
+ {
+ ::sleep(10);
+ }
+}
+
+void testservers_pause_before_reply()
+{
+#ifdef WIN32
+ Sleep(COMMS_SERVER_WAIT_BEFORE_REPLYING);
+#else
+ struct timespec t;
+ t.tv_sec = 0;
+ t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000; // convert to ns
+ ::nanosleep(&t, NULL);
+#endif
+}
+
+#define LARGE_DATA_BLOCK_SIZE 19870
+#define LARGE_DATA_SIZE (LARGE_DATA_BLOCK_SIZE*1000)
+
+void testservers_connection(SocketStream &rStream)
+{
+ IOStreamGetLine getline(rStream);
+
+ if(typeid(rStream) == typeid(SocketStreamTLS))
+ {
+ // need to wait for some data before sending stuff, otherwise timeout test doesn't work
+ std::string line;
+ while(!getline.GetLine(line))
+ ;
+ SocketStreamTLS &rtls = (SocketStreamTLS&)rStream;
+ std::string line1("CONNECTED:");
+ line1 += rtls.GetPeerCommonName();
+ line1 += '\n';
+ testservers_pause_before_reply();
+ rStream.Write(line1.c_str(), line1.size());
+ }
+
+ while(!getline.IsEOF())
+ {
+ std::string line;
+ while(!getline.GetLine(line))
+ ;
+ if(line == "QUIT")
+ {
+ break;
+ }
+ if(line == "LARGEDATA")
+ {
+ {
+ // Send lots of data
+ char data[LARGE_DATA_BLOCK_SIZE];
+ for(unsigned int y = 0; y < sizeof(data); y++)
+ {
+ data[y] = y & 0xff;
+ }
+ for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
+ {
+ rStream.Write(data, sizeof(data));
+ }
+ }
+ {
+ // Receive lots of data
+ char buf[1024];
+ int total = 0;
+ int r = 0;
+ while(total < LARGE_DATA_SIZE && (r = rStream.Read(buf, sizeof(buf))) != 0)
+ {
+ total += r;
+ }
+ TEST_THAT(total == LARGE_DATA_SIZE);
+ if (total != LARGE_DATA_SIZE)
+ {
+ BOX_ERROR("Expected " <<
+ LARGE_DATA_SIZE << " bytes " <<
+ "but was " << total);
+ return;
+ }
+ }
+ {
+ // Send lots of data again
+ char data[LARGE_DATA_BLOCK_SIZE];
+ for(unsigned int y = 0; y < sizeof(data); y++)
+ {
+ data[y] = y & 0xff;
+ }
+ for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
+ {
+ rStream.Write(data, sizeof(data));
+ }
+ }
+
+ // next!
+ continue;
+ }
+ std::string backwards;
+ for(std::string::const_reverse_iterator i(line.end()); i != std::string::const_reverse_iterator(line.begin()); ++i)
+ {
+ backwards += (*i);
+ }
+ backwards += '\n';
+ testservers_pause_before_reply();
+ rStream.Write(backwards.c_str(), backwards.size());
+ }
+ rStream.Shutdown();
+ rStream.Close();
+}
+
+
+
+class testserver : public ServerStream<SocketStream, SERVER_LISTEN_PORT>
+{
+public:
+ testserver() {}
+ ~testserver() {}
+
+ void Connection(SocketStream &rStream);
+
+ virtual const char *DaemonName() const
+ {
+ return "test-srv2";
+ }
+ const ConfigurationVerify *GetConfigVerify() const;
+
+};
+
+const ConfigurationVerify *testserver::GetConfigVerify() const
+{
+ static ConfigurationVerifyKey verifyserverkeys[] =
+ {
+ SERVERSTREAM_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses
+ };
+
+ static ConfigurationVerify verifyserver[] =
+ {
+ {
+ "Server",
+ 0,
+ verifyserverkeys,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+ };
+
+ static ConfigurationVerify verify =
+ {
+ "root",
+ verifyserver,
+ 0,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ };
+
+ return &verify;
+}
+
+void testserver::Connection(SocketStream &rStream)
+{
+ testservers_connection(rStream);
+}
+
+class testProtocolServer : public testserver
+{
+public:
+ testProtocolServer() {}
+ ~testProtocolServer() {}
+
+ void Connection(SocketStream &rStream);
+
+ virtual const char *DaemonName() const
+ {
+ return "test-srv4";
+ }
+};
+
+void testProtocolServer::Connection(SocketStream &rStream)
+{
+ TestProtocolServer server(rStream);
+ TestContext context;
+ server.DoServer(context);
+}
+
+
+class testTLSserver : public ServerTLS<SERVER_LISTEN_PORT>
+{
+public:
+ testTLSserver() {}
+ ~testTLSserver() {}
+
+ void Connection(SocketStreamTLS &rStream);
+
+ virtual const char *DaemonName() const
+ {
+ return "test-srv3";
+ }
+ const ConfigurationVerify *GetConfigVerify() const;
+
+};
+
+const ConfigurationVerify *testTLSserver::GetConfigVerify() const
+{
+ static ConfigurationVerifyKey verifyserverkeys[] =
+ {
+ SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses
+ };
+
+ static ConfigurationVerify verifyserver[] =
+ {
+ {
+ "Server",
+ 0,
+ verifyserverkeys,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+ };
+
+ static ConfigurationVerify verify =
+ {
+ "root",
+ verifyserver,
+ 0,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ };
+
+ return &verify;
+}
+
+void testTLSserver::Connection(SocketStreamTLS &rStream)
+{
+ testservers_connection(rStream);
+}
+
+
+void Srv2TestConversations(const std::vector<IOStream *> &conns)
+{
+ const static char *tosend[] = {
+ "test 1\n", "carrots\n", "pineapples\n", "booo!\n", 0
+ };
+ const static char *recieve[] = {
+ "1 tset", "storrac", "selppaenip", "!ooob", 0
+ };
+
+ IOStreamGetLine **getline = new IOStreamGetLine*[conns.size()];
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ getline[c] = new IOStreamGetLine(*conns[c]);
+
+ bool hadTimeout = false;
+ if(typeid(*conns[c]) == typeid(SocketStreamTLS))
+ {
+ SocketStreamTLS *ptls = (SocketStreamTLS *)conns[c];
+ printf("Connected to '%s'\n", ptls->GetPeerCommonName().c_str());
+
+ // Send some data, any data, to get the first response.
+ conns[c]->Write("Hello\n", 6);
+
+ std::string line1;
+ while(!getline[c]->GetLine(line1, false, COMMS_READ_TIMEOUT))
+ hadTimeout = true;
+ TEST_THAT(line1 == "CONNECTED:CLIENT");
+ TEST_THAT(hadTimeout)
+ }
+ }
+
+ for(int q = 0; tosend[q] != 0; ++q)
+ {
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ //printf("%d: %s", c, tosend[q]);
+ conns[c]->Write(tosend[q], strlen(tosend[q]));
+ std::string rep;
+ bool hadTimeout = false;
+ while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT))
+ hadTimeout = true;
+ TEST_THAT(rep == recieve[q]);
+ TEST_THAT(hadTimeout)
+ }
+ }
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ conns[c]->Write("LARGEDATA\n", 10);
+ }
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ // Receive lots of data
+ char buf[1024];
+ int total = 0;
+ int r = 0;
+ while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0)
+ {
+ total += r;
+ }
+ TEST_THAT(total == LARGE_DATA_SIZE);
+ }
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ // Send lots of data
+ char data[LARGE_DATA_BLOCK_SIZE];
+ for(unsigned int y = 0; y < sizeof(data); y++)
+ {
+ data[y] = y & 0xff;
+ }
+ for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s)
+ {
+ conns[c]->Write(data, sizeof(data));
+ }
+ }
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ // Receive lots of data again
+ char buf[1024];
+ int total = 0;
+ int r = 0;
+ while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0)
+ {
+ total += r;
+ }
+ TEST_THAT(total == LARGE_DATA_SIZE);
+ }
+
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ conns[c]->Write("QUIT\n", 5);
+ }
+
+ for(unsigned int c = 0; c < conns.size(); ++c)
+ {
+ if ( getline[c] ) delete getline[c];
+ getline[c] = 0;
+ }
+ if ( getline ) delete [] getline;
+ getline = 0;
+}
+
+void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream)
+{
+ std::auto_ptr<TestProtocolClientGetStream> reply(protocol.QueryGetStream(value, uncertainstream));
+ TEST_THAT(reply->GetStartingValue() == value);
+
+ // Get a stream
+ std::auto_ptr<IOStream> stream(protocol.ReceiveStream());
+
+ // check uncertainty
+ TEST_THAT(uncertainstream == (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown));
+
+ printf("stream is %s\n", uncertainstream?"uncertain size":"fixed size");
+
+ // Then check the contents
+ int values[998];
+ int v = value;
+ int count = 0;
+ int bytesleft = 0;
+ int bytessofar = 0;
+ while(stream->StreamDataLeft())
+ {
+ // Read some data
+ int bytes = stream->Read(((char*)values) + bytesleft, sizeof(values) - bytesleft);
+ bytessofar += bytes;
+ bytes += bytesleft;
+ int n = bytes / 4;
+ //printf("read %d, n = %d, so far = %d\n", bytes, n, bytessofar);
+ for(int t = 0; t < n; ++t)
+ {
+ if(values[t] != v) printf("%d, %d, %d\n", t, values[t], v);
+ TEST_THAT(values[t] == v++);
+ }
+ count += n;
+ bytesleft = bytes - (n*4);
+ if(bytesleft) ::memmove(values, ((char*)values) + bytes - bytesleft, bytesleft);
+ }
+
+ TEST_THAT(bytesleft == 0);
+ TEST_THAT(count == (24273*3)); // over 64 k of data, definately
+}
+
+
+int test(int argc, const char *argv[])
+{
+ // Server launching stuff
+ if(argc >= 2)
+ {
+ // this is a quick hack to allow passing some options
+ // to the daemon
+
+ const char* mode = argv[1];
+
+ if (test_args.length() > 0)
+ {
+ argv[1] = test_args.c_str();
+ }
+ else
+ {
+ argc--;
+ argv++;
+ }
+
+ if(strcmp(mode, "srv1") == 0)
+ {
+ // Run very basic daemon
+ basicdaemon daemon;
+ return daemon.Main("doesnotexist", argc, argv);
+ }
+ else if(strcmp(mode, "srv2") == 0)
+ {
+ // Run daemon which accepts connections
+ testserver daemon;
+ return daemon.Main("doesnotexist", argc, argv);
+ }
+ else if(strcmp(mode, "srv3") == 0)
+ {
+ testTLSserver daemon;
+ return daemon.Main("doesnotexist", argc, argv);
+ }
+ else if(strcmp(mode, "srv4") == 0)
+ {
+ testProtocolServer daemon;
+ return daemon.Main("doesnotexist", argc, argv);
+ }
+ }
+
+ //printf("SKIPPING TESTS------------------------\n");
+ //goto protocolserver;
+
+ // Launch a basic server
+ {
+ std::string cmd = "./test --test-daemon-args=";
+ cmd += test_args;
+ cmd += " srv1 testfiles/srv1.conf";
+ int pid = LaunchServer(cmd, "testfiles/srv1.pid");
+
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ // Check that it's written the expected file
+ TEST_THAT(TestFileExists("testfiles"
+ DIRECTORY_SEPARATOR "srv1.test1"));
+ TEST_THAT(ServerIsAlive(pid));
+
+ // Move the config file over
+ #ifdef WIN32
+ TEST_THAT(::unlink("testfiles"
+ DIRECTORY_SEPARATOR "srv1.conf") != -1);
+ #endif
+
+ TEST_THAT(::rename(
+ "testfiles" DIRECTORY_SEPARATOR "srv1b.conf",
+ "testfiles" DIRECTORY_SEPARATOR "srv1.conf")
+ != -1);
+
+ #ifndef WIN32
+ // Get it to reread the config file
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ // Check that new file exists
+ TEST_THAT(TestFileExists("testfiles"
+ DIRECTORY_SEPARATOR "srv1.test2"));
+ #endif // !WIN32
+
+ // Kill it off
+ TEST_THAT(KillServer(pid));
+
+ #ifndef WIN32
+ TestRemoteProcessMemLeaks(
+ "generic-daemon.memleaks");
+ #endif // !WIN32
+ }
+ }
+
+ // Launch a test forking server
+ {
+ std::string cmd = "./test --test-daemon-args=";
+ cmd += test_args;
+ cmd += " srv2 testfiles/srv2.conf";
+ int pid = LaunchServer(cmd, "testfiles/srv2.pid");
+
+ TEST_THAT(pid != -1 && pid != 0);
+
+ if(pid > 0)
+ {
+ // Will it restart?
+ TEST_THAT(ServerIsAlive(pid));
+
+ #ifndef WIN32
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ #endif // !WIN32
+
+ // Make some connections
+ {
+ SocketStream conn1;
+ conn1.Open(Socket::TypeINET, "localhost", 2003);
+
+ #ifndef WIN32
+ SocketStream conn2;
+ conn2.Open(Socket::TypeUNIX,
+ "testfiles/srv2.sock");
+ SocketStream conn3;
+ conn3.Open(Socket::TypeINET,
+ "localhost", 2003);
+ #endif // !WIN32
+
+ // Quick check that reconnections fail
+ TEST_CHECK_THROWS(conn1.Open(Socket::TypeUNIX,
+ "testfiles/srv2.sock");,
+ ServerException, SocketAlreadyOpen);
+
+ // Stuff some data around
+ std::vector<IOStream *> conns;
+ conns.push_back(&conn1);
+
+ #ifndef WIN32
+ conns.push_back(&conn2);
+ conns.push_back(&conn3);
+ #endif // !WIN32
+
+ Srv2TestConversations(conns);
+ // Implicit close
+ }
+
+ #ifndef WIN32
+ // HUP again
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ #endif // !WIN32
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifndef WIN32
+ TestRemoteProcessMemLeaks("test-srv2.memleaks");
+ #endif // !WIN32
+ }
+ }
+
+ // Launch a test SSL server
+ {
+ std::string cmd = "./test --test-daemon-args=";
+ cmd += test_args;
+ cmd += " srv3 testfiles/srv3.conf";
+ int pid = LaunchServer(cmd, "testfiles/srv3.pid");
+
+ TEST_THAT(pid != -1 && pid != 0);
+
+ if(pid > 0)
+ {
+ // Will it restart?
+ TEST_THAT(ServerIsAlive(pid));
+
+ #ifndef WIN32
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ #endif
+
+ // Make some connections
+ {
+ // SSL library
+ SSLLib::Initialise();
+
+ // Context first
+ TLSContext context;
+ context.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+
+ SocketStreamTLS conn1;
+ conn1.Open(context, Socket::TypeINET, "localhost", 2003);
+ #ifndef WIN32
+ SocketStreamTLS conn2;
+ conn2.Open(context, Socket::TypeUNIX,
+ "testfiles/srv3.sock");
+ SocketStreamTLS conn3;
+ conn3.Open(context, Socket::TypeINET,
+ "localhost", 2003);
+ #endif
+
+ // Quick check that reconnections fail
+ TEST_CHECK_THROWS(conn1.Open(context,
+ Socket::TypeUNIX,
+ "testfiles/srv3.sock");,
+ ServerException, SocketAlreadyOpen);
+
+ // Stuff some data around
+ std::vector<IOStream *> conns;
+ conns.push_back(&conn1);
+
+ #ifndef WIN32
+ conns.push_back(&conn2);
+ conns.push_back(&conn3);
+ #endif
+
+ Srv2TestConversations(conns);
+ // Implicit close
+ }
+
+ #ifndef WIN32
+ // HUP again
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ #endif
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifndef WIN32
+ TestRemoteProcessMemLeaks("test-srv3.memleaks");
+ #endif
+ }
+ }
+
+//protocolserver:
+ // Launch a test protocol handling server
+ {
+ std::string cmd = "./test --test-daemon-args=";
+ cmd += test_args;
+ cmd += " srv4 testfiles/srv4.conf";
+ int pid = LaunchServer(cmd, "testfiles/srv4.pid");
+
+ TEST_THAT(pid != -1 && pid != 0);
+
+ if(pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // Open a connection to it
+ SocketStream conn;
+ #ifdef WIN32
+ conn.Open(Socket::TypeINET, "localhost", 2003);
+ #else
+ conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock");
+ #endif
+
+ // Create a protocol
+ TestProtocolClient protocol(conn);
+
+ // Simple query
+ {
+ std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(41));
+ TEST_THAT(reply->GetValuePlusOne() == 42);
+ }
+ {
+ std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(809));
+ TEST_THAT(reply->GetValuePlusOne() == 810);
+ }
+
+ // Streams, twice, both uncertain and certain sizes
+ TestStreamReceive(protocol, 374, false);
+ TestStreamReceive(protocol, 23983, true);
+ TestStreamReceive(protocol, 12098, false);
+ TestStreamReceive(protocol, 4342, true);
+
+ // Try to send a stream
+ {
+ CollectInBufferStream s;
+ char buf[1663];
+ s.Write(buf, sizeof(buf));
+ s.SetForReading();
+ std::auto_ptr<TestProtocolClientGetStream> reply(protocol.QuerySendStream(0x73654353298ffLL, s));
+ TEST_THAT(reply->GetStartingValue() == sizeof(buf));
+ }
+
+ // Lots of simple queries
+ for(int q = 0; q < 514; q++)
+ {
+ std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(q));
+ TEST_THAT(reply->GetValuePlusOne() == (q+1));
+ }
+ // Send a list of strings to it
+ {
+ std::vector<std::string> strings;
+ strings.push_back(std::string("test1"));
+ strings.push_back(std::string("test2"));
+ strings.push_back(std::string("test3"));
+ std::auto_ptr<TestProtocolClientListsReply> reply(protocol.QueryLists(strings));
+ TEST_THAT(reply->GetNumberOfStrings() == 3);
+ }
+
+ // And another
+ {
+ std::auto_ptr<TestProtocolClientHello> reply(protocol.QueryHello(41,87,11,std::string("pingu")));
+ TEST_THAT(reply->GetNumber32() == 12);
+ TEST_THAT(reply->GetNumber16() == 89);
+ TEST_THAT(reply->GetNumber8() == 22);
+ TEST_THAT(reply->GetText() == "Hello world!");
+ }
+
+ // Quit query to finish
+ protocol.QueryQuit();
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifndef WIN32
+ TestRemoteProcessMemLeaks("test-srv4.memleaks");
+ #endif
+ }
+ }
+
+ return 0;
+}
+
diff --git a/test/basicserver/testfiles/clientCerts.pem b/test/basicserver/testfiles/clientCerts.pem
new file mode 100644
index 00000000..81d4c5cc
--- /dev/null
+++ b/test/basicserver/testfiles/clientCerts.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICOTCCAaICAQUwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV
+BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD
+VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDQz
+WhcNMzEwMTIyMTYyNDQzWjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u
+MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj
+IHNlcnZlcjEPMA0GA1UEAxMGQ0xJRU5UMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Zcr+TRrHk
+Q8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65y417qk5z
+NjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQABMA0GCSqG
+SIb3DQEBBQUAA4GBACYkSYlrKNv1v6lrES4j68S8u8SNlnSM+Z4pTHF/7K7SQeIn
+SKVV8EI8CLR5jIsQRRHKB9rYgYS4kB8SFbPyrsH8VKngjIUcjmTKLq9zpAt2zDNo
+m+y5SMXsaJF6Xbtbz+MSxXZZ6YBBuseY+Wkpz4ZGSVlQrHxjsuYdBFHIguM3
+-----END CERTIFICATE-----
diff --git a/test/basicserver/testfiles/clientPrivKey.pem b/test/basicserver/testfiles/clientPrivKey.pem
new file mode 100644
index 00000000..a4797b66
--- /dev/null
+++ b/test/basicserver/testfiles/clientPrivKey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Z
+cr+TRrHkQ8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65
+y417qk5zNjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQAB
+AoGAF92enbH158KaMnp/tlLqMrI7It5R5z4YRJLgMnBFl9j6pqPZEI9ge79N/L/Y
+2WSZXE7sLCaUktYwkc9LkOXkBYQI7EIOonLdmSsNCMbSBVbeczdM77dBscuCTKva
+nvre/2+hlmuWBNINqXlprBkvd5YF4Q/yeXzoXPuMIQ0tROECQQDqifOZOfCle8uA
+CgdHT9pO638PwrrldMHmZSK3gUmHmFe7ziGpNGCfKZ+wkSIvDg9INQvEXvQfLZiV
+n4J78IOHAkEAzB0SoUU0cL+wK3OQTTOlx4cgxaxgtsuvccIhqTh4Jp1Aj9iMKiPW
+yXvbGhDBTZP2IL5HoqSLc3SxfXgvn6O/nQJBALgJMYWdalBf2GoK9HUnmpTsw1I5
+qe/c8z13RIubvnfQuZ8be1xLRjn+LlkdOSaVMLanMSmQnJxOafmWJYxdSMcCQFBc
+5ffe8n2tyyPgdSEgQ5YiatHJQ67U1Te50lz44b16TnAUN2NkBu3/OM2zaRgtOEu9
+/yBXHpyPhk47Iqz84LUCQQCIDIKluoughLVjJS2eD28UJHM9Z+OvmyIE0fF0Q0vi
+E+Rn/+iWCoEJYa7WP5AEo/aeVXiCeHONXGF1AI8a8gb5
+-----END RSA PRIVATE KEY-----
diff --git a/test/basicserver/testfiles/clientReq.pem b/test/basicserver/testfiles/clientReq.pem
new file mode 100644
index 00000000..14e2c6df
--- /dev/null
+++ b/test/basicserver/testfiles/clientReq.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G
+A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2
+ZXIxDzANBgNVBAMTBkNMSUVOVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+uwCQ0CXRh4uBzu1V2SXYv9wvK0Mdbk8TDukBwRXqdMEGeiN+2XK/k0ax5EPAnpcp
+iAcdmX0FzCyrKAfBUnCKdMyE/3aytAIIblhOVD1OnZ7T5awOucuNe6pOczY4jsun
+KFpfu5LWdCLQrsu1R1q05h0WzWXlyrm+347nHAyXYssCAwEAAaAAMA0GCSqGSIb3
+DQEBBQUAA4GBAKV3H/yWrYep6yfEDQp61zn60tEnJOS5LVbuV7ivNjAue0/09wBT
+PGzTblwx116AT9GbTcbERK/ll549+tziTLT9NUT12ZcvaRezYP2PpaD8fiDKHs3D
+vSwpFoihLmUnDeMWE9Vbt+b0Fl/mdsH6sm3Mo0COG/DkolOVsydOj2Hp
+-----END CERTIFICATE REQUEST-----
diff --git a/test/basicserver/testfiles/clientTrustedCAs.pem b/test/basicserver/testfiles/clientTrustedCAs.pem
new file mode 100644
index 00000000..d72b70e5
--- /dev/null
+++ b/test/basicserver/testfiles/clientTrustedCAs.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV
+BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD
+VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4
+WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u
+MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj
+IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y
+3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K
+bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG
+9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu
+leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH
+1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ==
+-----END CERTIFICATE-----
diff --git a/test/basicserver/testfiles/key-creation.txt b/test/basicserver/testfiles/key-creation.txt
new file mode 100644
index 00000000..51f4eb77
--- /dev/null
+++ b/test/basicserver/testfiles/key-creation.txt
@@ -0,0 +1,83 @@
+$ openssl genrsa -out rootkey.pem 1024
+
+$ openssl req -new -key rootkey.pem -sha1 -out rootreq.pem
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) []:GB
+State or Province Name (full name) []:London
+Locality Name (eg, city) []:London
+Organization Name (eg, company) []:Test
+Organizational Unit Name (eg, section) []:basic server
+Common Name (eg, fully qualified host name) []:ROOT
+Email Address []:
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:
+An optional company name []:
+
+$ openssl x509 -req -in rootreq.pem -sha1 -extensions v3_ca -signkey rootkey.pem -out rootcert.pem -days 10000
+Signature ok
+subject=/C=GB/ST=London/L=London/O=Test/OU=basic server/CN=ROOT
+Getting Private key
+
+$ cp rootcert.pem serverTrustedCAs.pem
+$ cp rootcert.pem clientTrustedCAs.pem
+
+$ openssl genrsa -out clientPrivKey.pem 1024
+$ openssl req -new -key clientPrivKey.pem -sha1 -out clientReq.pem
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) []:GB
+State or Province Name (full name) []:London
+Locality Name (eg, city) []:London
+Organization Name (eg, company) []:Test
+Organizational Unit Name (eg, section) []:basic server
+Common Name (eg, fully qualified host name) []:CLIENT
+Email Address []:
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:
+An optional company name []:
+
+$ cat rootcert.pem rootkey.pem > root.pem
+
+$ echo 01 > root.srl
+
+$ openssl x509 -req -in clientReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out clientCerts.pem -days 10000
+
+$ openssl genrsa -out serverPrivKey.pem 1024
+$ openssl req -new -key serverPrivKey.pem -sha1 -out serverReq.pem
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) []:GB
+State or Province Name (full name) []:London
+Locality Name (eg, city) []:London
+Organization Name (eg, company) []:Test
+Organizational Unit Name (eg, section) []:basic server
+Common Name (eg, fully qualified host name) []:SERVER
+Email Address []:
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:
+An optional company name []:
+
+$ openssl x509 -req -in serverReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out serverCerts.pem -days 10000
+
diff --git a/test/basicserver/testfiles/root.pem b/test/basicserver/testfiles/root.pem
new file mode 100644
index 00000000..020c25c3
--- /dev/null
+++ b/test/basicserver/testfiles/root.pem
@@ -0,0 +1,29 @@
+-----BEGIN CERTIFICATE-----
+MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV
+BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD
+VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4
+WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u
+MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj
+IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y
+3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K
+bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG
+9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu
+leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH
+1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t
+gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI
+UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB
+AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW
+g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm
+ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja
+pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw
+iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb
+Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc
+lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x
+fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS
+8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS
+GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep
+-----END RSA PRIVATE KEY-----
diff --git a/test/basicserver/testfiles/root.srl b/test/basicserver/testfiles/root.srl
new file mode 100644
index 00000000..2c7456e3
--- /dev/null
+++ b/test/basicserver/testfiles/root.srl
@@ -0,0 +1 @@
+07
diff --git a/test/basicserver/testfiles/rootcert.pem b/test/basicserver/testfiles/rootcert.pem
new file mode 100644
index 00000000..d72b70e5
--- /dev/null
+++ b/test/basicserver/testfiles/rootcert.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV
+BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD
+VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4
+WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u
+MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj
+IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y
+3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K
+bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG
+9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu
+leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH
+1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ==
+-----END CERTIFICATE-----
diff --git a/test/basicserver/testfiles/rootkey.pem b/test/basicserver/testfiles/rootkey.pem
new file mode 100644
index 00000000..4eb0f59d
--- /dev/null
+++ b/test/basicserver/testfiles/rootkey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t
+gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI
+UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB
+AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW
+g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm
+ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja
+pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw
+iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb
+Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc
+lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x
+fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS
+8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS
+GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep
+-----END RSA PRIVATE KEY-----
diff --git a/test/basicserver/testfiles/rootreq.pem b/test/basicserver/testfiles/rootreq.pem
new file mode 100644
index 00000000..6da1e428
--- /dev/null
+++ b/test/basicserver/testfiles/rootreq.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBpDCCAQ0CAQAwZDELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G
+A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2
+ZXIxDTALBgNVBAMTBFJPT1QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMxh
+cn4gjQKT2JyvAN4bKZ1ncCHtE6YLfYulLexBVyFB3vnY7i2DJNQ/p3just0zbGxW
+N1Ed5v0AP4NWvMM1Fau09jFAzKM+FfB5DP+PDEDJVB3JgQhSR5xgC9YPCm31/rMX
+kilyrq52TG0TckcKdHfUQ5bUJxy36nKDwoGCPeRTAgMBAAGgADANBgkqhkiG9w0B
+AQUFAAOBgQCmy4L/D/m1Q23y+WB1Ub2u1efl0sb7zMWNzHsD/IR1CXSvXmAfPpr2
+hpJQj118ccaTqkRhA8gwhktMTBuGH5KiOLHYXRlniKo3G0yr0+fHWnjclZ+m6Bg1
+9HjJZYqIWRMQ78+wTpLCxliX6yp0JxMdx/v6/7jx3BtXz8cyU8ANAw==
+-----END CERTIFICATE REQUEST-----
diff --git a/test/basicserver/testfiles/serverCerts.pem b/test/basicserver/testfiles/serverCerts.pem
new file mode 100644
index 00000000..f61c554e
--- /dev/null
+++ b/test/basicserver/testfiles/serverCerts.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICOTCCAaICAQYwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV
+BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD
+VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNTA0
+WhcNMzEwMTIyMTYyNTA0WjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u
+MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj
+IHNlcnZlcjEPMA0GA1UEAxMGU0VSVkVSMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40XsmCEdHnDl
+AjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrYaQkgME2O
+mPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQABMA0GCSqG
+SIb3DQEBBQUAA4GBALgh7u/7GZUMjzOPGuIenkdrsP0Gbst7wuXrLaMrAMlAaWMH
+E9AgU/6Q9+2yFxisgAzRmyKydNP4E4YomsE8rbx08vGw/6Rc7L19/UsFJxeNC5Ue
+6hziI9boB9LL5em4N8v+z4yhGvj2CrKzBxLNy8MYPi2S3KfQ69FdipvRQRp/
+-----END CERTIFICATE-----
diff --git a/test/basicserver/testfiles/serverPrivKey.pem b/test/basicserver/testfiles/serverPrivKey.pem
new file mode 100644
index 00000000..f2d73fd4
--- /dev/null
+++ b/test/basicserver/testfiles/serverPrivKey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40Xs
+mCEdHnDlAjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrY
+aQkgME2OmPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQAB
+AoGBAJSH7zAC9OmXXHoGhWeQEbzO+yT6aHxdY8/KGeBZUMasYB7qqZb8eYWbToYm
+nS2cpVAh0gHZcfrdyuDwSQpPQIIA8gAPFHqR8T8VGrpChxgetYzkoPDapmcqKU4H
+YobFVA1gypK1IM5z3Z5kargqGmmzRIxX8BwWr6FGmFPp2+NBAkEA7A17g4JewNtY
+vtpM0NhIyw+7HN3ljf+pAvHM2pMw1Wk8TrbPJNQ20ZWnhGMdIvP0m25zna6pShL6
+0laf5EUWFQJBANx1SJ+Xb3P9IyrIlyMhrsYvAveezh6wimjAFFNYWmGEZ6uuHM5P
+eBSc3P0x0LbFKlGQWomxMb3ULwpjEueX9HkCQDMf0GpxJ/h5CUV8njp1PX7NT2c3
+H+qbPo2mtQl564+tFSSvLzn4xE6sLPXdSYgycf3f9CZol721UqGPpV2ZIOkCQQCQ
+trxxZmrW7LgFAZ+UhCvCFGISQcB0DNcOY+fzve+2S7/xxl1KYIgmn8HAws6K62oY
+GHYWJKbOQVaPrvFd7TWhAkA8VQPjDSRkdg2fU5RDTRfOQBczgc8aHTiqAv/S2g47
+lpsw8CLitobBvi3e5XuBKNIbnjeoZMbHcBZ+RXAAZe/Q
+-----END RSA PRIVATE KEY-----
diff --git a/test/basicserver/testfiles/serverReq.pem b/test/basicserver/testfiles/serverReq.pem
new file mode 100644
index 00000000..ce510fae
--- /dev/null
+++ b/test/basicserver/testfiles/serverReq.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G
+A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2
+ZXIxDzANBgNVBAMTBlNFUlZFUjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+y0e7RWnjbwnbwuZ0IH2IIBR8WTOTXLMSUmHBduxoT8pRxONF7JghHR5w5QI1rOPB
+q2De7X02u/yRBTPbO7IBfTsY/rbITEzWb8duboofIBxQoNEa2GkJIDBNjpj7j7RX
+AP4rEzAijuw/2htkmqY2PP15HtE7Hijt43z/S7cWc+0CAwEAAaAAMA0GCSqGSIb3
+DQEBBQUAA4GBAGdUCS76aBzPw4zcU999r6gE7/F8/bYlT/tr2SEyKzF+vC0widZN
+P3bg9IaNAWi84vw8WEB+j2wM3TPB5/kSKFpO2MxOHPERX+aOXh6JkN6a/ay5CDOT
+r/wCERRkqY2gphU5m3/S0Gd7wLbH/neBgNsHUzbNwwQ+uqkF2NRGg0V/
+-----END CERTIFICATE REQUEST-----
diff --git a/test/basicserver/testfiles/serverTrustedCAs.pem b/test/basicserver/testfiles/serverTrustedCAs.pem
new file mode 100644
index 00000000..d72b70e5
--- /dev/null
+++ b/test/basicserver/testfiles/serverTrustedCAs.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV
+BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD
+VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4
+WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u
+MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj
+IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y
+3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K
+bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG
+9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu
+leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH
+1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ==
+-----END CERTIFICATE-----
diff --git a/test/basicserver/testfiles/srv1.conf b/test/basicserver/testfiles/srv1.conf
new file mode 100644
index 00000000..ee68704e
--- /dev/null
+++ b/test/basicserver/testfiles/srv1.conf
@@ -0,0 +1,6 @@
+Server
+{
+ PidFile = testfiles/srv1.pid
+}
+
+TestFile = testfiles/srv1.test1
diff --git a/test/basicserver/testfiles/srv1b.conf b/test/basicserver/testfiles/srv1b.conf
new file mode 100644
index 00000000..d6d6eebd
--- /dev/null
+++ b/test/basicserver/testfiles/srv1b.conf
@@ -0,0 +1,6 @@
+Server
+{
+ PidFile = testfiles/srv1.pid
+}
+
+TestFile = testfiles/srv1.test2
diff --git a/test/basicserver/testfiles/srv2.conf b/test/basicserver/testfiles/srv2.conf
new file mode 100644
index 00000000..ef1d7c49
--- /dev/null
+++ b/test/basicserver/testfiles/srv2.conf
@@ -0,0 +1,6 @@
+Server
+{
+ PidFile = testfiles/srv2.pid
+ ListenAddresses = inet:localhost,unix:testfiles/srv2.sock
+}
+
diff --git a/test/basicserver/testfiles/srv3.conf b/test/basicserver/testfiles/srv3.conf
new file mode 100644
index 00000000..e2211553
--- /dev/null
+++ b/test/basicserver/testfiles/srv3.conf
@@ -0,0 +1,9 @@
+Server
+{
+ PidFile = testfiles/srv3.pid
+ ListenAddresses = inet:localhost,unix:testfiles/srv3.sock
+ CertificateFile = testfiles/serverCerts.pem
+ PrivateKeyFile = testfiles/serverPrivKey.pem
+ TrustedCAsFile = testfiles/serverTrustedCAs.pem
+}
+
diff --git a/test/basicserver/testfiles/srv4.conf b/test/basicserver/testfiles/srv4.conf
new file mode 100644
index 00000000..f05dff75
--- /dev/null
+++ b/test/basicserver/testfiles/srv4.conf
@@ -0,0 +1,6 @@
+Server
+{
+ PidFile = testfiles/srv4.pid
+ ListenAddresses = unix:testfiles/srv4.sock,inet:localhost
+}
+
diff --git a/test/basicserver/testprotocol.txt b/test/basicserver/testprotocol.txt
new file mode 100644
index 00000000..5bca9f49
--- /dev/null
+++ b/test/basicserver/testprotocol.txt
@@ -0,0 +1,42 @@
+# test protocol file
+
+Name Test
+IdentString Test-0.00
+ServerContextClass TestContext TestContext.h
+
+BEGIN_OBJECTS
+
+Error 0 IsError(Type,SubType) Reply
+ int32 Type
+ int32 SubType
+
+Hello 1 Command(Hello) Reply
+ int32 Number32
+ int16 Number16
+ int8 Number8
+ string Text
+
+Lists 2 Command(ListsReply)
+ vector<string> LotsOfText
+
+ListsReply 3 Reply
+ int32 NumberOfStrings
+
+Quit 4 Command(Quit) Reply EndsConversation
+
+Simple 5 Command(SimpleReply)
+ int32 Value
+
+SimpleReply 6 Reply
+ int32 ValuePlusOne
+
+GetStream 7 Command(GetStream) Reply
+ int32 StartingValue
+ bool UncertainSize
+
+SendStream 8 Command(GetStream) StreamWithCommand
+ int64 Value
+
+String 9 Command(String) Reply
+ string Test
+
diff --git a/test/bbackupd/Makefile.extra b/test/bbackupd/Makefile.extra
new file mode 100644
index 00000000..0ae56bd1
--- /dev/null
+++ b/test/bbackupd/Makefile.extra
@@ -0,0 +1,14 @@
+link-extra: ../../bin/bbackupd/autogen_ClientException.o \
+ ../../bin/bbackupd/BackupClientContext.o \
+ ../../bin/bbackupd/BackupClientDeleteList.o \
+ ../../bin/bbackupd/BackupClientDirectoryRecord.o \
+ ../../bin/bbackupd/Win32BackupService.o \
+ ../../bin/bbackupd/BackupClientInodeToIDMap.o \
+ ../../bin/bbackupd/Win32ServiceFunctions.o \
+ ../../bin/bbackupd/BackupDaemon.o \
+ ../../bin/bbstored/BackupStoreContext.o \
+ ../../bin/bbstored/BBStoreDHousekeeping.o \
+ ../../bin/bbstored/HousekeepStoreAccount.o \
+ ../../bin/bbstored/autogen_BackupProtocolServer.o \
+ ../../bin/bbstored/BackupCommands.o \
+ ../../bin/bbstored/BackupStoreDaemon.o
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;
+}
diff --git a/test/bbackupd/testextra b/test/bbackupd/testextra
new file mode 100644
index 00000000..798c8c67
--- /dev/null
+++ b/test/bbackupd/testextra
@@ -0,0 +1,4 @@
+mkdir testfiles/0_0
+mkdir testfiles/0_1
+mkdir testfiles/0_2
+mkdir testfiles/bbackupd-data
diff --git a/test/bbackupd/testfiles/accounts.txt b/test/bbackupd/testfiles/accounts.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/bbackupd/testfiles/accounts.txt
diff --git a/test/bbackupd/testfiles/bbackupd-exclude.conf.in b/test/bbackupd/testfiles/bbackupd-exclude.conf.in
new file mode 100644
index 00000000..4c08753f
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd-exclude.conf.in
@@ -0,0 +1,47 @@
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+StorePort = 22011
+AccountNumber = 0x01234567
+
+UpdateStoreInterval = 3
+MinimumFileAge = 4
+MaxUploadWait = 24
+DeleteRedundantLocationsAfter = 10
+
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+MaximumDiffingTime = 3
+KeepAliveTime = 1
+
+ExtendedLogging = no
+ExtendedLogFile = testfiles/bbackupd.log
+
+CommandSocket = testfiles/bbackupd.sock
+
+NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl
+SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+BackupLocations
+{
+ Test1
+ {
+ Path = testfiles/TestDir1
+ ExcludeDir = testfiles/TestDir1/spacetest/d3
+ ExcludeFile = testfiles/TestDir1/spacetest/f2
+ }
+}
+
diff --git a/test/bbackupd/testfiles/bbackupd-snapshot.conf.in b/test/bbackupd/testfiles/bbackupd-snapshot.conf.in
new file mode 100644
index 00000000..d245d077
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd-snapshot.conf.in
@@ -0,0 +1,56 @@
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+StorePort = 22011
+AccountNumber = 0x01234567
+
+AutomaticBackup = no
+UpdateStoreInterval = 0
+MinimumFileAge = 4
+MaxUploadWait = 24
+DeleteRedundantLocationsAfter = 10
+
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+MaximumDiffingTime = 3
+KeepAliveTime = 1
+
+ExtendedLogging = no
+ExtendedLogFile = testfiles/bbackupd.log
+
+CommandSocket = testfiles/bbackupd.sock
+
+NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl
+SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+BackupLocations
+{
+ Test1
+ {
+ Path = testfiles/TestDir1
+
+ ExcludeFile = testfiles/TestDir1/excluded_1
+ ExcludeFile = testfiles/TestDir1/excluded_2
+ ExcludeFilesRegex = \.excludethis$
+ ExcludeFilesRegex = EXCLUDE
+ AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis
+ ExcludeDir = testfiles/TestDir1/exclude_dir
+ ExcludeDir = testfiles/TestDir1/exclude_dir_2
+ ExcludeDirsRegex = not_this_dir
+ AlwaysIncludeDirsRegex = ALWAYSINCLUDE
+ }
+}
+
diff --git a/test/bbackupd/testfiles/bbackupd-symlink.conf.in b/test/bbackupd/testfiles/bbackupd-symlink.conf.in
new file mode 100644
index 00000000..33bb6157
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd-symlink.conf.in
@@ -0,0 +1,55 @@
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+StorePort = 22011
+AccountNumber = 0x01234567
+
+UpdateStoreInterval = 3
+MinimumFileAge = 4
+MaxUploadWait = 24
+DeleteRedundantLocationsAfter = 10
+
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+MaximumDiffingTime = 3
+KeepAliveTime = 1
+
+ExtendedLogging = no
+ExtendedLogFile = testfiles/bbackupd.log
+
+CommandSocket = testfiles/bbackupd.sock
+
+NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl
+SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+BackupLocations
+{
+ Test1
+ {
+ Path = testfiles/symlink-to-TestDir1
+
+ ExcludeFile = testfiles/TestDir1/excluded_1
+ ExcludeFile = testfiles/TestDir1/excluded_2
+ ExcludeFilesRegex = \.excludethis$
+ ExcludeFilesRegex = EXCLUDE
+ AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis
+ ExcludeDir = testfiles/TestDir1/exclude_dir
+ ExcludeDir = testfiles/TestDir1/exclude_dir_2
+ ExcludeDirsRegex = not_this_dir
+ AlwaysIncludeDirsRegex = ALWAYSINCLUDE
+ }
+}
+
diff --git a/test/bbackupd/testfiles/bbackupd-temploc.conf b/test/bbackupd/testfiles/bbackupd-temploc.conf
new file mode 100644
index 00000000..07cbdcd1
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd-temploc.conf
@@ -0,0 +1,55 @@
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+StorePort = 22011
+AccountNumber = 0x01234567
+
+UpdateStoreInterval = 3
+MinimumFileAge = 4
+MaxUploadWait = 24
+
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+MaximumDiffingTime = 3
+KeepAliveTime = 1
+
+ExtendedLogging = no
+ExtendedLogFile = testfiles/bbackupd.log
+
+CommandSocket = testfiles/bbackupd.sock
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+BackupLocations
+{
+ Test1
+ {
+ Path = testfiles/TestDir1
+
+ ExcludeFile = testfiles/TestDir1/excluded_1
+ ExcludeFile = testfiles/TestDir1/excluded_2
+ ExcludeFilesRegex = \.excludethis$
+ ExcludeFilesRegex = EXCLUDE
+ AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis
+ ExcludeDir = testfiles/TestDir1/exclude_dir
+ ExcludeDir = testfiles/TestDir1/exclude_dir_2
+ ExcludeDirsRegex = not_this_dir
+ AlwaysIncludeDirsRegex = ALWAYSINCLUDE
+ }
+ Test2
+ {
+ Path = testfiles/TestDir2
+ }
+}
+
diff --git a/test/bbackupd/testfiles/bbackupd.conf.in b/test/bbackupd/testfiles/bbackupd.conf.in
new file mode 100644
index 00000000..712b58b2
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd.conf.in
@@ -0,0 +1,55 @@
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+StorePort = 22011
+AccountNumber = 0x01234567
+
+UpdateStoreInterval = 3
+MinimumFileAge = 4
+MaxUploadWait = 24
+DeleteRedundantLocationsAfter = 10
+
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+MaximumDiffingTime = 3
+KeepAliveTime = 1
+
+ExtendedLogging = no
+ExtendedLogFile = testfiles/bbackupd.log
+
+CommandSocket = testfiles/bbackupd.sock
+
+NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl
+SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+BackupLocations
+{
+ Test1
+ {
+ Path = testfiles/TestDir1
+
+ ExcludeFile = testfiles/TestDir1/excluded_1
+ ExcludeFile = testfiles/TestDir1/excluded_2
+ ExcludeFilesRegex = \.excludethis$
+ ExcludeFilesRegex = EXCLUDE
+ AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis
+ ExcludeDir = testfiles/TestDir1/exclude_dir
+ ExcludeDir = testfiles/TestDir1/exclude_dir_2
+ ExcludeDirsRegex = not_this_dir
+ AlwaysIncludeDirsRegex = ALWAYSINCLUDE
+ }
+}
+
diff --git a/test/bbackupd/testfiles/bbackupd.keys b/test/bbackupd/testfiles/bbackupd.keys
new file mode 100644
index 00000000..d9135b97
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd.keys
Binary files differ
diff --git a/test/bbackupd/testfiles/bbstored.conf b/test/bbackupd/testfiles/bbstored.conf
new file mode 100644
index 00000000..87f4fe6b
--- /dev/null
+++ b/test/bbackupd/testfiles/bbstored.conf
@@ -0,0 +1,17 @@
+
+RaidFileConf = testfiles/raidfile.conf
+AccountDatabase = testfiles/accounts.txt
+
+ExtendedLogging = no
+
+TimeBetweenHousekeeping = 5
+
+Server
+{
+ PidFile = testfiles/bbstored.pid
+ ListenAddresses = inet:localhost:22011
+ CertificateFile = testfiles/serverCerts.pem
+ PrivateKeyFile = testfiles/serverPrivKey.pem
+ TrustedCAsFile = testfiles/serverTrustedCAs.pem
+}
+
diff --git a/test/bbackupd/testfiles/clientCerts.pem b/test/bbackupd/testfiles/clientCerts.pem
new file mode 100644
index 00000000..c1f14fa7
--- /dev/null
+++ b/test/bbackupd/testfiles/clientCerts.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w
+MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w
+MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92
+OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A
+LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k
+Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU
+knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx
+hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o
+EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ==
+-----END CERTIFICATE-----
diff --git a/test/bbackupd/testfiles/clientPrivKey.pem b/test/bbackupd/testfiles/clientPrivKey.pem
new file mode 100644
index 00000000..34b1af2a
--- /dev/null
+++ b/test/bbackupd/testfiles/clientPrivKey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev
+ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G
+9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB
+AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N
+eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq
+iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp
+81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9
+qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU
+LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ
+V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B
+BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8
+eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26
+Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw=
+-----END RSA PRIVATE KEY-----
diff --git a/test/bbackupd/testfiles/clientTrustedCAs.pem b/test/bbackupd/testfiles/clientTrustedCAs.pem
new file mode 100644
index 00000000..2a065879
--- /dev/null
+++ b/test/bbackupd/testfiles/clientTrustedCAs.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz
+MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e
+cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9
+I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6
+u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ
+hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM
+USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt
+SlL3iQzVXlF6NAhkzS54fQ==
+-----END CERTIFICATE-----
diff --git a/test/bbackupd/testfiles/extcheck1.pl.in b/test/bbackupd/testfiles/extcheck1.pl.in
new file mode 100755
index 00000000..5b70c677
--- /dev/null
+++ b/test/bbackupd/testfiles/extcheck1.pl.in
@@ -0,0 +1,58 @@
+#!@PERL@
+use strict;
+
+my $flags = $ARGV[0] or "";
+
+unless(open IN,"../../bin/bbackupquery/bbackupquery -Wwarning " .
+ "-c testfiles/bbackupd.conf " .
+ "-l testfiles/query4.log " .
+ "\"compare -ac$flags\" quit 2>&1 |")
+{
+ print "FAIL: opening compare utility\n";
+ exit 2;
+}
+
+my $ret = 1;
+my $seen = 0;
+
+while(<IN>)
+{
+ next unless m/\S/;
+ print "READ: $_";
+
+ if (m/continousupdate/)
+ {
+ unless (/exists/)
+ {
+ print "FAIL: continousupdate line does not match\n";
+ $ret = 2;
+ }
+ $seen = 1;
+ }
+ elsif (m/^No entry for terminal type/ or
+ m/^using dumb terminal settings/)
+ {
+ # skip these lines, may happen in Debian buildd
+ # with no terminal.
+ }
+ else
+ {
+ unless (/\AWARNING/ or /\ADifferences/ or /might be reason/
+ or /probably due to file mod/)
+ {
+ print "FAIL: Summary line does not match\n";
+ $ret = 2;
+ }
+ }
+}
+
+close IN;
+
+unless ($seen)
+{
+ print "FAIL: missing line matching continousupdate\n";
+ $ret = 2;
+}
+
+exit $ret;
+
diff --git a/test/bbackupd/testfiles/extcheck2.pl.in b/test/bbackupd/testfiles/extcheck2.pl.in
new file mode 100755
index 00000000..3671ad93
--- /dev/null
+++ b/test/bbackupd/testfiles/extcheck2.pl.in
@@ -0,0 +1,50 @@
+#!@PERL@
+use strict;
+
+my $flags = $ARGV[0] or "";
+
+unless(open IN,"../../bin/bbackupquery/bbackupquery -Wwarning " .
+ "-c testfiles/bbackupd.conf " .
+ "-l testfiles/query4.log " .
+ "\"compare -ac$flags\" quit 2>&1 |")
+{
+ print "Couldn't open compare utility\n";
+ exit 2;
+}
+
+my $ret = 1;
+
+while(<IN>)
+{
+ next unless m/\S/;
+ print "READ: $_";
+
+ if (m/continousupdate/)
+ {
+ unless (m/contents/ or m/attributes/)
+ {
+ print "FAIL: continuousupdate line does not match\n";
+ $ret = 2;
+ }
+ }
+ elsif (m/^No entry for terminal type/ or
+ m/^using dumb terminal settings/)
+ {
+ # skip these lines, may happen in Debian buildd
+ # with no terminal.
+ }
+ else
+ {
+ unless (/\AWARNING/ or /\ADifferences/ or /might be reason/
+ or /probably due to file mod/)
+ {
+ print "FAIL: summary line does not match\n";
+ $ret = 2;
+ }
+ }
+}
+
+close IN;
+
+exit $ret;
+
diff --git a/test/bbackupd/testfiles/notifyscript.pl.in b/test/bbackupd/testfiles/notifyscript.pl.in
new file mode 100755
index 00000000..d3e324e9
--- /dev/null
+++ b/test/bbackupd/testfiles/notifyscript.pl.in
@@ -0,0 +1,24 @@
+#!@TARGET_PERL@
+
+my $f = 'testfiles/notifyran.'.$ARGV[0].'.';
+
+if (-e 'testfiles/notifyscript.tag')
+{
+ open FILE, '< testfiles/notifyscript.tag' or die $!;
+ my $tag = <FILE>;
+ chomp $tag;
+ $f .= "$tag.";
+ close FILE;
+}
+
+my $n = 1;
+
+while(-e $f.$n)
+{
+ $n ++;
+}
+
+open FL,'>'.$f.$n;
+print FL localtime();
+close FL;
+
diff --git a/test/bbackupd/testfiles/raidfile.conf b/test/bbackupd/testfiles/raidfile.conf
new file mode 100644
index 00000000..641872b0
--- /dev/null
+++ b/test/bbackupd/testfiles/raidfile.conf
@@ -0,0 +1,10 @@
+
+disc0
+{
+ SetNumber = 0
+ BlockSize = 2048
+ Dir0 = testfiles/0_0
+ Dir1 = testfiles/0_1
+ Dir2 = testfiles/0_2
+}
+
diff --git a/test/bbackupd/testfiles/serverCerts.pem b/test/bbackupd/testfiles/serverCerts.pem
new file mode 100644
index 00000000..92467618
--- /dev/null
+++ b/test/bbackupd/testfiles/serverCerts.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w
+MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw
+MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV
+I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92
+cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH
+RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/
+myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2
+SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1
+HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t
+-----END CERTIFICATE-----
diff --git a/test/bbackupd/testfiles/serverPrivKey.pem b/test/bbackupd/testfiles/serverPrivKey.pem
new file mode 100644
index 00000000..fd87607d
--- /dev/null
+++ b/test/bbackupd/testfiles/serverPrivKey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP
+cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4
+bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB
+AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX
+P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT
+LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l
+4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN
+8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t
+Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX
+I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl
+XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48
+q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE
+xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn
+-----END RSA PRIVATE KEY-----
diff --git a/test/bbackupd/testfiles/serverTrustedCAs.pem b/test/bbackupd/testfiles/serverTrustedCAs.pem
new file mode 100644
index 00000000..2a065879
--- /dev/null
+++ b/test/bbackupd/testfiles/serverTrustedCAs.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz
+MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e
+cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9
+I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6
+u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ
+hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM
+USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt
+SlL3iQzVXlF6NAhkzS54fQ==
+-----END CERTIFICATE-----
diff --git a/test/bbackupd/testfiles/spacetest1.tgz b/test/bbackupd/testfiles/spacetest1.tgz
new file mode 100644
index 00000000..c653c0ae
--- /dev/null
+++ b/test/bbackupd/testfiles/spacetest1.tgz
Binary files differ
diff --git a/test/bbackupd/testfiles/spacetest2.tgz b/test/bbackupd/testfiles/spacetest2.tgz
new file mode 100644
index 00000000..aa47312d
--- /dev/null
+++ b/test/bbackupd/testfiles/spacetest2.tgz
Binary files differ
diff --git a/test/bbackupd/testfiles/syncallowscript.pl.in b/test/bbackupd/testfiles/syncallowscript.pl.in
new file mode 100755
index 00000000..f2ef1171
--- /dev/null
+++ b/test/bbackupd/testfiles/syncallowscript.pl.in
@@ -0,0 +1,33 @@
+#!@TARGET_PERL@
+
+use strict;
+use warnings;
+
+my $control_file = 'testfiles/syncallowscript.control';
+if (! -r $control_file)
+{
+ print "now\n";
+ exit 0;
+}
+
+my $control_state;
+open CONTROL, "< $control_file" or die "$control_file: $!";
+$control_state = <CONTROL>;
+defined $control_state or die "$control_file: read failed: $!";
+close CONTROL;
+
+my $marker_file_root = 'testfiles/syncallowscript.notifyran.';
+my $n = 1;
+my $marker_file;
+
+while($marker_file = $marker_file_root.$n and -e $marker_file)
+{
+ $n ++;
+}
+
+open FL,'>'.$marker_file or die "$marker_file: $!";
+print FL localtime();
+close FL;
+
+print $control_state;
+exit 0;
diff --git a/test/bbackupd/testfiles/test2.tgz b/test/bbackupd/testfiles/test2.tgz
new file mode 100644
index 00000000..ac7f18af
--- /dev/null
+++ b/test/bbackupd/testfiles/test2.tgz
Binary files differ
diff --git a/test/bbackupd/testfiles/test3.tgz b/test/bbackupd/testfiles/test3.tgz
new file mode 100644
index 00000000..c7d60cd7
--- /dev/null
+++ b/test/bbackupd/testfiles/test3.tgz
Binary files differ
diff --git a/test/bbackupd/testfiles/test_base.tgz b/test/bbackupd/testfiles/test_base.tgz
new file mode 100644
index 00000000..9c8ddfc0
--- /dev/null
+++ b/test/bbackupd/testfiles/test_base.tgz
Binary files differ
diff --git a/test/bbackupd/testfiles/testexclude.tgz b/test/bbackupd/testfiles/testexclude.tgz
new file mode 100644
index 00000000..ac7329d8
--- /dev/null
+++ b/test/bbackupd/testfiles/testexclude.tgz
Binary files differ
diff --git a/test/common/testcommon.cpp b/test/common/testcommon.cpp
new file mode 100644
index 00000000..e633969b
--- /dev/null
+++ b/test/common/testcommon.cpp
@@ -0,0 +1,882 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testcommon.cpp
+// Purpose: Tests for the code in lib/common
+// Created: 2003/07/23
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "Test.h"
+#include "Configuration.h"
+#include "FdGetLine.h"
+#include "Guards.h"
+#include "FileStream.h"
+#include "InvisibleTempFileStream.h"
+#include "IOStreamGetLine.h"
+#include "NamedLock.h"
+#include "ReadGatherStream.h"
+#include "MemBlockStream.h"
+#include "ExcludeList.h"
+#include "CommonException.h"
+#include "Conversion.h"
+#include "autogen_ConversionException.h"
+#include "CollectInBufferStream.h"
+#include "Archive.h"
+#include "Timer.h"
+#include "Logging.h"
+#include "ZeroStream.h"
+#include "PartialReadStream.h"
+
+#include "MemLeakFindOn.h"
+
+using namespace BoxConvert;
+
+void test_conversions()
+{
+ TEST_THAT((Convert<int32_t, const std::string &>(std::string("32"))) == 32);
+ TEST_THAT((Convert<int32_t, const char *>("42")) == 42);
+ TEST_THAT((Convert<int32_t, const char *>("-42")) == -42);
+ TEST_CHECK_THROWS((Convert<int8_t, const char *>("500")), ConversionException, IntOverflowInConvertFromString);
+ TEST_CHECK_THROWS((Convert<int8_t, const char *>("pants")), ConversionException, BadStringRepresentationOfInt);
+ TEST_CHECK_THROWS((Convert<int8_t, const char *>("")), ConversionException, CannotConvertEmptyStringToInt);
+
+ std::string a(Convert<std::string, int32_t>(63));
+ TEST_THAT(a == "63");
+ std::string b(Convert<std::string, int32_t>(-3473463));
+ TEST_THAT(b == "-3473463");
+ std::string c(Convert<std::string, int16_t>(344));
+ TEST_THAT(c == "344");
+}
+
+ConfigurationVerifyKey verifykeys1_1_1[] =
+{
+ ConfigurationVerifyKey("bing", ConfigTest_Exists),
+ ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt),
+ ConfigurationVerifyKey("terrible", ConfigTest_Exists | ConfigTest_LastEntry)
+};
+
+ConfigurationVerifyKey verifykeys1_1_2[] =
+{
+ ConfigurationVerifyKey("fish", ConfigTest_Exists | ConfigTest_IsInt),
+ ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry)
+};
+
+
+ConfigurationVerify verifysub1_1[] =
+{
+ {
+ "*",
+ 0,
+ verifykeys1_1_1,
+ ConfigTest_Exists,
+ 0
+ },
+ {
+ "otherthing",
+ 0,
+ verifykeys1_1_2,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+};
+
+ConfigurationVerifyKey verifykeys1_1[] =
+{
+ ConfigurationVerifyKey("value", ConfigTest_Exists | ConfigTest_IsInt),
+ ConfigurationVerifyKey("string1", ConfigTest_Exists),
+ ConfigurationVerifyKey("string2", ConfigTest_Exists | ConfigTest_LastEntry)
+};
+
+ConfigurationVerifyKey verifykeys1_2[] =
+{
+ ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt),
+ ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry)
+};
+
+ConfigurationVerify verifysub1[] =
+{
+ {
+ "test1",
+ verifysub1_1,
+ verifykeys1_1,
+ ConfigTest_Exists,
+ 0
+ },
+ {
+ "ping",
+ 0,
+ verifykeys1_2,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+};
+
+ConfigurationVerifyKey verifykeys1[] =
+{
+ ConfigurationVerifyKey("notExpected", 0),
+ ConfigurationVerifyKey("HasDefaultValue", 0, "Lovely default value"),
+ ConfigurationVerifyKey("MultiValue", ConfigTest_MultiValueAllowed),
+ ConfigurationVerifyKey("BoolTrue1", ConfigTest_IsBool),
+ ConfigurationVerifyKey("BoolTrue2", ConfigTest_IsBool),
+ ConfigurationVerifyKey("BoolFalse1", ConfigTest_IsBool),
+ ConfigurationVerifyKey("BoolFalse2", ConfigTest_IsBool),
+ ConfigurationVerifyKey("TOPlevel",
+ ConfigTest_LastEntry | ConfigTest_Exists)
+};
+
+ConfigurationVerify verify =
+{
+ "root",
+ verifysub1,
+ verifykeys1,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+};
+
+class TestLogger : public Logger
+{
+ private:
+ bool mTriggered;
+ Log::Level mTargetLevel;
+
+ public:
+ TestLogger(Log::Level targetLevel)
+ : mTriggered(false), mTargetLevel(targetLevel)
+ {
+ Logging::Add(this);
+ }
+ ~TestLogger()
+ {
+ Logging::Remove(this);
+ }
+
+ bool IsTriggered() { return mTriggered; }
+ void Reset() { mTriggered = false; }
+
+ virtual bool Log(Log::Level level, const std::string& rFile,
+ int line, std::string& rMessage)
+ {
+ if (level == mTargetLevel)
+ {
+ mTriggered = true;
+ }
+ return true;
+ }
+
+ virtual const char* GetType() { return "Test"; }
+ virtual void SetProgramName(const std::string& rProgramName) { }
+};
+
+int test(int argc, const char *argv[])
+{
+ // Test PartialReadStream and ReadGatherStream handling of files
+ // over 2GB (refs #2)
+ {
+ char buffer[8];
+
+ ZeroStream zero(0x80000003);
+ zero.Seek(0x7ffffffe, IOStream::SeekType_Absolute);
+ TEST_THAT(zero.GetPosition() == 0x7ffffffe);
+ TEST_THAT(zero.Read(buffer, 8) == 5);
+ TEST_THAT(zero.GetPosition() == 0x80000003);
+ TEST_THAT(zero.Read(buffer, 8) == 0);
+ zero.Seek(0, IOStream::SeekType_Absolute);
+ TEST_THAT(zero.GetPosition() == 0);
+
+ char* buffer2 = new char [0x1000000];
+ TEST_THAT(buffer2 != NULL);
+
+ PartialReadStream part(zero, 0x80000002);
+ for (int i = 0; i < 0x80; i++)
+ {
+ int read = part.Read(buffer2, 0x1000000);
+ TEST_THAT(read == 0x1000000);
+ }
+ TEST_THAT(part.Read(buffer, 8) == 2);
+ TEST_THAT(part.Read(buffer, 8) == 0);
+
+ delete [] buffer2;
+
+ ReadGatherStream gather(false);
+ zero.Seek(0, IOStream::SeekType_Absolute);
+ int component = gather.AddComponent(&zero);
+ gather.AddBlock(component, 0x80000002);
+ TEST_THAT(gather.Read(buffer, 8) == 8);
+ }
+
+ // Test self-deleting temporary file streams
+ {
+ std::string tempfile("testfiles/tempfile");
+ TEST_CHECK_THROWS(InvisibleTempFileStream fs(tempfile.c_str()),
+ CommonException, OSFileOpenError);
+ InvisibleTempFileStream fs(tempfile.c_str(), O_CREAT);
+
+ #ifdef WIN32
+ // file is still visible under Windows
+ TEST_THAT(TestFileExists(tempfile.c_str()));
+
+ // opening it again should work
+ InvisibleTempFileStream fs2(tempfile.c_str());
+ TEST_THAT(TestFileExists(tempfile.c_str()));
+
+ // opening it to create should work
+ InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT);
+ TEST_THAT(TestFileExists(tempfile.c_str()));
+
+ // opening it to create exclusively should fail
+ TEST_CHECK_THROWS(InvisibleTempFileStream fs4(tempfile.c_str(),
+ O_CREAT | O_EXCL), CommonException, OSFileOpenError);
+
+ fs2.Close();
+ #else
+ // file is not visible under Unix
+ TEST_THAT(!TestFileExists(tempfile.c_str()));
+
+ // opening it again should fail
+ TEST_CHECK_THROWS(InvisibleTempFileStream fs2(tempfile.c_str()),
+ CommonException, OSFileOpenError);
+
+ // opening it to create should work
+ InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT);
+ TEST_THAT(!TestFileExists(tempfile.c_str()));
+
+ // opening it to create exclusively should work
+ InvisibleTempFileStream fs4(tempfile.c_str(), O_CREAT | O_EXCL);
+ TEST_THAT(!TestFileExists(tempfile.c_str()));
+
+ fs4.Close();
+ #endif
+
+ fs.Close();
+ fs3.Close();
+
+ // now that it's closed, it should be invisible on all platforms
+ TEST_THAT(!TestFileExists(tempfile.c_str()));
+ }
+
+ // Test that memory leak detection doesn't crash
+ {
+ char *test = new char[1024];
+ delete [] test;
+ MemBlockStream *s = new MemBlockStream(test,12);
+ delete s;
+ }
+
+#ifdef BOX_MEMORY_LEAK_TESTING
+ {
+ Timers::Cleanup();
+
+ TEST_THAT(memleakfinder_numleaks() == 0);
+ void *block = ::malloc(12);
+ TEST_THAT(memleakfinder_numleaks() == 1);
+ void *b2 = ::realloc(block, 128*1024);
+ TEST_THAT(memleakfinder_numleaks() == 1);
+ ::free(b2);
+ TEST_THAT(memleakfinder_numleaks() == 0);
+ char *test = new char[1024];
+ TEST_THAT(memleakfinder_numleaks() == 1);
+ MemBlockStream *s = new MemBlockStream(test,12);
+ TEST_THAT(memleakfinder_numleaks() == 2);
+ delete s;
+ TEST_THAT(memleakfinder_numleaks() == 1);
+ delete [] test;
+ TEST_THAT(memleakfinder_numleaks() == 0);
+
+ Timers::Init();
+ }
+#endif // BOX_MEMORY_LEAK_TESTING
+
+ // test main() initialises timers for us, so uninitialise them
+ Timers::Cleanup();
+
+ // Check that using timer methods without initialisation
+ // throws an assertion failure. Can only do this in debug mode
+ #ifndef BOX_RELEASE_BUILD
+ TEST_CHECK_THROWS(Timers::Add(*(Timer*)NULL),
+ CommonException, AssertFailed);
+ TEST_CHECK_THROWS(Timers::Remove(*(Timer*)NULL),
+ CommonException, AssertFailed);
+ #endif
+
+ // TEST_CHECK_THROWS(Timers::Signal(), CommonException, AssertFailed);
+ #ifndef BOX_RELEASE_BUILD
+ TEST_CHECK_THROWS(Timers::Cleanup(), CommonException,
+ AssertFailed);
+ #endif
+
+ // Check that we can initialise the timers
+ Timers::Init();
+
+ // Check that double initialisation throws an exception
+ #ifndef BOX_RELEASE_BUILD
+ TEST_CHECK_THROWS(Timers::Init(), CommonException,
+ AssertFailed);
+ #endif
+
+ // Check that we can clean up the timers
+ Timers::Cleanup();
+
+ // Check that double cleanup throws an exception
+ #ifndef BOX_RELEASE_BUILD
+ TEST_CHECK_THROWS(Timers::Cleanup(), CommonException,
+ AssertFailed);
+ #endif
+
+ Timers::Init();
+
+ Timer t0(0, "t0"); // should never expire
+ Timer t1(1, "t1");
+ Timer t2(2, "t2");
+ Timer t3(3, "t3");
+
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(!t1.HasExpired());
+ TEST_THAT(!t2.HasExpired());
+ TEST_THAT(!t3.HasExpired());
+
+ safe_sleep(1);
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(t1.HasExpired());
+ TEST_THAT(!t2.HasExpired());
+ TEST_THAT(!t3.HasExpired());
+
+ safe_sleep(1);
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(t1.HasExpired());
+ TEST_THAT(t2.HasExpired());
+ TEST_THAT(!t3.HasExpired());
+
+ t1 = Timer(1, "t1a");
+ t2 = Timer(2, "t2a");
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(!t1.HasExpired());
+ TEST_THAT(!t2.HasExpired());
+
+ safe_sleep(1);
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(t1.HasExpired());
+ TEST_THAT(!t2.HasExpired());
+ TEST_THAT(t3.HasExpired());
+
+ // Leave timers initialised for rest of test.
+ // Test main() will cleanup after test finishes.
+
+ static const char *testfilelines[] =
+ {
+ "First line",
+ "Second line",
+ "Third",
+ "",
+ "",
+ "",
+ "sdf hjjk",
+ "",
+ "test",
+ "test#not comment",
+ "test#not comment",
+ "",
+ "nice line",
+ "fish",
+ "",
+ "ping",
+ "",
+ "",
+ "Nothing",
+ "Nothing",
+ 0
+ };
+
+ // First, test the FdGetLine class -- rather important this works!
+ {
+ FileHandleGuard<O_RDONLY> file("testfiles"
+ DIRECTORY_SEPARATOR "fdgetlinetest.txt");
+ FdGetLine getline(file);
+
+ int l = 0;
+ while(testfilelines[l] != 0)
+ {
+ TEST_THAT(!getline.IsEOF());
+ std::string line = getline.GetLine(true);
+ //printf("expected |%s| got |%s|\n", lines[l], line.c_str());
+ TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0);
+ l++;
+ }
+ TEST_THAT(getline.IsEOF());
+ TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF);
+ }
+ // and again without pre-processing
+ {
+ FileHandleGuard<O_RDONLY> file("testfiles"
+ DIRECTORY_SEPARATOR "fdgetlinetest.txt");
+ FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR
+ "fdgetlinetest.txt", "r");
+ TEST_THAT_ABORTONFAIL(file2 != 0);
+ FdGetLine getline(file);
+ char ll[512];
+
+ while(!feof(file2))
+ {
+ fgets(ll, sizeof(ll), file2);
+ int e = strlen(ll);
+ while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r'))
+ {
+ e--;
+ }
+ ll[e] = '\0';
+
+ TEST_THAT(!getline.IsEOF());
+ std::string line = getline.GetLine(false);
+ //printf("expected |%s| got |%s|\n", ll, line.c_str());
+ TEST_THAT(strcmp(ll, line.c_str()) == 0);
+ }
+ TEST_THAT(getline.IsEOF());
+ TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF);
+
+ fclose(file2);
+ }
+
+ // Then the IOStream version of get line, seeing as we're here...
+ {
+ FileStream file("testfiles" DIRECTORY_SEPARATOR
+ "fdgetlinetest.txt", O_RDONLY);
+ IOStreamGetLine getline(file);
+
+ int l = 0;
+ while(testfilelines[l] != 0)
+ {
+ TEST_THAT(!getline.IsEOF());
+ std::string line;
+ while(!getline.GetLine(line, true))
+ ;
+ //printf("expected |%s| got |%s|\n", lines[l], line.c_str());
+ TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0);
+ l++;
+ }
+ TEST_THAT(getline.IsEOF());
+ std::string dummy;
+ TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF);
+ }
+ // and again without pre-processing
+ {
+ FileStream file("testfiles" DIRECTORY_SEPARATOR
+ "fdgetlinetest.txt", O_RDONLY);
+ IOStreamGetLine getline(file);
+
+ FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR
+ "fdgetlinetest.txt", "r");
+ TEST_THAT_ABORTONFAIL(file2 != 0);
+ char ll[512];
+
+ while(!feof(file2))
+ {
+ fgets(ll, sizeof(ll), file2);
+ int e = strlen(ll);
+ while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r'))
+ {
+ e--;
+ }
+ ll[e] = '\0';
+
+ TEST_THAT(!getline.IsEOF());
+ std::string line;
+ while(!getline.GetLine(line, false))
+ ;
+ //printf("expected |%s| got |%s|\n", ll, line.c_str());
+ TEST_THAT(strcmp(ll, line.c_str()) == 0);
+ }
+ TEST_THAT(getline.IsEOF());
+ std::string dummy;
+ TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF);
+
+ fclose(file2);
+ }
+
+ // Doesn't exist
+ {
+ std::string errMsg;
+ TEST_CHECK_THROWS(std::auto_ptr<Configuration> pconfig(
+ Configuration::LoadAndVerify(
+ "testfiles" DIRECTORY_SEPARATOR "DOESNTEXIST",
+ &verify, errMsg)),
+ CommonException, OSFileOpenError);
+ }
+
+ // Basic configuration test
+ {
+ std::string errMsg;
+ std::auto_ptr<Configuration> pconfig(
+ Configuration::LoadAndVerify(
+ "testfiles" DIRECTORY_SEPARATOR "config1.txt",
+ &verify, errMsg));
+ if(!errMsg.empty())
+ {
+ printf("UNEXPECTED error msg is:\n------\n%s------\n", errMsg.c_str());
+ }
+ TEST_THAT_ABORTONFAIL(pconfig.get() != 0);
+ TEST_THAT(errMsg.empty());
+ TEST_THAT(pconfig->KeyExists("TOPlevel"));
+ TEST_THAT(pconfig->GetKeyValue("TOPlevel") == "value");
+ TEST_THAT(pconfig->KeyExists("MultiValue"));
+ TEST_THAT(pconfig->GetKeyValue("MultiValue") == "single");
+ TEST_THAT(!pconfig->KeyExists("not exist"));
+ TEST_THAT(pconfig->KeyExists("HasDefaultValue"));
+ TEST_THAT(pconfig->GetKeyValue("HasDefaultValue") == "Lovely default value");
+ TEST_CHECK_THROWS(pconfig->GetKeyValue("not exist"), CommonException, ConfigNoKey);
+ // list of keys
+ std::vector<std::string> keylist(pconfig->GetKeyNames());
+ TEST_THAT(keylist.size() == 3);
+ // will be sorted alphanumerically
+ TEST_THAT(keylist[2] == "TOPlevel" && keylist[1] == "MultiValue" && keylist[0] == "HasDefaultValue");
+ // list of sub configurations
+ std::vector<std::string> sublist(pconfig->GetSubConfigurationNames());
+ TEST_THAT(sublist.size() == 2);
+ TEST_THAT(sublist[0] == "test1");
+ TEST_THAT(sublist[1] == "ping");
+ TEST_THAT(pconfig->SubConfigurationExists("test1"));
+ TEST_THAT(pconfig->SubConfigurationExists("ping"));
+ TEST_CHECK_THROWS(pconfig->GetSubConfiguration("nosubconfig"), CommonException, ConfigNoSubConfig);
+ // Get a sub configuration
+ const Configuration &sub1 = pconfig->GetSubConfiguration("test1");
+ TEST_THAT(sub1.GetKeyValueInt("value") == 12);
+ std::vector<std::string> sublist2(sub1.GetSubConfigurationNames());
+ TEST_THAT(sublist2.size() == 4);
+ // And the sub-sub configs
+ const Configuration &sub1_1 = sub1.GetSubConfiguration("subconfig");
+ TEST_THAT(sub1_1.GetKeyValueInt("carrots") == 0x2356);
+ const Configuration &sub1_2 = sub1.GetSubConfiguration("subconfig2");
+ TEST_THAT(sub1_2.GetKeyValueInt("carrots") == -243895);
+ const Configuration &sub1_3 = sub1.GetSubConfiguration("subconfig3");
+ TEST_THAT(sub1_3.GetKeyValueInt("carrots") == 050);
+ TEST_THAT(sub1_3.GetKeyValue("terrible") == "absolutely");
+ }
+
+ static const char *file[] =
+ {
+ "testfiles" DIRECTORY_SEPARATOR "config2.txt",
+ // Value missing from root
+ "testfiles" DIRECTORY_SEPARATOR "config3.txt",
+ // Unexpected {
+ "testfiles" DIRECTORY_SEPARATOR "config4.txt",
+ // Missing }
+ "testfiles" DIRECTORY_SEPARATOR "config5.txt",
+ // { expected, but wasn't there
+ "testfiles" DIRECTORY_SEPARATOR "config6.txt",
+ // Duplicate key
+ "testfiles" DIRECTORY_SEPARATOR "config7.txt",
+ // Invalid key (no name)
+ "testfiles" DIRECTORY_SEPARATOR "config8.txt",
+ // Not all sub blocks terminated
+ "testfiles" DIRECTORY_SEPARATOR "config9.txt",
+ // Not valid integer
+ "testfiles" DIRECTORY_SEPARATOR "config9b.txt",
+ // Not valid integer
+ "testfiles" DIRECTORY_SEPARATOR "config9c.txt",
+ // Not valid integer
+ "testfiles" DIRECTORY_SEPARATOR "config9d.txt",
+ // Not valid integer
+ "testfiles" DIRECTORY_SEPARATOR "config10.txt",
+ // Missing key (in subblock)
+ "testfiles" DIRECTORY_SEPARATOR "config11.txt",
+ // Unknown key
+ "testfiles" DIRECTORY_SEPARATOR "config12.txt",
+ // Missing block
+ "testfiles" DIRECTORY_SEPARATOR "config13.txt",
+ // Subconfig (wildcarded) should exist, but missing (ie nothing present)
+ "testfiles" DIRECTORY_SEPARATOR "config16.txt",
+ // bad boolean value
+ 0
+ };
+
+ for(int l = 0; file[l] != 0; ++l)
+ {
+ std::string errMsg;
+ std::auto_ptr<Configuration> pconfig(Configuration::LoadAndVerify(file[l], &verify, errMsg));
+ TEST_THAT(pconfig.get() == 0);
+ TEST_THAT(!errMsg.empty());
+ printf("(%s) Error msg is:\n------\n%s------\n", file[l], errMsg.c_str());
+ }
+
+ // Check that multivalues happen as expected
+ // (single value in a multivalue already checked)
+ {
+ std::string errMsg;
+ std::auto_ptr<Configuration> pconfig(
+ Configuration::LoadAndVerify(
+ "testfiles" DIRECTORY_SEPARATOR "config14.txt",
+ &verify, errMsg));
+ TEST_THAT(pconfig.get() != 0);
+ TEST_THAT(errMsg.empty());
+ TEST_THAT(pconfig->KeyExists("MultiValue"));
+ // values are separated by a specific character
+ std::string expectedvalue("value1");
+ expectedvalue += Configuration::MultiValueSeparator;
+ expectedvalue += "secondvalue";
+ TEST_THAT(pconfig->GetKeyValue("MultiValue") == expectedvalue);
+ }
+
+ // Check boolean values
+ {
+ std::string errMsg;
+ std::auto_ptr<Configuration> pconfig(
+ Configuration::LoadAndVerify(
+ "testfiles" DIRECTORY_SEPARATOR "config15.txt",
+ &verify, errMsg));
+ TEST_THAT(pconfig.get() != 0);
+ TEST_THAT(errMsg.empty());
+ TEST_THAT(pconfig->GetKeyValueBool("BoolTrue1") == true);
+ TEST_THAT(pconfig->GetKeyValueBool("BoolTrue2") == true);
+ TEST_THAT(pconfig->GetKeyValueBool("BoolFalse1") == false);
+ TEST_THAT(pconfig->GetKeyValueBool("BoolFalse2") == false);
+ }
+
+ // Test named locks
+ {
+ NamedLock lock1;
+ // Try and get a lock on a name in a directory which doesn't exist
+ TEST_CHECK_THROWS(lock1.TryAndGetLock(
+ "testfiles"
+ DIRECTORY_SEPARATOR "non-exist"
+ DIRECTORY_SEPARATOR "lock"),
+ CommonException, OSFileError);
+
+ // And a more resonable request
+ TEST_THAT(lock1.TryAndGetLock(
+ "testfiles" DIRECTORY_SEPARATOR "lock1") == true);
+
+ // Try to lock something using the same lock
+ TEST_CHECK_THROWS(
+ lock1.TryAndGetLock(
+ "testfiles"
+ DIRECTORY_SEPARATOR "non-exist"
+ DIRECTORY_SEPARATOR "lock2"),
+ CommonException, NamedLockAlreadyLockingSomething);
+#if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK
+ // And again on that name
+ NamedLock lock2;
+ TEST_THAT(lock2.TryAndGetLock(
+ "testfiles" DIRECTORY_SEPARATOR "lock1") == false);
+#endif
+ }
+ {
+ // Check that it unlocked when it went out of scope
+ NamedLock lock3;
+ TEST_THAT(lock3.TryAndGetLock(
+ "testfiles" DIRECTORY_SEPARATOR "lock1") == true);
+ }
+ {
+ // And unlocking works
+ NamedLock lock4;
+ TEST_CHECK_THROWS(lock4.ReleaseLock(), CommonException,
+ NamedLockNotHeld);
+ TEST_THAT(lock4.TryAndGetLock(
+ "testfiles" DIRECTORY_SEPARATOR "lock4") == true);
+ lock4.ReleaseLock();
+ NamedLock lock5;
+ TEST_THAT(lock5.TryAndGetLock(
+ "testfiles" DIRECTORY_SEPARATOR "lock4") == true);
+ // And can reuse it
+ TEST_THAT(lock4.TryAndGetLock(
+ "testfiles" DIRECTORY_SEPARATOR "lock5") == true);
+ }
+
+ // Test the ReadGatherStream
+ {
+ #define GATHER_DATA1 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ #define GATHER_DATA2 "ZYZWVUTSRQPOMNOLKJIHGFEDCBA9876543210zyxwvutsrqpomno"
+
+ // Make two streams
+ MemBlockStream s1(GATHER_DATA1, sizeof(GATHER_DATA1));
+ MemBlockStream s2(GATHER_DATA2, sizeof(GATHER_DATA2));
+
+ // And a gather stream
+ ReadGatherStream gather(false /* no deletion */);
+
+ // Add the streams
+ int s1_c = gather.AddComponent(&s1);
+ int s2_c = gather.AddComponent(&s2);
+ TEST_THAT(s1_c == 0);
+ TEST_THAT(s2_c == 1);
+
+ // Set up some blocks
+ gather.AddBlock(s1_c, 11);
+ gather.AddBlock(s1_c, 2);
+ gather.AddBlock(s1_c, 8, true, 2);
+ gather.AddBlock(s2_c, 20);
+ gather.AddBlock(s1_c, 20);
+ gather.AddBlock(s2_c, 25);
+ gather.AddBlock(s1_c, 10, true, 0);
+ #define GATHER_RESULT "0123456789abc23456789ZYZWVUTSRQPOMNOLKJIHabcdefghijklmnopqrstGFEDCBA9876543210zyxwvuts0123456789"
+
+ // Read them in...
+ char buffer[1024];
+ unsigned int r = 0;
+ while(r < sizeof(GATHER_RESULT) - 1)
+ {
+ int s = gather.Read(buffer + r, 7);
+ r += s;
+
+ TEST_THAT(gather.GetPosition() == r);
+ if(r < sizeof(GATHER_RESULT) - 1)
+ {
+ TEST_THAT(gather.StreamDataLeft());
+ TEST_THAT(static_cast<size_t>(gather.BytesLeftToRead()) == sizeof(GATHER_RESULT) - 1 - r);
+ }
+ else
+ {
+ TEST_THAT(!gather.StreamDataLeft());
+ TEST_THAT(gather.BytesLeftToRead() == 0);
+ }
+ }
+ TEST_THAT(r == sizeof(GATHER_RESULT) - 1);
+ TEST_THAT(::memcmp(buffer, GATHER_RESULT, sizeof(GATHER_RESULT) - 1) == 0);
+ }
+
+ // Test ExcludeList
+ {
+ ExcludeList elist;
+ // Check assumption
+ TEST_THAT(Configuration::MultiValueSeparator == '\x01');
+ // Add definite entries
+ elist.AddDefiniteEntries(std::string("\x01"));
+ elist.AddDefiniteEntries(std::string(""));
+ elist.AddDefiniteEntries(std::string("Definite1\x01/dir/DefNumberTwo\x01\x01ThingDefThree"));
+ elist.AddDefiniteEntries(std::string("AnotherDef"));
+ TEST_THAT(elist.SizeOfDefiniteList() == 4);
+
+ // Add regex entries
+ #ifdef HAVE_REGEX_SUPPORT
+ elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$"));
+ elist.AddRegexEntries(std::string(""));
+ TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[:not_valid")), CommonException, BadRegularExpression);
+ TEST_THAT(elist.SizeOfRegexList() == 3);
+ #else
+ TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")), CommonException, RegexNotSupportedOnThisPlatform);
+ TEST_THAT(elist.SizeOfRegexList() == 0);
+ #endif
+
+ #ifdef WIN32
+ #define CASE_SENSITIVE false
+ #else
+ #define CASE_SENSITIVE true
+ #endif
+
+ // Try some matches!
+ TEST_THAT(elist.IsExcluded(std::string("Definite1")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("/dir/DefNumberTwo")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("ThingDefThree")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("AnotherDef")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("dir/DefNumberTwo")) == false);
+
+ // Try some case insensitive matches,
+ // that should pass on Win32 and fail elsewhere
+ TEST_THAT(elist.IsExcluded("DEFINITe1")
+ == !CASE_SENSITIVE);
+ TEST_THAT(elist.IsExcluded("/Dir/DefNumberTwo")
+ == !CASE_SENSITIVE);
+ TEST_THAT(elist.IsExcluded("thingdefthree")
+ == !CASE_SENSITIVE);
+
+ #ifdef HAVE_REGEX_SUPPORT
+ TEST_THAT(elist.IsExcluded(std::string("b.reg")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("B.reg")) == !CASE_SENSITIVE);
+ TEST_THAT(elist.IsExcluded(std::string("b.Reg")) == !CASE_SENSITIVE);
+ TEST_THAT(elist.IsExcluded(std::string("e.reg")) == false);
+ TEST_THAT(elist.IsExcluded(std::string("e.Reg")) == false);
+ TEST_THAT(elist.IsExcluded(std::string("DEfinite1")) == !CASE_SENSITIVE);
+ TEST_THAT(elist.IsExcluded(std::string("DEXCLUDEfinite1")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("DEfinitexclude1")) == !CASE_SENSITIVE);
+ TEST_THAT(elist.IsExcluded(std::string("exclude")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("ExcludE")) == !CASE_SENSITIVE);
+ #endif
+
+ #undef CASE_SENSITIVE
+
+ TestLogger logger(Log::WARNING);
+ TEST_THAT(!logger.IsTriggered());
+ elist.AddDefiniteEntries(std::string("/foo"));
+ TEST_THAT(!logger.IsTriggered());
+ elist.AddDefiniteEntries(std::string("/foo/"));
+ TEST_THAT(logger.IsTriggered());
+ logger.Reset();
+ elist.AddDefiniteEntries(std::string("/foo"
+ DIRECTORY_SEPARATOR));
+ TEST_THAT(logger.IsTriggered());
+ logger.Reset();
+ elist.AddDefiniteEntries(std::string("/foo"
+ DIRECTORY_SEPARATOR "bar\x01/foo"));
+ TEST_THAT(!logger.IsTriggered());
+ elist.AddDefiniteEntries(std::string("/foo"
+ DIRECTORY_SEPARATOR "bar\x01/foo"
+ DIRECTORY_SEPARATOR));
+ TEST_THAT(logger.IsTriggered());
+ }
+
+ test_conversions();
+
+ // test that we can use Archive and CollectInBufferStream
+ // to read and write arbitrary types to a memory buffer
+
+ {
+ CollectInBufferStream buffer;
+ ASSERT(buffer.GetPosition() == 0);
+
+ {
+ Archive archive(buffer, 0);
+ ASSERT(buffer.GetPosition() == 0);
+
+ archive.Write((bool) true);
+ archive.Write((bool) false);
+ archive.Write((int) 0x12345678);
+ archive.Write((int) 0x87654321);
+ archive.Write((int64_t) 0x0badfeedcafebabeLL);
+ archive.Write((uint64_t) 0xfeedfacedeadf00dLL);
+ archive.Write((uint8_t) 0x01);
+ archive.Write((uint8_t) 0xfe);
+ archive.Write(std::string("hello world!"));
+ archive.Write(std::string("goodbye cruel world!"));
+ }
+
+ CollectInBufferStream buf2;
+ buf2.Write(buffer.GetBuffer(), buffer.GetSize());
+ TEST_THAT(buf2.GetPosition() == buffer.GetSize());
+
+ buf2.SetForReading();
+ TEST_THAT(buf2.GetPosition() == 0);
+
+ {
+ Archive archive(buf2, 0);
+ TEST_THAT(buf2.GetPosition() == 0);
+
+ bool b;
+ archive.Read(b); TEST_THAT(b == true);
+ archive.Read(b); TEST_THAT(b == false);
+
+ int i;
+ archive.Read(i); TEST_THAT(i == 0x12345678);
+ archive.Read(i); TEST_THAT((unsigned int)i == 0x87654321);
+
+ uint64_t i64;
+ archive.Read(i64); TEST_THAT(i64 == 0x0badfeedcafebabeLL);
+ archive.Read(i64); TEST_THAT(i64 == 0xfeedfacedeadf00dLL);
+
+ uint8_t i8;
+ archive.Read(i8); TEST_THAT(i8 == 0x01);
+ archive.Read(i8); TEST_THAT(i8 == 0xfe);
+
+ std::string s;
+ archive.Read(s); TEST_THAT(s == "hello world!");
+ archive.Read(s); TEST_THAT(s == "goodbye cruel world!");
+
+ TEST_THAT(!buf2.StreamDataLeft());
+ }
+ }
+
+ return 0;
+}
diff --git a/test/common/testfiles/config1.txt b/test/common/testfiles/config1.txt
new file mode 100644
index 00000000..d000f759
--- /dev/null
+++ b/test/common/testfiles/config1.txt
@@ -0,0 +1,40 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+MultiValue = single
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config10.txt b/test/common/testfiles/config10.txt
new file mode 100644
index 00000000..02aeec74
--- /dev/null
+++ b/test/common/testfiles/config10.txt
@@ -0,0 +1,37 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config11.txt b/test/common/testfiles/config11.txt
new file mode 100644
index 00000000..cafabe74
--- /dev/null
+++ b/test/common/testfiles/config11.txt
@@ -0,0 +1,39 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ NOTEXPECTED= 34234
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config12.txt b/test/common/testfiles/config12.txt
new file mode 100644
index 00000000..17ed34f1
--- /dev/null
+++ b/test/common/testfiles/config12.txt
@@ -0,0 +1,33 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config13.txt b/test/common/testfiles/config13.txt
new file mode 100644
index 00000000..8de8ea5b
--- /dev/null
+++ b/test/common/testfiles/config13.txt
@@ -0,0 +1,15 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config14.txt b/test/common/testfiles/config14.txt
new file mode 100644
index 00000000..d409beaa
--- /dev/null
+++ b/test/common/testfiles/config14.txt
@@ -0,0 +1,41 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+MultiValue = value1
+MultiValue = secondvalue
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config15.txt b/test/common/testfiles/config15.txt
new file mode 100644
index 00000000..bfa7b022
--- /dev/null
+++ b/test/common/testfiles/config15.txt
@@ -0,0 +1,45 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+MultiValue = single
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
+BoolTrue1 = true
+BoolTrue2 = yes
+BoolFalse1 = fAlse
+BoolFalse2 = nO
+
diff --git a/test/common/testfiles/config16.txt b/test/common/testfiles/config16.txt
new file mode 100644
index 00000000..566070f2
--- /dev/null
+++ b/test/common/testfiles/config16.txt
@@ -0,0 +1,42 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+MultiValue = single
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
+BoolTrue1 = not a valid value
+
diff --git a/test/common/testfiles/config2.txt b/test/common/testfiles/config2.txt
new file mode 100644
index 00000000..724c911a
--- /dev/null
+++ b/test/common/testfiles/config2.txt
@@ -0,0 +1,39 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+# make this value missing
+# TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config3.txt b/test/common/testfiles/config3.txt
new file mode 100644
index 00000000..688675a4
--- /dev/null
+++ b/test/common/testfiles/config3.txt
@@ -0,0 +1,39 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ {
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config4.txt b/test/common/testfiles/config4.txt
new file mode 100644
index 00000000..1563d8aa
--- /dev/null
+++ b/test/common/testfiles/config4.txt
@@ -0,0 +1,40 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+}
+
+
diff --git a/test/common/testfiles/config5.txt b/test/common/testfiles/config5.txt
new file mode 100644
index 00000000..d1821ecc
--- /dev/null
+++ b/test/common/testfiles/config5.txt
@@ -0,0 +1,37 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config6.txt b/test/common/testfiles/config6.txt
new file mode 100644
index 00000000..d7381738
--- /dev/null
+++ b/test/common/testfiles/config6.txt
@@ -0,0 +1,39 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ bing= something else
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config7.txt b/test/common/testfiles/config7.txt
new file mode 100644
index 00000000..6a24d036
--- /dev/null
+++ b/test/common/testfiles/config7.txt
@@ -0,0 +1,39 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ = invalid thing here!
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config8.txt b/test/common/testfiles/config8.txt
new file mode 100644
index 00000000..3a66bbb3
--- /dev/null
+++ b/test/common/testfiles/config8.txt
@@ -0,0 +1,37 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+
diff --git a/test/common/testfiles/config9.txt b/test/common/testfiles/config9.txt
new file mode 100644
index 00000000..936ad6ce
--- /dev/null
+++ b/test/common/testfiles/config9.txt
@@ -0,0 +1,38 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050X
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config9b.txt b/test/common/testfiles/config9b.txt
new file mode 100644
index 00000000..65c44a19
--- /dev/null
+++ b/test/common/testfiles/config9b.txt
@@ -0,0 +1,38 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=C-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config9c.txt b/test/common/testfiles/config9c.txt
new file mode 100644
index 00000000..d9be55ad
--- /dev/null
+++ b/test/common/testfiles/config9c.txt
@@ -0,0 +1,38 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=2430-895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =050
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/config9d.txt b/test/common/testfiles/config9d.txt
new file mode 100644
index 00000000..28ea300e
--- /dev/null
+++ b/test/common/testfiles/config9d.txt
@@ -0,0 +1,38 @@
+test1
+{
+ value=12
+ string1 = carrots in may
+ string2 =on the string
+ subconfig
+ {
+ bing= nothing really
+ carrots =0x2356
+ terrible=lovely
+ }
+ subconfig2
+ {
+ bing= something
+ carrots=-243895
+ terrible=pgin!
+ }
+ subconfig3
+ {
+ bing= 435
+ carrots =090
+ terrible=absolutely
+ }
+ otherthing
+ {
+ string= ping
+ fish =0
+ }
+}
+
+TOPlevel= value
+
+ping
+{
+ carrots=324
+ string = casrts
+}
+
diff --git a/test/common/testfiles/fdgetlinetest.txt b/test/common/testfiles/fdgetlinetest.txt
new file mode 100644
index 00000000..f7b2c829
--- /dev/null
+++ b/test/common/testfiles/fdgetlinetest.txt
@@ -0,0 +1,20 @@
+First line
+ Second line
+ Third
+# comment
+ # comment
+
+ sdf hjjk
+
+ test #coment
+ test#not comment
+ test#not comment #comment
+
+nice line
+fish
+
+ping
+#comment
+
+ Nothing
+ Nothing \ No newline at end of file
diff --git a/test/compress/testcompress.cpp b/test/compress/testcompress.cpp
new file mode 100644
index 00000000..4a522d31
--- /dev/null
+++ b/test/compress/testcompress.cpp
@@ -0,0 +1,261 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testcompress.cpp
+// Purpose: Test lib/compress
+// Created: 5/12/03
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "Test.h"
+#include "Compress.h"
+#include "CompressStream.h"
+#include "CollectInBufferStream.h"
+
+#include "MemLeakFindOn.h"
+
+#define DATA_SIZE (1024*128+103)
+#define CHUNK_SIZE 2561
+#define DECOMP_CHUNK_SIZE 3
+
+// Stream for testing
+class CopyInToOutStream : public IOStream
+{
+public:
+ CopyInToOutStream() : currentBuffer(0) {buffers[currentBuffer].SetForReading();}
+ ~CopyInToOutStream() {}
+ int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite)
+ {
+ if(buffers[currentBuffer].StreamDataLeft())
+ {
+ return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout);
+ }
+
+ // Swap buffers?
+ if(buffers[(currentBuffer + 1) & 1].GetSize() > 0)
+ {
+ buffers[currentBuffer].Reset();
+ currentBuffer = (currentBuffer + 1) & 1;
+ buffers[currentBuffer].SetForReading();
+ return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout);
+ }
+
+ return 0;
+ }
+ void Write(const void *pBuffer, int NBytes)
+ {
+ buffers[(currentBuffer + 1) & 1].Write(pBuffer, NBytes);
+ }
+ bool StreamDataLeft()
+ {
+ return buffers[currentBuffer].StreamDataLeft() || buffers[(currentBuffer + 1) % 1].GetSize() > 0;
+ }
+ bool StreamClosed()
+ {
+ return false;
+ }
+ int currentBuffer;
+ CollectInBufferStream buffers[2];
+};
+
+// Test stream based interface
+int test_stream()
+{
+ // Make a load of compressible data to compress
+ CollectInBufferStream source;
+ uint16_t data[1024];
+ for(int x = 0; x < 1024; ++x)
+ {
+ data[x] = x;
+ }
+ for(int x = 0; x < (32*1024); ++x)
+ {
+ source.Write(data, (x % 1024) * 2);
+ }
+ source.SetForReading();
+
+ // Straight compress from one stream to another
+ {
+ CollectInBufferStream *poutput = new CollectInBufferStream;
+ CompressStream compress(poutput, true /* take ownership */, false /* read */, true /* write */);
+
+ source.CopyStreamTo(compress);
+ compress.Close();
+ poutput->SetForReading();
+
+ // Check sizes
+ TEST_THAT(poutput->GetSize() < source.GetSize());
+ BOX_TRACE("compressed size = " << poutput->GetSize() <<
+ ", source size = " << source.GetSize());
+
+ // Decompress the data
+ {
+ CollectInBufferStream decompressed;
+ CompressStream decompress(poutput, false /* don't take ownership */, true /* read */, false /* write */);
+ decompress.CopyStreamTo(decompressed);
+ decompress.Close();
+
+ TEST_THAT(decompressed.GetSize() == source.GetSize());
+ TEST_THAT(::memcmp(decompressed.GetBuffer(), source.GetBuffer(), decompressed.GetSize()) == 0);
+ }
+
+ // Don't delete poutput, let mem leak testing ensure it's deleted.
+ }
+
+ // Set source to the beginning
+ source.Seek(0, IOStream::SeekType_Absolute);
+
+ // Test where the same stream compresses and decompresses, should be fun!
+ {
+ CollectInBufferStream output;
+ CopyInToOutStream copyer;
+ CompressStream compress(&copyer, false /* no ownership */, true, true);
+
+ bool done = false;
+ int count = 0;
+ int written = 0;
+ while(!done)
+ {
+ ++count;
+ bool do_sync = (count % 256) == 0;
+ uint8_t buffer[4096];
+ int r = source.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite);
+ if(r == 0)
+ {
+ done = true;
+ compress.Close();
+ }
+ else
+ {
+ compress.Write(buffer, r);
+ written += r;
+ if(do_sync)
+ {
+ compress.WriteAllBuffered();
+ }
+ }
+
+ int r2 = 0;
+ do
+ {
+ r2 = compress.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite);
+ if(r2 > 0)
+ {
+ output.Write(buffer, r2);
+ }
+ } while(r2 > 0);
+ if(do_sync && r != 0)
+ {
+ // Check that everything is synced
+ TEST_THAT(output.GetSize() == written);
+ TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0);
+ }
+ }
+ output.SetForReading();
+
+ // Test that it's the same
+ TEST_THAT(output.GetSize() == source.GetSize());
+ TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0);
+ }
+
+ return 0;
+}
+
+// Test basic interface
+int test(int argc, const char *argv[])
+{
+ // Bad data to compress!
+ char *data = (char *)malloc(DATA_SIZE);
+ for(int l = 0; l < DATA_SIZE; ++l)
+ {
+ data[l] = l*23;
+ }
+
+ // parameters about compression
+ int maxOutput = Compress_MaxSizeForCompressedData(DATA_SIZE);
+ TEST_THAT(maxOutput >= DATA_SIZE);
+
+ char *compressed = (char *)malloc(maxOutput);
+ int compressedSize = 0;
+
+ // Do compression, in small chunks
+ {
+ Compress<true> compress;
+
+ int in_loc = 0;
+ while(!compress.OutputHasFinished())
+ {
+ int ins = DATA_SIZE - in_loc;
+ if(ins > CHUNK_SIZE) ins = CHUNK_SIZE;
+
+ if(ins == 0)
+ {
+ compress.FinishInput();
+ }
+ else
+ {
+ compress.Input(data + in_loc, ins);
+ }
+ in_loc += ins;
+
+ // Get output data
+ int s = 0;
+ do
+ {
+ TEST_THAT(compressedSize < maxOutput);
+ s = compress.Output(compressed + compressedSize, maxOutput - compressedSize);
+ compressedSize += s;
+ } while(s > 0);
+ }
+ }
+
+ // a reasonable test, especially given the compressability of the input data.
+ TEST_THAT(compressedSize < DATA_SIZE);
+
+ // decompression
+ char *decompressed = (char*)malloc(DATA_SIZE * 2);
+ int decomp_size = 0;
+ {
+ Compress<false> decompress;
+
+ int in_loc = 0;
+ while(!decompress.OutputHasFinished())
+ {
+ int ins = compressedSize - in_loc;
+ if(ins > DECOMP_CHUNK_SIZE) ins = DECOMP_CHUNK_SIZE;
+
+ if(ins == 0)
+ {
+ decompress.FinishInput();
+ }
+ else
+ {
+ decompress.Input(compressed + in_loc, ins);
+ }
+ in_loc += ins;
+
+ // Get output data
+ int s = 0;
+ do
+ {
+ TEST_THAT(decomp_size <= DATA_SIZE);
+ s = decompress.Output(decompressed + decomp_size, (DATA_SIZE*2) - decomp_size);
+ decomp_size += s;
+ } while(s > 0);
+ }
+ }
+
+ TEST_THAT(decomp_size == DATA_SIZE);
+ TEST_THAT(::memcmp(data, decompressed, DATA_SIZE) == 0);
+
+ ::free(data);
+ ::free(compressed);
+ ::free(decompressed);
+
+ return test_stream();
+}
diff --git a/test/crypto/testcrypto.cpp b/test/crypto/testcrypto.cpp
new file mode 100644
index 00000000..6d90e5e7
--- /dev/null
+++ b/test/crypto/testcrypto.cpp
@@ -0,0 +1,314 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testcrypto.cpp
+// Purpose: test lib/crypto
+// Created: 1/12/03
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+#include <strings.h>
+#include <openssl/rand.h>
+
+#include "Test.h"
+#include "CipherContext.h"
+#include "CipherBlowfish.h"
+#include "CipherAES.h"
+#include "CipherException.h"
+#include "RollingChecksum.h"
+#include "Random.h"
+
+#include "MemLeakFindOn.h"
+
+#define STRING1 "Mary had a little lamb"
+#define STRING2 "Skjdf sdjf sjksd fjkhsdfjk hsdfuiohcverfg sdfnj sdfgkljh sdfjb jlhdfvghsdip vjsdfv bsdfhjvg yuiosdvgpvj kvbn m,sdvb sdfuiovg sdfuivhsdfjkv"
+
+#define KEY "0123456701234567012345670123456"
+#define KEY2 "1234567012345670123456A"
+
+#define CHECKSUM_DATA_SIZE (128*1024)
+#define CHECKSUM_BLOCK_SIZE_BASE (65*1024)
+#define CHECKSUM_BLOCK_SIZE_LAST (CHECKSUM_BLOCK_SIZE_BASE + 64)
+#define CHECKSUM_ROLLS 16
+
+void check_random_int(uint32_t max)
+{
+ for(int c = 0; c < 1024; ++c)
+ {
+ uint32_t v = Random::RandomInt(max);
+ TEST_THAT(v >= 0 && v <= max);
+ }
+}
+
+#define ZERO_BUFFER(x) ::memset(x, 0, sizeof(x));
+
+template<typename CipherType, int BLOCKSIZE>
+void test_cipher()
+{
+ {
+ // Make a couple of cipher contexts
+ CipherContext encrypt1;
+ encrypt1.Reset();
+ encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ TEST_CHECK_THROWS(encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))),
+ CipherException, AlreadyInitialised);
+ // Encrpt something
+ char buf1[256];
+ unsigned int buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1));
+ TEST_THAT(buf1_used >= sizeof(STRING1));
+ // Decrypt it
+ CipherContext decrypt1;
+ decrypt1.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ char buf1_de[256];
+ unsigned int buf1_de_used = decrypt1.TransformBlock(buf1_de, sizeof(buf1_de), buf1, buf1_used);
+ TEST_THAT(buf1_de_used == sizeof(STRING1));
+ TEST_THAT(memcmp(STRING1, buf1_de, sizeof(STRING1)) == 0);
+
+ // Use them again...
+ char buf1_de2[256];
+ unsigned int buf1_de2_used = decrypt1.TransformBlock(buf1_de2, sizeof(buf1_de2), buf1, buf1_used);
+ TEST_THAT(buf1_de2_used == sizeof(STRING1));
+ TEST_THAT(memcmp(STRING1, buf1_de2, sizeof(STRING1)) == 0);
+
+ // Test the interface
+ char buf2[256];
+ TEST_CHECK_THROWS(encrypt1.Transform(buf2, sizeof(buf2), STRING1, sizeof(STRING1)),
+ CipherException, BeginNotCalled);
+ TEST_CHECK_THROWS(encrypt1.Final(buf2, sizeof(buf2)),
+ CipherException, BeginNotCalled);
+ encrypt1.Begin();
+ int e = 0;
+ e = encrypt1.Transform(buf2, sizeof(buf2), STRING2, sizeof(STRING2) - 16);
+ e += encrypt1.Transform(buf2 + e, sizeof(buf2) - e, STRING2 + sizeof(STRING2) - 16, 16);
+ e += encrypt1.Final(buf2 + e, sizeof(buf2) - e);
+ TEST_THAT(e >= (int)sizeof(STRING2));
+
+ // Then decrypt
+ char buf2_de[256];
+ decrypt1.Begin();
+ TEST_CHECK_THROWS(decrypt1.Transform(buf2_de, 2, buf2, e), CipherException, OutputBufferTooSmall);
+ TEST_CHECK_THROWS(decrypt1.Final(buf2_de, 2), CipherException, OutputBufferTooSmall);
+ int d = decrypt1.Transform(buf2_de, sizeof(buf2_de), buf2, e - 48);
+ d += decrypt1.Transform(buf2_de + d, sizeof(buf2_de) - d, buf2 + e - 48, 48);
+ d += decrypt1.Final(buf2_de + d, sizeof(buf2_de) - d);
+ TEST_THAT(d == sizeof(STRING2));
+ TEST_THAT(memcmp(STRING2, buf2_de, sizeof(STRING2)) == 0);
+
+ // Try a reset and rekey
+ encrypt1.Reset();
+ encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY2, sizeof(KEY2)));
+ buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1));
+ }
+
+ // Test initialisation vectors
+ {
+ // Init with random IV
+ CipherContext encrypt2;
+ encrypt2.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ int ivLen;
+ char iv2[BLOCKSIZE];
+ const void *ivGen = encrypt2.SetRandomIV(ivLen);
+ TEST_THAT(ivLen == BLOCKSIZE); // block size
+ TEST_THAT(ivGen != 0);
+ memcpy(iv2, ivGen, ivLen);
+
+ char buf3[256];
+ unsigned int buf3_used = encrypt2.TransformBlock(buf3, sizeof(buf3), STRING2, sizeof(STRING2));
+
+ // Encrypt again with different IV
+ char iv3[BLOCKSIZE];
+ int ivLen3;
+ const void *ivGen3 = encrypt2.SetRandomIV(ivLen3);
+ TEST_THAT(ivLen3 == BLOCKSIZE); // block size
+ TEST_THAT(ivGen3 != 0);
+ memcpy(iv3, ivGen3, ivLen3);
+ // Check the two generated IVs are different
+ TEST_THAT(memcmp(iv2, iv3, BLOCKSIZE) != 0);
+
+ char buf4[256];
+ unsigned int buf4_used = encrypt2.TransformBlock(buf4, sizeof(buf4), STRING2, sizeof(STRING2));
+
+ // check encryptions are different
+ TEST_THAT(buf3_used == buf4_used);
+ TEST_THAT(memcmp(buf3, buf4, buf3_used) != 0);
+
+ // Test that decryption with the right IV works
+ CipherContext decrypt2;
+ decrypt2.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY), iv2));
+ char buf3_de[256];
+ unsigned int buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used);
+ TEST_THAT(buf3_de_used == sizeof(STRING2));
+ TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) == 0);
+
+ // And that using the wrong one doesn't
+ decrypt2.SetIV(iv3);
+ buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used);
+ TEST_THAT(buf3_de_used == sizeof(STRING2));
+ TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) != 0);
+ }
+
+ // Test with padding off.
+ {
+ CipherContext encrypt3;
+ encrypt3.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ encrypt3.UsePadding(false);
+
+ // Should fail because the encrypted size is not a multiple of the block size
+ char buf4[256];
+ encrypt3.Begin();
+ ZERO_BUFFER(buf4);
+ int buf4_used = encrypt3.Transform(buf4, sizeof(buf4), STRING2, 6);
+ TEST_CHECK_THROWS(encrypt3.Final(buf4, sizeof(buf4)), CipherException, EVPFinalFailure);
+
+ // Check a nice encryption with the correct block size
+ CipherContext encrypt4;
+ encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ encrypt4.UsePadding(false);
+ encrypt4.Begin();
+ ZERO_BUFFER(buf4);
+ buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, 16);
+ buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4));
+ TEST_THAT(buf4_used == 16);
+
+ // Check it's encrypted to the same thing as when there's padding on
+ CipherContext encrypt4b;
+ encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ encrypt4b.Begin();
+ char buf4b[256];
+ ZERO_BUFFER(buf4b);
+ int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, 16);
+ buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b));
+ TEST_THAT(buf4b_used == 16+BLOCKSIZE);
+ TEST_THAT(::memcmp(buf4, buf4b, 16) == 0);
+
+ // Decrypt
+ char buf4_de[256];
+ CipherContext decrypt4;
+ decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ decrypt4.UsePadding(false);
+ decrypt4.Begin();
+ ZERO_BUFFER(buf4_de);
+ int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, 16);
+ buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de));
+ TEST_THAT(buf4_de_used == 16);
+ TEST_THAT(::memcmp(buf4_de, STRING2, 16) == 0);
+
+ // Test that the TransformBlock thing works as expected too with blocks the same size as the input
+ TEST_THAT(encrypt4.TransformBlock(buf4, 16, STRING2, 16) == 16);
+ // But that it exceptions if we try the trick with padding on
+ encrypt4.UsePadding(true);
+ TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, 16, STRING2, 16), CipherException, OutputBufferTooSmall);
+ }
+
+ // And again, but with different string size
+ {
+ char buf4[256];
+ int buf4_used;
+
+ // Check a nice encryption with the correct block size
+ CipherContext encrypt4;
+ encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ encrypt4.UsePadding(false);
+ encrypt4.Begin();
+ ZERO_BUFFER(buf4);
+ buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, (BLOCKSIZE*3)); // do three blocks worth
+ buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4));
+ TEST_THAT(buf4_used == (BLOCKSIZE*3));
+
+ // Check it's encrypted to the same thing as when there's padding on
+ CipherContext encrypt4b;
+ encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ encrypt4b.Begin();
+ char buf4b[256];
+ ZERO_BUFFER(buf4b);
+ int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, (BLOCKSIZE*3));
+ buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b));
+ TEST_THAT(buf4b_used == (BLOCKSIZE*4));
+ TEST_THAT(::memcmp(buf4, buf4b, (BLOCKSIZE*3)) == 0);
+
+ // Decrypt
+ char buf4_de[256];
+ CipherContext decrypt4;
+ decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
+ decrypt4.UsePadding(false);
+ decrypt4.Begin();
+ ZERO_BUFFER(buf4_de);
+ int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, (BLOCKSIZE*3));
+ buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de));
+ TEST_THAT(buf4_de_used == (BLOCKSIZE*3));
+ TEST_THAT(::memcmp(buf4_de, STRING2, (BLOCKSIZE*3)) == 0);
+
+ // Test that the TransformBlock thing works as expected too with blocks the same size as the input
+ TEST_THAT(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)) == (BLOCKSIZE*3));
+ // But that it exceptions if we try the trick with padding on
+ encrypt4.UsePadding(true);
+ TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)), CipherException, OutputBufferTooSmall);
+ }
+}
+
+int test(int argc, const char *argv[])
+{
+ Random::Initialise();
+
+ // Cipher type
+ ::printf("Blowfish...\n");
+ test_cipher<CipherBlowfish, 8>();
+#ifndef HAVE_OLD_SSL
+ ::printf("AES...\n");
+ test_cipher<CipherAES, 16>();
+#else
+ ::printf("Skipping AES -- not supported by version of OpenSSL in use.\n");
+#endif
+
+ ::printf("Misc...\n");
+ // Check rolling checksums
+ uint8_t *checkdata_blk = (uint8_t *)malloc(CHECKSUM_DATA_SIZE);
+ uint8_t *checkdata = checkdata_blk;
+ RAND_pseudo_bytes(checkdata, CHECKSUM_DATA_SIZE);
+ for(int size = CHECKSUM_BLOCK_SIZE_BASE; size <= CHECKSUM_BLOCK_SIZE_LAST; ++size)
+ {
+ // Test skip-roll code
+ RollingChecksum rollFast(checkdata, size);
+ rollFast.RollForwardSeveral(checkdata, checkdata+size, size, CHECKSUM_ROLLS/2);
+ RollingChecksum calc(checkdata + (CHECKSUM_ROLLS/2), size);
+ TEST_THAT(calc.GetChecksum() == rollFast.GetChecksum());
+
+ //printf("size = %d\n", size);
+ // Checksum to roll
+ RollingChecksum roll(checkdata, size);
+
+ // Roll forward
+ for(int l = 0; l < CHECKSUM_ROLLS; ++l)
+ {
+ // Calculate new one
+ RollingChecksum calc(checkdata, size);
+
+ //printf("%08X %08X %d %d\n", roll.GetChecksum(), calc.GetChecksum(), checkdata[0], checkdata[size]);
+
+ // Compare them!
+ TEST_THAT(calc.GetChecksum() == roll.GetChecksum());
+
+ // Roll it onwards
+ roll.RollForward(checkdata[0], checkdata[size], size);
+
+ // increment
+ ++checkdata;
+ }
+ }
+ ::free(checkdata_blk);
+
+ // Random integers
+ check_random_int(0);
+ check_random_int(1);
+ check_random_int(5);
+ check_random_int(15); // all 1's
+ check_random_int(1022);
+
+ return 0;
+}
+
+
+
diff --git a/test/httpserver/testfiles/httpserver.conf b/test/httpserver/testfiles/httpserver.conf
new file mode 100644
index 00000000..1a1c4644
--- /dev/null
+++ b/test/httpserver/testfiles/httpserver.conf
@@ -0,0 +1,8 @@
+
+AddressPrefix = http://localhost:1080
+
+Server
+{
+ PidFile = testfiles/httpserver.pid
+ ListenAddresses = inet:localhost:1080
+}
diff --git a/test/httpserver/testfiles/photos/puppy.jpg b/test/httpserver/testfiles/photos/puppy.jpg
new file mode 100644
index 00000000..a326a6a7
--- /dev/null
+++ b/test/httpserver/testfiles/photos/puppy.jpg
@@ -0,0 +1 @@
+omgpuppies!
diff --git a/test/httpserver/testfiles/s3simulator.conf b/test/httpserver/testfiles/s3simulator.conf
new file mode 100644
index 00000000..07921560
--- /dev/null
+++ b/test/httpserver/testfiles/s3simulator.conf
@@ -0,0 +1,10 @@
+AccessKey = 0PN5J17HBGZHT7JJ3X82
+SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o
+StoreDirectory = testfiles
+AddressPrefix = http://localhost:1080
+
+Server
+{
+ PidFile = testfiles/s3simulator.pid
+ ListenAddresses = inet:localhost:1080
+}
diff --git a/test/httpserver/testfiles/testrequests.pl b/test/httpserver/testfiles/testrequests.pl
new file mode 100755
index 00000000..85380ee0
--- /dev/null
+++ b/test/httpserver/testfiles/testrequests.pl
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+use strict;
+use LWP::UserAgent;
+
+my $url_base = 'http://localhost:1080';
+
+my $ua = LWP::UserAgent->new(env_proxy => 0, keep_alive => 1, timeout => 30);
+
+print "GET request...\n";
+
+my $response1 = $ua->get("$url_base/test-one/34/341s/234?p1=vOne&p2=vTwo");
+die $response1->content unless $response1->is_success();
+
+my $content = $response1->content();
+
+check_url($content, '/test-one/34/341s/234');
+check_params($content, 'p1'=>'vOne','p2'=>'vTwo');
+
+print "POST request...\n";
+
+my %post = ('sdfgksjhdfsd'=>'dfvsiufy2e3434','sxciuhwf8723e4'=>'238947829334',
+ '&sfsfsfskfhs'=>'?hdkfjhsjfds','fdsf=sdf2342'=>'3984sajhksda');
+
+my $response2 = $ua->post("$url_base/tdskjhfsjdkhf2943734?p1=vOne&p2=vTwo", \%post);
+
+my $content2 = $response2->content();
+
+check_url($content2, '/tdskjhfsjdkhf2943734');
+check_params($content2, %post);
+
+print "HEAD request...\n";
+
+my $response3 = $ua->head("$url_base/tdskjhfsdfkjhs");
+
+if($response3->content() ne '')
+{
+ print "Content not zero length\n";
+ exit(1);
+}
+
+if($response3->code() != 200)
+{
+ print "Wrong response code\n";
+ exit(1);
+}
+
+print "Redirected GET request...\n";
+
+my $response4 = $ua->get("$url_base/redirect?key=value");
+exit 4 unless $response4->is_success();
+
+my $content4 = $response4->content();
+
+check_url($content4, '/redirected');
+check_params($content4);
+
+print "Cookie tests...\n";
+
+# from examples in specs
+test_cookies('CUSTOMER=WILE_E_COYOTE', 'CUSTOMER=WILE_E_COYOTE');
+test_cookies('CUSTOMER="WILE_E_COYOTE"; C2="pants"', 'CUSTOMER=WILE_E_COYOTE', 'C2=pants');
+test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001');
+test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001', 'SHIPPING=FEDEX');
+test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', 'Customer=WILE_E_COYOTE');
+test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" ',
+ 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001');
+test_cookies(qq!\$Version="1"; Customer="WILE_E_COYOTE"; \$Path="/acme"; Part_Number="Rocket_Launcher_0001"; \$Path="/acme"; Shipping="FedEx"; \t \$Path="/acme"!,
+ 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001', 'Shipping=FedEx');
+
+# test the server setting cookies in the UA
+require HTTP::Cookies;
+$ua->cookie_jar(HTTP::Cookies->new());
+$ua->get("$url_base/set-cookie");
+test_cookies('', 'SetByServer=Value1');
+
+sub test_cookies
+{
+ my ($c_str, @cookies) = @_;
+ test_cookies2($c_str, @cookies);
+ $c_str =~ s/;/,/g;
+ test_cookies2($c_str, @cookies);
+}
+
+sub test_cookies2
+{
+ my ($c_str, @cookies) = @_;
+ my $r;
+ if($c_str ne '')
+ {
+ $r = $ua->get("$url_base/cookie", 'Cookie' => $c_str);
+ }
+ else
+ {
+ $r = $ua->get("$url_base/cookie");
+ }
+ my $c = $r->content();
+ for(@cookies)
+ {
+ unless($c =~ m/COOKIE:$_<br>/)
+ {
+ print "Cookie $_ not found\n";
+ exit(1);
+ }
+ }
+}
+
+
+sub check_url
+{
+ my ($c,$url) = @_;
+ unless($c =~ m~URI:</b> (.+?)</p>~)
+ {
+ print "URI not found\n";
+ exit(1);
+ }
+ if($url ne $1)
+ {
+ print "Wrong URI in content\n";
+ exit(1);
+ }
+}
+
+sub check_params
+{
+ my ($c,%params) = @_;
+
+ while($c =~ m/^PARAM:(.+)=(.+?)<br>/mg)
+ {
+ if($params{$1} ne $2)
+ {
+ print "$1=$2 not found in response\n";
+ exit(1);
+ }
+ delete $params{$1}
+ }
+
+ my @k = keys %params;
+ if($#k != -1)
+ {
+ print "Didn't find all params\n";
+ exit(1);
+ }
+}
diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp
new file mode 100644
index 00000000..160cb32f
--- /dev/null
+++ b/test/httpserver/testhttpserver.cpp
@@ -0,0 +1,480 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testhttpserver.cpp
+// Purpose: Test code for HTTP server class
+// Created: 26/3/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+
+#ifdef HAVE_SIGNAL_H
+ #include <signal.h>
+#endif
+
+#include <openssl/hmac.h>
+
+#include "autogen_HTTPException.h"
+#include "HTTPRequest.h"
+#include "HTTPResponse.h"
+#include "HTTPServer.h"
+#include "IOStreamGetLine.h"
+#include "S3Client.h"
+#include "S3Simulator.h"
+#include "ServerControl.h"
+#include "Test.h"
+#include "decode.h"
+#include "encode.h"
+
+#include "MemLeakFindOn.h"
+
+class TestWebServer : public HTTPServer
+{
+public:
+ TestWebServer();
+ ~TestWebServer();
+
+ virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse);
+
+};
+
+// Build a nice HTML response, so this can also be tested neatly in a browser
+void TestWebServer::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse)
+{
+ // Test redirection mechanism
+ if(rRequest.GetRequestURI() == "/redirect")
+ {
+ rResponse.SetAsRedirect("/redirected");
+ return;
+ }
+
+ // Set a cookie?
+ if(rRequest.GetRequestURI() == "/set-cookie")
+ {
+ rResponse.SetCookie("SetByServer", "Value1");
+ }
+
+ #define DEFAULT_RESPONSE_1 "<html>\n<head><title>TEST SERVER RESPONSE</title></head>\n<body><h1>Test response</h1>\n<p><b>URI:</b> "
+ #define DEFAULT_RESPONSE_3 "</p>\n<p><b>Query string:</b> "
+ #define DEFAULT_RESPONSE_4 "</p>\n<p><b>Method:</b> "
+ #define DEFAULT_RESPONSE_5 "</p>\n<p><b>Decoded query:</b><br>"
+ #define DEFAULT_RESPONSE_6 "</p>\n<p><b>Content type:</b> "
+ #define DEFAULT_RESPONSE_7 "</p>\n<p><b>Content length:</b> "
+ #define DEFAULT_RESPONSE_8 "</p>\n<p><b>Cookies:</b><br>\n"
+ #define DEFAULT_RESPONSE_2 "</p>\n</body>\n</html>\n"
+
+ rResponse.SetResponseCode(HTTPResponse::Code_OK);
+ rResponse.SetContentType("text/html");
+ rResponse.Write(DEFAULT_RESPONSE_1, sizeof(DEFAULT_RESPONSE_1) - 1);
+ const std::string &ruri(rRequest.GetRequestURI());
+ rResponse.Write(ruri.c_str(), ruri.size());
+ rResponse.Write(DEFAULT_RESPONSE_3, sizeof(DEFAULT_RESPONSE_3) - 1);
+ const std::string &rquery(rRequest.GetQueryString());
+ rResponse.Write(rquery.c_str(), rquery.size());
+ rResponse.Write(DEFAULT_RESPONSE_4, sizeof(DEFAULT_RESPONSE_4) - 1);
+ {
+ const char *m = "????";
+ switch(rRequest.GetMethod())
+ {
+ case HTTPRequest::Method_GET: m = "GET "; break;
+ case HTTPRequest::Method_HEAD: m = "HEAD"; break;
+ case HTTPRequest::Method_POST: m = "POST"; break;
+ default: m = "UNKNOWN";
+ }
+ rResponse.Write(m, 4);
+ }
+ rResponse.Write(DEFAULT_RESPONSE_5, sizeof(DEFAULT_RESPONSE_5) - 1);
+ {
+ const HTTPRequest::Query_t &rquery(rRequest.GetQuery());
+ for(HTTPRequest::Query_t::const_iterator i(rquery.begin()); i != rquery.end(); ++i)
+ {
+ rResponse.Write("\nPARAM:", 7);
+ rResponse.Write(i->first.c_str(), i->first.size());
+ rResponse.Write("=", 1);
+ rResponse.Write(i->second.c_str(), i->second.size());
+ rResponse.Write("<br>\n", 4);
+ }
+ }
+ rResponse.Write(DEFAULT_RESPONSE_6, sizeof(DEFAULT_RESPONSE_6) - 1);
+ const std::string &rctype(rRequest.GetContentType());
+ rResponse.Write(rctype.c_str(), rctype.size());
+ rResponse.Write(DEFAULT_RESPONSE_7, sizeof(DEFAULT_RESPONSE_7) - 1);
+ {
+ char l[32];
+ rResponse.Write(l, ::sprintf(l, "%d", rRequest.GetContentLength()));
+ }
+ rResponse.Write(DEFAULT_RESPONSE_8, sizeof(DEFAULT_RESPONSE_8) - 1);
+ const HTTPRequest::CookieJar_t *pcookies = rRequest.GetCookies();
+ if(pcookies != 0)
+ {
+ HTTPRequest::CookieJar_t::const_iterator i(pcookies->begin());
+ for(; i != pcookies->end(); ++i)
+ {
+ char t[512];
+ rResponse.Write(t, ::sprintf(t, "COOKIE:%s=%s<br>\n", i->first.c_str(), i->second.c_str()));
+ }
+ }
+ rResponse.Write(DEFAULT_RESPONSE_2, sizeof(DEFAULT_RESPONSE_2) - 1);
+}
+
+TestWebServer::TestWebServer() {}
+TestWebServer::~TestWebServer() {}
+
+int test(int argc, const char *argv[])
+{
+ if(argc >= 2 && ::strcmp(argv[1], "server") == 0)
+ {
+ // Run a server
+ TestWebServer server;
+ return server.Main("doesnotexist", argc - 1, argv + 1);
+ }
+
+ if(argc >= 2 && ::strcmp(argv[1], "s3server") == 0)
+ {
+ // Run a server
+ S3Simulator server;
+ return server.Main("doesnotexist", argc - 1, argv + 1);
+ }
+
+ // Start the server
+ int pid = LaunchServer("./test server testfiles/httpserver.conf", "testfiles/httpserver.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid <= 0)
+ {
+ return 0;
+ }
+
+ // Run the request script
+ TEST_THAT(::system("perl testfiles/testrequests.pl") == 0);
+
+ #ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+ #endif
+
+ SocketStream sock;
+ sock.Open(Socket::TypeINET, "localhost", 1080);
+
+ for (int i = 0; i < 4; i++)
+ {
+ HTTPRequest request(HTTPRequest::Method_GET,
+ "/test-one/34/341s/234?p1=vOne&p2=vTwo");
+
+ if (i < 2)
+ {
+ // first set of passes has keepalive off by default,
+ // so when i == 1 the socket has already been closed
+ // by the server, and we'll get -EPIPE when we try
+ // to send the request.
+ request.SetClientKeepAliveRequested(false);
+ }
+ else
+ {
+ request.SetClientKeepAliveRequested(true);
+ }
+
+ if (i == 1)
+ {
+ sleep(1); // need time for our process to realise
+ // that the peer has died, otherwise no SIGPIPE :(
+ TEST_CHECK_THROWS(request.Send(sock,
+ IOStream::TimeOutInfinite),
+ ConnectionException, SocketWriteError);
+ sock.Close();
+ sock.Open(Socket::TypeINET, "localhost", 1080);
+ continue;
+ }
+ else
+ {
+ request.Send(sock, IOStream::TimeOutInfinite);
+ }
+
+ HTTPResponse response;
+ response.Receive(sock);
+
+ TEST_THAT(response.GetResponseCode() == HTTPResponse::Code_OK);
+ TEST_THAT(response.GetContentType() == "text/html");
+
+ IOStreamGetLine getline(response);
+ std::string line;
+
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<html>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<head><title>TEST SERVER RESPONSE</title></head>",
+ line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<body><h1>Test response</h1>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>URI:</b> /test-one/34/341s/234</p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>Query string:</b> p1=vOne&p2=vTwo</p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>Method:</b> GET </p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>Decoded query:</b><br>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("PARAM:p1=vOne<br>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("PARAM:p2=vTwo<br></p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>Content type:</b> </p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>Content length:</b> -1</p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("<p><b>Cookies:</b><br>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("</p>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("</body>", line);
+ TEST_THAT(getline.GetLine(line));
+ TEST_EQUAL("</html>", line);
+ }
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/httpserver.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
+ #endif
+
+ // correct, official signature should succeed, with lower-case header
+ {
+ // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
+ HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg");
+ request.SetHostName("johnsmith.s3.amazonaws.com");
+ request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000");
+ request.AddHeader("authorization",
+ "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA=");
+
+ S3Simulator simulator;
+ simulator.Configure("testfiles/s3simulator.conf");
+
+ CollectInBufferStream response_buffer;
+ HTTPResponse response(&response_buffer);
+
+ simulator.Handle(request, response);
+ TEST_EQUAL(200, response.GetResponseCode());
+
+ std::string response_data((const char *)response.GetBuffer(),
+ response.GetSize());
+ TEST_EQUAL("omgpuppies!\n", response_data);
+ }
+
+ // modified signature should fail
+ {
+ // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
+ HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg");
+ request.SetHostName("johnsmith.s3.amazonaws.com");
+ request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000");
+ request.AddHeader("authorization",
+ "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbB=");
+
+ S3Simulator simulator;
+ simulator.Configure("testfiles/s3simulator.conf");
+
+ CollectInBufferStream response_buffer;
+ HTTPResponse response(&response_buffer);
+
+ simulator.Handle(request, response);
+ TEST_EQUAL(401, response.GetResponseCode());
+
+ std::string response_data((const char *)response.GetBuffer(),
+ response.GetSize());
+ TEST_EQUAL("<html><head>"
+ "<title>Internal Server Error</title></head>\n"
+ "<h1>Internal Server Error</h1>\n"
+ "<p>An error, type Authentication Failed occured "
+ "when processing the request.</p>"
+ "<p>Please try again later.</p></body>\n"
+ "</html>\n", response_data);
+ }
+
+ // S3Client tests
+ {
+ S3Simulator simulator;
+ simulator.Configure("testfiles/s3simulator.conf");
+ S3Client client(&simulator, "johnsmith.s3.amazonaws.com",
+ "0PN5J17HBGZHT7JJ3X82",
+ "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o");
+
+ HTTPResponse response = client.GetObject("/photos/puppy.jpg");
+ TEST_EQUAL(200, response.GetResponseCode());
+ std::string response_data((const char *)response.GetBuffer(),
+ response.GetSize());
+ TEST_EQUAL("omgpuppies!\n", response_data);
+
+ // make sure that assigning to HTTPResponse does clear stream
+ response = client.GetObject("/photos/puppy.jpg");
+ TEST_EQUAL(200, response.GetResponseCode());
+ response_data = std::string((const char *)response.GetBuffer(),
+ response.GetSize());
+ TEST_EQUAL("omgpuppies!\n", response_data);
+
+ response = client.GetObject("/nonexist");
+ TEST_EQUAL(404, response.GetResponseCode());
+
+ FileStream fs("testfiles/testrequests.pl");
+ response = client.PutObject("/newfile", fs);
+ TEST_EQUAL(200, response.GetResponseCode());
+
+ response = client.GetObject("/newfile");
+ TEST_EQUAL(200, response.GetResponseCode());
+ TEST_THAT(fs.CompareWith(response));
+ TEST_EQUAL(0, ::unlink("testfiles/newfile"));
+ }
+
+ {
+ HTTPRequest request(HTTPRequest::Method_PUT,
+ "/newfile");
+ request.SetHostName("quotes.s3.amazonaws.com");
+ request.AddHeader("date", "Wed, 01 Mar 2006 12:00:00 GMT");
+ request.AddHeader("authorization", "AWS 0PN5J17HBGZHT7JJ3X82:XtMYZf0hdOo4TdPYQknZk0Lz7rw=");
+ request.AddHeader("Content-Type", "text/plain");
+
+ FileStream fs("testfiles/testrequests.pl");
+ fs.CopyStreamTo(request);
+ request.SetForReading();
+
+ CollectInBufferStream response_buffer;
+ HTTPResponse response(&response_buffer);
+
+ S3Simulator simulator;
+ simulator.Configure("testfiles/s3simulator.conf");
+ simulator.Handle(request, response);
+
+ TEST_EQUAL(200, response.GetResponseCode());
+ TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2"));
+ TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id"));
+ TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date"));
+ TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified"));
+ TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag"));
+ TEST_EQUAL("", response.GetContentType());
+ TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server"));
+ TEST_EQUAL(0, response.GetSize());
+
+ FileStream f1("testfiles/testrequests.pl");
+ FileStream f2("testfiles/newfile");
+ TEST_THAT(f1.CompareWith(f2));
+ TEST_EQUAL(0, ::unlink("testfiles/newfile"));
+ }
+
+ // Start the S3Simulator server
+ pid = LaunchServer("./test s3server testfiles/s3simulator.conf",
+ "testfiles/s3simulator.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid <= 0)
+ {
+ return 0;
+ }
+
+ sock.Close();
+ sock.Open(Socket::TypeINET, "localhost", 1080);
+
+ {
+ HTTPRequest request(HTTPRequest::Method_GET, "/nonexist");
+ request.SetHostName("quotes.s3.amazonaws.com");
+ request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT");
+ request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA=");
+ request.SetClientKeepAliveRequested(true);
+ request.Send(sock, IOStream::TimeOutInfinite);
+
+ HTTPResponse response;
+ response.Receive(sock);
+ std::string value;
+ TEST_EQUAL(404, response.GetResponseCode());
+ }
+
+ #ifndef WIN32 // much harder to make files inaccessible on WIN32
+ // Make file inaccessible, should cause server to return a 403 error,
+ // unless of course the test is run as root :)
+ {
+ TEST_THAT(chmod("testfiles/testrequests.pl", 0) == 0);
+ HTTPRequest request(HTTPRequest::Method_GET,
+ "/testrequests.pl");
+ request.SetHostName("quotes.s3.amazonaws.com");
+ request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT");
+ request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8=");
+ request.SetClientKeepAliveRequested(true);
+ request.Send(sock, IOStream::TimeOutInfinite);
+
+ HTTPResponse response;
+ response.Receive(sock);
+ std::string value;
+ TEST_EQUAL(403, response.GetResponseCode());
+ TEST_THAT(chmod("testfiles/testrequests.pl", 0755) == 0);
+ }
+ #endif
+
+ {
+ HTTPRequest request(HTTPRequest::Method_GET,
+ "/testrequests.pl");
+ request.SetHostName("quotes.s3.amazonaws.com");
+ request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT");
+ request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8=");
+ request.SetClientKeepAliveRequested(true);
+ request.Send(sock, IOStream::TimeOutInfinite);
+
+ HTTPResponse response;
+ response.Receive(sock);
+ std::string value;
+ TEST_EQUAL(200, response.GetResponseCode());
+ TEST_EQUAL("qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY", response.GetHeaderValue("x-amz-id-2"));
+ TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id"));
+ TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date"));
+ TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified"));
+ TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag"));
+ TEST_EQUAL("text/plain", response.GetContentType());
+ TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server"));
+
+ FileStream file("testfiles/testrequests.pl");
+ TEST_THAT(file.CompareWith(response));
+ }
+
+ {
+ HTTPRequest request(HTTPRequest::Method_PUT,
+ "/newfile");
+ request.SetHostName("quotes.s3.amazonaws.com");
+ request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT");
+ request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:kfY1m6V3zTufRy2kj92FpQGKz4M=");
+ request.AddHeader("Content-Type", "text/plain");
+ FileStream fs("testfiles/testrequests.pl");
+ HTTPResponse response;
+ request.SendWithStream(sock,
+ IOStream::TimeOutInfinite /* or 10000 milliseconds */,
+ &fs, response);
+ std::string value;
+ TEST_EQUAL(200, response.GetResponseCode());
+ TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2"));
+ TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id"));
+ TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date"));
+ TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified"));
+ TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag"));
+ TEST_EQUAL("", response.GetContentType());
+ TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server"));
+ TEST_EQUAL(0, response.GetSize());
+
+ FileStream f1("testfiles/testrequests.pl");
+ FileStream f2("testfiles/newfile");
+ TEST_THAT(f1.CompareWith(f2));
+ }
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/s3simulator.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("generic-httpserver.memleaks");
+ #endif
+
+ return 0;
+}
+
diff --git a/test/raidfile/testextra b/test/raidfile/testextra
new file mode 100644
index 00000000..1f4fbcb2
--- /dev/null
+++ b/test/raidfile/testextra
@@ -0,0 +1,7 @@
+mkdir testfiles/0_0
+mkdir testfiles/0_1
+mkdir testfiles/0_2
+mkdir testfiles/1_0
+mkdir testfiles/1_1
+mkdir testfiles/1_2
+mkdir testfiles/2
diff --git a/test/raidfile/testfiles/raidfile.conf b/test/raidfile/testfiles/raidfile.conf
new file mode 100644
index 00000000..6c6d02f9
--- /dev/null
+++ b/test/raidfile/testfiles/raidfile.conf
@@ -0,0 +1,30 @@
+
+disc0
+{
+ SetNumber = 0
+ BlockSize = 2048
+ Dir0 = testfiles/0_0
+ Dir1 = testfiles/0_1
+ Dir2 = testfiles/0_2
+}
+
+disc1
+{
+ SetNumber = 1
+ BlockSize = 2048
+ Dir0 = testfiles/1_0
+ Dir1 = testfiles/1_1
+ Dir2 = testfiles/1_2
+}
+
+disc2
+{
+ SetNumber = 2
+ BlockSize = 2048
+ Dir0 = testfiles/2
+ Dir1 = testfiles/2
+ Dir2 = testfiles/2
+}
+
+
+
diff --git a/test/raidfile/testraidfile.cpp b/test/raidfile/testraidfile.cpp
new file mode 100644
index 00000000..160de5c9
--- /dev/null
+++ b/test/raidfile/testraidfile.cpp
@@ -0,0 +1,981 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: test/raidfile/test.cpp
+// Purpose: Test RaidFile system
+// Created: 2003/07/08
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef HAVE_SYSCALL
+#include <sys/syscall.h>
+#endif
+
+#include <string.h>
+
+#include "Test.h"
+#include "RaidFileController.h"
+#include "RaidFileWrite.h"
+#include "RaidFileException.h"
+#include "RaidFileRead.h"
+#include "Guards.h"
+
+#include "MemLeakFindOn.h"
+
+#define RAID_BLOCK_SIZE 2048
+#define RAID_NUMBER_DISCS 3
+
+#define TEST_DATA_SIZE (8*1024 + 173)
+
+#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
+ #define TRF_CAN_INTERCEPT
+#endif
+
+
+#ifdef TRF_CAN_INTERCEPT
+// function in intercept.cpp for setting up errors
+void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror);
+bool intercept_triggered();
+void intercept_clear_setup();
+#endif
+
+// Nice random data for testing written files
+class R250 {
+public:
+ // Set up internal state table with 32-bit random numbers.
+ // The bizarre bit-twiddling is because rand() returns 16 bits of which
+ // the bottom bit is always zero! Hence, I use only some of the bits.
+ // You might want to do something better than this....
+
+ R250(int seed) : posn1(0), posn2(103)
+ {
+ // populate the state and incr tables
+ srand(seed);
+
+ for (int i = 0; i != stateLen; ++i) {
+ state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2);
+ incrTable[i] = i == stateLen - 1 ? 0 : i + 1;
+ }
+
+ // stir up the numbers to ensure they're random
+
+ for (int j = 0; j != stateLen * 4; ++j)
+ (void) next();
+ }
+
+ // Returns the next random number. Xor together two elements separated
+ // by 103 mod 250, replacing the first element with the result. Then
+ // increment the two indices mod 250.
+ inline int next()
+ {
+ int ret = (state[posn1] ^= state[posn2]); // xor and replace element
+
+ posn1 = incrTable[posn1]; // increment indices using lookup table
+ posn2 = incrTable[posn2];
+
+ return ret;
+ }
+private:
+ enum { stateLen = 250 }; // length of the state table
+ int state[stateLen]; // holds the random number state
+ int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen
+ int posn1, posn2; // indices into the state table
+};
+
+void testReadingFileContents(int set, const char *filename, void *data, int datasize, bool TestRAIDProperties, int UsageInBlocks = -1)
+{
+ // Work out which disc is the "start" disc.
+ int h = 0;
+ int n = 0;
+ while(filename[n] != 0)
+ {
+ h += filename[n];
+ n++;
+ }
+ int startDisc = h % RAID_NUMBER_DISCS;
+
+//printf("UsageInBlocks = %d\n", UsageInBlocks);
+
+ // sizes of data to read
+ static int readsizes[] = {2047, 1, 1, 2047, 12, 1, 1, RAID_BLOCK_SIZE - (12+1+1), RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 246, (RAID_BLOCK_SIZE * 3) + 3, 243};
+
+ // read the data in to test it
+ char testbuff[(RAID_BLOCK_SIZE * 3) + 128]; // bigger than the max request above!
+ std::auto_ptr<RaidFileRead> pread = RaidFileRead::Open(set, filename);
+ if(UsageInBlocks != -1)
+ {
+ TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks());
+ }
+ //printf("%d, %d\n", pread->GetFileSize(), datasize);
+ TEST_THAT(pread->GetFileSize() == datasize);
+ IOStream &readstream1 = *(pread.get());
+ int dataread = 0;
+ int r;
+ int readsize = readsizes[0];
+ int bsc = 1;
+ while((r = readstream1.Read(testbuff, readsize)) > 0)
+ {
+ //printf("== read, asked: %d actual: %d\n", readsize, r);
+ TEST_THAT(((dataread+r) == datasize) || r == readsize);
+ TEST_THAT(r > 0);
+ TEST_THAT(readstream1.StreamDataLeft()); // check IOStream interface is correct
+ for(int z = 0; z < r; ++z)
+ {
+ TEST_THAT(((char*)data)[dataread+z] == testbuff[z]);
+ /*if(((char*)data)[dataread+z] != testbuff[z])
+ {
+ printf("z = %d\n", z);
+ }*/
+ }
+ // Next size...
+ if(bsc <= (int)((sizeof(readsizes) / sizeof(readsizes[0])) - 1))
+ {
+ readsize = readsizes[bsc++];
+ }
+ dataread += r;
+ }
+ TEST_THAT(dataread == datasize);
+ pread->Close();
+
+ // open and close it...
+ pread.reset(RaidFileRead::Open(set, filename).release());
+ if(UsageInBlocks != -1)
+ {
+ TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks());
+ }
+ IOStream &readstream2 = *(pread.get());
+
+ // positions to try seeking too..
+ static int seekpos[] = {0, 1, 2, 887, 887+256 /* no seek required */, RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 1, RAID_BLOCK_SIZE - 1, RAID_BLOCK_SIZE*3, RAID_BLOCK_SIZE + 23, RAID_BLOCK_SIZE * 4, RAID_BLOCK_SIZE * 4 + 1};
+
+ for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p)
+ {
+ //printf("== seekpos = %d\n", seekpos[p]);
+ // only try if test file size is big enough
+ if(seekpos[p]+256 > datasize) continue;
+
+ readstream2.Seek(seekpos[p], IOStream::SeekType_Absolute);
+ TEST_THAT(readstream2.Read(testbuff, 256) == 256);
+ TEST_THAT(readstream2.GetPosition() == seekpos[p] + 256);
+ TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0);
+ }
+
+ // open and close it...
+ pread.reset(RaidFileRead::Open(set, filename).release());
+ if(UsageInBlocks != -1)
+ {
+ TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks());
+ }
+ IOStream &readstream3 = *(pread.get());
+
+ int pos = 0;
+ for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p)
+ {
+ // only try if test file size is big enough
+ if(seekpos[p]+256 > datasize) continue;
+
+ //printf("pos %d, seekpos %d, p %d\n", pos, seekpos[p], p);
+
+ readstream3.Seek(seekpos[p] - pos, IOStream::SeekType_Relative);
+ TEST_THAT(readstream3.Read(testbuff, 256) == 256);
+ pos = seekpos[p] + 256;
+ TEST_THAT(readstream3.GetPosition() == pos);
+ TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0);
+ }
+
+ // Straight read of file
+ pread.reset(RaidFileRead::Open(set, filename).release());
+ if(UsageInBlocks != -1)
+ {
+ TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks());
+ }
+ IOStream &readstream4 = *(pread.get());
+ pos = 0;
+ int bytesread = 0;
+ while((r = readstream4.Read(testbuff, 988)) != 0)
+ {
+ TEST_THAT(readstream4.StreamDataLeft()); // check IOStream interface is behaving as expected
+
+ // check contents
+ TEST_THAT(::memcmp(((char*)data) + pos, testbuff, r) == 0);
+
+ // move on
+ pos += r;
+ bytesread += r;
+ }
+ TEST_THAT(!readstream4.StreamDataLeft()); // check IOStream interface is correct
+ pread.reset();
+
+ // Be nasty, and create some errors for the RAID stuff to recover from...
+ if(TestRAIDProperties)
+ {
+ char stripe1fn[256], stripe1fnRename[256];
+ sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename);
+ sprintf(stripe1fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, startDisc,
+ filename);
+ char stripe2fn[256], stripe2fnRename[256];
+ sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf", set,
+ (startDisc + 1) % RAID_NUMBER_DISCS, filename);
+ sprintf(stripe2fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf-REMOVED", set,
+ (startDisc + 1) % RAID_NUMBER_DISCS, filename);
+
+ // Read with stripe1 + parity
+ TEST_THAT(::rename(stripe2fn, stripe2fnRename) == 0);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(::rename(stripe2fnRename, stripe2fn) == 0);
+
+ // Read with stripe2 + parity
+ TEST_THAT(::rename(stripe1fn, stripe1fnRename) == 0);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(::rename(stripe1fnRename, stripe1fn) == 0);
+
+ // Munged filename for avoidance
+ char mungefilename[256];
+ char filenamepart[256];
+ sprintf(filenamepart, "%s.rf", filename);
+ int m = 0, s = 0;
+ while(filenamepart[s] != '\0')
+ {
+ if(filenamepart[s] == '/')
+ {
+ mungefilename[m++] = '_';
+ }
+ else if(filenamepart[s] == '_')
+ {
+ mungefilename[m++] = '_';
+ mungefilename[m++] = '_';
+ }
+ else
+ {
+ mungefilename[m++] = filenamepart[s];
+ }
+ s++;
+ }
+ mungefilename[m++] = '\0';
+ char stripe1munge[256];
+ sprintf(stripe1munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR ".raidfile-unreadable"
+ DIRECTORY_SEPARATOR "%s", set, startDisc,
+ mungefilename);
+ char stripe2munge[256];
+ sprintf(stripe2munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR ".raidfile-unreadable"
+ DIRECTORY_SEPARATOR "%s", set,
+ (startDisc + 1) % RAID_NUMBER_DISCS, mungefilename);
+
+
+#ifdef TRF_CAN_INTERCEPT
+ // Test I/O errors on opening
+ // stripe 1
+ intercept_setup_error(stripe1fn, 0, EIO, SYS_open);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+
+ // Check that the file was moved correctly.
+ TEST_THAT(TestFileExists(stripe1munge));
+ TEST_THAT(::rename(stripe1munge, stripe1fn) == 0);
+
+ // Test error in reading stripe 2
+ intercept_setup_error(stripe2fn, 0, EIO, SYS_open);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+
+ // Check that the file was moved correctly.
+ TEST_THAT(TestFileExists(stripe2munge));
+ TEST_THAT(::rename(stripe2munge, stripe2fn) == 0);
+
+ // Test I/O errors on seeking
+ // stripe 1, if the file is bigger than the minimum thing that it'll get seeked for
+ if(datasize > 257)
+ {
+ intercept_setup_error(stripe1fn, 1, EIO, SYS_lseek);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+
+ // Check that the file was moved correctly.
+ TEST_THAT(TestFileExists(stripe1munge));
+ TEST_THAT(::rename(stripe1munge, stripe1fn) == 0);
+ }
+
+ // Stripe 2, only if the file is big enough to merit this
+ if(datasize > (RAID_BLOCK_SIZE + 4))
+ {
+ intercept_setup_error(stripe2fn, 1, EIO, SYS_lseek);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+
+ // Check that the file was moved correctly.
+ TEST_THAT(TestFileExists(stripe2munge));
+ TEST_THAT(::rename(stripe2munge, stripe2fn) == 0);
+ }
+
+ // Test I/O errors on read, but only if the file is size greater than 0
+ if(datasize > 0)
+ {
+ // Where shall we error after?
+ int errafter = datasize / 4;
+
+ // Test error in reading stripe 1
+ intercept_setup_error(stripe1fn, errafter, EIO, SYS_readv);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+
+ // Check that the file was moved correctly.
+ TEST_THAT(TestFileExists(stripe1munge));
+ TEST_THAT(::rename(stripe1munge, stripe1fn) == 0);
+
+ // Can only test error if file size > RAID_BLOCK_SIZE, as otherwise stripe2 has nothing in it
+ if(datasize > RAID_BLOCK_SIZE)
+ {
+ // Test error in reading stripe 2
+ intercept_setup_error(stripe2fn, errafter, EIO, SYS_readv);
+ testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks);
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+
+ // Check that the file was moved correctly.
+ TEST_THAT(TestFileExists(stripe2munge));
+ TEST_THAT(::rename(stripe2munge, stripe2fn) == 0);
+ }
+ }
+#endif // TRF_CAN_INTERCEPT
+ }
+}
+
+
+void testReadWriteFileDo(int set, const char *filename, void *data, int datasize, bool DoTransform)
+{
+ // Work out which disc is the "start" disc.
+ int h = 0;
+ int n = 0;
+ while(filename[n] != 0)
+ {
+ h += filename[n];
+ n++;
+ }
+ int startDisc = h % RAID_NUMBER_DISCS;
+
+ // Another to test the transform works OK...
+ RaidFileWrite write4(set, filename);
+ write4.Open();
+ write4.Write(data, datasize);
+ // This time, don't discard and transform it to a RAID File
+ char writefnPre[256];
+ sprintf(writefnPre, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rfwX", set, startDisc, filename);
+ TEST_THAT(TestFileExists(writefnPre));
+ char writefn[256];
+ sprintf(writefn, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rfw", set, startDisc, filename);
+ int usageInBlocks = write4.GetDiscUsageInBlocks();
+ write4.Commit(DoTransform);
+ // Check that files are nicely done...
+ if(!DoTransform)
+ {
+ TEST_THAT(TestFileExists(writefn));
+ TEST_THAT(!TestFileExists(writefnPre));
+ }
+ else
+ {
+ TEST_THAT(!TestFileExists(writefn));
+ TEST_THAT(!TestFileExists(writefnPre));
+ // Stripe file sizes
+ int fullblocks = datasize / RAID_BLOCK_SIZE;
+ int leftover = datasize - (fullblocks * RAID_BLOCK_SIZE);
+ int fs1 = -2;
+ if((fullblocks & 1) == 0)
+ {
+ // last block of data will be on the first stripe
+ fs1 = ((fullblocks / 2) * RAID_BLOCK_SIZE) + leftover;
+ }
+ else
+ {
+ // last block is on second stripe
+ fs1 = ((fullblocks / 2)+1) * RAID_BLOCK_SIZE;
+ }
+ char stripe1fn[256];
+ sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename);
+ TEST_THAT(TestGetFileSize(stripe1fn) == fs1);
+ char stripe2fn[256];
+ sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf", set,
+ (startDisc + 1) % RAID_NUMBER_DISCS, filename);
+ TEST_THAT(TestGetFileSize(stripe2fn) == (int)(datasize - fs1));
+ // Parity file size
+ char parityfn[256];
+ sprintf(parityfn, "testfiles" DIRECTORY_SEPARATOR "%d_%d"
+ DIRECTORY_SEPARATOR "%s.rf", set,
+ (startDisc + 2) % RAID_NUMBER_DISCS, filename);
+ // Mildly complex calculation
+ unsigned int blocks = datasize / RAID_BLOCK_SIZE;
+ unsigned int bytesOver = datasize % RAID_BLOCK_SIZE;
+ int paritysize = (blocks / 2) * RAID_BLOCK_SIZE;
+ // Then add in stuff for the last couple of blocks
+ if((blocks & 1) == 0)
+ {
+ if(bytesOver == 0)
+ {
+ paritysize += sizeof(RaidFileRead::FileSizeType);
+ }
+ else
+ {
+ paritysize += (bytesOver == sizeof(RaidFileRead::FileSizeType))?(RAID_BLOCK_SIZE+sizeof(RaidFileRead::FileSizeType)):bytesOver;
+ }
+ }
+ else
+ {
+ paritysize += RAID_BLOCK_SIZE;
+ if(bytesOver == 0 || bytesOver >= (RAID_BLOCK_SIZE-sizeof(RaidFileRead::FileSizeType)))
+ {
+ paritysize += sizeof(RaidFileRead::FileSizeType);
+ }
+ }
+ //printf("datasize = %d, calc paritysize = %d, actual size of file = %d\n", datasize, paritysize, TestGetFileSize(parityfn));
+ TEST_THAT(TestGetFileSize(parityfn) == paritysize);
+ //printf("stripe1 size = %d, stripe2 size = %d, parity size = %d\n", TestGetFileSize(stripe1fn), TestGetFileSize(stripe2fn), TestGetFileSize(parityfn));
+
+ // Check that block calculation is correct
+ //printf("filesize = %d\n", datasize);
+ #define TO_BLOCKS_ROUND_UP(x) (((x) + (RAID_BLOCK_SIZE-1)) / RAID_BLOCK_SIZE)
+ TEST_THAT(usageInBlocks == (TO_BLOCKS_ROUND_UP(paritysize) + TO_BLOCKS_ROUND_UP(fs1) + TO_BLOCKS_ROUND_UP(datasize - fs1)));
+
+ // See about whether or not the files look correct
+ char testblock[1024]; // compiler bug? This can't go in the block below without corrupting stripe2fn...
+ if(datasize > (3*1024))
+ {
+ int f;
+ TEST_THAT((f = ::open(stripe1fn, O_RDONLY | O_BINARY,
+ 0)) != -1);
+ TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock)));
+ for(unsigned int q = 0; q < sizeof(testblock); ++q)
+ {
+ TEST_THAT(testblock[q] == ((char*)data)[q]);
+ }
+ ::close(f);
+ TEST_THAT((f = ::open(stripe2fn, O_RDONLY | O_BINARY,
+ 0)) != -1);
+ TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock)));
+ for(unsigned int q = 0; q < sizeof(testblock); ++q)
+ {
+ TEST_THAT(testblock[q] == ((char*)data)[q+RAID_BLOCK_SIZE]);
+ }
+ ::close(f);
+ }
+ }
+
+ // See if the contents look right
+ testReadingFileContents(set, filename, data, datasize, DoTransform /* only test RAID stuff if it has been transformed to RAID */, usageInBlocks);
+}
+
+void testReadWriteFile(int set, const char *filename, void *data, int datasize)
+{
+ // Test once, transforming it...
+ testReadWriteFileDo(set, filename, data, datasize, true);
+
+ // And then again, not transforming it
+ std::string fn(filename);
+ fn += "NT";
+ testReadWriteFileDo(set, fn.c_str(), data, datasize, false);
+}
+
+bool list_matches(const std::vector<std::string> &rList, const char *compareto[])
+{
+ // count in compare to
+ int count = 0;
+ while(compareto[count] != 0)
+ count++;
+
+ if((int)rList.size() != count)
+ {
+ return false;
+ }
+
+ // Space for bools
+ bool *found = new bool[count];
+
+ for(int c = 0; c < count; ++c)
+ {
+ found[c] = false;
+ }
+
+ for(int c = 0; c < count; ++c)
+ {
+ bool f = false;
+ for(int l = 0; l < (int)rList.size(); ++l)
+ {
+ if(rList[l] == compareto[c])
+ {
+ f = true;
+ break;
+ }
+ }
+ found[c] = f;
+ }
+
+ bool ret = true;
+ for(int c = 0; c < count; ++c)
+ {
+ if(found[c] == false)
+ {
+ ret = false;
+ }
+ }
+
+ delete [] found;
+
+ return ret;
+}
+
+void test_overwrites()
+{
+ // Opening twice is bad
+ {
+ RaidFileWrite writeA(0, "overwrite_A");
+ writeA.Open();
+ writeA.Write("TESTTEST", 8);
+
+ {
+#if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK
+ RaidFileWrite writeA2(0, "overwrite_A");
+ TEST_CHECK_THROWS(writeA2.Open(), RaidFileException, FileIsCurrentlyOpenForWriting);
+#endif
+ }
+ }
+
+ // But opening a file which has previously been open, but isn't now, is OK.
+
+ // Generate a random pre-existing write file (and ensure that it doesn't exist already)
+ int f;
+ TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2"
+ DIRECTORY_SEPARATOR "overwrite_B.rfwX",
+ O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0755)) != -1);
+ TEST_THAT(::write(f, "TESTTEST", 8) == 8);
+ ::close(f);
+
+ // Attempt to overwrite it, which should work nicely.
+ RaidFileWrite writeB(0, "overwrite_B");
+ writeB.Open();
+ writeB.Write("TEST", 4);
+ TEST_THAT(writeB.GetFileSize() == 4);
+ writeB.Commit();
+}
+
+
+int test(int argc, const char *argv[])
+{
+ #ifndef TRF_CAN_INTERCEPT
+ printf("NOTE: Skipping intercept based tests on this platform.\n\n");
+ #endif
+
+ // Initialise the controller
+ RaidFileController &rcontroller = RaidFileController::GetController();
+ rcontroller.Initialise("testfiles" DIRECTORY_SEPARATOR "raidfile.conf");
+
+ // some data
+ char data[TEST_DATA_SIZE];
+ R250 random(619);
+ for(unsigned int l = 0; l < sizeof(data); ++l)
+ {
+ data[l] = random.next() & 0xff;
+ }
+ char data2[57];
+ for(unsigned int l = 0; l < sizeof(data2); ++l)
+ {
+ data2[l] = l;
+ }
+
+ // Try creating a directory
+ RaidFileWrite::CreateDirectory(0, "test-dir");
+ TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_0"
+ DIRECTORY_SEPARATOR "test-dir"));
+ TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_1"
+ DIRECTORY_SEPARATOR "test-dir"));
+ TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_2"
+ DIRECTORY_SEPARATOR "test-dir"));
+ TEST_THAT(RaidFileRead::DirectoryExists(0, "test-dir"));
+ TEST_THAT(!RaidFileRead::DirectoryExists(0, "test-dir-not"));
+
+
+ // Test converting to disc set names
+ {
+ std::string n1(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 0));
+ std::string n2(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 1));
+ std::string n3(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 2));
+ std::string n4(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 3));
+ TEST_THAT(n1 != n2);
+ TEST_THAT(n2 != n3);
+ TEST_THAT(n1 != n3);
+ TEST_THAT(n1 == n4); // ie wraps around
+ BOX_TRACE("Gen paths = '" << n1 << "', '" << n2 <<
+ "', '" << n3);
+ }
+
+ // Test that creating and deleting a RaidFile with the wrong
+ // reference counts throws the expected errors.
+ {
+ RaidFileWrite write1(0, "write1", 1);
+ write1.Open();
+ write1.Commit();
+ TEST_CHECK_THROWS(write1.Delete(), RaidFileException,
+ RequestedDeleteReferencedFile);
+ }
+
+ {
+ RaidFileWrite write1(0, "write1", 0);
+ write1.Open(true);
+ TEST_CHECK_THROWS(write1.Commit(), RaidFileException,
+ RequestedModifyUnreferencedFile);
+ write1.Delete();
+ }
+
+ {
+ TEST_CHECK_THROWS(RaidFileWrite write1(0, "write1", 2),
+ RaidFileException,
+ RequestedModifyMultiplyReferencedFile);
+ }
+
+ // Create a RaidFile
+ RaidFileWrite write1(0, "test1");
+ IOStream &write1stream = write1; // use the stream interface where possible
+ write1.Open();
+ write1stream.Write(data, sizeof(data));
+ write1stream.Seek(1024, IOStream::SeekType_Absolute);
+ write1stream.Write(data2, sizeof(data2));
+ write1stream.Seek(1024, IOStream::SeekType_Relative);
+ write1stream.Write(data2, sizeof(data2));
+ write1stream.Seek(0, IOStream::SeekType_End);
+ write1stream.Write(data, sizeof(data));
+
+ // Before it's deleted, check to see the contents are as expected
+ int f;
+ TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2"
+ DIRECTORY_SEPARATOR "test1.rfwX", O_RDONLY | O_BINARY, 0))
+ >= 0);
+ char buffer[sizeof(data)];
+ int bytes_read = ::read(f, buffer, sizeof(buffer));
+ TEST_THAT(bytes_read == sizeof(buffer));
+ for(unsigned int l = 0; l < 1024; ++l)
+ {
+ TEST_THAT(buffer[l] == data[l]);
+ }
+ for(unsigned int l = 0; l < sizeof(data2); ++l)
+ {
+ TEST_THAT(buffer[l+1024] == data2[l]);
+ }
+ for(unsigned int l = 0; l < sizeof(data2); ++l)
+ {
+ TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]);
+ }
+ TEST_THAT(::lseek(f, sizeof(data), SEEK_SET) == sizeof(buffer));
+ bytes_read = ::read(f, buffer, sizeof(buffer));
+ TEST_THAT(bytes_read == sizeof(buffer));
+ for(unsigned int l = 0; l < 1024; ++l)
+ {
+ TEST_THAT(buffer[l] == data[l]);
+ }
+ // make sure that's the end of the file
+ TEST_THAT(::read(f, buffer, sizeof(buffer)) == 0);
+ ::close(f);
+
+ // Commit the data
+ write1.Commit();
+ TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2"
+ DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0))
+ >= 0);
+ ::close(f);
+
+ // Now try and read it
+ {
+ std::auto_ptr<RaidFileRead> pread = RaidFileRead::Open(0, "test1");
+ TEST_THAT(pread->GetFileSize() == sizeof(buffer)*2);
+
+ char buffer[sizeof(data)];
+ TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer));
+ for(unsigned int l = 0; l < 1024; ++l)
+ {
+ TEST_THAT(buffer[l] == data[l]);
+ }
+ for(unsigned int l = 0; l < sizeof(data2); ++l)
+ {
+ TEST_THAT(buffer[l+1024] == data2[l]);
+ }
+ for(unsigned int l = 0; l < sizeof(data2); ++l)
+ {
+ TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]);
+ }
+ pread->Seek(sizeof(data), IOStream::SeekType_Absolute);
+ TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer));
+ for(unsigned int l = 0; l < 1024; ++l)
+ {
+ TEST_THAT(buffer[l] == data[l]);
+ }
+ // make sure that's the end of the file
+ TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0);
+ // Seek backwards a bit
+ pread->Seek(-1024, IOStream::SeekType_Relative);
+ TEST_THAT(pread->Read(buffer, 1024) == 1024);
+ // make sure that's the end of the file
+ TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0);
+ // Test seeking to end works
+ pread->Seek(-1024, IOStream::SeekType_Relative);
+ TEST_THAT(pread->Read(buffer, 512) == 512);
+ pread->Seek(0, IOStream::SeekType_End);
+ TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0);
+ }
+
+ // Delete it
+ RaidFileWrite writeDel(0, "test1");
+ writeDel.Delete();
+
+ // And again...
+ RaidFileWrite write2(0, "test1");
+ write2.Open();
+ write2.Write(data, sizeof(data));
+ // This time, discard it
+ write2.Discard();
+ TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2"
+ DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0))
+ == -1);
+
+ // And leaving it there...
+ RaidFileWrite writeLeave(0, "test1");
+ writeLeave.Open();
+ writeLeave.Write(data, sizeof(data));
+ // This time, commit it
+ writeLeave.Commit();
+ TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2"
+ DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0))
+ != -1);
+ ::close(f);
+
+ // Then check that the thing will refuse to open it again.
+ RaidFileWrite write3(0, "test1");
+ TEST_CHECK_THROWS(write3.Open(), RaidFileException, CannotOverwriteExistingFile);
+
+ // Test overwrite behaviour
+ test_overwrites();
+
+ // Then... open it again allowing overwrites
+ RaidFileWrite write3b(0, "test1");
+ write3b.Open(true);
+ // Write something
+ write3b.Write(data + 3, sizeof(data) - 3);
+ write3b.Commit();
+ // Test it
+ testReadingFileContents(0, "test1", data+3, sizeof(data) - 3, false
+ /* TestRAIDProperties */);
+
+ // And once again, but this time making it a raid file
+ RaidFileWrite write3c(0, "test1");
+ write3c.Open(true);
+ // Write something
+ write3c.Write(data + 7, sizeof(data) - 7);
+ write3c.Commit(true); // make RAID
+ // Test it
+ testReadingFileContents(0, "test1", data+7, sizeof(data) - 7, false
+ /*TestRAIDProperties*/);
+
+ // Test opening a file which doesn't exist
+ TEST_CHECK_THROWS(
+ std::auto_ptr<RaidFileRead> preadnotexist = RaidFileRead::Open(1, "doesnt-exist"),
+ RaidFileException, RaidFileDoesntExist);
+
+ {
+ // Test unrecoverable damage
+ RaidFileWrite w(0, "damage");
+ w.Open();
+ w.Write(data, sizeof(data));
+ w.Commit(true);
+
+ // Try removing the parity file
+ TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0"
+ DIRECTORY_SEPARATOR "damage.rf",
+ "testfiles" DIRECTORY_SEPARATOR "0_0"
+ DIRECTORY_SEPARATOR "damage.rf-NT") == 0);
+ {
+ std::auto_ptr<RaidFileRead> pr0 = RaidFileRead::Open(0, "damage");
+ pr0->Read(buffer, sizeof(data));
+ }
+ TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf-NT", "testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0);
+
+ // Delete one of the files
+ TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "damage.rf") == 0); // stripe 1
+
+#ifdef TRF_CAN_INTERCEPT
+ // Open it and read...
+ {
+ intercept_setup_error("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "damage.rf", 0, EIO, SYS_read); // stripe 2
+ std::auto_ptr<RaidFileRead> pr1 = RaidFileRead::Open(0, "damage");
+ TEST_CHECK_THROWS(
+ pr1->Read(buffer, sizeof(data)),
+ RaidFileException, OSError);
+
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+ }
+#endif //TRF_CAN_INTERCEPT
+
+ // Delete another
+ TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); // parity
+
+ TEST_CHECK_THROWS(
+ std::auto_ptr<RaidFileRead> pread2 = RaidFileRead::Open(0, "damage"),
+ RaidFileException, FileIsDamagedNotRecoverable);
+ }
+
+ // Test reading a directory
+ {
+ RaidFileWrite::CreateDirectory(0, "dirread");
+ // Make some contents
+ RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "dfsdf1");
+ RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "ponwq2");
+ {
+ RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "sdf9873241");
+ w.Open();
+ w.Write(data, sizeof(data));
+ w.Commit(true);
+ }
+ {
+ RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "fsdcxjni3242");
+ w.Open();
+ w.Write(data, sizeof(data));
+ w.Commit(true);
+ }
+ {
+ RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "cskjnds3");
+ w.Open();
+ w.Write(data, sizeof(data));
+ w.Commit(false);
+ }
+
+ const static char *dir_list1[] = {"dfsdf1", "ponwq2", 0};
+ const static char *file_list1[] = {"sdf9873241", "fsdcxjni3242", "cskjnds3", 0};
+ const static char *file_list2[] = {"fsdcxjni3242", "cskjnds3", 0};
+
+ std::vector<std::string> names;
+ TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names));
+ TEST_THAT(list_matches(names, file_list1));
+ TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_DirsOnly, names));
+ TEST_THAT(list_matches(names, dir_list1));
+ // Delete things
+ TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0);
+ TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names));
+ TEST_THAT(list_matches(names, file_list1));
+ // Delete something else so that it's not recoverable
+ TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0);
+ TEST_THAT(false == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names));
+ TEST_THAT(list_matches(names, file_list1));
+ // And finally...
+ TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0);
+ TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names));
+ TEST_THAT(list_matches(names, file_list2));
+ }
+
+ // Check that sizes are reported correctly for non-raid discs
+ {
+ int sizeInBlocks = (sizeof(data) + RAID_BLOCK_SIZE - 1) / RAID_BLOCK_SIZE;
+ // for writing
+ {
+ RaidFileWrite write(2, "testS");
+ write.Open();
+ write.Write(data, sizeof(data));
+ TEST_THAT(write.GetDiscUsageInBlocks() == sizeInBlocks);
+ write.Commit();
+ }
+ // for reading
+ {
+ std::auto_ptr<RaidFileRead> pread(RaidFileRead::Open(2, "testS"));
+ TEST_THAT(pread->GetDiscUsageInBlocks() == sizeInBlocks);
+ }
+ }
+
+//printf("SKIPPING tests ------------------\n");
+//return 0;
+
+ // Test a load of transformed things
+ #define BIG_BLOCK_SIZE (25*1024 + 19)
+ MemoryBlockGuard<void*> bigblock(BIG_BLOCK_SIZE);
+ R250 randomX2(2165);
+ for(unsigned int l = 0; l < BIG_BLOCK_SIZE; ++l)
+ {
+ ((char*)(void*)bigblock)[l] = randomX2.next() & 0xff;
+ }
+
+ // First on one size of data, on different discs
+ testReadWriteFile(0, "testdd", data, sizeof(data));
+ testReadWriteFile(0, "test2", bigblock, BIG_BLOCK_SIZE);
+ testReadWriteFile(1, "testThree", bigblock, BIG_BLOCK_SIZE - 2048);
+ testReadWriteFile(1, "testX", bigblock, BIG_BLOCK_SIZE - 2289);
+ testReadWriteFile(1, "testSmall0", data, 0);
+ testReadWriteFile(1, "testSmall1", data, 1);
+ testReadWriteFile(1, "testSmall2", data, 2);
+ testReadWriteFile(1, "testSmall3", data, 3);
+ testReadWriteFile(1, "testSmall4", data, 4);
+ testReadWriteFile(0, "testSmall5", data, 5);
+ testReadWriteFile(0, "testSmall6", data, 6);
+ testReadWriteFile(1, "testSmall7", data, 7);
+ testReadWriteFile(1, "testSmall8", data, 8);
+ testReadWriteFile(1, "testSmall9", data, 9);
+ testReadWriteFile(1, "testSmall10", data, 10);
+ // See about a file which is one block bigger than the previous tests
+ {
+ char dataonemoreblock[TEST_DATA_SIZE + RAID_BLOCK_SIZE];
+ R250 random(715);
+ for(unsigned int l = 0; l < sizeof(dataonemoreblock); ++l)
+ {
+ dataonemoreblock[l] = random.next() & 0xff;
+ }
+ testReadWriteFile(0, "testfour", dataonemoreblock, sizeof(dataonemoreblock));
+ }
+
+ // Some more nasty sizes
+ static int nastysize[] = {0, 1, 2, 7, 8, 9, (RAID_BLOCK_SIZE/2)+3,
+ RAID_BLOCK_SIZE-9, RAID_BLOCK_SIZE-8, RAID_BLOCK_SIZE-7, RAID_BLOCK_SIZE-6, RAID_BLOCK_SIZE-5,
+ RAID_BLOCK_SIZE-4, RAID_BLOCK_SIZE-3, RAID_BLOCK_SIZE-2, RAID_BLOCK_SIZE-1};
+ for(int o = 0; o <= 5; ++o)
+ {
+ for(unsigned int n = 0; n < (sizeof(nastysize)/sizeof(nastysize[0])); ++n)
+ {
+ int s = (o*RAID_BLOCK_SIZE)+nastysize[n];
+ char fn[64];
+ sprintf(fn, "testN%d", s);
+ testReadWriteFile(n&1, fn, bigblock, s);
+ }
+ }
+
+ // Finally, a mega test (not necessary for every run, I would have thought)
+/* unsigned int megamax = (1024*128) + 9;
+ MemoryBlockGuard<void*> megablock(megamax);
+ R250 randomX3(183);
+ for(unsigned int l = 0; l < megamax; ++l)
+ {
+ ((char*)(void*)megablock)[l] = randomX3.next() & 0xff;
+ }
+ for(unsigned int s = 0; s < megamax; ++s)
+ {
+ testReadWriteFile(s & 1, "mega", megablock, s);
+ RaidFileWrite deleter(s & 1, "mega");
+ deleter.Delete();
+ RaidFileWrite deleter2(s & 1, "megaNT");
+ deleter2.Delete();
+ }*/
+
+ return 0;
+}
diff --git a/test/win32/Makefile b/test/win32/Makefile
new file mode 100644
index 00000000..1212bc6f
--- /dev/null
+++ b/test/win32/Makefile
@@ -0,0 +1,5 @@
+timezone.exe: timezone.cpp Makefile
+ g++ -g -O0 -mno-cygwin -I../../lib/win32 -o timezone.exe timezone.cpp
+
+clean:
+ rm timezone.exe
diff --git a/test/win32/testlibwin32.cpp b/test/win32/testlibwin32.cpp
new file mode 100644
index 00000000..fb735c56
--- /dev/null
+++ b/test/win32/testlibwin32.cpp
@@ -0,0 +1,345 @@
+// win32test.cpp : Defines the entry point for the console application.
+//
+
+//#include <windows.h>
+#include "Box.h"
+
+#ifdef WIN32
+
+#include <assert.h>
+#include <AccCtrl.h>
+#include <Aclapi.h>
+
+#include "../../bin/bbackupd/BackupDaemon.h"
+#include "BoxPortsAndFiles.h"
+#include "emu.h"
+
+int main(int argc, char* argv[])
+{
+ // ACL tests
+ char* exename = getenv("WINDIR");
+
+ PSID psidOwner;
+ PSID psidGroup;
+ PACL pDacl;
+ PSECURITY_DESCRIPTOR pSecurityDesc;
+
+ DWORD result = GetNamedSecurityInfo(
+ exename, // pObjectName
+ SE_FILE_OBJECT, // ObjectType
+ DACL_SECURITY_INFORMATION | // SecurityInfo
+ GROUP_SECURITY_INFORMATION |
+ OWNER_SECURITY_INFORMATION,
+ &psidOwner, // ppsidOwner,
+ &psidGroup, // ppsidGroup,
+ &pDacl, // ppDacl,
+ NULL, // ppSacl,
+ &pSecurityDesc // ppSecurityDescriptor
+ );
+ if (result != ERROR_SUCCESS)
+ {
+ printf("Error getting security info for '%s': error %d",
+ exename, result);
+ }
+ assert(result == ERROR_SUCCESS);
+
+ char namebuf[1024];
+ char domainbuf[1024];
+ SID_NAME_USE nametype;
+ DWORD namelen = sizeof(namebuf);
+ DWORD domainlen = sizeof(domainbuf);
+
+ assert(LookupAccountSid(NULL, psidOwner, namebuf, &namelen,
+ domainbuf, &domainlen, &nametype));
+
+ printf("Owner:\n");
+ printf("User name: %s\n", namebuf);
+ printf("Domain name: %s\n", domainbuf);
+ printf("Name type: %d\n", nametype);
+ printf("\n");
+
+ namelen = sizeof(namebuf);
+ domainlen = sizeof(domainbuf);
+
+ assert(LookupAccountSid(NULL, psidGroup, namebuf, &namelen,
+ domainbuf, &domainlen, &nametype));
+
+ printf("Group:\n");
+ printf("User name: %s\n", namebuf);
+ printf("Domain name: %s\n", domainbuf);
+ printf("Name type: %d\n", nametype);
+ printf("\n");
+
+ ULONG numEntries;
+ PEXPLICIT_ACCESS pEntries;
+ result = GetExplicitEntriesFromAcl
+ (
+ pDacl, // pAcl
+ &numEntries, // pcCountOfExplicitEntries,
+ &pEntries // pListOfExplicitEntries
+ );
+ assert(result == ERROR_SUCCESS);
+
+ printf("Found %lu explicit DACL entries for '%s'\n\n",
+ (unsigned long)numEntries, exename);
+
+ for (ULONG i = 0; i < numEntries; i++)
+ {
+ EXPLICIT_ACCESS* pEntry = &(pEntries[i]);
+ printf("DACL entry %lu:\n", (unsigned long)i);
+
+ DWORD perms = pEntry->grfAccessPermissions;
+ printf(" Access permissions: ", perms);
+
+ #define PRINT_PERM(name) \
+ if (perms & name) \
+ { \
+ printf(#name " "); \
+ perms &= ~name; \
+ }
+
+ PRINT_PERM(FILE_ADD_FILE);
+ PRINT_PERM(FILE_ADD_SUBDIRECTORY);
+ PRINT_PERM(FILE_ALL_ACCESS);
+ PRINT_PERM(FILE_APPEND_DATA);
+ PRINT_PERM(FILE_CREATE_PIPE_INSTANCE);
+ PRINT_PERM(FILE_DELETE_CHILD);
+ PRINT_PERM(FILE_EXECUTE);
+ PRINT_PERM(FILE_LIST_DIRECTORY);
+ PRINT_PERM(FILE_READ_ATTRIBUTES);
+ PRINT_PERM(FILE_READ_DATA);
+ PRINT_PERM(FILE_READ_EA);
+ PRINT_PERM(FILE_TRAVERSE);
+ PRINT_PERM(FILE_WRITE_ATTRIBUTES);
+ PRINT_PERM(FILE_WRITE_DATA);
+ PRINT_PERM(FILE_WRITE_EA);
+ PRINT_PERM(STANDARD_RIGHTS_READ);
+ PRINT_PERM(STANDARD_RIGHTS_WRITE);
+ PRINT_PERM(SYNCHRONIZE);
+ PRINT_PERM(DELETE);
+ PRINT_PERM(READ_CONTROL);
+ PRINT_PERM(WRITE_DAC);
+ PRINT_PERM(WRITE_OWNER);
+ PRINT_PERM(MAXIMUM_ALLOWED);
+ PRINT_PERM(GENERIC_ALL);
+ PRINT_PERM(GENERIC_EXECUTE);
+ PRINT_PERM(GENERIC_WRITE);
+ PRINT_PERM(GENERIC_READ);
+ printf("\n");
+
+ if (perms)
+ {
+ printf(" Bits left over: %08x\n", perms);
+ }
+ assert(!perms);
+
+ printf(" Access mode: ");
+ switch(pEntry->grfAccessMode)
+ {
+ case NOT_USED_ACCESS:
+ printf("NOT_USED_ACCESS\n"); break;
+ case GRANT_ACCESS:
+ printf("GRANT_ACCESS\n"); break;
+ case DENY_ACCESS:
+ printf("DENY_ACCESS\n"); break;
+ case REVOKE_ACCESS:
+ printf("REVOKE_ACCESS\n"); break;
+ case SET_AUDIT_SUCCESS:
+ printf("SET_AUDIT_SUCCESS\n"); break;
+ case SET_AUDIT_FAILURE:
+ printf("SET_AUDIT_FAILURE\n"); break;
+ default:
+ printf("Unknown (%08x)\n", pEntry->grfAccessMode);
+ }
+
+ printf(" Trustee: ");
+ assert(pEntry->Trustee.pMultipleTrustee == NULL);
+ assert(pEntry->Trustee.MultipleTrusteeOperation == NO_MULTIPLE_TRUSTEE);
+ switch(pEntry->Trustee.TrusteeForm)
+ {
+ case TRUSTEE_IS_SID:
+ {
+ PSID trusteeSid = (PSID)(pEntry->Trustee.ptstrName);
+
+ namelen = sizeof(namebuf);
+ domainlen = sizeof(domainbuf);
+
+ assert(LookupAccountSid(NULL, trusteeSid, namebuf, &namelen,
+ domainbuf, &domainlen, &nametype));
+
+ printf("SID of %s\\%s (%d)\n", domainbuf, namebuf, nametype);
+ }
+ break;
+ case TRUSTEE_IS_NAME:
+ printf("Name\n"); break;
+ case TRUSTEE_BAD_FORM:
+ printf("Bad form\n"); assert(0);
+ case TRUSTEE_IS_OBJECTS_AND_SID:
+ printf("Objects and SID\n"); break;
+ case TRUSTEE_IS_OBJECTS_AND_NAME:
+ printf("Objects and name\n"); break;
+ default:
+ printf("Unknown form\n"); assert(0);
+ }
+
+ printf(" Trustee type: ");
+ switch(pEntry->Trustee.TrusteeType)
+ {
+ case TRUSTEE_IS_UNKNOWN:
+ printf("Unknown type.\n"); break;
+ case TRUSTEE_IS_USER:
+ printf("User\n"); break;
+ case TRUSTEE_IS_GROUP:
+ printf("Group\n"); break;
+ case TRUSTEE_IS_DOMAIN:
+ printf("Domain\n"); break;
+ case TRUSTEE_IS_ALIAS:
+ printf("Alias\n"); break;
+ case TRUSTEE_IS_WELL_KNOWN_GROUP:
+ printf("Well-known group\n"); break;
+ case TRUSTEE_IS_DELETED:
+ printf("Deleted account\n"); break;
+ case TRUSTEE_IS_INVALID:
+ printf("Invalid trustee type\n"); break;
+ case TRUSTEE_IS_COMPUTER:
+ printf("Computer\n"); break;
+ default:
+ printf("Unknown type %d\n", pEntry->Trustee.TrusteeType);
+ assert(0);
+ }
+
+ printf("\n");
+ }
+
+ assert(LocalFree((HLOCAL)pEntries) == 0);
+ assert(LocalFree((HLOCAL)pSecurityDesc) == 0);
+
+ chdir("c:\\tmp");
+ openfile("test", O_CREAT, 0);
+ struct stat ourfs;
+ //test our opendir, readdir and closedir
+ //functions
+ DIR *ourDir = opendir("C:");
+
+ if ( ourDir != NULL )
+ {
+ struct dirent *info;
+ do
+ {
+ info = readdir(ourDir);
+ if (info) printf("File/Dir name is : %s\r\n", info->d_name);
+ }
+ while (info != NULL);
+
+ closedir(ourDir);
+ }
+
+ std::string diry("C:\\Projects\\boxbuild\\testfiles\\");
+ ourDir = opendir(diry.c_str());
+ if ( ourDir != NULL )
+ {
+ struct dirent *info;
+ do
+ {
+ info = readdir(ourDir);
+ if (info == NULL) break;
+ std::string file(diry + info->d_name);
+ stat(file.c_str(), &ourfs);
+ if (info) printf("File/Dir name is : %s\r\n", info->d_name);
+ }
+ while ( info != NULL );
+
+ closedir(ourDir);
+
+ }
+
+ stat("c:\\windows", &ourfs);
+ stat("c:\\autoexec.bat", &ourfs);
+ printf("Finished dir read\n");
+
+ //test our getopt function
+ char * test_argv[] =
+ {
+ "foobar.exe",
+ "-qwc",
+ "-",
+ "-c",
+ "fgfgfg",
+ "-f",
+ "-l",
+ "hello",
+ "-",
+ "force-sync",
+ NULL
+ };
+ int test_argc;
+ for (test_argc = 0; test_argv[test_argc]; test_argc++) { }
+ const char* opts = "qwc:l:";
+
+ assert(getopt(test_argc, test_argv, opts) == 'q');
+ assert(getopt(test_argc, test_argv, opts) == 'w');
+ assert(getopt(test_argc, test_argv, opts) == 'c');
+ assert(strcmp(optarg, "-") == 0);
+ assert(getopt(test_argc, test_argv, opts) == 'c');
+ assert(strcmp(optarg, "fgfgfg") == 0);
+ assert(getopt(test_argc, test_argv, opts) == '?');
+ assert(optopt == 'f');
+ assert(getopt(test_argc, test_argv, opts) == 'l');
+ assert(strcmp(optarg, "hello") == 0);
+ assert(getopt(test_argc, test_argv, opts) == -1);
+ // assert(optopt == 0); // no more options
+ assert(strcmp(test_argv[optind], "-") == 0);
+ assert(strcmp(test_argv[optind+1], "force-sync") == 0);
+ //end of getopt test
+
+ //now test our statfs funct
+ stat("c:\\cert.cer", &ourfs);
+
+ char *timee;
+
+ timee = ctime(&ourfs.st_mtime);
+
+ if (S_ISREG(ourfs.st_mode))
+ {
+ printf("is a normal file\n");
+ }
+ else
+ {
+ printf("is a directory?\n");
+ exit(1);
+ }
+
+ lstat(getenv("WINDIR"), &ourfs);
+
+ if ( S_ISDIR(ourfs.st_mode))
+ {
+ printf("is a directory\n");
+ }
+ else
+ {
+ printf("is a file?\n");
+ exit(1);
+ }
+
+ //test the syslog functions
+ openlog("Box Backup", 0,0);
+ //the old ones are the best...
+ syslog(LOG_ERR, "Hello World");
+ syslog(LOG_ERR, "Value of int is: %i", 6);
+
+ closelog();
+
+ /*
+ //first off get the path name for the default
+ char buf[MAX_PATH];
+
+ GetModuleFileName(NULL, buf, sizeof(buf));
+ std::string buffer(buf);
+ std::string conf("-c " + buffer.substr(0,(buffer.find("win32test.exe"))) + "bbackupd.conf");
+ //std::string conf( "-c " + buffer.substr(0,(buffer.find("bbackupd.exe"))) + "bbackupd.conf");
+ */
+
+ return 0;
+}
+
+#endif // WIN32
diff --git a/test/win32/timezone.cpp b/test/win32/timezone.cpp
new file mode 100644
index 00000000..1d81bcb3
--- /dev/null
+++ b/test/win32/timezone.cpp
@@ -0,0 +1,87 @@
+#include <time.h>
+#include <windows.h>
+
+typedef int uid_t;
+typedef int gid_t;
+typedef int u_int32_t;
+
+#include "emu.h"
+
+int main(int argc, char** argv)
+{
+ time_t time_now = time(NULL);
+ char* time_str = strdup(asctime(gmtime(&time_now)));
+ time_str[24] = 0;
+
+ printf("Time now is %d (%s)\n", time_now, time_str);
+
+ char testfile[80];
+ snprintf(testfile, sizeof(testfile), "test.%d", time_now);
+ printf("Test file is: %s\n", testfile);
+
+ _unlink(testfile);
+
+ /*
+ int fd = open(testfile, O_RDWR | O_CREAT | O_EXCL);
+ if (fd < 0)
+ {
+ perror("open");
+ exit(1);
+ }
+ close(fd);
+ */
+
+ HANDLE fh = CreateFileA(testfile, FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
+
+ if (!fh)
+ {
+ fprintf(stderr, "Failed to open file '%s': error %d\n",
+ testfile, GetLastError());
+ exit(1);
+ }
+
+ BY_HANDLE_FILE_INFORMATION fi;
+
+ if (!GetFileInformationByHandle(fh, &fi))
+ {
+ fprintf(stderr, "Failed to get file information for '%s': "
+ "error %d\n", testfile, GetLastError());
+ exit(1);
+ }
+
+ if (!CloseHandle(fh))
+ {
+ fprintf(stderr, "Failed to close file: error %d\n",
+ GetLastError());
+ exit(1);
+ }
+
+ time_t created_time = ConvertFileTimeToTime_t(&fi.ftCreationTime);
+ time_str = strdup(asctime(gmtime(&created_time)));
+ time_str[24] = 0;
+
+ printf("File created time: %d (%s)\n", created_time, time_str);
+
+ printf("Difference is: %d\n", created_time - time_now);
+
+ if (abs(created_time - time_now) > 30)
+ {
+ fprintf(stderr, "Error: time difference too big: "
+ "bug in emu.h?\n");
+ exit(1);
+ }
+
+ /*
+ sleep(1);
+
+ if (_unlink(testfile) != 0)
+ {
+ perror("Failed to delete test file");
+ exit(1);
+ }
+ */
+
+ exit(0);
+}