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