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