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