Moving this thread back onto the list.
On Thu, Jan 21, 2010 at 08:50, Kern Sibbald <[email protected]> wrote:
> No problem. I have been working on Bacula for more than 10 years now, so a
> few more days to see what you have created is no problem :-)
There it is. Once again, this is under development and DWIM. Use, or
look at, at your own risk :)
Basically, it removes the ability of the SD to run any and all commands
on a client.
Also attached is a patch which I propose to be added ASAP. If there is
no plugin handling a command, it will mark the backup job as
erroneous instead of OK.
All code in this email is released under GPLv2+. If you need any other
licences on top let me know (no idea how hard the licence requirements
are so I thought it would be better to state it explicitly).
Richard
#include "bacula.h"
#include "fd_plugins.h"
#undef malloc
#undef free
#undef strdup
#ifdef CONFIG_FILE
#else
#define CONFIG_FILE "/usr/local/globalways/bacula/etc/client_mysql_credentials"
#endif
#ifdef RESTORE_FILE
#else
#define RESTORE_FILE "/root/mysql-restore.sql"
#endif
#define PLUGIN_TERMINATOR "mysql"
#define MAXLEN 5000
#define CMDSTRLEN 70000*sizeof(char)
#define DBPSTRLEN 62500*sizeof(char)
#define fi __FILE__
#define li __LINE__
#define DEBUG_LEVEL 150
/*Struktur Parameter Config-File*/
struct backup_parameters
{
char username[MAXLEN];
char password[MAXLEN];
char options[MAXLEN];
}
backup_parameters;
/*Trimmfunktion für Leerzeichen in Configfile*/
char * trim (char * s)
{
char *s1 = s, *s2 = &s[strlen (s) - 1];
while ((isspace (*s2)) && (s2 >= s1))
{
s2--;
}
*(s2+1) = '\0';
while ((isspace (*s1)) && (s1 < s2))
{
s1++;
}
strcpy (s, s1);
return s;
}
/*Config-File Parser*/
int parse_config (bFuncs *bfuncs,bpContext *ctx, struct backup_parameters * parms)
{
char *s, buff[256];
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: using configfile '%s'\n", CONFIG_FILE);
FILE *fp = fopen (CONFIG_FILE, "r");
if (fp == NULL)
{
return(0);
}
while ((s = fgets (buff, sizeof buff, fp)) != NULL)
{
if (buff[0] == '\n' || buff[0] == '#')
{
continue;
}
char name[MAXLEN], value[MAXLEN];
s = strtok(buff, "=");
if (s==NULL)
{
continue;
}
strncpy (name, s, MAXLEN);
s = strtok(NULL, "=");
if (s==NULL)
{
continue;
}
strncpy (value, s, MAXLEN);
trim (value);
if (strcmp(name, "username")==0)
{
strncpy (parms->username, value, MAXLEN);
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: config: username='%s'\n", value);
}
else if (strcmp(name, "password")==0)
{
strncpy (parms->password, value, MAXLEN);
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: config: password='%s'\n", value);
}
else if (strcmp(name, "options")==0)
{
strncpy (parms->options, value, MAXLEN);
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: config: options='%s'\n", value);
}
else
{
break;
}
}
fclose(fp);
return(1);
}
#define TRUE 1
#define FALSE 0
/*Entfernen von Sonderzeichen, zur Erhoehung der Sicherheit des Backup-Kommandos*/
char * removechars(bFuncs *bfuncs,bpContext *ctx, char *src,char *key)
{
char *dest;
size_t len_src;
size_t len_key;
int found;
unsigned int i;
unsigned int j;
unsigned int k;
i = 0;
j = 0;
k = 0;
len_src = 0;
len_key = 0;
dest = NULL;
len_src = strlen(src);
len_key = strlen(key);
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: removing special chars '%s' from string '%s'\n", CONFIG_FILE);
dest =(char *)malloc(sizeof(char)*len_src + 1);
if (NULL == dest)
{
return(0);
}
memset(dest, 0x00, sizeof(char)*len_src + 1);
for(i = 0; i < len_src; i++)
{
found = FALSE;
for (j = 0;j < len_key;j++)
{
if (src[i] == key[j])
{
found = TRUE;
}
}
if (FALSE == found)
{
dest[k] = src[i];
k++;
}
}
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: removing special chars '%s' from string '%s': '%s'\n", key, src, dest);
return(dest);
}
#ifdef __cplusplus
extern "C" {
#endif
// static const int dbglvl = DEBUG_LEVEL;
#define PLUGIN_LICENSE "Bacula GPLv2"
#define PLUGIN_AUTHOR "Globalways AG"
#define PLUGIN_DATE "2009-01-18"
#define PLUGIN_VERSION "1"
#define PLUGIN_DESCRIPTION "Bacula mysqldump file daemon plugin"
/*Zeiger auf Bacula-Funktionen*/
static bFuncs *bfuncs = NULL;
static bInfo *binfo = NULL;
/*Referenzierte Funktionen von Bacula*/
static bRC newPlugin(bpContext *ctx);
static bRC freePlugin(bpContext *ctx);
static bRC getPluginValue(bpContext *ctx, pVariable var, void *value);
static bRC setPluginValue(bpContext *ctx, pVariable var, void *value);
static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value);
static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp);
static bRC endBackupFile(bpContext *ctx);
static bRC pluginIO(bpContext *ctx, struct io_pkt *io);
static bRC startRestoreFile(bpContext *ctx, const char *cmd);
static bRC endRestoreFile(bpContext *ctx);
static bRC createFile(bpContext *ctx, struct restore_pkt *rp);
static bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp);
static char *apply_rp_codes(struct plugin_ctx * p_ctx);
/*Plugin Informationen*/
static pInfo pluginInfo = {
sizeof(pluginInfo),
FD_PLUGIN_INTERFACE_VERSION,
FD_PLUGIN_MAGIC,
PLUGIN_LICENSE,
PLUGIN_AUTHOR,
PLUGIN_DATE,
PLUGIN_VERSION,
PLUGIN_DESCRIPTION,
};
/*Plugin Startpunkte*/
static pFuncs pluginFuncs = {
sizeof(pluginFuncs),
FD_PLUGIN_INTERFACE_VERSION,
newPlugin,
freePlugin,
getPluginValue,
setPluginValue,
handlePluginEvent,
startBackupFile,
endBackupFile,
startRestoreFile,
endRestoreFile,
pluginIO,
createFile,
setFileAttributes
};
/*Plugin privater Kontext*/
struct plugin_ctx {
boffset_t offset;
FILE *fd; /* pipe file descriptor */
bool backup; /* set for backup (not needed) */
char *cmd; /* plugin command line */
char *fname; /* filename to "backup/restore" */
char *reader; /* reader program for backup */
char *writer; /* writer program for backup */
char where[1024];
int replace;
};
bRC loadPlugin(bInfo *lbinfo, bFuncs *lbfuncs, pInfo **pinfo, pFuncs **pfuncs)
{
bfuncs = lbfuncs; /* set Bacula funct pointers */
binfo = lbinfo;
*pinfo = &pluginInfo; /* return pointer to our info */
*pfuncs = &pluginFuncs; /* return pointer to our functions */
return bRC_OK;
}
bRC unloadPlugin()
{
return bRC_OK;
}
/*Neue Instanz des Plugins erstellen, Speicherbereich reservieren*/
static bRC newPlugin(bpContext *ctx)
{
struct plugin_ctx *p_ctx = (struct plugin_ctx *)malloc(sizeof(struct plugin_ctx));
if (!p_ctx)
{
return bRC_Error;
}
memset(p_ctx, 0, sizeof(struct plugin_ctx));
ctx->pContext = (void *)p_ctx; /* set our context pointer */
return bRC_OK;
}
/*Plugin aus Speicher entfernen, eigene Daten entfernen*/
static bRC freePlugin(bpContext *ctx)
{
struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
if (p_ctx->cmd)
{
free(p_ctx->cmd); /* free any allocated command string */
}
free(p_ctx); /* free our private context */
p_ctx = NULL;
return bRC_OK;
}
static bRC getPluginValue(bpContext *ctx, pVariable var, void *value)
{
return bRC_OK;
}
static bRC setPluginValue(bpContext *ctx, pVariable var, void *value)
{
return bRC_OK;
}
static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value)
{
struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
switch (event->eventType)
{
case bEventJobStart:
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: JobStart=%s\n", (char *)value);
break;
case bEventJobEnd:
break;
case bEventStartBackupJob:
break;
case bEventEndBackupJob:
break;
case bEventLevel:
break;
case bEventSince:
break;
case bEventStartRestoreJob:
break;
case bEventEndRestoreJob:
break;
case bEventRestoreCommand:
/*Durchfall erwuenscht*/
case bEventBackupCommand:
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: bEventBackupCommand='%s'\n", (char *)value);
char *configfile;
configfile = CONFIG_FILE;
char *restorefile;
restorefile = RESTORE_FILE;
/*Speicher für Config-Parameter reservieren, Config-File einlesen*/
struct backup_parameters configparms;
if(parse_config(bfuncs, ctx, &configparms)!=1)
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Error reading config-file: '%s'", configfile);
}
char **databases;
char *databasesparm;
char *removalchars;
char *removalchars_dbparms;
char *cmdstring;
char *p;
/*Zeichen, die entfernt werden sollen, aus Sicherheitsgruenden*/
removalchars = "&;`'\\\"|*?~<>^()[]{}$\n\r#!\t\v\f=";
removalchars_dbparms = "&;`'\\\"|?~<>^()[]{}$\n\r#!\t\v\f=";
/*Parameter setzen, aus Config-File, sowie uebergebener Parameter und Parameter pruefen*/
cmdstring = (char *)malloc(CMDSTRLEN);
databasesparm = (char *)malloc(DBPSTRLEN);
if(!cmdstring || !databasesparm)
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Could not allocate memory!\n");
return bRC_Error;
}
if(strlen(configparms.username)!=0) { // sicheren String erstellen fuer Benutzername
strncpy(configparms.username, removechars(bfuncs, ctx, configparms.username, removalchars), MAXLEN*sizeof(char));
} else {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Username not found: '%s'\n", configfile);
}
if(strlen(configparms.password)!=0) { // sicheren String erstellen fuer Passwort
strncpy(configparms.password, removechars(bfuncs, ctx, configparms.password, removalchars), MAXLEN*sizeof(char));
} else if(strcmp(configparms.password, "blank")==0) {
/*Es soll kein Passwort uebergeben werden -> nichts zu tun, da keine Zeichen entfernt werden muessen*/
} else {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Password not found: '%s'\n", configfile);
}
if(strlen(configparms.options)!=0) { // sicheren String erstellen fuer die Optionen
strncpy(configparms.options, removechars(bfuncs, ctx, configparms.options, removalchars), MAXLEN*sizeof(char));
} else {
strcpy(configparms.options, " ");
}
/* Pruefung des Aufbaus des vom Director uebergebenen Strings mit Parametern */
strncpy(cmdstring, (char *) value, CMDSTRLEN);
p = strtok(cmdstring, ":");
if (p == NULL) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Missing plugin parameters!\n", (char *) value);
return bRC_Error;
}
strcpy(cmdstring, p);
p = NULL;
if (strcmp(cmdstring, PLUGIN_TERMINATOR) != 0) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Plugin terminator not found: '%s'\n", (char *) value);
return bRC_Error;
}
p = strtok(NULL, ":");
if (p == NULL) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Database names missing!\n", (char *) value);
return bRC_Error;
}
strcpy(cmdstring, p);
p = NULL;
strcpy(cmdstring, removechars(bfuncs, ctx, cmdstring, removalchars_dbparms));
strncpy(databasesparm, cmdstring, DBPSTRLEN);
if (strlen(databasesparm) == 0) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Database names missing: '%s'\n", (char *) value);
free(cmdstring);
return bRC_Error;
}
strcpy(cmdstring, "");
/*Kommando-String aufbauen, nachdem Parameter geprueft wurden*/
strcat(
cmdstring,
"mysql:/__MYSQL__/mysql-backup.sql:/usr/bin/mysqldump --disable-keys --quote-names --single-transaction --complete-insert --extended-insert --create-options --user=\""
);
strcat(cmdstring, configparms.username);
strcat(cmdstring, "\"");
if (strcmp(configparms.password, "blank") != 0) {
strcat(cmdstring, " --password=\"");
strcat(cmdstring, configparms.password);
strcat(cmdstring, "\"");
}
strcat(cmdstring, " ");
strcat(cmdstring, configparms.options);
if (strcmp(databasesparm, "all-databases") == 0 || strcmp(databasesparm, "*") == 0) {
strcat(cmdstring, " --all-databases ");
} else {
int i;
i = 0;
databases = (char **) malloc(62500 * sizeof(char*)); //TODO: US: UNNÖTIGER MALLOC
databases[i] = (char *) malloc(250 * sizeof(char));
if (!databases || !databases[i]) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Could not allocate memory!\n");
return bRC_Error;
}
p = strtok(databasesparm, ",");
if (p == NULL) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Database names not correctly specified!\n", (char *) value);
return bRC_Error;
}
strncpy(databases[i], p, 250 * sizeof(char));
p = NULL;
i++;
for (;;) {
char *m;
m = strtok(NULL, ",");
if (m == NULL) {
break;
}
databases[i] = (char *) malloc(250 * sizeof(char));
if (!databases[i]) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Could not allocate memory!\n");
return bRC_Error;
}
strncpy(databases[i], m, 250 * sizeof(char));
i++;
}
strcpy(databasesparm, "");
while (i > 0) { // FIXED, US, mögliche endlosschleife
i--;
strcat(databasesparm, databases[i]);
strcat(databasesparm, " ");
free(databases[i]);
}
free(databases);
strcat(cmdstring, " --databases ");
strcat(cmdstring, databasesparm);
}
strcat(cmdstring, ":/bin/cat - > ");
strcat(cmdstring, restorefile);
//bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "!!!! DEBUG CMDSTRING: %s\n", cmdstring);
free(databasesparm);
/*Kommando-String parsen und Elemente in Bacula-Funktionsspeicher uebergeben*/
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: bEventBackupCommand cmdstring='%s'\n", cmdstring);
p_ctx->cmd = strdup((char *) cmdstring);
free(cmdstring);
p = strchr(p_ctx->cmd, ':');
if (!p) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Plugin terminator not found: '%s'\n", cmdstring);
return bRC_Error;
}
*p++ = 0; /* terminate plugin */
p_ctx->fname = p;
p = strchr(p, ':');
if (!p) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: File terminator not found: '%s'\n", cmdstring);
return bRC_Error;
}
*p++ = 0; /* terminate file */
p_ctx->reader = p;
p = strchr(p, ':');
if (!p) {
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Reader terminator not found: '%s'\n", cmdstring);
return bRC_Error;
}
*p++ = 0; /* terminate reader string */
p_ctx->writer = p;
break;
default:
break;
}
return bRC_OK;
}
static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp)
{
struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
time_t now = time(NULL);
sp->fname = p_ctx->fname;
sp->type = FT_REG;
sp->statp.st_mode = 0700 | S_IFREG;
sp->statp.st_ctime = now;
sp->statp.st_mtime = now;
sp->statp.st_atime = now;
sp->statp.st_size = -1;
sp->statp.st_blksize = 4096;
sp->statp.st_blocks = 1;
p_ctx->backup = true;
return bRC_OK;
}
static bRC endBackupFile(bpContext *ctx)
{
return bRC_OK;
}
static bRC pluginIO(bpContext *ctx, struct io_pkt *io)
{
struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
io->status = 0;
io->io_errno = 0;
switch(io->func)
{
case IO_OPEN:
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: IO_OPEN\n");
if (io->flags & (O_CREAT | O_WRONLY))
{
char *writer_codes = apply_rp_codes(p_ctx);
p_ctx->fd = popen(writer_codes, "w");
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: IO_OPEN fd=%d writer=%s\n", p_ctx->fd, writer_codes);
if (!p_ctx->fd)
{
io->io_errno = errno;
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Open pipe writer=%s failed: ERR=%s\n", writer_codes, strerror(errno));
if (writer_codes)
{
free(writer_codes);
}
return bRC_Error;
}
if (writer_codes)
{
free(writer_codes);
}
}
else
{
p_ctx->fd = popen(p_ctx->reader, "r");
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: IO_OPEN fd=%p reader=%s\n", p_ctx->fd, p_ctx->reader);
if (!p_ctx->fd)
{
io->io_errno = errno;
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Open pipe reader=%s failed: ERR=%s\n", p_ctx->reader, strerror(errno));
return bRC_Error;
}
}
sleep(1); /* let pipe connect */
break;
case IO_READ:
if (!p_ctx->fd)
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Logic error: NULL read FD\n");
return bRC_Error;
}
io->status = fread(io->buf, 1, io->count, p_ctx->fd);
if (io->status == 0 && ferror(p_ctx->fd))
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Pipe read error: ERR=%s\n", strerror(errno));
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: Pipe read error: ERR=%s\n", strerror(errno));
return bRC_Error;
}
break;
case IO_WRITE:
if (!p_ctx->fd)
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Logic error: NULL write FD\n");
return bRC_Error;
}
io->status = fwrite(io->buf, 1, io->count, p_ctx->fd);
if (io->status == 0 && ferror(p_ctx->fd))
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Pipe write error\n");
bfuncs->DebugMessage(ctx, fi, li, DEBUG_LEVEL, "mysql-fd: Pipe read error: ERR=%s\n", strerror(errno));
return bRC_Error;
}
break;
case IO_CLOSE:
if (!p_ctx->fd)
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: Logic error: NULL FD on bpipe close\n");
return bRC_Error;
}
io->status = pclose(p_ctx->fd);
if(io->status != 0)
{
bfuncs->JobMessage(ctx, fi, li, M_FATAL, 0, "mysql-fd: !!!!!! ->Error returned by command executed for piping. Check username, password, configuration...!!!!!!<-\n");
return bRC_Error;
}
break;
case IO_SEEK:
io->offset = p_ctx->offset;
break;
}
return bRC_OK;
}
static bRC startRestoreFile(bpContext *ctx, const char *cmd)
{
return bRC_OK;
}
static bRC endRestoreFile(bpContext *ctx)
{
return bRC_OK;
}
/*************************************************************************
* This is called during restore to create the file (if necessary)
* We must return in rp->create_status:
*
* CF_ERROR -- error
* CF_SKIP -- skip processing this file
* CF_EXTRACT -- extract the file (i.e.call i/o routines)
* CF_CREATED -- created, but no content to extract (typically directories)
*
*************************************************************************/
static bRC createFile(bpContext *ctx, struct restore_pkt *rp)
{
if (strlen(rp->where) > 512)
{
printf("mysql-fd: Restore target dir too long. Restricting to first 512 bytes.\n");
}
strncpy(((struct plugin_ctx *)ctx->pContext)->where, rp->where, 513);
((struct plugin_ctx *)ctx->pContext)->replace = rp->replace;
rp->create_status = CF_EXTRACT;
return bRC_OK;
}
static bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp)
{
return bRC_OK;
}
/*************************************************************************
* Apply codes in writer command:
* %w -> "where"
* %r -> "replace"
*
* Replace:
* 'always' => 'a', chr(97)
* 'ifnewer' => 'w', chr(119)
* 'ifolder' => 'o', chr(111)
* 'never' => 'n', chr(110)
*
* This function will allocate the required amount of memory with malloc.
* Need to be free()d manually.
* Inspired by edit_job_codes in lib/util.c
*************************************************************************/
static char *apply_rp_codes(struct plugin_ctx * p_ctx)
{
char *p, *q;
const char *str;
char add[10];
int w_count = 0, r_count = 0;
char *omsg;
char *imsg = p_ctx->writer;
if (!imsg)
{
return NULL;
}
if ((p = imsg))
{
while ((q = strstr(p, "%w")))
{
w_count++;
p=q+1;
}
p = imsg;
while ((q = strstr(p, "%r")))
{
r_count++;
p=q+1;
}
}
omsg = (char*)malloc(strlen(imsg) + (w_count * (strlen(p_ctx->where)-2)) - r_count + 1);
if (!omsg)
{
fprintf(stderr, "Out of memory.");
return NULL;
}
*omsg = 0;
for (p=imsg; *p; p++)
{
if (*p == '%')
{
switch (*++p)
{
case '%':
str = "%";
break;
case 'w':
str = p_ctx->where;
break;
case 'r':
snprintf(add, 2, "%c", p_ctx->replace);
str = add;
break;
default:
add[0] = '%';
add[1] = *p;
add[2] = 0;
str = add;
break;
}
}
else
{
add[0] = *p;
add[1] = 0;
str = add;
}
strcat(omsg, str);
}
return omsg;
}
#ifdef __cplusplus
}
#endif
Index: src/filed/fd_plugins.c
===================================================================
--- src/filed/fd_plugins.c (revision 1400)
+++ src/filed/fd_plugins.c (revision 1401)
@@ -302,7 +302,7 @@
}
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;
------------------------------------------------------------------------------
The Planet: dedicated and managed hosting, cloud storage, colocation
Stay online with enterprise data centers and the best network in the business
Choose flexible plans and management services without long-term contracts
Personal 24x7 support from experience hosting pros just a phone call away.
http://p.sf.net/sfu/theplanet-com
_______________________________________________
Bacula-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/bacula-devel