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