2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2010 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation, which is
11 listed in the file LICENSE.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
29 * Main program to test loading and running Bacula plugins.
30 * Destined to become Bacula pluginloader, ...
32 * Kern Sibbald, October 2007
38 extern DLL_IMP_EXP char *exepath;
40 const int dbglvl = 150;
42 const char *plugin_type = "-fd.dll";
44 const char *plugin_type = "-fd.so";
47 extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
48 extern bool check_changes(JCR *jcr, FF_PKT *ff_pkt);
50 /* Function pointers to be set here */
51 extern DLL_IMP_EXP int (*plugin_bopen)(BFILE *bfd, const char *fname, int flags, mode_t mode);
52 extern DLL_IMP_EXP int (*plugin_bclose)(BFILE *bfd);
53 extern DLL_IMP_EXP ssize_t (*plugin_bread)(BFILE *bfd, void *buf, size_t count);
54 extern DLL_IMP_EXP ssize_t (*plugin_bwrite)(BFILE *bfd, void *buf, size_t count);
55 extern DLL_IMP_EXP boffset_t (*plugin_blseek)(BFILE *bfd, boffset_t offset, int whence);
58 /* Forward referenced functions */
59 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value);
60 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value);
61 static bRC baculaRegisterEvents(bpContext *ctx, ...);
62 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
63 int type, utime_t mtime, const char *fmt, ...);
64 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
65 int level, const char *fmt, ...);
66 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
68 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem);
69 static bRC baculaAddExclude(bpContext *ctx, const char *file);
70 static bRC baculaAddInclude(bpContext *ctx, const char *file);
71 static bRC baculaAddOptions(bpContext *ctx, const char *opts);
72 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type);
73 static bRC baculaAddWild(bpContext *ctx, const char *item, int type);
74 static bRC baculaNewOptions(bpContext *ctx);
75 static bRC baculaNewInclude(bpContext *ctx);
76 static bool is_plugin_compatible(Plugin *plugin);
77 static bool get_plugin_name(JCR *jcr, char *cmd, int *ret);
78 static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp);
81 * These will be plugged into the global pointer structure for
84 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode);
85 static int my_plugin_bclose(BFILE *bfd);
86 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count);
87 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count);
88 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence);
90 #define for_this_plug(plugin, str, len) (((len) == (plugin)->file_len) && strncmp((plugin)->file, str, len) == 0)
93 static bInfo binfo = {
95 FD_PLUGIN_INTERFACE_VERSION
98 /* Bacula entry points */
99 static bFuncs bfuncs = {
101 FD_PLUGIN_INTERFACE_VERSION,
102 baculaRegisterEvents,
120 * Bacula private context
123 JCR *jcr; /* jcr for plugin */
124 bRC rc; /* last return code */
125 bool disabled; /* set if plugin disabled */
126 findINCEXE *exclude; /* pointer to exclude files */
127 findINCEXE *include; /* pointer to include/exclude files */
130 static bool is_plugin_disabled(bpContext *plugin_ctx)
136 b_ctx = (bacula_ctx *)plugin_ctx->bContext;
137 return b_ctx->disabled;
140 static bool is_plugin_disabled(JCR *jcr)
142 return is_plugin_disabled(jcr->plugin_ctx);
146 * Create a plugin event
147 * When receiving bEventCancelCommand, this function is called by an other thread.
149 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)
151 bpContext *plugin_ctx;
157 bool call_if_canceled = false;
160 if (!plugin_list || !jcr || !jcr->plugin_ctx_list) {
161 return; /* Return if no plugins loaded */
165 * Some events are sent to only a particular plugin or must be
166 * called even if the job is canceled
169 case bEventPluginCommand:
170 name = (char *)value;
171 if (!get_plugin_name(jcr, name, &len)) {
175 case bEventEndBackupJob:
176 case bEventEndVerifyJob:
177 call_if_canceled = true;
179 case bEventStartRestoreJob:
181 jcr->plugin->restoreFileStarted = false;
182 jcr->plugin->createFileCalled = false;
185 case bEventEndRestoreJob:
186 call_if_canceled = true;
187 if (jcr->plugin && jcr->plugin->restoreFileStarted) {
188 plug_func(jcr->plugin)->endRestoreFile(jcr->plugin_ctx);
189 jcr->plugin->restoreFileStarted = false;
190 jcr->plugin->createFileCalled = false;
197 if (!call_if_canceled && jcr->is_job_canceled()) {
201 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
202 event.eventType = eventType;
204 Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
207 * Pass event to every plugin (except if name is set). If name
208 * is set, we pass it only to the plugin with that name.
210 foreach_alist(plugin, plugin_list) {
211 if (name && !for_this_plug(plugin, name, len)) {
215 plugin_ctx = &plugin_ctx_list[i++];
216 if (is_plugin_disabled(plugin_ctx)) {
219 rc = plug_func(plugin)->handlePluginEvent(plugin_ctx, &event, value);
228 * Check if file was seen for accurate
230 bool plugin_check_file(JCR *jcr, char *fname)
236 if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
237 return false; /* Return if no plugins loaded */
240 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
242 Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
244 /* Pass event to every plugin */
245 foreach_alist(plugin, plugin_list) {
246 jcr->plugin_ctx = &plugin_ctx_list[i++];
247 jcr->plugin = plugin;
248 if (is_plugin_disabled(jcr)) {
251 if (plug_func(plugin)->checkFile == NULL) {
254 rc = plug_func(plugin)->checkFile(jcr->plugin_ctx, fname);
255 if (rc == bRC_Seen) {
261 jcr->plugin_ctx = NULL;
262 return rc == bRC_Seen;
265 /* Get the first part of the the plugin command
266 * systemstate:/@SYSTEMSTATE/
268 * => can use for_this_plug(plug, cmd, ret);
270 * The plugin command can contain only the plugin name
274 static bool get_plugin_name(JCR *jcr, char *cmd, int *ret)
278 if (!cmd || (*cmd == '\0')) {
281 /* Handle plugin command here backup */
282 Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
283 if ((p = strchr(cmd, ':')) == NULL) {
284 if (strchr(cmd, ' ') == NULL) { /* we have just the plugin name */
287 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
290 } else { /* plugin:argument */
301 * Sequence of calls for a backup:
302 * 1. plugin_save() here is called with ff_pkt
303 * 2. we find the plugin requested on the command string
304 * 3. we generate a bEventBackupCommand event to the specified plugin
305 * and pass it the command string.
306 * 4. we make a startPluginBackup call to the plugin, which gives
307 * us the data we need in save_pkt
308 * 5. we call Bacula's save_file() subroutine to save the specified
309 * file. The plugin will be called at pluginIO() to supply the
312 * Sequence of calls for restore:
313 * See subroutine plugin_name_stream() below.
315 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
320 char *cmd = ff_pkt->top_fname;
323 POOL_MEM fname(PM_FNAME);
324 POOL_MEM link(PM_FNAME);
326 if (!plugin_list || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
327 Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" requested, but is not loaded.\n", cmd);
328 return 1; /* Return if no plugins loaded */
331 jcr->cmd_plugin = true;
332 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
333 event.eventType = bEventBackupCommand;
335 if (!get_plugin_name(jcr, cmd, &len)) {
339 /* Note, we stop the loop on the first plugin that matches the name */
340 foreach_alist(plugin, plugin_list) {
341 Dmsg4(dbglvl, "plugin=%s plen=%d cmd=%s len=%d\n", plugin->file, plugin->file_len, cmd, len);
342 if (!for_this_plug(plugin, cmd, len)) {
347 * We put the current plugin pointer, and the plugin context
348 * into the jcr, because during save_file(), the plugin
349 * will be called many times and these values are needed.
351 jcr->plugin_ctx = &plugin_ctx_list[i];
352 jcr->plugin = plugin;
353 if (is_plugin_disabled(jcr)) {
357 Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
358 /* Send the backup command to the right plugin*/
359 if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, cmd) != bRC_OK) {
362 /* Loop getting filenames to backup then saving them */
363 while (!jcr->is_job_canceled()) {
364 memset(&sp, 0, sizeof(sp));
365 sp.pkt_size = sizeof(sp);
366 sp.pkt_end = sizeof(sp);
370 Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
372 /* Get the file save parameters. I.e. the stat pkt ... */
373 if (plug_func(plugin)->startBackupFile(jcr->plugin_ctx, &sp) != bRC_OK) {
377 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no type in startBackupFile packet.\n"),
381 jcr->plugin_sp = &sp;
384 * Copy fname and link because save_file() zaps them. This
385 * avoids zaping the plugin's strings.
387 ff_pkt->type = sp.type;
388 if (sp.type == FT_RESTORE_FIRST) {
389 if (!sp.object_name) {
390 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no object_name in startBackupFile packet.\n"),
394 ff_pkt->fname = cmd; /* full plugin string */
395 ff_pkt->object_name = sp.object_name;
396 ff_pkt->object_index = sp.index; /* restore object index */
397 ff_pkt->object_compression = 0; /* no compression for now */
398 ff_pkt->object = sp.object;
399 ff_pkt->object_len = sp.object_len;
402 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no fname in startBackupFile packet.\n"),
406 pm_strcpy(fname, sp.fname);
407 pm_strcpy(link, sp.link);
408 ff_pkt->fname = fname.c_str();
409 ff_pkt->link = link.c_str();
410 ff_pkt->delta_seq = sp.delta_seq;
411 if (sp.flags & FO_DELTA) {
412 ff_pkt->flags |= FO_DELTA;
413 ff_pkt->delta_seq++; /* make new delta sequence number */
415 ff_pkt->flags &= ~FO_DELTA; /* clean delta sequence number */
416 ff_pkt->delta_seq = 0;
418 if (sp.flags & FO_OFFSETS) {
419 ff_pkt->flags |= FO_OFFSETS;
421 if (sp.flags & FO_PORTABLE_DATA) {
422 ff_pkt->flags |= FO_PORTABLE_DATA;
424 ff_pkt->flags |= FO_PLUGIN; /* data from plugin */
427 memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
428 Dmsg2(dbglvl, "startBackup returned type=%d, fname=%s\n", sp.type, sp.fname);
430 Dmsg2(dbglvl, "index=%d object=%s\n", sp.index, sp.object);
432 /* Call Bacula core code to backup the plugin's file */
433 save_file(jcr, ff_pkt, true);
434 bRC rc = plug_func(plugin)->endBackupFile(jcr->plugin_ctx);
435 if (rc == bRC_More || rc == bRC_OK) {
436 accurate_mark_file_as_seen(jcr, fname.c_str());
438 if (rc == bRC_More) {
442 } /* end while loop */
444 } /* end loop over all plugins */
445 Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" not found.\n", cmd);
448 jcr->cmd_plugin = false;
450 jcr->plugin_ctx = NULL;
455 * Send plugin name start/end record to SD
457 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
460 int index = jcr->JobFiles;
461 struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
464 Jmsg0(jcr, M_FATAL, 0, _("Plugin save packet not found.\n"));
467 if (jcr->is_job_canceled()) {
472 index++; /* JobFiles not incremented yet */
474 Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
475 /* Send stream header */
476 if (!sd->fsend("%ld %d 0", index, STREAM_PLUGIN_NAME)) {
477 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
481 Dmsg1(50, "send plugin name hdr: %s\n", sd->msg);
484 /* Send data -- not much */
485 stat = sd->fsend("%ld 1 %d %s%c", index, sp->portable, sp->cmd, 0);
487 /* Send end of data */
488 stat = sd->fsend("%ld 0", jcr->JobFiles);
491 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
495 Dmsg1(dbglvl, "send plugin start/end: %s\n", sd->msg);
496 sd->signal(BNET_EOD); /* indicate end of plugin name data */
501 * Plugin name stream found during restore. The record passed in
502 * argument name was generated in send_plugin_name() above.
504 * Returns: true if start of stream
505 * false if end of steam
507 bool plugin_name_stream(JCR *jcr, char *name)
511 bool start, portable;
515 bpContext *plugin_ctx_list = jcr->plugin_ctx_list;
517 Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
518 skip_nonspaces(&p); /* skip over jcr->JobFiles */
522 /* Start of plugin data */
523 skip_nonspaces(&p); /* skip start/end flag */
525 portable = *p == '1';
526 skip_nonspaces(&p); /* skip portable flag */
531 * End of plugin data, notify plugin, then clear flags
533 Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
534 if (jcr->plugin && jcr->plugin->restoreFileStarted) {
535 plug_func(jcr->plugin)->endRestoreFile(jcr->plugin_ctx);
536 jcr->plugin->restoreFileStarted = false;
537 jcr->plugin->createFileCalled = false;
539 jcr->plugin_ctx = NULL;
543 if (!plugin_ctx_list) {
548 * After this point, we are dealing with a restore start
550 if (!get_plugin_name(jcr, cmd, &len)) {
555 * Search for correct plugin as specified on the command
557 foreach_alist(plugin, plugin_list) {
559 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
560 if (!for_this_plug(plugin, cmd, len)) {
564 jcr->plugin_ctx = &plugin_ctx_list[i];
565 jcr->plugin = plugin;
566 if (is_plugin_disabled(jcr)) {
569 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
570 event.eventType = bEventRestoreCommand;
571 if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx,
572 &event, cmd) != bRC_OK) {
575 if (plugin->restoreFileStarted)
577 Jmsg0(jcr, M_FATAL, 0, "Unbalanced call to startRestoreFile\n");
580 plug_func(plugin)->startRestoreFile(jcr->plugin_ctx, cmd);
581 plugin->restoreFileStarted = true;
584 Jmsg1(jcr, M_WARNING, 0, _("Plugin=%s not found.\n"), cmd);
591 * Tell the plugin to create the file. Return values are
592 * This is called only during Restore
595 * CF_SKIP -- skip processing this file
596 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
597 * CF_CREATED -- created, but no content to extract (typically directories)
600 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
602 bpContext *plugin_ctx = jcr->plugin_ctx;
603 Plugin *plugin = jcr->plugin;
604 struct restore_pkt rp;
608 if (!plugin || !plugin_ctx || !set_cmd_plugin(bfd, jcr) || jcr->is_job_canceled()) {
612 rp.pkt_size = sizeof(rp);
613 rp.pkt_end = sizeof(rp);
614 rp.delta_seq = attr->delta_seq;
615 rp.stream = attr->stream;
616 rp.data_stream = attr->data_stream;
617 rp.type = attr->type;
618 rp.file_index = attr->file_index;
619 rp.LinkFI = attr->LinkFI;
621 rp.statp = attr->statp; /* structure assignment */
622 rp.attrEx = attr->attrEx;
623 rp.ofname = attr->ofname;
624 rp.olname = attr->olname;
625 rp.where = jcr->where;
626 rp.RegexWhere = jcr->RegexWhere;
627 rp.replace = jcr->replace;
628 rp.create_status = CF_ERROR;
629 Dmsg4(dbglvl, "call plugin createFile stream=%d type=%d LinkFI=%d File=%s\n",
630 rp.stream, rp.type, rp.LinkFI, rp.ofname);
632 Dmsg1(dbglvl, "attrEx=\"%s\"\n", rp.attrEx);
634 if (!plugin->restoreFileStarted || plugin->createFileCalled)
636 Jmsg0(jcr, M_FATAL, 0, "Unbalanced call to createFile\n");
639 rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
641 Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
645 if (rp.create_status == CF_SKIP) {
648 if (rp.create_status == CF_ERROR) {
649 Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
653 /* Created link or directory? */
654 if (rp.create_status == CF_CREATED) {
655 return rp.create_status; /* yes, no need to bopen */
658 flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
659 Dmsg0(dbglvl, "call bopen\n");
660 int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
661 Dmsg1(50, "bopen status=%d\n", stat);
664 be.set_errno(bfd->berrno);
665 Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
666 attr->ofname, be.bstrerror());
667 Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
671 if (!is_bopen(bfd)) {
672 Dmsg0(000, "===== BFD is not open!!!!\n");
678 * Reset the file attributes after all file I/O is done -- this allows
679 * the previous access time/dates to be set properly, and it also allows
680 * us to properly set directory permissions.
681 * Not currently Implemented.
683 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
685 Dmsg0(dbglvl, "plugin_set_attributes\n");
689 pm_strcpy(attr->ofname, "*none*");
694 * Print to file the plugin info.
696 void dump_fd_plugin(Plugin *plugin, FILE *fp)
701 pInfo *info = (pInfo *)plugin->pinfo;
702 fprintf(fp, "\tversion=%d\n", info->version);
703 fprintf(fp, "\tdate=%s\n", NPRTB(info->plugin_date));
704 fprintf(fp, "\tmagic=%s\n", NPRTB(info->plugin_magic));
705 fprintf(fp, "\tauthor=%s\n", NPRTB(info->plugin_author));
706 fprintf(fp, "\tlicence=%s\n", NPRTB(info->plugin_license));
707 fprintf(fp, "\tversion=%s\n", NPRTB(info->plugin_version));
708 fprintf(fp, "\tdescription=%s\n", NPRTB(info->plugin_description));
712 * This entry point is called internally by Bacula to ensure
713 * that the plugin IO calls come into this code.
715 void load_fd_plugins(const char *plugin_dir)
720 Dmsg0(dbglvl, "plugin dir is NULL\n");
724 plugin_list = New(alist(10, not_owned_by_alist));
725 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type,
726 is_plugin_compatible)) {
727 /* Either none found, or some error */
728 if (plugin_list->size() == 0) {
731 Dmsg0(dbglvl, "No plugins loaded\n");
736 /* Plug entry points called from findlib */
737 plugin_bopen = my_plugin_bopen;
738 plugin_bclose = my_plugin_bclose;
739 plugin_bread = my_plugin_bread;
740 plugin_bwrite = my_plugin_bwrite;
741 plugin_blseek = my_plugin_blseek;
744 * Verify that the plugin is acceptable, and print information
747 foreach_alist(plugin, plugin_list) {
748 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
749 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
752 dbg_plugin_add_hook(dump_fd_plugin);
756 * Check if a plugin is compatible. Called by the load_plugin function
757 * to allow us to verify the plugin.
759 static bool is_plugin_compatible(Plugin *plugin)
761 pInfo *info = (pInfo *)plugin->pinfo;
762 Dmsg0(50, "is_plugin_compatible called\n");
763 if (debug_level >= 50) {
764 dump_fd_plugin(plugin, stdin);
766 if (strcmp(info->plugin_magic, FD_PLUGIN_MAGIC) != 0) {
767 Jmsg(NULL, M_ERROR, 0, _("Plugin magic wrong. Plugin=%s wanted=%s got=%s\n"),
768 plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic);
769 Dmsg3(50, "Plugin magic wrong. Plugin=%s wanted=%s got=%s\n",
770 plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic);
774 if (info->version != FD_PLUGIN_INTERFACE_VERSION) {
775 Jmsg(NULL, M_ERROR, 0, _("Plugin version incorrect. Plugin=%s wanted=%d got=%d\n"),
776 plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version);
777 Dmsg3(50, "Plugin version incorrect. Plugin=%s wanted=%d got=%d\n",
778 plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version);
781 if (strcmp(info->plugin_license, "Bacula AGPLv3") != 0 &&
782 strcmp(info->plugin_license, "AGPLv3") != 0) {
783 Jmsg(NULL, M_ERROR, 0, _("Plugin license incompatible. Plugin=%s license=%s\n"),
784 plugin->file, info->plugin_license);
785 Dmsg2(50, "Plugin license incompatible. Plugin=%s license=%s\n",
786 plugin->file, info->plugin_license);
795 * Create a new instance of each plugin for this Job
796 * Note, plugin_list can exist but jcr->plugin_ctx_list can
797 * be NULL if no plugins were loaded.
799 void new_plugins(JCR *jcr)
805 Dmsg0(dbglvl, "plugin list is NULL\n");
808 if (jcr->is_job_canceled() || jcr->JobId == 0) {
812 int num = plugin_list->size();
815 Dmsg0(dbglvl, "No plugins loaded\n");
819 jcr->plugin_ctx_list = (bpContext *)malloc(sizeof(bpContext) * num);
821 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
822 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
823 foreach_alist(plugin, plugin_list) {
824 /* Start a new instance of each plugin */
825 bacula_ctx *b_ctx = (bacula_ctx *)malloc(sizeof(bacula_ctx));
826 memset(b_ctx, 0, sizeof(bacula_ctx));
828 plugin_ctx_list[i].bContext = (void *)b_ctx; /* Bacula private context */
829 plugin_ctx_list[i].pContext = NULL;
830 if (plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]) != bRC_OK) {
831 b_ctx->disabled = true;
837 * Free the plugin instances for this Job
839 void free_plugins(JCR *jcr)
844 if (!plugin_list || !jcr->plugin_ctx_list) {
845 return; /* no plugins, nothing to do */
848 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
849 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
850 foreach_alist(plugin, plugin_list) {
851 /* Free the plugin instance */
852 plug_func(plugin)->freePlugin(&plugin_ctx_list[i]);
853 free(plugin_ctx_list[i++].bContext); /* free Bacula private context */
855 free(plugin_ctx_list);
856 jcr->plugin_ctx_list = NULL;
859 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
862 Plugin *plugin = (Plugin *)jcr->plugin;
865 Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
866 if (!plugin || !jcr->plugin_ctx) {
869 io.pkt_size = sizeof(io);
870 io.pkt_end = sizeof(io);
879 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
880 bfd->berrno = io.io_errno;
882 errno = b_errno_win32;
885 bfd->lerror = io.lerror;
887 Dmsg1(50, "Return from plugin open status=%d\n", io.status);
891 static int my_plugin_bclose(BFILE *bfd)
894 Plugin *plugin = (Plugin *)jcr->plugin;
897 Dmsg0(dbglvl, "===== plugin_bclose\n");
898 if (!plugin || !jcr->plugin_ctx) {
901 io.pkt_size = sizeof(io);
902 io.pkt_end = sizeof(io);
908 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
909 bfd->berrno = io.io_errno;
911 errno = b_errno_win32;
914 bfd->lerror = io.lerror;
916 Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status);
920 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
923 Plugin *plugin = (Plugin *)jcr->plugin;
926 Dmsg0(dbglvl, "plugin_bread\n");
927 if (!plugin || !jcr->plugin_ctx) {
930 io.pkt_size = sizeof(io);
931 io.pkt_end = sizeof(io);
934 io.buf = (char *)buf;
938 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
939 bfd->offset = io.offset;
940 bfd->berrno = io.io_errno;
942 errno = b_errno_win32;
945 bfd->lerror = io.lerror;
947 return (ssize_t)io.status;
950 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
953 Plugin *plugin = (Plugin *)jcr->plugin;
956 Dmsg0(dbglvl, "plugin_bwrite\n");
957 if (!plugin || !jcr->plugin_ctx) {
960 io.pkt_size = sizeof(io);
961 io.pkt_end = sizeof(io);
964 io.buf = (char *)buf;
967 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
968 bfd->berrno = io.io_errno;
970 errno = b_errno_win32;
973 bfd->lerror = io.lerror;
975 return (ssize_t)io.status;
978 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
981 Plugin *plugin = (Plugin *)jcr->plugin;
984 Dmsg0(dbglvl, "plugin_bseek\n");
985 if (!plugin || !jcr->plugin_ctx) {
988 io.pkt_size = sizeof(io);
989 io.pkt_end = sizeof(io);
995 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
996 bfd->berrno = io.io_errno;
998 errno = b_errno_win32;
1000 errno = io.io_errno;
1001 bfd->lerror = io.lerror;
1003 return (boffset_t)io.offset;
1006 /* ==============================================================
1008 * Callbacks from the plugin
1010 * ==============================================================
1012 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
1019 switch (var) { /* General variables, no need of ctx */
1021 *((char **)value) = my_name;
1023 case bVarWorkingDir:
1024 *(void **)value = me->working_directory;
1027 *(char **)value = exepath;
1033 if (!ctx) { /* Other variables need context */
1037 jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1044 *((int *)value) = jcr->JobId;
1045 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
1048 *((int *)value) = jcr->getJobLevel();
1049 Dmsg1(dbglvl, "Bacula: return bVarJobLevel=%d\n", jcr->getJobLevel());
1052 *((int *)value) = jcr->getJobType();
1053 Dmsg1(dbglvl, "Bacula: return bVarJobType=%d\n", jcr->getJobType());
1056 *((char **)value) = jcr->client_name;
1057 Dmsg1(dbglvl, "Bacula: return Client_name=%s\n", jcr->client_name);
1060 *((char **)value) = jcr->Job;
1061 Dmsg1(dbglvl, "Bacula: return Job name=%s\n", jcr->Job);
1064 *((int *)value) = jcr->JobStatus;
1065 Dmsg1(dbglvl, "Bacula: return bVarJobStatus=%d\n", jcr->JobStatus);
1068 *((int *)value) = (int)jcr->mtime;
1069 Dmsg1(dbglvl, "Bacula: return since=%d\n", (int)jcr->mtime);
1072 *((int *)value) = (int)jcr->accurate;
1073 Dmsg1(dbglvl, "Bacula: return accurate=%d\n", (int)jcr->accurate);
1076 break; /* a write only variable, ignore read request */
1080 *(void **)value = g_pVSSClient->GetVssObject();
1085 case bVarVssDllHandle:
1088 *(void **)value = g_pVSSClient->GetVssDllHandle();
1094 *(char **)value = jcr->where;
1096 case bVarRegexWhere:
1097 *(char **)value = jcr->RegexWhere;
1100 case bVarFDName: /* get warning with g++ if we missed one */
1101 case bVarWorkingDir:
1108 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
1111 if (!value || !ctx) {
1114 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
1115 jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1119 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
1122 if (!accurate_mark_file_as_seen(jcr, (char *)value)) {
1132 static bRC baculaRegisterEvents(bpContext *ctx, ...)
1141 va_start(args, ctx);
1142 while ((event = va_arg(args, uint32_t))) {
1143 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
1149 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
1150 int type, utime_t mtime, const char *fmt, ...)
1157 jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1162 va_start(arg_ptr, fmt);
1163 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1165 Jmsg(jcr, type, mtime, "%s", buf);
1169 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
1170 int level, const char *fmt, ...)
1175 va_start(arg_ptr, fmt);
1176 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1178 d_msg(file, line, level, "%s", buf);
1182 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
1186 return sm_malloc(file, line, size);
1188 return malloc(size);
1192 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem)
1195 sm_free(file, line, mem);
1201 static bool is_ctx_good(bpContext *ctx, JCR *&jcr, bacula_ctx *&bctx)
1206 bctx = (bacula_ctx *)ctx->bContext;
1218 * Let the plugin define files/directories to be excluded
1219 * from the main backup.
1221 static bRC baculaAddExclude(bpContext *ctx, const char *file)
1225 if (!is_ctx_good(ctx, jcr, bctx)) {
1231 if (!bctx->exclude) {
1232 bctx->exclude = new_exclude(jcr);
1233 new_options(jcr, bctx->exclude);
1235 set_incexe(jcr, bctx->exclude);
1236 add_file_to_fileset(jcr, file, true);
1237 Dmsg1(100, "Add exclude file=%s\n", file);
1242 * Let the plugin define files/directories to be excluded
1243 * from the main backup.
1245 static bRC baculaAddInclude(bpContext *ctx, const char *file)
1249 if (!is_ctx_good(ctx, jcr, bctx)) {
1255 if (!bctx->include) {
1256 bctx->include = new_preinclude(jcr);
1257 new_options(jcr, bctx->include);
1259 set_incexe(jcr, bctx->include);
1260 add_file_to_fileset(jcr, file, true);
1261 Dmsg1(100, "Add include file=%s\n", file);
1265 static bRC baculaAddOptions(bpContext *ctx, const char *opts)
1269 if (!is_ctx_good(ctx, jcr, bctx)) {
1275 add_options_to_fileset(jcr, opts);
1276 Dmsg1(1000, "Add options=%s\n", opts);
1280 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type)
1284 if (!is_ctx_good(ctx, jcr, bctx)) {
1290 add_regex_to_fileset(jcr, item, type);
1291 Dmsg1(100, "Add regex=%s\n", item);
1295 static bRC baculaAddWild(bpContext *ctx, const char *item, int type)
1299 if (!is_ctx_good(ctx, jcr, bctx)) {
1305 add_wild_to_fileset(jcr, item, type);
1306 Dmsg1(100, "Add wild=%s\n", item);
1310 static bRC baculaNewOptions(bpContext *ctx)
1314 if (!is_ctx_good(ctx, jcr, bctx)) {
1317 (void)new_options(jcr, NULL);
1321 static bRC baculaNewInclude(bpContext *ctx)
1325 if (!is_ctx_good(ctx, jcr, bctx)) {
1328 (void)new_include(jcr);
1334 * Check if a file have to be backuped using Accurate code
1336 static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp)
1341 bRC ret = bRC_Error;
1343 if (!is_ctx_good(ctx, jcr, bctx)) {
1352 * Copy fname and link because save_file() zaps them. This
1353 * avoids zaping the plugin's strings.
1355 ff_pkt->type = sp->type;
1357 Jmsg0(jcr, M_FATAL, 0, _("Command plugin: no fname in baculaCheckChanges packet.\n"));
1361 ff_pkt->fname = sp->fname;
1362 ff_pkt->link = sp->link;
1363 memcpy(&ff_pkt->statp, &sp->statp, sizeof(ff_pkt->statp));
1365 if (check_changes(jcr, ff_pkt)) {
1371 /* check_changes() can update delta sequence number, return it to the
1374 sp->delta_seq = ff_pkt->delta_seq;
1377 Dmsg1(100, "checkChanges=%i\n", ret);
1384 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
1385 int (*plugin_bclose)(JCR *jcr) = NULL;
1386 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
1387 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
1388 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
1390 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
1395 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
1400 int main(int argc, char *argv[])
1402 char plugin_dir[1000];
1407 strcpy(my_name, "test-fd");
1409 getcwd(plugin_dir, sizeof(plugin_dir)-1);
1410 load_fd_plugins(plugin_dir);
1418 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
1419 generate_plugin_event(jcr1, bEventJobEnd);
1420 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
1422 generate_plugin_event(jcr2, bEventJobEnd);
1427 Dmsg0(dbglvl, "bacula: OK ...\n");
1428 close_memory_pool();
1429 sm_dump(false); /* unit test */
1433 #endif /* TEST_PROGRAM */