]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/fd_plugins.c
Ensure that two plugins starting with the same name won't share events
[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       Dmsg4(0, "plugin=%s plen=%d cmd=%s len=%d\n", plugin->file, plugin->len, cmd, len);
331       if ((plugin->len != len) || (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.offset = 0;
911    io.lerror = 0;
912    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
913    bfd->offset = io.offset;
914    bfd->berrno = io.io_errno;
915    if (io.win32) {
916       errno = b_errno_win32;
917    } else {
918       errno = io.io_errno;
919       bfd->lerror = io.lerror;
920    }
921    return (ssize_t)io.status;
922 }
923
924 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
925 {
926    JCR *jcr = bfd->jcr;
927    Plugin *plugin = (Plugin *)jcr->plugin;
928    struct io_pkt io;
929
930    Dmsg0(dbglvl, "plugin_bwrite\n");
931    if (!plugin || !jcr->plugin_ctx) {
932       return 0;
933    }
934    io.pkt_size = sizeof(io);
935    io.pkt_end = sizeof(io);
936    io.func = IO_WRITE;
937    io.count = count;
938    io.buf = (char *)buf;
939    io.win32 = false;
940    io.lerror = 0;
941    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
942    bfd->berrno = io.io_errno;
943    if (io.win32) {
944       errno = b_errno_win32;
945    } else {
946       errno = io.io_errno;
947       bfd->lerror = io.lerror;
948    }
949    return (ssize_t)io.status;
950 }
951
952 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
953 {
954    JCR *jcr = bfd->jcr;
955    Plugin *plugin = (Plugin *)jcr->plugin;
956    struct io_pkt io;
957
958    Dmsg0(dbglvl, "plugin_bseek\n");
959    if (!plugin || !jcr->plugin_ctx) {
960       return 0;
961    }
962    io.pkt_size = sizeof(io);
963    io.pkt_end = sizeof(io);
964    io.func = IO_SEEK;
965    io.offset = offset;
966    io.whence = whence;
967    io.win32 = false;
968    io.lerror = 0;
969    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
970    bfd->berrno = io.io_errno;
971    if (io.win32) {
972       errno = b_errno_win32;
973    } else {
974       errno = io.io_errno;
975       bfd->lerror = io.lerror;
976    }
977    return (boffset_t)io.offset;
978 }
979
980 /* ==============================================================
981  *
982  * Callbacks from the plugin
983  *
984  * ==============================================================
985  */
986 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
987 {
988    JCR *jcr;
989    if (!value) {
990       return bRC_Error;
991    }
992
993    switch (var) {               /* General variables, no need of ctx */
994    case bVarFDName:
995       *((char **)value) = my_name;
996       break;
997    case bVarWorkingDir:
998       *(void **)value = me->working_directory;
999       break;
1000    case bVarExePath:
1001       *(char **)value = exepath;
1002       break;
1003    default:
1004       break;
1005    }
1006
1007    if (!ctx) {                  /* Other variables need context */
1008       return bRC_Error;
1009    }
1010
1011    jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1012    if (!jcr) {
1013       return bRC_Error;
1014    }
1015
1016    switch (var) {
1017    case bVarJobId:
1018       *((int *)value) = jcr->JobId;
1019       Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
1020       break;
1021    case bVarLevel:
1022       *((int *)value) = jcr->getJobLevel();
1023       Dmsg1(dbglvl, "Bacula: return bVarJobLevel=%d\n", jcr->getJobLevel());
1024       break;
1025    case bVarType:
1026       *((int *)value) = jcr->getJobType();
1027       Dmsg1(dbglvl, "Bacula: return bVarJobType=%d\n", jcr->getJobType());
1028       break;
1029    case bVarClient:
1030       *((char **)value) = jcr->client_name;
1031       Dmsg1(dbglvl, "Bacula: return Client_name=%s\n", jcr->client_name);
1032       break;
1033    case bVarJobName:
1034       *((char **)value) = jcr->Job;
1035       Dmsg1(dbglvl, "Bacula: return Job name=%s\n", jcr->Job);
1036       break;
1037    case bVarJobStatus:
1038       *((int *)value) = jcr->JobStatus;
1039       Dmsg1(dbglvl, "Bacula: return bVarJobStatus=%d\n", jcr->JobStatus);
1040       break;
1041    case bVarSinceTime:
1042       *((int *)value) = (int)jcr->mtime;
1043       Dmsg1(dbglvl, "Bacula: return since=%d\n", (int)jcr->mtime);
1044       break;
1045    case bVarAccurate:
1046       *((int *)value) = (int)jcr->accurate;
1047       Dmsg1(dbglvl, "Bacula: return accurate=%d\n", (int)jcr->accurate);
1048       break;
1049    case bVarFileSeen:
1050       break;                 /* a write only variable, ignore read request */
1051    case bVarVssObject:
1052 #ifdef HAVE_WIN32
1053       if (g_pVSSClient) {
1054          *(void **)value = g_pVSSClient->GetVssObject();
1055          break;
1056        }
1057 #endif
1058        return bRC_Error;
1059    case bVarVssDllHandle:
1060 #ifdef HAVE_WIN32
1061       if (g_pVSSClient) {
1062          *(void **)value = g_pVSSClient->GetVssDllHandle();
1063          break;
1064        }
1065 #endif
1066        return bRC_Error;
1067    case bVarWhere:
1068       *(char **)value = jcr->where;
1069       break;
1070    case bVarRegexWhere:
1071       *(char **)value = jcr->RegexWhere;
1072       break;
1073
1074    case bVarFDName:             /* get warning with g++ if we missed one */
1075    case bVarWorkingDir:
1076    case bVarExePath:
1077       break;
1078    }
1079    return bRC_OK;
1080 }
1081
1082 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
1083 {
1084    JCR *jcr;
1085    if (!value || !ctx) {
1086       return bRC_Error;
1087    }
1088 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
1089    jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1090    if (!jcr) {
1091       return bRC_Error;
1092    }
1093 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
1094    switch (var) {
1095    case bVarFileSeen:
1096       if (!accurate_mark_file_as_seen(jcr, (char *)value)) {
1097          return bRC_Error;
1098       } 
1099       break;
1100    default:
1101       break;
1102    }
1103    return bRC_OK;
1104 }
1105
1106 static bRC baculaRegisterEvents(bpContext *ctx, ...)
1107 {
1108    va_list args;
1109    uint32_t event;
1110
1111    if (!ctx) {
1112       return bRC_Error;
1113    }
1114
1115    va_start(args, ctx);
1116    while ((event = va_arg(args, uint32_t))) {
1117       Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
1118    }
1119    va_end(args);
1120    return bRC_OK;
1121 }
1122
1123 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
1124   int type, utime_t mtime, const char *fmt, ...)
1125 {
1126    va_list arg_ptr;
1127    char buf[2000];
1128    JCR *jcr;
1129
1130    if (ctx) {
1131       jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1132    } else {
1133       jcr = NULL;
1134    }
1135
1136    va_start(arg_ptr, fmt);
1137    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1138    va_end(arg_ptr);
1139    Jmsg(jcr, type, mtime, "%s", buf);
1140    return bRC_OK;
1141 }
1142
1143 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
1144   int level, const char *fmt, ...)
1145 {
1146    va_list arg_ptr;
1147    char buf[2000];
1148
1149    va_start(arg_ptr, fmt);
1150    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1151    va_end(arg_ptr);
1152    d_msg(file, line, level, "%s", buf);
1153    return bRC_OK;
1154 }
1155
1156 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
1157               size_t size)
1158 {
1159 #ifdef SMARTALLOC
1160    return sm_malloc(file, line, size);
1161 #else
1162    return malloc(size);
1163 #endif
1164 }
1165
1166 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem)
1167 {
1168 #ifdef SMARTALLOC
1169    sm_free(file, line, mem);
1170 #else
1171    free(mem);
1172 #endif
1173 }
1174
1175 static bool is_ctx_good(bpContext *ctx, JCR *&jcr, bacula_ctx *&bctx)
1176 {
1177    if (!ctx) {
1178       return false;
1179    }
1180    bctx = (bacula_ctx *)ctx->bContext;
1181    if (!bctx) {
1182       return false;
1183    }
1184    jcr = bctx->jcr;
1185    if (!jcr) {
1186       return false;
1187    }
1188    return true;
1189 }
1190
1191 /**
1192  * Let the plugin define files/directories to be excluded
1193  *  from the main backup.
1194  */
1195 static bRC baculaAddExclude(bpContext *ctx, const char *file)
1196 {
1197    JCR *jcr;
1198    bacula_ctx *bctx;
1199    if (!is_ctx_good(ctx, jcr, bctx)) {
1200       return bRC_Error;
1201    }
1202    if (!file) {
1203       return bRC_Error;
1204    }
1205    if (!bctx->exclude) {  
1206       bctx->exclude = new_exclude(jcr);
1207       new_options(jcr, bctx->exclude);
1208    }
1209    set_incexe(jcr, bctx->exclude);
1210    add_file_to_fileset(jcr, file, true);
1211    Dmsg1(100, "Add exclude file=%s\n", file);
1212    return bRC_OK;
1213 }
1214
1215 /**
1216  * Let the plugin define files/directories to be excluded
1217  *  from the main backup.
1218  */
1219 static bRC baculaAddInclude(bpContext *ctx, const char *file)
1220 {
1221    JCR *jcr;
1222    bacula_ctx *bctx;
1223    if (!is_ctx_good(ctx, jcr, bctx)) {
1224       return bRC_Error;
1225    }
1226    if (!file) {
1227       return bRC_Error;
1228    }
1229    if (!bctx->include) {  
1230       bctx->include = new_preinclude(jcr);
1231       new_options(jcr, bctx->include);
1232    }
1233    set_incexe(jcr, bctx->include);
1234    add_file_to_fileset(jcr, file, true);
1235    Dmsg1(100, "Add include file=%s\n", file);
1236    return bRC_OK;
1237 }
1238
1239 static bRC baculaAddOptions(bpContext *ctx, const char *opts)
1240 {
1241    JCR *jcr;
1242    bacula_ctx *bctx;
1243    if (!is_ctx_good(ctx, jcr, bctx)) {
1244       return bRC_Error;
1245    }
1246    if (!opts) {
1247       return bRC_Error;
1248    }
1249    add_options_to_fileset(jcr, opts);
1250    Dmsg1(1000, "Add options=%s\n", opts);
1251    return bRC_OK;
1252 }
1253
1254 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type)
1255 {
1256    JCR *jcr;
1257    bacula_ctx *bctx;
1258    if (!is_ctx_good(ctx, jcr, bctx)) {
1259       return bRC_Error;
1260    }
1261    if (!item) {
1262       return bRC_Error;
1263    }
1264    add_regex_to_fileset(jcr, item, type);
1265    Dmsg1(100, "Add regex=%s\n", item);
1266    return bRC_OK;
1267 }
1268
1269 static bRC baculaAddWild(bpContext *ctx, const char *item, int type)
1270 {
1271    JCR *jcr;
1272    bacula_ctx *bctx;
1273    if (!is_ctx_good(ctx, jcr, bctx)) {
1274       return bRC_Error;
1275    }
1276    if (!item) {
1277       return bRC_Error;
1278    }
1279    add_wild_to_fileset(jcr, item, type);
1280    Dmsg1(100, "Add wild=%s\n", item);
1281    return bRC_OK;
1282 }
1283
1284 static bRC baculaNewOptions(bpContext *ctx)
1285 {
1286    JCR *jcr;
1287    bacula_ctx *bctx;
1288    if (!is_ctx_good(ctx, jcr, bctx)) {
1289       return bRC_Error;
1290    }
1291    (void)new_options(jcr, NULL);
1292    return bRC_OK;
1293 }
1294
1295 static bRC baculaNewInclude(bpContext *ctx)
1296 {
1297    JCR *jcr;
1298    bacula_ctx *bctx;
1299    if (!is_ctx_good(ctx, jcr, bctx)) {
1300       return bRC_Error;
1301    }
1302    (void)new_include(jcr);
1303    return bRC_OK;
1304 }
1305
1306
1307 /* 
1308  * Check if a file have to be backuped using Accurate code
1309  */
1310 static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp)
1311 {
1312    JCR *jcr;
1313    bacula_ctx *bctx;
1314    FF_PKT *ff_pkt;
1315    bRC ret = bRC_Error;
1316
1317    if (!is_ctx_good(ctx, jcr, bctx)) {
1318       goto bail_out;
1319    }
1320    if (!sp) {
1321       goto bail_out;
1322    }
1323    
1324    ff_pkt = jcr->ff;
1325    /*
1326     * Copy fname and link because save_file() zaps them.  This 
1327     *  avoids zaping the plugin's strings.
1328     */
1329    ff_pkt->type = sp->type;
1330    if (!sp->fname) {
1331       Jmsg0(jcr, M_FATAL, 0, _("Command plugin: no fname in baculaCheckChanges packet.\n"));
1332       goto bail_out;
1333    }
1334
1335    ff_pkt->fname = sp->fname;
1336    ff_pkt->link = sp->link;
1337    memcpy(&ff_pkt->statp, &sp->statp, sizeof(ff_pkt->statp));
1338
1339    if (check_changes(jcr, ff_pkt))  {
1340       ret = bRC_OK;
1341    } else {
1342       ret = bRC_Seen;
1343    }
1344
1345    /* check_changes() can update delta sequence number, return it to the
1346     * plugin 
1347     */
1348    sp->delta_seq = ff_pkt->delta_seq;
1349
1350 bail_out:
1351    Dmsg1(100, "checkChanges=%i\n", ret);
1352    return ret;
1353 }
1354
1355
1356 #ifdef TEST_PROGRAM
1357
1358 int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
1359 int     (*plugin_bclose)(JCR *jcr) = NULL;
1360 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
1361 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
1362 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
1363
1364 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
1365 {
1366    return 0;
1367 }
1368
1369 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
1370 {
1371    return true;
1372 }
1373
1374 int main(int argc, char *argv[])
1375 {
1376    char plugin_dir[1000];
1377    JCR mjcr1, mjcr2;
1378    JCR *jcr1 = &mjcr1;
1379    JCR *jcr2 = &mjcr2;
1380
1381    strcpy(my_name, "test-fd");
1382     
1383    getcwd(plugin_dir, sizeof(plugin_dir)-1);
1384    load_fd_plugins(plugin_dir);
1385
1386    jcr1->JobId = 111;
1387    new_plugins(jcr1);
1388
1389    jcr2->JobId = 222;
1390    new_plugins(jcr2);
1391
1392    generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
1393    generate_plugin_event(jcr1, bEventJobEnd);
1394    generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
1395    free_plugins(jcr1);
1396    generate_plugin_event(jcr2, bEventJobEnd);
1397    free_plugins(jcr2);
1398
1399    unload_plugins();
1400
1401    Dmsg0(dbglvl, "bacula: OK ...\n");
1402    close_memory_pool();
1403    sm_dump(false);     /* unit test */
1404    return 0;
1405 }
1406
1407 #endif /* TEST_PROGRAM */