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(000, "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 void plugin_name_stream(JCR *jcr, char *name)
256 bool start, portable;
260 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
261 if (!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
282 plugin = (Plugin *)jcr->plugin;
283 plug_func(plugin)->endRestoreFile((bpContext *)jcr->plugin_ctx);
285 jcr->plugin_ctx = NULL;
291 * After this point, we are dealing with a restore start
294 Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
295 if (!(p = strchr(cmd, ':'))) {
296 Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
305 * Search for correct plugin as specified on the command
307 foreach_alist(plugin, plugin_list) {
309 Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
310 if (strncmp(plugin->file, cmd, len) != 0) {
314 Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
315 event.eventType = bEventRestoreCommand;
316 if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i],
317 &event, cmd) != bRC_OK) {
320 jcr->plugin_ctx = &plugin_ctx_list[i];
321 jcr->plugin = plugin;
329 * Tell the plugin to create the file. Return values are
332 * CF_SKIP -- skip processing this file
333 * CF_EXTRACT -- extract the file (i.e.call i/o routines)
334 * CF_CREATED -- created, but no content to extract (typically directories)
337 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
339 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
340 Plugin *plugin = (Plugin *)jcr->plugin;
341 struct restore_pkt rp;
344 if (!set_cmd_plugin(bfd, jcr)) {
347 rp.pkt_size = sizeof(rp);
348 rp.pkt_end = sizeof(rp);
349 rp.stream = attr->stream;
350 rp.data_stream = attr->data_stream;
351 rp.type = attr->type;
352 rp.file_index = attr->file_index;
353 rp.LinkFI = attr->LinkFI;
355 rp.statp = attr->statp; /* structure assignment */
356 rp.attrEx = attr->attrEx;
357 rp.ofname = attr->ofname;
358 rp.olname = attr->olname;
359 rp.where = jcr->where;
360 rp.RegexWhere = jcr->RegexWhere;
361 rp.replace = jcr->replace;
362 if (plug_func(plugin)->createFile(plugin_ctx, &rp) != bRC_OK) {
365 io.pkt_size = sizeof(io);
366 io.pkt_end = sizeof(io);
370 io.mode = 0777 & attr->statp.st_mode;
372 if (plug_func(plugin)->pluginIO(plugin_ctx, &io) != bRC_OK) {
379 * Reset the file attributes after all file I/O is done -- this allows
380 * the previous access time/dates to be set properly, and it also allows
381 * us to properly set directory permissions.
383 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
389 * This entry point is called internally by Bacula to ensure
390 * that the plugin IO calls come into this code.
392 void load_fd_plugins(const char *plugin_dir)
397 Dmsg0(dbglvl, "plugin dir is NULL\n");
401 plugin_list = New(alist(10, not_owned_by_alist));
402 if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
403 /* Either none found, or some error */
404 if (plugin_list->size() == 0) {
407 Dmsg0(dbglvl, "No plugins loaded\n");
412 /* Plug entry points called from findlib */
413 plugin_bopen = my_plugin_bopen;
414 plugin_bclose = my_plugin_bclose;
415 plugin_bread = my_plugin_bread;
416 plugin_bwrite = my_plugin_bwrite;
417 plugin_blseek = my_plugin_blseek;
418 foreach_alist(plugin, plugin_list) {
419 Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
420 Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
426 * Create a new instance of each plugin for this Job
427 * Note, plugin_list can exist but jcr->plugin_ctx_list can
428 * be NULL if no plugins were loaded.
430 void new_plugins(JCR *jcr)
436 Dmsg0(dbglvl, "plugin list is NULL\n");
440 int num = plugin_list->size();
443 Dmsg0(dbglvl, "No plugins loaded\n");
447 jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
449 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
450 Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
451 foreach_alist(plugin, plugin_list) {
452 /* Start a new instance of each plugin */
453 plugin_ctx_list[i].bContext = (void *)jcr;
454 plugin_ctx_list[i].pContext = NULL;
455 plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
460 * Free the plugin instances for this Job
462 void free_plugins(JCR *jcr)
467 if (!plugin_list || !jcr->plugin_ctx_list) {
468 return; /* no plugins, nothing to do */
471 bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
472 Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
473 foreach_alist(plugin, plugin_list) {
474 /* Free the plugin instance */
475 plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
477 free(plugin_ctx_list);
478 jcr->plugin_ctx_list = NULL;
481 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
484 Plugin *plugin = (Plugin *)jcr->plugin;
485 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
487 Dmsg0(dbglvl, "plugin_bopen\n");
488 io.pkt_size = sizeof(io);
489 io.pkt_end = sizeof(io);
498 plug_func(plugin)->pluginIO(plugin_ctx, &io);
499 bfd->berrno = io.io_errno;
501 errno = b_errno_win32;
504 bfd->lerror = io.lerror;
509 static int my_plugin_bclose(BFILE *bfd)
512 Plugin *plugin = (Plugin *)jcr->plugin;
513 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
515 Dmsg0(dbglvl, "plugin_bclose\n");
516 io.pkt_size = sizeof(io);
517 io.pkt_end = sizeof(io);
523 plug_func(plugin)->pluginIO(plugin_ctx, &io);
524 bfd->berrno = io.io_errno;
526 errno = b_errno_win32;
529 bfd->lerror = io.lerror;
534 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
537 Plugin *plugin = (Plugin *)jcr->plugin;
538 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
540 Dmsg0(dbglvl, "plugin_bread\n");
541 io.pkt_size = sizeof(io);
542 io.pkt_end = sizeof(io);
545 io.buf = (char *)buf;
548 plug_func(plugin)->pluginIO(plugin_ctx, &io);
549 bfd->berrno = io.io_errno;
551 errno = b_errno_win32;
554 bfd->lerror = io.lerror;
556 return (ssize_t)io.status;
559 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
562 Plugin *plugin = (Plugin *)jcr->plugin;
563 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
565 Dmsg0(dbglvl, "plugin_bwrite\n");
566 io.pkt_size = sizeof(io);
567 io.pkt_end = sizeof(io);
570 io.buf = (char *)buf;
573 plug_func(plugin)->pluginIO(plugin_ctx, &io);
574 bfd->berrno = io.io_errno;
576 errno = b_errno_win32;
579 bfd->lerror = io.lerror;
581 return (ssize_t)io.status;
584 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
587 Plugin *plugin = (Plugin *)jcr->plugin;
588 bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
590 Dmsg0(dbglvl, "plugin_bseek\n");
591 io.pkt_size = sizeof(io);
592 io.pkt_end = sizeof(io);
598 plug_func(plugin)->pluginIO(plugin_ctx, &io);
599 bfd->berrno = io.io_errno;
601 errno = b_errno_win32;
604 bfd->lerror = io.lerror;
606 return (boffset_t)io.offset;
609 /* ==============================================================
611 * Callbacks from the plugin
613 * ==============================================================
615 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
617 JCR *jcr = (JCR *)(ctx->bContext);
618 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
622 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr);
625 *((int *)value) = jcr->JobId;
626 Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
629 *((char **)value) = my_name;
630 Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
643 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
645 Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
649 static bRC baculaRegisterEvents(bpContext *ctx, ...)
655 while ((event = va_arg(args, uint32_t))) {
656 Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
662 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
663 int type, time_t mtime, const char *fmt, ...)
667 JCR *jcr = (JCR *)(ctx->bContext);
669 va_start(arg_ptr, fmt);
670 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
672 Jmsg(jcr, type, mtime, "%s", buf);
676 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
677 int level, const char *fmt, ...)
682 va_start(arg_ptr, fmt);
683 bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
685 d_msg(file, line, level, "%s", buf);
691 int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
692 int (*plugin_bclose)(JCR *jcr) = NULL;
693 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
694 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
695 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
697 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
702 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
707 int main(int argc, char *argv[])
709 char plugin_dir[1000];
714 strcpy(my_name, "test-fd");
716 getcwd(plugin_dir, sizeof(plugin_dir)-1);
717 load_fd_plugins(plugin_dir);
725 generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
726 generate_plugin_event(jcr1, bEventJobEnd);
727 generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
729 generate_plugin_event(jcr2, bEventJobEnd);
734 Dmsg0(dbglvl, "bacula: OK ...\n");
740 #endif /* TEST_PROGRAM */