X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Ffiled%2Ffd_plugins.c;h=493368000ed23a4156ae7a071e85caf0ca85d251;hb=4d3a52a88293008a938268882e2d32af29b58094;hp=1fc546354d2071847fd90edb9d3445bb6f05a86e;hpb=22ede583f311dbb6782a69fc2b84195555010ed0;p=bacula%2Fbacula diff --git a/bacula/src/filed/fd_plugins.c b/bacula/src/filed/fd_plugins.c index 1fc546354d..493368000e 100644 --- a/bacula/src/filed/fd_plugins.c +++ b/bacula/src/filed/fd_plugins.c @@ -1,12 +1,12 @@ /* Bacula® - The Network Backup Solution - Copyright (C) 2007-2008 Free Software Foundation Europe e.V. + Copyright (C) 2007-2010 Free Software Foundation Europe e.V. The main author of Bacula is Kern Sibbald, with contributions from many others, a complete list can be found in the file AUTHORS. This program is Free Software; you can redistribute it and/or - modify it under the terms of version two of the GNU General Public + modify it under the terms of version three of the GNU Affero General Public License as published by the Free Software Foundation, which is listed in the file LICENSE. @@ -15,7 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Affero General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -25,7 +25,7 @@ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, Switzerland, email:ftf@fsfeurope.org. */ -/* +/** * Main program to test loading and running Bacula plugins. * Destined to become Bacula pluginloader, ... * @@ -34,7 +34,10 @@ #include "bacula.h" #include "filed.h" -const int dbglvl = 50; +extern CLIENT *me; +extern char *exepath; + +const int dbglvl = 150; #ifdef HAVE_WIN32 const char *plugin_type = "-fd.dll"; #else @@ -42,6 +45,7 @@ const char *plugin_type = "-fd.so"; #endif extern int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level); +extern bool check_changes(JCR *jcr, FF_PKT *ff_pkt); /* Function pointers to be set here */ extern DLL_IMP_EXP int (*plugin_bopen)(BFILE *bfd, const char *fname, int flags, mode_t mode); @@ -56,10 +60,27 @@ static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value); static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value); static bRC baculaRegisterEvents(bpContext *ctx, ...); static bRC baculaJobMsg(bpContext *ctx, const char *file, int line, - int type, time_t mtime, const char *fmt, ...); + int type, utime_t mtime, const char *fmt, ...); static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line, int level, const char *fmt, ...); +static void *baculaMalloc(bpContext *ctx, const char *file, int line, + size_t size); +static void baculaFree(bpContext *ctx, const char *file, int line, void *mem); +static bRC baculaAddExclude(bpContext *ctx, const char *file); +static bRC baculaAddInclude(bpContext *ctx, const char *file); +static bRC baculaAddOptions(bpContext *ctx, const char *opts); +static bRC baculaAddRegex(bpContext *ctx, const char *item, int type); +static bRC baculaAddWild(bpContext *ctx, const char *item, int type); +static bRC baculaNewOptions(bpContext *ctx); +static bRC baculaNewInclude(bpContext *ctx); +static bool is_plugin_compatible(Plugin *plugin); +static bool get_plugin_name(JCR *jcr, char *cmd, int *ret); +static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp); +/* + * These will be plugged into the global pointer structure for + * the findlib. + */ static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode); static int my_plugin_bclose(BFILE *bfd); static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count); @@ -69,7 +90,7 @@ static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence); /* Bacula info */ static bInfo binfo = { - sizeof(bFuncs), + sizeof(bInfo), FD_PLUGIN_INTERFACE_VERSION }; @@ -81,41 +102,172 @@ static bFuncs bfuncs = { baculaGetValue, baculaSetValue, baculaJobMsg, - baculaDebugMsg + baculaDebugMsg, + baculaMalloc, + baculaFree, + baculaAddExclude, + baculaAddInclude, + baculaAddOptions, + baculaAddRegex, + baculaAddWild, + baculaNewOptions, + baculaNewInclude, + baculaCheckChanges }; +/* + * Bacula private context + */ +struct bacula_ctx { + JCR *jcr; /* jcr for plugin */ + bRC rc; /* last return code */ + bool disabled; /* set if plugin disabled */ + findINCEXE *exclude; /* pointer to exclude files */ + findINCEXE *include; /* pointer to include/exclude files */ +}; -/* +static bool is_plugin_disabled(bpContext *plugin_ctx) +{ + bacula_ctx *b_ctx; + if (!plugin_ctx) { + return true; + } + b_ctx = (bacula_ctx *)plugin_ctx->bContext; + return b_ctx->disabled; +} + +static bool is_plugin_disabled(JCR *jcr) +{ + return is_plugin_disabled(jcr->plugin_ctx); +} + +/** * Create a plugin event + * When receiving bEventCancelCommand, this function is called by an other thread. */ void generate_plugin_event(JCR *jcr, bEventType eventType, void *value) { + bpContext *plugin_ctx; bEvent event; Plugin *plugin; int i = 0; + char *name=NULL; + int len; + bRC rc; - if (!plugin_list || !jcr->plugin_ctx_list) { + if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) { return; /* Return if no plugins loaded */ } + + /* Some events are sent to only a particular plugin */ + switch(eventType) { + case bEventPluginCommand: + name = (char *)value; + if (!get_plugin_name(jcr, name, &len)) { + return; + } + break; + default: + break; + } bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list; event.eventType = eventType; Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId); - /* Pass event to every plugin */ + /* Pass event to every plugin (except if name is set) */ foreach_alist(plugin, plugin_list) { - bRC rc; - rc = plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i++], &event, value); + if (name && strncmp(plugin->file, name, len) != 0) { + i++; + continue; + } + plugin_ctx = &plugin_ctx_list[i++]; + if (is_plugin_disabled(plugin_ctx)) { + continue; + } + rc = plug_func(plugin)->handlePluginEvent(plugin_ctx, &event, value); if (rc != bRC_OK) { break; } } - return; } -/* +/** + * Check if file was seen for accurate + */ +bool plugin_check_file(JCR *jcr, char *fname) +{ + Plugin *plugin; + int rc = bRC_OK; + int i = 0; + + if (!plugin_list || !jcr || !jcr->plugin_ctx_list || jcr->is_job_canceled()) { + return false; /* Return if no plugins loaded */ + } + + bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list; + + Dmsg2(dbglvl, "plugin_ctx=%p JobId=%d\n", jcr->plugin_ctx_list, jcr->JobId); + + /* Pass event to every plugin */ + foreach_alist(plugin, plugin_list) { + jcr->plugin_ctx = &plugin_ctx_list[i++]; + jcr->plugin = plugin; + if (is_plugin_disabled(jcr)) { + continue; + } + if (plug_func(plugin)->checkFile == NULL) { + continue; + } + rc = plug_func(plugin)->checkFile(jcr->plugin_ctx, fname); + if (rc == bRC_Seen) { + break; + } + } + + jcr->plugin = NULL; + jcr->plugin_ctx = NULL; + return rc == bRC_Seen; +} + +/* Get the first part of the the plugin command + * systemstate:/@SYSTEMSTATE/ + * => ret = 11 + * => can use strncmp(plugin_name, cmd, ret); + * + * The plugin command can contain only the plugin name + * Plugin = alldrives + * => ret = 9 + */ +static bool get_plugin_name(JCR *jcr, char *cmd, int *ret) +{ + char *p; + int len; + if (!cmd || (*cmd == '\0')) { + return false; + } + /* Handle plugin command here backup */ + Dmsg1(dbglvl, "plugin cmd=%s\n", cmd); + if ((p = strchr(cmd, ':')) == NULL) { + if (strchr(cmd, ' ') == NULL) { /* we have just the plugin name */ + len = strlen(cmd); + } else { + Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd); + return false; + } + } else { /* plugin:argument */ + len = p - cmd; + if (len <= 0) { + return false; + } + } + *ret = len; + return true; +} + +/** * Sequence of calls for a backup: * 1. plugin_save() here is called with ff_pkt * 2. we find the plugin requested on the command string @@ -135,12 +287,14 @@ int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level) Plugin *plugin; int i = 0; int len; - char *p; char *cmd = ff_pkt->top_fname; struct save_pkt sp; bEvent event; + POOL_MEM fname(PM_FNAME); + POOL_MEM link(PM_FNAME); - if (!plugin_list || !jcr->plugin_ctx_list) { + if (!plugin_list || !jcr->plugin_ctx_list || jcr->is_job_canceled()) { + Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" requested, but is not loaded.\n", cmd); return 1; /* Return if no plugins loaded */ } @@ -148,30 +302,35 @@ int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level) bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list; event.eventType = bEventBackupCommand; - /* Handle plugin command here backup */ - Dmsg1(dbglvl, "plugin cmd=%s\n", cmd); - if (!(p = strchr(cmd, ':'))) { - Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd); - goto bail_out; - } - len = p - cmd; - if (len <= 0) { + if (!get_plugin_name(jcr, cmd, &len)) { goto bail_out; } + /* Note, we stop the loop on the first plugin that matches the name */ foreach_alist(plugin, plugin_list) { Dmsg3(dbglvl, "plugin=%s cmd=%s len=%d\n", plugin->file, cmd, len); if (strncmp(plugin->file, cmd, len) != 0) { i++; continue; } + /* + * We put the current plugin pointer, and the plugin context + * into the jcr, because during save_file(), the plugin + * will be called many times and these values are needed. + */ + jcr->plugin_ctx = &plugin_ctx_list[i]; + jcr->plugin = plugin; + if (is_plugin_disabled(jcr)) { + goto bail_out; + } + Dmsg1(dbglvl, "Command plugin = %s\n", cmd); /* Send the backup command to the right plugin*/ - if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], &event, cmd) != bRC_OK) { + if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, cmd) != bRC_OK) { goto bail_out; } /* Loop getting filenames to backup then saving them */ - while (!job_canceled(jcr)) { + while (!jcr->is_job_canceled()) { memset(&sp, 0, sizeof(sp)); sp.pkt_size = sizeof(sp); sp.pkt_end = sizeof(sp); @@ -179,58 +338,105 @@ int plugin_save(JCR *jcr, FF_PKT *ff_pkt, bool top_level) sp.cmd = cmd; Dmsg3(dbglvl, "startBackup st_size=%p st_blocks=%p sp=%p\n", &sp.statp.st_size, &sp.statp.st_blocks, &sp); - /* Get the file save parameters */ - if (plug_func(plugin)->startBackupFile(&plugin_ctx_list[i], &sp) != bRC_OK) { + /* Get the file save parameters. I.e. the stat pkt ... */ + if (plug_func(plugin)->startBackupFile(jcr->plugin_ctx, &sp) != bRC_OK) { goto bail_out; } - if (sp.type == 0 || sp.fname == NULL) { - Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\" returned bad startBackupFile packet.\n"), + if (sp.type == 0) { + Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no type in startBackupFile packet.\n"), cmd); goto bail_out; } - jcr->plugin_ctx = &plugin_ctx_list[i]; - jcr->plugin = plugin; jcr->plugin_sp = &sp; ff_pkt = jcr->ff; - ff_pkt->fname = sp.fname; + /* + * Copy fname and link because save_file() zaps them. This + * avoids zaping the plugin's strings. + */ ff_pkt->type = sp.type; + if (sp.type == FT_RESTORE_FIRST) { + if (!sp.object_name) { + Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no object_name in startBackupFile packet.\n"), + cmd); + goto bail_out; + } + ff_pkt->fname = cmd; /* full plugin string */ + ff_pkt->object_name = sp.object_name; + ff_pkt->object_index = sp.index; /* restore object index */ + ff_pkt->object_compression = 0; /* no compression for now */ + ff_pkt->object = sp.object; + ff_pkt->object_len = sp.object_len; + } else { + if (!sp.fname) { + Jmsg1(jcr, M_FATAL, 0, _("Command plugin \"%s\": no fname in startBackupFile packet.\n"), + cmd); + goto bail_out; + } + pm_strcpy(fname, sp.fname); + pm_strcpy(link, sp.link); + ff_pkt->fname = fname.c_str(); + ff_pkt->link = link.c_str(); + } + memcpy(&ff_pkt->statp, &sp.statp, sizeof(ff_pkt->statp)); - Dmsg1(dbglvl, "Save_file: file=%s\n", ff_pkt->fname); + Dmsg2(dbglvl, "startBackup returned type=%d, fname=%s\n", sp.type, sp.fname); + if (sp.object) { + Dmsg2(dbglvl, "index=%d object=%s\n", sp.index, sp.object); + } + /* Call Bacula core code to backup the plugin's file */ save_file(jcr, ff_pkt, true); - bRC rc = plug_func(plugin)->endBackupFile(&plugin_ctx_list[i]); + bRC rc = plug_func(plugin)->endBackupFile(jcr->plugin_ctx); + if (rc == bRC_More || rc == bRC_OK) { + accurate_mark_file_as_seen(jcr, fname.c_str()); + } if (rc == bRC_More) { continue; } goto bail_out; } + goto bail_out; } - Jmsg1(jcr, M_ERROR, 0, "Command plugin \"%s\" not found.\n", cmd); + Jmsg1(jcr, M_FATAL, 0, "Command plugin \"%s\" not found.\n", cmd); bail_out: jcr->cmd_plugin = false; + jcr->plugin = NULL; + jcr->plugin_ctx = NULL; return 1; } -/* +/** * Send plugin name start/end record to SD */ bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start) { int stat; + int index = jcr->JobFiles; struct save_pkt *sp = (struct save_pkt *)jcr->plugin_sp; + + if (!sp) { + Jmsg0(jcr, M_FATAL, 0, _("Plugin save packet not found.\n")); + return false; + } + if (jcr->is_job_canceled()) { + return false; + } + if (start) { + index++; /* JobFiles not incremented yet */ + } Dmsg1(dbglvl, "send_plugin_name=%s\n", sp->cmd); /* Send stream header */ - if (!sd->fsend("%ld %d 0", jcr->JobFiles+1, STREAM_PLUGIN_NAME)) { + if (!sd->fsend("%ld %d 0", index, STREAM_PLUGIN_NAME)) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), sd->bstrerror()); return false; } - Dmsg1(000, "send: %s\n", sd->msg); + Dmsg1(50, "send plugin name hdr: %s\n", sd->msg); if (start) { /* Send data -- not much */ - stat = sd->fsend("%ld 1 %d %s%c", jcr->JobFiles+1, sp->portable, sp->cmd, 0); + stat = sd->fsend("%ld 1 %d %s%c", index, sp->portable, sp->cmd, 0); } else { /* Send end of data */ stat = sd->fsend("0 0"); @@ -240,16 +446,19 @@ bool send_plugin_name(JCR *jcr, BSOCK *sd, bool start) sd->bstrerror()); return false; } - Dmsg1(dbglvl, "send: %s\n", sd->msg); + Dmsg1(dbglvl, "send plugin start/end: %s\n", sd->msg); sd->signal(BNET_EOD); /* indicate end of plugin name data */ return true; } -/* +/** * Plugin name stream found during restore. The record passed in * argument name was generated in send_plugin_name() above. + * + * Returns: true if start of stream + * false if end of steam */ -void plugin_name_stream(JCR *jcr, char *name) +bool plugin_name_stream(JCR *jcr, char *name) { char *p = name; char *cmd; @@ -257,10 +466,7 @@ void plugin_name_stream(JCR *jcr, char *name) Plugin *plugin; int len; int i = 0; - bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list; - if (!plugin_ctx_list) { - goto bail_out; - } + bpContext *plugin_ctx_list = jcr->plugin_ctx_list; Dmsg1(dbglvl, "Read plugin stream string=%s\n", name); skip_nonspaces(&p); /* skip over jcr->JobFiles */ @@ -278,26 +484,22 @@ void plugin_name_stream(JCR *jcr, char *name) /* * End of plugin data, notify plugin, then clear flags */ + Dmsg2(dbglvl, "End plugin data plugin=%p ctx=%p\n", jcr->plugin, jcr->plugin_ctx); if (jcr->plugin) { - plugin = (Plugin *)jcr->plugin; - plug_func(plugin)->endRestoreFile((bpContext *)jcr->plugin_ctx); + plug_func(jcr->plugin)->endRestoreFile(jcr->plugin_ctx); } jcr->plugin_ctx = NULL; jcr->plugin = NULL; goto bail_out; } + if (!plugin_ctx_list) { + goto bail_out; + } /* * After this point, we are dealing with a restore start */ - - Dmsg1(dbglvl, "plugin restore cmd=%s\n", cmd); - if (!(p = strchr(cmd, ':'))) { - Jmsg1(jcr, M_ERROR, 0, "Malformed plugin command: %s\n", cmd); - goto bail_out; - } - len = p - cmd; - if (len <= 0) { + if (!get_plugin_name(jcr, cmd, &len)) { goto bail_out; } @@ -311,22 +513,30 @@ void plugin_name_stream(JCR *jcr, char *name) i++; continue; } + jcr->plugin_ctx = &plugin_ctx_list[i]; + jcr->plugin = plugin; + if (is_plugin_disabled(jcr)) { + goto bail_out; + } Dmsg1(dbglvl, "Restore Command plugin = %s\n", cmd); event.eventType = bEventRestoreCommand; - if (plug_func(plugin)->handlePluginEvent(&plugin_ctx_list[i], + if (plug_func(plugin)->handlePluginEvent(jcr->plugin_ctx, &event, cmd) != bRC_OK) { goto bail_out; } - jcr->plugin_ctx = &plugin_ctx_list[i]; - jcr->plugin = plugin; + /* ***FIXME**** check error code */ + plug_func(plugin)->startRestoreFile((bpContext *)jcr->plugin_ctx, cmd); goto bail_out; } + Jmsg1(jcr, M_WARNING, 0, _("Plugin=%s not found.\n"), cmd); + bail_out: - return; + return start; } -/* +/** * Tell the plugin to create the file. Return values are + * This is called only during Restore * * CF_ERROR -- error * CF_SKIP -- skip processing this file @@ -336,14 +546,16 @@ bail_out: */ int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace) { - bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx; - Plugin *plugin = (Plugin *)jcr->plugin; + bpContext *plugin_ctx = jcr->plugin_ctx; + Plugin *plugin = jcr->plugin; struct restore_pkt rp; - struct io_pkt io; + int flags; + int rc; - if (!set_cmd_plugin(bfd, jcr)) { + if (!plugin || !plugin_ctx || !set_cmd_plugin(bfd, jcr) || jcr->is_job_canceled()) { return CF_ERROR; } + rp.pkt_size = sizeof(rp); rp.pkt_end = sizeof(rp); rp.stream = attr->stream; @@ -359,33 +571,85 @@ int plugin_create_file(JCR *jcr, ATTR *attr, BFILE *bfd, int replace) rp.where = jcr->where; rp.RegexWhere = jcr->RegexWhere; rp.replace = jcr->replace; - if (plug_func(plugin)->createFile(plugin_ctx, &rp) != bRC_OK) { + rp.create_status = CF_ERROR; + Dmsg4(dbglvl, "call plugin createFile stream=%d type=%d LinkFI=%d File=%s\n", + rp.stream, rp.type, rp.LinkFI, rp.ofname); + if (rp.attrEx) { + Dmsg1(dbglvl, "attrEx=\"%s\"\n", rp.attrEx); + } + rc = plug_func(plugin)->createFile(plugin_ctx, &rp); + if (rc != bRC_OK) { + Qmsg2(jcr, M_ERROR, 0, _("Plugin createFile call failed. Stat=%d file=%s\n"), + rc, attr->ofname); return CF_ERROR; } - io.pkt_size = sizeof(io); - io.pkt_end = sizeof(io); - io.func = IO_OPEN; - io.count = 0; - io.buf = NULL; - io.mode = 0777 & attr->statp.st_mode; - io.flags = O_WRONLY; - if (plug_func(plugin)->pluginIO(plugin_ctx, &io) != bRC_OK) { + if (rp.create_status == CF_SKIP) { + return CF_SKIP; + } + if (rp.create_status == CF_ERROR) { + Qmsg1(jcr, M_ERROR, 0, _("Plugin createFile call failed. Returned CF_ERROR file=%s\n"), + attr->ofname); return CF_ERROR; } + /* Created link or directory? */ + if (rp.create_status == CF_CREATED) { + return rp.create_status; /* yes, no need to bopen */ + } + + flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; + Dmsg0(dbglvl, "call bopen\n"); + int stat = bopen(bfd, attr->ofname, flags, S_IRUSR | S_IWUSR); + Dmsg1(50, "bopen status=%d\n", stat); + if (stat < 0) { + berrno be; + be.set_errno(bfd->berrno); + Qmsg2(jcr, M_ERROR, 0, _("Could not create %s: ERR=%s\n"), + attr->ofname, be.bstrerror()); + Dmsg2(dbglvl,"Could not bopen file %s: ERR=%s\n", attr->ofname, be.bstrerror()); + return CF_ERROR; + } + + if (!is_bopen(bfd)) { + Dmsg0(000, "===== BFD is not open!!!!\n"); + } return CF_EXTRACT; } -/* +/** * Reset the file attributes after all file I/O is done -- this allows * the previous access time/dates to be set properly, and it also allows * us to properly set directory permissions. + * Not currently Implemented. */ bool plugin_set_attributes(JCR *jcr, ATTR *attr, BFILE *ofd) { + Dmsg0(dbglvl, "plugin_set_attributes\n"); + if (is_bopen(ofd)) { + bclose(ofd); + } + pm_strcpy(attr->ofname, "*none*"); return true; } /* + * Print to file the plugin info. + */ +void dump_fd_plugin(Plugin *plugin, FILE *fp) +{ + if (!plugin) { + return ; + } + pInfo *info = (pInfo *)plugin->pinfo; + fprintf(fp, "\tversion=%d\n", info->version); + fprintf(fp, "\tdate=%s\n", NPRTB(info->plugin_date)); + fprintf(fp, "\tmagic=%s\n", NPRTB(info->plugin_magic)); + fprintf(fp, "\tauthor=%s\n", NPRTB(info->plugin_author)); + fprintf(fp, "\tlicence=%s\n", NPRTB(info->plugin_license)); + fprintf(fp, "\tversion=%s\n", NPRTB(info->plugin_version)); + fprintf(fp, "\tdescription=%s\n", NPRTB(info->plugin_description)); +} + +/** * This entry point is called internally by Bacula to ensure * that the plugin IO calls come into this code. */ @@ -399,7 +663,8 @@ void load_fd_plugins(const char *plugin_dir) } plugin_list = New(alist(10, not_owned_by_alist)); - if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type)) { + if (!load_plugins((void *)&binfo, (void *)&bfuncs, plugin_dir, plugin_type, + is_plugin_compatible)) { /* Either none found, or some error */ if (plugin_list->size() == 0) { delete plugin_list; @@ -415,14 +680,59 @@ void load_fd_plugins(const char *plugin_dir) plugin_bread = my_plugin_bread; plugin_bwrite = my_plugin_bwrite; plugin_blseek = my_plugin_blseek; + + /* + * Verify that the plugin is acceptable, and print information + * about it. + */ foreach_alist(plugin, plugin_list) { Jmsg(NULL, M_INFO, 0, _("Loaded plugin: %s\n"), plugin->file); Dmsg1(dbglvl, "Loaded plugin: %s\n", plugin->file); + } + + dbg_plugin_add_hook(dump_fd_plugin); +} + +/** + * Check if a plugin is compatible. Called by the load_plugin function + * to allow us to verify the plugin. + */ +static bool is_plugin_compatible(Plugin *plugin) +{ + pInfo *info = (pInfo *)plugin->pinfo; + Dmsg0(50, "is_plugin_compatible called\n"); + if (debug_level >= 50) { + dump_fd_plugin(plugin, stdin); + } + if (strcmp(info->plugin_magic, FD_PLUGIN_MAGIC) != 0) { + Jmsg(NULL, M_ERROR, 0, _("Plugin magic wrong. Plugin=%s wanted=%s got=%s\n"), + plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic); + Dmsg3(50, "Plugin magic wrong. Plugin=%s wanted=%s got=%s\n", + plugin->file, FD_PLUGIN_MAGIC, info->plugin_magic); + return false; + } + if (info->version != FD_PLUGIN_INTERFACE_VERSION) { + Jmsg(NULL, M_ERROR, 0, _("Plugin version incorrect. Plugin=%s wanted=%d got=%d\n"), + plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version); + Dmsg3(50, "Plugin version incorrect. Plugin=%s wanted=%d got=%d\n", + plugin->file, FD_PLUGIN_INTERFACE_VERSION, info->version); + return false; } + if (strcmp(info->plugin_license, "Bacula AGPLv3") != 0 && + strcmp(info->plugin_license, "AGPLv3") != 0) { + Jmsg(NULL, M_ERROR, 0, _("Plugin license incompatible. Plugin=%s license=%s\n"), + plugin->file, info->plugin_license); + Dmsg2(50, "Plugin license incompatible. Plugin=%s license=%s\n", + plugin->file, info->plugin_license); + return false; + } + + return true; } -/* + +/** * Create a new instance of each plugin for this Job * Note, plugin_list can exist but jcr->plugin_ctx_list can * be NULL if no plugins were loaded. @@ -436,6 +746,9 @@ void new_plugins(JCR *jcr) Dmsg0(dbglvl, "plugin list is NULL\n"); return; } + if (jcr->is_job_canceled() || jcr->JobId == 0) { + return; + } int num = plugin_list->size(); @@ -444,19 +757,24 @@ void new_plugins(JCR *jcr) return; } - jcr->plugin_ctx_list = (void *)malloc(sizeof(bpContext) * num); + jcr->plugin_ctx_list = (bpContext *)malloc(sizeof(bpContext) * num); bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list; Dmsg2(dbglvl, "Instantiate plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId); foreach_alist(plugin, plugin_list) { /* Start a new instance of each plugin */ - plugin_ctx_list[i].bContext = (void *)jcr; + bacula_ctx *b_ctx = (bacula_ctx *)malloc(sizeof(bacula_ctx)); + memset(b_ctx, 0, sizeof(bacula_ctx)); + b_ctx->jcr = jcr; + plugin_ctx_list[i].bContext = (void *)b_ctx; /* Bacula private context */ plugin_ctx_list[i].pContext = NULL; - plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]); + if (plug_func(plugin)->newPlugin(&plugin_ctx_list[i++]) != bRC_OK) { + b_ctx->disabled = true; + } } } -/* +/** * Free the plugin instances for this Job */ void free_plugins(JCR *jcr) @@ -470,9 +788,10 @@ void free_plugins(JCR *jcr) bpContext *plugin_ctx_list = (bpContext *)jcr->plugin_ctx_list; Dmsg2(dbglvl, "Free instance plugin_ctx=%p JobId=%d\n", plugin_ctx_list, jcr->JobId); - foreach_alist(plugin, plugin_list) { + foreach_alist(plugin, plugin_list) { /* Free the plugin instance */ - plug_func(plugin)->freePlugin(&plugin_ctx_list[i++]); + plug_func(plugin)->freePlugin(&plugin_ctx_list[i]); + free(plugin_ctx_list[i++].bContext); /* free Bacula private context */ } free(plugin_ctx_list); jcr->plugin_ctx_list = NULL; @@ -482,9 +801,12 @@ static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode { JCR *jcr = bfd->jcr; Plugin *plugin = (Plugin *)jcr->plugin; - bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx; struct io_pkt io; - Dmsg0(dbglvl, "plugin_bopen\n"); + + Dmsg1(dbglvl, "plugin_bopen flags=%x\n", flags); + if (!plugin || !jcr->plugin_ctx) { + return 0; + } io.pkt_size = sizeof(io); io.pkt_end = sizeof(io); io.func = IO_OPEN; @@ -495,7 +817,7 @@ static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode io.mode = mode; io.win32 = false; io.lerror = 0; - plug_func(plugin)->pluginIO(plugin_ctx, &io); + plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io); bfd->berrno = io.io_errno; if (io.win32) { errno = b_errno_win32; @@ -503,6 +825,7 @@ static int my_plugin_bopen(BFILE *bfd, const char *fname, int flags, mode_t mode errno = io.io_errno; bfd->lerror = io.lerror; } + Dmsg1(50, "Return from plugin open status=%d\n", io.status); return io.status; } @@ -510,9 +833,12 @@ static int my_plugin_bclose(BFILE *bfd) { JCR *jcr = bfd->jcr; Plugin *plugin = (Plugin *)jcr->plugin; - bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx; struct io_pkt io; - Dmsg0(dbglvl, "plugin_bclose\n"); + + Dmsg0(dbglvl, "===== plugin_bclose\n"); + if (!plugin || !jcr->plugin_ctx) { + return 0; + } io.pkt_size = sizeof(io); io.pkt_end = sizeof(io); io.func = IO_CLOSE; @@ -520,7 +846,7 @@ static int my_plugin_bclose(BFILE *bfd) io.buf = NULL; io.win32 = false; io.lerror = 0; - plug_func(plugin)->pluginIO(plugin_ctx, &io); + plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io); bfd->berrno = io.io_errno; if (io.win32) { errno = b_errno_win32; @@ -528,6 +854,7 @@ static int my_plugin_bclose(BFILE *bfd) errno = io.io_errno; bfd->lerror = io.lerror; } + Dmsg1(dbglvl, "plugin_bclose stat=%d\n", io.status); return io.status; } @@ -535,9 +862,12 @@ static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count) { JCR *jcr = bfd->jcr; Plugin *plugin = (Plugin *)jcr->plugin; - bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx; struct io_pkt io; + Dmsg0(dbglvl, "plugin_bread\n"); + if (!plugin || !jcr->plugin_ctx) { + return 0; + } io.pkt_size = sizeof(io); io.pkt_end = sizeof(io); io.func = IO_READ; @@ -545,7 +875,7 @@ static ssize_t my_plugin_bread(BFILE *bfd, void *buf, size_t count) io.buf = (char *)buf; io.win32 = false; io.lerror = 0; - plug_func(plugin)->pluginIO(plugin_ctx, &io); + plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io); bfd->berrno = io.io_errno; if (io.win32) { errno = b_errno_win32; @@ -560,9 +890,12 @@ static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count) { JCR *jcr = bfd->jcr; Plugin *plugin = (Plugin *)jcr->plugin; - bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx; struct io_pkt io; + Dmsg0(dbglvl, "plugin_bwrite\n"); + if (!plugin || !jcr->plugin_ctx) { + return 0; + } io.pkt_size = sizeof(io); io.pkt_end = sizeof(io); io.func = IO_WRITE; @@ -570,7 +903,7 @@ static ssize_t my_plugin_bwrite(BFILE *bfd, void *buf, size_t count) io.buf = (char *)buf; io.win32 = false; io.lerror = 0; - plug_func(plugin)->pluginIO(plugin_ctx, &io); + plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io); bfd->berrno = io.io_errno; if (io.win32) { errno = b_errno_win32; @@ -585,9 +918,12 @@ static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence) { JCR *jcr = bfd->jcr; Plugin *plugin = (Plugin *)jcr->plugin; - bpContext *plugin_ctx = (bpContext *)jcr->plugin_ctx; struct io_pkt io; + Dmsg0(dbglvl, "plugin_bseek\n"); + if (!plugin || !jcr->plugin_ctx) { + return 0; + } io.pkt_size = sizeof(io); io.pkt_end = sizeof(io); io.func = IO_SEEK; @@ -595,7 +931,7 @@ static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence) io.whence = whence; io.win32 = false; io.lerror = 0; - plug_func(plugin)->pluginIO(plugin_ctx, &io); + plug_func(plugin)->pluginIO(jcr->plugin_ctx, &io); bfd->berrno = io.io_errno; if (io.win32) { errno = b_errno_win32; @@ -614,27 +950,95 @@ static boffset_t my_plugin_blseek(BFILE *bfd, boffset_t offset, int whence) */ static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value) { - JCR *jcr = (JCR *)(ctx->bContext); -// Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var); + JCR *jcr; if (!value) { return bRC_Error; } -// Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); + + switch (var) { /* General variables, no need of ctx */ + case bVarFDName: + *((char **)value) = my_name; + break; + case bVarWorkingDir: + *(void **)value = me->working_directory; + break; + case bVarExePath: + *(char **)value = exepath; + break; + default: + break; + } + + if (!ctx) { /* Other variables need context */ + return bRC_Error; + } + + jcr = ((bacula_ctx *)ctx->bContext)->jcr; + if (!jcr) { + return bRC_Error; + } + switch (var) { case bVarJobId: *((int *)value) = jcr->JobId; Dmsg1(dbglvl, "Bacula: return bVarJobId=%d\n", jcr->JobId); break; - case bVarFDName: - *((char **)value) = my_name; - Dmsg1(dbglvl, "Bacula: return my_name=%s\n", my_name); - break; case bVarLevel: + *((int *)value) = jcr->getJobLevel(); + Dmsg1(dbglvl, "Bacula: return bVarJobLevel=%d\n", jcr->getJobLevel()); + break; case bVarType: + *((int *)value) = jcr->getJobType(); + Dmsg1(dbglvl, "Bacula: return bVarJobType=%d\n", jcr->getJobType()); + break; case bVarClient: + *((char **)value) = jcr->client_name; + Dmsg1(dbglvl, "Bacula: return Client_name=%s\n", jcr->client_name); + break; case bVarJobName: + *((char **)value) = jcr->Job; + Dmsg1(dbglvl, "Bacula: return Job name=%s\n", jcr->Job); + break; case bVarJobStatus: + *((int *)value) = jcr->JobStatus; + Dmsg1(dbglvl, "Bacula: return bVarJobStatus=%d\n", jcr->JobStatus); + break; case bVarSinceTime: + *((int *)value) = (int)jcr->mtime; + Dmsg1(dbglvl, "Bacula: return since=%d\n", (int)jcr->mtime); + break; + case bVarAccurate: + *((int *)value) = (int)jcr->accurate; + Dmsg1(dbglvl, "Bacula: return accurate=%d\n", (int)jcr->accurate); + break; + case bVarFileSeen: + break; /* a write only variable, ignore read request */ + case bVarVssObject: +#ifdef HAVE_WIN32 + if (g_pVSSClient) { + *(void **)value = g_pVSSClient->GetVssObject(); + break; + } +#endif + return bRC_Error; + case bVarVssDllHandle: +#ifdef HAVE_WIN32 + if (g_pVSSClient) { + *(void **)value = g_pVSSClient->GetVssDllHandle(); + break; + } +#endif + return bRC_Error; + case bVarWhere: + *(char **)value = jcr->where; + break; + case bVarRegexWhere: + *(char **)value = jcr->RegexWhere; + break; + + case bVarFDName: /* get warning with g++ if we missed one */ + case bVarWorkingDir: + case bVarExePath: break; } return bRC_OK; @@ -642,7 +1046,25 @@ static bRC baculaGetValue(bpContext *ctx, bVariable var, void *value) static bRC baculaSetValue(bpContext *ctx, bVariable var, void *value) { - Dmsg1(dbglvl, "bacula: baculaSetValue var=%d\n", var); + JCR *jcr; + if (!value || !ctx) { + return bRC_Error; + } +// Dmsg1(dbglvl, "bacula: baculaGetValue var=%d\n", var); + jcr = ((bacula_ctx *)ctx->bContext)->jcr; + if (!jcr) { + return bRC_Error; + } +// Dmsg1(dbglvl, "Bacula: jcr=%p\n", jcr); + switch (var) { + case bVarFileSeen: + if (!accurate_mark_file_as_seen(jcr, (char *)value)) { + return bRC_Error; + } + break; + default: + break; + } return bRC_OK; } @@ -651,6 +1073,10 @@ static bRC baculaRegisterEvents(bpContext *ctx, ...) va_list args; uint32_t event; + if (!ctx) { + return bRC_Error; + } + va_start(args, ctx); while ((event = va_arg(args, uint32_t))) { Dmsg1(dbglvl, "Plugin wants event=%u\n", event); @@ -660,11 +1086,17 @@ static bRC baculaRegisterEvents(bpContext *ctx, ...) } static bRC baculaJobMsg(bpContext *ctx, const char *file, int line, - int type, time_t mtime, const char *fmt, ...) + int type, utime_t mtime, const char *fmt, ...) { va_list arg_ptr; char buf[2000]; - JCR *jcr = (JCR *)(ctx->bContext); + JCR *jcr; + + if (ctx) { + jcr = ((bacula_ctx *)ctx->bContext)->jcr; + } else { + jcr = NULL; + } va_start(arg_ptr, fmt); bvsnprintf(buf, sizeof(buf), fmt, arg_ptr); @@ -686,6 +1118,201 @@ static bRC baculaDebugMsg(bpContext *ctx, const char *file, int line, return bRC_OK; } +static void *baculaMalloc(bpContext *ctx, const char *file, int line, + size_t size) +{ +#ifdef SMARTALLOC + return sm_malloc(file, line, size); +#else + return malloc(size); +#endif +} + +static void baculaFree(bpContext *ctx, const char *file, int line, void *mem) +{ +#ifdef SMARTALLOC + sm_free(file, line, mem); +#else + free(mem); +#endif +} + +static bool is_ctx_good(bpContext *ctx, JCR *&jcr, bacula_ctx *&bctx) +{ + if (!ctx) { + return false; + } + bctx = (bacula_ctx *)ctx->bContext; + if (!bctx) { + return false; + } + jcr = bctx->jcr; + if (!jcr) { + return false; + } + return true; +} + +/** + * Let the plugin define files/directories to be excluded + * from the main backup. + */ +static bRC baculaAddExclude(bpContext *ctx, const char *file) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + if (!file) { + return bRC_Error; + } + if (!bctx->exclude) { + bctx->exclude = new_exclude(jcr); + new_options(jcr, bctx->exclude); + } + set_incexe(jcr, bctx->exclude); + add_file_to_fileset(jcr, file, true); + Dmsg1(100, "Add exclude file=%s\n", file); + return bRC_OK; +} + +/** + * Let the plugin define files/directories to be excluded + * from the main backup. + */ +static bRC baculaAddInclude(bpContext *ctx, const char *file) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + if (!file) { + return bRC_Error; + } + if (!bctx->include) { + bctx->include = new_preinclude(jcr); + new_options(jcr, bctx->include); + } + set_incexe(jcr, bctx->include); + add_file_to_fileset(jcr, file, true); + Dmsg1(100, "Add include file=%s\n", file); + return bRC_OK; +} + +static bRC baculaAddOptions(bpContext *ctx, const char *opts) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + if (!opts) { + return bRC_Error; + } + add_options_to_fileset(jcr, opts); + Dmsg1(1000, "Add options=%s\n", opts); + return bRC_OK; +} + +static bRC baculaAddRegex(bpContext *ctx, const char *item, int type) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + if (!item) { + return bRC_Error; + } + add_regex_to_fileset(jcr, item, type); + Dmsg1(100, "Add regex=%s\n", item); + return bRC_OK; +} + +static bRC baculaAddWild(bpContext *ctx, const char *item, int type) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + if (!item) { + return bRC_Error; + } + add_wild_to_fileset(jcr, item, type); + Dmsg1(100, "Add wild=%s\n", item); + return bRC_OK; +} + +static bRC baculaNewOptions(bpContext *ctx) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + (void)new_options(jcr, NULL); + return bRC_OK; +} + +static bRC baculaNewInclude(bpContext *ctx) +{ + JCR *jcr; + bacula_ctx *bctx; + if (!is_ctx_good(ctx, jcr, bctx)) { + return bRC_Error; + } + (void)new_include(jcr); + return bRC_OK; +} + + +/* + * Check if a file have to be backuped using Accurate code + */ +static bRC baculaCheckChanges(bpContext *ctx, struct save_pkt *sp) +{ + JCR *jcr; + bacula_ctx *bctx; + FF_PKT *ff_pkt; + bRC ret = bRC_Error; + + if (!is_ctx_good(ctx, jcr, bctx)) { + goto bail_out; + } + if (!sp) { + goto bail_out; + } + + ff_pkt = jcr->ff; + /* + * Copy fname and link because save_file() zaps them. This + * avoids zaping the plugin's strings. + */ + ff_pkt->type = sp->type; + if (!sp->fname) { + Jmsg0(jcr, M_FATAL, 0, _("Command plugin: no fname in baculaCheckChanges packet.\n")); + goto bail_out; + } + + ff_pkt->fname = sp->fname; + ff_pkt->link = sp->link; + memcpy(&ff_pkt->statp, &sp->statp, sizeof(ff_pkt->statp)); + + if (check_changes(jcr, ff_pkt)) { + ret = bRC_OK; + } else { + ret = bRC_Seen; + } + +bail_out: + Dmsg1(100, "checkChanges=%i\n", ret); + return ret; +} + + #ifdef TEST_PROGRAM int (*plugin_bopen)(JCR *jcr, const char *fname, int flags, mode_t mode) = NULL; @@ -733,7 +1360,7 @@ int main(int argc, char *argv[]) Dmsg0(dbglvl, "bacula: OK ...\n"); close_memory_pool(); - sm_dump(false); + sm_dump(false); /* unit test */ return 0; }