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