2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2008 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 two of the GNU 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 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
37 const int dbglvl = 50;
39 const char *plugin_type = "-fd.dll";
41 const char *plugin_type = "-fd.so";
44 extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
46 /* Function pointers to be set here */
47 extern DLL_IMP_EXP int (*plugin_bopen)(BFILE *bfd, const char *fname, int flags, mode_t mode);
48 extern DLL_IMP_EXP int (*plugin_bclose)(BFILE *bfd);
49 extern DLL_IMP_EXP ssize_t (*plugin_bread)(BFILE *bfd, void *buf, size_t count);
50 extern DLL_IMP_EXP ssize_t (*plugin_bwrite)(BFILE *bfd, void *buf, size_t count);
51 extern DLL_IMP_EXP boffset_t (*plugin_blseek)(BFILE *bfd, boffset_t offset, int whence);
54 /* Forward referenced functions */
55 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value);
56 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value);
57 static bRC baculaRegisterEvents(bpContext *ctx, ...);
58 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
59 int type, time_t mtime, const char *fmt, ...);
60 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
61 int level, const char *fmt, ...);
62 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
64 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem);
67 * These will be plugged into the global pointer structure for
70 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode);
71 static int my_plugin_bclose(BFILE *bfd);
72 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count);
73 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count);
74 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence);
78 static bInfo binfo = {
80 FD_PLUGIN_INTERFACE_VERSION
83 /* Bacula entry points */
84 static bFuncs bfuncs = {
86 FD_PLUGIN_INTERFACE_VERSION,
97 * Bacula private context
100 JCR *jcr; /* jcr for plugin */
101 bRC rc; /* last return code */
102 bool disabled; /* set if plugin disabled */
105 static bool is_plugin_disabled(JCR *jcr)
108 if (!jcr->plugin_ctx) {
111 b_ctx = (bacula_ctx *)jcr->plugin_ctx->bContext;
112 return b_ctx->disabled;
117 * Create a plugin event
119 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)
125 if (!plugin_list || !jcr->plugin_ctx_list) {
126 return; /* Return if no plugins loaded */
129 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
130 event.eventType = eventType;
132 Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
134 /* Pass event to every plugin */
135 foreach_alist(plugin, plugin_list) {
137 jcr->plugin_ctx = &plugin_ctx_list[i++];
138 jcr->plugin = plugin;
139 if (is_plugin_disabled(jcr)) {
142 rc = plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, value);
149 jcr->plugin_ctx = NULL;
154 * Sequence of calls for a backup:
155 * 1. plugin_save() here is called with ff_pkt
156 * 2. we find the plugin requested on the command string
157 * 3. we generate a bEventBackupCommand event to the specified plugin
158 * and pass it the command string.
159 * 4. we make a startPluginBackup call to the plugin, which gives
160 * us the data we need in save_pkt
161 * 5. we call Bacula's save_file() subroutine to save the specified
162 * file. The plugin will be called at pluginIO() to supply the
165 * Sequence of calls for restore:
166 * See subroutine plugin_name_stream() below.
168 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
174 char *cmd = ff_pkt->top_fname;
178 if (!plugin_list || !jcr->plugin_ctx_list) {
179 return 1; /* Return if no plugins loaded */
182 jcr->cmd_plugin = true;
183 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
184 event.eventType = bEventBackupCommand;
186 /* Handle plugin command here backup */
187 Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
188 if (!(p = strchr(cmd, ':'))) {
189 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
197 /* Note, we stop the loop on the first plugin that matches the name */
198 foreach_alist(plugin, plugin_list) {
199 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
200 if (strncmp(plugin->file, cmd, len) != 0) {
205 * We put the current plugin pointer, and the plugin context
206 * into the jcr, because during save_file(), the plugin
207 * will be called many times and these values are needed.
209 jcr->plugin_ctx = &plugin_ctx_list[i];
210 jcr->plugin = plugin;
211 if (is_plugin_disabled(jcr)) {
215 Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
216 /* Send the backup command to the right plugin*/
217 if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, cmd) != bRC_OK) {
220 /* Loop getting filenames to backup then saving them */
221 while (!job_canceled(jcr)) {
222 memset(&sp, 0, sizeof(sp));
223 sp.pkt_size = sizeof(sp);
224 sp.pkt_end = sizeof(sp);
227 Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
229 /* Get the file save parameters */
230 if (plug_func(plugin)->startBackupFile(jcr->plugin_ctx, &sp) != bRC_OK) {
233 if (sp.type == 0 || sp.fname == NULL) {
234 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\" returned bad startBackupFile packet.\n"),
238 jcr->plugin_sp = &sp;
240 ff_pkt->fname = sp.fname;
241 ff_pkt->link = sp.link;
242 ff_pkt->type = sp.type;
243 memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
244 Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname);
245 save_file(jcr, ff_pkt, true);
246 bRC rc = plug_func(plugin)->endBackupFile(jcr->plugin_ctx);
247 if (rc == bRC_More) {
254 Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
257 jcr->cmd_plugin = false;
259 jcr->plugin_ctx = NULL;
264 * Send plugin name start/end record to SD
266 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
269 int index = jcr->JobFiles;
270 struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
273 Jmsg0(jcr, M_FATAL, 0, _("Plugin save packet not found.\n"));
278 index++; /* JobFiles not incremented yet */
280 Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
281 /* Send stream header */
282 if (!sd->fsend("%ld %d 0", index, STREAM_PLUGIN_NAME)) {
283 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
287 Dmsg1(50, "send: %s\n", sd->msg);
290 /* Send data -- not much */
291 stat = sd->fsend("%ld 1 %d %s%c", index, sp->portable, sp->cmd, 0);
293 /* Send end of data */
294 stat = sd->fsend("0 0");
297 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
301 Dmsg1(dbglvl, "send: %s\n", sd->msg);
302 sd->signal(BNET_EOD); /* indicate end of plugin name data */
307 * Plugin name stream found during restore. The record passed in
308 * argument name was generated in send_plugin_name() above.
310 * Returns: true if start of stream
311 * false if end of steam
313 bool plugin_name_stream(JCR *jcr, char *name)
317 bool start, portable;
321 bpContext *plugin_ctx_list = jcr->plugin_ctx_list;
323 Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
324 skip_nonspaces(&p); /* skip over jcr->JobFiles */
328 /* Start of plugin data */
329 skip_nonspaces(&p); /* skip start/end flag */
331 portable = *p == '1';
332 skip_nonspaces(&p); /* skip portable flag */
337 * End of plugin data, notify plugin, then clear flags
339 Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
341 plug_func(jcr->plugin)->endRestoreFile(jcr->plugin_ctx);
343 jcr->plugin_ctx = NULL;
347 if (!plugin_ctx_list) {
352 * After this point, we are dealing with a restore start
355 // Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
356 if (!(p = strchr(cmd, ':'))) {
357 Jmsg1(jcr, M_ERROR, 0,
358 _("Malformed plugin command. Name not terminated by colon: %s\n"), cmd);
367 * Search for correct plugin as specified on the command
369 foreach_alist(plugin, plugin_list) {
371 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
372 if (strncmp(plugin->file, cmd, len) != 0) {
376 jcr->plugin_ctx = &plugin_ctx_list[i];
377 jcr->plugin = plugin;
378 if (is_plugin_disabled(jcr)) {
381 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
382 event.eventType = bEventRestoreCommand;
383 if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx,
384 &event, cmd) != bRC_OK) {
387 /* ***FIXME**** check error code */
388 plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd);
391 Jmsg1(jcr, M_WARNING, 0, _("Plugin=%s not found.\n"), cmd);
398 * Tell the plugin to create the file. Return values are
401 * CF_SKIP -- skip processing this file
402 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
403 * CF_CREATED -- created, but no content to extract (typically directories)
406 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
408 bpContext *plugin_ctx = jcr->plugin_ctx;
409 Plugin *plugin = jcr->plugin;
410 struct restore_pkt rp;
414 if (!plugin || !plugin_ctx || !set_cmd_plugin(bfd, jcr)) {
417 rp.pkt_size = sizeof(rp);
418 rp.pkt_end = sizeof(rp);
419 rp.stream = attr->stream;
420 rp.data_stream = attr->data_stream;
421 rp.type = attr->type;
422 rp.file_index = attr->file_index;
423 rp.LinkFI = attr->LinkFI;
425 rp.statp = attr->statp; /* structure assignment */
426 rp.attrEx = attr->attrEx;
427 rp.ofname = attr->ofname;
428 rp.olname = attr->olname;
429 rp.where = jcr->where;
430 rp.RegexWhere = jcr->RegexWhere;
431 rp.replace = jcr->replace;
432 rp.create_status = CF_ERROR;
433 Dmsg1(dbglvl, "call plugin createFile=%s\n", rp.ofname);
434 rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
436 Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
440 if (rp.create_status == CF_ERROR) {
441 Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
445 /* Created link or directory? */
446 if (rp.create_status == CF_CREATED) {
447 return rp.create_status; /* yes, no need to bopen */
450 flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
451 Dmsg0(dbglvl, "call bopen\n");
452 int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
453 Dmsg1(50, "bopen status=%d\n", stat);
456 be.set_errno(bfd->berrno);
457 Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
458 attr->ofname, be.bstrerror());
459 Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
463 if (!is_bopen(bfd)) {
464 Dmsg0(000, "===== BFD is not open!!!!\n");
470 * Reset the file attributes after all file I/O is done -- this allows
471 * the previous access time/dates to be set properly, and it also allows
472 * us to properly set directory permissions.
474 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
476 Dmsg0(dbglvl, "plugin_set_attributes\n");
480 pm_strcpy(attr->ofname, "*none*");
485 * This entry point is called internally by Bacula to ensure
486 * that the plugin IO calls come into this code.
488 void load_fd_plugins(const char *plugin_dir)
493 Dmsg0(dbglvl, "plugin dir is NULL\n");
497 plugin_list = New(alist(10, not_owned_by_alist));
498 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
499 /* Either none found, or some error */
500 if (plugin_list->size() == 0) {
503 Dmsg0(dbglvl, "No plugins loaded\n");
508 /* Plug entry points called from findlib */
509 plugin_bopen = my_plugin_bopen;
510 plugin_bclose = my_plugin_bclose;
511 plugin_bread = my_plugin_bread;
512 plugin_bwrite = my_plugin_bwrite;
513 plugin_blseek = my_plugin_blseek;
514 foreach_alist(plugin, plugin_list) {
515 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
516 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
522 * Create a new instance of each plugin for this Job
523 * Note, plugin_list can exist but jcr->plugin_ctx_list can
524 * be NULL if no plugins were loaded.
526 void new_plugins(JCR *jcr)
532 Dmsg0(dbglvl, "plugin list is NULL\n");
536 int num = plugin_list->size();
539 Dmsg0(dbglvl, "No plugins loaded\n");
543 jcr->plugin_ctx_list = (bpContext *)malloc(sizeof(bpContext) * num);
545 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
546 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
547 foreach_alist(plugin, plugin_list) {
548 /* Start a new instance of each plugin */
549 bacula_ctx *b_ctx = (bacula_ctx *)malloc(sizeof(bacula_ctx));
550 memset(b_ctx, 0, sizeof(bacula_ctx));
552 plugin_ctx_list[i].bContext = (void *)b_ctx; /* Bacula private context */
553 plugin_ctx_list[i].pContext = NULL;
554 if (plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]) != bRC_OK) {
555 b_ctx->disabled = true;
561 * Free the plugin instances for this Job
563 void free_plugins(JCR *jcr)
568 if (!plugin_list || !jcr->plugin_ctx_list) {
569 return; /* no plugins, nothing to do */
572 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
573 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
574 foreach_alist(plugin, plugin_list) {
575 /* Free the plugin instance */
576 plug_func(plugin)->freePlugin(&plugin_ctx_list[i]);
577 free(plugin_ctx_list[i++].bContext); /* free Bacula private context */
579 free(plugin_ctx_list);
580 jcr->plugin_ctx_list = NULL;
583 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
586 Plugin *plugin = (Plugin *)jcr->plugin;
589 Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
590 io.pkt_size = sizeof(io);
591 io.pkt_end = sizeof(io);
600 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
601 bfd->berrno = io.io_errno;
603 errno = b_errno_win32;
606 bfd->lerror = io.lerror;
608 Dmsg1(50, "Return from plugin open status=%d\n", io.status);
612 static int my_plugin_bclose(BFILE *bfd)
615 Plugin *plugin = (Plugin *)jcr->plugin;
617 Dmsg0(dbglvl, "===== plugin_bclose\n");
618 io.pkt_size = sizeof(io);
619 io.pkt_end = sizeof(io);
625 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
626 bfd->berrno = io.io_errno;
628 errno = b_errno_win32;
631 bfd->lerror = io.lerror;
633 Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status);
637 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
640 Plugin *plugin = (Plugin *)jcr->plugin;
642 Dmsg0(dbglvl, "plugin_bread\n");
643 io.pkt_size = sizeof(io);
644 io.pkt_end = sizeof(io);
647 io.buf = (char *)buf;
650 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
651 bfd->berrno = io.io_errno;
653 errno = b_errno_win32;
656 bfd->lerror = io.lerror;
658 return (ssize_t)io.status;
661 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
664 Plugin *plugin = (Plugin *)jcr->plugin;
666 Dmsg0(dbglvl, "plugin_bwrite\n");
667 io.pkt_size = sizeof(io);
668 io.pkt_end = sizeof(io);
671 io.buf = (char *)buf;
674 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
675 bfd->berrno = io.io_errno;
677 errno = b_errno_win32;
680 bfd->lerror = io.lerror;
682 return (ssize_t)io.status;
685 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
688 Plugin *plugin = (Plugin *)jcr->plugin;
690 Dmsg0(dbglvl, "plugin_bseek\n");
691 io.pkt_size = sizeof(io);
692 io.pkt_end = sizeof(io);
698 plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
699 bfd->berrno = io.io_errno;
701 errno = b_errno_win32;
704 bfd->lerror = io.lerror;
706 return (boffset_t)io.offset;
709 /* ==============================================================
711 * Callbacks from the plugin
713 * ==============================================================
715 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
718 jcr = ((bacula_ctx *)ctx->bContext)->jcr;
719 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
720 if (!value || !ctx) {
723 jcr = ((bacula_ctx *)ctx->bContext)->jcr;
727 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
730 *((int *)value) = jcr->JobId;
731 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
734 *((char **)value) = my_name;
735 Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
748 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
750 if (!value || !ctx) {
753 Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
757 static bRC baculaRegisterEvents(bpContext *ctx, ...)
767 while ((event = va_arg(args, uint32_t))) {
768 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
774 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
775 int type, time_t mtime, const char *fmt, ...)
782 jcr = ((bacula_ctx *)ctx->bContext)->jcr;
787 va_start(arg_ptr, fmt);
788 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
790 Jmsg(jcr, type, mtime, "%s", buf);
794 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
795 int level, const char *fmt, ...)
800 va_start(arg_ptr, fmt);
801 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
803 d_msg(file, line, level, "%s", buf);
807 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
811 return sm_malloc(file, line, size);
817 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem)
820 sm_free(file, line, mem);
830 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
831 int (*plugin_bclose)(JCR *jcr) = NULL;
832 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
833 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
834 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
836 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
841 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
846 int main(int argc, char *argv[])
848 char plugin_dir[1000];
853 strcpy(my_name, "test-fd");
855 getcwd(plugin_dir, sizeof(plugin_dir)-1);
856 load_fd_plugins(plugin_dir);
864 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
865 generate_plugin_event(jcr1, bEventJobEnd);
866 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
868 generate_plugin_event(jcr2, bEventJobEnd);
873 Dmsg0(dbglvl, "bacula: OK ...\n");
879 #endif /* TEST_PROGRAM */