--- /dev/null
+#
+# Copyright (C) 2000-2019 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+
+RMF = /bin/rm -f
+CXX = /usr/bin/g++
+NO_ECHO = @
+
+#
+# Flags
+#
+
+CXXFLAGS = -g -O2 -Wall
+
+#
+# Paths
+#
+
+COMMON_PATH = ../common
+BUILD_PATH = ../../build
+SRC_PATH = $(BUILD_PATH)/src
+JOURNAL_PATH = $(SRC_PATH)/plugins/fd
+FD_PLUGIN_PATH = $(SRC_PATH)/plugins/fd
+FILED_PATH = $(SRC_PATH)/filed
+CLIENT_PATH = $(SRC_PATH)/tools/cdp-client
+
+BIN_DIR = bin
+
+#
+# Libs and Includes
+#
+
+LIBS = -L$(SRC_PATH)/lib -L$(SRC_PATH)/findlib -lbac -lbacfind -lpthread -ldl
+
+INCLUDES = -I. -I$(COMMON_PATH) -I$(SRC_PATH) -I$(CLIENT_PATH) -I$(JOURNAL_PATH) -I$(SRC_PATH)/lib -I$(SRC_PATH)/findlib -I $(FILED_PATH)
+
+#
+# Libtool specific settings
+#
+
+LIBTOOL = $(BUILD_PATH)/libtool
+LIBTOOL_COMPILE = $(LIBTOOL) --silent --tag=CXX --mode=compile
+LIBTOOL_LINK = $(LIBTOOL) --silent --tag=CXX --mode=link
+LIBTOOL_CLEAN = $(LIBTOOL) --silent --tag=CXX --mode=clean
+
+.SUFFIXES: .c .cpp .lo
+
+# inference rules
+.c.lo:
+ @echo "Compiling $<"
+ $(NO_ECHO) $(LIBTOOL_COMPILE) $(CXX) -c $(CPPFLAGS) $(INCLUDES) $<
+.cpp.lo:
+ @echo "Compiling $<"
+ $(NO_ECHO) $(LIBTOOL_COMPILE) $(CXX) -c $(CPPFLAGS) $(INCLUDES) $<
+
+#-------------------------------------------------------------------------
+
+all: Makefile test-dirs journal-test folderwatcher-test backupservice-test cdp-plugin-test
+ mkdir -p $(BIN_DIR)
+ @echo "==== Make of cdp-tests is good ===="
+ @echo " "
+
+test-dirs:
+ mkdir -p $(BIN_DIR)
+
+run-all: all
+ $(BIN_DIR)/journal-test
+ $(BIN_DIR)/folderwatcher-test
+ $(BIN_DIR)/backupservice-test
+ $(BIN_DIR)/cdp-plugin-test
+
+clean:
+ @$(RMF) -r $(BIN_DIR) .libs _libs ./tmp ./*.lo
+
+JTEST_OBJS = journal-test.lo $(JOURNAL_PATH)/journal.lo
+journal-test: Makefile $(JTEST_OBJS)
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(JTEST_OBJS) $(LIBS)
+
+FTEST_OBJS = folderwatcher-test.lo $(CLIENT_PATH)/folderwatcher.lo
+folderwatcher-test: Makefile $(FTEST_OBJS)
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(FTEST_OBJS) $(LIBS)
+
+BTEST_OBJS = backupservice-test.lo $(JOURNAL_PATH)/journal.lo $(CLIENT_PATH)/folderwatcher.lo $(CLIENT_PATH)/backupservice.lo
+backupservice-test: Makefile $(BTEST_OBJS)
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(BTEST_OBJS) $(LIBS)
+
+PTEST_OBJS = cdp-plugin-test.lo $(JOURNAL_PATH)/journal.lo
+cdp-plugin-test: Makefile $(PTEST_OBJS)
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(PTEST_OBJS) $(LIBS)
+
--- /dev/null
+#
+# Copyright (C) 2000-2019 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+
+RMF = /bin/rm -f
+CXX = x86_64-w64-mingw32-g++
+NO_ECHO = @
+
+#
+# Flags
+#
+
+CXXFLAGS = -DHAVE_MINGW -DHAVE_WIN32 -DHAVE_MINGW_W64 -DHAVE_ZLIB_H -DHAVE_LIBZ -g -O2 -Wall -DUNICODE
+
+#
+# Paths
+#
+
+DEPKGS = ../../../depkgs-mingw-w64
+COMMON_PATH = ../common
+BUILD_PATH = ../../build
+SRC_PATH = $(BUILD_PATH)/src
+JOURNAL_PATH = $(SRC_PATH)/plugins/fd
+CDP_FD_PATH = $(SRC_PATH)/plugins/fd
+CLIENT_PATH = $(SRC_PATH)/tools/cdp-client
+
+BIN_DIR = bin
+
+#
+# Libs and Includes
+#
+
+LIBS = -lwsock32 -lstdc++ -L../../../bacula/src/win32/release64 -lbacula -lpthread
+
+INCLUDES = -I. -I../../../bacula/src/ -I../../../bacula/src/filed -I$(COMMON_PATH) -I$(SRC_PATH) -I$(CLIENT_PATH) -I$(CDP_FD_PATH) -I$(JOURNAL_PATH) -I../../../bacula/src/win32/compat -I$(DEPKGS)/include/ -I$(DEPKGS)/include/pthreads
+
+#
+# Libtool specific settings
+#
+
+LINK = x86_64-w64-mingw32-g++
+LFLAGS = -static-libgcc -static-libstdc++ -mthreads -Wl,-enable-stdcall-fixup -Wl,-enable-auto-import -fno-strict-aliasing -Wl,-enable-runtime-pseudo-reloc -mconsole
+
+.SUFFIXES: .c .cpp .o
+
+# inference rules
+.c.o:
+ @echo "Compiling $<"
+ $(NO_ECHO) $(CXX) -c $(CXXFLAGS) $(INCLUDES) $<
+.cpp.o:
+ @echo "Compiling $<"
+ $(NO_ECHO) $(CXX) -c $(CXXFLAGS) $(INCLUDES) $<
+
+#-------------------------------------------------------------------------
+
+all: Makefile test-dirs backupservice-test.exe journal-test.exe folderwatcher-test.exe cdp-plugin-test.exe
+ @echo "==== Make of cdp-tests (WIN_64) is good ===="
+ @echo " "
+
+test-dirs:
+ mkdir -p $(BIN_DIR)
+
+clean:
+ @$(RMF) -r $(BIN_DIR) .libs _libs ./tmp ./*.o
+
+JTEST_OBJS = journal-test.o journal.o
+journal-test.exe: Makefile $(JTEST_OBJS) win-test-process.exe
+ $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(JTEST_OBJS) $(LIBS)
+
+win-test-process.exe: Makefile win-test-process.o
+ $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ win-test-process.o $(LIBS)
+
+FTEST_OBJS = folderwatcher-test.o folderwatcher.o
+folderwatcher-test.exe: Makefile $(FTEST_OBJS)
+ $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(FTEST_OBJS) $(LIBS)
+
+BTEST_OBJS = backupservice-test.o backupservice.o folderwatcher.o journal.o
+backupservice-test.exe: Makefile $(BTEST_OBJS)
+ $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(BTEST_OBJS) $(LIBS)
+
+PTEST_OBJS = cdp-plugin-test.o journal.o
+cdp-plugin-test.exe: Makefile $(PTEST_OBJS)
+ $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(PTEST_OBJS) $(LIBS)
+
+#-------------------------------------------------------------------------
+
+backupservice.o: $(CLIENT_PATH)/backupservice.h $(CLIENT_PATH)/backupservice.cpp
+ $(CXX) -c $(CXXFLAGS) $(INCLUDES) -o backupservice.o $(CLIENT_PATH)/backupservice.cpp
+
+journal.o: $(JOURNAL_PATH)/journal.c $(JOURNAL_PATH)/journal.h
+ $(CXX) -c $(CXXFLAGS) $(INCLUDES) -o journal.o $(JOURNAL_PATH)/journal.c
+
+folderwatcher.o: $(CLIENT_PATH)/folderwatcher.cpp $(CLIENT_PATH)/folderwatcher.h
+ $(CXX) -c $(CXXFLAGS) $(INCLUDES) -o folderwatcher.o $(CLIENT_PATH)/folderwatcher.cpp
+
+
--- /dev/null
+/*
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2004-2019 Bacula Systems SA
+ All rights reserved.
+
+ The main author of Bacula is Kern Sibbald, with contributions from many
+ others, a complete list can be found in the file AUTHORS.
+
+ Licensees holding a valid Bacula Systems SA license may use this file
+ and others of this release in accordance with the proprietary license
+ agreement provided in the LICENSE file. Redistribution of any part of
+ this release is not permitted.
+
+ Bacula® is a registered trademark of Kern Sibbald.
+*/
+
+#include "backupservice.h"
+#include "journal.h"
+#include "btestutils.h"
+
+class BackupServiceTest
+{
+ char *_tmpDirPath;
+ char *_jPath;
+ char *_spoolPath;
+ Journal *_journal;
+ BackupService *_bservice;
+
+public:
+ BackupServiceTest() {
+ setupTitle("BackupServiceTest");
+
+ _tmpDirPath = BTU::getTmpPath();
+ _spoolPath = BTU::concat(_tmpDirPath, "/cdp-spool-dir");
+ _jPath = BTU::concat(_tmpDirPath, "/cdp-client.journal");
+
+ BTU::rmDir(_tmpDirPath);
+ BTU::mkDir(_tmpDirPath);
+ BTU::mkpath(_tmpDirPath, "cdp-spool-dir");
+
+ _journal = new Journal();
+ _journal->setJournalPath(_jPath, _spoolPath);
+ _bservice = new BackupService();
+
+ logParam("Tmp Folder", _tmpDirPath);
+ logParam("Journal File", _jPath);
+ logParam("Spool Directory", _spoolPath);
+
+ char *file1 = BTU::concat(_tmpDirPath, "/test_f1.txt");
+ BTU::mkfile(file1);
+ }
+
+ ~BackupServiceTest() {
+// BTU::rmDir(_tmpDirPath);
+ }
+
+private:
+
+#ifndef HAVE_WIN32
+ char *verifyFileWasCopied(const char *srcfpath, const char *srcfname, const char *destDirPath) {
+ struct dirent *de;
+ char *destFile = NULL;
+ DIR *destDir = opendir(destDirPath);
+
+ if (destDir == NULL) {
+ printf("ERROR: Destination directory not found: %s", destDirPath);
+ exit(-1);
+ }
+
+ while ((de = readdir(destDir)) != NULL) {
+ if (strstr(de->d_name, srcfname) != 0) {
+ destFile = bstrdup(de->d_name);
+ break;
+ }
+ }
+
+ if (destFile == NULL) {
+ printf("ERROR: Destination file not found. Spool File not created for %s", srcfpath);
+ exit(-1);
+ }
+
+ destFile = BTU::concat3(destDirPath, "/", destFile);
+ char *srcContents = BTU::readFile(srcfpath);
+ char *dstContents = BTU::readFile(destFile);
+ BTU::verifyStrings("File Copy in the Spool Dir", srcContents, dstContents);
+ closedir(destDir);
+ return destFile;
+ }
+
+#endif
+
+ void verifyFileRecord(const char *fpath, const char *spoolfpath) {
+ _journal->beginTransaction("r");
+ FileRecord *rec = _journal->readFileRecord();
+
+ if(rec == NULL) {
+ printf("FileRecord belonging to file %s was not found!", fpath);
+ exit(-1);
+ }
+
+ BTU::verifyStrings("fname", fpath, rec->name);
+ BTU::verifyStrings("sname", spoolfpath, rec->sname);
+ BTU::verifyLength("mtime", rec->mtime, 10);
+ BTU::verifyNotNull("fattrs", rec->fattrs);
+ _journal->endTransaction();
+ }
+
+public:
+
+ void bservice_should_update_spool_dir() {
+ title("Service should update the Spool Directory"
+ " inside the Journal's Settings Record");
+
+ _bservice->start(_spoolPath, _journal);
+ SettingsRecord *rec = _journal->readSettings();
+ BTU::verifyNotNull("Spool Dir", rec->getSpoolDir());
+ BTU::verifyStrings("Spool Dir", _spoolPath, rec->getSpoolDir());
+
+ delete _bservice;
+ _bservice = new BackupService();
+ }
+
+ void bservice_should_save_file_and_record() {
+ title("Service should save changed file in the Spool Directory"
+ " and save the file's metadata in the Journal");
+
+ char *fname = (char *) "test_f1.txt";
+ char *fpath = BTU::concat3(_tmpDirPath, "/", fname);
+
+ _bservice->start(_spoolPath, _journal);
+ _bservice->onChange(fpath);
+
+#ifndef HAVE_WIN32
+ char *spoolFilePath = verifyFileWasCopied(fpath, fname, _spoolPath);
+ verifyFileRecord(fpath, spoolFilePath);
+#endif
+ }
+
+ void bservice_should_stop_watching_folder() {
+ title("Service should stop watching folder");
+
+ FolderRecord rec;
+ rec.path = BTU::concat3(_tmpDirPath, "/", "folder1");
+ BTU::mkDir(rec.path);
+ _journal->writeFolderRecord(rec);
+
+ rec.path = BTU::concat3(_tmpDirPath, "/", "folder2");
+ BTU::mkDir(rec.path);
+ _journal->writeFolderRecord(rec);
+
+ rec.path = BTU::concat3(_tmpDirPath, "/", "folder3");
+ BTU::mkDir(rec.path);
+ _journal->writeFolderRecord(rec);
+
+ _bservice->start(_spoolPath, _journal);
+ _bservice->stopWatching("folder2");
+ }
+
+};
+
+int main(int argc, char *argv[])
+{
+ lmgr_init_thread();
+ BackupServiceTest test;
+ test.bservice_should_update_spool_dir();
+ printf("OK\n");
+ test.bservice_should_save_file_and_record();
+ printf("OK\n");
+ test.bservice_should_stop_watching_folder();
+ printf("OK\n");
+ lmgr_cleanup_thread();
+}
--- /dev/null
+/*
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2004-2019 Bacula Systems SA
+ All rights reserved.
+
+ The main author of Bacula is Kern Sibbald, with contributions from many
+ others, a complete list can be found in the file AUTHORS.
+
+ Licensees holding a valid Bacula Systems SA license may use this file
+ and others of this release in accordance with the proprietary license
+ agreement provided in the LICENSE file. Redistribution of any part of
+ this release is not permitted.
+
+ Bacula® is a registered trademark of Kern Sibbald.
+ */
+
+#include "fd-plugin-driver.h"
+#include "journal.h"
+#include "cdp-fd.c"
+
+class CdpPluginTest
+{
+ char *_tmpDirPath;
+ char *_pluginPath;
+ char *_jobName;
+ char *_workingPath;
+ char *_spoolPath;
+ char *_jPath;
+ Journal _journal;
+ PluginTestDriver _d;
+
+public:
+
+ void setPaths() {
+#ifndef HAVE_WIN32
+ _pluginPath = (char *) "./cdp-fd.so";
+#else
+ _pluginPath = (char *) "./cdp-fd.dll";
+#endif
+
+ _tmpDirPath = BTU::getTmpPath();
+ _workingPath = BTU::concat(_tmpDirPath, "/working");
+ _spoolPath = BTU::concat(_tmpDirPath, "/spool-dir");
+ _jPath = BTU::concat3(_tmpDirPath, "/", JOURNAL_CLI_FNAME);
+ _jobName = (char *) "my_test_job";
+ }
+
+ void makeTestFolders() {
+ BTU::rmDir(_tmpDirPath);
+ BTU::mkDir(_tmpDirPath);
+ BTU::mkDir(_workingPath);
+ BTU::mkDir(_spoolPath);
+ }
+
+ void logParams() {
+ logParam("Plugin Path ", _pluginPath);
+ logParam("Test Dir ", _tmpDirPath);
+ logParam("Journal File ", _jPath);
+ logParam("Working Dir ", _workingPath);
+ logParam("Spool Dir ", _spoolPath);
+ logParam("Fake Job Name", _jobName);
+ }
+
+ CdpPluginTest() {
+ setupTitle("CdpPluginTest");
+
+ OSDependentInit();
+ this->setPaths();
+ this->makeTestFolders();
+ this->logParams();
+
+ _journal.setJournalPath(_jPath, _spoolPath);
+ _d.setWorkingDir(_workingPath);
+ debug_level = 60;
+ }
+
+ ~CdpPluginTest() {
+// BTU::rmDir(_tmpDirPath);
+ }
+
+private:
+
+ void resetJournal() {
+ BTU::rmFile(_jPath);
+ _journal.setJournalPath(_jPath, _spoolPath);
+ }
+
+ void makeFolderRecord(const char *fpath) {
+ FolderRecord rec;
+ rec.path = bstrdup(fpath);
+ _journal.writeFolderRecord(rec);
+ }
+
+ FileRecord makeTestFile(const char *fname, const char *fcontents) {
+ FileRecord rec;
+ rec.name = BTU::concat3(_tmpDirPath, "/", fname);
+ BTU::mkfile(rec.name, fcontents);
+ rec.encode_attrs();
+
+ POOLMEM *tmp_sname = get_pool_memory(PM_FNAME);
+ Mmsg(tmp_sname, "%s/%d_%s", _spoolPath, rec.mtime, fname);
+ rec.sname = bstrdup(tmp_sname);
+ free_and_null_pool_memory(tmp_sname);
+ BTU::mkfile(rec.sname, fcontents);
+
+ _journal.writeFileRecord(rec);
+ return rec;
+ }
+
+ void verifySavePacket(FileRecord &rec, save_pkt &sp) {
+ POOLMEM *expected_fname = get_pool_memory(PM_FNAME);
+ rec.getBaculaName(expected_fname);
+ BTU::verifyStrings("SavePacket.sname", expected_fname, sp.fname);
+ BTU::verifyInt64("SavePacket.mtime", rec.mtime, (int64_t) sp.statp.st_mtime);
+ free_and_null_pool_memory(expected_fname);
+ }
+
+ void sendBackupEvents() {
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ POOLMEM *command = get_pool_memory(PM_FNAME);
+ Mmsg(command, "cdp: userHome=%s", _tmpDirPath);
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) command);
+ BTU::verifyBRC("Backup userHome param parsing", bRC_OK, rc);
+ free_and_null_pool_memory(command);
+ }
+
+ void simulateBackup(FileRecord &rec, const char *fdata) {
+ struct save_pkt sp;
+ bRC rc = _d.startBackupFile(&sp);
+ BTU::verifyBRC("Start Backup RC", bRC_OK, rc);
+ verifySavePacket(rec, sp);
+
+ _d.openFileIO(O_RDONLY);
+ _d.readFileIO(fdata, strlen(fdata));
+ _d.closeFileIO();
+ _d.endBackupFile();
+ }
+
+ void simulateRestore(const char *fname, const char *fc) {
+ struct restore_pkt rp;
+ rp.ofname = BTU::concat3(_tmpDirPath, "/", fname);
+
+ bRC rc = _d.startRestoreFile();
+ BTU::verifyBRC("Start Restore RC", bRC_Core, rc);
+
+ rc = _d.createFile(&rp);
+ BTU::verifyBRC("Create File RC", bRC_OK, rc);
+ BTU::verifyInt("Create Status", CF_CORE, rp.create_status);
+
+ _d.openFileIO(O_WRONLY);
+ _d.writeFileIO(fc);
+ _d.closeFileIO();
+ _d.endRestoreFile();
+
+ char *fc2 = BTU::readFile(rp.ofname);
+ BTU::verifyStrings("Restored file contents", fc, fc2);
+ }
+
+public:
+ void plugin_should_handle_check_file() {
+ title("Plugin should have checkFile() function implemented");
+
+ subtitle("Plugin is NOT USED by Bacula - should always return bRC_OK");
+ _d.startTest(_pluginPath);
+
+ bRC rc = _d.checkFile("/tmp/foo/test_file.txt");
+ BTU::verifyBRC("Check File RC", bRC_OK, rc);
+
+ rc = _d.checkFile("/tmp/foo/test_file2.txt");
+ BTU::verifyBRC("Check File RC", bRC_OK, rc);
+
+ _d.endTest();
+
+
+ subtitle("Plugin is USED by Bacula - should always return bRC_Seen");
+ _d.startTest(_pluginPath);
+
+ this->sendBackupEvents();
+
+ rc = _d.checkFile("/tmp/foo/test_file.txt");
+ BTU::verifyBRC("Check File RC", bRC_Seen, rc);
+
+ rc = _d.checkFile("/tmp/foo/test_file2.txt");
+ BTU::verifyBRC("Check File RC", bRC_Seen, rc);
+
+ _d.endTest();
+ }
+
+ void plugin_should_handle_backup_cycle() {
+ title("Plugin should handle Backup Cycle");
+
+ makeFolderRecord(_tmpDirPath);
+
+ const char *fc1 = "akfkaklfass";
+ FileRecord r1 = makeTestFile("test_file1.txt", fc1);
+
+ const char *fc2 = "kajeçkfjafkçsjfçlasflsaç";
+ FileRecord r2 = makeTestFile("my_cat.png", fc2);
+
+ const char *fc3 = "jaeaij";
+ FileRecord r3 = makeTestFile("funny_meme.gif", fc3);
+ char *ojc = BTU::readFile(_jPath);
+
+ _d.startTest(_pluginPath);
+
+ this->sendBackupEvents();
+
+ POOLMEM *migratedJournal = get_pool_memory(PM_FNAME);
+ Mmsg(migratedJournal, "%s/%s_0.journal", _workingPath, _jobName);
+ char *mjc = BTU::readFile(migratedJournal);
+ BTU::verifyNotNull("Migrated Journal", mjc);
+ BTU::verifyStrings("Migrated Journal", ojc, mjc);
+
+ this->simulateBackup(r1, fc1);
+ this->simulateBackup(r2, fc2);
+ this->simulateBackup(r3, fc3);
+
+ _d.tryBackupStop();
+ _d.endTest();
+
+ BTU::verifyFileDoesntExist(migratedJournal);
+ }
+
+ void plugin_should_handle_cancel_backup_event() {
+ POOLMEM *newJournal = get_pool_memory(PM_FNAME);
+ Mmsg(newJournal, "%s/%s_0.journal", _workingPath, _jobName);
+
+ title("Plugin should handle cancel Backup event");
+
+ const char *fc1 = "akfkaklfass";
+ FileRecord r1 = makeTestFile("test_file1.txt", fc1);
+
+ const char *fc2 = "kajeçkfjafkçsjfçlasflsaç";
+ FileRecord r2 = makeTestFile("my_cat.png", fc2);
+
+ const char *fc3 = "jaeaij";
+ FileRecord r3 = makeTestFile("funny_meme.gif", fc3);
+
+ _d.startTest(_pluginPath);
+
+ this->sendBackupEvents();
+ this->simulateBackup(r1, fc1);
+ this->simulateBackup(r2, fc2);
+
+ bEvent cancelEvent = { bEventCancelCommand };
+ _d.sendPluginEvent(cancelEvent, NULL);
+
+ _d.endTest();
+
+ BTU::verifyFileExists(newJournal);
+ BTU::rmDir(_workingPath);
+ BTU::mkpath(_tmpDirPath, "working");
+ }
+
+ void plugin_should_handle_restore_cycle() {
+ title("Plugin should handle Restore Cycle");
+
+ _d.startTest(_pluginPath);
+
+ this->simulateRestore("rfile_t1.txt", "kjfsakjfsalk");
+ this->simulateRestore("rfile_t2.txt", "jksakjfsakjfsalkyas7");
+ this->simulateRestore("rfile_t3.txt", "kjfsak");
+
+ _d.endTest();
+ }
+
+ void plugin_should_handle_userhome_param() {
+ title("Plugin should handle \'userhome\' parameter");
+
+ _d.startTest(_pluginPath);
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: userhome=/root");
+ CdpContext *pCtx = (CdpContext *) _d.ctx.pContext;
+ alist *userHomes = &(pCtx->userHomes);
+ alist *journals = &(pCtx->journals);
+ BTU::verifyBRC("User parsing", bRC_OK, rc);
+ BTU::verifyBiggerThan("User Homes", userHomes->size(), 0);
+ BTU::verifyBiggerThan("Journals", journals->size(), 0);
+ BTU::verifyInt64("Sizes should be the same", userHomes->size(), journals->size());
+ BTU::verifyStrings("Expected User Home", "/root", (char *) (*userHomes)[0]);
+
+ _d.endTest();
+ }
+
+ void plugin_should_handle_invalid_userhome_path() {
+ title("Plugin should handle invalid \'userHome\' path");
+
+ _d.startTest(_pluginPath);
+
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: userHome=/non/existent/path");
+ BTU::verifyBRC("userHome parsing", bRC_Error, rc);
+
+ _d.endTest();
+ }
+
+ void plugin_should_handle_user_param() {
+ title("Plugin should handle \'user\' parameter");
+
+ _d.startTest(_pluginPath);
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: user=root");
+ CdpContext *pCtx = (CdpContext *) _d.ctx.pContext;
+ alist *userHomes = &(pCtx->userHomes);
+ alist *journals = &(pCtx->journals);
+ BTU::verifyBRC("User parsing", bRC_OK, rc);
+ BTU::verifyBiggerThan("User Homes", userHomes->size(), 0);
+ BTU::verifyBiggerThan("Journals", journals->size(), 0);
+ BTU::verifyInt64("Sizes should be the same", userHomes->size(), journals->size());
+ BTU::verifyStrings("Expected User Home", "/root", (char *) (*userHomes)[0]);
+
+ _d.endTest();
+ }
+
+ void plugin_should_handle_invalid_user() {
+ title("Plugin should handle invalid \'user\' parameter");
+
+ _d.startTest(_pluginPath);
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: user=this-user-does-not-exit");
+ BTU::verifyBRC("User parsing", bRC_Error, rc);
+
+ _d.endTest();
+ }
+
+ void plugin_should_handle_group_param() {
+ title("Plugin should handle \'group\' parameter");
+
+ _d.startTest(_pluginPath);
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: group=sudo");
+ CdpContext *pCtx = (CdpContext *) _d.ctx.pContext;
+ alist *userHomes = &(pCtx->userHomes);
+ alist *journals = &(pCtx->journals);
+ BTU::verifyBRC("Group parsing", bRC_OK, rc);
+ BTU::verifyBiggerThan("User Homes", userHomes->size(), 0);
+ BTU::verifyBiggerThan("Journals", journals->size(), 0);
+ BTU::verifyInt64("Sizes should be the same", userHomes->size(), journals->size());
+ _d.endTest();
+ }
+
+ void plugin_should_handle_invalid_group() {
+ title("Plugin should handle invalid \'group\' parameter");
+
+ _d.startTest(_pluginPath);
+ _d.setJobName(bstrdup(_jobName));
+ bEvent jobStart = { bEventJobStart };
+ _d.sendPluginEvent(jobStart, NULL);
+
+ bEvent pluginComm = { bEventPluginCommand };
+ bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: group=non-exisent");
+ CdpContext *pCtx = (CdpContext *) _d.ctx.pContext;
+ BTU::verifyBRC("Group parsing", bRC_Error, rc);
+
+ _d.endTest();
+ }
+
+#ifdef HAVE_WIN32
+ void plugin_should_add_drivers_to_vss_snapshot() {
+ title("Plugin should add drivers to VSS Snapshot");
+
+ this->resetJournal();
+ this->makeFolderRecord("A:/FolderA1");
+ this->makeFolderRecord("A:/FolderA2/xyz/jkl");
+ this->makeFolderRecord("D:/FolderD");
+ this->makeFolderRecord("Z:/FolderZ1");
+ this->makeFolderRecord("Z:/FolderZ2/jkl");
+ this->makeFolderRecord("Z:/FolderZ3/abcd/ghi");
+
+ _d.startTest(_pluginPath);
+
+ this->sendBackupEvents();
+
+ char drives[50];
+ bEvent pluginComm = { bEventVssPrepareSnapshot };
+ _d.sendPluginEvent(pluginComm, (void *) drives);
+ BTU::verifyStrings("VSS Drives List", "ADZ", drives);
+
+ _d.endTest();
+ }
+#endif
+};
+
+int main(int argc, char *argv[])
+{
+ CdpPluginTest test;
+ test.plugin_should_handle_check_file();
+ printf("OK\n");
+ test.plugin_should_handle_backup_cycle();
+ printf("OK\n");
+ test.plugin_should_handle_cancel_backup_event();
+ printf("OK\n");
+ test.plugin_should_handle_restore_cycle();
+ printf("OK\n");
+#ifdef HAVE_WIN32
+ test.plugin_should_add_drivers_to_vss_snapshot();
+ printf("OK\n");
+#else
+ test.plugin_should_handle_userhome_param();
+ printf("OK\n");
+ test.plugin_should_handle_invalid_userhome_path();
+ printf("OK\n");
+ test.plugin_should_handle_user_param();
+ printf("OK\n");
+ test.plugin_should_handle_invalid_user();
+ printf("OK\n");
+ test.plugin_should_handle_group_param();
+ printf("OK\n");
+ test.plugin_should_handle_invalid_group();
+ printf("OK\n");
+#endif
+}
+
--- /dev/null
+/*
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2004-2019 Bacula Systems SA
+ All rights reserved.
+
+ The main author of Bacula is Kern Sibbald, with contributions from many
+ others, a complete list can be found in the file AUTHORS.
+
+ Licensees holding a valid Bacula Systems SA license may use this file
+ and others of this release in accordance with the proprietary license
+ agreement provided in the LICENSE file. Redistribution of any part of
+ this release is not permitted.
+
+ Bacula® is a registered trademark of Kern Sibbald.
+*/
+
+#include "folderwatcher.h"
+#include "btestutils.h"
+
+class FolderWatcherTest: public FileChangeHandler
+{
+ char *_tmpDirPath;
+ FolderWatcher *_watcher;
+ alist _notifiedFiles;
+
+ char *cpfile;
+ char *cpfile2;
+
+public:
+ FolderWatcherTest(): _notifiedFiles(100, false) {
+ setupTitle("FolderWatcherTest");
+
+ _tmpDirPath = BTU::getTmpPath();
+ logParam("Tmp Folder", _tmpDirPath);
+
+ BTU::rmDir(_tmpDirPath);
+ BTU::mkpath(_tmpDirPath, "");
+ BTU::mkpath(_tmpDirPath, "test_rmdir1");
+ BTU::mkpath(_tmpDirPath, "a1");
+ BTU::mkpath(_tmpDirPath, "a1/b1");
+ BTU::mkpath(_tmpDirPath, "a1/b1/c1");
+ BTU::mkpath(_tmpDirPath, "a1/b1/c1/test_rmdir2");
+ BTU::mkpath(_tmpDirPath, "a1/b1/c2");
+ BTU::mkpath(_tmpDirPath, "a1/b2");
+ BTU::mkpath(_tmpDirPath, "a1/b2/c1");
+ BTU::mkpath(_tmpDirPath, "a1/b3");
+ BTU::mkpath(_tmpDirPath, "a1/b3/c3");
+ BTU::mkpath(_tmpDirPath, "a2");
+ BTU::mkpath(_tmpDirPath, "a2/b3");
+ BTU::mkpath(_tmpDirPath, "a2/b3/c3");
+
+ char *mvfile = BTU::concat(_tmpDirPath, "/test_mv1.txt");
+ char *delfile = BTU::concat(_tmpDirPath, "/test_del1.txt");
+ char *delfile2 = BTU::concat(_tmpDirPath, "/a1/b3/test_del2.txt");
+ char *touchfile = BTU::concat(_tmpDirPath, "/a1/b2/test_touch1.txt");
+ cpfile = BTU::concat(_tmpDirPath, "/test_cp1.txt");
+ cpfile2 = BTU::concat(_tmpDirPath, "/a1/b3/test_cp2.txt");
+
+ BTU::mkfile(mvfile);
+ BTU::mkfile(delfile);
+ BTU::mkfile(delfile2);
+ BTU::mkfile(touchfile);
+ BTU::mkfile(cpfile);
+ BTU::mkfile(cpfile2);
+
+ _watcher = new FolderWatcher(this);
+ char *err_msg = _watcher->watch(_tmpDirPath);
+
+ if (err_msg != NULL) {
+ printf("%s", err_msg);
+ exit(-1);
+ }
+ }
+
+ ~FolderWatcherTest() {
+ //BTU::rmDir(_tmpDirPath);
+ }
+
+ void onChange(const char *fpath) {
+ _notifiedFiles.append((void *) bstrdup(fpath));
+ }
+
+private:
+
+ void verifyWatcherNotifiedChange(const char *fpath) {
+ time_t start = clock();
+ while (difftime(clock(), start) < 5000) {
+ for (int i = 0; i < _notifiedFiles.size(); ++i) {
+ char *file = (char *) _notifiedFiles.get(i);
+ if (strcmp(file, fpath) == 0) {
+ printf("SUCCESS: notified %s\n\n", fpath);
+ return;
+ }
+ }
+
+ usleep(1000); // 1 ms
+ }
+
+ printf("ERROR: watcher did not notify %s\n", fpath);
+ exit(-1);
+ }
+
+ void verifyWatcherNotifiedNothing() {
+ usleep(300000); // 300 ms
+
+ if (_notifiedFiles.size() > 0) {
+ for (int i = 0; i < _notifiedFiles.size(); ++i) {
+ char *file = (char *) _notifiedFiles.get(i);
+ printf("ERROR: notified %s\n", file);
+ }
+ exit(-1);
+ }
+
+ printf("SUCCESS: no notifications\n\n");
+ }
+
+public:
+
+ void clear() {
+ _notifiedFiles.destroy();
+ }
+
+ //TODO rename
+ void watcher_should_notify_creation_of_new_file() {
+ title("Watcher should notify creation of a new file");
+
+ subtitle("1");
+ char *file = BTU::concat(_tmpDirPath, "/test_file.txt");
+ BTU::mkfile(file);
+ verifyWatcherNotifiedChange(file);
+
+ _notifiedFiles.destroy();
+
+ subtitle("2");
+ char *file2 = BTU::concat(_tmpDirPath, "/a1/b1/test_file2.txt");
+ BTU::mkfile(file2);
+ verifyWatcherNotifiedChange(file2);
+
+ _notifiedFiles.destroy();
+
+ subtitle("3");
+ char *file3 = BTU::concat(_tmpDirPath, "/a2/test_file3.txt");
+ BTU::mkfile(file3);
+ verifyWatcherNotifiedChange(file3);
+
+ _notifiedFiles.destroy();
+
+ subtitle("4");
+ BTU::mkpath(_tmpDirPath, "a2/b3/new_folder");
+ verifyWatcherNotifiedNothing();
+
+ char *file4 = BTU::concat(_tmpDirPath, "/a2/b3/new_folder/test_file4.txt");
+ BTU::mkfile(file4);
+ verifyWatcherNotifiedChange(file4);
+
+ _notifiedFiles.destroy();
+
+ subtitle("5");
+ char *targetFile = BTU::concat(_tmpDirPath, "/a1/b2/copied1.txt");
+ BTU::cpFile(cpfile, targetFile);
+ verifyWatcherNotifiedChange(targetFile);
+
+ _notifiedFiles.destroy();
+
+ subtitle("6");
+ targetFile = BTU::concat(_tmpDirPath, "/a2/b3/c3/copied2.txt");
+ BTU::cpFile(cpfile2, targetFile);
+ verifyWatcherNotifiedChange(targetFile);
+ }
+
+ void watcher_should_notify_nothing_on_file_removal() {
+ title("Watcher should notify nothing on file removal");
+
+ subtitle("1");
+ char *file = BTU::concat(_tmpDirPath, "/test_del1.txt");
+ BTU::rmFile(file);
+ verifyWatcherNotifiedNothing();
+
+ _notifiedFiles.destroy();
+
+ subtitle("2");
+ char *file2 = BTU::concat(_tmpDirPath, "/a1/b3/test_del2.txt");
+ BTU::rmFile(file2);
+ verifyWatcherNotifiedNothing();
+ }
+
+ void watcher_should_notify_changes_on_file() {
+ title("Watcher should notify changes on a file");
+
+#ifndef HAVE_WIN32
+ subtitle("1");
+ char *file1 = BTU::concat(_tmpDirPath, "/a1/b2/test_touch1.txt");
+ BTU::touch(file1);
+ verifyWatcherNotifiedChange(file1);
+
+ _notifiedFiles.destroy();
+
+ subtitle("2");
+ char *file2 = BTU::concat(_tmpDirPath, "/test_mv1.txt");
+ char *file3 = BTU::concat(_tmpDirPath, "/a1/b1/c1/test_mv1_moved.txt");
+ BTU::mvFile(file2, file3);
+ verifyWatcherNotifiedChange(file3);
+#endif
+ }
+
+ void watcher_should_notify_nothing_on_creation_of_new_dir() {
+ title("Watcher should notify nothing on creation of a new directory");
+
+ subtitle("1");
+ BTU::mkpath(_tmpDirPath, "a42");
+ verifyWatcherNotifiedNothing();
+
+ _notifiedFiles.destroy();
+
+ subtitle("2");
+ BTU::mkpath(_tmpDirPath, "a100");
+ verifyWatcherNotifiedNothing();
+
+ _notifiedFiles.destroy();
+
+ BTU::mkpath(_tmpDirPath, "a100/b57");
+ verifyWatcherNotifiedNothing();
+ }
+
+ void watcher_should_notify_nothing_on_dir_removal() {
+ title("Watcher should notify nothing on directory removal");
+
+ subtitle("1");
+ char *dir1 = BTU::concat(_tmpDirPath, "/test_rmdir1");
+ BTU::rmDir(dir1);
+ verifyWatcherNotifiedNothing();
+
+ _notifiedFiles.destroy();
+
+ subtitle("2");
+ char *dir2 = BTU::concat(_tmpDirPath, "/a1/b1/c1/test_rmdir2");
+ BTU::rmDir(dir2);
+ verifyWatcherNotifiedNothing();
+ }
+
+ void watcher_should_not_notify_changes_on_dir_metadata() {
+ title("Watcher should notify nothing on directory metadata changes");
+
+ subtitle("1");
+ char *dir = BTU::concat(_tmpDirPath, "/a1");
+ BTU::touch(dir);
+ verifyWatcherNotifiedNothing();
+ }
+
+ void delete_watcher_should_stop_watch() {
+ title("Deleting watcher should stop watch");
+ delete _watcher;
+ }
+};
+
+int main(int arg, char *argv[])
+{
+ lmgr_init_thread();
+ FolderWatcherTest test;
+ test.watcher_should_notify_creation_of_new_file();
+ test.clear();
+ test.watcher_should_notify_nothing_on_file_removal();
+ test.clear();
+ test.watcher_should_notify_changes_on_file();
+ test.clear();
+ test.watcher_should_notify_nothing_on_creation_of_new_dir();
+ test.clear();
+ test.watcher_should_notify_nothing_on_dir_removal();
+ test.clear();
+ test.watcher_should_not_notify_changes_on_dir_metadata();
+ test.clear();
+}
--- /dev/null
+/*
+ Bacula® - The Network Backup Solution
+
+ Copyright (C) 2004-2019 Bacula Systems SA
+ All rights reserved.
+
+ The main author of Bacula is Kern Sibbald, with contributions from many
+ others, a complete list can be found in the file AUTHORS.
+
+ Licensees holding a valid Bacula Systems SA license may use this file
+ and others of this release in accordance with the proprietary license
+ agreement provided in the LICENSE file. Redistribution of any part of
+ this release is not permitted.
+
+ Bacula® is a registered trademark of Kern Sibbald.
+ */
+
+#include "cdp.h"
+#include "journal.h"
+#include "btestutils.h"
+#include "string.h"
+
+#ifndef HAVE_WIN32
+#include <sys/file.h>
+#endif
+
+/**
+ Test thread used to confirm that the Journal class waits for the
+ Journal File to be available
+ */
+void *thread_journal(void *arg) {
+ printf("PARENT - thread_journal started....\n");
+ Journal *journal = (Journal *) arg;
+ printf("PARENT - Calling beginTransaction()\n");
+ journal->beginTransaction("r");
+ printf("PARENT - beginTransaction() completed\n");
+ return NULL;
+}
+
+
+class JournalTest
+{
+
+ char *_tmpDirPath;
+ char *_jPath;
+ Journal *_journal;
+
+ public:
+ JournalTest() {
+ setupTitle("Journal Tests");
+
+ _tmpDirPath = BTU::getTmpPath();
+ _jPath = BTU::concat(_tmpDirPath, "/cdp-cli.journal");
+ _journal = new Journal();
+
+ logParam("TmpDir", _tmpDirPath);
+ logParam("Journal File", _jPath);
+
+ BTU::rmDir(_tmpDirPath);
+ BTU::mkDir(_tmpDirPath);
+ }
+
+ ~JournalTest() {
+ // BTU::rmDir(_tmpDirPath);
+ }
+
+ private:
+
+ void verifyFileContainsRecord(const char *fname, const FileRecord &rec) {
+ const int SANITY_CHECK = 10000;
+ char *fcontents = BTU::readFile(fname);
+ char expected[SANITY_CHECK];
+ bsnprintf(expected, SANITY_CHECK,
+ "File {" NL
+ "name=%s" NL
+ "sname=%s" NL
+ "mtime=%lld" NL
+ "attrs=%s" NL
+ "}" NL,
+ rec.name,
+ rec.sname,
+ rec.mtime,
+ rec.fattrs);
+ if (strstr(fcontents, expected) == NULL) {
+ printf("ERROR: FileRecord of file %s not found.\n"
+ "FCONTENTS:\n%s\n"
+ "EXPECTED:\n%s\n", rec.name, fcontents, expected);
+ exit(-1);
+ }
+ }
+
+ void verifyFolderNotContainsRecord(const char *fname, const FolderRecord &rec) {
+ const int SANITY_CHECK = 1000000;
+ char *fcontents = BTU::readFile(fname);
+ char expected[SANITY_CHECK];
+ bsnprintf(expected, SANITY_CHECK,
+ "Folder {" NL
+ "path=%s" NL
+ "}" NL,
+ rec.path);
+ if (strstr(fcontents, expected) != NULL) {
+ printf("ERROR: FolderRecord of folder %s not found.", rec.path);
+ exit(-1);
+ }
+ }
+
+ void verifyFolderContainsRecord(const char *fname, const FolderRecord &rec) {
+ const int SANITY_CHECK = 1000000;
+ char *fcontents = BTU::readFile(fname);
+ char expected[SANITY_CHECK];
+ bsnprintf(expected, SANITY_CHECK,
+ "Folder {" NL
+ "path=%s" NL
+ "}" NL,
+ rec.path);
+ if (strstr(fcontents, expected) == NULL) {
+ printf("ERROR: FolderRecord of folder %s not found.", rec.path);
+ exit(-1);
+ }
+ }
+
+ void do_update_settings_test(SettingsRecord &rec) {
+ if (!_journal->writeSettings(rec)) {
+ printf("ERROR: could not write SettingsRecord");
+ exit(-1);
+ }
+ SettingsRecord *read = _journal->readSettings();
+ BTU::verifyNotNull("SettingsRecord", read);
+ BTU::verifyInt64("jversion", rec.journalVersion, read->journalVersion);
+ BTU::verifyInt64("heartbeat", rec.heartbeat, read->heartbeat);
+ BTU::verifyStrings("spooldir", rec.getSpoolDir(), read->getSpoolDir());
+ delete read;
+ delete read;
+ }
+
+ void do_write_record_test(const char* fname) {
+ FileRecord rec;
+ rec.name = BTU::concat3(_tmpDirPath, "/" ,fname);
+ char *tmp = BTU::concat("12345_", fname);
+ rec.sname = BTU::concat3(_tmpDirPath, "/" , tmp);
+ BTU::mkfile(rec.name);
+
+ if (!rec.encode_attrs()) {
+ printf("ERROR: could not encode file attributes: %s", rec.name);
+ exit(-1);
+ }
+
+ if (!_journal->writeFileRecord(rec)) {
+ printf("ERROR: could not write FileRecord: %s", rec.name);
+ exit(-1);
+ }
+ verifyFileContainsRecord(_jPath, rec);
+ }
+
+ void do_write_folder_test(const char* fname) {
+ FolderRecord rec;
+ rec.path = BTU::concat3(_tmpDirPath, "/" ,fname);
+ if (!_journal->writeFolderRecord(rec)) {
+ printf("ERROR: could not write FolderRecord: %s", fname);
+ exit(-1);
+ }
+ verifyFolderContainsRecord(_jPath, rec);
+ }
+
+ void do_read_file_test(const char* fname) {
+ FileRecord *fr = _journal->readFileRecord();
+
+ if(fname != NULL) {
+ char *fpath = BTU::concat3(_tmpDirPath, "/", fname);
+ BTU::verifyStrings("File Path", fpath, fr->name);
+ free(fpath);
+ } else {
+ BTU::verifyNull("FileRecord", fr);
+ }
+
+ delete fr;
+ }
+
+ void do_read_folder_test(const char* folder) {
+ FolderRecord *fr = _journal->readFolderRecord();
+
+ if(fr == NULL && folder != NULL) {
+ printf("ERROR: expected a non-null FolderRecord");
+ exit(-1);
+ } else if(folder != NULL) {
+ char *fpath = BTU::concat3(_tmpDirPath, "/", folder);
+ BTU::verifyStrings("File Path", fpath, fr->path);
+ free(fpath);
+ } else {
+ BTU::verifyNull("FolderRecord", fr);
+ }
+
+ delete fr;
+ }
+
+ void do_remove_folder_test(const char* folder) {
+ FolderRecord rec;
+ rec.path = BTU::concat3(_tmpDirPath, "/" , folder);
+ if (!_journal->removeFolderRecord(rec.path)) {
+ printf("ERROR: could not remove FolderRecord: %s", rec.path);
+ exit(-1);
+ }
+
+ verifyFolderNotContainsRecord(_jPath, rec);
+ }
+
+ public:
+
+ void journal_filerecord_should_encode_and_decode_file_attributes() {
+ title("File Record should encode and decode attributes");
+
+ FileRecord rec;
+ struct stat sbuf;
+
+ rec.name = BTU::concat3(_tmpDirPath, "/", "testde.txt");
+ BTU::mkfile(rec.name, "çasfkas");
+
+ if (!rec.encode_attrs()) {
+ printf("ERROR: could not encode attributes of file %s", rec.name);
+ exit(-1);
+ }
+
+ rec.decode_attrs(sbuf);
+ BTU::verifyInt64("mtime", rec.mtime, (int64_t) sbuf.st_mtime);
+ }
+
+ void journal_should_create_jfile_on_set_path() {
+ title("setJournalPath() should create a new Journal File"
+ " with a default SettingsRecord"
+ " if this File does not exist");
+
+ if (!_journal->setJournalPath(_jPath)) {
+ printf("ERROR: could not set journal path to %s", _jPath);
+ exit(-1);
+ }
+
+ SettingsRecord *rec = _journal->readSettings();
+ BTU::verifyNotNull("SettingsRecord", rec);
+ BTU::verifyInt64("heartbeat", -1, rec->heartbeat);
+ BTU::verifyInt64("jversion", JOURNAL_VERSION, rec->journalVersion);
+ }
+
+ // void journal_should_update_settings_record() {
+ // title("writeSettings() should update Settings Record");
+
+ // char *spath = CDP::spoolDir();
+ // logParam("Test Spool Dir", spath);
+
+ // SettingsRecord r1;
+ // r1.heartbeat = 500000;
+ // r1.journalVersion = 2;
+ // r1.spoolDir = (char *) "/home/foo/bar";
+ // do_update_settings_test(r1);
+
+ // SettingsRecord r2;
+ // r2.heartbeat = 999999;
+ // r2.journalVersion = 40;
+ // do_update_settings_test(r2);
+ // }
+
+ void journal_should_add_new_file_records() {
+ title("Journal should add new File Records");
+
+ do_write_record_test("file_test1.txt");
+ do_write_record_test("file_test2.pdf");
+ do_write_record_test("file_test3.png");
+ do_write_record_test("file_test4.odt");
+ }
+
+ void journal_should_add_new_folder_records() {
+ title("Journal should add new Folder Records");
+
+ do_write_folder_test("folder1");
+ do_write_folder_test("folder2");
+ do_write_folder_test("folder3");
+ }
+
+ void journal_should_add_new_file_records_2() {
+ title("Journal should add new File Records (2)");
+
+ do_write_record_test("file_test5.gif");
+ do_write_record_test("file_test6.cpp");
+ }
+
+ void journal_should_add_new_folder_records_2() {
+ title("Journal should add new Folder Records (2)");
+
+ do_write_folder_test("folder4");
+ do_write_folder_test("folder5");
+ }
+
+ void journal_should_read_all_file_records() {
+ title("Journal should read all File Records");
+
+ _journal->beginTransaction("r");
+ do_read_file_test("file_test1.txt");
+ do_read_file_test("file_test2.pdf");
+ do_read_file_test("file_test3.png");
+ do_read_file_test("file_test4.odt");
+ do_read_file_test("file_test5.gif");
+ do_read_file_test("file_test6.cpp");
+ do_read_file_test(NULL);
+ _journal->endTransaction();
+ }
+
+ void journal_should_read_all_folder_records() {
+ title("Journal should read all Folder Records");
+
+ _journal->beginTransaction("r");
+ do_read_folder_test("folder1");
+ do_read_folder_test("folder2");
+ do_read_folder_test("folder3");
+ do_read_folder_test("folder4");
+ do_read_folder_test("folder5");
+ do_read_folder_test(NULL);
+ _journal->endTransaction();
+ }
+
+ void journal_should_remove_folder_records() {
+ title("Journal should remove Folder Records");
+
+ char *oldJournal = BTU::concat(_tmpDirPath, "/old.journal");
+ BTU::cpFile(_jPath, oldJournal);
+
+ do_remove_folder_test("folder3");
+ do_remove_folder_test("folder4");
+ do_remove_folder_test("folder1");
+ do_remove_folder_test("folder2");
+
+ BTU::rmFile(_jPath);
+ BTU::cpFile(oldJournal, _jPath);
+ }
+
+ void journal_should_migrate_to_new_path() {
+ title("Journal should migrate to New Path");
+
+ char *f1 = BTU::readFile(_jPath);
+ char *newPath = BTU::concat(_tmpDirPath, "/migrated.journal");
+
+ if (!_journal->migrateTo(newPath)) {
+ printf("ERROR: could not migrate Journal to %s", newPath);
+ exit(-1);
+ }
+
+ // Verify that the migrated Journal has the same contents
+ // as the old one
+ char *f2 = BTU::readFile(newPath);
+ BTU::verifyNotNull("Migrated Journal", f2);
+ BTU::verifyStrings("Migrated Journal", f1, f2);
+ free(f1);
+ free(f2);
+
+ // Verify that the old Journal now only have:
+ // 1-) SettingsRecord
+ // 2-) FolderRecords
+ _journal = new Journal();
+ _journal->setJournalPath(_jPath);
+
+ SettingsRecord *rec = _journal->readSettings();
+ BTU::verifyNotNull("SettingsRecord", rec);
+ BTU::verifyInt64("jversion", 1, rec->journalVersion);
+ BTU::verifyInt64("heartbeat", -1, rec->heartbeat);
+
+ _journal->beginTransaction("r");
+ do_read_folder_test("folder1");
+ do_read_folder_test("folder2");
+ do_read_folder_test("folder3");
+ do_read_folder_test("folder4");
+ do_read_folder_test("folder5");
+ do_read_folder_test(NULL);
+ _journal->endTransaction();
+
+ _journal->beginTransaction("r");
+ do_read_file_test(NULL);
+ _journal->endTransaction();
+ }
+
+ void journal_should_handle_errors_on_reading() {
+ title("Journal should handle errors while reading a Record");
+
+ subtitle("extract_val() subroutine should handle invalid key-value pairs");
+ char *val;
+
+ // No '=' character
+ val = _journal->extract_val("noequalssign\n");
+ BTU::verifyNull("extract_val() test", val);
+
+ // No '\n' character
+ val = _journal->extract_val("noendline=true");
+ BTU::verifyNull("extract_val() test", val);
+
+ // No '=' and '\n' character
+ val = _journal->extract_val("no_both");
+ BTU::verifyNull("extract_val() test", val);
+ }
+
+ void journal_should_wait_for_jfile_to_be_available() {
+ title("Journal should wait for Jornal File to be available");
+
+#ifndef HAVE_WIN32
+ pid_t pid;
+ pid = fork();
+
+ if (pid < 0) {
+ printf("ERROR: Could not create Test Process\n");
+ exit(-1);
+ }
+
+ if (pid == 0) {
+ printf("Created Child Process\n");
+ FILE *fp = bfopen(_jPath, "r");
+ int fd = fileno(fp);
+ printf("CHILD - got lock for Journal File\n");
+ flock(fd, LOCK_EX);
+ sleep(3);
+ printf("CHILD - released lock for Journal File\n");
+ fclose(fp);
+ exit(0);
+ } else {
+ sleep(1);
+ pthread_t jthread;
+ pthread_create(&jthread, NULL, thread_journal, (void *) _journal);
+ if (_journal->hasTransaction) {
+ printf("ERROR: Journal didn't respect the File Lock \n");
+ exit(-1);
+ }
+ pthread_join(jthread, NULL);
+ if (!_journal->hasTransaction) {
+ printf("ERROR: Journal didn't have a transaction after the Journal File was available \n");
+ exit(-1);
+ }
+ }
+#else
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&pi, sizeof(pi));
+
+ POOLMEM *_jPathWrapper = get_pool_memory(PM_FNAME);
+ Mmsg(_jPathWrapper, "c:\\Program Files\\Bacula\\win_test_process.exe \"%s\"", _jPath);
+ const size_t wsize = (strlen(_jPathWrapper) + 1) * 2;
+ wchar_t *wjpath = new wchar_t[wsize];
+ mbstowcs(wjpath, (char *) _jPathWrapper, wsize);
+
+ if(!CreateProcess(NULL, // No module name (use command line)
+ wjpath, // Command line
+ NULL, // Process handle not inheritable
+ NULL, // Thread handle not inheritable
+ FALSE, // Set handle inheritance to FALSE
+ 0, // No creation flags
+ NULL, // Use parent's environment block
+ NULL, // Use parent's starting directory
+ &si, // Pointer to STARTUPINFO structure
+ &pi ) // Pointer to PROCESS_INFORMATION structure
+ )
+ {
+ printf( "CreateProcess failed (%lu).\n", GetLastError());
+ return;
+ }
+
+ sleep(1);
+ _journal->beginTransaction("r");
+ if (!_journal->hasTransaction) {
+ printf("ERROR: Journal didn't have a transaction after the Journal File was available \n");
+ exit(-1);
+ }
+
+ // Wait until child process exits.
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ // Close process and thread handles.
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+#endif
+ }
+
+};
+
+int main(int argc, char *argv[])
+{
+ lmgr_init_thread();
+ JournalTest test;
+ test.journal_filerecord_should_encode_and_decode_file_attributes();
+ printf("OK\n");
+ test.journal_should_create_jfile_on_set_path();
+ printf("OK\n");
+ //TODO fix update settings;
+ //test.journal_should_update_settings_record();
+ test.journal_should_add_new_file_records();
+ printf("OK\n");
+ test.journal_should_add_new_folder_records();
+ printf("OK\n");
+ test.journal_should_add_new_file_records_2();
+ printf("OK\n");
+ test.journal_should_add_new_folder_records_2();
+ printf("OK\n");
+ test.journal_should_read_all_file_records();
+ printf("OK\n");
+ test.journal_should_read_all_folder_records();
+ printf("OK\n");
+ test.journal_should_remove_folder_records();
+ printf("OK\n");
+ test.journal_should_migrate_to_new_path();
+ printf("OK\n");
+ test.journal_should_handle_errors_on_reading();
+ printf("OK\n");
+ test.journal_should_wait_for_jfile_to_be_available();
+ printf("OK\n");
+}
--- /dev/null
+#include "bacula.h"
+
+static BOOL
+file_size (HANDLE h, DWORD * lower, DWORD * upper)
+{
+ *lower = GetFileSize (h, upper);
+ return 1;
+}
+
+/* LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. */
+# ifndef LOCKFILE_FAIL_IMMEDIATELY
+# define LOCKFILE_FAIL_IMMEDIATELY 1
+# endif
+
+/* Acquire a lock. */
+static BOOL
+do_lock (HANDLE h, int non_blocking, int exclusive)
+{
+ BOOL res;
+ DWORD size_lower, size_upper;
+ OVERLAPPED ovlp;
+ int flags = 0;
+
+ /* We're going to lock the whole file, so get the file size. */
+ res = file_size (h, &size_lower, &size_upper);
+ if (!res)
+ return 0;
+
+ /* Start offset is 0, and also zero the remaining members of this struct. */
+ memset (&ovlp, 0, sizeof ovlp);
+
+ if (non_blocking)
+ flags |= LOCKFILE_FAIL_IMMEDIATELY;
+ if (exclusive)
+ flags |= LOCKFILE_EXCLUSIVE_LOCK;
+
+ return LockFileEx (h, flags, 0, size_lower, size_upper, &ovlp);
+}
+
+/* Unlock reader or exclusive lock. */
+static BOOL
+do_unlock (HANDLE h)
+{
+ int res;
+ DWORD size_lower, size_upper;
+
+ res = file_size (h, &size_lower, &size_upper);
+ if (!res)
+ return 0;
+
+ return UnlockFile (h, 0, 0, size_lower, size_upper);
+}
+
+int main(int argc, char *argv[])
+{
+ printf("Created Child Process\n");
+ FILE *fp = bfopen(argv[1], "r");
+
+ if (!fp) {
+ printf("ERROR: could not open file %s\n", argv[1]);
+ exit(-1);
+ }
+
+ int fd = fileno(fp);
+ HANDLE handle = (HANDLE) _get_osfhandle (fd);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ printf("ERROR: could not get File Handle: %s\n", argv[1]);
+ exit(-1);
+ }
+
+ if (!do_lock(handle, 1, 1)) {
+ printf("ERROR: could not lock file: %s\n", argv[1]);
+ exit(-1);
+ }
+
+ printf("CHILD - got lock for Journal File\n");
+ sleep(3);
+
+ if (!do_unlock(handle)) {
+ printf("ERROR: could not lock file: %s\n", argv[1]);
+ exit(-1);
+ }
+
+ printf("CHILD - released lock for Journal File\n");
+ fclose(fp);
+ return 0;
+}
--- /dev/null
+#!/bin/sh
+
+export REGRESS_DEBUG=1
+. scripts/functions
+
+TestName="cdp-client-tests.sh"
+Path=${cwd}
+CdpClientPath=$Path/build/src/tools/cdp-client/cdp-client
+TmpDir="$Path/tmp"
+SpoolDir=$TmpDir/"spool-dir"
+JournalFile=$TmpDir/".bcdp-cli.journal"
+WatchedDir1=$TmpDir/"folder1"
+WatchedDir2=$TmpDir/"folder2"
+
+make -C $Path/build/src/tools/cdp-client/ install > /dev/null
+
+start_test
+
+run_cdp_client()
+{
+Args="-d 999 -s $SpoolDir -j $TmpDir -f $WatchedDir1 -f $WatchedDir2"
+ if [ -f "$CdpClientPath" ]
+ then
+ $CdpClientPath $Args > $TmpDir/"cdp.log" &
+ cdppid=$!
+ trap "kill $cdppid" EXIT TERM INT
+ else
+ print_debug "CDP Client not found: $CdpClientPath. Please, make the binary."
+ fi
+sleep 0.4
+}
+
+mkfile_and_wait()
+{
+echo 'abcde' > $1
+sleep 0.4
+}
+
+mkdir_and_wait()
+{
+mkdir -p $1
+sleep 0.4
+}
+
+touch_and_wait()
+{
+touch $1
+sleep 0.4
+}
+
+cp_and_wait()
+{
+cp $1 $2
+sleep 0.4
+}
+
+mv_and_wait()
+{
+mv $1 $2
+sleep 0.4
+}
+
+verify_spooldir_contains_file()
+{
+print_debug "Verifying if Spool Dir contains: $1"
+FoundFile=$(find $SpoolDir | grep -o "$1" | wc -l)
+if [ "$FoundFile" -eq "1" ]; then
+ print_debug "OK"
+else
+ print_debug "FAILED"
+ estat=1
+fi
+}
+
+verify_journal_contains_record()
+{
+print_debug "Verifying if Journal File contains File Record"
+JournalHasFile=$(cat $JournalFile | grep "name=$1" | wc -l)
+if [ "$JournalHasFile" -eq "1" ]; then
+ print_debug "OK"
+else
+ print_debug "FAILED"
+ estat=1
+fi
+}
+
+rm -rf $TmpDir
+mkdir $TmpDir
+mkdir $WatchedDir1
+mkdir $WatchedDir2
+SedTestFile=$WatchedDir2/"sed_test.txt"
+mkfile_and_wait $SedTestFile
+TouchTestFile=$WatchedDir2/"touch_test.txt"
+
+run_cdp_client
+
+print_debug "TEST 1: CDP Client should detect creation of a new file:"
+
+TestFile1=$WatchedDir1/"test1.txt"
+print_debug "EXAMPLE 1: Creation of file $TestFile1"
+mkfile_and_wait $TestFile1
+verify_spooldir_contains_file "[0-9]\{10\}_test1.txt"
+verify_journal_contains_record "$TestFile1"
+
+TestFile2=$WatchedDir1/"test2.txt"
+print_debug "EXAMPLE 2: Creation of file $TestFile2"
+mkfile_and_wait $TestFile2
+verify_spooldir_contains_file "[0-9]\{10\}_test2.txt"
+verify_journal_contains_record "$TestFile2"
+
+NewPath=$WatchedDir2/"my/new/path"
+TestFile3=$NewPath/"test3.txt"
+print_debug "EXAMPLE 3: Creation of file $TestFile3 after creation of new path $NewPath"
+
+mkdir_and_wait $NewPath
+mkfile_and_wait $TestFile3
+verify_spooldir_contains_file "[0-9]\{10\}_test3.txt"
+verify_journal_contains_record "$TestFile3"
+
+TestFileCopy=$WatchedDir2/"test1_copy.txt"
+print_debug "EXAMPLE 4: Copy of file $TestFile1 into $TestFileCopy"
+cp_and_wait $TestFile1 $TestFileCopy
+verify_spooldir_contains_file "[0-9]\{10\}_test1_copy.txt"
+verify_journal_contains_record $TestFileCopy
+
+print_debug "---------------------------------------------------------------------------"
+print_debug "TEST 2: CDP Client should detect file changes:"
+
+TestFile1=$WatchedDir2/"test_mv.gif"
+TestFileMove=$WatchedDir1/"test_mv_renamed.gif"
+print_debug "EXAMPLE 1: Move file $TestFile1 into $TestFileMove"
+mkfile_and_wait $TestFile1
+mv_and_wait $TestFile1 $TestFileMove
+verify_spooldir_contains_file "[0-9]\{10\}_test_mv_renamed.gif"
+verify_journal_contains_record $TestFileMove
+
+print_debug "EXAMPLE 2: Sed file $SedTestFile"
+sed -i "$ a #newline" $SedTestFile
+sleep 0.4
+verify_spooldir_contains_file "[0-9]\{10\}_sed_test.txt"
+verify_journal_contains_record $SedTestFile
+
+print_debug "EXAMPLE 3: Touch on file $TouchTestFile"
+mkfile_and_wait $TouchTestFile
+touch_and_wait $TouchTestFile
+verify_spooldir_contains_file "[0-9]\{10\}_touch_test.txt"
+verify_journal_contains_record $TouchTestFile
+
+end_test
--- /dev/null
+#!/bin/sh
+
+#export REGRESS_DEBUG=1
+
+TestName="cdp-plugin-test"
+JobName="PluginCdpTest"
+. scripts/functions
+UserHome="$tmp/testUserHome"
+WatchedDir="$UserHome/watched-dir"
+SpoolDir="$UserHome/spool-dir"
+RestoreDir="$tmp/brestores"
+
+make_cdp_plugin()
+{
+(cd $src/src/plugins/fd; make cdp)
+cp -a build/src/plugins/fd/.libs/cdp-fd.so $bin/plugins
+}
+
+write_tests_bcommands()
+{
+cat <<END_OF_DATA >$tmp/bconcmds
+@output /dev/null
+messages
+@$out $tmp/log_backup.out
+setdebug level=4 storage=File1
+label volume=TestVolume001 storage=File1 pool=Default slot=1 drive=0
+show job=$JobName
+run job=$JobName yes
+status storage=File1
+@sleep 1
+@sleep 1
+wait
+messages
+list files jobid=1
+@#
+@# now do a restore
+@#
+@$out $tmp/log_restore.out
+setdebug level=4 storage=File1
+restore where=$RestoreDir select all done
+yes
+wait
+messages
+quit
+END_OF_DATA
+}
+
+create_journal()
+{
+cat <<END_OF_DATA > $UserHome/.bcdp-cli.journal
+Settings {
+spooldir=$SpoolDir
+heartbeat=-1
+jversion=1
+}
+Folder {
+path=$WatchedDir
+}
+File {
+name=$WatchedDir/test_file1
+sname=$SpoolDir/1553697755_test_file1
+atime=1553697755
+attrs=gE VBM6 IGk B Po Po A b BAA I Bcm4vb Bcm4vb Bcm4vb A A A
+}
+File {
+name=$WatchedDir/test_file2
+sname=$SpoolDir/1553697777_test_file2
+atime=1553697777
+attrs=gE VBM6 IGk B Po Po A b BAA I Bcm4vb Bcm4vb Bcm4vb A A A
+}
+File {
+name=$WatchedDir/test_file3
+sname=$SpoolDir/1553697799_test_file3
+atime=1553697799
+attrs=gE VBM6 IGk B Po Po A b BAA I Bcm4vb Bcm4vb Bcm4vb A A A
+}
+END_OF_DATA
+}
+
+create_testfiles()
+{
+#If you change the file size, remember to change it's attributes as well
+echo "kflasflsalfsaklsaffsfasfsa" > $WatchedDir/test_file1
+echo "kflasflsalfsaklsaffsfasfsa" > $SpoolDir/1553697755_test_file1
+
+echo "kflasflsalf18sdakdjsakl121" > $WatchedDir/test_file2
+echo "kflasflsalf18sdakdjsakl121" > $SpoolDir/1553697777_test_file2
+
+echo "kflasa9s6dsa968dsa986d1289" > $WatchedDir/test_file3
+echo "kflasa9s6dsa968dsa986d1289" > $SpoolDir/1553697799_test_file3
+}
+
+verify_backup_ok()
+{
+nb=`grep 'Backup OK' $tmp/log_backup.out | wc -l`
+if [ $nb -lt 1 ]; then
+ print_debug "ERROR: should have found Backup OK message"
+ bstat=1
+fi
+}
+
+verify_restore_ok()
+{
+nb=`grep 'Restore OK' $tmp/log_restore.out | wc -l`
+if [ $nb -lt 1 ]; then
+ print_debug "ERROR: should have found Restore OK message"
+ bstat=1
+fi
+
+backup_count=`find $UserHome -type f | wc -l`
+#Do not count the Journal, which was not backed up
+backup_count=$((backup_count-1))
+restore_count=`find $RestoreDir$UserHome -type f | wc -l`
+
+if [ $backup_count != $restore_count ]; then
+ print_debug "ERROR: File count mismatch. Backup count: $backup_count. Restore Count: $restore_count"
+fi
+}
+
+scripts/cleanup
+scripts/copy-cdp-plugin-confs
+
+start_test
+
+make_cdp_plugin
+mkdir $UserHome
+mkdir $SpoolDir
+mkdir $WatchedDir
+create_journal
+create_testfiles
+
+write_tests_bcommands
+run_bacula
+stop_bacula
+
+verify_backup_ok
+verify_restore_ok
+
+end_test
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2000-2019 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+# CDP Unit Tests
+#
+
+. scripts/functions
+
+BIN_DIR="$unitsrc/cdp/bin"
+
+exec_bin()
+{
+LOGFILE=$tmp/$1.log
+(cd $BIN_DIR; ./$1 > $LOGFILE)
+
+nb=`grep 'ERROR' $LOGFILE | wc -l`
+if [ $nb -gt 0 ]; then
+ print_debug "ERROR: One or more tests failed."
+ exit 1
+fi
+}
+
+(cd $src/src/plugins/fd; make cdp)
+(cd $unitsrc/cdp; make)
+cp -a $src/src/plugins/fd/.libs/cdp-fd.so $unitsrc/cdp/bin
+
+exec_bin "journal-test"
+exec_bin "folderwatcher-test"
+exec_bin "backupservice-test"
+exec_bin "cdp-plugin-test"