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->type = sp.type;
197 memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
198 Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname);
199 save_file(jcr, ff_pkt, true);
200 bRC rc = plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]);
201 if (rc == bRC_More) {
207 Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
210 jcr->cmd_plugin = false;
215 * Send plugin name start/end record to SD
217 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
220 struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
222 Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
223 /* Send stream header */
224 if (!sd->fsend("%ld %d 0", jcr->JobFiles+1, STREAM_PLUGIN_NAME)) {
225 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
229 Dmsg1(50, "send: %s\n", sd->msg);
232 /* Send data -- not much */
233 stat = sd->fsend("%ld 1 %d %s%c", jcr->JobFiles+1, sp->portable, sp->cmd, 0);
235 /* Send end of data */
236 stat = sd->fsend("0 0");
239 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
243 Dmsg1(dbglvl, "send: %s\n", sd->msg);
244 sd->signal(BNET_EOD); /* indicate end of plugin name data */
249 * Plugin name stream found during restore. The record passed in
250 * argument name was generated in send_plugin_name() above.
252 * Returns: true if start of stream
253 * false if end of steam
255 bool plugin_name_stream(JCR *jcr, char *name)
259 bool start, portable;
263 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
265 Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
266 skip_nonspaces(&p); /* skip over jcr->JobFiles */
270 /* Start of plugin data */
271 skip_nonspaces(&p); /* skip start/end flag */
273 portable = *p == '1';
274 skip_nonspaces(&p); /* skip portable flag */
279 * End of plugin data, notify plugin, then clear flags
281 Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
283 plugin = (Plugin *)jcr->plugin;
284 plug_func(plugin)->endRestoreFile((bpContext *)jcr->plugin_ctx);
286 jcr->plugin_ctx = NULL;
290 if (!plugin_ctx_list) {
295 * After this point, we are dealing with a restore start
298 // Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
299 if (!(p = strchr(cmd, ':'))) {
300 Jmsg1(jcr, M_ERROR, 0,
301 _("Malformed plugin command. Name not terminated by colon: %s\n"), cmd);
310 * Search for correct plugin as specified on the command
312 foreach_alist(plugin, plugin_list) {
314 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
315 if (strncmp(plugin->file, cmd, len) != 0) {
319 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
320 event.eventType = bEventRestoreCommand;
321 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i],
322 &event, cmd) != bRC_OK) {
325 jcr->plugin_ctx = &plugin_ctx_list[i];
326 jcr->plugin = plugin;
327 plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd);
335 * Tell the plugin to create the file. Return values are
338 * CF_SKIP -- skip processing this file
339 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
340 * CF_CREATED -- created, but no content to extract (typically directories)
343 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
345 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
346 Plugin *plugin = (Plugin *)jcr->plugin;
347 struct restore_pkt rp;
351 if (!set_cmd_plugin(bfd, jcr)) {
354 rp.pkt_size = sizeof(rp);
355 rp.pkt_end = sizeof(rp);
356 rp.stream = attr->stream;
357 rp.data_stream = attr->data_stream;
358 rp.type = attr->type;
359 rp.file_index = attr->file_index;
360 rp.LinkFI = attr->LinkFI;
362 rp.statp = attr->statp; /* structure assignment */
363 rp.attrEx = attr->attrEx;
364 rp.ofname = attr->ofname;
365 rp.olname = attr->olname;
366 rp.where = jcr->where;
367 rp.RegexWhere = jcr->RegexWhere;
368 rp.replace = jcr->replace;
369 rp.create_status = CF_ERROR;
370 Dmsg1(dbglvl, "call plugin createFile=%s\n", rp.ofname);
371 rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
373 Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
377 if (rp.create_status == CF_ERROR) {
378 Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
382 /* Created link or directory? */
383 if (rp.create_status == CF_CREATED) {
384 return rp.create_status; /* yes, no need to bopen */
387 flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
388 Dmsg0(dbglvl, "call bopen\n");
389 int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
390 Dmsg1(50, "bopen status=%d\n", stat);
393 be.set_errno(bfd->berrno);
394 Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
395 attr->ofname, be.bstrerror());
396 Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
400 if (!is_bopen(bfd)) {
401 Dmsg0(000, "===== BFD is not open!!!!\n");
407 * Reset the file attributes after all file I/O is done -- this allows
408 * the previous access time/dates to be set properly, and it also allows
409 * us to properly set directory permissions.
411 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
413 Dmsg0(dbglvl, "plugin_set_attributes\n");
417 pm_strcpy(attr->ofname, "*none*");
422 * This entry point is called internally by Bacula to ensure
423 * that the plugin IO calls come into this code.
425 void load_fd_plugins(const char *plugin_dir)
430 Dmsg0(dbglvl, "plugin dir is NULL\n");
434 plugin_list = New(alist(10, not_owned_by_alist));
435 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
436 /* Either none found, or some error */
437 if (plugin_list->size() == 0) {
440 Dmsg0(dbglvl, "No plugins loaded\n");
445 /* Plug entry points called from findlib */
446 plugin_bopen = my_plugin_bopen;
447 plugin_bclose = my_plugin_bclose;
448 plugin_bread = my_plugin_bread;
449 plugin_bwrite = my_plugin_bwrite;
450 plugin_blseek = my_plugin_blseek;
451 foreach_alist(plugin, plugin_list) {
452 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
453 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
459 * Create a new instance of each plugin for this Job
460 * Note, plugin_list can exist but jcr->plugin_ctx_list can
461 * be NULL if no plugins were loaded.
463 void new_plugins(JCR *jcr)
469 Dmsg0(dbglvl, "plugin list is NULL\n");
473 int num = plugin_list->size();
476 Dmsg0(dbglvl, "No plugins loaded\n");
480 jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
482 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
483 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
484 foreach_alist(plugin, plugin_list) {
485 /* Start a new instance of each plugin */
486 plugin_ctx_list[i].bContext = (void *)jcr;
487 plugin_ctx_list[i].pContext = NULL;
488 plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
493 * Free the plugin instances for this Job
495 void free_plugins(JCR *jcr)
500 if (!plugin_list || !jcr->plugin_ctx_list) {
501 return; /* no plugins, nothing to do */
504 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
505 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
506 foreach_alist(plugin, plugin_list) {
507 /* Free the plugin instance */
508 plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
510 free(plugin_ctx_list);
511 jcr->plugin_ctx_list = NULL;
514 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
517 Plugin *plugin = (Plugin *)jcr->plugin;
518 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
520 Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
521 io.pkt_size = sizeof(io);
522 io.pkt_end = sizeof(io);
531 plug_func(plugin)->pluginIO(plugin_ctx, &io);
532 bfd->berrno = io.io_errno;
534 errno = b_errno_win32;
537 bfd->lerror = io.lerror;
539 Dmsg1(50, "Return from plugin open status=%d\n", io.status);
543 static int my_plugin_bclose(BFILE *bfd)
546 Plugin *plugin = (Plugin *)jcr->plugin;
547 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
549 Dmsg0(dbglvl, "===== plugin_bclose\n");
550 io.pkt_size = sizeof(io);
551 io.pkt_end = sizeof(io);
557 plug_func(plugin)->pluginIO(plugin_ctx, &io);
558 bfd->berrno = io.io_errno;
560 errno = b_errno_win32;
563 bfd->lerror = io.lerror;
565 Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status);
569 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
572 Plugin *plugin = (Plugin *)jcr->plugin;
573 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
575 Dmsg0(dbglvl, "plugin_bread\n");
576 io.pkt_size = sizeof(io);
577 io.pkt_end = sizeof(io);
580 io.buf = (char *)buf;
583 plug_func(plugin)->pluginIO(plugin_ctx, &io);
584 bfd->berrno = io.io_errno;
586 errno = b_errno_win32;
589 bfd->lerror = io.lerror;
591 return (ssize_t)io.status;
594 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
597 Plugin *plugin = (Plugin *)jcr->plugin;
598 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
600 Dmsg0(dbglvl, "plugin_bwrite\n");
601 io.pkt_size = sizeof(io);
602 io.pkt_end = sizeof(io);
605 io.buf = (char *)buf;
608 plug_func(plugin)->pluginIO(plugin_ctx, &io);
609 bfd->berrno = io.io_errno;
611 errno = b_errno_win32;
614 bfd->lerror = io.lerror;
616 return (ssize_t)io.status;
619 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
622 Plugin *plugin = (Plugin *)jcr->plugin;
623 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
625 Dmsg0(dbglvl, "plugin_bseek\n");
626 io.pkt_size = sizeof(io);
627 io.pkt_end = sizeof(io);
633 plug_func(plugin)->pluginIO(plugin_ctx, &io);
634 bfd->berrno = io.io_errno;
636 errno = b_errno_win32;
639 bfd->lerror = io.lerror;
641 return (boffset_t)io.offset;
644 /* ==============================================================
646 * Callbacks from the plugin
648 * ==============================================================
650 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
652 JCR *jcr = (JCR *)(ctx->bContext);
653 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
657 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
660 *((int *)value) = jcr->JobId;
661 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
664 *((char **)value) = my_name;
665 Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
678 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
680 Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
684 static bRC baculaRegisterEvents(bpContext *ctx, ...)
690 while ((event = va_arg(args, uint32_t))) {
691 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
697 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
698 int type, time_t mtime, const char *fmt, ...)
702 JCR *jcr = (JCR *)(ctx->bContext);
704 va_start(arg_ptr, fmt);
705 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
707 Jmsg(jcr, type, mtime, "%s", buf);
711 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
712 int level, const char *fmt, ...)
717 va_start(arg_ptr, fmt);
718 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
720 d_msg(file, line, level, "%s", buf);
726 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
727 int (*plugin_bclose)(JCR *jcr) = NULL;
728 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
729 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
730 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
732 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
737 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
742 int main(int argc, char *argv[])
744 char plugin_dir[1000];
749 strcpy(my_name, "test-fd");
751 getcwd(plugin_dir, sizeof(plugin_dir)-1);
752 load_fd_plugins(plugin_dir);
760 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
761 generate_plugin_event(jcr1, bEventJobEnd);
762 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
764 generate_plugin_event(jcr2, bEventJobEnd);
769 Dmsg0(dbglvl, "bacula: OK ...\n");
775 #endif /* TEST_PROGRAM */