]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/fd_plugins.c
Implement bEventPluginCommand event for systemstate 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 two of the GNU 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 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
39 const int dbglvl = 150;
40 #ifdef HAVE_WIN32
41 const char *plugin_type = "-fd.dll";
42 #else
43 const char *plugin_type = "-fd.so";
44 #endif
45
46 extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
47
48 /* Function pointers to be set here */
49 extern DLL_IMP_EXP int     (*plugin_bopen)(BFILE *bfd, const char *fname, int flags, mode_t mode);
50 extern DLL_IMP_EXP int     (*plugin_bclose)(BFILE *bfd);
51 extern DLL_IMP_EXP ssize_t (*plugin_bread)(BFILE *bfd, void *buf, size_t count);
52 extern DLL_IMP_EXP ssize_t (*plugin_bwrite)(BFILE *bfd, void *buf, size_t count);
53 extern DLL_IMP_EXP boffset_t (*plugin_blseek)(BFILE *bfd, boffset_t offset, int whence);
54
55
56 /* Forward referenced functions */
57 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value);
58 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value);
59 static bRC baculaRegisterEvents(bpContext *ctx, ...);
60 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
61   int type, utime_t mtime, const char *fmt, ...);
62 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
63   int level, const char *fmt, ...);
64 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
65               size_t size);
66 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem);
67 static bRC  baculaAddExclude(bpContext *ctx, const char *file);
68 static bRC baculaAddInclude(bpContext *ctx, const char *file);
69 static bRC baculaAddOptions(bpContext *ctx, const char *opts);
70 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type);
71 static bRC baculaAddWild(bpContext *ctx, const char *item, int type);
72 static bRC baculaNewOptions(bpContext *ctx);
73 static bRC baculaNewInclude(bpContext *ctx);
74 static bool is_plugin_compatible(Plugin *plugin);
75 static bool get_plugin_name(JCR *jcr, char *cmd, int *ret);
76
77 /*
78  * These will be plugged into the global pointer structure for
79  *  the findlib.
80  */
81 static int     my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode);
82 static int     my_plugin_bclose(BFILE *bfd);
83 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count);
84 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count);
85 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence);
86
87
88 /* Bacula info */
89 static bInfo binfo = {
90    sizeof(bInfo),
91    FD_PLUGIN_INTERFACE_VERSION 
92 };
93
94 /* Bacula entry points */
95 static bFuncs bfuncs = {
96    sizeof(bFuncs),
97    FD_PLUGIN_INTERFACE_VERSION,
98    baculaRegisterEvents,
99    baculaGetValue,
100    baculaSetValue,
101    baculaJobMsg,
102    baculaDebugMsg,
103    baculaMalloc,
104    baculaFree,
105    baculaAddExclude,
106    baculaAddInclude,
107    baculaAddOptions,
108    baculaAddRegex,
109    baculaAddWild,
110    baculaNewOptions,
111    baculaNewInclude
112 };
113
114 /* 
115  * Bacula private context
116  */
117 struct bacula_ctx {
118    JCR *jcr;                             /* jcr for plugin */
119    bRC  rc;                              /* last return code */
120    bool disabled;                        /* set if plugin disabled */
121    findINCEXE *exclude;                  /* pointer to exclude files */
122    findINCEXE *include;                  /* pointer to include/exclude files */
123 };
124
125 static bool is_plugin_disabled(JCR *jcr)
126 {
127    bacula_ctx *b_ctx;
128    if (!jcr->plugin_ctx) {
129       return true;
130    }
131    b_ctx = (bacula_ctx *)jcr->plugin_ctx->bContext;
132    return b_ctx->disabled;
133 }
134
135
136 /**
137  * Create a plugin event 
138  */
139 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)     
140 {
141    bEvent event;
142    Plugin *plugin;
143    int i = 0;
144    char *name=NULL;
145    int len;
146    bRC rc;
147
148    if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
149       return;                         /* Return if no plugins loaded */
150    }
151    
152    /* Some events are sent to only a particular plugin */
153    switch(eventType) {
154    case bEventPluginCommand:
155       name = (char *)value;
156       if (!get_plugin_name(jcr, name, &len)) {
157          return;
158       }
159       break;
160    default:
161       break;
162    }
163
164    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
165    event.eventType = eventType;
166
167    Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
168
169    /* Pass event to every plugin (except if name is set) */
170    foreach_alist(plugin, plugin_list) {
171       if (name && strncmp(plugin->file, name, len) != 0) {
172          i++;
173          continue;
174       }
175       jcr->plugin_ctx = &plugin_ctx_list[i++];
176       jcr->plugin = plugin;
177       if (is_plugin_disabled(jcr)) {
178          continue;
179       }
180       rc = plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, value);
181       if (rc != bRC_OK) {
182          break;
183       }
184    }
185
186    jcr->plugin = NULL;
187    jcr->plugin_ctx = NULL;
188    return;
189 }
190
191 /**
192  * Check if file was seen for accurate
193  */
194 bool plugin_check_file(JCR *jcr, char *fname)
195 {
196    Plugin *plugin;
197    int rc = bRC_OK;
198    int i = 0;
199
200    if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
201       return false;                      /* Return if no plugins loaded */
202    }
203
204    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
205
206    Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
207
208    /* Pass event to every plugin */
209    foreach_alist(plugin, plugin_list) {
210       jcr->plugin_ctx = &plugin_ctx_list[i++];
211       jcr->plugin = plugin;
212       if (is_plugin_disabled(jcr)) {
213          continue;
214       }
215       if (plug_func(plugin)->checkFile == NULL) {
216          continue;
217       }
218       rc = plug_func(plugin)->checkFile(jcr->plugin_ctx, fname);
219       if (rc == bRC_Seen) {
220          break;
221       }
222    }
223
224    jcr->plugin = NULL;
225    jcr->plugin_ctx = NULL;
226    return rc == bRC_Seen;
227 }
228
229 static bool get_plugin_name(JCR *jcr, char *cmd, int *ret)
230 {
231    char *p;
232    int len;
233    if (!cmd) {
234       return false;
235    }
236    /* Handle plugin command here backup */
237    Dmsg1(dbglvl, "plugin cmd=%s\n", cmd);
238    if (!(p = strchr(cmd, ':'))) {
239       Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
240       return false;
241    }
242    len = p - cmd;
243    if (len <= 0) {
244       return false;
245    }
246    *ret = len;
247    return true;
248 }
249
250 /**  
251  * Sequence of calls for a backup:
252  * 1. plugin_save() here is called with ff_pkt
253  * 2. we find the plugin requested on the command string
254  * 3. we generate a bEventBackupCommand event to the specified plugin
255  *    and pass it the command string.
256  * 4. we make a startPluginBackup call to the plugin, which gives
257  *    us the data we need in save_pkt
258  * 5. we call Bacula's save_file() subroutine to save the specified
259  *    file.  The plugin will be called at pluginIO() to supply the
260  *    file data.
261  *
262  * Sequence of calls for restore:
263  *   See subroutine plugin_name_stream() below.
264  */
265 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
266 {
267    Plugin *plugin;
268    int i = 0;
269    int len;
270    char *cmd = ff_pkt->top_fname;
271    struct save_pkt sp;
272    bEvent event;
273    POOL_MEM fname(PM_FNAME);
274    POOL_MEM link(PM_FNAME);
275
276    if (!plugin_list || !jcr->plugin_ctx_list || jcr->is_job_canceled()) {
277       Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" not loaded.\n", cmd);
278       return 1;                            /* Return if no plugins loaded */
279    }
280
281    jcr->cmd_plugin = true;
282    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
283    event.eventType = bEventBackupCommand;
284
285    if (!get_plugin_name(jcr, cmd, &len)) {
286       goto bail_out;
287    }
288
289    /* Note, we stop the loop on the first plugin that matches the name */
290    foreach_alist(plugin, plugin_list) {
291       Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
292       if (strncmp(plugin->file, cmd, len) != 0) {
293          i++;
294          continue;
295       }
296       /* 
297        * We put the current plugin pointer, and the plugin context
298        *  into the jcr, because during save_file(), the plugin
299        *  will be called many times and these values are needed.
300        */
301       jcr->plugin_ctx = &plugin_ctx_list[i];
302       jcr->plugin = plugin;
303       if (is_plugin_disabled(jcr)) {
304          goto bail_out;
305       }
306
307       Dmsg1(dbglvl, "Command plugin = %s\n", cmd);
308       /* Send the backup command to the right plugin*/
309       if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, cmd) != bRC_OK) {
310          goto bail_out;
311       }
312       /* Loop getting filenames to backup then saving them */
313       while (!jcr->is_job_canceled()) { 
314          memset(&sp, 0, sizeof(sp));
315          sp.pkt_size = sizeof(sp);
316          sp.pkt_end = sizeof(sp);
317          sp.portable = true;
318          sp.cmd = cmd;
319          Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
320                 &sp);
321          /* Get the file save parameters. I.e. the stat pkt ... */
322          if (plug_func(plugin)->startBackupFile(jcr->plugin_ctx, &sp) != bRC_OK) {
323             goto bail_out;
324          }
325          if (sp.type == 0) {
326             Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no type in startBackupFile packet.\n"),
327                cmd);
328             goto bail_out;
329          }
330          jcr->plugin_sp = &sp;
331          ff_pkt = jcr->ff;
332          /*
333           * Copy fname and link because save_file() zaps them.  This 
334           *  avoids zaping the plugin's strings.
335           */
336          ff_pkt->type = sp.type;
337          if (sp.type == FT_RESTORE_FIRST) {
338             if (!sp.object_name) {
339                Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no object_name in startBackupFile packet.\n"),
340                   cmd);
341                goto bail_out;
342             }
343             ff_pkt->fname = cmd;                 /* full plugin string */
344             ff_pkt->object_name = sp.object_name;
345             ff_pkt->object_index = sp.index;     /* restore object index */
346             ff_pkt->object_compression = 0;      /* no compression for now */
347             ff_pkt->object = sp.object;
348             ff_pkt->object_len = sp.object_len;
349          } else {
350             if (!sp.fname) {
351                Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no fname in startBackupFile packet.\n"),
352                   cmd);
353                goto bail_out;
354             }
355             pm_strcpy(fname, sp.fname);
356             pm_strcpy(link, sp.link);
357             ff_pkt->fname = fname.c_str();
358             ff_pkt->link = link.c_str();
359          }
360
361          memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
362          Dmsg2(dbglvl, "startBackup returned type=%d, fname=%s\n", sp.type, sp.fname);
363          if (sp.object) {
364             Dmsg2(dbglvl, "index=%d object=%s\n", sp.index, sp.object);
365          }   
366          /* Call Bacula core code to backup the plugin's file */
367          save_file(jcr, ff_pkt, true);
368          bRC rc = plug_func(plugin)->endBackupFile(jcr->plugin_ctx);
369          if (rc == bRC_More || rc == bRC_OK) {
370             accurate_mark_file_as_seen(jcr, fname.c_str());
371          }
372          if (rc == bRC_More) {
373             continue;
374          }
375          goto bail_out;
376       }
377       goto bail_out;
378    }
379    Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" not found.\n", cmd);
380
381 bail_out:
382    jcr->cmd_plugin = false;
383    jcr->plugin = NULL;
384    jcr->plugin_ctx = NULL;
385    return 1;
386 }
387
388 /**
389  * Send plugin name start/end record to SD
390  */
391 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
392 {
393    int stat;
394    int index = jcr->JobFiles;
395    struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
396
397    if (!sp) {
398       Jmsg0(jcr, M_FATAL, 0, _("Plugin save packet not found.\n"));
399       return false;
400    }
401    if (jcr->is_job_canceled()) {
402       return false;
403    }
404   
405    if (start) {
406       index++;                  /* JobFiles not incremented yet */
407    }
408    Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd);
409    /* Send stream header */
410    if (!sd->fsend("%ld %d 0", index, STREAM_PLUGIN_NAME)) {
411      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
412            sd->bstrerror());
413      return false;
414    }
415    Dmsg1(50, "send plugin name hdr: %s\n", sd->msg);
416
417    if (start) {
418       /* Send data -- not much */
419       stat = sd->fsend("%ld 1 %d %s%c", index, sp->portable, sp->cmd, 0);
420    } else {
421       /* Send end of data */
422       stat = sd->fsend("0 0");
423    }
424    if (!stat) {
425       Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
426             sd->bstrerror());
427          return false;
428    }
429    Dmsg1(dbglvl, "send plugin start/end: %s\n", sd->msg);
430    sd->signal(BNET_EOD);            /* indicate end of plugin name data */
431    return true;
432 }
433
434 /**
435  * Plugin name stream found during restore.  The record passed in
436  *  argument name was generated in send_plugin_name() above.
437  *
438  * Returns: true  if start of stream
439  *          false if end of steam
440  */
441 bool plugin_name_stream(JCR *jcr, char *name)    
442 {
443    char *p = name;
444    char *cmd;
445    bool start, portable;
446    Plugin *plugin;
447    int len;
448    int i = 0;
449    bpContext *plugin_ctx_list = jcr->plugin_ctx_list;
450
451    Dmsg1(dbglvl, "Read plugin stream string=%s\n", name);
452    skip_nonspaces(&p);             /* skip over jcr->JobFiles */
453    skip_spaces(&p);
454    start = *p == '1';
455    if (start) {
456       /* Start of plugin data */
457       skip_nonspaces(&p);          /* skip start/end flag */
458       skip_spaces(&p);
459       portable = *p == '1';
460       skip_nonspaces(&p);          /* skip portable flag */
461       skip_spaces(&p);
462       cmd = p;
463    } else {
464       /*
465        * End of plugin data, notify plugin, then clear flags   
466        */
467       Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx);
468       if (jcr->plugin) {
469          plug_func(jcr->plugin)->endRestoreFile(jcr->plugin_ctx);
470       }
471       jcr->plugin_ctx = NULL;
472       jcr->plugin = NULL;
473       goto bail_out;
474    }
475    if (!plugin_ctx_list) {
476       goto bail_out;
477    }
478       
479    /*
480     * After this point, we are dealing with a restore start
481     */
482
483 // Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd);
484    if (!(p = strchr(cmd, ':'))) {
485       Jmsg1(jcr, M_ERROR, 0,
486            _("Malformed plugin command. Name not terminated by colon: %s\n"), cmd);
487       goto bail_out;
488    }
489    len = p - cmd;
490    if (len <= 0) {
491       goto bail_out;
492    }
493
494    /*
495     * Search for correct plugin as specified on the command 
496     */
497    foreach_alist(plugin, plugin_list) {
498       bEvent event;
499       Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
500       if (strncmp(plugin->file, cmd, len) != 0) {
501          i++;
502          continue;
503       }
504       jcr->plugin_ctx = &plugin_ctx_list[i];
505       jcr->plugin = plugin;
506       if (is_plugin_disabled(jcr)) {
507          goto bail_out;
508       }
509       Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd);
510       event.eventType = bEventRestoreCommand;     
511       if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, 
512             &event, cmd) != bRC_OK) {
513          goto bail_out;
514       }
515       /* ***FIXME**** check error code */
516       plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd);
517       goto bail_out;
518    }
519    Jmsg1(jcr, M_WARNING, 0, _("Plugin=%s not found.\n"), cmd);
520
521 bail_out:
522    return start;
523 }
524
525 /**
526  * Tell the plugin to create the file.  Return values are
527  *   This is called only during Restore
528  *
529  *  CF_ERROR    -- error
530  *  CF_SKIP     -- skip processing this file
531  *  CF_EXTRACT  -- extract the file (i.e.call i/o routines)
532  *  CF_CREATED  -- created, but no content to extract (typically directories)
533  *
534  */
535 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
536 {
537    bpContext *plugin_ctx = jcr->plugin_ctx;
538    Plugin *plugin = jcr->plugin;
539    struct restore_pkt rp;
540    int flags;
541    int rc;
542
543    if (!plugin || !plugin_ctx || !set_cmd_plugin(bfd, jcr) || jcr->is_job_canceled()) {
544       return CF_ERROR;
545    }
546
547    rp.pkt_size = sizeof(rp);
548    rp.pkt_end = sizeof(rp);
549    rp.stream = attr->stream;
550    rp.data_stream = attr->data_stream;
551    rp.type = attr->type;
552    rp.file_index = attr->file_index;
553    rp.LinkFI = attr->LinkFI;
554    rp.uid = attr->uid;
555    rp.statp = attr->statp;                /* structure assignment */
556    rp.attrEx = attr->attrEx;
557    rp.ofname = attr->ofname;
558    rp.olname = attr->olname;
559    rp.where = jcr->where;
560    rp.RegexWhere = jcr->RegexWhere;
561    rp.replace = jcr->replace;
562    rp.create_status = CF_ERROR;
563    Dmsg4(dbglvl, "call plugin createFile stream=%d type=%d LinkFI=%d File=%s\n", 
564          rp.stream, rp.type, rp.LinkFI, rp.ofname);
565    if (rp.attrEx) {
566       Dmsg1(dbglvl, "attrEx=\"%s\"\n", rp.attrEx);
567    }
568    rc = plug_func(plugin)->createFile(plugin_ctx, &rp);
569    if (rc != bRC_OK) {
570       Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"),
571             rc, attr->ofname);
572       return CF_ERROR;
573    }
574    if (rp.create_status == CF_ERROR) {
575       Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"),
576             attr->ofname);
577       return CF_ERROR;
578    }
579    /* Created link or directory? */
580    if (rp.create_status == CF_CREATED) {
581       return rp.create_status;        /* yes, no need to bopen */
582    }
583
584    flags =  O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
585    Dmsg0(dbglvl, "call bopen\n");
586    int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR);
587    Dmsg1(50, "bopen status=%d\n", stat);
588    if (stat < 0) {
589       berrno be;
590       be.set_errno(bfd->berrno);
591       Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"),
592             attr->ofname, be.bstrerror());
593       Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror());
594       return CF_ERROR;
595    }
596
597    if (!is_bopen(bfd)) {
598       Dmsg0(000, "===== BFD is not open!!!!\n");
599    }
600    return CF_EXTRACT;
601 }
602
603 /**
604  * Reset the file attributes after all file I/O is done -- this allows
605  *  the previous access time/dates to be set properly, and it also allows
606  *  us to properly set directory permissions.
607  *  Not currently Implemented.
608  */
609 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
610 {
611    Dmsg0(dbglvl, "plugin_set_attributes\n");
612    if (is_bopen(ofd)) {
613       bclose(ofd);
614    }
615    pm_strcpy(attr->ofname, "*none*");
616    return true;
617 }
618
619 /*
620  * Print to file the plugin info.
621  */
622 void dump_fd_plugin(Plugin *plugin, FILE *fp)
623 {
624    if (!plugin) {
625       return ;
626    }
627    pInfo *info = (pInfo *)plugin->pinfo;
628    fprintf(fp, "\tversion=%d\n", info->version);
629    fprintf(fp, "\tdate=%s\n", NPRTB(info->plugin_date));
630    fprintf(fp, "\tmagic=%s\n", NPRTB(info->plugin_magic));
631    fprintf(fp, "\tauthor=%s\n", NPRTB(info->plugin_author));
632    fprintf(fp, "\tlicence=%s\n", NPRTB(info->plugin_license));
633    fprintf(fp, "\tversion=%s\n", NPRTB(info->plugin_version));
634    fprintf(fp, "\tdescription=%s\n", NPRTB(info->plugin_description));
635 }
636
637 /**
638  * This entry point is called internally by Bacula to ensure
639  *  that the plugin IO calls come into this code.
640  */
641 void load_fd_plugins(const char *plugin_dir)
642 {
643    Plugin *plugin;
644
645    if (!plugin_dir) {
646       Dmsg0(dbglvl, "plugin dir is NULL\n");
647       return;
648    }
649
650    plugin_list = New(alist(10, not_owned_by_alist));
651    if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type,
652                      is_plugin_compatible)) {
653       /* Either none found, or some error */
654       if (plugin_list->size() == 0) {
655          delete plugin_list;
656          plugin_list = NULL;
657          Dmsg0(dbglvl, "No plugins loaded\n");
658          return;
659       }
660    }
661
662    /* Plug entry points called from findlib */
663    plugin_bopen  = my_plugin_bopen;
664    plugin_bclose = my_plugin_bclose;
665    plugin_bread  = my_plugin_bread;
666    plugin_bwrite = my_plugin_bwrite;
667    plugin_blseek = my_plugin_blseek;
668
669    /* 
670     * Verify that the plugin is acceptable, and print information
671     *  about it.
672     */
673    foreach_alist(plugin, plugin_list) {
674       Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file);
675       Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file);
676    }
677
678    dbg_plugin_add_hook(dump_fd_plugin);
679 }
680
681 /**
682  * Check if a plugin is compatible.  Called by the load_plugin function
683  *  to allow us to verify the plugin.
684  */
685 static bool is_plugin_compatible(Plugin *plugin)
686 {
687    pInfo *info = (pInfo *)plugin->pinfo;
688    Dmsg0(50, "is_plugin_compatible called\n");
689    if (debug_level >= 50) {
690       dump_fd_plugin(plugin, stdin);
691    }
692    if (strcmp(info->plugin_magic, FD_PLUGIN_MAGIC) != 0) {
693       Jmsg(NULL, M_ERROR, 0, _("Plugin magic wrong. Plugin=%s wanted=%s got=%s\n"),
694            plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic);
695       Dmsg3(50, "Plugin magic wrong. Plugin=%s wanted=%s got=%s\n",
696            plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic);
697
698       return false;
699    }
700    if (info->version != FD_PLUGIN_INTERFACE_VERSION) {
701       Jmsg(NULL, M_ERROR, 0, _("Plugin version incorrect. Plugin=%s wanted=%d got=%d\n"),
702            plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version);
703       Dmsg3(50, "Plugin version incorrect. Plugin=%s wanted=%d got=%d\n",
704            plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version);
705       return false;
706    }
707    if (strcmp(info->plugin_license, "Bacula GPLv2") != 0 &&
708        strcmp(info->plugin_license, "GPLv2") != 0) {
709       Jmsg(NULL, M_ERROR, 0, _("Plugin license incompatible. Plugin=%s license=%s\n"),
710            plugin->file, info->plugin_license);
711       Dmsg2(50, "Plugin license incompatible. Plugin=%s license=%s\n",
712            plugin->file, info->plugin_license);
713       return false;
714    }
715       
716    return true;
717 }
718
719
720 /**
721  * Create a new instance of each plugin for this Job
722  *   Note, plugin_list can exist but jcr->plugin_ctx_list can
723  *   be NULL if no plugins were loaded.
724  */
725 void new_plugins(JCR *jcr)
726 {
727    Plugin *plugin;
728    int i = 0;
729
730    if (!plugin_list) {
731       Dmsg0(dbglvl, "plugin list is NULL\n");
732       return;
733    }
734    if (jcr->is_job_canceled() || jcr->JobId == 0) {
735       return;
736    }
737
738    int num = plugin_list->size();
739
740    if (num == 0) {
741       Dmsg0(dbglvl, "No plugins loaded\n");
742       return;
743    }
744
745    jcr->plugin_ctx_list = (bpContext *)malloc(sizeof(bpContext) * num);
746
747    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
748    Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
749    foreach_alist(plugin, plugin_list) {
750       /* Start a new instance of each plugin */
751       bacula_ctx *b_ctx = (bacula_ctx *)malloc(sizeof(bacula_ctx));
752       memset(b_ctx, 0, sizeof(bacula_ctx));
753       b_ctx->jcr = jcr;
754       plugin_ctx_list[i].bContext = (void *)b_ctx;   /* Bacula private context */
755       plugin_ctx_list[i].pContext = NULL;
756       if (plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]) != bRC_OK) {
757          b_ctx->disabled = true;
758       }
759    }
760 }
761
762 /**
763  * Free the plugin instances for this Job
764  */
765 void free_plugins(JCR *jcr)
766 {
767    Plugin *plugin;
768    int i = 0;
769
770    if (!plugin_list || !jcr->plugin_ctx_list) {
771       return;                         /* no plugins, nothing to do */
772    }
773
774    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
775    Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
776    foreach_alist(plugin, plugin_list) {   
777       /* Free the plugin instance */
778       plug_func(plugin)->freePlugin(&plugin_ctx_list[i]);
779       free(plugin_ctx_list[i++].bContext);     /* free Bacula private context */
780    }
781    free(plugin_ctx_list);
782    jcr->plugin_ctx_list = NULL;
783 }
784
785 static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode)
786 {
787    JCR *jcr = bfd->jcr;
788    Plugin *plugin = (Plugin *)jcr->plugin;
789    struct io_pkt io;
790
791    Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags);
792    if (!plugin || !jcr->plugin_ctx) {
793       return 0;
794    }
795    io.pkt_size = sizeof(io);
796    io.pkt_end = sizeof(io);
797    io.func = IO_OPEN;
798    io.count = 0;
799    io.buf = NULL;
800    io.fname = fname;
801    io.flags = flags;
802    io.mode = mode;
803    io.win32 = false;
804    io.lerror = 0;
805    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
806    bfd->berrno = io.io_errno;
807    if (io.win32) {
808       errno = b_errno_win32;
809    } else {
810       errno = io.io_errno;
811       bfd->lerror = io.lerror;
812    }
813    Dmsg1(50, "Return from plugin open status=%d\n", io.status);
814    return io.status;
815 }
816
817 static int my_plugin_bclose(BFILE *bfd)
818 {
819    JCR *jcr = bfd->jcr;
820    Plugin *plugin = (Plugin *)jcr->plugin;
821    struct io_pkt io;
822
823    Dmsg0(dbglvl, "===== plugin_bclose\n");
824    if (!plugin || !jcr->plugin_ctx) {
825       return 0;
826    }
827    io.pkt_size = sizeof(io);
828    io.pkt_end = sizeof(io);
829    io.func = IO_CLOSE;
830    io.count = 0;
831    io.buf = NULL;
832    io.win32 = false;
833    io.lerror = 0;
834    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
835    bfd->berrno = io.io_errno;
836    if (io.win32) {
837       errno = b_errno_win32;
838    } else {
839       errno = io.io_errno;
840       bfd->lerror = io.lerror;
841    }
842    Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status);
843    return io.status;
844 }
845
846 static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count)
847 {
848    JCR *jcr = bfd->jcr;
849    Plugin *plugin = (Plugin *)jcr->plugin;
850    struct io_pkt io;
851
852    Dmsg0(dbglvl, "plugin_bread\n");
853    if (!plugin || !jcr->plugin_ctx) {
854       return 0;
855    }
856    io.pkt_size = sizeof(io);
857    io.pkt_end = sizeof(io);
858    io.func = IO_READ;
859    io.count = count;
860    io.buf = (char *)buf;
861    io.win32 = false;
862    io.lerror = 0;
863    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
864    bfd->berrno = io.io_errno;
865    if (io.win32) {
866       errno = b_errno_win32;
867    } else {
868       errno = io.io_errno;
869       bfd->lerror = io.lerror;
870    }
871    return (ssize_t)io.status;
872 }
873
874 static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count)
875 {
876    JCR *jcr = bfd->jcr;
877    Plugin *plugin = (Plugin *)jcr->plugin;
878    struct io_pkt io;
879
880    Dmsg0(dbglvl, "plugin_bwrite\n");
881    if (!plugin || !jcr->plugin_ctx) {
882       return 0;
883    }
884    io.pkt_size = sizeof(io);
885    io.pkt_end = sizeof(io);
886    io.func = IO_WRITE;
887    io.count = count;
888    io.buf = (char *)buf;
889    io.win32 = false;
890    io.lerror = 0;
891    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
892    bfd->berrno = io.io_errno;
893    if (io.win32) {
894       errno = b_errno_win32;
895    } else {
896       errno = io.io_errno;
897       bfd->lerror = io.lerror;
898    }
899    return (ssize_t)io.status;
900 }
901
902 static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence)
903 {
904    JCR *jcr = bfd->jcr;
905    Plugin *plugin = (Plugin *)jcr->plugin;
906    struct io_pkt io;
907
908    Dmsg0(dbglvl, "plugin_bseek\n");
909    if (!plugin || !jcr->plugin_ctx) {
910       return 0;
911    }
912    io.pkt_size = sizeof(io);
913    io.pkt_end = sizeof(io);
914    io.func = IO_SEEK;
915    io.offset = offset;
916    io.whence = whence;
917    io.win32 = false;
918    io.lerror = 0;
919    plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io);
920    bfd->berrno = io.io_errno;
921    if (io.win32) {
922       errno = b_errno_win32;
923    } else {
924       errno = io.io_errno;
925       bfd->lerror = io.lerror;
926    }
927    return (boffset_t)io.offset;
928 }
929
930 /* ==============================================================
931  *
932  * Callbacks from the plugin
933  *
934  * ==============================================================
935  */
936 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
937 {
938    JCR *jcr;
939    if (!value || !ctx) {
940       return bRC_Error;
941    }
942 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
943    jcr = ((bacula_ctx *)ctx->bContext)->jcr;
944    if (!jcr) {
945       return bRC_Error;
946    }
947 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
948    switch (var) {
949    case bVarJobId:
950       *((int *)value) = jcr->JobId;
951       Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
952       break;
953    case bVarFDName:
954       *((char **)value) = my_name;
955       Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
956       break;
957    case bVarLevel:
958       *((int *)value) = jcr->getJobLevel();
959       Dmsg1(dbglvl, "Bacula: return bVarJobLevel=%d\n", jcr->getJobLevel());
960       break;
961    case bVarType:
962       *((int *)value) = jcr->getJobType();
963       Dmsg1(dbglvl, "Bacula: return bVarJobType=%d\n", jcr->getJobType());
964       break;
965    case bVarClient:
966       *((char **)value) = jcr->client_name;
967       Dmsg1(dbglvl, "Bacula: return Client_name=%s\n", jcr->client_name);
968       break;
969    case bVarJobName:
970       *((char **)value) = jcr->Job;
971       Dmsg1(dbglvl, "Bacula: return Job name=%s\n", jcr->Job);
972       break;
973    case bVarJobStatus:
974       *((int *)value) = jcr->JobStatus;
975       Dmsg1(dbglvl, "Bacula: return bVarJobStatus=%d\n", jcr->JobStatus);
976       break;
977    case bVarSinceTime:
978       *((int *)value) = (int)jcr->mtime;
979       Dmsg1(dbglvl, "Bacula: return since=%d\n", (int)jcr->mtime);
980       break;
981    case bVarAccurate:
982       *((int *)value) = (int)jcr->accurate;
983       Dmsg1(dbglvl, "Bacula: return accurate=%d\n", (int)jcr->accurate);
984       break;
985    case bVarFileSeen:
986       break;                 /* a write only variable, ignore read request */
987    case bVarVssObject:
988 #ifdef HAVE_WIN32
989       if (g_pVSSClient) {
990          *(void **)value = g_pVSSClient->GetVssObject();
991          break;
992        }
993 #endif
994        return bRC_Error;
995    case bVarVssDllHandle:
996 #ifdef HAVE_WIN32
997       if (g_pVSSClient) {
998          *(void **)value = g_pVSSClient->GetVssDllHandle();
999          break;
1000        }
1001 #endif
1002        return bRC_Error;
1003    case bVarWorkingDir:
1004       *(void **)value = me->working_directory;
1005       break;
1006    }
1007    return bRC_OK;
1008 }
1009
1010 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
1011 {
1012    JCR *jcr;
1013    if (!value || !ctx) {
1014       return bRC_Error;
1015    }
1016 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
1017    jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1018    if (!jcr) {
1019       return bRC_Error;
1020    }
1021 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
1022    switch (var) {
1023    case bVarFileSeen:
1024       if (!accurate_mark_file_as_seen(jcr, (char *)value)) {
1025          return bRC_Error;
1026       } 
1027       break;
1028    default:
1029       break;
1030    }
1031    return bRC_OK;
1032 }
1033
1034 static bRC baculaRegisterEvents(bpContext *ctx, ...)
1035 {
1036    va_list args;
1037    uint32_t event;
1038
1039    if (!ctx) {
1040       return bRC_Error;
1041    }
1042
1043    va_start(args, ctx);
1044    while ((event = va_arg(args, uint32_t))) {
1045       Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
1046    }
1047    va_end(args);
1048    return bRC_OK;
1049 }
1050
1051 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
1052   int type, utime_t mtime, const char *fmt, ...)
1053 {
1054    va_list arg_ptr;
1055    char buf[2000];
1056    JCR *jcr;
1057
1058    if (ctx) {
1059       jcr = ((bacula_ctx *)ctx->bContext)->jcr;
1060    } else {
1061       jcr = NULL;
1062    }
1063
1064    va_start(arg_ptr, fmt);
1065    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1066    va_end(arg_ptr);
1067    Jmsg(jcr, type, mtime, "%s", buf);
1068    return bRC_OK;
1069 }
1070
1071 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
1072   int level, const char *fmt, ...)
1073 {
1074    va_list arg_ptr;
1075    char buf[2000];
1076
1077    va_start(arg_ptr, fmt);
1078    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
1079    va_end(arg_ptr);
1080    d_msg(file, line, level, "%s", buf);
1081    return bRC_OK;
1082 }
1083
1084 static void *baculaMalloc(bpContext *ctx, const char *file, int line,
1085               size_t size)
1086 {
1087 #ifdef SMARTALLOC
1088    return sm_malloc(file, line, size);
1089 #else
1090    return malloc(size);
1091 #endif
1092 }
1093
1094 static void baculaFree(bpContext *ctx, const char *file, int line, void *mem)
1095 {
1096 #ifdef SMARTALLOC
1097    sm_free(file, line, mem);
1098 #else
1099    free(mem);
1100 #endif
1101 }
1102
1103 static bool is_ctx_good(bpContext *ctx, JCR *&jcr, bacula_ctx *&bctx)
1104 {
1105    if (!ctx) {
1106       return false;
1107    }
1108    bctx = (bacula_ctx *)ctx->bContext;
1109    if (!bctx) {
1110       return false;
1111    }
1112    jcr = bctx->jcr;
1113    if (!jcr) {
1114       return false;
1115    }
1116    return true;
1117 }
1118
1119 /**
1120  * Let the plugin define files/directories to be excluded
1121  *  from the main backup.
1122  */
1123 static bRC baculaAddExclude(bpContext *ctx, const char *file)
1124 {
1125    JCR *jcr;
1126    bacula_ctx *bctx;
1127    if (!is_ctx_good(ctx, jcr, bctx)) {
1128       return bRC_Error;
1129    }
1130    if (!file) {
1131       return bRC_Error;
1132    }
1133    if (!bctx->exclude) {  
1134       bctx->exclude = new_exclude(jcr);
1135       new_options(jcr, bctx->exclude);
1136    }
1137    set_incexe(jcr, bctx->exclude);
1138    add_file_to_fileset(jcr, file, true);
1139    Dmsg1(100, "Add exclude file=%s\n", file);
1140    return bRC_OK;
1141 }
1142
1143 /**
1144  * Let the plugin define files/directories to be excluded
1145  *  from the main backup.
1146  */
1147 static bRC baculaAddInclude(bpContext *ctx, const char *file)
1148 {
1149    JCR *jcr;
1150    bacula_ctx *bctx;
1151    if (!is_ctx_good(ctx, jcr, bctx)) {
1152       return bRC_Error;
1153    }
1154    if (!file) {
1155       return bRC_Error;
1156    }
1157    if (!bctx->include) {  
1158       bctx->include = new_preinclude(jcr);
1159       new_options(jcr, bctx->include);
1160    }
1161    set_incexe(jcr, bctx->include);
1162    add_file_to_fileset(jcr, file, true);
1163    Dmsg1(100, "Add include file=%s\n", file);
1164    return bRC_OK;
1165 }
1166
1167 static bRC baculaAddOptions(bpContext *ctx, const char *opts)
1168 {
1169    JCR *jcr;
1170    bacula_ctx *bctx;
1171    if (!is_ctx_good(ctx, jcr, bctx)) {
1172       return bRC_Error;
1173    }
1174    if (!opts) {
1175       return bRC_Error;
1176    }
1177    add_options_to_fileset(jcr, opts);
1178    Dmsg1(1000, "Add options=%s\n", opts);
1179    return bRC_OK;
1180 }
1181
1182 static bRC baculaAddRegex(bpContext *ctx, const char *item, int type)
1183 {
1184    JCR *jcr;
1185    bacula_ctx *bctx;
1186    if (!is_ctx_good(ctx, jcr, bctx)) {
1187       return bRC_Error;
1188    }
1189    if (!item) {
1190       return bRC_Error;
1191    }
1192    add_regex_to_fileset(jcr, item, type);
1193    Dmsg1(100, "Add regex=%s\n", item);
1194    return bRC_OK;
1195 }
1196
1197 static bRC baculaAddWild(bpContext *ctx, const char *item, int type)
1198 {
1199    JCR *jcr;
1200    bacula_ctx *bctx;
1201    if (!is_ctx_good(ctx, jcr, bctx)) {
1202       return bRC_Error;
1203    }
1204    if (!item) {
1205       return bRC_Error;
1206    }
1207    add_wild_to_fileset(jcr, item, type);
1208    Dmsg1(100, "Add wild=%s\n", item);
1209    return bRC_OK;
1210 }
1211
1212 static bRC baculaNewOptions(bpContext *ctx)
1213 {
1214    JCR *jcr;
1215    bacula_ctx *bctx;
1216    if (!is_ctx_good(ctx, jcr, bctx)) {
1217       return bRC_Error;
1218    }
1219    (void)new_options(jcr, NULL);
1220    return bRC_OK;
1221 }
1222
1223 static bRC baculaNewInclude(bpContext *ctx)
1224 {
1225    JCR *jcr;
1226    bacula_ctx *bctx;
1227    if (!is_ctx_good(ctx, jcr, bctx)) {
1228       return bRC_Error;
1229    }
1230    (void)new_include(jcr);
1231    return bRC_OK;
1232 }
1233
1234
1235 #ifdef TEST_PROGRAM
1236
1237 int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
1238 int     (*plugin_bclose)(JCR *jcr) = NULL;
1239 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
1240 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
1241 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
1242
1243 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
1244 {
1245    return 0;
1246 }
1247
1248 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
1249 {
1250    return true;
1251 }
1252
1253 int main(int argc, char *argv[])
1254 {
1255    char plugin_dir[1000];
1256    JCR mjcr1, mjcr2;
1257    JCR *jcr1 = &mjcr1;
1258    JCR *jcr2 = &mjcr2;
1259
1260    strcpy(my_name, "test-fd");
1261     
1262    getcwd(plugin_dir, sizeof(plugin_dir)-1);
1263    load_fd_plugins(plugin_dir);
1264
1265    jcr1->JobId = 111;
1266    new_plugins(jcr1);
1267
1268    jcr2->JobId = 222;
1269    new_plugins(jcr2);
1270
1271    generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
1272    generate_plugin_event(jcr1, bEventJobEnd);
1273    generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
1274    free_plugins(jcr1);
1275    generate_plugin_event(jcr2, bEventJobEnd);
1276    free_plugins(jcr2);
1277
1278    unload_plugins();
1279
1280    Dmsg0(dbglvl, "bacula: OK ...\n");
1281    close_memory_pool();
1282    sm_dump(false);
1283    return 0;
1284 }
1285
1286 #endif /* TEST_PROGRAM */