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