]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/fd_plugins.c
kes Improve plugin debug. Create plugin test.
[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)(JCR *jcr, const char *fname, int flags, mode_t mode);
48 extern DLL_IMP_EXP int     (*plugin_bclose)(JCR *jcr);
49 extern DLL_IMP_EXP ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count);
50 extern DLL_IMP_EXP ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count);
51 extern DLL_IMP_EXP boffset_t (*plugin_blseek)(JCR *jcr, 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(JCR *jcr, const char *fname, int flags, mode_t mode);
64 static int     my_plugin_bclose(JCR *jcr);
65 static ssize_t my_plugin_bread(JCR *jcr, void *buf, size_t count);
66 static ssize_t my_plugin_bwrite(JCR *jcr, void *buf, size_t count);
67 static boffset_t my_plugin_blseek(JCR *jcr, 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(JCR *jcr, const char *fname, int flags, mode_t mode)
467 {
468    Plugin *plugin = (Plugin *)jcr->plugin;
469    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
470    struct io_pkt io;
471    Dmsg0(dbglvl, "plugin_bopen\n");
472    io.func = IO_OPEN;
473    io.count = 0;
474    io.buf = NULL;
475    io.mode = mode;
476    io.flags = flags;
477    plug_func(plugin)->pluginIO(plugin_ctx, &io);
478    return io.status;
479 }
480
481 static int my_plugin_bclose(JCR *jcr)
482 {
483    Plugin *plugin = (Plugin *)jcr->plugin;
484    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
485    struct io_pkt io;
486    Dmsg0(dbglvl, "plugin_bclose\n");
487    io.func = IO_CLOSE;
488    io.count = 0;
489    io.buf = NULL;
490    plug_func(plugin)->pluginIO(plugin_ctx, &io);
491    return io.status;
492 }
493
494 static ssize_t my_plugin_bread(JCR *jcr, void *buf, size_t count)
495 {
496    Plugin *plugin = (Plugin *)jcr->plugin;
497    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
498    struct io_pkt io;
499    Dmsg0(dbglvl, "plugin_bread\n");
500    io.func = IO_READ;
501    io.count = count;
502    io.buf = (char *)buf;
503    plug_func(plugin)->pluginIO(plugin_ctx, &io);
504    return (ssize_t)io.status;
505 }
506
507 static ssize_t my_plugin_bwrite(JCR *jcr, void *buf, size_t count)
508 {
509    Plugin *plugin = (Plugin *)jcr->plugin;
510    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
511    struct io_pkt io;
512    Dmsg0(dbglvl, "plugin_bwrite\n");
513    io.func = IO_WRITE;
514    io.count = count;
515    io.buf = (char *)buf;
516    plug_func(plugin)->pluginIO(plugin_ctx, &io);
517    return (ssize_t)io.status;
518 }
519
520 static boffset_t my_plugin_blseek(JCR *jcr, boffset_t offset, int whence)
521 {
522    Plugin *plugin = (Plugin *)jcr->plugin;
523    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
524    struct io_pkt io;
525    Dmsg0(dbglvl, "plugin_bseek\n");
526    io.func = IO_SEEK;
527    io.offset = offset;
528    io.whence = whence;
529    plug_func(plugin)->pluginIO(plugin_ctx, &io);
530    return (boffset_t)io.offset;
531 }
532
533 /* ==============================================================
534  *
535  * Callbacks from the plugin
536  *
537  * ==============================================================
538  */
539 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
540 {
541    JCR *jcr = (JCR *)(ctx->bContext);
542 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
543    if (!value) {
544       return bRC_Error;
545    }
546 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
547    switch (var) {
548    case bVarJobId:
549       *((int *)value) = jcr->JobId;
550       Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
551       break;
552    case bVarFDName:
553       *((char **)value) = my_name;
554       Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
555       break;
556    case bVarLevel:
557    case bVarType:
558    case bVarClient:
559    case bVarJobName:
560    case bVarJobStatus:
561    case bVarSinceTime:
562       break;
563    }
564    return bRC_OK;
565 }
566
567 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
568 {
569    Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
570    return bRC_OK;
571 }
572
573 static bRC baculaRegisterEvents(bpContext *ctx, ...)
574 {
575    va_list args;
576    uint32_t event;
577
578    va_start(args, ctx);
579    while ((event = va_arg(args, uint32_t))) {
580       Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
581    }
582    va_end(args);
583    return bRC_OK;
584 }
585
586 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
587   int type, time_t mtime, const char *fmt, ...)
588 {
589    va_list arg_ptr;
590    char buf[2000];
591    JCR *jcr = (JCR *)(ctx->bContext);
592
593    va_start(arg_ptr, fmt);
594    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
595    va_end(arg_ptr);
596    Jmsg(jcr, type, mtime, "%s", buf);
597    return bRC_OK;
598 }
599
600 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
601   int level, const char *fmt, ...)
602 {
603    va_list arg_ptr;
604    char buf[2000];
605
606    va_start(arg_ptr, fmt);
607    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
608    va_end(arg_ptr);
609    d_msg(file, line, level, "%s", buf);
610    return bRC_OK;
611 }
612
613 #ifdef TEST_PROGRAM
614
615 int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
616 int     (*plugin_bclose)(JCR *jcr) = NULL;
617 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
618 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
619 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
620
621 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
622 {
623    return 0;
624 }
625
626 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
627 {
628    return true;
629 }
630
631 int main(int argc, char *argv[])
632 {
633    char plugin_dir[1000];
634    JCR mjcr1, mjcr2;
635    JCR *jcr1 = &mjcr1;
636    JCR *jcr2 = &mjcr2;
637
638    strcpy(my_name, "test-fd");
639     
640    getcwd(plugin_dir, sizeof(plugin_dir)-1);
641    load_fd_plugins(plugin_dir);
642
643    jcr1->JobId = 111;
644    new_plugins(jcr1);
645
646    jcr2->JobId = 222;
647    new_plugins(jcr2);
648
649    generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
650    generate_plugin_event(jcr1, bEventJobEnd);
651    generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
652    free_plugins(jcr1);
653    generate_plugin_event(jcr2, bEventJobEnd);
654    free_plugins(jcr2);
655
656    unload_plugins();
657
658    Dmsg0(dbglvl, "bacula: OK ...\n");
659    close_memory_pool();
660    sm_dump(false);
661    return 0;
662 }
663
664 #endif /* TEST_PROGRAM */