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 in_plugin; /* in plugin code */
103 bool disabled; /* set if plugin disabled */
107 * Create a plugin event
109 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)
115 if (!plugin_list || !jcr->plugin_ctx_list) {
116 return; /* Return if no plugins loaded */
119 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
120 event.eventType = eventType;
122 Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
124 /* Pass event to every plugin */
125 foreach_alist(plugin, plugin_list) {
127 rc = plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i++], &event, value);
137 * Sequence of calls for a backup:
138 * 1. plugin_save() here is called with ff_pkt
139 * 2. we find the plugin requested on the command string
140 * 3. we generate a bEventBackupCommand event to the specified plugin
141 * and pass it the command string.
142 * 4. we make a startPluginBackup call to the plugin, which gives
143 * us the data we need in save_pkt
144 * 5. we call Bacula's save_file() subroutine to save the specified
145 * file. The plugin will be called at pluginIO() to supply the
148 * Sequence of calls for restore:
149 * See subroutine plugin_name_stream() below.
151 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
157 char *cmd = ff_pkt->top_fname;
161 if (!plugin_list || !jcr->plugin_ctx_list) {
162 return 1; /* Return if no plugins loaded */
165 jcr->cmd_plugin = true;
166 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
167 event.eventType = bEventBackupCommand;
169 /* Handle plugin command here backup */
170 Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
171 if (!(p = strchr(cmd, ':'))) {
172 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
180 foreach_alist(plugin, plugin_list) {
181 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
182 if (strncmp(plugin->file, cmd, len) != 0) {
186 Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
187 /* Send the backup command to the right plugin*/
188 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], &event, cmd) != bRC_OK) {
191 /* Loop getting filenames to backup then saving them */
192 while (!job_canceled(jcr)) {
193 memset(&sp, 0, sizeof(sp));
194 sp.pkt_size = sizeof(sp);
195 sp.pkt_end = sizeof(sp);
198 Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
200 /* Get the file save parameters */
201 if (plug_func(plugin)->startBackupFile(&plugin_ctx_list[i], &sp) != bRC_OK) {
204 if (sp.type == 0 || sp.fname == NULL) {
205 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\" returned bad startBackupFile packet.\n"),
209 jcr->plugin_ctx = &plugin_ctx_list[i];
210 jcr->plugin = plugin;
211 jcr->plugin_sp = &sp;
213 ff_pkt->fname = sp.fname;
214 ff_pkt->link = sp.link;
215 ff_pkt->type = sp.type;
216 memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
217 Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname);
218 save_file(jcr, ff_pkt, true);
219 bRC rc = plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]);
220 if (rc == bRC_More) {
226 Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
229 jcr->cmd_plugin = false;
234 * Send plugin name start/end record to SD
236 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
239 int index = jcr->JobFiles;
240 struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
243 index++; /* JobFiles not incremented yet */
245 Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
246 /* Send stream header */
247 if (!sd->fsend("%ld %d 0", index, STREAM_PLUGIN_NAME)) {
248 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
252 Dmsg1(50, "send: %s\n", sd->msg);
255 /* Send data -- not much */
256 stat = sd->fsend("%ld 1 %d %s%c", index, sp->portable, sp->cmd, 0);
258 /* Send end of data */
259 stat = sd->fsend("0 0");
262 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
266 Dmsg1(dbglvl, "send: %s\n", sd->msg);
267 sd->signal(BNET_EOD); /* indicate end of plugin name data */
272 * Plugin name stream found during restore. The record passed in
273 * argument name was generated in send_plugin_name() above.
275 * Returns: true if start of stream
276 * false if end of steam
278 bool plugin_name_stream(JCR *jcr, char *name)
282 bool start, portable;
286 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
288 Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
289 skip_nonspaces(&p); /* skip over jcr->JobFiles */
293 /* Start of plugin data */
294 skip_nonspaces(&p); /* skip start/end flag */
296 portable = *p == '1';
297 skip_nonspaces(&p); /* skip portable flag */
302 * End of plugin data, notify plugin, then clear flags
304 Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
306 plugin = (Plugin *)jcr->plugin;
307 plug_func(plugin)->endRestoreFile((bpContext *)jcr->plugin_ctx);
309 jcr->plugin_ctx = NULL;
313 if (!plugin_ctx_list) {
318 * After this point, we are dealing with a restore start
321 // Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
322 if (!(p = strchr(cmd, ':'))) {
323 Jmsg1(jcr, M_ERROR, 0,
324 _("Malformed plugin command. Name not terminated by colon: %s\n"), cmd);
333 * Search for correct plugin as specified on the command
335 foreach_alist(plugin, plugin_list) {
337 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
338 if (strncmp(plugin->file, cmd, len) != 0) {
342 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
343 event.eventType = bEventRestoreCommand;
344 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i],
345 &event, cmd) != bRC_OK) {
348 jcr->plugin_ctx = &plugin_ctx_list[i];
349 jcr->plugin = plugin;
350 plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd);
358 * Tell the plugin to create the file. Return values are
361 * CF_SKIP -- skip processing this file
362 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
363 * CF_CREATED -- created, but no content to extract (typically directories)
366 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
368 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
369 Plugin *plugin = (Plugin *)jcr->plugin;
370 struct restore_pkt rp;
374 if (!set_cmd_plugin(bfd, jcr)) {
377 rp.pkt_size = sizeof(rp);
378 rp.pkt_end = sizeof(rp);
379 rp.stream = attr->stream;
380 rp.data_stream = attr->data_stream;
381 rp.type = attr->type;
382 rp.file_index = attr->file_index;
383 rp.LinkFI = attr->LinkFI;
385 rp.statp = attr->statp; /* structure assignment */
386 rp.attrEx = attr->attrEx;
387 rp.ofname = attr->ofname;
388 rp.olname = attr->olname;
389 rp.where = jcr->where;
390 rp.RegexWhere = jcr->RegexWhere;
391 rp.replace = jcr->replace;
392 rp.create_status = CF_ERROR;
393 Dmsg1(dbglvl, "call plugin createFile=%s\n", rp.ofname);
394 rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
396 Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
400 if (rp.create_status == CF_ERROR) {
401 Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
405 /* Created link or directory? */
406 if (rp.create_status == CF_CREATED) {
407 return rp.create_status; /* yes, no need to bopen */
410 flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
411 Dmsg0(dbglvl, "call bopen\n");
412 int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
413 Dmsg1(50, "bopen status=%d\n", stat);
416 be.set_errno(bfd->berrno);
417 Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
418 attr->ofname, be.bstrerror());
419 Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
423 if (!is_bopen(bfd)) {
424 Dmsg0(000, "===== BFD is not open!!!!\n");
430 * Reset the file attributes after all file I/O is done -- this allows
431 * the previous access time/dates to be set properly, and it also allows
432 * us to properly set directory permissions.
434 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
436 Dmsg0(dbglvl, "plugin_set_attributes\n");
440 pm_strcpy(attr->ofname, "*none*");
445 * This entry point is called internally by Bacula to ensure
446 * that the plugin IO calls come into this code.
448 void load_fd_plugins(const char *plugin_dir)
453 Dmsg0(dbglvl, "plugin dir is NULL\n");
457 plugin_list = New(alist(10, not_owned_by_alist));
458 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
459 /* Either none found, or some error */
460 if (plugin_list->size() == 0) {
463 Dmsg0(dbglvl, "No plugins loaded\n");
468 /* Plug entry points called from findlib */
469 plugin_bopen = my_plugin_bopen;
470 plugin_bclose = my_plugin_bclose;
471 plugin_bread = my_plugin_bread;
472 plugin_bwrite = my_plugin_bwrite;
473 plugin_blseek = my_plugin_blseek;
474 foreach_alist(plugin, plugin_list) {
475 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
476 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
482 * Create a new instance of each plugin for this Job
483 * Note, plugin_list can exist but jcr->plugin_ctx_list can
484 * be NULL if no plugins were loaded.
486 void new_plugins(JCR *jcr)
492 Dmsg0(dbglvl, "plugin list is NULL\n");
496 int num = plugin_list->size();
499 Dmsg0(dbglvl, "No plugins loaded\n");
503 jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
505 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
506 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
507 foreach_alist(plugin, plugin_list) {
508 /* Start a new instance of each plugin */
509 bacula_ctx *b_ctx = (bacula_ctx *)malloc(sizeof(bacula_ctx));
510 memset(b_ctx, 0, sizeof(bacula_ctx));
512 plugin_ctx_list[i].bContext = (void *)b_ctx; /* Bacula private context */
513 plugin_ctx_list[i].pContext = NULL;
514 plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
519 * Free the plugin instances for this Job
521 void free_plugins(JCR *jcr)
526 if (!plugin_list || !jcr->plugin_ctx_list) {
527 return; /* no plugins, nothing to do */
530 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
531 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
532 foreach_alist(plugin, plugin_list) {
533 free(plugin_ctx_list[i].bContext); /* free Bacula private context */
534 /* Free the plugin instance */
535 plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
537 free(plugin_ctx_list);
538 jcr->plugin_ctx_list = NULL;
541 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
544 Plugin *plugin = (Plugin *)jcr->plugin;
545 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
547 Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
548 io.pkt_size = sizeof(io);
549 io.pkt_end = sizeof(io);
558 plug_func(plugin)->pluginIO(plugin_ctx, &io);
559 bfd->berrno = io.io_errno;
561 errno = b_errno_win32;
564 bfd->lerror = io.lerror;
566 Dmsg1(50, "Return from plugin open status=%d\n", io.status);
570 static int my_plugin_bclose(BFILE *bfd)
573 Plugin *plugin = (Plugin *)jcr->plugin;
574 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
576 Dmsg0(dbglvl, "===== plugin_bclose\n");
577 io.pkt_size = sizeof(io);
578 io.pkt_end = sizeof(io);
584 plug_func(plugin)->pluginIO(plugin_ctx, &io);
585 bfd->berrno = io.io_errno;
587 errno = b_errno_win32;
590 bfd->lerror = io.lerror;
592 Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status);
596 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
599 Plugin *plugin = (Plugin *)jcr->plugin;
600 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
602 Dmsg0(dbglvl, "plugin_bread\n");
603 io.pkt_size = sizeof(io);
604 io.pkt_end = sizeof(io);
607 io.buf = (char *)buf;
610 plug_func(plugin)->pluginIO(plugin_ctx, &io);
611 bfd->berrno = io.io_errno;
613 errno = b_errno_win32;
616 bfd->lerror = io.lerror;
618 return (ssize_t)io.status;
621 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
624 Plugin *plugin = (Plugin *)jcr->plugin;
625 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
627 Dmsg0(dbglvl, "plugin_bwrite\n");
628 io.pkt_size = sizeof(io);
629 io.pkt_end = sizeof(io);
632 io.buf = (char *)buf;
635 plug_func(plugin)->pluginIO(plugin_ctx, &io);
636 bfd->berrno = io.io_errno;
638 errno = b_errno_win32;
641 bfd->lerror = io.lerror;
643 return (ssize_t)io.status;
646 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
649 Plugin *plugin = (Plugin *)jcr->plugin;
650 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
652 Dmsg0(dbglvl, "plugin_bseek\n");
653 io.pkt_size = sizeof(io);
654 io.pkt_end = sizeof(io);
660 plug_func(plugin)->pluginIO(plugin_ctx, &io);
661 bfd->berrno = io.io_errno;
663 errno = b_errno_win32;
666 bfd->lerror = io.lerror;
668 return (boffset_t)io.offset;
671 /* ==============================================================
673 * Callbacks from the plugin
675 * ==============================================================
677 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
679 JCR *jcr = ((bacula_ctx *)ctx->bContext)->jcr;
680 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
684 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
687 *((int *)value) = jcr->JobId;
688 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
691 *((char **)value) = my_name;
692 Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
705 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
707 Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
711 static bRC baculaRegisterEvents(bpContext *ctx, ...)
717 while ((event = va_arg(args, uint32_t))) {
718 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
724 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
725 int type, time_t mtime, const char *fmt, ...)
729 JCR *jcr = ((bacula_ctx *)ctx->bContext)->jcr;
731 va_start(arg_ptr, fmt);
732 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
734 Jmsg(jcr, type, mtime, "%s", buf);
738 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
739 int level, const char *fmt, ...)
744 va_start(arg_ptr, fmt);
745 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
747 d_msg(file, line, level, "%s", buf);
751 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
755 return sm_malloc(file, line, size);
761 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem)
764 sm_free(file, line, mem);
774 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
775 int (*plugin_bclose)(JCR *jcr) = NULL;
776 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
777 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
778 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
780 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
785 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
790 int main(int argc, char *argv[])
792 char plugin_dir[1000];
797 strcpy(my_name, "test-fd");
799 getcwd(plugin_dir, sizeof(plugin_dir)-1);
800 load_fd_plugins(plugin_dir);
808 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
809 generate_plugin_event(jcr1, bEventJobEnd);
810 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
812 generate_plugin_event(jcr2, bEventJobEnd);
817 Dmsg0(dbglvl, "bacula: OK ...\n");
823 #endif /* TEST_PROGRAM */