summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/backupdiff/difftestfiles.cpp332
-rw-r--r--test/backupdiff/testbackupdiff.cpp579
-rw-r--r--test/backupdiff/testextra40
-rw-r--r--test/backupstore/testbackupstore.cpp1949
-rw-r--r--test/backupstore/testextra42
-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.cpp618
-rw-r--r--test/backupstorefix/testextra43
-rwxr-xr-xtest/backupstorefix/testfiles/testbackupstorefix.pl251
-rw-r--r--test/backupstorepatch/testbackupstorepatch.cpp658
-rw-r--r--test/backupstorepatch/testextra44
-rw-r--r--test/basicserver/Makefile.extra21
-rw-r--r--test/basicserver/TestCommands.cpp137
-rw-r--r--test/basicserver/TestContext.cpp54
-rw-r--r--test/basicserver/TestContext.h45
-rw-r--r--test/basicserver/testbasicserver.cpp667
-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/testbbackupd.cpp1096
-rw-r--r--test/bbackupd/testextra42
-rw-r--r--test/bbackupd/testfiles/accounts.txt0
-rw-r--r--test/bbackupd/testfiles/bbackupd.conf50
-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.pl71
-rwxr-xr-xtest/bbackupd/testfiles/extcheck2.pl67
-rwxr-xr-xtest/bbackupd/testfiles/notifyscript.pl53
-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
-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.cpp607
-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.cpp298
-rw-r--r--test/crypto/testcrypto.cpp352
-rw-r--r--test/raidfile/intercept.cpp310
-rw-r--r--test/raidfile/testextra45
-rw-r--r--test/raidfile/testfiles/raidfile.conf30
-rw-r--r--test/raidfile/testraidfile.cpp947
-rw-r--r--test/win32/testlibwin32.cpp158
-rw-r--r--test/win32/timezone.cpp125
105 files changed, 11136 insertions, 0 deletions
diff --git a/test/backupdiff/difftestfiles.cpp b/test/backupdiff/difftestfiles.cpp
new file mode 100644
index 00000000..cf28fab1
--- /dev/null
+++ b/test/backupdiff/difftestfiles.cpp
@@ -0,0 +1,332 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: createtestfiles.cpp
+// Purpose: Create the test files for the backupdiff test
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.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..bab6c941
--- /dev/null
+++ b/test/backupdiff/testbackupdiff.cpp
@@ -0,0 +1,579 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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;
+}
+
+void make_file_of_zeros(const char *filename, size_t size)
+{
+ static const size_t bs = 0x10000;
+ size_t remSize = size;
+ void *b = malloc(bs);
+ memset(b, 0, bs);
+ FILE *f = fopen(filename, "wb");
+
+ // Using largish blocks like this is much faster, while not consuming too much RAM
+ while(remSize > bs)
+ {
+ fwrite(b, bs, 1, f);
+ remSize -= bs;
+ }
+ fwrite(b, remSize, 1, f);
+
+ fclose(f);
+ free(b);
+
+ TEST_THAT((size_t)TestGetFileSize(filename) == 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);
+ TRACE2("Reading index from '%s', has %lld blocks\n", filename, nblocks);
+ TRACE0("======== ===== ========== ======== ========\n Index Where EncSz/Idx Size WChcksm\n");
+ // 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);
+ if(s > 0)
+ {
+ nnew++;
+ TRACE2("%8lld this s=%8lld", b, s);
+ }
+ else
+ {
+ nold++;
+ TRACE2("%8lld other i=%8lld", b, 0 - s);
+ }
+ // 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));
+ TRACE2(" %8d %08x\n", ntohl(entryEnc.mSize), ntohl(entryEnc.mWeakChecksum));
+
+ }
+ TRACE0("======== ===== ========== ======== ========\n");
+ 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
+ {
+ // Emulate the above stage!
+ char cmd[256];
+ sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to);
+ ::system(cmd);
+ }
+
+ // 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 NDEBUG
+ BackupStoreFile::TraceDetailsOfDiffProcess = true;
+ #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);
+ 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);
+ 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"));
+ }
+ }
+
+ // 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);
+ }
+
+ // Check that diffing against a file which isn't "complete" and referes 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 sock up lots of processor
+ // time -- because of lots of matches found. Check this out!
+ make_file_of_zeros("testfiles/zero.0", 20*1024*1024);
+ make_file_of_zeros("testfiles/zero.1", 200*1024*1024);
+ // 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);
+ TEST_THAT(time(0) < (beginTime + 40));
+ }
+ // 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..6f5f4473
--- /dev/null
+++ b/test/backupdiff/testextra
@@ -0,0 +1,40 @@
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+rm -rf testfiles
+mkdir testfiles
diff --git a/test/backupstore/testbackupstore.cpp b/test/backupstore/testbackupstore.cpp
new file mode 100644
index 00000000..593ae121
--- /dev/null
+++ b/test/backupstore/testbackupstore.cpp
@@ -0,0 +1,1949 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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 "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
+{
+ 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
+ TEST_THAT(fn2.find("name") == fn2.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);
+ unlink("testfiles/test_download");
+}
+
+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);
+}
+
+int64_t create_test_data_subdirs(BackupProtocolClient &protocol, int64_t indir, const char *name, int depth)
+{
+ // 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);
+
+ // 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);
+ create_test_data_subdirs(protocol, subdirid, "dir_Two", depth - 1);
+ }
+
+ // Stick some files in it
+ {
+ BackupStoreFilenameClear name("file_One");
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/file1", subdirid, name));
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ subdirid,
+ 0x123456789abcdefLL, /* modification time */
+ 0x7362383249872dfLL, /* attr hash */
+ 0, /* diff from ID */
+ name,
+ *upload));
+ }
+ {
+ BackupStoreFilenameClear name("file_Two");
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/file1", subdirid, name));
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ subdirid,
+ 0x123456789abcdefLL, /* modification time */
+ 0x7362383249872dfLL, /* attr hash */
+ 0, /* diff from ID */
+ name,
+ *upload));
+ }
+ {
+ BackupStoreFilenameClear name("file_Three");
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/file1", subdirid, name));
+ std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+ subdirid,
+ 0x123456789abcdefLL, /* modification time */
+ 0x7362383249872dfLL, /* attr hash */
+ 0, /* diff from ID */
+ name,
+ *upload));
+ }
+
+ 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);
+ 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);
+ }
+ // 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);
+ }
+}
+
+
+int test_server(const char *hostname)
+{
+ // Context
+ TLSContext context;
+ context.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+
+ // 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
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Get it logging
+ FILE *protocolLog = ::fopen("testfiles/protocol.log", "w");
+ TEST_THAT(protocolLog != 0);
+ protocol.SetLogToFile(protocolLog);
+
+ // 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));
+
+ // Check marker is 0
+ TEST_THAT(loginConf->GetClientStoreMarker() == 0);
+
+ // Check that we can't open a new connection which requests write permissions
+ {
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED);
+ 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();
+ }
+
+ // Set the client store marker
+ protocol.QuerySetClientStoreMarker(0x8732523ab23aLL);
+
+ // Open a new connection which is read only
+ SocketStreamTLS connReadOnly;
+ connReadOnly.Open(context, Socket::TypeINET, hostname, BOX_PORT_BBSTORED);
+ 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);
+ }
+
+ test_server_1(protocol, protocolReadOnly);
+
+
+ // 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();
+ }
+
+ // 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
+ check_dir_after_uploads(protocolReadOnly, attrtest);
+ // And on the read/write one
+ check_dir_after_uploads(protocol, attrtest);
+ }
+
+ // 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();
+ }
+ // 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);
+ }
+ // 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();
+ }
+ // 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);
+ }
+ // 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());
+ }
+ // 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);
+ }
+
+ // 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);
+ }
+ // 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();
+ }
+ // 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:
+
+ // Create some nice recursive directories
+ int64_t dirtodelete = create_test_data_subdirs(protocol,
+ BackupProtocolClientListDirectory::RootDirectory, "test_delete", 6 /* depth */);
+
+ // 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
+ protocolReadOnly.QueryFinished();
+ protocol.QueryFinished();
+
+ // Close logs
+ ::fclose(protocolReadOnlyLog);
+ ::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);
+ }
+
+ // 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);
+ }
+ }
+
+ // 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.
+ int pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored.conf", "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);
+
+ // 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("../../bin/bbstoreaccounts/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
+
+ TEST_THAT(ServerIsAlive(pid));
+
+ TEST_THAT(test_server("localhost") == 0);
+
+ // 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));
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+
+ // 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("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Start things up
+ pid = LaunchServer("../../bin/bbstored/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);
+printf("after.objectsNotDel=%i, deleted=%i, old=%i\n",after.objectsNotDel, after.deleted, after.old);
+
+ // 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("../../bin/bbstoreaccounts/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);
+
+ // 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));
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ }
+
+ 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("../../bin/bbstoreaccounts/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("../../bin/bbstored/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));
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ }
+
+
+ return 0;
+}
+
+int test(int argc, const char *argv[])
+{
+ // 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
+ 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..ad7dd552
--- /dev/null
+++ b/test/backupstore/testextra
@@ -0,0 +1,42 @@
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+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..4862033b
--- /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
+ 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..62d098b1
--- /dev/null
+++ b/test/backupstorefix/testbackupstorefix.cpp
@@ -0,0 +1,618 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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 "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 suprious 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("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf check 01234567"); \
+ ::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf check 01234567 fix");
+
+// Wait a given number of seconds for something to complete
+void wait_for_operation(int seconds)
+{
+ printf("waiting: ");
+ fflush(stdout);
+ for(int l = 0; l < seconds; ++l)
+ {
+ sleep(1);
+ printf(".");
+ fflush(stdout);
+ }
+ printf("\n");
+}
+
+// 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);
+ // 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()
+{
+ 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 suprious 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("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf create 01234567 0 10000B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Start the bbstored server
+ int pid = LaunchServer("../../bin/bbstored/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 testfiles/testbackupstorefix.pl init") == 0);
+
+ int bbackupd_pid = LaunchServer("../../bin/bbackupd/bbackupd testfiles/bbackupd.conf", "testfiles/bbackupd.pid");
+ TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
+ if(bbackupd_pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(bbackupd_pid));
+
+ // Create a nice store directory
+ wait_for_operation(30);
+
+ // That'll do nicely, stop the server
+ TEST_THAT(KillServer(bbackupd_pid));
+ TestRemoteProcessMemLeaks("bbackupd.memleaks");
+ }
+
+ // Generate a list of all the object IDs
+ TEST_THAT_ABORTONFAIL(::system("../../bin/bbackupquery/bbackupquery -q -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);
+ 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 suprious 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 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));
+ // 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 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, supurious 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 notSupriousFileSize = 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);
+ notSupriousFileSize = 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 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() == notSupriousFileSize);
+ }
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ ::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 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 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 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 testfiles/testbackupstorefix.pl reroot 6") == 0);
+
+
+ // ------------------------------------------------------------------------------------------------
+ // Stop server
+ TEST_THAT(KillServer(pid));
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ }
+
+ return 0;
+}
+
diff --git a/test/backupstorefix/testextra b/test/backupstorefix/testextra
new file mode 100644
index 00000000..d55200d6
--- /dev/null
+++ b/test/backupstorefix/testextra
@@ -0,0 +1,43 @@
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+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 b/test/backupstorefix/testfiles/testbackupstorefix.pl
new file mode 100755
index 00000000..b2b111c9
--- /dev/null
+++ b/test/backupstorefix/testfiles/testbackupstorefix.pl
@@ -0,0 +1,251 @@
+#!/usr/bin/perl
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+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;
+ $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 -q -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/\[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 -q -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..9edd427b
--- /dev/null
+++ b/test/backupstorepatch/testbackupstorepatch.cpp
@@ -0,0 +1,658 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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 "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 "RaidFileRead.h"
+#include "RaidFileWrite.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreException.h"
+#include "RaidFileException.h"
+#include "MemBlockStream.h"
+#include "BackupClientFileAttributes.h"
+#include "BackupClientCryptoKeys.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("../../bin/bbstoreaccounts/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();
+
+ // First, try logging in without an account having been created... just make sure login fails.
+ int pid = LaunchServer("../../bin/bbstored/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);
+
+ // 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;
+ printf("ID %lld, completely different: %s\n", 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);
+ }
+ 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);
+ 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);
+ }
+
+ // Send the server a restart signal, so it does housekeeping immedaitely, and wait for it to happen
+ ::sleep(1); // wait for old connections to terminate
+ ::kill(pid, SIGHUP);
+ // 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));
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ }
+
+ ::free(buffer);
+
+ return 0;
+}
diff --git a/test/backupstorepatch/testextra b/test/backupstorepatch/testextra
new file mode 100644
index 00000000..be970ffa
--- /dev/null
+++ b/test/backupstorepatch/testextra
@@ -0,0 +1,44 @@
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+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..51280db9
--- /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..d879ff9d
--- /dev/null
+++ b/test/basicserver/TestCommands.cpp
@@ -0,0 +1,137 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+
+#include "Box.h"
+
+#include <syslog.h>
+
+#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..8c99dde7
--- /dev/null
+++ b/test/basicserver/TestContext.cpp
@@ -0,0 +1,54 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+
+#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..27663e95
--- /dev/null
+++ b/test/basicserver/TestContext.h
@@ -0,0 +1,45 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+
+class TestContext
+{
+public:
+ TestContext();
+ ~TestContext();
+};
diff --git a/test/basicserver/testbasicserver.cpp b/test/basicserver/testbasicserver.cpp
new file mode 100644
index 00000000..9045b34b
--- /dev/null
+++ b/test/basicserver/testbasicserver.cpp
@@ -0,0 +1,667 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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 "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()
+{
+ struct timespec t;
+ t.tv_sec = 0;
+ t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000; // convert to ns
+ ::nanosleep(&t, NULL);
+}
+
+#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);
+ }
+
+ // 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(0) // no default 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(0) // 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)
+ {
+ 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)
+ {
+ if(strcmp(argv[1], "srv1") == 0)
+ {
+ // Run very basic daemon
+ basicdaemon daemon;
+ return daemon.Main("doesnotexist", argc - 1, argv + 1);
+ }
+ else if(strcmp(argv[1], "srv2") == 0)
+ {
+ // Run daemon which accepts connections
+ testserver daemon;
+ return daemon.Main("doesnotexist", argc - 1, argv + 1);
+ }
+ else if(strcmp(argv[1], "srv3") == 0)
+ {
+ testTLSserver daemon;
+ return daemon.Main("doesnotexist", argc - 1, argv + 1);
+ }
+ else if(strcmp(argv[1], "srv4") == 0)
+ {
+ testProtocolServer daemon;
+ return daemon.Main("doesnotexist", argc - 1, argv + 1);
+ }
+ }
+
+//printf("SKIPPING TESTS------------------------\n");
+//goto protocolserver;
+
+ // Launch a basic server
+ {
+ int pid = LaunchServer("./test srv1 testfiles/srv1.conf", "testfiles/srv1.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ // Check that it's written the expected file
+ TEST_THAT(TestFileExists("testfiles/srv1.test1"));
+ TEST_THAT(ServerIsAlive(pid));
+ // Move the config file over
+ TEST_THAT(::rename("testfiles/srv1b.conf", "testfiles/srv1.conf") != -1);
+ // 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/srv1.test2"));
+ // Kill it off
+ TEST_THAT(KillServer(pid));
+ TestRemoteProcessMemLeaks("generic-daemon.memleaks");
+ }
+ }
+
+ // Launch a test forking server
+ {
+ int pid = LaunchServer("./test srv2 testfiles/srv2.conf", "testfiles/srv2.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ // Will it restart?
+ TEST_THAT(ServerIsAlive(pid));
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ // Make some connections
+ {
+ SocketStream conn1;
+ conn1.Open(Socket::TypeINET, "localhost", 2003);
+ SocketStream conn2;
+ conn2.Open(Socket::TypeUNIX, "testfiles/srv2.sock");
+ SocketStream conn3;
+ conn3.Open(Socket::TypeINET, "localhost", 2003);
+ // 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);
+ conns.push_back(&conn2);
+ conns.push_back(&conn3);
+ Srv2TestConversations(conns);
+ // Implicit close
+ }
+ // HUP again
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+ TestRemoteProcessMemLeaks("test-srv2.memleaks");
+ }
+ }
+
+ // Launch a test SSL server
+ {
+ int pid = LaunchServer("./test srv3 testfiles/srv3.conf", "testfiles/srv3.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ // Will it restart?
+ TEST_THAT(ServerIsAlive(pid));
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ // 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);
+ SocketStreamTLS conn2;
+ conn2.Open(context, Socket::TypeUNIX, "testfiles/srv3.sock");
+ SocketStreamTLS conn3;
+ conn3.Open(context, Socket::TypeINET, "localhost", 2003);
+ // 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);
+ conns.push_back(&conn2);
+ conns.push_back(&conn3);
+ Srv2TestConversations(conns);
+ // Implicit close
+ }
+ // HUP again
+ TEST_THAT(HUPServer(pid));
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+ TestRemoteProcessMemLeaks("test-srv3.memleaks");
+ }
+ }
+
+//protocolserver:
+ // Launch a test protocol handling server
+ {
+ int pid = LaunchServer("./test srv4 testfiles/srv4.conf", "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;
+ conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock");
+
+ // 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));
+ TestRemoteProcessMemLeaks("test-srv4.memleaks");
+ }
+ }
+
+ 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..b4c5627c
--- /dev/null
+++ b/test/basicserver/testfiles/srv4.conf
@@ -0,0 +1,6 @@
+Server
+{
+ PidFile = testfiles/srv4.pid
+ ListenAddresses = unix:testfiles/srv4.sock
+}
+
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/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp
new file mode 100644
index 00000000..a085a96c
--- /dev/null
+++ b/test/bbackupd/testbbackupd.cpp
@@ -0,0 +1,1096 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testbbackupd.cpp
+// Purpose: test backup daemon (and associated client bits)
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_XATTR_H
+#include <cerrno>
+#include <sys/xattr.h>
+#endif
+#include <map>
+
+#include "Test.h"
+#include "BackupClientFileAttributes.h"
+#include "CommonException.h"
+#include "BackupStoreException.h"
+#include "FileModificationTime.h"
+#include "autogen_BackupProtocolClient.h"
+#include "SSLLib.h"
+#include "TLSContext.h"
+#include "SocketStreamTLS.h"
+#include "BoxPortsAndFiles.h"
+#include "BackupStoreConstants.h"
+#include "Socket.h"
+#include "BackupClientRestore.h"
+#include "BackupStoreDirectory.h"
+#include "BackupClientCryptoKeys.h"
+#include "CollectInBufferStream.h"
+#include "Utils.h"
+#include "BoxTime.h"
+#include "BoxTimeToUnix.h"
+
+#include "MemLeakFindOn.h"
+
+// ENOATTR may be defined in a separate header file which we may not have
+#ifndef ENOATTR
+#define ENOATTR ENODATA
+#endif
+
+// two cycles and a bit
+#define TIME_TO_WAIT_FOR_BACKUP_OPERATION 12
+
+void wait_for_backup_operation(int seconds = TIME_TO_WAIT_FOR_BACKUP_OPERATION)
+{
+ printf("waiting: ");
+ fflush(stdout);
+ for(int l = 0; l < seconds; ++l)
+ {
+ sleep(1);
+ printf(".");
+ fflush(stdout);
+ }
+ printf("\n");
+}
+
+int bbstored_pid = 0;
+
+#ifdef HAVE_SYS_XATTR_H
+bool readxattr_into_map(const char *filename, std::map<std::string,std::string> &rOutput)
+{
+ rOutput.clear();
+
+ ssize_t xattrNamesBufferSize = llistxattr(filename, NULL, 0);
+ if(xattrNamesBufferSize < 0)
+ {
+ return false;
+ }
+ else if(xattrNamesBufferSize > 0)
+ {
+ // There is some data there to look at
+ char *xattrNamesBuffer = (char*)malloc(xattrNamesBufferSize + 4);
+ if(xattrNamesBuffer == NULL) return false;
+ char *xattrDataBuffer = 0;
+ int xattrDataBufferSize = 0;
+ // note: will leak these buffers if a read error occurs. (test code, so doesn't matter)
+
+ ssize_t ns = llistxattr(filename, xattrNamesBuffer, xattrNamesBufferSize);
+ if(ns < 0)
+ {
+ return false;
+ }
+ else if(ns > 0)
+ {
+ // Read all the attribute values
+ const char *xattrName = xattrNamesBuffer;
+ while(xattrName < (xattrNamesBuffer + ns))
+ {
+ // Store size of name
+ int xattrNameSize = strlen(xattrName);
+
+ bool ok = true;
+
+ ssize_t dataSize = lgetxattr(filename, xattrName, NULL, 0);
+ if(dataSize < 0)
+ {
+ if(errno == ENOATTR)
+ {
+ // Deleted from under us
+ ok = false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if(dataSize == 0)
+ {
+ // something must have removed all the data from under us
+ ok = false;
+ }
+ else
+ {
+ // Make sure there's enough space in the buffer to get the attribute
+ if(xattrDataBuffer == 0)
+ {
+ xattrDataBuffer = (char*)malloc(dataSize + 4);
+ xattrDataBufferSize = dataSize + 4;
+ }
+ else if(xattrDataBufferSize < (dataSize + 4))
+ {
+ char *resized = (char*)realloc(xattrDataBuffer, dataSize + 4);
+ if(resized == NULL) return false;
+ xattrDataBuffer = resized;
+ xattrDataBufferSize = dataSize + 4;
+ }
+ }
+
+ // Read the data!
+ dataSize = 0;
+ if(ok)
+ {
+ dataSize = lgetxattr(filename, xattrName, xattrDataBuffer,
+ xattrDataBufferSize - 1 /*for terminator*/);
+ if(dataSize < 0)
+ {
+ if(errno == ENOATTR)
+ {
+ // Deleted from under us
+ ok = false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if(dataSize == 0)
+ {
+ // something must have deleted this from under us
+ ok = false;
+ }
+ else
+ {
+ // Terminate the data
+ xattrDataBuffer[dataSize] = '\0';
+ }
+ // Got the data in the buffer
+ }
+
+ // Store in map
+ if(ok)
+ {
+ rOutput[std::string(xattrName)] = std::string(xattrDataBuffer, dataSize);
+ }
+
+ // Next attribute
+ xattrName += xattrNameSize + 1;
+ }
+ }
+
+ if(xattrNamesBuffer != 0) ::free(xattrNamesBuffer);
+ if(xattrDataBuffer != 0) ::free(xattrDataBuffer);
+ }
+
+ return true;
+}
+
+static FILE *xattrTestDataHandle = 0;
+bool write_xattr_test(const char *filename, const char *attrName, unsigned int length, bool *pNotSupported = 0)
+{
+ if(xattrTestDataHandle == 0)
+ {
+ xattrTestDataHandle = ::fopen("testfiles/test3.tgz", "rb"); // largest test file
+ }
+ if(xattrTestDataHandle == 0)
+ {
+ return false;
+ }
+ else
+ {
+ char data[1024];
+ if(length > sizeof(data)) length = sizeof(data);
+
+ if(::fread(data, length, 1, xattrTestDataHandle) != 1)
+ {
+ return false;
+ }
+
+ if(::lsetxattr(filename, attrName, data, length, 0) != 0)
+ {
+ if(pNotSupported != 0)
+ {
+ *pNotSupported = (errno == ENOTSUP);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+void finish_with_write_xattr_test()
+{
+ if(xattrTestDataHandle != 0)
+ {
+ ::fclose(xattrTestDataHandle);
+ }
+}
+#endif // HAVE_SYS_XATTR_H
+
+bool attrmatch(const char *f1, const char *f2)
+{
+ struct stat s1, s2;
+ TEST_THAT(::lstat(f1, &s1) == 0);
+ TEST_THAT(::lstat(f2, &s2) == 0);
+
+#ifdef HAVE_SYS_XATTR_H
+ {
+ std::map<std::string,std::string> xattr1, xattr2;
+ if(!readxattr_into_map(f1, xattr1)
+ || !readxattr_into_map(f2, xattr2))
+ {
+ return false;
+ }
+ if(!(xattr1 == xattr2))
+ {
+ return false;
+ }
+ }
+#endif // HAVE_SYS_XATTR_H
+
+ // if link, just make sure other file is a link too, and that the link to names match
+ if((s1.st_mode & S_IFMT) == S_IFLNK)
+ {
+ if((s2.st_mode & S_IFMT) != S_IFLNK) return false;
+
+ char p1[PATH_MAX], p2[PATH_MAX];
+ int p1l = ::readlink(f1, p1, PATH_MAX);
+ int p2l = ::readlink(f2, p2, PATH_MAX);
+ TEST_THAT(p1l != -1 && p2l != -1);
+ // terminate strings properly
+ p1[p1l] = '\0';
+ p2[p2l] = '\0';
+ return strcmp(p1, p2) == 0;
+ }
+
+ // modification times
+ if(FileModificationTime(s1) != FileModificationTime(s2))
+ {
+ return false;
+ }
+
+ // compare the rest
+ return (s1.st_mode == s2.st_mode && s1.st_uid == s2.st_uid && s1.st_gid == s2.st_gid);
+}
+
+int test_basics()
+{
+ // Read attributes from files
+ BackupClientFileAttributes t1;
+ t1.ReadAttributes("testfiles/test1");
+ TEST_THAT(!t1.IsSymLink());
+ BackupClientFileAttributes t2;
+ t2.ReadAttributes("testfiles/test2");
+ TEST_THAT(t2.IsSymLink());
+ // Check that it's actually been encrypted (search for symlink name encoded in it)
+ void *te = ::memchr(t2.GetBuffer(), 't', t2.GetSize() - 3);
+ TEST_THAT(te == 0 || ::memcmp(te, "test", 4) != 0);
+
+ BackupClientFileAttributes t3;
+ TEST_CHECK_THROWS(t3.ReadAttributes("doesn't exist"), CommonException, OSFileError);
+
+ // Create some more files
+ FILE *f = fopen("testfiles/test1_n", "w");
+ fclose(f);
+ f = fopen("testfiles/test2_n", "w");
+ fclose(f);
+
+ // Apply attributes to these new files
+ t1.WriteAttributes("testfiles/test1_n");
+ t2.WriteAttributes("testfiles/test2_n");
+ TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"), CommonException, OSFileError);
+ TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"), BackupStoreException, AttributesNotLoaded);
+
+ // Test that atttributes are vaguely similar
+ TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_n"));
+ TEST_THAT(attrmatch("testfiles/test2", "testfiles/test2_n"));
+
+ // Check encryption, and recovery from encryption
+ // First, check that two attributes taken from the same thing have different encrypted values (think IV)
+ BackupClientFileAttributes t1b;
+ t1b.ReadAttributes("testfiles/test1");
+ TEST_THAT(::memcmp(t1.GetBuffer(), t1b.GetBuffer(), t1.GetSize()) != 0);
+ // But that comparing them works OK.
+ TEST_THAT(t1 == t1b);
+ // Then store them both to a stream
+ CollectInBufferStream stream;
+ t1.WriteToStream(stream);
+ t1b.WriteToStream(stream);
+ // Read them back again
+ stream.SetForReading();
+ BackupClientFileAttributes t1_r, t1b_r;
+ t1_r.ReadFromStream(stream, 1000);
+ t1b_r.ReadFromStream(stream, 1000);
+ TEST_THAT(::memcmp(t1_r.GetBuffer(), t1b_r.GetBuffer(), t1_r.GetSize()) != 0);
+ TEST_THAT(t1_r == t1b_r);
+ TEST_THAT(t1 == t1_r);
+ TEST_THAT(t1b == t1b_r);
+ TEST_THAT(t1_r == t1b);
+ TEST_THAT(t1b_r == t1);
+
+#ifdef HAVE_SYS_XATTR_H
+ // Write some attributes to the file, checking for ENOTSUP
+ bool xattrNotSupported = false;
+ if(!write_xattr_test("testfiles/test1", "user.attr_1", 1000, &xattrNotSupported) && xattrNotSupported)
+ {
+ ::printf("***********\nYour platform supports xattr, but your filesystem does not.\nSkipping tests.\n***********\n");
+ }
+ else
+ {
+ BackupClientFileAttributes x1, x2, x3, x4;
+
+ // Write more attributes
+ TEST_THAT(write_xattr_test("testfiles/test1", "user.attr_2", 947));
+ TEST_THAT(write_xattr_test("testfiles/test1", "user.sadfohij39998.3hj", 123));
+
+ // Read file attributes
+ x1.ReadAttributes("testfiles/test1");
+
+ // Write file attributes
+ FILE *f = fopen("testfiles/test1_nx", "w");
+ fclose(f);
+ x1.WriteAttributes("testfiles/test1_nx");
+
+ // Compare to see if xattr copied
+ TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_nx"));
+
+ // Add more attributes to a file
+ x2.ReadAttributes("testfiles/test1");
+ TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23));
+
+ // Read them again, and check that the Compare() function detects that they're different
+ x3.ReadAttributes("testfiles/test1");
+ TEST_THAT(x1.Compare(x2, true, true));
+ TEST_THAT(!x1.Compare(x3, true, true));
+
+ // Change the value of one of them, leaving the size the same.
+ TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23));
+ x4.ReadAttributes("testfiles/test1");
+ TEST_THAT(!x1.Compare(x4, true, true));
+ }
+ finish_with_write_xattr_test();
+#endif // HAVE_SYS_XATTR_H
+
+ return 0;
+}
+
+int test_setupaccount()
+{
+ TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf create 01234567 0 1000B 2000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+ return 0;
+}
+
+int test_run_bbstored()
+{
+ bbstored_pid = LaunchServer("../../bin/bbstored/bbstored testfiles/bbstored.conf", "testfiles/bbstored.pid");
+ TEST_THAT(bbstored_pid != -1 && bbstored_pid != 0);
+ if(bbstored_pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(bbstored_pid));
+ return 0; // success
+ }
+
+ return 1;
+}
+
+int test_kill_bbstored()
+{
+ TEST_THAT(KillServer(bbstored_pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(bbstored_pid));
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ return 0;
+}
+
+int64_t GetDirID(BackupProtocolClient &protocol, const char *name, int64_t InDirectory)
+{
+ protocol.QueryListDirectory(
+ InDirectory,
+ BackupProtocolClientListDirectory::Flags_Dir,
+ BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ true /* want attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, protocol.GetTimeout());
+
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ int64_t dirid = 0;
+ BackupStoreFilenameClear dirname(name);
+ while((en = i.Next()) != 0)
+ {
+ if(en->GetName() == dirname)
+ {
+ dirid = en->GetObjectID();
+ }
+ }
+ return dirid;
+}
+
+void terminate_on_alarm(int sigraised)
+{
+ abort();
+}
+
+void do_interrupted_restore(const TLSContext &context, int64_t restoredirid)
+{
+ int pid = 0;
+ switch((pid = fork()))
+ {
+ case 0:
+ // child process
+ {
+ // connect and log in
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED);
+ BackupProtocolClient protocol(conn);
+ protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
+
+ // Test the restoration
+ TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-interrupt", true /* print progress dots */) == Restore_Complete);
+
+ // Log out
+ protocol.QueryFinished();
+ }
+ exit(0);
+ break;
+
+ case -1:
+ {
+ printf("Fork failed\n");
+ exit(1);
+ }
+
+ default:
+ {
+ // Wait until a resume file is written, then terminate the child
+ while(true)
+ {
+ // Test for existence of the result file
+ int64_t resumesize = 0;
+ if(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize) && resumesize > 16)
+ {
+ // It's done something. Terminate it.
+ ::kill(pid, SIGTERM);
+ break;
+ }
+
+ // Process finished?
+ int status = 0;
+ if(waitpid(pid, &status, WNOHANG) != 0)
+ {
+ // child has finished anyway.
+ return;
+ }
+
+ // Give up timeslot so as not to hog the processor
+ ::sleep(0);
+ }
+
+ // Just wait until the child has completed
+ int status = 0;
+ waitpid(pid, &status, 0);
+ }
+ }
+}
+
+
+int test_bbackupd()
+{
+// // First, wait for a normal period to make sure the last changes attributes are within a normal backup timeframe.
+// wait_for_backup_operation();
+
+ // Connection gubbins
+ TLSContext context;
+ context.Initialise(false /* client */,
+ "testfiles/clientCerts.pem",
+ "testfiles/clientPrivKey.pem",
+ "testfiles/clientTrustedCAs.pem");
+
+ // unpack the files for the initial test
+ TEST_THAT(::system("rm -rf testfiles/TestDir1") == 0);
+ TEST_THAT(::system("mkdir testfiles/TestDir1") == 0);
+ TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz | ( cd testfiles/TestDir1 && tar xf - )") == 0);
+
+ int pid = LaunchServer("../../bin/bbackupd/bbackupd testfiles/bbackupd.conf", "testfiles/bbackupd.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid > 0)
+ {
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // First, check storage space handling -- wait for file to be uploaded
+ wait_for_backup_operation();
+ //TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf info 01234567") == 0);
+ // Set limit to something very small
+ // About 28 blocks will be used at this point. bbackupd will only pause if the size used is
+ // greater than soft limit + 1/3 of (hard - soft). Set small values for limits accordingly.
+ TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 10B 40B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Unpack some more files
+ TEST_THAT(::system("gzip -d < testfiles/spacetest2.tgz | ( cd testfiles/TestDir1 && tar xf - )") == 0);
+ // Delete a file and a directory
+ TEST_THAT(::unlink("testfiles/TestDir1/spacetest/d1/f3") == 0);
+ TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d3/d4") == 0);
+ wait_for_backup_operation();
+
+ // Make sure there are some differences
+ int compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query0a.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 2*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Put the limit back
+ TEST_THAT_ABORTONFAIL(::system("../../bin/bbstoreaccounts/bbstoreaccounts -c testfiles/bbstored.conf setlimit 01234567 1000B 2000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Check that the notify script was run
+ TEST_THAT(TestFileExists("testfiles/notifyran.store-full.1"));
+ // But only once!
+ TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2"));
+
+ // unpack the initial files again
+ TEST_THAT(::system("gzip -d < testfiles/test_base.tgz | ( cd testfiles && tar xf - )") == 0);
+
+ // wait for it to do it's stuff
+ wait_for_backup_operation();
+
+ // Check that the contents of the store are the same as the contents
+ // of the disc (-a = all, -c = give result in return code)
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query1.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ printf("Delete file and update another, create symlink.\n");
+
+ // Delete a file
+ TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0);
+ // New symlink
+ TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/symlink-to-dir") == 0);
+
+ // Update a file (will be uploaded as a diff)
+ {
+ // Check that the file is over the diffing threshold in the bbstored.conf file
+ TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") > 1024);
+
+ // Add a bit to the end
+ FILE *f = ::fopen("testfiles/TestDir1/f45.df", "a");
+ TEST_THAT(f != 0);
+ ::fprintf(f, "EXTRA STUFF");
+ ::fclose(f);
+ TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") > 1024);
+ }
+
+ // wait for backup daemon to do it's stuff, and compare again
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query2.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ // Try a quick compare, just for fun
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query2q.log \"compare -acq\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Bad case: delete a file/symlink, replace it with a directory
+ printf("Replace symlink with directory, add new directory\n");
+ TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir") == 0);
+ TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) == 0);
+ TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) == 0);
+ // NOTE: create a file within the directory to avoid deletion by the housekeeping process later
+ TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file/contents") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // And the inverse, replace a directory with a file/symlink
+ printf("Replace directory with symlink\n");
+ TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file/contents") == 0);
+ TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0);
+ TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // And then, put it back to how it was before.
+ printf("Replace symlink with directory (which was a symlink)\n");
+ TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file") == 0);
+ TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) == 0);
+ TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file/contents2") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // And finally, put it back to how it was before it was put back to how it was before
+ // This gets lots of nasty things in the store with directories over other old directories.
+ printf("Put it all back to how it was\n");
+ TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file/contents2") == 0);
+ TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0);
+ TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/x1/dir-to-file") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // case which went wrong: rename a tracked file over a deleted file
+ printf("Rename an existing file over a deleted file\n");
+ TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", "testfiles/TestDir1/x1/dsfdsfs98.fd") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3s.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ printf("Add files with old times, update attributes of one to latest time\n");
+
+ // Move that file back
+ TEST_THAT(::rename("testfiles/TestDir1/x1/dsfdsfs98.fd", "testfiles/TestDir1/df9834.dsf") == 0);
+
+ // Add some more files
+ // Because the 'm' option is not used, these files will look very old to the daemon.
+ // Lucky it'll upload them then!
+ TEST_THAT(::system("gzip -d < testfiles/test2.tgz | ( cd testfiles && tar xf - )") == 0);
+ ::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415);
+
+ // Wait and test
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Check that modifying files with old timestamps still get added
+ printf("Modify existing file, but change timestamp to rather old\n");
+ // Time critical, so sync
+ TEST_THAT(::system("../../bin/bbackupctl/bbackupctl -q -c testfiles/bbackupd.conf wait-for-sync") == 0);
+ TestRemoteProcessMemLeaks("bbackupctl.memleaks");
+ // Then wait a second, to make sure the scan is complete
+ ::sleep(1);
+ // Then modify an existing file
+ {
+ chmod("testfiles/TestDir1/sub23/rand.h", 0777); // in the archive, it's read only
+ FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", "w+");
+ TEST_THAT(f != 0);
+ fprintf(f, "MODIFIED!\n");
+ fclose(f);
+ // and then move the time backwards!
+ struct timeval times[2];
+ BoxTimeToTimeval(SecondsToBoxTime((time_t)(365*24*60*60)), times[1]);
+ times[0] = times[1];
+ TEST_THAT(::utimes("testfiles/TestDir1/sub23/rand.h", times) == 0);
+ }
+ // Wait and test
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3e.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Add some files and directories which are marked as excluded
+ printf("Add files and dirs for exclusion test\n");
+ TEST_THAT(::system("gzip -d < testfiles/testexclude.tgz | ( cd testfiles && tar xf - )") == 0);
+ // Wait and test
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3c.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3d.log \"compare -acE\" quit");
+ TEST_THAT(compareReturnValue == 2*256); // should find differences
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // These tests only work as non-root users.
+ if(::getuid() != 0)
+ {
+ // Check that read errors are reported neatly
+ printf("Add unreadable files\n");
+ {
+ // Dir and file which can't be read
+ TEST_THAT(::mkdir("testfiles/TestDir1/sub23/read-fail-test-dir", 0000) == 0);
+ int fd = ::open("testfiles/TestDir1/read-fail-test-file", O_CREAT | O_WRONLY, 0000);
+ TEST_THAT(fd != -1);
+ ::close(fd);
+ }
+ // Wait and test...
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3e.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 2*256); // should find differences
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ // Check that it was reported correctly
+ TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));
+ // Set permissions on file and dir to stop errors in the future
+ ::chmod("testfiles/TestDir1/sub23/read-fail-test-dir", 0770);
+ ::chmod("testfiles/TestDir1/read-fail-test-file", 0770);
+ }
+
+ printf("Continuously update file, check isn't uploaded\n");
+
+ // Make sure everything happens at the same point in the sync cycle: wait until exactly the start of a sync
+ TEST_THAT(::system("../../bin/bbackupctl/bbackupctl -c testfiles/bbackupd.conf wait-for-sync") == 0);
+ TestRemoteProcessMemLeaks("bbackupctl.memleaks");
+ // Then wait a second, to make sure the scan is complete
+ ::sleep(1);
+
+ {
+ // Open a file, then save something to it every second
+ for(int l = 0; l < 12; ++l)
+ {
+ FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+");
+ TEST_THAT(f != 0);
+ fprintf(f, "Loop iteration %d\n", l);
+ fflush(f);
+ sleep(1);
+ printf(".");
+ fflush(stdout);
+ ::fclose(f);
+ }
+ printf("\n");
+
+ // Check there's a difference
+ compareReturnValue = ::system("testfiles/extcheck1.pl");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ printf("Keep on continuously updating file, check it is uploaded eventually\n");
+
+ for(int l = 0; l < 28; ++l)
+ {
+ FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+");
+ TEST_THAT(f != 0);
+ fprintf(f, "Loop 2 iteration %d\n", l);
+ fflush(f);
+ sleep(1);
+ printf(".");
+ fflush(stdout);
+ ::fclose(f);
+ }
+ printf("\n");
+
+ compareReturnValue = ::system("testfiles/extcheck2.pl");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ }
+
+ printf("Delete directory, change attributes\n");
+
+ // Delete a directory
+ TEST_THAT(::system("rm -rf testfiles/TestDir1/x1") == 0);
+ // Change attributes on an original file.
+ ::chmod("testfiles/TestDir1/df9834.dsf", 0423);
+
+ // Wait and test
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query4.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ printf("Restore files and directories\n");
+ int64_t deldirid = 0;
+ int64_t restoredirid = 0;
+ {
+ // connect and log in
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED);
+ BackupProtocolClient protocol(conn);
+ protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
+
+ // Find the ID of the Test1 directory
+ restoredirid = GetDirID(protocol, "Test1", BackupProtocolClientListDirectory::RootDirectory);
+ TEST_THAT(restoredirid != 0);
+
+ // Test the restoration
+ TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-Test1", true /* print progress dots */) == Restore_Complete);
+
+ // Compare it
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query10.log \"compare -cE Test1 testfiles/restore-Test1\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Make sure you can't restore a restored directory
+ TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-Test1", true /* print progress dots */) == Restore_TargetExists);
+
+ // Find ID of the deleted directory
+ deldirid = GetDirID(protocol, "x1", restoredirid);
+ TEST_THAT(deldirid != 0);
+
+ // Just check it doesn't bomb out -- will check this properly later (when bbackupd is stopped)
+ TEST_THAT(BackupClientRestore(protocol, deldirid, "testfiles/restore-Test1-x1", true /* print progress dots */, true /* deleted files */) == Restore_Complete);
+
+ // Log out
+ protocol.QueryFinished();
+ }
+
+ printf("Add files with current time\n");
+
+ // Add some more files and modify others
+ // Use the m flag this time so they have a recent modification time
+ TEST_THAT(::system("gzip -d < testfiles/test3.tgz | ( cd testfiles && tar xmf - )") == 0);
+
+ // Wait and test
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query5.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Rename directory
+ printf("Rename directory\n");
+ TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", "testfiles/TestDir1/renamed-dir") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ // and again, but with quick flag
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6q.log \"compare -acq\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Rename some files -- one under the threshold, others above
+ printf("Rename files\n");
+ TEST_THAT(rename("testfiles/TestDir1/continousupdate", "testfiles/TestDir1/continousupdate-ren") == 0);
+ TEST_THAT(rename("testfiles/TestDir1/df324", "testfiles/TestDir1/df324-ren") == 0);
+ TEST_THAT(rename("testfiles/TestDir1/sub23/find2perl", "testfiles/TestDir1/find2perl-ren") == 0);
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Check that modifying files with madly in the future timestamps still get added
+ printf("Create a file with timestamp to way ahead in the future\n");
+ // Time critical, so sync
+ TEST_THAT(::system("../../bin/bbackupctl/bbackupctl -q -c testfiles/bbackupd.conf wait-for-sync") == 0);
+ TestRemoteProcessMemLeaks("bbackupctl.memleaks");
+ // Then wait a second, to make sure the scan is complete
+ ::sleep(1);
+ // Then modify an existing file
+ {
+ FILE *f = fopen("testfiles/TestDir1/sub23/in-the-future", "w");
+ TEST_THAT(f != 0);
+ fprintf(f, "Back to the future!\n");
+ fclose(f);
+ // and then move the time forwards!
+ struct timeval times[2];
+ BoxTimeToTimeval(GetCurrentBoxTime() + SecondsToBoxTime((time_t)(365*24*60*60)), times[1]);
+ times[0] = times[1];
+ TEST_THAT(::utimes("testfiles/TestDir1/sub23/in-the-future", times) == 0);
+ }
+ // Wait and test
+ wait_for_backup_operation();
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query3e.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ printf("Change client store marker\n");
+
+ // Then... connect to the server, and change the client store marker. See what that does!
+ {
+ bool done = false;
+ int tries = 4;
+ while(!done && tries > 0)
+ {
+ try
+ {
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED);
+ BackupProtocolClient protocol(conn);
+ protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)); // read-write
+ // Make sure the marker isn't zero, because that's the default, and it should have changed
+ TEST_THAT(loginConf->GetClientStoreMarker() != 0);
+
+ // Change it to something else
+ protocol.QuerySetClientStoreMarker(12);
+
+ // Success!
+ done = true;
+
+ // Log out
+ protocol.QueryFinished();
+ }
+ catch(...)
+ {
+ tries--;
+ }
+ }
+ TEST_THAT(done);
+ }
+
+ printf("Check change of store marker pauses daemon\n");
+
+ // Make a change to a file, to detect whether or not it's hanging around
+ // waiting to retry.
+ {
+ FILE *f = ::fopen("testfiles/TestDir1/fileaftermarker", "w");
+ TEST_THAT(f != 0);
+ ::fprintf(f, "Lovely file you got there.");
+ ::fclose(f);
+ }
+
+ // Wait and test that there *are* differences
+ wait_for_backup_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2); // little bit longer than usual
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query6.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 2*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ printf("Interrupted restore\n");
+ {
+ do_interrupted_restore(context, restoredirid);
+ int64_t resumesize = 0;
+ TEST_THAT(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize));
+ TEST_THAT(resumesize > 16); // make sure it has recorded something to resume
+
+ printf("\nResume restore\n");
+
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED);
+ BackupProtocolClient protocol(conn);
+ protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)); // read-write
+
+ // Check that the restore fn returns resume possible, rather than doing anything
+ TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-interrupt", true /* print progress dots */) == Restore_ResumePossible);
+
+ // Then resume it
+ TEST_THAT(BackupClientRestore(protocol, restoredirid, "testfiles/restore-interrupt", true /* print progress dots */, false /* deleted files */, false /* undelete server */, true /* resume */) == Restore_Complete);
+
+ protocol.QueryFinished();
+
+ // Then check it has restored the correct stuff
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query14.log \"compare -cE Test1 testfiles/restore-interrupt\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ }
+
+ printf("Check restore deleted files\n");
+ {
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost", BOX_PORT_BBSTORED);
+ BackupProtocolClient protocol(conn);
+ protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)); // read-write
+
+ // Do restore and undelete
+ TEST_THAT(BackupClientRestore(protocol, deldirid, "testfiles/restore-Test1-x1-2", true /* print progress dots */, true /* deleted files */, true /* undelete on server */) == Restore_Complete);
+
+ protocol.QueryFinished();
+
+ // Do a compare with the now undeleted files
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query11.log \"compare -cE Test1/x1 testfiles/restore-Test1-x1-2\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ }
+
+ // Final check on notifications
+ TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));
+
+ // Kill the daemon
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+ TestRemoteProcessMemLeaks("bbackupd.memleaks");
+
+ // Start it again
+ pid = LaunchServer("../../bin/bbackupd/bbackupd testfiles/bbackupd.conf", "testfiles/bbackupd.pid");
+ TEST_THAT(pid != -1 && pid != 0);
+ if(pid != -1 && pid != 0)
+ {
+ // Wait and comapre
+ wait_for_backup_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2); // little bit longer than usual
+ compareReturnValue = ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query4.log \"compare -ac\" quit");
+ TEST_THAT(compareReturnValue == 1*256);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ // Kill it again
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+ TestRemoteProcessMemLeaks("bbackupd.memleaks");
+ }
+ }
+
+ // List the files on the server
+ ::system("../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/queryLIST.log \"list -rotdh\" quit");
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+ if(::getuid() == 0)
+ {
+ ::printf("WARNING: This test was run as root. Some tests have been omitted.\n");
+ }
+
+ return 0;
+}
+
+int test(int argc, const char *argv[])
+{
+ // SSL library
+ SSLLib::Initialise();
+
+ // Keys for subsystems
+ BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys");
+
+ // Initial files
+ TEST_THAT(::system("gzip -d < testfiles/test_base.tgz | ( cd testfiles && tar xf - )") == 0);
+
+ // Do the tests
+
+ int r = test_basics();
+ if(r != 0) return r;
+
+ r = test_setupaccount();
+ if(r != 0) return r;
+
+ r = test_run_bbstored();
+ if(r != 0) return r;
+
+ r = test_bbackupd();
+ if(r != 0) return r;
+
+ test_kill_bbstored();
+
+ return 0;
+}
+
diff --git a/test/bbackupd/testextra b/test/bbackupd/testextra
new file mode 100644
index 00000000..ad7dd552
--- /dev/null
+++ b/test/bbackupd/testextra
@@ -0,0 +1,42 @@
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+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.conf b/test/bbackupd/testfiles/bbackupd.conf
new file mode 100644
index 00000000..c25b4eab
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd.conf
@@ -0,0 +1,50 @@
+
+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
+
+MaximumDiffingTime = 8
+
+ExtendedLogging = yes
+
+CommandSocket = testfiles/bbackupd.sock
+
+NotifyScript = perl testfiles/notifyscript.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..18c73a40
--- /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
+ 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 b/test/bbackupd/testfiles/extcheck1.pl
new file mode 100755
index 00000000..8bd5b91d
--- /dev/null
+++ b/test/bbackupd/testfiles/extcheck1.pl
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+use strict;
+
+unless(open IN,"../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query4.log \"compare -ac\" quit|")
+{
+ print "Couldn't open compare utility\n";
+ exit 2;
+}
+
+my $ret = 1;
+my $seen = 0;
+
+while(<IN>)
+{
+ next unless m/\S/;
+ if(m/continousupdate/)
+ {
+ $ret = 2 unless m/exists/;
+ $seen = 1;
+ }
+ else
+ {
+ $ret = 2 unless m/\AWARNING/ || m/\ADifferences/ || /might be reason/ || /probably due to file mod/;
+ }
+ print;
+}
+
+close IN;
+
+$ret = 2 unless $seen;
+
+exit $ret;
+
diff --git a/test/bbackupd/testfiles/extcheck2.pl b/test/bbackupd/testfiles/extcheck2.pl
new file mode 100755
index 00000000..abf63ba9
--- /dev/null
+++ b/test/bbackupd/testfiles/extcheck2.pl
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+use strict;
+
+unless(open IN,"../../bin/bbackupquery/bbackupquery -q -c testfiles/bbackupd.conf -l testfiles/query4.log \"compare -ac\" quit|")
+{
+ print "Couldn't open compare utility\n";
+ exit 2;
+}
+
+my $ret = 1;
+
+while(<IN>)
+{
+ next unless m/\S/;
+ if(m/continousupdate/)
+ {
+ $ret = 2 unless m/contents/ || m/attributes/;
+ }
+ else
+ {
+ $ret = 2 unless m/\AWARNING/ || m/\ADifferences/ || /might be reason/ || /probably due to file mod/;
+ }
+ print;
+}
+
+close IN;
+
+exit $ret;
+
diff --git a/test/bbackupd/testfiles/notifyscript.pl b/test/bbackupd/testfiles/notifyscript.pl
new file mode 100755
index 00000000..deadadb9
--- /dev/null
+++ b/test/bbackupd/testfiles/notifyscript.pl
@@ -0,0 +1,53 @@
+#!/usr/bin/perl
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+
+
+my $f = 'testfiles/notifyran.'.$ARGV[0].'.';
+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/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..ac388b33
--- /dev/null
+++ b/test/common/testcommon.cpp
@@ -0,0 +1,607 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: testcommon.cpp
+// Purpose: Tests for the code in lib/common
+// Created: 2003/07/23
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "Test.h"
+#include "Configuration.h"
+#include "FdGetLine.h"
+#include "Guards.h"
+#include "FileStream.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 "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[] =
+{
+ {"bing", 0, ConfigTest_Exists, 0},
+ {"carrots", 0, ConfigTest_Exists | ConfigTest_IsInt, 0},
+ {"terrible", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+};
+
+ConfigurationVerifyKey verifykeys1_1_2[] =
+{
+ {"fish", 0, ConfigTest_Exists | ConfigTest_IsInt, 0},
+ {"string", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+};
+
+
+ConfigurationVerify verifysub1_1[] =
+{
+ {
+ "*",
+ 0,
+ verifykeys1_1_1,
+ ConfigTest_Exists,
+ 0
+ },
+ {
+ "otherthing",
+ 0,
+ verifykeys1_1_2,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+};
+
+ConfigurationVerifyKey verifykeys1_1[] =
+{
+ {"value", 0, ConfigTest_Exists | ConfigTest_IsInt, 0},
+ {"string1", 0, ConfigTest_Exists, 0},
+ {"string2", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+};
+
+ConfigurationVerifyKey verifykeys1_2[] =
+{
+ {"carrots", 0, ConfigTest_Exists | ConfigTest_IsInt, 0},
+ {"string", 0, ConfigTest_Exists | ConfigTest_LastEntry, 0}
+};
+
+ConfigurationVerify verifysub1[] =
+{
+ {
+ "test1",
+ verifysub1_1,
+ verifykeys1_1,
+ ConfigTest_Exists,
+ 0
+ },
+ {
+ "ping",
+ 0,
+ verifykeys1_2,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+};
+
+ConfigurationVerifyKey verifykeys1[] =
+{
+ {"notExpected", 0, 0, 0},
+ {"HasDefaultValue", "Lovely default value", 0, 0},
+ {"MultiValue", 0, ConfigTest_MultiValueAllowed, 0},
+ {"BoolTrue1", 0, ConfigTest_IsBool, 0},
+ {"BoolTrue2", 0, ConfigTest_IsBool, 0},
+ {"BoolFalse1", 0, ConfigTest_IsBool, 0},
+ {"BoolFalse2", 0, ConfigTest_IsBool, 0},
+ {"TOPlevel", 0, ConfigTest_LastEntry | ConfigTest_Exists, 0}
+};
+
+ConfigurationVerify verify =
+{
+ "root",
+ verifysub1,
+ verifykeys1,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+};
+
+int test(int argc, const char *argv[])
+{
+ // Test memory leak detection
+#ifdef BOX_MEMORY_LEAK_TESTING
+ {
+ 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);
+ }
+#endif // BOX_MEMORY_LEAK_TESTING
+
+
+ static 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_H
+ 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
+ // 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);
+ #ifdef HAVE_REGEX_H
+ TEST_THAT(elist.IsExcluded(std::string("b.reg")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("e.reg")) == false);
+ TEST_THAT(elist.IsExcluded(std::string("b.Reg")) == false);
+ TEST_THAT(elist.IsExcluded(std::string("DEfinite1")) == false);
+ TEST_THAT(elist.IsExcluded(std::string("DEXCLUDEfinite1")) == true);
+ TEST_THAT(elist.IsExcluded(std::string("DEfinitexclude1")) == false);
+ TEST_THAT(elist.IsExcluded(std::string("exclude")) == true);
+ #endif
+ }
+
+ test_conversions();
+
+ 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..a7ea1298
--- /dev/null
+++ b/test/compress/testcompress.cpp
@@ -0,0 +1,298 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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());
+ TRACE2("compressed size = %d, source size = %d\n", poutput->GetSize(), 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..f3e9fb50
--- /dev/null
+++ b/test/crypto/testcrypto.cpp
@@ -0,0 +1,352 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: 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/raidfile/intercept.cpp b/test/raidfile/intercept.cpp
new file mode 100644
index 00000000..aa756b60
--- /dev/null
+++ b/test/raidfile/intercept.cpp
@@ -0,0 +1,310 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: intercept.cpp
+// Purpose: Syscall interception code for the raidfile test
+// Created: 2003/07/22
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_SYS_SYSCALL_H
+ #include <sys/syscall.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/uio.h>
+#include <errno.h>
+
+#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
+
+#if !defined(HAVE_SYSCALL) && !defined(HAVE___SYSCALL) && !defined(HAVE___SYSCALL_NEED_DEFN)
+ #define PLATFORM_NO_SYSCALL
+#endif
+
+#ifdef PLATFORM_NO_SYSCALL
+ // For some reason, syscall just doesn't work on Darwin
+ // so instead, we build functions using assembler in a varient
+ // of the technique used in the Darwin Libc
+ extern "C" int
+ TEST_open(const char *path, int flags, mode_t mode);
+ extern "C" int
+ TEST_close(int d);
+ extern "C" ssize_t
+ TEST_write(int d, const void *buf, size_t nbytes);
+ extern "C" ssize_t
+ TEST_read(int d, void *buf, size_t nbytes);
+ extern "C" ssize_t
+ TEST_readv(int d, const struct iovec *iov, int iovcnt);
+ extern "C" off_t
+ TEST_lseek(int fildes, off_t offset, int whence);
+#else
+ #ifdef HAVE___SYSCALL_NEED_DEFN
+ // Need this, not declared in syscall.h nor unistd.h
+ extern "C" off_t __syscall(quad_t number, ...);
+ #endif
+ #ifndef HAVE_SYSCALL
+ #undef syscall
+ #define syscall __syscall
+ #endif
+#endif
+
+#include <string.h>
+#include <stdio.h>
+
+#include "MemLeakFindOn.h"
+
+bool intercept_enabled = false;
+const char *intercept_filename = 0;
+int intercept_filedes = -1;
+off_t intercept_errorafter = 0;
+int intercept_errno = 0;
+int intercept_syscall = 0;
+off_t intercept_filepos = 0;
+
+#define SIZE_ALWAYS_ERROR -773
+
+void intercept_clear_setup()
+{
+ intercept_enabled = false;
+ intercept_filename = 0;
+ intercept_filedes = -1;
+ intercept_errorafter = 0;
+ intercept_syscall = 0;
+ intercept_filepos = 0;
+}
+
+bool intercept_triggered()
+{
+ return !intercept_enabled;
+}
+
+void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror)
+{
+ TRACE4("Setup for error: %s, after %d, err %d, syscall %d\n", filename, errorafter, errortoreturn, syscalltoerror);
+ intercept_enabled = true;
+ intercept_filename = filename;
+ intercept_filedes = -1;
+ intercept_errorafter = errorafter;
+ intercept_syscall = syscalltoerror;
+ intercept_errno = errortoreturn;
+ intercept_filepos = 0;
+}
+
+bool intercept_errornow(int d, int size, int syscallnum)
+{
+ if(intercept_filedes != -1 && d == intercept_filedes && syscallnum == intercept_syscall)
+ {
+ //printf("Checking for err, %d, %d, %d\n", d, size, syscallnum);
+ if(size == SIZE_ALWAYS_ERROR)
+ {
+ // Looks good for an error!
+ TRACE2("Returning error %d for syscall %d\n", intercept_errno, syscallnum);
+ return true;
+ }
+ // where are we in the file?
+ if(intercept_filepos >= intercept_errorafter || intercept_filepos >= ((off_t)intercept_errorafter - size))
+ {
+ TRACE3("Returning error %d for syscall %d, file pos %d\n", intercept_errno, syscallnum, (int)intercept_filepos);
+ return true;
+ }
+ }
+ return false; // no error please!
+}
+
+int intercept_reterr()
+{
+ intercept_enabled = false;
+ intercept_filename = 0;
+ intercept_filedes = -1;
+ intercept_errorafter = 0;
+ intercept_syscall = 0;
+ return intercept_errno;
+}
+
+#define CHECK_FOR_FAKE_ERROR_COND(D, S, CALL, FAILRES) \
+ if(intercept_enabled) \
+ { \
+ if(intercept_errornow(D, S, CALL)) \
+ { \
+ errno = intercept_reterr(); \
+ return FAILRES; \
+ } \
+ }
+
+extern "C" int
+open(const char *path, int flags, mode_t mode)
+{
+ if(intercept_enabled)
+ {
+ if(intercept_syscall == SYS_open && strcmp(path, intercept_filename) == 0)
+ {
+ errno = intercept_reterr();
+ return -1;
+ }
+ }
+#ifdef PLATFORM_NO_SYSCALL
+ int r = TEST_open(path, flags, mode);
+#else
+ int r = syscall(SYS_open, path, flags, mode);
+#endif
+ if(intercept_enabled && intercept_filedes == -1)
+ {
+ // Right file?
+ if(strcmp(intercept_filename, path) == 0)
+ {
+ intercept_filedes = r;
+ //printf("Found file to intercept, h = %d\n", r);
+ }
+ }
+ return r;
+}
+
+extern "C" int
+open64(const char *path, int flags, mode_t mode)
+{
+ // With _FILE_OFFSET_BITS set to 64 this should really use (flags |
+ // O_LARGEFILE) here, but not actually necessary for the tests and not
+ // worth the trouble finding O_LARGEFILE
+ return open(path, flags, mode);
+}
+
+extern "C" int
+close(int d)
+{
+ CHECK_FOR_FAKE_ERROR_COND(d, SIZE_ALWAYS_ERROR, SYS_close, -1);
+#ifdef PLATFORM_NO_SYSCALL
+ int r = TEST_close(d);
+#else
+ int r = syscall(SYS_close, d);
+#endif
+ if(r == 0)
+ {
+ if(d == intercept_filedes)
+ {
+ intercept_filedes = -1;
+ }
+ }
+ return r;
+}
+
+extern "C" ssize_t
+write(int d, const void *buf, size_t nbytes)
+{
+ CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_write, -1);
+#ifdef PLATFORM_NO_SYSCALL
+ int r = TEST_write(d, buf, nbytes);
+#else
+ int r = syscall(SYS_write, d, buf, nbytes);
+#endif
+ if(r != -1)
+ {
+ intercept_filepos += r;
+ }
+ return r;
+}
+
+extern "C" ssize_t
+read(int d, void *buf, size_t nbytes)
+{
+ CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_read, -1);
+#ifdef PLATFORM_NO_SYSCALL
+ int r = TEST_read(d, buf, nbytes);
+#else
+ int r = syscall(SYS_read, d, buf, nbytes);
+#endif
+ if(r != -1)
+ {
+ intercept_filepos += r;
+ }
+ return r;
+}
+
+extern "C" ssize_t
+readv(int d, const struct iovec *iov, int iovcnt)
+{
+ // how many bytes?
+ int nbytes = 0;
+ for(int b = 0; b < iovcnt; ++b)
+ {
+ nbytes += iov[b].iov_len;
+ }
+
+ CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_readv, -1);
+#ifdef PLATFORM_NO_SYSCALL
+ int r = TEST_readv(d, iov, iovcnt);
+#else
+ int r = syscall(SYS_readv, d, iov, iovcnt);
+#endif
+ if(r != -1)
+ {
+ intercept_filepos += r;
+ }
+ return r;
+}
+
+extern "C" off_t
+lseek(int fildes, off_t offset, int whence)
+{
+ // random magic for lseek syscall, see /usr/src/lib/libc/sys/lseek.c
+ CHECK_FOR_FAKE_ERROR_COND(fildes, 0, SYS_lseek, -1);
+#ifdef PLATFORM_NO_SYSCALL
+ int r = TEST_lseek(fildes, offset, whence);
+#else
+ #ifdef HAVE_LSEEK_DUMMY_PARAM
+ off_t r = syscall(SYS_lseek, fildes, 0 /* extra 0 required here! */, offset, whence);
+ #elif defined(_FILE_OFFSET_BITS)
+ // Don't bother trying to call SYS__llseek on 32 bit since it is
+ // fiddly and not needed for the tests
+ off_t r = syscall(SYS_lseek, fildes, (uint32_t)offset, whence);
+ #else
+ off_t r = syscall(SYS_lseek, fildes, offset, whence);
+ #endif
+#endif
+ if(r != -1)
+ {
+ intercept_filepos = r;
+ }
+ return r;
+}
+
+#endif // n PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
diff --git a/test/raidfile/testextra b/test/raidfile/testextra
new file mode 100644
index 00000000..7b0bd613
--- /dev/null
+++ b/test/raidfile/testextra
@@ -0,0 +1,45 @@
+# distribution boxbackup-0.10 (svn version: 494)
+#
+# Copyright (c) 2003 - 2006
+# Ben Summers and contributors. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# 3. All use of this software and associated advertising materials must
+# display the following acknowledgment:
+# This product includes software developed by Ben Summers.
+# 4. The names of the Authors may not be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# [Where legally impermissible the Authors do not disclaim liability for
+# direct physical injury or death caused solely by defects in the software
+# unless it is modified by a third party.]
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+#
+#
+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..04b5b454
--- /dev/null
+++ b/test/raidfile/testraidfile.cpp
@@ -0,0 +1,947 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: test/raidfile/test.cpp
+// Purpose: Test RaidFile system
+// Created: 2003/07/08
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/syscall.h>
+
+#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
+
+ // Be nasty, and create some errors for the RAID stuff to recover from...
+ if(TestRAIDProperties)
+ {
+ char stripe1fn[256], stripe1fnRename[256];
+ sprintf(stripe1fn, "testfiles/%d_%d/%s.rf", set, startDisc, filename);
+ sprintf(stripe1fnRename, "testfiles/%d_%d/%s.rf-REMOVED", set, startDisc, filename);
+ char stripe2fn[256], stripe2fnRename[256];
+ sprintf(stripe2fn, "testfiles/%d_%d/%s.rf", set, (startDisc + 1) % RAID_NUMBER_DISCS, filename);
+ sprintf(stripe2fnRename, "testfiles/%d_%d/%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/%d_%d/.raidfile-unreadable/%s", set, startDisc, mungefilename);
+ char stripe2munge[256];
+ sprintf(stripe2munge, "testfiles/%d_%d/.raidfile-unreadable/%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/%d_%d/%s.rfwX", set, startDisc, filename);
+ TEST_THAT(TestFileExists(writefnPre));
+ char writefn[256];
+ sprintf(writefn, "testfiles/%d_%d/%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/%d_%d/%s.rf", set, startDisc, filename);
+ TEST_THAT(TestGetFileSize(stripe1fn) == fs1);
+ char stripe2fn[256];
+ sprintf(stripe2fn, "testfiles/%d_%d/%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/%d_%d/%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, 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, 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/0_2/overwrite_B.rfwX", O_WRONLY | O_CREAT | O_EXCL, 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/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/0_0/test-dir"));
+ TEST_THAT(TestDirExists("testfiles/0_1/test-dir"));
+ TEST_THAT(TestDirExists("testfiles/0_2/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
+ TRACE3("Gen paths= '%s','%s',%s'\n", n1.c_str(), n2.c_str(), n3.c_str());
+ }
+
+ // 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/0_2/test1.rfwX", O_RDONLY, 0)) >= 0);
+ char buffer[sizeof(data)];
+ TEST_THAT(::read(f, 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]);
+ }
+ TEST_THAT(::lseek(f, sizeof(data), SEEK_SET) == sizeof(buffer));
+ TEST_THAT(::read(f, 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(::read(f, buffer, sizeof(buffer)) == 0);
+ ::close(f);
+
+ // Commit the data
+ write1.Commit();
+ TEST_THAT((f = ::open("testfiles/0_2/test1.rfw", O_RDONLY, 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/0_2/test1.rfw", O_RDONLY, 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/0_2/test1.rfw", O_RDONLY, 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/0_0/damage.rf", "testfiles/0_0/damage.rf-NT") == 0);
+ {
+ std::auto_ptr<RaidFileRead> pr0 = RaidFileRead::Open(0, "damage");
+ pr0->Read(buffer, sizeof(data));
+ }
+ TEST_THAT(::rename("testfiles/0_0/damage.rf-NT", "testfiles/0_0/damage.rf") == 0);
+
+ // Delete one of the files
+ TEST_THAT(::unlink("testfiles/0_1/damage.rf") == 0); // stripe 1
+
+#ifdef TRF_CAN_INTERCEPT
+ // Open it and read...
+ {
+ intercept_setup_error("testfiles/0_2/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/0_0/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/dfsdf1");
+ RaidFileWrite::CreateDirectory(0, "dirread/ponwq2");
+ {
+ RaidFileWrite w(0, "dirread/sdf9873241");
+ w.Open();
+ w.Write(data, sizeof(data));
+ w.Commit(true);
+ }
+ {
+ RaidFileWrite w(0, "dirread/fsdcxjni3242");
+ w.Open();
+ w.Write(data, sizeof(data));
+ w.Commit(true);
+ }
+ {
+ RaidFileWrite w(0, "dirread/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/0_0/dirread/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/0_1/dirread/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/0_2/dirread/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/testlibwin32.cpp b/test/win32/testlibwin32.cpp
new file mode 100644
index 00000000..02dfd5b5
--- /dev/null
+++ b/test/win32/testlibwin32.cpp
@@ -0,0 +1,158 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// win32test.cpp : Defines the entry point for the console application.
+//
+
+//#include <windows.h>
+#include "Box.h"
+
+#ifdef WIN32
+
+#include "../../bin/bbackupd/BackupDaemon.h"
+#include "BoxPortsAndFiles.h"
+#include "emu.h"
+
+int main(int argc, char* argv[])
+{
+ 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");
+#if 0
+ //remove - sleepycat include a version of getopt - mine never REALLY worked !
+ //test our getopt function
+ std::string commline("-q -c fgfgfg -f -l hello");
+
+ int c;
+ while((c = getopt(commline.size(), (char * const *)commline.c_str(), "qwc:l:")) != -1)
+ {
+ printf("switch = %c, param is %s\r\n", c, optarg);
+ }
+#endif
+ //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");
+ }
+ else
+ {
+ printf("is a directory?");
+ }
+
+ lstat("c:\\windows", &ourfs);
+
+ if ( S_ISDIR(ourfs.st_mode))
+ {
+ printf("is a directory");
+ }
+ else
+ {
+ printf("is a file?");
+ }
+
+ //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..0a336206
--- /dev/null
+++ b/test/win32/timezone.cpp
@@ -0,0 +1,125 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+#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);
+}