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 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
148 event.eventType = bEventBackupCommand;
150 /* Handle plugin command here backup */
151 Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
152 if (!(p = strchr(cmd, ':'))) {
153 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
161 foreach_alist(plugin, plugin_list) {
162 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
163 if (strncmp(plugin->file, cmd, len) != 0) {
167 Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
168 /* Send the backup command */
169 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], &event, cmd) != bRC_OK) {
172 /* Loop getting filenames to backup then saving them */
173 while (!job_canceled(jcr)) {
174 memset(&sp, 0, sizeof(sp));
175 sp.pkt_size = sizeof(sp);
176 sp.pkt_end = sizeof(sp);
179 Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
181 /* Get the file save parameters */
182 if (plug_func(plugin)->startBackupFile(&plugin_ctx_list[i], &sp) != bRC_OK) {
185 if (sp.type == 0 || sp.fname == NULL) {
186 Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\" returned bad startBackupFile packet.\n"),
190 jcr->plugin_ctx = &plugin_ctx_list[i];
191 jcr->plugin = plugin;
192 jcr->plugin_sp = &sp;
194 ff_pkt->fname = sp.fname;
195 ff_pkt->type = sp.type;
196 memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
197 Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname);
198 save_file(jcr, ff_pkt, true);
199 bRC rc = plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]);
200 if (rc == bRC_More) {
206 Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
213 * Send plugin name start/end record to SD
215 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
218 struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
220 Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
221 /* Send stream header */
222 if (!sd->fsend("%ld %d 0", jcr->JobFiles+1, STREAM_PLUGIN_NAME)) {
223 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
227 Dmsg1(000, "send: %s\n", sd->msg);
230 /* Send data -- not much */
231 stat = sd->fsend("%ld 1 %d %s%c", jcr->JobFiles+1, sp->portable, sp->cmd, 0);
233 /* Send end of data */
234 stat = sd->fsend("0 0");
237 Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
241 Dmsg1(dbglvl, "send: %s\n", sd->msg);
242 sd->signal(BNET_EOD); /* indicate end of plugin name data */
247 * Plugin name stream found during restore. The record passed in
248 * argument name was generated in send_plugin_name() above.
250 void plugin_name_stream(JCR *jcr, char *name)
254 bool start, portable;
258 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
259 if (!plugin_ctx_list) {
263 Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
264 skip_nonspaces(&p); /* skip over jcr->JobFiles */
268 /* Start of plugin data */
269 skip_nonspaces(&p); /* skip start/end flag */
271 portable = *p == '1';
272 skip_nonspaces(&p); /* skip portable flag */
277 * End of plugin data, notify plugin, then clear flags
279 plugin = (Plugin *)jcr->plugin;
280 plug_func(plugin)->endRestoreFile(&plugin_ctx_list[i]);
281 jcr->plugin_ctx = NULL;
287 * After this point, we are dealing with a restore start
290 Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
291 if (!(p = strchr(cmd, ':'))) {
292 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
301 * Search for correct plugin as specified on the command
303 foreach_alist(plugin, plugin_list) {
305 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
306 if (strncmp(plugin->file, cmd, len) != 0) {
310 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
311 event.eventType = bEventRestoreCommand;
312 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i],
313 &event, cmd) != bRC_OK) {
316 jcr->plugin_ctx = &plugin_ctx_list[i];
317 jcr->plugin = plugin;
325 * Tell the plugin to create the file. Return values are
328 * CF_SKIP -- skip processing this file
329 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
330 * CF_CREATED -- created, but no content to extract (typically directories)
333 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
335 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
336 Plugin *plugin = (Plugin *)jcr->plugin;
337 struct restore_pkt rp;
340 if (!set_cmd_plugin(bfd, jcr)) {
343 rp.pkt_size = sizeof(rp);
344 rp.pkt_end = sizeof(rp);
345 rp.stream = attr->stream;
346 rp.data_stream = attr->data_stream;
347 rp.type = attr->type;
348 rp.file_index = attr->file_index;
349 rp.LinkFI = attr->LinkFI;
351 rp.statp = attr->statp; /* structure assignment */
352 rp.attrEx = attr->attrEx;
353 rp.ofname = attr->ofname;
354 rp.olname = attr->olname;
355 rp.where = jcr->where;
356 rp.RegexWhere = jcr->RegexWhere;
357 rp.replace = jcr->replace;
358 if (plug_func(plugin)->createFile(plugin_ctx, &rp) != bRC_OK) {
361 io.pkt_size = sizeof(io);
362 io.pkt_end = sizeof(io);
366 io.mode = 0777 & attr->statp.st_mode;
368 if (plug_func(plugin)->pluginIO(plugin_ctx, &io) != bRC_OK) {
375 * Reset the file attributes after all file I/O is done -- this allows
376 * the previous access time/dates to be set properly, and it also allows
377 * us to properly set directory permissions.
379 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
385 * This entry point is called internally by Bacula to ensure
386 * that the plugin IO calls come into this code.
388 void load_fd_plugins(const char *plugin_dir)
393 Dmsg0(dbglvl, "plugin dir is NULL\n");
397 plugin_list = New(alist(10, not_owned_by_alist));
398 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
399 /* Either none found, or some error */
400 if (plugin_list->size() == 0) {
403 Dmsg0(dbglvl, "No plugins loaded\n");
408 /* Plug entry points called from findlib */
409 plugin_bopen = my_plugin_bopen;
410 plugin_bclose = my_plugin_bclose;
411 plugin_bread = my_plugin_bread;
412 plugin_bwrite = my_plugin_bwrite;
413 plugin_blseek = my_plugin_blseek;
414 foreach_alist(plugin, plugin_list) {
415 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
416 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
422 * Create a new instance of each plugin for this Job
423 * Note, plugin_list can exist but jcr->plugin_ctx_list can
424 * be NULL if no plugins were loaded.
426 void new_plugins(JCR *jcr)
432 Dmsg0(dbglvl, "plugin list is NULL\n");
436 int num = plugin_list->size();
439 Dmsg0(dbglvl, "No plugins loaded\n");
443 jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
445 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
446 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
447 foreach_alist(plugin, plugin_list) {
448 /* Start a new instance of each plugin */
449 plugin_ctx_list[i].bContext = (void *)jcr;
450 plugin_ctx_list[i].pContext = NULL;
451 plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
456 * Free the plugin instances for this Job
458 void free_plugins(JCR *jcr)
463 if (!plugin_list || !jcr->plugin_ctx_list) {
464 return; /* no plugins, nothing to do */
467 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
468 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
469 foreach_alist(plugin, plugin_list) {
470 /* Free the plugin instance */
471 plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
473 free(plugin_ctx_list);
474 jcr->plugin_ctx_list = NULL;
477 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
480 Plugin *plugin = (Plugin *)jcr->plugin;
481 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
483 Dmsg0(dbglvl, "plugin_bopen\n");
484 io.pkt_size = sizeof(io);
485 io.pkt_end = sizeof(io);
494 plug_func(plugin)->pluginIO(plugin_ctx, &io);
495 bfd->berrno = io.io_errno;
497 errno = b_errno_win32;
500 bfd->lerror = io.lerror;
505 static int my_plugin_bclose(BFILE *bfd)
508 Plugin *plugin = (Plugin *)jcr->plugin;
509 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
511 Dmsg0(dbglvl, "plugin_bclose\n");
512 io.pkt_size = sizeof(io);
513 io.pkt_end = sizeof(io);
519 plug_func(plugin)->pluginIO(plugin_ctx, &io);
520 bfd->berrno = io.io_errno;
522 errno = b_errno_win32;
525 bfd->lerror = io.lerror;
530 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
533 Plugin *plugin = (Plugin *)jcr->plugin;
534 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
536 Dmsg0(dbglvl, "plugin_bread\n");
537 io.pkt_size = sizeof(io);
538 io.pkt_end = sizeof(io);
541 io.buf = (char *)buf;
544 plug_func(plugin)->pluginIO(plugin_ctx, &io);
545 bfd->berrno = io.io_errno;
547 errno = b_errno_win32;
550 bfd->lerror = io.lerror;
552 return (ssize_t)io.status;
555 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
558 Plugin *plugin = (Plugin *)jcr->plugin;
559 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
561 Dmsg0(dbglvl, "plugin_bwrite\n");
562 io.pkt_size = sizeof(io);
563 io.pkt_end = sizeof(io);
566 io.buf = (char *)buf;
569 plug_func(plugin)->pluginIO(plugin_ctx, &io);
570 bfd->berrno = io.io_errno;
572 errno = b_errno_win32;
575 bfd->lerror = io.lerror;
577 return (ssize_t)io.status;
580 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
583 Plugin *plugin = (Plugin *)jcr->plugin;
584 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
586 Dmsg0(dbglvl, "plugin_bseek\n");
587 io.pkt_size = sizeof(io);
588 io.pkt_end = sizeof(io);
594 plug_func(plugin)->pluginIO(plugin_ctx, &io);
595 bfd->berrno = io.io_errno;
597 errno = b_errno_win32;
600 bfd->lerror = io.lerror;
602 return (boffset_t)io.offset;
605 /* ==============================================================
607 * Callbacks from the plugin
609 * ==============================================================
611 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
613 JCR *jcr = (JCR *)(ctx->bContext);
614 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
618 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
621 *((int *)value) = jcr->JobId;
622 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
625 *((char **)value) = my_name;
626 Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
639 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
641 Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
645 static bRC baculaRegisterEvents(bpContext *ctx, ...)
651 while ((event = va_arg(args, uint32_t))) {
652 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
658 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
659 int type, time_t mtime, const char *fmt, ...)
663 JCR *jcr = (JCR *)(ctx->bContext);
665 va_start(arg_ptr, fmt);
666 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
668 Jmsg(jcr, type, mtime, "%s", buf);
672 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
673 int level, const char *fmt, ...)
678 va_start(arg_ptr, fmt);
679 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
681 d_msg(file, line, level, "%s", buf);
687 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
688 int (*plugin_bclose)(JCR *jcr) = NULL;
689 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
690 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
691 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
693 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
698 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
703 int main(int argc, char *argv[])
705 char plugin_dir[1000];
710 strcpy(my_name, "test-fd");
712 getcwd(plugin_dir, sizeof(plugin_dir)-1);
713 load_fd_plugins(plugin_dir);
721 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
722 generate_plugin_event(jcr1, bEventJobEnd);
723 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
725 generate_plugin_event(jcr2, bEventJobEnd);
730 Dmsg0(dbglvl, "bacula: OK ...\n");
736 #endif /* TEST_PROGRAM */