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