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, ...);
63 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode);
64 static int my_plugin_bclose(BFILE *bfd);
65 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count);
66 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count);
67 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence);
71 static bInfo binfo = {
73 FD_PLUGIN_INTERFACE_VERSION
76 /* Bacula entry points */
77 static bFuncs bfuncs = {
79 FD_PLUGIN_INTERFACE_VERSION,
89 * Create a plugin event
91 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)
97 if (!plugin_list || !jcr->plugin_ctx_list) {
98 return; /* Return if no plugins loaded */
101 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
102 event.eventType = eventType;
104 Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
106 /* Pass event to every plugin */
107 foreach_alist(plugin, plugin_list) {
109 rc = plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i++], &event, value);
119 * Sequence of calls for a backup:
120 * 1. plugin_save() here is called with ff_pkt
121 * 2. we find the plugin requested on the command string
122 * 3. we generate a bEventBackupCommand event to the specified plugin
123 * and pass it the command string.
124 * 4. we make a startPluginBackup call to the plugin, which gives
125 * us the data we need in save_pkt
126 * 5. we call Bacula's save_file() subroutine to save the specified
127 * file. The plugin will be called at pluginIO() to supply the
130 * Sequence of calls for restore:
131 * See subroutine plugin_name_stream() below.
133 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
139 char *cmd = ff_pkt->top_fname;
143 if (!plugin_list || !jcr->plugin_ctx_list) {
144 return 1; /* Return if no plugins loaded */
147 jcr->cmd_plugin = true;
148 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
149 event.eventType = bEventBackupCommand;
151 /* Handle plugin command here backup */
152 Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
153 if (!(p = strchr(cmd, ':'))) {
154 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
162 foreach_alist(plugin, plugin_list) {
163 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
164 if (strncmp(plugin->file, cmd, len) != 0) {
168 Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
169 /* Send the backup command to the right plugin*/
170 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], &event, cmd) != bRC_OK) {
173 /* Loop getting filenames to backup then saving them */
174 while (!job_canceled(jcr)) {
175 memset(&sp, 0, sizeof(sp));
176 sp.pkt_size = sizeof(sp);
177 sp.pkt_end = sizeof(sp);
180 Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
182 /* Get the file save parameters */
183 if (plug_func(plugin)->startBackupFile(&plugin_ctx_list[i], &sp) != bRC_OK) {
186 if (sp.type == 0 || sp.fname == NULL) {
187 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\" returned bad startBackupFile packet.\n"),
191 jcr->plugin_ctx = &plugin_ctx_list[i];
192 jcr->plugin = plugin;
193 jcr->plugin_sp = &sp;
195 ff_pkt->fname = sp.fname;
196 ff_pkt->link = sp.link;
197 ff_pkt->type = sp.type;
198 memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
199 Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname);
200 save_file(jcr, ff_pkt, true);
201 bRC rc = plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]);
202 if (rc == bRC_More) {
208 Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
211 jcr->cmd_plugin = false;
216 * Send plugin name start/end record to SD
218 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
221 struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
223 Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
224 /* Send stream header */
225 if (!sd->fsend("%ld %d 0", jcr->JobFiles+1, STREAM_PLUGIN_NAME)) {
226 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
230 Dmsg1(50, "send: %s\n", sd->msg);
233 /* Send data -- not much */
234 stat = sd->fsend("%ld 1 %d %s%c", jcr->JobFiles+1, sp->portable, sp->cmd, 0);
236 /* Send end of data */
237 stat = sd->fsend("0 0");
240 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
244 Dmsg1(dbglvl, "send: %s\n", sd->msg);
245 sd->signal(BNET_EOD); /* indicate end of plugin name data */
250 * Plugin name stream found during restore. The record passed in
251 * argument name was generated in send_plugin_name() above.
253 * Returns: true if start of stream
254 * false if end of steam
256 bool plugin_name_stream(JCR *jcr, char *name)
260 bool start, portable;
264 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
266 Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
267 skip_nonspaces(&p); /* skip over jcr->JobFiles */
271 /* Start of plugin data */
272 skip_nonspaces(&p); /* skip start/end flag */
274 portable = *p == '1';
275 skip_nonspaces(&p); /* skip portable flag */
280 * End of plugin data, notify plugin, then clear flags
282 Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
284 plugin = (Plugin *)jcr->plugin;
285 plug_func(plugin)->endRestoreFile((bpContext *)jcr->plugin_ctx);
287 jcr->plugin_ctx = NULL;
291 if (!plugin_ctx_list) {
296 * After this point, we are dealing with a restore start
299 // Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
300 if (!(p = strchr(cmd, ':'))) {
301 Jmsg1(jcr, M_ERROR, 0,
302 _("Malformed plugin command. Name not terminated by colon: %s\n"), cmd);
311 * Search for correct plugin as specified on the command
313 foreach_alist(plugin, plugin_list) {
315 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
316 if (strncmp(plugin->file, cmd, len) != 0) {
320 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
321 event.eventType = bEventRestoreCommand;
322 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i],
323 &event, cmd) != bRC_OK) {
326 jcr->plugin_ctx = &plugin_ctx_list[i];
327 jcr->plugin = plugin;
328 plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd);
336 * Tell the plugin to create the file. Return values are
339 * CF_SKIP -- skip processing this file
340 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
341 * CF_CREATED -- created, but no content to extract (typically directories)
344 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
346 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
347 Plugin *plugin = (Plugin *)jcr->plugin;
348 struct restore_pkt rp;
352 if (!set_cmd_plugin(bfd, jcr)) {
355 rp.pkt_size = sizeof(rp);
356 rp.pkt_end = sizeof(rp);
357 rp.stream = attr->stream;
358 rp.data_stream = attr->data_stream;
359 rp.type = attr->type;
360 rp.file_index = attr->file_index;
361 rp.LinkFI = attr->LinkFI;
363 rp.statp = attr->statp; /* structure assignment */
364 rp.attrEx = attr->attrEx;
365 rp.ofname = attr->ofname;
366 rp.olname = attr->olname;
367 rp.where = jcr->where;
368 rp.RegexWhere = jcr->RegexWhere;
369 rp.replace = jcr->replace;
370 rp.create_status = CF_ERROR;
371 Dmsg1(dbglvl, "call plugin createFile=%s\n", rp.ofname);
372 rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
374 Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
378 if (rp.create_status == CF_ERROR) {
379 Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
383 /* Created link or directory? */
384 if (rp.create_status == CF_CREATED) {
385 return rp.create_status; /* yes, no need to bopen */
388 flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
389 Dmsg0(dbglvl, "call bopen\n");
390 int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
391 Dmsg1(50, "bopen status=%d\n", stat);
394 be.set_errno(bfd->berrno);
395 Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
396 attr->ofname, be.bstrerror());
397 Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
401 if (!is_bopen(bfd)) {
402 Dmsg0(000, "===== BFD is not open!!!!\n");
408 * Reset the file attributes after all file I/O is done -- this allows
409 * the previous access time/dates to be set properly, and it also allows
410 * us to properly set directory permissions.
412 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
414 Dmsg0(dbglvl, "plugin_set_attributes\n");
418 pm_strcpy(attr->ofname, "*none*");
423 * This entry point is called internally by Bacula to ensure
424 * that the plugin IO calls come into this code.
426 void load_fd_plugins(const char *plugin_dir)
431 Dmsg0(dbglvl, "plugin dir is NULL\n");
435 plugin_list = New(alist(10, not_owned_by_alist));
436 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
437 /* Either none found, or some error */
438 if (plugin_list->size() == 0) {
441 Dmsg0(dbglvl, "No plugins loaded\n");
446 /* Plug entry points called from findlib */
447 plugin_bopen = my_plugin_bopen;
448 plugin_bclose = my_plugin_bclose;
449 plugin_bread = my_plugin_bread;
450 plugin_bwrite = my_plugin_bwrite;
451 plugin_blseek = my_plugin_blseek;
452 foreach_alist(plugin, plugin_list) {
453 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
454 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
460 * Create a new instance of each plugin for this Job
461 * Note, plugin_list can exist but jcr->plugin_ctx_list can
462 * be NULL if no plugins were loaded.
464 void new_plugins(JCR *jcr)
470 Dmsg0(dbglvl, "plugin list is NULL\n");
474 int num = plugin_list->size();
477 Dmsg0(dbglvl, "No plugins loaded\n");
481 jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
483 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
484 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
485 foreach_alist(plugin, plugin_list) {
486 /* Start a new instance of each plugin */
487 plugin_ctx_list[i].bContext = (void *)jcr;
488 plugin_ctx_list[i].pContext = NULL;
489 plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
494 * Free the plugin instances for this Job
496 void free_plugins(JCR *jcr)
501 if (!plugin_list || !jcr->plugin_ctx_list) {
502 return; /* no plugins, nothing to do */
505 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
506 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
507 foreach_alist(plugin, plugin_list) {
508 /* Free the plugin instance */
509 plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
511 free(plugin_ctx_list);
512 jcr->plugin_ctx_list = NULL;
515 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
518 Plugin *plugin = (Plugin *)jcr->plugin;
519 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
521 Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
522 io.pkt_size = sizeof(io);
523 io.pkt_end = sizeof(io);
532 plug_func(plugin)->pluginIO(plugin_ctx, &io);
533 bfd->berrno = io.io_errno;
535 errno = b_errno_win32;
538 bfd->lerror = io.lerror;
540 Dmsg1(50, "Return from plugin open status=%d\n", io.status);
544 static int my_plugin_bclose(BFILE *bfd)
547 Plugin *plugin = (Plugin *)jcr->plugin;
548 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
550 Dmsg0(dbglvl, "===== plugin_bclose\n");
551 io.pkt_size = sizeof(io);
552 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(dbglvl, "plugin_bclose stat=%d\n", io.status);
570 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
573 Plugin *plugin = (Plugin *)jcr->plugin;
574 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
576 Dmsg0(dbglvl, "plugin_bread\n");
577 io.pkt_size = sizeof(io);
578 io.pkt_end = sizeof(io);
581 io.buf = (char *)buf;
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 return (ssize_t)io.status;
595 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
598 Plugin *plugin = (Plugin *)jcr->plugin;
599 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
601 Dmsg0(dbglvl, "plugin_bwrite\n");
602 io.pkt_size = sizeof(io);
603 io.pkt_end = sizeof(io);
606 io.buf = (char *)buf;
609 plug_func(plugin)->pluginIO(plugin_ctx, &io);
610 bfd->berrno = io.io_errno;
612 errno = b_errno_win32;
615 bfd->lerror = io.lerror;
617 return (ssize_t)io.status;
620 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
623 Plugin *plugin = (Plugin *)jcr->plugin;
624 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
626 Dmsg0(dbglvl, "plugin_bseek\n");
627 io.pkt_size = sizeof(io);
628 io.pkt_end = sizeof(io);
634 plug_func(plugin)->pluginIO(plugin_ctx, &io);
635 bfd->berrno = io.io_errno;
637 errno = b_errno_win32;
640 bfd->lerror = io.lerror;
642 return (boffset_t)io.offset;
645 /* ==============================================================
647 * Callbacks from the plugin
649 * ==============================================================
651 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
653 JCR *jcr = (JCR *)(ctx->bContext);
654 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
658 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
661 *((int *)value) = jcr->JobId;
662 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
665 *((char **)value) = my_name;
666 Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
679 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
681 Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
685 static bRC baculaRegisterEvents(bpContext *ctx, ...)
691 while ((event = va_arg(args, uint32_t))) {
692 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
698 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
699 int type, time_t mtime, const char *fmt, ...)
703 JCR *jcr = (JCR *)(ctx->bContext);
705 va_start(arg_ptr, fmt);
706 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
708 Jmsg(jcr, type, mtime, "%s", buf);
712 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
713 int level, const char *fmt, ...)
718 va_start(arg_ptr, fmt);
719 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
721 d_msg(file, line, level, "%s", buf);
727 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
728 int (*plugin_bclose)(JCR *jcr) = NULL;
729 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
730 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
731 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
733 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
738 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
743 int main(int argc, char *argv[])
745 char plugin_dir[1000];
750 strcpy(my_name, "test-fd");
752 getcwd(plugin_dir, sizeof(plugin_dir)-1);
753 load_fd_plugins(plugin_dir);
761 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
762 generate_plugin_event(jcr1, bEventJobEnd);
763 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
765 generate_plugin_event(jcr2, bEventJobEnd);
770 Dmsg0(dbglvl, "bacula: OK ...\n");
776 #endif /* TEST_PROGRAM */