]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/fd_plugins.c
Tweak technotes
[bacula/bacula] / bacula / src / filed / fd_plugins.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2008 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 const int dbglvl = 50;
38 const char *plugin_type = "-fd.so";
39
40 extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
41
42 /* Function pointers to be set here */
43 extern DLL_IMP_EXP int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode);
44 extern DLL_IMP_EXP int     (*plugin_bclose)(JCR *jcr);
45 extern DLL_IMP_EXP ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count);
46 extern DLL_IMP_EXP ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count);
47 extern DLL_IMP_EXP boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence);
48
49
50 /* Forward referenced functions */
51 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value);
52 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value);
53 static bRC baculaRegisterEvents(bpContext *ctx, ...);
54 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
55   int type, time_t mtime, const char *fmt, ...);
56 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
57   int level, const char *fmt, ...);
58
59 static int     my_plugin_bopen(JCR *jcr, const char *fname, int flags, mode_t mode);
60 static int     my_plugin_bclose(JCR *jcr);
61 static ssize_t my_plugin_bread(JCR *jcr, void *buf, size_t count);
62 static ssize_t my_plugin_bwrite(JCR *jcr, void *buf, size_t count);
63 static boffset_t my_plugin_blseek(JCR *jcr, boffset_t offset, int whence);
64
65
66 /* Bacula info */
67 static bInfo binfo = {
68    sizeof(bFuncs),
69    FD_PLUGIN_INTERFACE_VERSION 
70 };
71
72 /* Bacula entry points */
73 static bFuncs bfuncs = {
74    sizeof(bFuncs),
75    FD_PLUGIN_INTERFACE_VERSION,
76    baculaRegisterEvents,
77    baculaGetValue,
78    baculaSetValue,
79    baculaJobMsg,
80    baculaDebugMsg
81 };
82
83
84 /*
85  * Create a plugin event 
86  */
87 void generate_plugin_event(JCR *jcr, bEventType eventType, void *value)     
88 {
89    bEvent event;
90    Plugin *plugin;
91    int i = 0;
92
93    if (!plugin_list) {
94       return;                         /* Return if no plugins loaded */
95    }
96
97    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
98    event.eventType = eventType;
99
100    Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId);
101
102    /* Pass event to every plugin */
103    foreach_alist(plugin, plugin_list) {
104       bRC rc;
105       rc = plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i++], &event, value);
106       if (rc != bRC_OK) {
107          break;
108       }
109    }
110
111    return;
112 }
113
114 /*   
115  * Sequence of calls for a backup:
116  * 1. plugin_save() here is called with ff_pkt
117  * 2. we find the plugin requested on the command string
118  * 3. we generate a bEventBackupCommand event to the specified plugin
119  *    and pass it the command string.
120  * 4. we make a startPluginBackup call to the plugin, which gives
121  *    us the data we need in save_pkt
122  * 5. we call Bacula's save_file() subroutine to save the specified
123  *    file.  The plugin will be called at pluginIO() to supply the
124  *    file data.
125  *
126  * Sequence of calls for restore:
127  *   See subroutine plugin_name_stream() below.
128  */
129 int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
130 {
131    Plugin *plugin;
132    int i = 0;
133    int len;
134    char *p;
135    char *cmd = ff_pkt->top_fname;
136    struct save_pkt sp;
137    bEvent event;
138
139    if (!plugin_list) {
140       return 1;                            /* Return if no plugins loaded */
141    }
142
143    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
144    event.eventType = bEventBackupCommand;
145
146    /* Handle plugin command here backup */
147    Dmsg1(100, "plugin cmd=%s\n", cmd);
148    if (!(p = strchr(cmd, ':'))) {
149       Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
150       goto bail_out;
151    }
152    len = p - cmd;
153    if (len <= 0) {
154       goto bail_out;
155    }
156
157    foreach_alist(plugin, plugin_list) {
158       Dmsg3(100, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
159       if (strncmp(plugin->file, cmd, len) != 0) {
160          i++;
161          continue;
162       }
163       Dmsg1(100, "Command plugin = %s\n", cmd);
164       /* Send the backup command */
165       if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], &event, cmd) != bRC_OK) {
166          goto bail_out;
167       }
168       /* Loop getting filenames to backup then saving them */
169       while (!job_canceled(jcr)) { 
170          memset(&sp, 0, sizeof(sp));
171          sp.type = FT_REG;
172          sp.portable = true;
173          sp.cmd = cmd;
174          Dmsg3(000, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks,
175                 &sp);
176          /* Get the file save parameters */
177          if (plug_func(plugin)->startBackupFile(&plugin_ctx_list[i], &sp) != bRC_OK) {
178             goto bail_out;
179          }
180          jcr->plugin_ctx = &plugin_ctx_list[i];
181          jcr->plugin = plugin;
182          jcr->plugin_sp = &sp;
183          ff_pkt = jcr->ff;
184          ff_pkt->fname = sp.fname;
185          ff_pkt->type = sp.type;
186          memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp));
187          Dmsg1(000, "Save_file: file=%s\n", ff_pkt->fname);
188          save_file(jcr, ff_pkt, true);
189          if (plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]) != bRC_More) {
190             goto bail_out;
191          }
192       }
193    }
194    Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd);
195
196 bail_out:
197    return 1;
198 }
199
200 /* 
201  * Send plugin name start/end record to SD
202  */
203 bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start)
204 {
205    int stat;
206    struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp;
207   
208    Dmsg1(000, "send_plugin_name=%s\n", sp->cmd);
209    if (!sd->fsend("%ld %d 0", jcr->JobFiles+1, STREAM_PLUGIN_NAME)) {
210      Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
211            sd->bstrerror());
212      return false;
213    }
214    Dmsg1(000, "send: %s\n", sd->msg);
215
216    if (start) {
217       stat = sd->fsend("%ld 1 %d %s%c", jcr->JobFiles+1, sp->portable, sp->cmd, 0);
218    } else {
219       stat = sd->fsend("%ld 0");
220    }
221    if (!stat) {
222       Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
223             sd->bstrerror());
224          return false;
225    }
226    Dmsg1(000, "send: %s\n", sd->msg);
227    sd->signal(BNET_EOD);            /* indicate end of plugin name data */
228    return true;
229 }
230
231 /*
232  * Plugin name stream found during restore.  The record passed in
233  *  argument name was generated in send_plugin_name() above.
234  */
235 void plugin_name_stream(JCR *jcr, char *name)    
236 {
237    char *p = name;
238    char *cmd;
239    bool start, portable;
240    Plugin *plugin;
241    int len;
242    int i = 0;
243    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
244
245    Dmsg1(100, "Read plugin stream string=%s\n", name);
246    skip_nonspaces(&p);             /* skip over jcr->JobFiles */
247    skip_spaces(&p);
248    start = *p == '1';
249    skip_nonspaces(&p);             /* skip start/end flag */
250    skip_spaces(&p);
251    portable = *p == '1';
252    skip_nonspaces(&p);             /* skip portable flag */
253    skip_spaces(&p);
254    cmd = p;
255     
256    /* Check for restore end */
257    if (!start) {
258       /*
259        * If end of restore, notify plugin, then clear flags   
260        */
261       plugin = (Plugin *)jcr->plugin;
262       plug_func(plugin)->endRestoreFile(&plugin_ctx_list[i]);
263       jcr->plugin_ctx = NULL;
264       jcr->plugin = NULL;
265       goto bail_out;
266    }
267       
268    /*
269     * After this point, we are dealing with a restore start
270     */
271
272    Dmsg1(100, "plugin restore cmd=%s\n", cmd);
273    if (!(p = strchr(cmd, ':'))) {
274       Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd);
275       goto bail_out;
276    }
277    len = p - cmd;
278    if (len <= 0) {
279       goto bail_out;
280    }
281
282    /*
283     * Search for correct plugin as specified on the command 
284     */
285    foreach_alist(plugin, plugin_list) {
286       bEvent event;
287       Dmsg3(100, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len);
288       if (strncmp(plugin->file, cmd, len) != 0) {
289          i++;
290          continue;
291       }
292       Dmsg1(100, "Restore Command plugin = %s\n", cmd);
293       event.eventType = bEventRestoreCommand;     
294       if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], 
295             &event, cmd) != bRC_OK) {
296          goto bail_out;
297       }
298       jcr->plugin_ctx = &plugin_ctx_list[i];
299       jcr->plugin = plugin;
300       goto bail_out;
301    }
302 bail_out:
303    return;
304 }
305
306 /*
307  * Tell the plugin to create the file.  Return values are
308  *
309  *  CF_ERROR    -- error
310  *  CF_SKIP     -- skip processing this file
311  *  CF_EXTRACT  -- extract the file (i.e.call i/o routines)
312  *  CF_CREATED  -- created, but no content to extract (typically directories)
313  *
314  */
315 int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace)
316 {
317    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
318    Plugin *plugin = (Plugin *)jcr->plugin;
319    struct restore_pkt rp;
320    struct io_pkt io;
321
322    if (!set_cmd_plugin(bfd, jcr)) {
323       return CF_ERROR;
324    }
325    rp.stream = attr->stream;
326    rp.data_stream = attr->data_stream;
327    rp.type = attr->type;
328    rp.file_index = attr->file_index;
329    rp.LinkFI = attr->LinkFI;
330    rp.uid = attr->uid;
331    rp.statp = attr->statp;                /* structure assignment */
332    rp.attrEx = attr->attrEx;
333    rp.ofname = attr->ofname;
334    rp.olname = attr->olname;
335    rp.where = jcr->where;
336    rp.RegexWhere = jcr->RegexWhere;
337    rp.replace = jcr->replace;
338    if (plug_func(plugin)->createFile(plugin_ctx, &rp) != bRC_OK) {
339       return CF_ERROR;
340    }
341    io.func = IO_OPEN;
342    io.count = 0;
343    io.buf = NULL;
344    io.mode = 0777 & attr->statp.st_mode;
345    io.flags = O_WRONLY;
346    if (plug_func(plugin)->pluginIO(plugin_ctx, &io) != bRC_OK) {
347       return CF_ERROR;
348    }
349    return CF_EXTRACT;
350 }
351
352 /*
353  * Reset the file attributes after all file I/O is done -- this allows
354  *  the previous access time/dates to be set properly, and it also allows
355  *  us to properly set directory permissions.
356  */
357 bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd)
358 {
359    return true;
360 }
361
362 void load_fd_plugins(const char *plugin_dir)
363 {
364    if (!plugin_dir) {
365       return;
366    }
367
368    plugin_list = New(alist(10, not_owned_by_alist));
369    load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type);
370
371    /* Plug entry points called from findlib */
372    plugin_bopen  = my_plugin_bopen;
373    plugin_bclose = my_plugin_bclose;
374    plugin_bread  = my_plugin_bread;
375    plugin_bwrite = my_plugin_bwrite;
376    plugin_blseek = my_plugin_blseek;
377    foreach_alist(plugin, plugin_list) {
378       Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n", plugin->file));
379    }
380
381 }
382
383 /*
384  * Create a new instance of each plugin for this Job
385  */
386 void new_plugins(JCR *jcr)
387 {
388    Plugin *plugin;
389    int i = 0;
390
391    if (!plugin_list) {
392       return;
393    }
394
395    int num = plugin_list->size();
396
397    if (num == 0) {
398       return;
399    }
400
401    jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num);
402
403    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
404    Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
405    foreach_alist(plugin, plugin_list) {
406       /* Start a new instance of each plugin */
407       plugin_ctx_list[i].bContext = (void *)jcr;
408       plugin_ctx_list[i].pContext = NULL;
409       plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]);
410    }
411 }
412
413 /*
414  * Free the plugin instances for this Job
415  */
416 void free_plugins(JCR *jcr)
417 {
418    Plugin *plugin;
419    int i = 0;
420
421    if (!plugin_list) {
422       return;
423    }
424
425    bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list;
426    Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId);
427    foreach_alist(plugin, plugin_list) {
428       /* Free the plugin instance */
429       plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]);
430    }
431    free(plugin_ctx_list);
432    jcr->plugin_ctx_list = NULL;
433 }
434
435 static int my_plugin_bopen(JCR *jcr, const char *fname, int flags, mode_t mode)
436 {
437    Plugin *plugin = (Plugin *)jcr->plugin;
438    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
439    struct io_pkt io;
440    Dmsg0(000, "plugin_bopen\n");
441    io.func = IO_OPEN;
442    io.count = 0;
443    io.buf = NULL;
444    io.mode = mode;
445    io.flags = flags;
446    plug_func(plugin)->pluginIO(plugin_ctx, &io);
447    return io.status;
448 }
449
450 static int my_plugin_bclose(JCR *jcr)
451 {
452    Plugin *plugin = (Plugin *)jcr->plugin;
453    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
454    struct io_pkt io;
455    Dmsg0(000, "plugin_bclose\n");
456    io.func = IO_CLOSE;
457    io.count = 0;
458    io.buf = NULL;
459    plug_func(plugin)->pluginIO(plugin_ctx, &io);
460    return io.status;
461 }
462
463 static ssize_t my_plugin_bread(JCR *jcr, void *buf, size_t count)
464 {
465    Plugin *plugin = (Plugin *)jcr->plugin;
466    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
467    struct io_pkt io;
468    Dmsg0(000, "plugin_bread\n");
469    io.func = IO_READ;
470    io.count = count;
471    io.buf = (char *)buf;
472    plug_func(plugin)->pluginIO(plugin_ctx, &io);
473    return (ssize_t)io.status;
474 }
475
476 static ssize_t my_plugin_bwrite(JCR *jcr, void *buf, size_t count)
477 {
478    Plugin *plugin = (Plugin *)jcr->plugin;
479    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
480    struct io_pkt io;
481    Dmsg0(000, "plugin_bwrite\n");
482    io.func = IO_WRITE;
483    io.count = count;
484    io.buf = (char *)buf;
485    plug_func(plugin)->pluginIO(plugin_ctx, &io);
486    return (ssize_t)io.status;
487 }
488
489 static boffset_t my_plugin_blseek(JCR *jcr, boffset_t offset, int whence)
490 {
491    Plugin *plugin = (Plugin *)jcr->plugin;
492    bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx;
493    struct io_pkt io;
494    Dmsg0(000, "plugin_bseek\n");
495    io.func = IO_SEEK;
496    io.offset = offset;
497    io.whence = whence;
498    plug_func(plugin)->pluginIO(plugin_ctx, &io);
499    return (boffset_t)io.offset;
500 }
501
502 /* ==============================================================
503  *
504  * Callbacks from the plugin
505  *
506  * ==============================================================
507  */
508 static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value)
509 {
510    JCR *jcr = (JCR *)(ctx->bContext);
511 // Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var);
512    if (!value) {
513       return bRC_Error;
514    }
515 // Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); 
516    switch (var) {
517    case bVarJobId:
518       *((int *)value) = jcr->JobId;
519       Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId);
520       break;
521    case bVarFDName:
522       *((char **)value) = my_name;
523       Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name);
524       break;
525    case bVarLevel:
526    case bVarType:
527    case bVarClient:
528    case bVarJobName:
529    case bVarJobStatus:
530    case bVarSinceTime:
531       break;
532    }
533    return bRC_OK;
534 }
535
536 static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value)
537 {
538    Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var);
539    return bRC_OK;
540 }
541
542 static bRC baculaRegisterEvents(bpContext *ctx, ...)
543 {
544    va_list args;
545    uint32_t event;
546
547    va_start(args, ctx);
548    while ((event = va_arg(args, uint32_t))) {
549       Dmsg1(dbglvl, "Plugin wants event=%u\n", event);
550    }
551    va_end(args);
552    return bRC_OK;
553 }
554
555 static bRC baculaJobMsg(bpContext *ctx, const char *file, int line,
556   int type, time_t mtime, const char *fmt, ...)
557 {
558    va_list arg_ptr;
559    char buf[2000];
560    JCR *jcr = (JCR *)(ctx->bContext);
561
562    va_start(arg_ptr, fmt);
563    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
564    va_end(arg_ptr);
565    Jmsg(jcr, type, mtime, "%s", buf);
566    return bRC_OK;
567 }
568
569 static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line,
570   int level, const char *fmt, ...)
571 {
572    va_list arg_ptr;
573    char buf[2000];
574
575    va_start(arg_ptr, fmt);
576    bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
577    va_end(arg_ptr);
578    d_msg(file, line, level, "%s", buf);
579    return bRC_OK;
580 }
581
582 #ifdef TEST_PROGRAM
583
584 int     (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL;
585 int     (*plugin_bclose)(JCR *jcr) = NULL;
586 ssize_t (*plugin_bread)(JCR *jcr, void *buf, size_t count) = NULL;
587 ssize_t (*plugin_bwrite)(JCR *jcr, void *buf, size_t count) = NULL;
588 boffset_t (*plugin_blseek)(JCR *jcr, boffset_t offset, int whence) = NULL;
589
590 int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level)
591 {
592    return 0;
593 }
594
595 bool set_cmd_plugin(BFILE *bfd, JCR *jcr)
596 {
597    return true;
598 }
599
600 int main(int argc, char *argv[])
601 {
602    char plugin_dir[1000];
603    JCR mjcr1, mjcr2;
604    JCR *jcr1 = &mjcr1;
605    JCR *jcr2 = &mjcr2;
606
607    strcpy(my_name, "test-fd");
608     
609    getcwd(plugin_dir, sizeof(plugin_dir)-1);
610    load_fd_plugins(plugin_dir);
611
612    jcr1->JobId = 111;
613    new_plugins(jcr1);
614
615    jcr2->JobId = 222;
616    new_plugins(jcr2);
617
618    generate_plugin_event(jcr1, bEventJobStart, (void *)"Start Job 1");
619    generate_plugin_event(jcr1, bEventJobEnd);
620    generate_plugin_event(jcr2, bEventJobStart, (void *)"Start Job 2");
621    free_plugins(jcr1);
622    generate_plugin_event(jcr2, bEventJobEnd);
623    free_plugins(jcr2);
624
625    unload_plugins();
626
627    Dmsg0(dbglvl, "bacula: OK ...\n");
628    close_memory_pool();
629    sm_dump(false);
630    return 0;
631 }
632
633 #endif /* TEST_PROGRAM */