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