1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 
20 #include "bacula.h"
21 #include "fd_plugins.h"
22 #include "fd_common.h"
23 #include "lib/cmd_parser.h"
24 #include "lib/mem_pool.h"
25 #include "findlib/bfile.h"
26 #include "journal.h"
27 
28 #ifdef __cplusplus
29 extern "C" {
30 #endif
31 
32 #define PLUGIN_LICENSE      "AGPLv3"
33 #define PLUGIN_AUTHOR       "Henrique Faria"
34 #define PLUGIN_DATE         "February 2019"
35 #define PLUGIN_VERSION      "0.1"
36 #define PLUGIN_DESCRIPTION  "CDP Plugin"
37 
38 #ifdef HAVE_WIN32
39 #define CONCAT_PATH "%s\\%s"
40 #define WORKING_JOURNAL_TEMPLATE "%s\\%s_%d.journal"
41 #else
42 #define CONCAT_PATH "%s/%s"
43 #define WORKING_JOURNAL_TEMPLATE "%s/%s_%d.journal"
44 #endif
45 
46 /* Forward referenced functions */
47 static bRC newPlugin(bpContext *ctx);
48 static bRC freePlugin(bpContext *ctx);
49 static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value);
50 static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp);
51 static bRC endBackupFile(bpContext *ctx);
52 static bRC pluginIO(bpContext *ctx, struct io_pkt *io);
53 static bRC startRestoreFile(bpContext *ctx, const char *cmd);
54 static bRC createFile(bpContext *ctx, struct restore_pkt *rp);
55 static bRC endRestoreFile(bpContext *ctx);
56 static bRC checkFile(bpContext *ctx, char *fname);
57 
58 /* Pointers to Bacula functions */
59 static bFuncs *bfuncs = NULL;
60 static bInfo  *binfo = NULL;
61 
62 /* Backup Variables */
63 static char *working = NULL;
64 
65 static pInfo pluginInfo = {
66    sizeof(pluginInfo),
67    FD_PLUGIN_INTERFACE_VERSION,
68    FD_PLUGIN_MAGIC,
69    PLUGIN_LICENSE,
70    PLUGIN_AUTHOR,
71    PLUGIN_DATE,
72    PLUGIN_VERSION,
73    PLUGIN_DESCRIPTION
74 };
75 
76 static pFuncs pluginFuncs = {
77    sizeof(pluginFuncs),
78    FD_PLUGIN_INTERFACE_VERSION,
79 
80    /* Entry points into plugin */
81    newPlugin,                    /* new plugin instance */
82    freePlugin,                   /* free plugin instance */
83    NULL,
84    NULL,
85    handlePluginEvent,
86    startBackupFile,
87    endBackupFile,
88    startRestoreFile,
89    endRestoreFile,
90    pluginIO,
91    createFile,
92    NULL,
93    checkFile,
94    NULL,                         /* No ACL/XATTR */
95    NULL,                         /* No Restore file list */
96    NULL                          /* No checkStream */
97 };
98 
99 static int DBGLVL = 50;
100 
101 class CdpContext: public SMARTALLOC
102 {
103 public:
104    bpContext *ctx;
105 
106    /** Used by both Backup and Restore Cycles **/
107    BFILE fd;
108    POOLMEM *fname;
109    bool is_in_use;
110 
111    /** Used only by the Backup Cycle **/
112    POOLMEM *clientJPath;
113    POOLMEM *jobJPath;
114    POOLMEM *drivesList; // Windows only
115    char *jobName;
116 
117    bool accurate_warning;
118    bool started_backup;
119    bool canceled;
120    alist userHomes;
121    alist journals;
122    int jIndex;
123    cmd_parser parser;
124    Journal *journal;
125 
CdpContext(bpContext * actx)126    CdpContext(bpContext *actx):
127      ctx(actx), fname(NULL), is_in_use(false), clientJPath(NULL),
128      jobJPath(NULL), drivesList(NULL), jobName(NULL), accurate_warning(false),
129      started_backup(false), canceled(false),
130      userHomes(100, owned_by_alist), journals(100, not_owned_by_alist), jIndex(0)
131    {
132       fname = get_pool_memory(PM_FNAME);
133       clientJPath = get_pool_memory(PM_FNAME);
134       jobJPath = get_pool_memory(PM_FNAME);
135 #ifdef HAVE_WIN32
136       drivesList = get_pool_memory(PM_FNAME);
137       *drivesList = 0;
138 #endif
139       *fname = *clientJPath = *jobJPath = 0;
140    };
141 
142    /** Methods called during Backup */
migrateJournal()143    void migrateJournal() {
144       char *uh;
145       int i = 0;
146 
147       foreach_alist(uh, &userHomes) {
148          Journal *j = new Journal();
149          Mmsg(clientJPath, CONCAT_PATH, uh, JOURNAL_CLI_FNAME);
150          j->setJournalPath(clientJPath);
151 
152          Mmsg(jobJPath, WORKING_JOURNAL_TEMPLATE, working, jobName, i);
153          j->migrateTo(jobJPath);
154          journals.append(j);
155          i++;
156       }
157    };
158 
handleBackupCommand(bpContext * ctx,char * cmd)159    bool handleBackupCommand(bpContext *ctx, char *cmd) {
160       int i;
161       POOLMEM *userHome;
162       parser.parse_cmd(cmd);
163       for (i = 1; i < parser.argc ; i++) {
164 
165          if (strcasecmp(parser.argk[i], "userhome") == 0 && parser.argv[i]) {
166             userHome = get_pool_memory(PM_FNAME);
167             pm_strcpy(userHome, parser.argv[i]);
168             struct stat sp;
169 
170             if (stat(userHome, &sp) != 0) {
171                Jmsg(ctx, M_ERROR, _("Parameter userhome not found: %s\n"), userHome);
172                return false;
173             }
174 
175             if (!S_ISDIR(sp.st_mode)) {
176                Jmsg(ctx, M_ERROR, _("Paramater userhome is not a directory: %s\n"), userHome);
177                return false;
178             }
179 
180             Dmsg(ctx, DBGLVL, "User Home: %s\n", userHome);
181             userHomes.append(bstrdup(userHome));
182             free_and_null_pool_memory(userHome);
183          } else if (strcasecmp(parser.argk[i], "user") == 0 && parser.argv[i]) {
184             userHome = get_pool_memory(PM_FNAME);
185             int rc = get_user_home_directory(parser.argv[i], userHome);
186 
187             if (rc != 0) {
188                Jmsg(ctx, M_ERROR, _("User not found in the system: %s\n"), parser.argv[i]);
189                return false;
190             }
191 
192             userHomes.append(bstrdup(userHome));
193             Dmsg(ctx, DBGLVL, "User Home: %s\n", userHome);
194             free_and_null_pool_memory(userHome);
195             return true;
196          } else if (strcasecmp(parser.argk[i], "group") == 0 && parser.argv[i]) {
197             int rc = get_home_directories(parser.argv[i], &userHomes);
198 
199             if (rc != 0) {
200                return false;
201             }
202 
203             return true;
204      } else {
205             Jmsg(ctx, M_ERROR, _("Can't analyse plugin command line %s\n"), cmd);
206             return false;
207          }
208       }
209 
210       return true;
211    };
212 
nextRecord()213    FileRecord *nextRecord() {
214       if (canceled) {
215          if (journal) {
216             journal->endTransaction();
217          }
218          return NULL;
219       }
220 
221       if (!started_backup) {
222          if (jIndex >= journals.size()) {
223             return NULL;
224          }
225 
226          journal = (Journal *) journals[jIndex];
227 
228          if (!journal->beginTransaction("r")) {
229             return NULL;
230          }
231 
232          started_backup = true;
233       }
234 
235       FileRecord *fc = journal->readFileRecord();
236 
237       if (fc == NULL) {
238          journal->endTransaction();
239          started_backup = false;
240          unlink(journal->_jPath);
241          Dmsg(ctx, DBGLVL, "No more files to backup. Deleting journal: %s\n", journal->_jPath);
242          delete(journal);
243          jIndex++;
244       }
245 
246       return fc;
247    };
248 
~CdpContext()249    ~CdpContext() {
250       if (journal) {
251          // Clear any possible pending transaction (e.g canceled job)
252          journal->endTransaction();
253          canceled = true;
254       }
255 
256       free_and_null_pool_memory(clientJPath);
257       free_and_null_pool_memory(jobJPath);
258       free_and_null_pool_memory(fname);
259 #ifdef HAVE_WIN32
260       free_and_null_pool_memory(drivesList);
261 #endif
262    };
263 
264    /* Adjust the current fileset depending on what we find in the Journal */
adapt(Journal * j)265    void adapt(Journal *j) {
266       SettingsRecord *settings = j->readSettings();
267 
268       /* We should not backup the Spool Directory */
269       if (settings != NULL) {
270          char *sdir = bstrdup(settings->getSpoolDir());
271          bfuncs->AddExclude(ctx, sdir);
272          Dmsg(ctx, DBGLVL, "Excluded Spool Directory from FileSet %s\n", sdir);
273          delete settings;
274       }
275 
276       /* Foreach folder watched, we add the folder to the backup */
277       if (!j->beginTransaction("r")) {
278          return;
279       }
280 
281       FolderRecord *rec;
282 
283 #ifdef HAVE_WIN32
284       int i = 0;
285 
286       for(;;) {
287          rec = j->readFolderRecord();
288 
289          if (rec == NULL) {
290             drivesList[i] = '\0';
291             break;
292          }
293 
294          /*On Windows, we must also add the folder drives to create
295            the VSS Snapshot */
296          if (!strchr(drivesList, rec->path[0])) {
297             drivesList[i++] = toupper(rec->path[0]);
298             Dmsg(ctx, DBGLVL, "Included Drive %c\n", rec->path[0]);
299          }
300 
301          bfuncs->AddInclude(ctx, rec->path);
302          Dmsg(ctx, DBGLVL, "Included Directory into the FileSet %s\n", rec->path);
303          delete rec;
304       }
305 
306 #else
307       for(;;) {
308          rec = j->readFolderRecord();
309 
310          if (rec == NULL) {
311             break;
312          }
313 
314          bfuncs->AddInclude(ctx, rec->path);
315          Dmsg(ctx, DBGLVL, "Included Directory %s\n", rec->path);
316          delete rec;
317       }
318 #endif
319 
320       j->endTransaction();
321    };
322 
adaptFileSet()323    void adaptFileSet() {
324       for (int i = 0; i < journals.size(); i++) {
325          Journal *j = (Journal *) journals[i];
326          adapt(j);
327       }
328 
329    }
330 };
331 
332 /*
333  * Plugin called here when it is first loaded
334  */
335 bRC DLL_IMP_EXP
loadPlugin(bInfo * lbinfo,bFuncs * lbfuncs,pInfo ** pinfo,pFuncs ** pfuncs)336 loadPlugin(bInfo *lbinfo, bFuncs *lbfuncs, pInfo **pinfo, pFuncs **pfuncs)
337 {
338    bfuncs = lbfuncs;                  /* set Bacula funct pointers */
339    binfo  = lbinfo;
340 
341    *pinfo  = &pluginInfo;             /* return pointer to our info */
342    *pfuncs = &pluginFuncs;            /* return pointer to our functions */
343 
344    bfuncs->getBaculaValue(NULL, bVarWorkingDir, (void *)&working);
345    return bRC_OK;
346 }
347 
348 /*
349  * Plugin called here when it is unloaded, normally when
350  *  Bacula is going to exit.
351  */
352 bRC DLL_IMP_EXP
unloadPlugin()353 unloadPlugin()
354 {
355    return bRC_OK;
356 }
357 
358 /*
359  * Called here to make a new instance of the plugin -- i.e. when
360  *  a new Job is started.  There can be multiple instances of
361  *  each plugin that are running at the same time.  Your
362  *  plugin instance must be thread safe and keep its own
363  *  local data.
364  */
newPlugin(bpContext * ctx)365 static bRC newPlugin(bpContext *ctx)
366 {
367   CdpContext *pCtx = New(CdpContext(ctx));
368   ctx->pContext = (void *) pCtx;        /* set our context pointer */
369   Dmsg(ctx, DBGLVL, "Working Directory: %s\n", working);
370   return bRC_OK;
371 }
372 
373 /*
374  * Release everything concerning a particular instance of a
375  *  plugin. Normally called when the Job terminates.
376  */
freePlugin(bpContext * ctx)377 static bRC freePlugin(bpContext *ctx)
378 {
379    CdpContext *pCtx = (CdpContext *) ctx->pContext;
380    delete(pCtx);
381    return bRC_OK;
382 }
383 
handlePluginEvent(bpContext * ctx,bEvent * event,void * value)384 static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value)
385 {
386    CdpContext *pCtx = (CdpContext *) ctx->pContext;
387 
388    switch (event->eventType) {
389 
390    case bEventPluginCommand:
391       if (!pCtx->handleBackupCommand(ctx, (char *) value)) {
392          return bRC_Error;
393       };
394       pCtx->is_in_use = true;
395       pCtx->migrateJournal();
396       pCtx->adaptFileSet();
397       break;
398 
399    case bEventEstimateCommand:
400       Jmsg(ctx, M_FATAL, _("The CDP plugin doesn't support estimate\n"));
401       return bRC_Error;
402 
403    case bEventJobStart:
404       bfuncs->getBaculaValue(NULL, bVarJobName, (void *) &(pCtx->jobName));
405 
406       if (pCtx->jobName == NULL) {
407          pCtx->jobName = (char *) "backup_job";
408       }
409 
410       Dmsg(ctx, DBGLVL, "Job Name: %s\n", pCtx->jobName);
411       break;
412 
413    case bEventCancelCommand:
414       pCtx->canceled = true;
415       Dmsg(ctx, DBGLVL, "Job canceled\n");
416       break;
417 
418 #ifdef HAVE_WIN32
419    case bEventVssPrepareSnapshot:
420       strcpy((char *) value, pCtx->drivesList);
421       Dmsg(ctx, DBGLVL, "VSS Drives list: %s\n", pCtx->drivesList);
422       break;
423 #endif
424 
425    default:
426       break;
427    }
428 
429    return bRC_OK;
430 }
431 
432 /*
433  * Called when starting to backup a file.  Here the plugin must
434  *  return the "stat" packet for the directory/file and provide
435  *  certain information so that Bacula knows what the file is.
436  *  The plugin can create "Virtual" files by giving them a
437  *  name that is not normally found on the file system.
438  */
startBackupFile(bpContext * ctx,struct save_pkt * sp)439 static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp)
440 {
441    CdpContext *pCtx = (CdpContext *) ctx->pContext;
442    FileRecord *rec = pCtx->nextRecord();
443 
444    if(rec != NULL) {
445       //Fill save_pkt struct
446       POOLMEM *bacula_fname = get_pool_memory(PM_FNAME);
447       rec->getBaculaName(bacula_fname);
448       sp->fname = bstrdup(bacula_fname);
449       sp->type = FT_REG;
450       rec->decode_attrs(sp->statp);
451 
452       //Save the name of the file that's inside the Spool Dir
453       //That's the file that will be backed up
454       pm_strcpy(pCtx->fname, rec->sname);
455       delete(rec);
456       free_and_null_pool_memory(bacula_fname);
457       Dmsg(ctx, DBGLVL, "Starting backup of file: %s\n", sp->fname);
458       return bRC_OK;
459    } else {
460       return bRC_Stop;
461    }
462 
463 }
464 
465 /*
466  * Done backing up a file.
467  */
endBackupFile(bpContext * ctx)468 static bRC endBackupFile(bpContext *ctx)
469 {
470    return bRC_More;
471 }
472 
473 /*
474  * Do actual I/O.  Bacula calls this after startBackupFile
475  *   or after startRestoreFile to do the actual file
476  *   input or output.
477  */
pluginIO(bpContext * ctx,struct io_pkt * io)478 static bRC pluginIO(bpContext *ctx, struct io_pkt *io)
479 {
480    CdpContext *pCtx = (CdpContext *) ctx->pContext;
481 
482    io->status = -1;
483    io->io_errno = 0;
484 
485    if (!pCtx) {
486       return bRC_Error;
487    }
488 
489    switch (io->func) {
490    case IO_OPEN:
491       if (bopen(&pCtx->fd, pCtx->fname, io->flags, io->mode) < 0) {
492             io->io_errno = errno;
493             io->status = -1;
494             Jmsg(ctx, M_ERROR, "Open file %s failed: ERR=%s\n",
495                  pCtx->fname, strerror(errno));
496             return bRC_Error;
497       }
498       io->status = 1;
499       break;
500 
501    case IO_READ:
502       if (!is_bopen(&pCtx->fd)) {
503          Jmsg(ctx, M_FATAL, "Logic error: NULL read FD\n");
504          return bRC_Error;
505       }
506 
507       /* Read data from file */
508       io->status = bread(&pCtx->fd, io->buf, io->count);
509       break;
510 
511    case IO_WRITE:
512       if (!is_bopen(&pCtx->fd)) {
513          Jmsg(ctx, M_FATAL, "Logic error: NULL write FD\n");
514          return bRC_Error;
515       }
516 
517       io->status = bwrite(&pCtx->fd, io->buf, io->count);
518       break;
519 
520    case IO_SEEK:
521       if (!is_bopen(&pCtx->fd)) {
522          Jmsg(ctx, M_FATAL, "Logic error: NULL FD on delta seek\n");
523          return bRC_Error;
524       }
525       /* Seek not needed for this plugin, we don't use real sparse file */
526       io->status = blseek(&pCtx->fd, io->offset, io->whence);
527       break;
528 
529    /* Cleanup things during close */
530    case IO_CLOSE:
531       io->status = bclose(&pCtx->fd);
532       break;
533    }
534 
535    return bRC_OK;
536 }
537 
startRestoreFile(bpContext * ctx,const char * cmd)538 static bRC startRestoreFile(bpContext *ctx, const char *cmd)
539 {
540    Dmsg(ctx, DBGLVL, "Started file restoration\n");
541    return bRC_Core;
542 }
543 
544 /*
545  * Called here to give the plugin the information needed to
546  *  re-create the file on a restore.  It basically gets the
547  *  stat packet that was created during the backup phase.
548  *  This data is what is needed to create the file, but does
549  *  not contain actual file data.
550  */
createFile(bpContext * ctx,struct restore_pkt * rp)551 static bRC createFile(bpContext *ctx, struct restore_pkt *rp)
552 {
553    CdpContext *pCtx = (CdpContext *) ctx->pContext;
554    pm_strcpy(pCtx->fname, rp->ofname);
555    rp->create_status = CF_CORE;
556    Dmsg(ctx, DBGLVL, "Creating file %s\n", rp->ofname);
557    return bRC_OK;
558 }
559 
endRestoreFile(bpContext * ctx)560 static bRC endRestoreFile(bpContext *ctx)
561 {
562    Dmsg(ctx, DBGLVL, "Finished file restoration\n");
563    return bRC_OK;
564 }
565 
566 /* When using Incremental dump, all previous dumps are necessary */
checkFile(bpContext * ctx,char * fname)567 static bRC checkFile(bpContext *ctx, char *fname)
568 {
569    CdpContext *pCtx = (CdpContext *) ctx->pContext;
570 
571    if (pCtx->is_in_use) {
572       if (!pCtx->accurate_warning) {
573          pCtx->accurate_warning = true;
574          Jmsg(ctx, M_WARNING, "Accurate mode is not supported. Please disable Accurate mode for this job.\n");
575       }
576 
577       return bRC_Seen;
578    } else {
579       return bRC_OK;
580    }
581 }
582 
583 
584 #ifdef __cplusplus
585 }
586 #endif
587 
588