]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/fd_plugins.c
kes Rework the pluginIO Bacula internal code to enable
[bacula/bacula] / bacula / src / filed / fd_plugins.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2008 Free Software Foundation Europe e.V.
5
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.
12
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.
17
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
21    02110-1301, USA.
22
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.
27 */
28 /*
29  * Main program to test loading and running Bacula plugins.
30  *   Destined to become Bacula pluginloader, ...
31  *
32  * Kern Sibbald, October 2007
33  */
34 #include "bacula.h"
35 #include "filed.h"
36
37 const int dbglvl = 50;
38 #ifdef HAVE_WIN32
39 const char *plugin_type = "-fd.dll";
40 #else
41 const char *plugin_type = "-fd.so";
42 #endif
43
44 extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
45
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);
52
53
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
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);
68
69
70 /* Bacula info */
71 static bInfo binfo = {
72    sizeof(bFuncs),
73    FD_PLUGIN_INTERFACE_VERSION 
74 };
75
76 /* Bacula entry points */
77 static bFuncs bfuncs = {
78    sizeof(bFuncs),
79    FD_PLUGIN_INTERFACE_VERSION,
80    baculaRegisterEvents,
81    baculaGetValue,
82    baculaSetValue,
83    baculaJobMsg,
84    baculaDebugMsg
85 };
86
87
88 /*
89  * Create a plugin event 
90  */
91 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)     
92 {
93    bEvent event;
94    Plugin *plugin;
95    int i = 0;
96
97    if (!plugin_list || !jcr->plugin_ctx_list) {
98       return;                         /* Return if no plugins loaded */
99    }
100
101    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
102    event.eventType = eventType;
103
104    Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
105
106    /* Pass event to every plugin */
107    foreach_alist(plugin, plugin_list) {
108       bRC rc;
109       rc = plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i++], &event, value);
110       if (rc != bRC_OK) {
111          break;
112       }
113    }
114
115    return;
116 }
117
118 /*   
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
128  *    file data.
129  *
130  * Sequence of calls for restore:
131  *   See subroutine plugin_name_stream() below.
132  */
133 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
134 {
135    Plugin *plugin;
136    int i = 0;
137    int len;
138    char *p;
139    char *cmd = ff_pkt->top_fname;
140    struct save_pkt sp;
141    bEvent event;
142
143    if (!plugin_list || !jcr->plugin_ctx_list) {
144       return 1;                            /* Return if no plugins loaded */
145    }
146
147    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
148    event.eventType = bEventBackupCommand;
149
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);
154       goto bail_out;
155    }
156    len = p - cmd;
157    if (len <= 0) {
158       goto bail_out;
159    }
160
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) {
164          i++;
165          continue;
166       }
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) {
170          goto bail_out;
171       }
172       /* Loop getting filenames to backup then saving them */
173       while (!job_canceled(jcr)) { 
174          memset(&sp, 0, sizeof(sp));
175          sp.type = FT_REG;
176          sp.portable = true;
177          sp.cmd = cmd;
178          Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
179                 &sp);
180          /* Get the file save parameters */
181          if (plug_func(plugin)->startBackupFile(&plugin_ctx_list[i], &sp) != bRC_OK) {
182             goto bail_out;
183          }
184          jcr->plugin_ctx = &plugin_ctx_list[i];
185          jcr->plugin = plugin;
186          jcr->plugin_sp = &sp;
187          ff_pkt = jcr->ff;
188          ff_pkt->fname = sp.fname;
189          ff_pkt->type = sp.type;
190          memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
191          Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname);
192          save_file(jcr, ff_pkt, true);
193          if (plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]) != bRC_More) {
194             goto bail_out;
195          }
196       }
197    }
198    Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
199
200 bail_out:
201    return 1;
202 }
203
204 /* 
205  * Send plugin name start/end record to SD
206  */
207 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
208 {
209    int stat;
210    struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
211   
212    Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
213    /* Send stream header */
214    if (!sd->fsend("%ld %d 0", jcr->JobFiles+1, STREAM_PLUGIN_NAME)) {
215      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
216            sd->bstrerror());
217      return false;
218    }
219    Dmsg1(000, "send: %s\n", sd->msg);
220
221    if (start) {
222       /* Send data -- not much */
223       stat = sd->fsend("%ld 1 %d %s%c", jcr->JobFiles+1, sp->portable, sp->cmd, 0);
224    } else {
225       /* Send end of data */
226       stat = sd->fsend("0 0");
227    }
228    if (!stat) {
229       Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
230             sd->bstrerror());
231          return false;
232    }
233    Dmsg1(dbglvl, "send: %s\n", sd->msg);
234    sd->signal(BNET_EOD);            /* indicate end of plugin name data */
235    return true;
236 }
237
238 /*
239  * Plugin name stream found during restore.  The record passed in
240  *  argument name was generated in send_plugin_name() above.
241  */
242 void plugin_name_stream(JCR *jcr, char *name)    
243 {
244    char *p = name;
245    char *cmd;
246    bool start, portable;
247    Plugin *plugin;
248    int len;
249    int i = 0;
250    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
251    if (!plugin_ctx_list) {
252       goto bail_out;
253    }
254
255    Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
256    skip_nonspaces(&p);             /* skip over jcr->JobFiles */
257    skip_spaces(&p);
258    start = *p == '1';
259    if (start) {
260       /* Start of plugin data */
261       skip_nonspaces(&p);          /* skip start/end flag */
262       skip_spaces(&p);
263       portable = *p == '1';
264       skip_nonspaces(&p);          /* skip portable flag */
265       skip_spaces(&p);
266       cmd = p;
267    } else {
268       /*
269        * End of plugin data, notify plugin, then clear flags   
270        */
271       plugin = (Plugin *)jcr->plugin;
272       plug_func(plugin)->endRestoreFile(&plugin_ctx_list[i]);
273       jcr->plugin_ctx = NULL;
274       jcr->plugin = NULL;
275       goto bail_out;
276    }
277       
278    /*
279     * After this point, we are dealing with a restore start
280     */
281
282    Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
283    if (!(p = strchr(cmd, ':'))) {
284       Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
285       goto bail_out;
286    }
287    len = p - cmd;
288    if (len <= 0) {
289       goto bail_out;
290    }
291
292    /*
293     * Search for correct plugin as specified on the command 
294     */
295    foreach_alist(plugin, plugin_list) {
296       bEvent event;
297       Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
298       if (strncmp(plugin->file, cmd, len) != 0) {
299          i++;
300          continue;
301       }
302       Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
303       event.eventType = bEventRestoreCommand;     
304       if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], 
305             &event, cmd) != bRC_OK) {
306          goto bail_out;
307       }
308       jcr->plugin_ctx = &plugin_ctx_list[i];
309       jcr->plugin = plugin;
310       goto bail_out;
311    }
312 bail_out:
313    return;
314 }
315
316 /*
317  * Tell the plugin to create the file.  Return values are
318  *
319  *  CF_ERROR    -- error
320  *  CF_SKIP     -- skip processing this file
321  *  CF_EXTRACT  -- extract the file (i.e.call i/o routines)
322  *  CF_CREATED  -- created, but no content to extract (typically directories)
323  *
324  */
325 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
326 {
327    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
328    Plugin *plugin = (Plugin *)jcr->plugin;
329    struct restore_pkt rp;
330    struct io_pkt io;
331
332    if (!set_cmd_plugin(bfd, jcr)) {
333       return CF_ERROR;
334    }
335    rp.stream = attr->stream;
336    rp.data_stream = attr->data_stream;
337    rp.type = attr->type;
338    rp.file_index = attr->file_index;
339    rp.LinkFI = attr->LinkFI;
340    rp.uid = attr->uid;
341    rp.statp = attr->statp;                /* structure assignment */
342    rp.attrEx = attr->attrEx;
343    rp.ofname = attr->ofname;
344    rp.olname = attr->olname;
345    rp.where = jcr->where;
346    rp.RegexWhere = jcr->RegexWhere;
347    rp.replace = jcr->replace;
348    if (plug_func(plugin)->createFile(plugin_ctx, &rp) != bRC_OK) {
349       return CF_ERROR;
350    }
351    io.func = IO_OPEN;
352    io.count = 0;
353    io.buf = NULL;
354    io.mode = 0777 & attr->statp.st_mode;
355    io.flags = O_WRONLY;
356    if (plug_func(plugin)->pluginIO(plugin_ctx, &io) != bRC_OK) {
357       return CF_ERROR;
358    }
359    return CF_EXTRACT;
360 }
361
362 /*
363  * Reset the file attributes after all file I/O is done -- this allows
364  *  the previous access time/dates to be set properly, and it also allows
365  *  us to properly set directory permissions.
366  */
367 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
368 {
369    return true;
370 }
371
372 /*
373  * This entry point is called internally by Bacula to ensure
374  *  that the plugin IO calls come into this code.
375  */
376 void load_fd_plugins(const char *plugin_dir)
377 {
378    Plugin *plugin;
379
380    if (!plugin_dir) {
381       Dmsg0(dbglvl, "plugin dir is NULL\n");
382       return;
383    }
384
385    plugin_list = New(alist(10, not_owned_by_alist));
386    if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) {
387       /* Either none found, or some error */
388       if (plugin_list->size() == 0) {
389          delete plugin_list;
390          plugin_list = NULL;
391          Dmsg0(dbglvl, "No plugins loaded\n");
392          return;
393       }
394    }
395
396    /* Plug entry points called from findlib */
397    plugin_bopen  = my_plugin_bopen;
398    plugin_bclose = my_plugin_bclose;
399    plugin_bread  = my_plugin_bread;
400    plugin_bwrite = my_plugin_bwrite;
401    plugin_blseek = my_plugin_blseek;
402    foreach_alist(plugin, plugin_list) {
403       Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
404       Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
405
406    }
407
408 }
409
410 /*
411  * Create a new instance of each plugin for this Job
412  *   Note, plugin_list can exist but jcr->plugin_ctx_list can
413  *   be NULL if no plugins were loaded.
414  */
415 void new_plugins(JCR *jcr)
416 {
417    Plugin *plugin;
418    int i = 0;
419
420    if (!plugin_list) {
421       Dmsg0(dbglvl, "plugin list is NULL\n");
422       return;
423    }
424
425    int num = plugin_list->size();
426
427    if (num == 0) {
428       Dmsg0(dbglvl, "No plugins loaded\n");
429       return;
430    }
431
432    jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
433
434    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
435    Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
436    foreach_alist(plugin, plugin_list) {
437       /* Start a new instance of each plugin */
438       plugin_ctx_list[i].bContext = (void *)jcr;
439       plugin_ctx_list[i].pContext = NULL;
440       plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
441    }
442 }
443
444 /*
445  * Free the plugin instances for this Job
446  */
447 void free_plugins(JCR *jcr)
448 {
449    Plugin *plugin;
450    int i = 0;
451
452    if (!plugin_list || !jcr->plugin_ctx_list) {
453       return;                         /* no plugins, nothing to do */
454    }
455
456    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
457    Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
458    foreach_alist(plugin, plugin_list) {
459       /* Free the plugin instance */
460       plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
461    }
462    free(plugin_ctx_list);
463    jcr->plugin_ctx_list = NULL;
464 }
465
466 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
467 {
468    JCR *jcr = bfd->jcr;
469    Plugin *plugin = (Plugin *)jcr->plugin;
470    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
471    struct io_pkt io;
472    Dmsg0(dbglvl, "plugin_bopen\n");
473    io.func = IO_OPEN;
474    io.count = 0;
475    io.buf = NULL;
476    io.fname = fname;
477    io.flags = flags;
478    io.mode = mode;
479    io.win32 = false;
480    io.lerror = 0;
481    plug_func(plugin)->pluginIO(plugin_ctx, &io);
482    bfd->berrno = io.io_errno;
483    if (io.win32) {
484       errno = b_errno_win32;
485    } else {
486       errno = io.io_errno;
487       bfd->lerror = io.lerror;
488    }
489    return io.status;
490 }
491
492 static int my_plugin_bclose(BFILE *bfd)
493 {
494    JCR *jcr = bfd->jcr;
495    Plugin *plugin = (Plugin *)jcr->plugin;
496    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
497    struct io_pkt io;
498    Dmsg0(dbglvl, "plugin_bclose\n");
499    io.func = IO_CLOSE;
500    io.count = 0;
501    io.buf = NULL;
502    io.win32 = false;
503    io.lerror = 0;
504    plug_func(plugin)->pluginIO(plugin_ctx, &io);
505    bfd->berrno = io.io_errno;
506    if (io.win32) {
507       errno = b_errno_win32;
508    } else {
509       errno = io.io_errno;
510       bfd->lerror = io.lerror;
511    }
512    return io.status;
513 }
514
515 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
516 {
517    JCR *jcr = bfd->jcr;
518    Plugin *plugin = (Plugin *)jcr->plugin;
519    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
520    struct io_pkt io;
521    Dmsg0(dbglvl, "plugin_bread\n");
522    io.func = IO_READ;
523    io.count = count;
524    io.buf = (char *)buf;
525    io.win32 = false;
526    io.lerror = 0;
527    plug_func(plugin)->pluginIO(plugin_ctx, &io);
528    bfd->berrno = io.io_errno;
529    if (io.win32) {
530       errno = b_errno_win32;
531    } else {
532       errno = io.io_errno;
533       bfd->lerror = io.lerror;
534    }
535    return (ssize_t)io.status;
536 }
537
538 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
539 {
540    JCR *jcr = bfd->jcr;
541    Plugin *plugin = (Plugin *)jcr->plugin;
542    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
543    struct io_pkt io;
544    Dmsg0(dbglvl, "plugin_bwrite\n");
545    io.func = IO_WRITE;
546    io.count = count;
547    io.buf = (char *)buf;
548    io.win32 = false;
549    io.lerror = 0;
550    plug_func(plugin)->pluginIO(plugin_ctx, &io);
551    bfd->berrno = io.io_errno;
552    if (io.win32) {
553       errno = b_errno_win32;
554    } else {
555       errno = io.io_errno;
556       bfd->lerror = io.lerror;
557    }
558    return (ssize_t)io.status;
559 }
560
561 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
562 {
563    JCR *jcr = bfd->jcr;
564    Plugin *plugin = (Plugin *)jcr->plugin;
565    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
566    struct io_pkt io;
567    Dmsg0(dbglvl, "plugin_bseek\n");
568    io.func = IO_SEEK;
569    io.offset = offset;
570    io.whence = whence;
571    io.win32 = false;
572    io.lerror = 0;
573    plug_func(plugin)->pluginIO(plugin_ctx, &io);
574    bfd->berrno = io.io_errno;
575    if (io.win32) {
576       errno = b_errno_win32;
577    } else {
578       errno = io.io_errno;
579       bfd->lerror = io.lerror;
580    }
581    return (boffset_t)io.offset;
582 }
583
584 /* ==============================================================
585  *
586  * Callbacks from the plugin
587  *
588  * ==============================================================
589  */
590 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
591 {
592    JCR *jcr = (JCR *)(ctx->bContext);
593 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
594    if (!value) {
595       return bRC_Error;
596    }
597 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
598    switch (var) {
599    case bVarJobId:
600       *((int *)value) = jcr->JobId;
601       Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
602       break;
603    case bVarFDName:
604       *((char **)value) = my_name;
605       Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
606       break;
607    case bVarLevel:
608    case bVarType:
609    case bVarClient:
610    case bVarJobName:
611    case bVarJobStatus:
612    case bVarSinceTime:
613       break;
614    }
615    return bRC_OK;
616 }
617
618 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
619 {
620    Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
621    return bRC_OK;
622 }
623
624 static bRC baculaRegisterEvents(bpContext *ctx, ...)
625 {
626    va_list args;
627    uint32_t event;
628
629    va_start(args, ctx);
630    while ((event = va_arg(args, uint32_t))) {
631       Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
632    }
633    va_end(args);
634    return bRC_OK;
635 }
636
637 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
638   int type, time_t mtime, const char *fmt, ...)
639 {
640    va_list arg_ptr;
641    char buf[2000];
642    JCR *jcr = (JCR *)(ctx->bContext);
643
644    va_start(arg_ptr, fmt);
645    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
646    va_end(arg_ptr);
647    Jmsg(jcr, type, mtime, "%s", buf);
648    return bRC_OK;
649 }
650
651 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
652   int level, const char *fmt, ...)
653 {
654    va_list arg_ptr;
655    char buf[2000];
656
657    va_start(arg_ptr, fmt);
658    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
659    va_end(arg_ptr);
660    d_msg(file, line, level, "%s", buf);
661    return bRC_OK;
662 }
663
664 #ifdef TEST_PROGRAM
665
666 int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
667 int     (*plugin_bclose)(JCR *jcr) = NULL;
668 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
669 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
670 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
671
672 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
673 {
674    return 0;
675 }
676
677 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
678 {
679    return true;
680 }
681
682 int main(int argc, char *argv[])
683 {
684    char plugin_dir[1000];
685    JCR mjcr1, mjcr2;
686    JCR *jcr1 = &mjcr1;
687    JCR *jcr2 = &mjcr2;
688
689    strcpy(my_name, "test-fd");
690     
691    getcwd(plugin_dir, sizeof(plugin_dir)-1);
692    load_fd_plugins(plugin_dir);
693
694    jcr1->JobId = 111;
695    new_plugins(jcr1);
696
697    jcr2->JobId = 222;
698    new_plugins(jcr2);
699
700    generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
701    generate_plugin_event(jcr1, bEventJobEnd);
702    generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
703    free_plugins(jcr1);
704    generate_plugin_event(jcr2, bEventJobEnd);
705    free_plugins(jcr2);
706
707    unload_plugins();
708
709    Dmsg0(dbglvl, "bacula: OK ...\n");
710    close_memory_pool();
711    sm_dump(false);
712    return 0;
713 }
714
715 #endif /* TEST_PROGRAM */