]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/fd_plugins.c
Permit to get Where/RegexWhere from Plugin and give access to Accurate data
[bacula/bacula] / bacula / src / filed / fd_plugins.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2010 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 three of the GNU Affero 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 Affero 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 extern CLIENT *me;
38
39 const int dbglvl = 150;
40 #ifdef HAVE_WIN32
41 const char *plugin_type = "-fd.dll";
42 #else
43 const char *plugin_type = "-fd.so";
44 #endif
45
46 extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
47 extern bool check_changes(JCR *jcr, FF_PKT *ff_pkt);
48
49 /* Function pointers to be set here */
50 extern DLL_IMP_EXP int     (*plugin_bopen)(BFILE *bfd, const char *fname, int flags, mode_t mode);
51 extern DLL_IMP_EXP int     (*plugin_bclose)(BFILE *bfd);
52 extern DLL_IMP_EXP ssize_t (*plugin_bread)(BFILE *bfd, void *buf, size_t count);
53 extern DLL_IMP_EXP ssize_t (*plugin_bwrite)(BFILE *bfd, void *buf, size_t count);
54 extern DLL_IMP_EXP boffset_t (*plugin_blseek)(BFILE *bfd, boffset_t offset, int whence);
55
56
57 /* Forward referenced functions */
58 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value);
59 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value);
60 static bRC baculaRegisterEvents(bpContext *ctx, ...);
61 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
62   int type, utime_t mtime, const char *fmt, ...);
63 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
64   int level, const char *fmt, ...);
65 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
66               size_t size);
67 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem);
68 static bRC  baculaAddExclude(bpContext *ctx, const char *file);
69 static bRC baculaAddInclude(bpContext *ctx, const char *file);
70 static bRC baculaAddOptions(bpContext *ctx, const char *opts);
71 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type);
72 static bRC baculaAddWild(bpContext *ctx, const char *item, int type);
73 static bRC baculaNewOptions(bpContext *ctx);
74 static bRC baculaNewInclude(bpContext *ctx);
75 static bool is_plugin_compatible(Plugin *plugin);
76 static bool get_plugin_name(JCR *jcr, char *cmd, int *ret);
77 static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp);
78
79 /*
80  * These will be plugged into the global pointer structure for
81  *  the findlib.
82  */
83 static int     my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode);
84 static int     my_plugin_bclose(BFILE *bfd);
85 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count);
86 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count);
87 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence);
88
89
90 /* Bacula info */
91 static bInfo binfo = {
92    sizeof(bInfo),
93    FD_PLUGIN_INTERFACE_VERSION 
94 };
95
96 /* Bacula entry points */
97 static bFuncs bfuncs = {
98    sizeof(bFuncs),
99    FD_PLUGIN_INTERFACE_VERSION,
100    baculaRegisterEvents,
101    baculaGetValue,
102    baculaSetValue,
103    baculaJobMsg,
104    baculaDebugMsg,
105    baculaMalloc,
106    baculaFree,
107    baculaAddExclude,
108    baculaAddInclude,
109    baculaAddOptions,
110    baculaAddRegex,
111    baculaAddWild,
112    baculaNewOptions,
113    baculaNewInclude,
114    baculaCheckChanges
115 };
116
117 /* 
118  * Bacula private context
119  */
120 struct bacula_ctx {
121    JCR *jcr;                             /* jcr for plugin */
122    bRC  rc;                              /* last return code */
123    bool disabled;                        /* set if plugin disabled */
124    findINCEXE *exclude;                  /* pointer to exclude files */
125    findINCEXE *include;                  /* pointer to include/exclude files */
126 };
127
128 static bool is_plugin_disabled(bpContext *plugin_ctx)
129 {
130    bacula_ctx *b_ctx;
131    if (!plugin_ctx) {
132       return true;
133    }
134    b_ctx = (bacula_ctx *)plugin_ctx->bContext;
135    return b_ctx->disabled;
136 }
137
138 static bool is_plugin_disabled(JCR *jcr)
139 {
140    return is_plugin_disabled(jcr->plugin_ctx);
141 }
142
143 /**
144  * Create a plugin event 
145  * When receiving bEventCancelCommand, this function is called by an other thread. 
146  */
147 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)     
148 {
149    bpContext *plugin_ctx;
150    bEvent event;
151    Plugin *plugin;
152    int i = 0;
153    char *name=NULL;
154    int len;
155    bRC rc;
156
157    if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
158       return;                         /* Return if no plugins loaded */
159    }
160    
161    /* Some events are sent to only a particular plugin */
162    switch(eventType) {
163    case bEventPluginCommand:
164       name = (char *)value;
165       if (!get_plugin_name(jcr, name, &len)) {
166          return;
167       }
168       break;
169    default:
170       break;
171    }
172
173    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
174    event.eventType = eventType;
175
176    Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
177
178    /* Pass event to every plugin (except if name is set) */
179    foreach_alist(plugin, plugin_list) {
180       if (name && strncmp(plugin->file, name, len) != 0) {
181          i++;
182          continue;
183       }
184       plugin_ctx = &plugin_ctx_list[i++];
185       if (is_plugin_disabled(plugin_ctx)) {
186          continue;
187       }
188       rc = plug_func(plugin)->handlePluginEvent(plugin_ctx, &event, value);
189       if (rc != bRC_OK) {
190          break;
191       }
192    }
193    return;
194 }
195
196 /**
197  * Check if file was seen for accurate
198  */
199 bool plugin_check_file(JCR *jcr, char *fname)
200 {
201    Plugin *plugin;
202    int rc = bRC_OK;
203    int i = 0;
204
205    if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
206       return false;                      /* Return if no plugins loaded */
207    }
208
209    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
210
211    Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
212
213    /* Pass event to every plugin */
214    foreach_alist(plugin, plugin_list) {
215       jcr->plugin_ctx = &plugin_ctx_list[i++];
216       jcr->plugin = plugin;
217       if (is_plugin_disabled(jcr)) {
218          continue;
219       }
220       if (plug_func(plugin)->checkFile == NULL) {
221          continue;
222       }
223       rc = plug_func(plugin)->checkFile(jcr->plugin_ctx, fname);
224       if (rc == bRC_Seen) {
225          break;
226       }
227    }
228
229    jcr->plugin = NULL;
230    jcr->plugin_ctx = NULL;
231    return rc == bRC_Seen;
232 }
233
234 /* Get the first part of the the plugin command
235  *  systemstate:/@SYSTEMSTATE/ 
236  * => ret = 11
237  * => can use strncmp(plugin_name, cmd, ret);
238  *
239  * The plugin command can contain only the plugin name
240  *  Plugin = alldrives
241  * => ret = 9
242  */
243 static bool get_plugin_name(JCR *jcr, char *cmd, int *ret)
244 {
245    char *p;
246    int len;
247    if (!cmd || (*cmd == '\0')) {
248       return false;
249    }
250    /* Handle plugin command here backup */
251    Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
252    if ((p = strchr(cmd, ':')) == NULL) {
253       if (strchr(cmd, ' ') == NULL) { /* we have just the plugin name */
254          len = strlen(cmd);
255       } else {
256          Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
257          return false;
258       }
259    } else {                     /* plugin:argument */
260       len = p - cmd;
261       if (len <= 0) {
262          return false;
263       }
264    }
265    *ret = len;
266    return true;
267 }
268
269 /**  
270  * Sequence of calls for a backup:
271  * 1. plugin_save() here is called with ff_pkt
272  * 2. we find the plugin requested on the command string
273  * 3. we generate a bEventBackupCommand event to the specified plugin
274  *    and pass it the command string.
275  * 4. we make a startPluginBackup call to the plugin, which gives
276  *    us the data we need in save_pkt
277  * 5. we call Bacula's save_file() subroutine to save the specified
278  *    file.  The plugin will be called at pluginIO() to supply the
279  *    file data.
280  *
281  * Sequence of calls for restore:
282  *   See subroutine plugin_name_stream() below.
283  */
284 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
285 {
286    Plugin *plugin;
287    int i = 0;
288    int len;
289    char *cmd = ff_pkt->top_fname;
290    struct save_pkt sp;
291    bEvent event;
292    POOL_MEM fname(PM_FNAME);
293    POOL_MEM link(PM_FNAME);
294
295    if (!plugin_list || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
296       Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" requested, but is not loaded.\n", cmd);
297       return 1;                            /* Return if no plugins loaded */
298    }
299
300    jcr->cmd_plugin = true;
301    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
302    event.eventType = bEventBackupCommand;
303
304    if (!get_plugin_name(jcr, cmd, &len)) {
305       goto bail_out;
306    }
307
308    /* Note, we stop the loop on the first plugin that matches the name */
309    foreach_alist(plugin, plugin_list) {
310       Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
311       if (strncmp(plugin->file, cmd, len) != 0) {
312          i++;
313          continue;
314       }
315       /* 
316        * We put the current plugin pointer, and the plugin context
317        *  into the jcr, because during save_file(), the plugin
318        *  will be called many times and these values are needed.
319        */
320       jcr->plugin_ctx = &plugin_ctx_list[i];
321       jcr->plugin = plugin;
322       if (is_plugin_disabled(jcr)) {
323          goto bail_out;
324       }
325
326       Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
327       /* Send the backup command to the right plugin*/
328       if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, cmd) != bRC_OK) {
329          goto bail_out;
330       }
331       /* Loop getting filenames to backup then saving them */
332       while (!jcr->is_job_canceled()) { 
333          memset(&sp, 0, sizeof(sp));
334          sp.pkt_size = sizeof(sp);
335          sp.pkt_end = sizeof(sp);
336          sp.portable = true;
337          sp.cmd = cmd;
338          Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
339                 &sp);
340          /* Get the file save parameters. I.e. the stat pkt ... */
341          if (plug_func(plugin)->startBackupFile(jcr->plugin_ctx, &sp) != bRC_OK) {
342             goto bail_out;
343          }
344          if (sp.type == 0) {
345             Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no type in startBackupFile packet.\n"),
346                cmd);
347             goto bail_out;
348          }
349          jcr->plugin_sp = &sp;
350          ff_pkt = jcr->ff;
351          /*
352           * Copy fname and link because save_file() zaps them.  This 
353           *  avoids zaping the plugin's strings.
354           */
355          ff_pkt->type = sp.type;
356          if (sp.type == FT_RESTORE_FIRST) {
357             if (!sp.object_name) {
358                Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no object_name in startBackupFile packet.\n"),
359                   cmd);
360                goto bail_out;
361             }
362             ff_pkt->fname = cmd;                 /* full plugin string */
363             ff_pkt->object_name = sp.object_name;
364             ff_pkt->object_index = sp.index;     /* restore object index */
365             ff_pkt->object_compression = 0;      /* no compression for now */
366             ff_pkt->object = sp.object;
367             ff_pkt->object_len = sp.object_len;
368          } else {
369             if (!sp.fname) {
370                Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no fname in startBackupFile packet.\n"),
371                   cmd);
372                goto bail_out;
373             }
374             pm_strcpy(fname, sp.fname);
375             pm_strcpy(link, sp.link);
376             ff_pkt->fname = fname.c_str();
377             ff_pkt->link = link.c_str();
378          }
379
380          memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
381          Dmsg2(dbglvl, "startBackup returned type=%d, fname=%s\n", sp.type, sp.fname);
382          if (sp.object) {
383             Dmsg2(dbglvl, "index=%d object=%s\n", sp.index, sp.object);
384          }   
385          /* Call Bacula core code to backup the plugin's file */
386          save_file(jcr, ff_pkt, true);
387          bRC rc = plug_func(plugin)->endBackupFile(jcr->plugin_ctx);
388          if (rc == bRC_More || rc == bRC_OK) {
389             accurate_mark_file_as_seen(jcr, fname.c_str());
390          }
391          if (rc == bRC_More) {
392             continue;
393          }
394          goto bail_out;
395       }
396       goto bail_out;
397    }
398    Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" not found.\n", cmd);
399
400 bail_out:
401    jcr->cmd_plugin = false;
402    jcr->plugin = NULL;
403    jcr->plugin_ctx = NULL;
404    return 1;
405 }
406
407 /**
408  * Send plugin name start/end record to SD
409  */
410 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
411 {
412    int stat;
413    int index = jcr->JobFiles;
414    struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
415
416    if (!sp) {
417       Jmsg0(jcr, M_FATAL, 0, _("Plugin save packet not found.\n"));
418       return false;
419    }
420    if (jcr->is_job_canceled()) {
421       return false;
422    }
423   
424    if (start) {
425       index++;                  /* JobFiles not incremented yet */
426    }
427    Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
428    /* Send stream header */
429    if (!sd->fsend("%ld %d 0", index, STREAM_PLUGIN_NAME)) {
430      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
431            sd->bstrerror());
432      return false;
433    }
434    Dmsg1(50, "send plugin name hdr: %s\n", sd->msg);
435
436    if (start) {
437       /* Send data -- not much */
438       stat = sd->fsend("%ld 1 %d %s%c", index, sp->portable, sp->cmd, 0);
439    } else {
440       /* Send end of data */
441       stat = sd->fsend("0 0");
442    }
443    if (!stat) {
444       Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
445             sd->bstrerror());
446          return false;
447    }
448    Dmsg1(dbglvl, "send plugin start/end: %s\n", sd->msg);
449    sd->signal(BNET_EOD);            /* indicate end of plugin name data */
450    return true;
451 }
452
453 /**
454  * Plugin name stream found during restore.  The record passed in
455  *  argument name was generated in send_plugin_name() above.
456  *
457  * Returns: true  if start of stream
458  *          false if end of steam
459  */
460 bool plugin_name_stream(JCR *jcr, char *name)    
461 {
462    char *p = name;
463    char *cmd;
464    bool start, portable;
465    Plugin *plugin;
466    int len;
467    int i = 0;
468    bpContext *plugin_ctx_list = jcr->plugin_ctx_list;
469
470    Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
471    skip_nonspaces(&p);             /* skip over jcr->JobFiles */
472    skip_spaces(&p);
473    start = *p == '1';
474    if (start) {
475       /* Start of plugin data */
476       skip_nonspaces(&p);          /* skip start/end flag */
477       skip_spaces(&p);
478       portable = *p == '1';
479       skip_nonspaces(&p);          /* skip portable flag */
480       skip_spaces(&p);
481       cmd = p;
482    } else {
483       /*
484        * End of plugin data, notify plugin, then clear flags   
485        */
486       Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
487       if (jcr->plugin) {
488          plug_func(jcr->plugin)->endRestoreFile(jcr->plugin_ctx);
489       }
490       jcr->plugin_ctx = NULL;
491       jcr->plugin = NULL;
492       goto bail_out;
493    }
494    if (!plugin_ctx_list) {
495       goto bail_out;
496    }
497       
498    /*
499     * After this point, we are dealing with a restore start
500     */
501    if (!get_plugin_name(jcr, cmd, &len)) {
502       goto bail_out;
503    }
504
505    /*
506     * Search for correct plugin as specified on the command 
507     */
508    foreach_alist(plugin, plugin_list) {
509       bEvent event;
510       Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
511       if (strncmp(plugin->file, cmd, len) != 0) {
512          i++;
513          continue;
514       }
515       jcr->plugin_ctx = &plugin_ctx_list[i];
516       jcr->plugin = plugin;
517       if (is_plugin_disabled(jcr)) {
518          goto bail_out;
519       }
520       Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
521       event.eventType = bEventRestoreCommand;     
522       if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, 
523             &event, cmd) != bRC_OK) {
524          goto bail_out;
525       }
526       /* ***FIXME**** check error code */
527       plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd);
528       goto bail_out;
529    }
530    Jmsg1(jcr, M_WARNING, 0, _("Plugin=%s not found.\n"), cmd);
531
532 bail_out:
533    return start;
534 }
535
536 /**
537  * Tell the plugin to create the file.  Return values are
538  *   This is called only during Restore
539  *
540  *  CF_ERROR    -- error
541  *  CF_SKIP     -- skip processing this file
542  *  CF_EXTRACT  -- extract the file (i.e.call i/o routines)
543  *  CF_CREATED  -- created, but no content to extract (typically directories)
544  *
545  */
546 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
547 {
548    bpContext *plugin_ctx = jcr->plugin_ctx;
549    Plugin *plugin = jcr->plugin;
550    struct restore_pkt rp;
551    int flags;
552    int rc;
553
554    if (!plugin || !plugin_ctx || !set_cmd_plugin(bfd, jcr) || jcr->is_job_canceled()) {
555       return CF_ERROR;
556    }
557
558    rp.pkt_size = sizeof(rp);
559    rp.pkt_end = sizeof(rp);
560    rp.stream = attr->stream;
561    rp.data_stream = attr->data_stream;
562    rp.type = attr->type;
563    rp.file_index = attr->file_index;
564    rp.LinkFI = attr->LinkFI;
565    rp.uid = attr->uid;
566    rp.statp = attr->statp;                /* structure assignment */
567    rp.attrEx = attr->attrEx;
568    rp.ofname = attr->ofname;
569    rp.olname = attr->olname;
570    rp.where = jcr->where;
571    rp.RegexWhere = jcr->RegexWhere;
572    rp.replace = jcr->replace;
573    rp.create_status = CF_ERROR;
574    Dmsg4(dbglvl, "call plugin createFile stream=%d type=%d LinkFI=%d File=%s\n", 
575          rp.stream, rp.type, rp.LinkFI, rp.ofname);
576    if (rp.attrEx) {
577       Dmsg1(dbglvl, "attrEx=\"%s\"\n", rp.attrEx);
578    }
579    rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
580    if (rc != bRC_OK) {
581       Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
582             rc, attr->ofname);
583       return CF_ERROR;
584    }
585    if (rp.create_status == CF_SKIP) {
586       return CF_SKIP;
587    }
588    if (rp.create_status == CF_ERROR) {
589       Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
590             attr->ofname);
591       return CF_ERROR;
592    }
593    /* Created link or directory? */
594    if (rp.create_status == CF_CREATED) {
595       return rp.create_status;        /* yes, no need to bopen */
596    }
597
598    flags =  O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
599    Dmsg0(dbglvl, "call bopen\n");
600    int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
601    Dmsg1(50, "bopen status=%d\n", stat);
602    if (stat < 0) {
603       berrno be;
604       be.set_errno(bfd->berrno);
605       Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
606             attr->ofname, be.bstrerror());
607       Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
608       return CF_ERROR;
609    }
610
611    if (!is_bopen(bfd)) {
612       Dmsg0(000, "===== BFD is not open!!!!\n");
613    }
614    return CF_EXTRACT;
615 }
616
617 /**
618  * Reset the file attributes after all file I/O is done -- this allows
619  *  the previous access time/dates to be set properly, and it also allows
620  *  us to properly set directory permissions.
621  *  Not currently Implemented.
622  */
623 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
624 {
625    Dmsg0(dbglvl, "plugin_set_attributes\n");
626    if (is_bopen(ofd)) {
627       bclose(ofd);
628    }
629    pm_strcpy(attr->ofname, "*none*");
630    return true;
631 }
632
633 /*
634  * Print to file the plugin info.
635  */
636 void dump_fd_plugin(Plugin *plugin, FILE *fp)
637 {
638    if (!plugin) {
639       return ;
640    }
641    pInfo *info = (pInfo *)plugin->pinfo;
642    fprintf(fp, "\tversion=%d\n", info->version);
643    fprintf(fp, "\tdate=%s\n", NPRTB(info->plugin_date));
644    fprintf(fp, "\tmagic=%s\n", NPRTB(info->plugin_magic));
645    fprintf(fp, "\tauthor=%s\n", NPRTB(info->plugin_author));
646    fprintf(fp, "\tlicence=%s\n", NPRTB(info->plugin_license));
647    fprintf(fp, "\tversion=%s\n", NPRTB(info->plugin_version));
648    fprintf(fp, "\tdescription=%s\n", NPRTB(info->plugin_description));
649 }
650
651 /**
652  * This entry point is called internally by Bacula to ensure
653  *  that the plugin IO calls come into this code.
654  */
655 void load_fd_plugins(const char *plugin_dir)
656 {
657    Plugin *plugin;
658
659    if (!plugin_dir) {
660       Dmsg0(dbglvl, "plugin dir is NULL\n");
661       return;
662    }
663
664    plugin_list = New(alist(10, not_owned_by_alist));
665    if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type,
666                      is_plugin_compatible)) {
667       /* Either none found, or some error */
668       if (plugin_list->size() == 0) {
669          delete plugin_list;
670          plugin_list = NULL;
671          Dmsg0(dbglvl, "No plugins loaded\n");
672          return;
673       }
674    }
675
676    /* Plug entry points called from findlib */
677    plugin_bopen  = my_plugin_bopen;
678    plugin_bclose = my_plugin_bclose;
679    plugin_bread  = my_plugin_bread;
680    plugin_bwrite = my_plugin_bwrite;
681    plugin_blseek = my_plugin_blseek;
682
683    /* 
684     * Verify that the plugin is acceptable, and print information
685     *  about it.
686     */
687    foreach_alist(plugin, plugin_list) {
688       Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
689       Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
690    }
691
692    dbg_plugin_add_hook(dump_fd_plugin);
693 }
694
695 /**
696  * Check if a plugin is compatible.  Called by the load_plugin function
697  *  to allow us to verify the plugin.
698  */
699 static bool is_plugin_compatible(Plugin *plugin)
700 {
701    pInfo *info = (pInfo *)plugin->pinfo;
702    Dmsg0(50, "is_plugin_compatible called\n");
703    if (debug_level >= 50) {
704       dump_fd_plugin(plugin, stdin);
705    }
706    if (strcmp(info->plugin_magic, FD_PLUGIN_MAGIC) != 0) {
707       Jmsg(NULL, M_ERROR, 0, _("Plugin magic wrong. Plugin=%s wanted=%s got=%s\n"),
708            plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic);
709       Dmsg3(50, "Plugin magic wrong. Plugin=%s wanted=%s got=%s\n",
710            plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic);
711
712       return false;
713    }
714    if (info->version != FD_PLUGIN_INTERFACE_VERSION) {
715       Jmsg(NULL, M_ERROR, 0, _("Plugin version incorrect. Plugin=%s wanted=%d got=%d\n"),
716            plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version);
717       Dmsg3(50, "Plugin version incorrect. Plugin=%s wanted=%d got=%d\n",
718            plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version);
719       return false;
720    }
721    if (strcmp(info->plugin_license, "Bacula AGPLv3") != 0 &&
722        strcmp(info->plugin_license, "AGPLv3") != 0) {
723       Jmsg(NULL, M_ERROR, 0, _("Plugin license incompatible. Plugin=%s license=%s\n"),
724            plugin->file, info->plugin_license);
725       Dmsg2(50, "Plugin license incompatible. Plugin=%s license=%s\n",
726            plugin->file, info->plugin_license);
727       return false;
728    }
729       
730    return true;
731 }
732
733
734 /**
735  * Create a new instance of each plugin for this Job
736  *   Note, plugin_list can exist but jcr->plugin_ctx_list can
737  *   be NULL if no plugins were loaded.
738  */
739 void new_plugins(JCR *jcr)
740 {
741    Plugin *plugin;
742    int i = 0;
743
744    if (!plugin_list) {
745       Dmsg0(dbglvl, "plugin list is NULL\n");
746       return;
747    }
748    if (jcr->is_job_canceled() || jcr->JobId == 0) {
749       return;
750    }
751
752    int num = plugin_list->size();
753
754    if (num == 0) {
755       Dmsg0(dbglvl, "No plugins loaded\n");
756       return;
757    }
758
759    jcr->plugin_ctx_list = (bpContext *)malloc(sizeof(bpContext) * num);
760
761    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
762    Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
763    foreach_alist(plugin, plugin_list) {
764       /* Start a new instance of each plugin */
765       bacula_ctx *b_ctx = (bacula_ctx *)malloc(sizeof(bacula_ctx));
766       memset(b_ctx, 0, sizeof(bacula_ctx));
767       b_ctx->jcr = jcr;
768       plugin_ctx_list[i].bContext = (void *)b_ctx;   /* Bacula private context */
769       plugin_ctx_list[i].pContext = NULL;
770       if (plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]) != bRC_OK) {
771          b_ctx->disabled = true;
772       }
773    }
774 }
775
776 /**
777  * Free the plugin instances for this Job
778  */
779 void free_plugins(JCR *jcr)
780 {
781    Plugin *plugin;
782    int i = 0;
783
784    if (!plugin_list || !jcr->plugin_ctx_list) {
785       return;                         /* no plugins, nothing to do */
786    }
787
788    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
789    Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
790    foreach_alist(plugin, plugin_list) {   
791       /* Free the plugin instance */
792       plug_func(plugin)->freePlugin(&plugin_ctx_list[i]);
793       free(plugin_ctx_list[i++].bContext);     /* free Bacula private context */
794    }
795    free(plugin_ctx_list);
796    jcr->plugin_ctx_list = NULL;
797 }
798
799 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
800 {
801    JCR *jcr = bfd->jcr;
802    Plugin *plugin = (Plugin *)jcr->plugin;
803    struct io_pkt io;
804
805    Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
806    if (!plugin || !jcr->plugin_ctx) {
807       return 0;
808    }
809    io.pkt_size = sizeof(io);
810    io.pkt_end = sizeof(io);
811    io.func = IO_OPEN;
812    io.count = 0;
813    io.buf = NULL;
814    io.fname = fname;
815    io.flags = flags;
816    io.mode = mode;
817    io.win32 = false;
818    io.lerror = 0;
819    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
820    bfd->berrno = io.io_errno;
821    if (io.win32) {
822       errno = b_errno_win32;
823    } else {
824       errno = io.io_errno;
825       bfd->lerror = io.lerror;
826    }
827    Dmsg1(50, "Return from plugin open status=%d\n", io.status);
828    return io.status;
829 }
830
831 static int my_plugin_bclose(BFILE *bfd)
832 {
833    JCR *jcr = bfd->jcr;
834    Plugin *plugin = (Plugin *)jcr->plugin;
835    struct io_pkt io;
836
837    Dmsg0(dbglvl, "===== plugin_bclose\n");
838    if (!plugin || !jcr->plugin_ctx) {
839       return 0;
840    }
841    io.pkt_size = sizeof(io);
842    io.pkt_end = sizeof(io);
843    io.func = IO_CLOSE;
844    io.count = 0;
845    io.buf = NULL;
846    io.win32 = false;
847    io.lerror = 0;
848    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
849    bfd->berrno = io.io_errno;
850    if (io.win32) {
851       errno = b_errno_win32;
852    } else {
853       errno = io.io_errno;
854       bfd->lerror = io.lerror;
855    }
856    Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status);
857    return io.status;
858 }
859
860 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
861 {
862    JCR *jcr = bfd->jcr;
863    Plugin *plugin = (Plugin *)jcr->plugin;
864    struct io_pkt io;
865
866    Dmsg0(dbglvl, "plugin_bread\n");
867    if (!plugin || !jcr->plugin_ctx) {
868       return 0;
869    }
870    io.pkt_size = sizeof(io);
871    io.pkt_end = sizeof(io);
872    io.func = IO_READ;
873    io.count = count;
874    io.buf = (char *)buf;
875    io.win32 = false;
876    io.lerror = 0;
877    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
878    bfd->berrno = io.io_errno;
879    if (io.win32) {
880       errno = b_errno_win32;
881    } else {
882       errno = io.io_errno;
883       bfd->lerror = io.lerror;
884    }
885    return (ssize_t)io.status;
886 }
887
888 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
889 {
890    JCR *jcr = bfd->jcr;
891    Plugin *plugin = (Plugin *)jcr->plugin;
892    struct io_pkt io;
893
894    Dmsg0(dbglvl, "plugin_bwrite\n");
895    if (!plugin || !jcr->plugin_ctx) {
896       return 0;
897    }
898    io.pkt_size = sizeof(io);
899    io.pkt_end = sizeof(io);
900    io.func = IO_WRITE;
901    io.count = count;
902    io.buf = (char *)buf;
903    io.win32 = false;
904    io.lerror = 0;
905    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
906    bfd->berrno = io.io_errno;
907    if (io.win32) {
908       errno = b_errno_win32;
909    } else {
910       errno = io.io_errno;
911       bfd->lerror = io.lerror;
912    }
913    return (ssize_t)io.status;
914 }
915
916 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
917 {
918    JCR *jcr = bfd->jcr;
919    Plugin *plugin = (Plugin *)jcr->plugin;
920    struct io_pkt io;
921
922    Dmsg0(dbglvl, "plugin_bseek\n");
923    if (!plugin || !jcr->plugin_ctx) {
924       return 0;
925    }
926    io.pkt_size = sizeof(io);
927    io.pkt_end = sizeof(io);
928    io.func = IO_SEEK;
929    io.offset = offset;
930    io.whence = whence;
931    io.win32 = false;
932    io.lerror = 0;
933    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
934    bfd->berrno = io.io_errno;
935    if (io.win32) {
936       errno = b_errno_win32;
937    } else {
938       errno = io.io_errno;
939       bfd->lerror = io.lerror;
940    }
941    return (boffset_t)io.offset;
942 }
943
944 /* ==============================================================
945  *
946  * Callbacks from the plugin
947  *
948  * ==============================================================
949  */
950 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
951 {
952    JCR *jcr;
953    if (!value || !ctx) {
954       return bRC_Error;
955    }
956 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
957    jcr = ((bacula_ctx *)ctx->bContext)->jcr;
958    if (!jcr) {
959       return bRC_Error;
960    }
961 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
962    switch (var) {
963    case bVarJobId:
964       *((int *)value) = jcr->JobId;
965       Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
966       break;
967    case bVarFDName:
968       *((char **)value) = my_name;
969       Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
970       break;
971    case bVarLevel:
972       *((int *)value) = jcr->getJobLevel();
973       Dmsg1(dbglvl, "Bacula: return bVarJobLevel=%d\n", jcr->getJobLevel());
974       break;
975    case bVarType:
976       *((int *)value) = jcr->getJobType();
977       Dmsg1(dbglvl, "Bacula: return bVarJobType=%d\n", jcr->getJobType());
978       break;
979    case bVarClient:
980       *((char **)value) = jcr->client_name;
981       Dmsg1(dbglvl, "Bacula: return Client_name=%s\n", jcr->client_name);
982       break;
983    case bVarJobName:
984       *((char **)value) = jcr->Job;
985       Dmsg1(dbglvl, "Bacula: return Job name=%s\n", jcr->Job);
986       break;
987    case bVarJobStatus:
988       *((int *)value) = jcr->JobStatus;
989       Dmsg1(dbglvl, "Bacula: return bVarJobStatus=%d\n", jcr->JobStatus);
990       break;
991    case bVarSinceTime:
992       *((int *)value) = (int)jcr->mtime;
993       Dmsg1(dbglvl, "Bacula: return since=%d\n", (int)jcr->mtime);
994       break;
995    case bVarAccurate:
996       *((int *)value) = (int)jcr->accurate;
997       Dmsg1(dbglvl, "Bacula: return accurate=%d\n", (int)jcr->accurate);
998       break;
999    case bVarFileSeen:
1000       break;                 /* a write only variable, ignore read request */
1001    case bVarVssObject:
1002 #ifdef HAVE_WIN32
1003       if (g_pVSSClient) {
1004          *(void **)value = g_pVSSClient->GetVssObject();
1005          break;
1006        }
1007 #endif
1008        return bRC_Error;
1009    case bVarVssDllHandle:
1010 #ifdef HAVE_WIN32
1011       if (g_pVSSClient) {
1012          *(void **)value = g_pVSSClient->GetVssDllHandle();
1013          break;
1014        }
1015 #endif
1016        return bRC_Error;
1017    case bVarWorkingDir:
1018       *(void **)value = me->working_directory;
1019       break;
1020    case bVarWhere:
1021       *(char **)value = jcr->where;
1022       break;
1023    case bVarRegexWhere:
1024       *(char **)value = jcr->RegexWhere;
1025       break;
1026    }
1027    return bRC_OK;
1028 }
1029
1030 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
1031 {
1032    JCR *jcr;
1033    if (!value || !ctx) {
1034       return bRC_Error;
1035    }
1036 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
1037    jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1038    if (!jcr) {
1039       return bRC_Error;
1040    }
1041 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
1042    switch (var) {
1043    case bVarFileSeen:
1044       if (!accurate_mark_file_as_seen(jcr, (char *)value)) {
1045          return bRC_Error;
1046       } 
1047       break;
1048    default:
1049       break;
1050    }
1051    return bRC_OK;
1052 }
1053
1054 static bRC baculaRegisterEvents(bpContext *ctx, ...)
1055 {
1056    va_list args;
1057    uint32_t event;
1058
1059    if (!ctx) {
1060       return bRC_Error;
1061    }
1062
1063    va_start(args, ctx);
1064    while ((event = va_arg(args, uint32_t))) {
1065       Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
1066    }
1067    va_end(args);
1068    return bRC_OK;
1069 }
1070
1071 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
1072   int type, utime_t mtime, const char *fmt, ...)
1073 {
1074    va_list arg_ptr;
1075    char buf[2000];
1076    JCR *jcr;
1077
1078    if (ctx) {
1079       jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1080    } else {
1081       jcr = NULL;
1082    }
1083
1084    va_start(arg_ptr, fmt);
1085    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1086    va_end(arg_ptr);
1087    Jmsg(jcr, type, mtime, "%s", buf);
1088    return bRC_OK;
1089 }
1090
1091 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
1092   int level, const char *fmt, ...)
1093 {
1094    va_list arg_ptr;
1095    char buf[2000];
1096
1097    va_start(arg_ptr, fmt);
1098    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1099    va_end(arg_ptr);
1100    d_msg(file, line, level, "%s", buf);
1101    return bRC_OK;
1102 }
1103
1104 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
1105               size_t size)
1106 {
1107 #ifdef SMARTALLOC
1108    return sm_malloc(file, line, size);
1109 #else
1110    return malloc(size);
1111 #endif
1112 }
1113
1114 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem)
1115 {
1116 #ifdef SMARTALLOC
1117    sm_free(file, line, mem);
1118 #else
1119    free(mem);
1120 #endif
1121 }
1122
1123 static bool is_ctx_good(bpContext *ctx, JCR *&jcr, bacula_ctx *&bctx)
1124 {
1125    if (!ctx) {
1126       return false;
1127    }
1128    bctx = (bacula_ctx *)ctx->bContext;
1129    if (!bctx) {
1130       return false;
1131    }
1132    jcr = bctx->jcr;
1133    if (!jcr) {
1134       return false;
1135    }
1136    return true;
1137 }
1138
1139 /**
1140  * Let the plugin define files/directories to be excluded
1141  *  from the main backup.
1142  */
1143 static bRC baculaAddExclude(bpContext *ctx, const char *file)
1144 {
1145    JCR *jcr;
1146    bacula_ctx *bctx;
1147    if (!is_ctx_good(ctx, jcr, bctx)) {
1148       return bRC_Error;
1149    }
1150    if (!file) {
1151       return bRC_Error;
1152    }
1153    if (!bctx->exclude) {  
1154       bctx->exclude = new_exclude(jcr);
1155       new_options(jcr, bctx->exclude);
1156    }
1157    set_incexe(jcr, bctx->exclude);
1158    add_file_to_fileset(jcr, file, true);
1159    Dmsg1(100, "Add exclude file=%s\n", file);
1160    return bRC_OK;
1161 }
1162
1163 /**
1164  * Let the plugin define files/directories to be excluded
1165  *  from the main backup.
1166  */
1167 static bRC baculaAddInclude(bpContext *ctx, const char *file)
1168 {
1169    JCR *jcr;
1170    bacula_ctx *bctx;
1171    if (!is_ctx_good(ctx, jcr, bctx)) {
1172       return bRC_Error;
1173    }
1174    if (!file) {
1175       return bRC_Error;
1176    }
1177    if (!bctx->include) {  
1178       bctx->include = new_preinclude(jcr);
1179       new_options(jcr, bctx->include);
1180    }
1181    set_incexe(jcr, bctx->include);
1182    add_file_to_fileset(jcr, file, true);
1183    Dmsg1(100, "Add include file=%s\n", file);
1184    return bRC_OK;
1185 }
1186
1187 static bRC baculaAddOptions(bpContext *ctx, const char *opts)
1188 {
1189    JCR *jcr;
1190    bacula_ctx *bctx;
1191    if (!is_ctx_good(ctx, jcr, bctx)) {
1192       return bRC_Error;
1193    }
1194    if (!opts) {
1195       return bRC_Error;
1196    }
1197    add_options_to_fileset(jcr, opts);
1198    Dmsg1(1000, "Add options=%s\n", opts);
1199    return bRC_OK;
1200 }
1201
1202 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type)
1203 {
1204    JCR *jcr;
1205    bacula_ctx *bctx;
1206    if (!is_ctx_good(ctx, jcr, bctx)) {
1207       return bRC_Error;
1208    }
1209    if (!item) {
1210       return bRC_Error;
1211    }
1212    add_regex_to_fileset(jcr, item, type);
1213    Dmsg1(100, "Add regex=%s\n", item);
1214    return bRC_OK;
1215 }
1216
1217 static bRC baculaAddWild(bpContext *ctx, const char *item, int type)
1218 {
1219    JCR *jcr;
1220    bacula_ctx *bctx;
1221    if (!is_ctx_good(ctx, jcr, bctx)) {
1222       return bRC_Error;
1223    }
1224    if (!item) {
1225       return bRC_Error;
1226    }
1227    add_wild_to_fileset(jcr, item, type);
1228    Dmsg1(100, "Add wild=%s\n", item);
1229    return bRC_OK;
1230 }
1231
1232 static bRC baculaNewOptions(bpContext *ctx)
1233 {
1234    JCR *jcr;
1235    bacula_ctx *bctx;
1236    if (!is_ctx_good(ctx, jcr, bctx)) {
1237       return bRC_Error;
1238    }
1239    (void)new_options(jcr, NULL);
1240    return bRC_OK;
1241 }
1242
1243 static bRC baculaNewInclude(bpContext *ctx)
1244 {
1245    JCR *jcr;
1246    bacula_ctx *bctx;
1247    if (!is_ctx_good(ctx, jcr, bctx)) {
1248       return bRC_Error;
1249    }
1250    (void)new_include(jcr);
1251    return bRC_OK;
1252 }
1253
1254
1255 /* 
1256  * Check if a file have to be backuped using Accurate code
1257  */
1258 static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp)
1259 {
1260    JCR *jcr;
1261    bacula_ctx *bctx;
1262    FF_PKT *ff_pkt;
1263    bRC ret = bRC_Error;
1264
1265    if (!is_ctx_good(ctx, jcr, bctx)) {
1266       goto bail_out;
1267    }
1268    if (!sp) {
1269       goto bail_out;
1270    }
1271    
1272    ff_pkt = jcr->ff;
1273    /*
1274     * Copy fname and link because save_file() zaps them.  This 
1275     *  avoids zaping the plugin's strings.
1276     */
1277    ff_pkt->type = sp->type;
1278    if (!sp->fname) {
1279       Jmsg0(jcr, M_FATAL, 0, _("Command plugin: no fname in baculaCheckChanges packet.\n"));
1280       goto bail_out;
1281    }
1282
1283    ff_pkt->fname = sp->fname;
1284    ff_pkt->link = sp->link;
1285    memcpy(&ff_pkt->statp, &sp->statp, sizeof(ff_pkt->statp));
1286
1287    if (check_changes(jcr, ff_pkt))  {
1288       ret = bRC_OK;
1289    } else {
1290       ret = bRC_Seen;
1291    }
1292
1293 bail_out:
1294    Dmsg1(100, "checkChanges=%i\n", ret);
1295    return ret;
1296 }
1297
1298
1299 #ifdef TEST_PROGRAM
1300
1301 int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
1302 int     (*plugin_bclose)(JCR *jcr) = NULL;
1303 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
1304 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
1305 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
1306
1307 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
1308 {
1309    return 0;
1310 }
1311
1312 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
1313 {
1314    return true;
1315 }
1316
1317 int main(int argc, char *argv[])
1318 {
1319    char plugin_dir[1000];
1320    JCR mjcr1, mjcr2;
1321    JCR *jcr1 = &mjcr1;
1322    JCR *jcr2 = &mjcr2;
1323
1324    strcpy(my_name, "test-fd");
1325     
1326    getcwd(plugin_dir, sizeof(plugin_dir)-1);
1327    load_fd_plugins(plugin_dir);
1328
1329    jcr1->JobId = 111;
1330    new_plugins(jcr1);
1331
1332    jcr2->JobId = 222;
1333    new_plugins(jcr2);
1334
1335    generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
1336    generate_plugin_event(jcr1, bEventJobEnd);
1337    generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
1338    free_plugins(jcr1);
1339    generate_plugin_event(jcr2, bEventJobEnd);
1340    free_plugins(jcr2);
1341
1342    unload_plugins();
1343
1344    Dmsg0(dbglvl, "bacula: OK ...\n");
1345    close_memory_pool();
1346    sm_dump(false);     /* unit test */
1347    return 0;
1348 }
1349
1350 #endif /* TEST_PROGRAM */