From: Henrique Date: Tue, 24 Nov 2020 16:05:34 +0000 (+0100) Subject: BEE Backport CDP Plugin X-Git-Tag: Release-11.3.2~109 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4ed6e4335180544ed014771c24cceaf7ad862de;p=thirdparty%2Fbacula.git BEE Backport CDP Plugin Author: Henrique Date: Mon Nov 18 23:46:16 2019 -0300 cdp: added 'group' Plugin Parameter Author: Henrique Date: Wed Nov 13 02:23:37 2019 -0300 cdp: added plugin param 'user' and fixed segfault on get_user_home_directory(...) Author: Henrique Date: Mon Nov 11 08:54:13 2019 -0300 cdp: fix 5494 about a crash when a invalid userHome param is specified Author: Henrique Date: Fri Jul 26 15:52:21 2019 -0300 cdp-plugin: tweak debug messages Author: Henrique Date: Fri Jul 26 15:17:22 2019 -0300 cdp-plugin: tweak Journal Debug Level Author: Henrique Date: Fri Jul 26 14:51:09 2019 -0300 cdp-plugin: tweak Author: Henrique Date: Wed Jul 24 15:48:35 2019 -0300 cdp-plugin: tweak Author: Henrique Date: Tue Jul 16 21:32:04 2019 -0300 cdp-plugin: fixed Unit Tests for Windows Author: Henrique Date: Mon Jul 15 22:13:51 2019 -0300 cdp-fd-plugin: 1) Fixed Unit Tests 2) Fixed problem of double free during backup (Settings Record Spool Dir) 3) Small refactoring Author: Eric Bollengier Date: Wed Sep 25 11:44:09 2019 +0200 cdp: Fix issue with context initialization Author: Henrique Date: Fri Jun 21 11:26:01 2019 -0300 cdp: Fix #5085 about wrong windows backlashes Author: Henrique Date: Wed May 22 04:20:58 2019 -0300 cdp-fd-plugin: changed the name of backed up files to a readable date format Author: Henrique Date: Wed May 22 03:44:45 2019 -0300 cdp: fix #5066 about restore error on Windows Author: Henrique Date: Mon May 20 00:46:24 2019 -0300 cdp-client: tweak debug messages Author: Henrique Date: Sun May 19 23:17:04 2019 -0300 cdp: fix about file copy on Windows not working (Windows was keeping the file lock) Author: Henrique Date: Thu May 16 01:25:56 2019 -0300 cdp: Fix #5049 about file change not being notified Author: Henrique Date: Tue May 14 13:33:38 2019 -0300 cdp-fd-plugin: added compatibility with Windows Author: Henrique Date: Wed May 8 00:13:20 2019 -0300 cdp-fd-plugin: changed name pattern of the file that's backed up by bacula Author: Henrique Date: Thu May 2 21:19:33 2019 -0300 cdp: Fix #4991 about userHome usage Author: Henrique Date: Tue Apr 30 20:10:02 2019 -0300 cdp-client: finished journal implementation and unit tests for Windows Author: Henrique Date: Thu Apr 25 22:58:24 2019 -0300 cdp-client: added journal tests for Windows Author: Henrique Date: Tue Apr 16 15:46:08 2019 -0300 cdp-fd-plugin: fixed userHome plugin parameter Author: Eric Bollengier Date: Mon Apr 8 23:01:24 2019 +0200 cdp: Fix backport Author: Henrique Date: Mon Apr 8 10:48:34 2019 -0300 cdp-client: removed Qt Dependencies from the BackupService and the FolderWatcher Author: Henrique Date: Thu Apr 4 22:15:00 2019 -0300 cdp: Save the spool directory inside the journal. Configure the FileSet with the journal 1) cdp-client: Changed BackupService to save the Spool Directory inside the Journal 2) cdp-fd-plugin: Added code to adapt the FileSet based on the Journal's watched folders and the Spool Directory Author: Henrique Faria Date: Tue Apr 2 09:59:59 2019 +0200 cdp: Add CDP File Daemon plugin Author: Henrique Date: Mon Apr 1 14:37:05 2019 -0300 cdp-client: temporary #ifndefs in order to not break compilation for Windows Author: Henrique Date: Mon Apr 1 11:03:40 2019 -0300 cdp-client: added Qt4 connect() compatibility Author: Henrique Date: Mon Apr 1 10:22:39 2019 -0300 cdp-client: fixed Windows compilation Author: Henrique Date: Fri Mar 29 11:32:05 2019 -0300 tray-monitor: added cdp-client Author: Henrique Date: Thu Mar 28 17:20:33 2019 -0300 cdp-client: tweak UI text and code refactor Author: Henrique Date: Thu Mar 28 10:45:25 2019 -0300 cdp-fd-plugin: fixed compilation Author: Henrique Date: Thu Mar 21 09:10:04 2019 +0100 android: Update documentation and build scripts Author: Henrique Date: Wed Mar 20 22:56:37 2019 -0300 tray-monitor: added CDP Client Author: Henrique Date: Wed Mar 20 15:26:27 2019 -0300 cdp: Fixed Journal endTransaction() method Author: Henrique Date: Wed Mar 20 15:11:59 2019 -0300 cdp: Added destructors to FileRecord and FolderRecord Author: Henrique Date: Wed Mar 20 14:28:39 2019 -0300 cdp-client: fixed variable initialization of Journal Class Author: Henrique Date: Wed Mar 20 14:14:42 2019 -0300 cdp: 1-) Fixed variable initialization from Record Classes 2-) Fixed tests Author: Henrique Date: Wed Mar 20 12:48:34 2019 -0300 cdp-fd-plugin: changed createFile() function to let Bacula handle restore Author: Henrique Date: Tue Mar 19 22:58:02 2019 -0300 cdp-fd-plugin: tweak Author: Henrique Date: Tue Mar 19 20:31:03 2019 -0300 cdp-client: added command-line argument: debug level (-d) Author: Henrique Date: Tue Mar 19 18:27:16 2019 -0300 cdp-client: Added a Journal Version to the Settings Record Author: Henrique Date: Tue Mar 19 17:17:55 2019 -0300 cdp-client: upgraded command-line arguments 1) Created gui and non-gui mode (arg '-c') 2) Created optional arguments for Journal Path and Spool Dir Also, small refactor on backupservice. Author: Henrique Date: Mon Mar 18 21:12:56 2019 -0300 cdp-client: fixed memory leaks (FD Plugin) Author: Henrique Date: Mon Mar 18 21:05:51 2019 -0300 cdp-client: fixed memory leaks Author: Henrique Date: Mon Mar 18 19:49:39 2019 -0300 cdp-client: fixed error when copying big files to Spool Dir Author: Henrique Date: Sun Mar 17 14:07:05 2019 -0300 cdp-client: Fixed Bugs: 1) Watch from root would create invalid paths 2) Fixed INotify error ENOENT when the path was valid Author: Henrique Date: Sat Mar 16 18:56:35 2019 -0300 cdp-client: Added Error Dialog for errors with the FolderWatcher Author: Henrique Date: Thu Mar 14 19:17:17 2019 -0300 cdp-client: changed FolderWatcher implementation to use INotify Author: Henrique Date: Wed Mar 13 18:15:11 2019 -0300 cdp-client: tweak small fix Author: Henrique Date: Wed Mar 13 16:23:44 2019 -0300 cdp-client: created GUI with add / remove folders functions Author: Henrique Date: Wed Mar 13 16:02:32 2019 -0300 cdp-client: created removeFolderRecord() function in the Journal Author: Henrique Date: Tue Mar 12 00:07:42 2019 -0300 cdp-client: fixed CDP FD Plugin after testing its execution with Bacula Author: Henrique Date: Sun Mar 10 20:47:50 2019 -0300 cdp-client: tweak small renaming Author: Henrique Date: Fri Mar 8 03:32:17 2019 -0300 cdp-client: changed journal path in command-line client Author: Henrique Date: Fri Mar 8 03:10:40 2019 -0300 cdp-fd-plugin: 1-) Created Plugin Parameter "userHome" 2-) Fixed memory leaks 3-) Code refactoring Author: Henrique Date: Thu Mar 7 12:16:36 2019 -0300 cdp-client: fixed checkFile() on FD Plugin Author: Henrique Date: Thu Mar 7 00:49:05 2019 -0300 cdp-client: FD Plugin now backups files from Spool Dir Author: Henrique Date: Wed Mar 6 10:27:00 2019 -0300 tray-monitor: tweak (removed auto-generated files) Author: Henrique Date: Wed Mar 6 00:02:06 2019 -0300 cdp-client: 1-) Added flock() to Journal calls 2-) Added new Records: SettingsRecord and FolderRecord 3-) Small refactors on the code Author: Henrique Date: Sun Mar 3 16:47:46 2019 -0300 tray-monitor: fixed broken desktop compilation Author: Henrique Date: Mon Mar 4 12:48:20 2019 -0300 cdp-client: added flock() to Journal calls Author: Henrique Date: Mon Mar 4 11:48:19 2019 -0300 cdp-client: improved Journal Author: Henrique Date: Thu Feb 28 16:47:35 2019 -0300 cdp-client: tweak Author: Henrique Date: Wed Feb 27 22:12:43 2019 -0300 cdp-client: tweak Author: Henrique Date: Wed Feb 27 19:17:37 2019 -0300 cdp-client: tweak Author: Henrique Date: Wed Feb 27 18:02:40 2019 -0300 cdp-client: tweak Author: Henrique Date: Tue Feb 26 12:07:24 2019 -0300 cdp-client: tweak Author: Henrique Date: Tue Feb 26 11:38:45 2019 -0300 cdp-client: tweak Author: Henrique Date: Mon Feb 25 22:30:19 2019 -0300 cdp-client: created cdp-fdp-plugin (restore part) Author: Henrique Date: Fri Feb 22 03:07:45 2019 -0300 cdp-client: created cdp-fdp-plugin (backup part) Author: Henrique Date: Sat Feb 16 14:51:44 2019 -0200 cdp-client: created client executable Author: Henrique Date: Thu Feb 14 20:58:29 2019 -0200 cdp-client: added backupservice Author: Henrique Date: Wed Feb 13 13:54:58 2019 -0200 cdp-client: added journal Author: Henrique Date: Wed Feb 13 13:53:20 2019 -0200 cdp-client: tweak folder watcher Author: Henrique Date: Fri Feb 8 12:43:34 2019 -0200 cdp-client: created folder watcher --- diff --git a/bacula/src/plugins/fd/cdp-fd.c b/bacula/src/plugins/fd/cdp-fd.c new file mode 100644 index 000000000..0d197362f --- /dev/null +++ b/bacula/src/plugins/fd/cdp-fd.c @@ -0,0 +1,587 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2022 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#include "bacula.h" +#include "fd_plugins.h" +#include "fd_common.h" +#include "lib/cmd_parser.h" +#include "lib/mem_pool.h" +#include "findlib/bfile.h" +#include "journal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PLUGIN_LICENSE "AGPLv3" +#define PLUGIN_AUTHOR "Henrique Faria" +#define PLUGIN_DATE "February 2019" +#define PLUGIN_VERSION "0.1" +#define PLUGIN_DESCRIPTION "CDP Plugin" + +#ifdef HAVE_WIN32 +#define CONCAT_PATH "%s\\%s" +#define WORKING_JOURNAL_TEMPLATE "%s\\%s_%d.journal" +#else +#define CONCAT_PATH "%s/%s" +#define WORKING_JOURNAL_TEMPLATE "%s/%s_%d.journal" +#endif + +/* Forward referenced functions */ +static bRC newPlugin(bpContext *ctx); +static bRC freePlugin(bpContext *ctx); +static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value); +static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp); +static bRC endBackupFile(bpContext *ctx); +static bRC pluginIO(bpContext *ctx, struct io_pkt *io); +static bRC startRestoreFile(bpContext *ctx, const char *cmd); +static bRC createFile(bpContext *ctx, struct restore_pkt *rp); +static bRC endRestoreFile(bpContext *ctx); +static bRC checkFile(bpContext *ctx, char *fname); + +/* Pointers to Bacula functions */ +static bFuncs *bfuncs = NULL; +static bInfo *binfo = NULL; + +/* Backup Variables */ +static char *working = NULL; + +static pInfo pluginInfo = { + sizeof(pluginInfo), + FD_PLUGIN_INTERFACE_VERSION, + FD_PLUGIN_MAGIC, + PLUGIN_LICENSE, + PLUGIN_AUTHOR, + PLUGIN_DATE, + PLUGIN_VERSION, + PLUGIN_DESCRIPTION +}; + +static pFuncs pluginFuncs = { + sizeof(pluginFuncs), + FD_PLUGIN_INTERFACE_VERSION, + + /* Entry points into plugin */ + newPlugin, /* new plugin instance */ + freePlugin, /* free plugin instance */ + NULL, + NULL, + handlePluginEvent, + startBackupFile, + endBackupFile, + startRestoreFile, + endRestoreFile, + pluginIO, + createFile, + NULL, + checkFile, + NULL, /* No ACL/XATTR */ + NULL, /* No Restore file list */ + NULL /* No checkStream */ +}; + +static int DBGLVL = 50; + +class CdpContext: public SMARTALLOC +{ +public: + bpContext *ctx; + + /** Used by both Backup and Restore Cycles **/ + BFILE fd; + POOLMEM *fname; + bool is_in_use; + + /** Used only by the Backup Cycle **/ + POOLMEM *clientJPath; + POOLMEM *jobJPath; + POOLMEM *drivesList; // Windows only + char *jobName; + + bool accurate_warning; + bool started_backup; + bool canceled; + alist userHomes; + alist journals; + int jIndex; + cmd_parser parser; + Journal *journal; + + CdpContext(bpContext *actx): + ctx(actx), fname(NULL), is_in_use(false), clientJPath(NULL), + jobJPath(NULL), drivesList(NULL), jobName(NULL), accurate_warning(false), + started_backup(false), canceled(false), + userHomes(100, owned_by_alist), journals(100, not_owned_by_alist), jIndex(0) + { + fname = get_pool_memory(PM_FNAME); + clientJPath = get_pool_memory(PM_FNAME); + jobJPath = get_pool_memory(PM_FNAME); +#ifdef HAVE_WIN32 + drivesList = get_pool_memory(PM_FNAME); + *drivesList = 0; +#endif + *fname = *clientJPath = *jobJPath = 0; + }; + + /** Methods called during Backup */ + void migrateJournal() { + char *uh; + int i = 0; + + foreach_alist(uh, &userHomes) { + Journal *j = new Journal(); + Mmsg(clientJPath, CONCAT_PATH, uh, JOURNAL_CLI_FNAME); + j->setJournalPath(clientJPath); + + Mmsg(jobJPath, WORKING_JOURNAL_TEMPLATE, working, jobName, i); + j->migrateTo(jobJPath); + journals.append(j); + i++; + } + }; + + bool handleBackupCommand(bpContext *ctx, char *cmd) { + int i; + POOLMEM *userHome; + parser.parse_cmd(cmd); + for (i = 1; i < parser.argc ; i++) { + + if (strcasecmp(parser.argk[i], "userhome") == 0 && parser.argv[i]) { + userHome = get_pool_memory(PM_FNAME); + pm_strcpy(userHome, parser.argv[i]); + struct stat sp; + + if (stat(userHome, &sp) != 0) { + Jmsg(ctx, M_ERROR, _("Parameter userhome not found: %s\n"), userHome); + return false; + } + + if (!S_ISDIR(sp.st_mode)) { + Jmsg(ctx, M_ERROR, _("Paramater userhome is not a directory: %s\n"), userHome); + return false; + } + + Dmsg(ctx, DBGLVL, "User Home: %s\n", userHome); + userHomes.append(bstrdup(userHome)); + free_and_null_pool_memory(userHome); + } else if (strcasecmp(parser.argk[i], "user") == 0 && parser.argv[i]) { + userHome = get_pool_memory(PM_FNAME); + int rc = get_user_home_directory(parser.argv[i], userHome); + + if (rc != 0) { + Jmsg(ctx, M_ERROR, _("User not found in the system: %s\n"), parser.argv[i]); + return false; + } + + userHomes.append(bstrdup(userHome)); + Dmsg(ctx, DBGLVL, "User Home: %s\n", userHome); + free_and_null_pool_memory(userHome); + return true; + } else if (strcasecmp(parser.argk[i], "group") == 0 && parser.argv[i]) { + int rc = get_home_directories(parser.argv[i], &userHomes); + + if (rc != 0) { + return false; + } + + return true; + } else { + Jmsg(ctx, M_ERROR, _("Can't analyse plugin command line %s\n"), cmd); + return false; + } + } + + return true; + }; + + FileRecord *nextRecord() { + if (canceled) { + if (journal) { + journal->endTransaction(); + } + return NULL; + } + + if (!started_backup) { + if (jIndex >= journals.size()) { + return NULL; + } + + journal = (Journal *) journals[jIndex]; + + if (!journal->beginTransaction("r")) { + return NULL; + } + + started_backup = true; + } + + FileRecord *fc = journal->readFileRecord(); + + if (fc == NULL) { + journal->endTransaction(); + started_backup = false; + unlink(journal->_jPath); + Dmsg(ctx, DBGLVL, "No more files to backup. Deleting journal: %s\n", journal->_jPath); + delete(journal); + jIndex++; + } + + return fc; + }; + + ~CdpContext() { + if (journal) { + // Clear any possible pending transaction (e.g canceled job) + journal->endTransaction(); + canceled = true; + } + + free_and_null_pool_memory(clientJPath); + free_and_null_pool_memory(jobJPath); + free_and_null_pool_memory(fname); +#ifdef HAVE_WIN32 + free_and_null_pool_memory(drivesList); +#endif + }; + + /* Adjust the current fileset depending on what we find in the Journal */ + void adapt(Journal *j) { + SettingsRecord *settings = j->readSettings(); + + /* We should not backup the Spool Directory */ + if (settings != NULL) { + char *sdir = bstrdup(settings->getSpoolDir()); + bfuncs->AddExclude(ctx, sdir); + Dmsg(ctx, DBGLVL, "Excluded Spool Directory from FileSet %s\n", sdir); + delete settings; + } + + /* Foreach folder watched, we add the folder to the backup */ + if (!j->beginTransaction("r")) { + return; + } + + FolderRecord *rec; + +#ifdef HAVE_WIN32 + int i = 0; + + for(;;) { + rec = j->readFolderRecord(); + + if (rec == NULL) { + drivesList[i] = '\0'; + break; + } + + /*On Windows, we must also add the folder drives to create + the VSS Snapshot */ + if (!strchr(drivesList, rec->path[0])) { + drivesList[i++] = toupper(rec->path[0]); + Dmsg(ctx, DBGLVL, "Included Drive %c\n", rec->path[0]); + } + + bfuncs->AddInclude(ctx, rec->path); + Dmsg(ctx, DBGLVL, "Included Directory into the FileSet %s\n", rec->path); + delete rec; + } + +#else + for(;;) { + rec = j->readFolderRecord(); + + if (rec == NULL) { + break; + } + + bfuncs->AddInclude(ctx, rec->path); + Dmsg(ctx, DBGLVL, "Included Directory %s\n", rec->path); + delete rec; + } +#endif + + j->endTransaction(); + }; + + void adaptFileSet() { + for (int i = 0; i < journals.size(); i++) { + Journal *j = (Journal *) journals[i]; + adapt(j); + } + + } +}; + +/* + * Plugin called here when it is first loaded + */ +bRC DLL_IMP_EXP +loadPlugin(bInfo *lbinfo, bFuncs *lbfuncs, pInfo **pinfo, pFuncs **pfuncs) +{ + bfuncs = lbfuncs; /* set Bacula funct pointers */ + binfo = lbinfo; + + *pinfo = &pluginInfo; /* return pointer to our info */ + *pfuncs = &pluginFuncs; /* return pointer to our functions */ + + bfuncs->getBaculaValue(NULL, bVarWorkingDir, (void *)&working); + return bRC_OK; +} + +/* + * Plugin called here when it is unloaded, normally when + * Bacula is going to exit. + */ +bRC DLL_IMP_EXP +unloadPlugin() +{ + return bRC_OK; +} + +/* + * Called here to make a new instance of the plugin -- i.e. when + * a new Job is started. There can be multiple instances of + * each plugin that are running at the same time. Your + * plugin instance must be thread safe and keep its own + * local data. + */ +static bRC newPlugin(bpContext *ctx) +{ + CdpContext *pCtx = New(CdpContext(ctx)); + ctx->pContext = (void *) pCtx; /* set our context pointer */ + Dmsg(ctx, DBGLVL, "Working Directory: %s\n", working); + return bRC_OK; +} + +/* + * Release everything concerning a particular instance of a + * plugin. Normally called when the Job terminates. + */ +static bRC freePlugin(bpContext *ctx) +{ + CdpContext *pCtx = (CdpContext *) ctx->pContext; + delete(pCtx); + return bRC_OK; +} + +static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value) +{ + CdpContext *pCtx = (CdpContext *) ctx->pContext; + + switch (event->eventType) { + + case bEventPluginCommand: + if (!pCtx->handleBackupCommand(ctx, (char *) value)) { + return bRC_Error; + }; + pCtx->is_in_use = true; + pCtx->migrateJournal(); + pCtx->adaptFileSet(); + break; + + case bEventEstimateCommand: + Jmsg(ctx, M_FATAL, _("The CDP plugin doesn't support estimate\n")); + return bRC_Error; + + case bEventJobStart: + bfuncs->getBaculaValue(NULL, bVarJobName, (void *) &(pCtx->jobName)); + + if (pCtx->jobName == NULL) { + pCtx->jobName = (char *) "backup_job"; + } + + Dmsg(ctx, DBGLVL, "Job Name: %s\n", pCtx->jobName); + break; + + case bEventCancelCommand: + pCtx->canceled = true; + Dmsg(ctx, DBGLVL, "Job canceled\n"); + break; + +#ifdef HAVE_WIN32 + case bEventVssPrepareSnapshot: + strcpy((char *) value, pCtx->drivesList); + Dmsg(ctx, DBGLVL, "VSS Drives list: %s\n", pCtx->drivesList); + break; +#endif + + default: + break; + } + + return bRC_OK; +} + +/* + * Called when starting to backup a file. Here the plugin must + * return the "stat" packet for the directory/file and provide + * certain information so that Bacula knows what the file is. + * The plugin can create "Virtual" files by giving them a + * name that is not normally found on the file system. + */ +static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp) +{ + CdpContext *pCtx = (CdpContext *) ctx->pContext; + FileRecord *rec = pCtx->nextRecord(); + + if(rec != NULL) { + //Fill save_pkt struct + POOLMEM *bacula_fname = get_pool_memory(PM_FNAME); + rec->getBaculaName(bacula_fname); + sp->fname = bstrdup(bacula_fname); + sp->type = FT_REG; + rec->decode_attrs(sp->statp); + + //Save the name of the file that's inside the Spool Dir + //That's the file that will be backed up + pm_strcpy(pCtx->fname, rec->sname); + delete(rec); + free_and_null_pool_memory(bacula_fname); + Dmsg(ctx, DBGLVL, "Starting backup of file: %s\n", sp->fname); + return bRC_OK; + } else { + return bRC_Stop; + } + +} + +/* + * Done backing up a file. + */ +static bRC endBackupFile(bpContext *ctx) +{ + return bRC_More; +} + +/* + * Do actual I/O. Bacula calls this after startBackupFile + * or after startRestoreFile to do the actual file + * input or output. + */ +static bRC pluginIO(bpContext *ctx, struct io_pkt *io) +{ + CdpContext *pCtx = (CdpContext *) ctx->pContext; + + io->status = -1; + io->io_errno = 0; + + if (!pCtx) { + return bRC_Error; + } + + switch (io->func) { + case IO_OPEN: + if (bopen(&pCtx->fd, pCtx->fname, io->flags, io->mode) < 0) { + io->io_errno = errno; + io->status = -1; + Jmsg(ctx, M_ERROR, "Open file %s failed: ERR=%s\n", + pCtx->fname, strerror(errno)); + return bRC_Error; + } + io->status = 1; + break; + + case IO_READ: + if (!is_bopen(&pCtx->fd)) { + Jmsg(ctx, M_FATAL, "Logic error: NULL read FD\n"); + return bRC_Error; + } + + /* Read data from file */ + io->status = bread(&pCtx->fd, io->buf, io->count); + break; + + case IO_WRITE: + if (!is_bopen(&pCtx->fd)) { + Jmsg(ctx, M_FATAL, "Logic error: NULL write FD\n"); + return bRC_Error; + } + + io->status = bwrite(&pCtx->fd, io->buf, io->count); + break; + + case IO_SEEK: + if (!is_bopen(&pCtx->fd)) { + Jmsg(ctx, M_FATAL, "Logic error: NULL FD on delta seek\n"); + return bRC_Error; + } + /* Seek not needed for this plugin, we don't use real sparse file */ + io->status = blseek(&pCtx->fd, io->offset, io->whence); + break; + + /* Cleanup things during close */ + case IO_CLOSE: + io->status = bclose(&pCtx->fd); + break; + } + + return bRC_OK; +} + +static bRC startRestoreFile(bpContext *ctx, const char *cmd) +{ + Dmsg(ctx, DBGLVL, "Started file restoration\n"); + return bRC_Core; +} + +/* + * Called here to give the plugin the information needed to + * re-create the file on a restore. It basically gets the + * stat packet that was created during the backup phase. + * This data is what is needed to create the file, but does + * not contain actual file data. + */ +static bRC createFile(bpContext *ctx, struct restore_pkt *rp) +{ + CdpContext *pCtx = (CdpContext *) ctx->pContext; + pm_strcpy(pCtx->fname, rp->ofname); + rp->create_status = CF_CORE; + Dmsg(ctx, DBGLVL, "Creating file %s\n", rp->ofname); + return bRC_OK; +} + +static bRC endRestoreFile(bpContext *ctx) +{ + Dmsg(ctx, DBGLVL, "Finished file restoration\n"); + return bRC_OK; +} + +/* When using Incremental dump, all previous dumps are necessary */ +static bRC checkFile(bpContext *ctx, char *fname) +{ + CdpContext *pCtx = (CdpContext *) ctx->pContext; + + if (pCtx->is_in_use) { + if (!pCtx->accurate_warning) { + pCtx->accurate_warning = true; + Jmsg(ctx, M_WARNING, "Accurate mode is not supported. Please disable Accurate mode for this job.\n"); + } + + return bRC_Seen; + } else { + return bRC_OK; + } +} + + +#ifdef __cplusplus +} +#endif + diff --git a/bacula/src/plugins/fd/file-record.h b/bacula/src/plugins/fd/file-record.h new file mode 100644 index 000000000..6909a8f87 --- /dev/null +++ b/bacula/src/plugins/fd/file-record.h @@ -0,0 +1,116 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2022 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. + */ + +#ifndef journalfilerecord_H +#define journalfilerecord_H + +#include "bacula.h" +#include + +/** + * Implementation is part of Bacula's findlib module, and is on: + * "/src/findlib/attribs.c" + */ +extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI); +extern void encode_stat(char *buf, struct stat *statp, int stat_size, int32_t LinkFI, int data_stream); + +/** + * @brief Data that is saved and retrieved by using the @class Journal + */ +class FileRecord +{ + public: + char *name; + char *sname; + char *fattrs; + int64_t mtime; + + FileRecord(): + name(NULL), sname(NULL), fattrs(NULL), mtime(0) + {} + + bool encode_attrs() { + struct stat statbuf; +#ifndef HAVE_WIN32 + if(lstat(this->name, &statbuf) != 0) { + return false; + } + + this->mtime = (int64_t) statbuf.st_mtime; + this->fattrs = (char *) malloc(500 * sizeof(char)); + encode_stat(this->fattrs, &statbuf, sizeof(statbuf), 0, 0); +#else + FILE *fp = fopen(this->name, "r"); + + if (!fp) { + Dmsg1(0, "Could not open file %s\n", this->name); + return false; + } + + int fd = _fileno(fp); + + if(fstat(fd, &statbuf) != 0) { + fclose(fp); + Dmsg1(0, "Could not encode attributes of file %s\n", this->name); + return false; + } + + this->mtime = (int64_t) statbuf.st_mtime; + this->fattrs = (char *) malloc(500 * sizeof(char)); + encode_stat(this->fattrs, &statbuf, sizeof(statbuf), 0, 0); + fclose(fp); +#endif + return true; + } + + void decode_attrs(struct stat &sbuf) { + int32_t lfi; + decode_stat(this->fattrs, &sbuf, sizeof(sbuf), &lfi); + } + + bool equals(const FileRecord *rec) { + return strcmp(this->name, rec->name) == 0 + && strcmp(this->fattrs, rec->fattrs) == 0 + && this->mtime == rec->mtime; + } + + void getBaculaName(POOLMEM *target) { + char mtime_date[200]; + time_t t = (time_t) mtime; + struct tm *timeinfo = localtime(&t); + strftime(mtime_date, 200, "%Y%m%d_%H%M%S", timeinfo); + Mmsg(target, "%s.%s", name, mtime_date); + } + + ~FileRecord() { + if (name != NULL) { + free(name); + } + + if (sname != NULL) { + free(sname); + } + + if (fattrs != NULL) { + free(fattrs); + } + } +}; + +#endif diff --git a/bacula/src/plugins/fd/folder-record.h b/bacula/src/plugins/fd/folder-record.h new file mode 100644 index 000000000..f3565a444 --- /dev/null +++ b/bacula/src/plugins/fd/folder-record.h @@ -0,0 +1,46 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2022 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#ifndef folder_record_H +#define folder_record_H + +#include "bacula.h" +#include + +/** + * @brief Data that is saved and retrieved by using the @class Journal + */ +class FolderRecord +{ +public: + char *path; + + FolderRecord(): + path(NULL) + {} + + ~FolderRecord() { + if (path != NULL) { + free(path); + } + + } +}; + +#endif diff --git a/bacula/src/plugins/fd/journal.c b/bacula/src/plugins/fd/journal.c new file mode 100644 index 000000000..d2467258f --- /dev/null +++ b/bacula/src/plugins/fd/journal.c @@ -0,0 +1,760 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2022 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. + */ + +#include "journal.h" + +static int DBGLVL = 90; + +bool Journal::beginTransaction(const char *mode) +{ + if (hasTransaction) { + return true; + } + + bool hasLock = false; + int timeout = 1800; + + for (int time = 0; time < timeout; time++) { + _fp = bfopen(_jPath, mode); + + if (!_fp) { + Dmsg0(0, "Tried to start transaction but Journal File was not found.\n"); + return false; + } + + _fd = fileno(_fp); + int rc = flock(_fd, LOCK_EX | LOCK_NB); + + //Success. Lock acquired. + if (rc == 0) { + hasLock = true; + break; + } + + fclose(_fp); + sleep(1); + } + + if (hasLock) { + hasTransaction = true; + return true; + } else { + Dmsg0(0, "Tried to start transaction but could not lock Journal File.\n"); + return false; + } +} + +void Journal::endTransaction() +{ + if (!hasTransaction) { + return; + } + + if (_fp != NULL) { + int rc = flock(_fd, LOCK_UN); + + if (rc != 0) { + Dmsg0(0, "could not release flock\n"); + } + + fclose(_fp); + _fp = NULL; + } + + _fd = -1; + hasTransaction = false; +} + +/** + * Given a string formatted as 'key=val\n', + * this function tries to return 'val' + */ +char *Journal::extract_val(const char *key_val) +{ + const int SANITY_CHECK = 10000; + int max_idx = cstrlen(key_val) - 1; + char *val = (char *) malloc(SANITY_CHECK * sizeof(char)); + + int idx_keyend = 0; + + while(key_val[idx_keyend] != '=') { + idx_keyend++; + + if(idx_keyend > max_idx) { + free(val); + return NULL; + } + } + + int i; + int j = 0; + for(i = idx_keyend + 1; key_val[i] != '\n' ; i++) { + val[j] = key_val[i]; + j++; + + if(i > max_idx) { + free(val); + return NULL; + } + } + + val[j] = '\0'; + return val; +} + +bool Journal::setJournalPath(const char *path) +{ + _jPath = bstrdup(path); + FILE *jfile = bfopen(_jPath, "r"); + + if (!jfile) { + if (this->beginTransaction("w")) { + SettingsRecord rec; + rec.journalVersion = JOURNAL_VERSION; + this->writeSettings(rec); + } else { + Dmsg1(0, "(ERROR) Could not create Journal File: %s\n", path); + return false; + } + } else { + fclose(jfile); + } + + return true; +} + +bool Journal::setJournalPath(const char *path, const char *spoolDir) +{ + _jPath = bstrdup(path); + FILE *jfile = fopen(_jPath, "r"); + + if (!jfile) { + if (this->beginTransaction("w")) { + SettingsRecord rec; + rec.journalVersion = JOURNAL_VERSION; + rec.setSpoolDir(spoolDir); + this->writeSettings(rec); + } else { + Dmsg1(0, "(ERROR) Could not create Journal File: %s\n", path); + return false; + } + } else { + fclose(jfile); + } + + return true; +} + + +bool Journal::writeSettings(SettingsRecord &rec) +{ + int rc; + bool success = true; + const char *spoolDir; + char jversion[50]; + char heartbeat[50]; + + if(!this->beginTransaction("r+")) { + Dmsg0(50, "Could not start transaction for writeSettings()\n"); + success = false; + goto bail_out; + } + + spoolDir = rec.getSpoolDir(); + + if (spoolDir == NULL) { + spoolDir = ""; + } + + edit_int64(rec.heartbeat, heartbeat); + edit_int64(rec.journalVersion, jversion); + rc = fprintf(_fp, + "Settings {\n" + "spooldir=%s\n" + "heartbeat=%s\n" + "jversion=%s\n" + "}\n", + spoolDir, + heartbeat, + jversion); + + if(rc < 0) { + success = false; + Dmsg1(50, "(ERROR) Could not write SettingsRecord. RC=%d\n", rc); + goto bail_out; + } + + Dmsg3(DBGLVL, + "WROTE RECORD:\n" + " Settings {\n" + " spooldir=%s\n" + " heartbeat=%s\n" + " jversion=%s\n" + " }\n", + spoolDir, + heartbeat, + jversion); + +bail_out: + this->endTransaction(); + return success; +} + +SettingsRecord *Journal::readSettings() +{ + const int SANITY_CHECK = 10000; + bool corrupted = false; + char tmp[SANITY_CHECK]; + char jversion[SANITY_CHECK]; + char heartbeat[SANITY_CHECK]; + char spoolpath[SANITY_CHECK]; + char *jvstr = NULL; + char *hbstr = NULL; + SettingsRecord *rec = NULL; + + if(!this->beginTransaction("r+")) { + Dmsg0(0, "Could not start transaction for readSettings()\n"); + goto bail_out; + } + + //reads line "Settings {\n" + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + + rec = new SettingsRecord(); + + //reads Spool Dir + if(!bfgets(spoolpath, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + rec->setSpoolDir(extract_val(spoolpath)); + if(rec->getSpoolDir() == NULL) { + corrupted = true; + goto bail_out; + } + + //reads Heartbeat + if(!bfgets(heartbeat, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + hbstr = extract_val(heartbeat); + if(hbstr == NULL) { + corrupted = true; + goto bail_out; + } + rec->heartbeat = atoi(hbstr); + + //reads Journal Version + if(!bfgets(jversion, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + jvstr = extract_val(jversion); + if(jvstr == NULL) { + corrupted = true; + goto bail_out; + } + rec->journalVersion = atoi(jvstr); + + //reads line "}\n" + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + + Dmsg3(DBGLVL, + "READ RECORD:\n" + " Settings {\n" + " spooldir=%s\n" + " heartbeat=%s\n" + " jversion=%s\n" + " }\n", + rec->getSpoolDir(), + hbstr, + jvstr); + +bail_out: + if(jvstr != NULL) { + free(jvstr); + } + + if(hbstr != NULL) { + free(hbstr); + } + + if(rec != NULL && rec->getSpoolDir() != NULL && strcmp(rec->getSpoolDir(), "") == 0) { + free(rec->getSpoolDir()); + rec->setSpoolDir(NULL); + } + + if(corrupted) { + Dmsg0(0, "Could not read Settings Record. Journal is Corrupted.\n"); + + if(rec != NULL) { + delete rec; + rec = NULL; + } + } + + this->endTransaction(); + return rec; +} + +bool Journal::writeFileRecord(const FileRecord &record) +{ + int rc; + bool success = true; + char mtime_str[50]; + + if(!this->beginTransaction("a")) { + success = false; + Dmsg0(0, "Could not start transaction for writeFileRecord()\n"); + goto bail_out; + } + + edit_int64(record.mtime, mtime_str); + rc = fprintf(_fp, + "File {\n" + "name=%s\n" + "sname=%s\n" + "mtime=%s\n" + "attrs=%s\n" + "}\n", + record.name, + record.sname, + mtime_str, + record.fattrs); + + if(rc < 0) { + success = false; + Dmsg1(50, "(ERROR) Could not write FileRecord. RC=%d\n", rc); + goto bail_out; + } + + Dmsg4(DBGLVL, + "NEW RECORD:\n" + " File {\n" + " name=%s\n" + " sname=%s\n" + " mtime=%s" + " attrs=%s\n" + " }\n", + record.name, + record.sname, + mtime_str, + record.fattrs); + +bail_out: + this->endTransaction(); + return success; +} + +FileRecord *Journal::readFileRecord() +{ + const int SANITY_CHECK = 10000; + bool corrupted = false; + char tmp[SANITY_CHECK]; + char fname[SANITY_CHECK]; + char sname[SANITY_CHECK]; + char fattrs[SANITY_CHECK]; + char mtime[SANITY_CHECK]; + char *mstr = NULL; + FileRecord *rec = NULL; + + if(!hasTransaction) { + Dmsg0(0, "(ERROR) Journal::readFileRecord() called without any transaction\n"); + goto bail_out; + } + + //Reads lines until it finds a FileRecord, or until EOF + for(;;) { + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + goto bail_out; + } + + if(strstr(tmp, "File {\n") != NULL) { + break; + } + } + + rec = new FileRecord(); + + //reads filename + if(!bfgets(fname, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + rec->name = extract_val(fname); + if(rec->name == NULL) { + corrupted = true; + goto bail_out; + } + + //reads spoolname + if(!bfgets(sname, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + rec->sname = extract_val(sname); + if(rec->sname == NULL) { + corrupted = true; + goto bail_out; + } + + //reads mtime + if(!bfgets(mtime, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + mstr = extract_val(mtime); + if(mstr == NULL) { + corrupted = true; + goto bail_out; + } + rec->mtime = atoi(mstr); + + //reads encoded attributes + if(!bfgets(fattrs, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + rec->fattrs = extract_val(fattrs); + if(rec->fattrs == NULL) { + corrupted = true; + goto bail_out; + } + + Dmsg4(DBGLVL, + "READ RECORD:\n" + " File {\n" + " name=%s\n" + " sname=%s\n" + " mtime=%s\n" + " attrs=%s\n" + " }\n", + rec->name, + rec->sname, + mstr, + rec->fattrs); + + //reads line "}\n" + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + +bail_out: + if(mstr != NULL) { + free(mstr); + } + + if(corrupted) { + Dmsg0(0, "Could not read File Record. Journal is Corrupted.\n"); + + if(rec != NULL) { + delete rec; + rec = NULL; + } + } + + return rec; +} + +bool Journal::writeFolderRecord(const FolderRecord &record) +{ + int rc; + bool success = true; + + if(!this->beginTransaction("a")) { + success = false; + Dmsg0(0, "Could not start transaction for writeFileRecord()\n"); + goto bail_out; + } + + rc = fprintf(_fp, + "Folder {\n" + "path=%s\n" + "}\n", + record.path); + + if(rc < 0) { + success = false; + Dmsg1(0, "(ERROR) Could not write FolderRecord. RC=%d\n", rc); + goto bail_out; + } + + Dmsg1(DBGLVL, + "NEW RECORD:\n" + " Folder {\n" + " path=%s\n" + " }\n", + record.path); + +bail_out: + this->endTransaction(); + return success; +} + +FolderRecord *Journal::readFolderRecord() +{ + const int SANITY_CHECK = 10000; + bool corrupted = false; + char tmp[SANITY_CHECK]; + char path[SANITY_CHECK]; + FolderRecord *rec = NULL; + + if(!hasTransaction) { + Dmsg0(0, "(ERROR) Journal::readFolderRecord() called without any transaction\n"); + goto bail_out; + } + + //Reads lines until it finds a FolderRecord, or until EOF + for(;;) { + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + //No need to set 'corrupted = true' here + goto bail_out; + } + + if(strstr(tmp, "Folder {\n") != NULL) { + break; + } + } + + rec = new FolderRecord(); + + //reads folder path + if(!bfgets(path, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + rec->path = extract_val(path); + if(rec->path == NULL) { + corrupted = true; + goto bail_out; + } + + Dmsg1(DBGLVL, + "READ RECORD:\n" + " Folder {\n" + " path=%s\n" + " }\n", + rec->path); + + //reads line "}\n" + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + corrupted = true; + goto bail_out; + } + +bail_out: + if(corrupted) { + Dmsg0(0, "Could not read FolderRecord. Journal is Corrupted.\n"); + + if(rec != NULL) { + delete rec; + rec = NULL; + } + } + + return rec; +} + +bool Journal::removeFolderRecord(const char* folder) +{ + bool success = false; + const int SANITY_CHECK = 10000; + char path[SANITY_CHECK]; + char tmp[SANITY_CHECK]; + char *recPath; + FILE *tmpFp = NULL; + int rc; + + POOL_MEM tmp_jPath; + Mmsg(tmp_jPath, "%s.temp", _jPath); + + if(!this->beginTransaction("r")) { + goto bail_out; + } + + tmpFp = bfopen(tmp_jPath.c_str(), "w"); + + if(tmpFp == NULL) { + goto bail_out; + } + + for(;;) { + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + goto bail_out; + } + + if(strstr(tmp, "Folder {\n") != NULL) { + //reads folder path + if(!bfgets(path, SANITY_CHECK, _fp)) { + goto bail_out; + } + + recPath = extract_val(path); + + if(recPath == NULL) { + goto bail_out; + } + + //reads line "}\n" + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + goto bail_out; + } + + if(bstrcmp(folder, recPath) == 0) { + //Didn't found the Record that needs to be removed + rc = fprintf(tmpFp, + "Folder {\n" + "path=%s\n" + "}\n", + recPath); + + if(rc < 0) { + goto bail_out; + } + } else { + //Found the Folder Record that needs to be removed + success = true; + } + + } else { + fprintf(tmpFp, "%s", tmp); + } + } + +bail_out: + + if(tmpFp != NULL) { + fclose(tmpFp); + } + + if(success) { + fclose(_fp); + _fp = NULL; + unlink(_jPath); + rc = rename(tmp_jPath.c_str(), _jPath); + + if(rc != 0) { + Dmsg0(0, "Could not rename TMP Journal\n"); + } + } + + this->endTransaction(); + return success; +} + +bool Journal::migrateTo(const char *newPath) +{ + bool success = true; + const int SANITY_CHECK = 10000; + char tmp[SANITY_CHECK]; + FILE *tmpFp = NULL; + FILE *newFp = NULL; + int rc; + + POOLMEM *tmp_jPath = get_pool_memory(PM_FNAME); + Mmsg(tmp_jPath, "%s.temp", newPath); + + if(!this->beginTransaction("r")) { + success = false; + goto bail_out; + } + + Dmsg2(DBGLVL, "Migrating Journal %s to %s...\n", _jPath, newPath); + tmpFp = bfopen(tmp_jPath, "w"); + newFp = bfopen(newPath, "w"); + + if (tmpFp == NULL) { + Dmsg1(0, "Could not bfopen %s. Aborting migration.\n", tmp_jPath); + success = false; + goto bail_out; + } + + if (newFp == NULL) { + Dmsg1(0, "Could not bfopen %s. Aborting migration.\n", newPath); + success = false; + goto bail_out; + } + + // Migrate everything to the new Journal ('newFp') + // Remove FileRecords from the old Journal + // by not saving them in 'tmpFp' + for(;;) { + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + break; + } + + if(strstr(tmp, "File {") != NULL) { + //Found a FileRecord + //Write it only in the New Jornal + fprintf(newFp, "%s", tmp); + + for(int i = 0; i < 5; i++) { + if(!bfgets(tmp, SANITY_CHECK, _fp)) { + //Found a corrupted FileRecord + Dmsg0(0, "Found a corrupt FileRecord. Canceling Migration"); + success = false; + goto bail_out; + } + + fprintf(newFp, "%s", tmp); + } + + } else { + fprintf(newFp, "%s", tmp); + fprintf(tmpFp, "%s", tmp); + } + } + +bail_out: + + if(newFp != NULL) { + fclose(newFp); + } + + if(tmpFp != NULL) { + fclose(tmpFp); + } + + if(success) { + fclose(_fp); + _fp = NULL; + unlink(_jPath); + rc = rename(tmp_jPath, _jPath); + + if(rc != 0) { + Dmsg0(0, "Could not rename TMP Journal\n"); + } + + free(_jPath); + _jPath = bstrdup(newPath); + Dmsg0(DBGLVL, "Journal migration completed\n"); + } + + free_and_null_pool_memory(tmp_jPath); + this->endTransaction(); + return success; +} diff --git a/bacula/src/plugins/fd/journal.h b/bacula/src/plugins/fd/journal.h new file mode 100644 index 000000000..76b9516f8 --- /dev/null +++ b/bacula/src/plugins/fd/journal.h @@ -0,0 +1,94 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2022 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#ifndef journal_H +#define journal_H + +#include "settings-record.h" +#include "folder-record.h" +#include "file-record.h" + +#ifndef HAVE_WIN32 +#include +#endif + +#ifdef HAVE_WIN32 +#define JOURNAL_CLI_FNAME "bcdp-cli.journal" +#else +#define JOURNAL_CLI_FNAME ".bcdp-cli.journal" +#endif + +#define JOURNAL_VERSION 1 + +/** + * @brief The Journal persists and retrieves @class FileRecord objects. + * + * Used by: + * + * 1-) The CDP Client, to store information about files that + * should backed up. + * + * 2-) The CDP FD Plugin, to decide which file should be backed + * up on a specific Job. + * + * The current implementation uses a plain text file to store the records. + */ +class Journal +{ + +private: + FILE * _fp; + int _fd; + +public: + char *_jPath; + bool hasTransaction; + + Journal(): + _fp(NULL), _fd(-1), _jPath(NULL), hasTransaction(false) + {} + + ~Journal() {} + + bool setJournalPath(const char *path); + bool setJournalPath(const char *path, const char *spoolDir); + bool migrateTo(const char* newPath); + + bool beginTransaction(const char *mode); + void endTransaction(); + + bool writeSettings(SettingsRecord &record); + SettingsRecord *readSettings(); + + bool writeFileRecord(const FileRecord &record); + FileRecord *readFileRecord(); + + bool removeFolderRecord(const char *folder); + bool writeFolderRecord(const FolderRecord &record); + FolderRecord *readFolderRecord(); + + /** Public only because it's used by Unit Tests */ + char *extract_val(const char *key_val); + + //TODO: warnSizeFull(); + //TODO: removeRecord() + //TODO: pruneRecords() +}; + +#endif diff --git a/bacula/src/plugins/fd/settings-record.h b/bacula/src/plugins/fd/settings-record.h new file mode 100644 index 000000000..e7da2caff --- /dev/null +++ b/bacula/src/plugins/fd/settings-record.h @@ -0,0 +1,57 @@ +/* + Bacula(R) - The Network Backup Solution + + Copyright (C) 2000-2022 Kern Sibbald + + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. + + You may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. + + This notice must be preserved when any source code is + conveyed and/or propagated. + + Bacula(R) is a registered trademark of Kern Sibbald. +*/ + +#ifndef settingsrecord_H +#define settingsrecord_H + +#include "bacula.h" +#include + +/** + * @brief Data that is saved and retrieved by using the @class Journal + */ +class SettingsRecord +{ +private: + char *spoolDir; + +public: + int64_t heartbeat; + int64_t journalVersion; + + const char *getSpoolDir() { + return spoolDir; + } + + void setSpoolDir(const char *sdir) { + if (sdir == NULL) { + return; + } + + spoolDir = bstrdup(sdir); + } + + SettingsRecord(): + spoolDir(NULL), heartbeat(-1), journalVersion(-1) + {} + + ~SettingsRecord() {} +}; + +#endif diff --git a/bacula/src/tools/cdp-client/folderwatcher.cpp b/bacula/src/tools/cdp-client/folderwatcher.cpp index 044b8e68d..23adfd54b 100644 --- a/bacula/src/tools/cdp-client/folderwatcher.cpp +++ b/bacula/src/tools/cdp-client/folderwatcher.cpp @@ -68,7 +68,7 @@ POOLMEM *FolderWatcher::watch(const char *folder) if (rc) { POOLMEM *err_msg = get_pool_memory(PM_EMSG); Mmsg(err_msg, "Error: could not start INotify Thread." - "Please, contact Bacula Systems.\n"); + "Please, contact Bacula Support.\n"); return err_msg; } @@ -116,7 +116,7 @@ POOLMEM *FolderWatcher::watchDirRecursive(const char *dir) break; default: - Mmsg(err_msg, "Unknown Error. Please contact Bacula Systems."); + Mmsg(err_msg, "Unknown Error. Please contact Bacula Support."); break; } @@ -411,7 +411,7 @@ POOLMEM *FolderWatcher::watch(const char *folder) _dirHandle = NULL; POOLMEM *err_msg = get_pool_memory(PM_EMSG); Mmsg(err_msg, "Error: could not create handle for folder %s. " - "Please, contact Bacula Systems.\n", folder); + "Please, contact Bacula Support.\n", folder); return err_msg; } @@ -421,7 +421,7 @@ POOLMEM *FolderWatcher::watch(const char *folder) if (rc) { POOLMEM *err_msg = get_pool_memory(PM_EMSG); Mmsg(err_msg, "Error: could not start Watcher Thread. " - "Please, contact Bacula Systems.\n"); + "Please, contact Bacula Support.\n"); return err_msg; }