From: Kern Sibbald Date: Tue, 19 Apr 2005 16:49:55 +0000 (+0000) Subject: - Fix new Python code to work for Director. X-Git-Tag: Release-1.38.0~554 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=ec5a4ec2639dc12fb79faad0d76513a9b1e4e87a;p=bacula%2Fbacula - Fix new Python code to work for Director. - Move lib/python.c to lib/pythonlib.c so that debug output is easier to read (can distinguish lib from dird, ...). git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@1937 91ce42f0-d328-0410-95d8-f526ca767f89 --- diff --git a/bacula/examples/python/DirStartUp.py b/bacula/examples/python/DirStartUp.py new file mode 100644 index 0000000000..2a810f4bc7 --- /dev/null +++ b/bacula/examples/python/DirStartUp.py @@ -0,0 +1,90 @@ +# +# Bacula Python interface script +# + +# You must import both sys and bacula +import sys, bacula + +# This is the list of Bacula daemon events that you +# can receive. +class BaculaEvents: + def __init__(self): + # Called here when a new Bacula Events class is + # is created. Normally not used + noop = 1 + + def JobStart(self, job): + """ + Called here when a new job is started. If you want + to do anything with the Job, you must register + events you want to receive. + """ + events = JobEvents() # create instance of Job class + events.job = job # save Bacula's job pointer + job.set_events(events) # register events desired + sys.stderr = events # send error output to Bacula + sys.stdout = events # send stdout to Bacula + jobid = job.get("JobId"); client = job.get("Client"); + numvols = job.get("NumVols") + job.set(JobReport="Python StartJob: JobId=%d Client=%s NumVols=%d\n" % (jobid,client,numvols)) + return 1 + + # Bacula Job is going to terminate + def JobEnd(self, job): + jobid = job.get("JobId") + client = job.get("Client") + job.set(JobReport="Python EndJob output: JobId=%d Client=%s.\n" % (jobid, client)) + if (jobid < 2) : + startid = job.run("run kernsave") + print "Python started new Job: jobid=", startid + return 1 + + # Called here when the Bacula daemon is going to exit + def Exit(self, job): + print "Daemon exiting." + +bacula.set_events(BaculaEvents()) # register daemon events desired + +""" + There are the Job events that you can receive. +""" +class JobEvents: + def __init__(self): + # Called here when you instantiate the Job. Not + # normally used + noop = 1 + + def NewVolume(self, job): + jobid = job.get("JobId") + client = job.get("Client") + numvol = job.get("NumVols"); + print "JobId=%d Client=%s NumVols=%d" % (jobid, client, numvol) + job.set(JobReport="Python before New Volume set for Job.\n") + job.set(VolumeName="TestA-001") + job.set(JobReport="Python after New Volume set for Job.\n") + return 1 + + + # Pass output back to Bacula + def write(self, text): + self.job.write(text) + + # Open file to be backed up. file is the filename + def open(self, file): + print "Open %s called" % file + self.fd = open('m.py', 'rb') + jobid = self.job.get("JobId") + print "Open: JobId=%d" % jobid + print "name=%s" % bacula.name + + # Read file data into Bacula memory buffer (mem) + # return length read. 0 => EOF, -1 => error + def read(self, mem): + print "Read called\n" + len = self.fd.readinto(mem) + print "Read %s bytes into mem.\n" % len + return len + + # Close file + def close(self): + self.fd.close() diff --git a/bacula/examples/python/FDStartUp.py b/bacula/examples/python/FDStartUp.py new file mode 100644 index 0000000000..2fe0fb27ee --- /dev/null +++ b/bacula/examples/python/FDStartUp.py @@ -0,0 +1,92 @@ +# +# Bacula Python interface script +# + +# You must import both sys and bacula +import sys, bacula + +# This is the list of Bacula daemon events that you +# can receive. +class BaculaEvents: + def __init__(self): + # Called here when a new Bacula Events class is + # is created. Normally not used + noop = 1 + + def JobStart(self, job): + """ + Called here when a new job is started. If you want + to do anything with the Job, you must register + events you want to receive. + """ + events = JobEvents() # create instance of Job class + events.job = job # save Bacula's job pointer + job.set_events(events) # register events desired + sys.stderr = events # send error output to Bacula + sys.stdout = events # send stdout to Bacula + jobid = job.get("JobId") + client = job.get("Client") + numvols = job.get("NumVols") + job.set(JobReport="Python StartJob: JobId=%d Client=%s NumVols=%d\n" % (jobid,client,numvols)) + return 1 + + # Bacula Job is going to terminate + def JobEnd(self, job): + jobid = job.get("JobId") + client = job.get("Client") + job.set(JobReport="Python EndJob output: JobId=%d Client=%s.\n" % (jobid, client)) + if (jobid < 2) : + startid = job.run("run kernsave") + print "Python started new Job: jobid=", startid + return 1 + + # Called here when the Bacula daemon is going to exit + def Exit(self, job): + noop = 1 + +bacula.set_events(BaculaEvents()) # register daemon events desired + +""" + There are the Job events that you can receive. +""" +class JobEvents: + def __init__(self): + # Called here when you instantiate the Job. Not + # normally used + noop = 1 + + def NewVolume(self, job): + jobid = job.get("JobId") + print "JobId=", jobid + client = job.get("Client") + print "Client=" + client + numvol = job.get("NumVols"); + print "NumVols=", numvol + job.set(JobReport="Python New Volume set for Job.\n") + job.set(VolumeName="TestA-001") + return 1 + + + # Pass output back to Bacula + def write(self, text): + self.job.write(text) + + # Open file to be backed up. file is the filename + def open(self, file): + print "Open %s called" % file + self.fd = open('m.py', 'rb') + jobid = self.job.get("JobId") + print "Open: JobId=%d" % jobid + print "name=%s" % bacula.name + + # Read file data into Bacula memory buffer (mem) + # return length read. 0 => EOF, -1 => error + def read(self, mem): + print "Read called\n" + len = self.fd.readinto(mem) + print "Read %s bytes into mem.\n" % len + return len + + # Close file + def close(self): + self.fd.close() diff --git a/bacula/examples/python/SDStartUp.py b/bacula/examples/python/SDStartUp.py new file mode 100644 index 0000000000..2fe0fb27ee --- /dev/null +++ b/bacula/examples/python/SDStartUp.py @@ -0,0 +1,92 @@ +# +# Bacula Python interface script +# + +# You must import both sys and bacula +import sys, bacula + +# This is the list of Bacula daemon events that you +# can receive. +class BaculaEvents: + def __init__(self): + # Called here when a new Bacula Events class is + # is created. Normally not used + noop = 1 + + def JobStart(self, job): + """ + Called here when a new job is started. If you want + to do anything with the Job, you must register + events you want to receive. + """ + events = JobEvents() # create instance of Job class + events.job = job # save Bacula's job pointer + job.set_events(events) # register events desired + sys.stderr = events # send error output to Bacula + sys.stdout = events # send stdout to Bacula + jobid = job.get("JobId") + client = job.get("Client") + numvols = job.get("NumVols") + job.set(JobReport="Python StartJob: JobId=%d Client=%s NumVols=%d\n" % (jobid,client,numvols)) + return 1 + + # Bacula Job is going to terminate + def JobEnd(self, job): + jobid = job.get("JobId") + client = job.get("Client") + job.set(JobReport="Python EndJob output: JobId=%d Client=%s.\n" % (jobid, client)) + if (jobid < 2) : + startid = job.run("run kernsave") + print "Python started new Job: jobid=", startid + return 1 + + # Called here when the Bacula daemon is going to exit + def Exit(self, job): + noop = 1 + +bacula.set_events(BaculaEvents()) # register daemon events desired + +""" + There are the Job events that you can receive. +""" +class JobEvents: + def __init__(self): + # Called here when you instantiate the Job. Not + # normally used + noop = 1 + + def NewVolume(self, job): + jobid = job.get("JobId") + print "JobId=", jobid + client = job.get("Client") + print "Client=" + client + numvol = job.get("NumVols"); + print "NumVols=", numvol + job.set(JobReport="Python New Volume set for Job.\n") + job.set(VolumeName="TestA-001") + return 1 + + + # Pass output back to Bacula + def write(self, text): + self.job.write(text) + + # Open file to be backed up. file is the filename + def open(self, file): + print "Open %s called" % file + self.fd = open('m.py', 'rb') + jobid = self.job.get("JobId") + print "Open: JobId=%d" % jobid + print "name=%s" % bacula.name + + # Read file data into Bacula memory buffer (mem) + # return length read. 0 => EOF, -1 => error + def read(self, mem): + print "Read called\n" + len = self.fd.readinto(mem) + print "Read %s bytes into mem.\n" % len + return len + + # Close file + def close(self): + self.fd.close() diff --git a/bacula/src/dird/dird.c b/bacula/src/dird/dird.c index a6f072d8b7..c1c0547945 100644 --- a/bacula/src/dird/dird.c +++ b/bacula/src/dird/dird.c @@ -222,8 +222,8 @@ int main (int argc, char *argv[]) init_console_msg(working_directory); - init_python_interpreter(director->hdr.name, director->scripts_directory ? - director->scripts_directory : ".", "DirStartUp"); + init_python_interpreter(director->hdr.name, director->scripts_directory, + "DirStartUp"); set_thread_concurrency(director->MaxConcurrentJobs * 2 + 4 /* UA */ + 4 /* sched+watchdog+jobsvr+misc */); @@ -255,12 +255,13 @@ int main (int argc, char *argv[]) /* Cleanup and then exit */ static void terminate_dird(int sig) { - static int already_here = FALSE; + static bool already_here = false; if (already_here) { /* avoid recursive temination problems */ exit(1); } - already_here = TRUE; + already_here = true; + generate_daemon_event(NULL, "Exit"); write_state_file(director->working_directory, "bacula-dir", get_first_port_host_order(director->DIRaddrs)); delete_pid_file(director->pid_directory, "bacula-dir", get_first_port_host_order(director->DIRaddrs)); // signal(SIGCHLD, SIG_IGN); /* don't worry about children now */ diff --git a/bacula/src/dird/job.c b/bacula/src/dird/job.c index bf9a4ea8e4..a42b66bc24 100644 --- a/bacula/src/dird/job.c +++ b/bacula/src/dird/job.c @@ -93,7 +93,6 @@ JobId_t run_job(JCR *jcr) } jcr->term_wait_inited = true; - generate_daemon_event(jcr, "StartJob"); /* * Open database @@ -113,6 +112,7 @@ JobId_t run_job(JCR *jcr) } Dmsg0(50, "DB opened\n"); + /* * Create Job record */ @@ -124,10 +124,11 @@ JobId_t run_job(JCR *jcr) goto bail_out; } JobId = jcr->JobId = jcr->jr.JobId; - Dmsg4(100, "Created job record JobId=%d Name=%s Type=%c Level=%c\n", jcr->JobId, jcr->Job, jcr->jr.JobType, jcr->jr.JobLevel); + generate_daemon_event(jcr, "JobStart"); + if (!get_or_create_client_record(jcr)) { goto bail_out; } diff --git a/bacula/src/dird/python.c b/bacula/src/dird/python.c index e717b1271e..f5445366e8 100644 --- a/bacula/src/dird/python.c +++ b/bacula/src/dird/python.c @@ -36,30 +36,27 @@ #include extern JCR *get_jcr_from_PyObject(PyObject *self); -extern PyObject *find_method(PyObject *eventsObject, PyObject *method, char *name); +extern PyObject *find_method(PyObject *eventsObject, PyObject *method, + const char *name); -static PyObject *jcr_get(PyObject *self, PyObject *args); +static PyObject *job_get(PyObject *self, PyObject *args); static PyObject *jcr_write(PyObject *self, PyObject *args); static PyObject *jcr_set(PyObject *self, PyObject *args, PyObject *keyw); -static PyObject *set_jcr_events(PyObject *self, PyObject *args); +static PyObject *set_job_events(PyObject *self, PyObject *args); static PyObject *jcr_run(PyObject *self, PyObject *args); /* Define Job entry points */ PyMethodDef JobMethods[] = { - {"get", jcr_get, METH_VARARGS, "Get Job variables."}, + {"get", job_get, METH_VARARGS, "Get Job variables."}, {"set", (PyCFunction)jcr_set, METH_VARARGS|METH_KEYWORDS, "Set Job variables."}, - {"set_events", set_jcr_events, METH_VARARGS, "Define Job events."}, + {"set_events", set_job_events, METH_VARARGS, "Define Job events."}, {"write", jcr_write, METH_VARARGS, "Write output."}, {"run", (PyCFunction)jcr_run, METH_VARARGS, "Run a Bacula command."}, {NULL, NULL, 0, NULL} /* last item */ }; -static PyObject *open_method = NULL; -static PyObject *read_method = NULL; -static PyObject *close_method = NULL; - struct s_vars { const char *name; char *fmt; @@ -85,7 +82,7 @@ static struct s_vars vars[] = { }; /* Return Job variables */ -static PyObject *jcr_get(PyObject *self, PyObject *args) +static PyObject *job_get(PyObject *self, PyObject *args) { JCR *jcr; char *item; @@ -93,6 +90,7 @@ static PyObject *jcr_get(PyObject *self, PyObject *args) int i; char buf[10]; + Dmsg0(100, "In jcr_get.\n"); if (!PyArg_ParseTuple(args, "s:get", &item)) { return NULL; } @@ -146,6 +144,8 @@ static PyObject *jcr_set(PyObject *self, PyObject *args, PyObject *keyw) char *msg = NULL; char *VolumeName = NULL; static char *kwlist[] = {"JobReport", "VolumeName", NULL}; + + Dmsg0(100, "In jcr_set.\n"); if (!PyArg_ParseTupleAndKeywords(args, keyw, "|ss:set", kwlist, &msg, &VolumeName)) { return NULL; @@ -155,27 +155,39 @@ static PyObject *jcr_set(PyObject *self, PyObject *args, PyObject *keyw) if (msg) { Jmsg(jcr, M_INFO, 0, "%s", msg); } + + if (!VolumeName) { + Dmsg1(100, "No VolumeName. Event=%s\n", jcr->event); + } else { + Dmsg2(100, "Vol=%s Event=%s\n", VolumeName, jcr->event); + } /* Make sure VolumeName is valid and we are in VolumeName event */ - if (VolumeName && strcmp("VolumeName", jcr->event) == 0 && + if (VolumeName && strcmp("NewVolume", jcr->event) == 0 && is_volume_name_legal(NULL, VolumeName)) { pm_strcpy(jcr->VolumeName, VolumeName); - } else { + Dmsg1(100, "Set Vol=%s\n", VolumeName); + } else if (VolumeName) { jcr->VolumeName[0] = 0; + Dmsg0(100, "Zap VolumeName.\n"); return Py_BuildValue("i", 0); /* invalid volume name */ } return Py_BuildValue("i", 1); } -static PyObject *set_jcr_events(PyObject *self, PyObject *args) +static PyObject *set_job_events(PyObject *self, PyObject *args) { PyObject *eObject; + JCR *jcr; + + Dmsg0(100, "In set_job_events.\n"); if (!PyArg_ParseTuple(args, "O:set_events_hook", &eObject)) { return NULL; } - Py_XINCREF(eObject); - open_method = find_method(eObject, open_method, "open"); - read_method = find_method(eObject, read_method, "read"); - close_method = find_method(eObject, close_method, "close"); + jcr = get_jcr_from_PyObject(self); + Py_XDECREF((PyObject *)jcr->Python_events); + Py_INCREF(eObject); + jcr->Python_events = (void *)eObject; + Py_INCREF(Py_None); return Py_None; } @@ -184,6 +196,7 @@ static PyObject *set_jcr_events(PyObject *self, PyObject *args) static PyObject *jcr_write(PyObject *self, PyObject *args) { char *text; + if (!PyArg_ParseTuple(args, "s:write", &text)) { return NULL; } @@ -219,22 +232,39 @@ static PyObject *jcr_run(PyObject *self, PyObject *args) int generate_job_event(JCR *jcr, const char *event) { -#ifdef xxx - PyObject *eObject, *method; + PyObject *method = NULL; + PyObject *Job = (PyObject *)jcr->Python_job; + PyObject *result = NULL; + int stat = 0; + + if (!Job) { + return 0; + } + PyEval_AcquireLock(); - method = find_method(eObject, method, event); + PyObject *events = (PyObject *)jcr->Python_events; + method = find_method(events, method, event); + if (!method) { + goto bail_out; + } - PyObject *result = PyObject_CallFunction(method, "s", "m.py"); + bstrncpy(jcr->event, event, sizeof(jcr->event)); + result = PyObject_CallFunction(method, "O", Job); + jcr->event[0] = 0; /* no event in progress */ if (result == NULL) { - PyErr_Print(); - PyErr_Clear(); + if (PyErr_Occurred()) { + PyErr_Print(); + Dmsg1(000, "Error in Python method %s\n", event); + } + } else { + stat = 1; } Py_XDECREF(result); +bail_out: PyEval_ReleaseLock(); -#endif - return 1; + return stat; } #else diff --git a/bacula/src/dird/ua_cmds.c b/bacula/src/dird/ua_cmds.c index 7125d4e57f..5c0da912ad 100644 --- a/bacula/src/dird/ua_cmds.c +++ b/bacula/src/dird/ua_cmds.c @@ -571,8 +571,8 @@ static int python_cmd(UAContext *ua, const char *cmd) { if (strcasecmp(ua->argk[1], _("restart")) == 0) { term_python_interpreter(); - init_python_interpreter(director->hdr.name, director->scripts_directory ? - director->scripts_directory : ".", "DirStartUp"); + init_python_interpreter(director->hdr.name, + director->scripts_directory, "DirStartUp"); bsendmsg(ua, _("Python interpreter restarted.\n")); } else { bsendmsg(ua, _("Nothing done.\n")); diff --git a/bacula/src/filed/filed.c b/bacula/src/filed/filed.c index 9fdd1983f7..bcfa65c5ed 100644 --- a/bacula/src/filed/filed.c +++ b/bacula/src/filed/filed.c @@ -230,8 +230,7 @@ int main (int argc, char *argv[]) me += 1000000; #endif - init_python_interpreter(me->hdr.name, me->scripts_directory ? - me->scripts_directory : ".", "FDStartUp"); + init_python_interpreter(me->hdr.name, me->scripts_directory, "FDStartUp"); set_thread_concurrency(10); diff --git a/bacula/src/filed/python.c b/bacula/src/filed/python.c index ea7c0cdb6d..3af306a477 100644 --- a/bacula/src/filed/python.c +++ b/bacula/src/filed/python.c @@ -36,7 +36,8 @@ #include extern JCR *get_jcr_from_PyObject(PyObject *self); -extern PyObject *find_method(PyObject *eventsObject, PyObject *method, char *name); +extern PyObject *find_method(PyObject *eventsObject, PyObject *method, + const char *name); static PyObject *jcr_get(PyObject *self, PyObject *args); static PyObject *jcr_write(PyObject *self, PyObject *args); diff --git a/bacula/src/jcr.h b/bacula/src/jcr.h index 6cccbdcd02..271655b635 100644 --- a/bacula/src/jcr.h +++ b/bacula/src/jcr.h @@ -143,7 +143,8 @@ struct JCR { bool prefix_links; /* Prefix links with Where path */ bool gui; /* set if gui using console */ bool authenticated; /* set when client authenticated */ - void *Python_jcr; /* Python Job Object */ + void *Python_job; /* Python Job Object */ + void *Python_events; /* Python Events Object */ /* Daemon specific part of JCR */ /* This should be empty in the library */ diff --git a/bacula/src/lib/Makefile.in b/bacula/src/lib/Makefile.in index 47b9c2d2d0..df440b5e9f 100644 --- a/bacula/src/lib/Makefile.in +++ b/bacula/src/lib/Makefile.in @@ -29,7 +29,7 @@ LIBSRCS = alloc.c attr.c base64.c berrno.c bsys.c bget_msg.c \ queue.c res.c rwlock.c scan.c serial.c sha1.c \ semlock.c signal.c smartall.c tree.c \ util.c var.c watchdog.c workq.c btimers.c \ - address_conf.c python.c + address_conf.c pythonlib.c LIBOBJS = alloc.o attr.o base64.o berrno.o bsys.o bget_msg.o \ @@ -41,7 +41,7 @@ LIBOBJS = alloc.o attr.o base64.o berrno.o bsys.o bget_msg.o \ queue.o res.o rwlock.o scan.o serial.o sha1.o \ semlock.o signal.o smartall.o tree.o \ util.o var.o watchdog.o workq.o btimers.o \ - address_conf.o python.o + address_conf.o pythonlib.o EXTRAOBJS = @OBJLIST@ @@ -72,7 +72,7 @@ Makefile: $(srcdir)/Makefile.in $(topdir)/config.status cd $(topdir) \ && CONFIG_FILES=$(thisdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status -python.o: python.c +pythonlib.o: pythonlib.c $(CXX) $(DEFS) $(DEBUG) -c $(WCFLAGS) $(CPPFLAGS) $(python) -I$(srcdir) -I$(basedir) $(DINCLUDE) $(CFLAGS) $< rwlock_test: diff --git a/bacula/src/lib/jcr.c b/bacula/src/lib/jcr.c index 44a0e48488..709a30d9b2 100755 --- a/bacula/src/lib/jcr.c +++ b/bacula/src/lib/jcr.c @@ -354,9 +354,6 @@ void free_jcr(JCR *jcr) #endif dequeue_messages(jcr); - if (jcr->JobId != 0) { - generate_daemon_event(jcr, "JobEnd"); - } lock_jcr_chain(); jcr->use_count--; /* decrement use count */ if (jcr->use_count < 0) { @@ -369,6 +366,14 @@ void free_jcr(JCR *jcr) Dmsg2(3400, "free_jcr 0x%x use_count=%d\n", jcr, jcr->use_count); return; } + + /* + * At this point, we are actually releasing the JCR, which + * means that the job is complete. + */ + if (jcr->JobId != 0) { + generate_daemon_event(jcr, "JobEnd"); + } remove_jcr(jcr); /* remove Jcr from chain */ unlock_jcr_chain(); diff --git a/bacula/src/lib/python.c b/bacula/src/lib/python.c deleted file mode 100644 index cfc3ebde90..0000000000 --- a/bacula/src/lib/python.c +++ /dev/null @@ -1,357 +0,0 @@ -/* - * - * Bacula common code library interface to Python - * - * Kern Sibbald, November MMIV - * - * Version $Id$ - * - */ - -/* - Copyright (C) 2004-2005 Kern Sibbald - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - 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 along with this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. - - */ - -#include "bacula.h" -#include "jcr.h" - -#ifdef HAVE_PYTHON - -#undef _POSIX_C_SOURCE -#include - -static PyObject *bacula_module = NULL; /* We create this */ -static PyObject *StartUp_module = NULL; /* We import this */ - -/* These are the daemon events or methods that are defined */ -static PyObject *JobStart_method = NULL; -static PyObject *JobEnd_method = NULL; -static PyObject *Exit_method = NULL; - - -static PyObject *set_bacula_events(PyObject *self, PyObject *args); -static PyObject *bacula_write(PyObject *self, PyObject *args); - -PyObject *find_method(PyObject *eventsObject, PyObject *method, char *name); - -/* Define Bacula daemon method entry points */ -static PyMethodDef BaculaMethods[] = { - {"set_events", set_bacula_events, METH_VARARGS, "Define Bacula events."}, - {"write", bacula_write, METH_VARARGS, "Write output."}, - {NULL, NULL, 0, NULL} /* last item */ -}; - - -/* - * This is a Bacula Job type as defined in Python. We store a pointer - * to the jcr. That is all we need, but the user's script may keep - * local data attached to this. - */ -typedef struct JCRObject { - PyObject_HEAD - JCR *jcr; -} JCRObject; - -static PyTypeObject JCRType = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ - "bacula.jcr", /*tp_name*/ - sizeof(JCRObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - 0, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - "JCR objects", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; - -/* Return the JCR pointer from the JCRObject */ -JCR *get_jcr_from_PyObject(PyObject *self) -{ - return ((JCRObject *)self)->jcr; -} - - -/* Start the interpreter */ -void init_python_interpreter(const char *progname, const char *scripts, - const char *module) -{ - char buf[MAXSTRING]; - Py_SetProgramName((char *)progname); - Py_Initialize(); - PyEval_InitThreads(); - bacula_module = Py_InitModule("bacula", BaculaMethods); - if (!bacula_module) { - Jmsg(NULL, M_ERROR_TERM, 0, "Could not initialize Python\n"); - } - bsnprintf(buf, sizeof(buf), "import sys\n" - "sys.path.append('%s')\n", scripts); - if (PyRun_SimpleString(buf) != 0) { - Jmsg(NULL, M_ERROR_TERM, 0, "Could not Run Python string %s\n", buf); - } - JCRType.tp_methods = BaculaMethods; - if(PyType_Ready(&JCRType) != 0) { - Jmsg(NULL, M_ERROR_TERM, 0, "Could not initialize Python Job type.\n"); - PyErr_Print(); - } - StartUp_module = PyImport_ImportModule((char *)module); - if (!StartUp_module) { - Jmsg(NULL, M_ERROR_TERM, 0, "Could not import script %s/%s.\n", - scripts, module); - PyErr_Clear(); - } - PyEval_ReleaseLock(); -} - - -void term_python_interpreter() -{ - Py_XDECREF(StartUp_module); - Py_Finalize(); -} - -static PyObject *set_bacula_events(PyObject *self, PyObject *args) -{ - PyObject *eObject; - if (!PyArg_ParseTuple(args, "O:set_bacula_events", &eObject)) { - return NULL; - } - JobStart_method = find_method(eObject, JobStart_method, "JobStart"); - JobEnd_method = find_method(eObject, JobEnd_method, "JobEnd"); - Exit_method = find_method(eObject, Exit_method, "Exit"); - - Py_XINCREF(eObject); - Py_INCREF(Py_None); - return Py_None; -} - -/* Write text to daemon output */ -static PyObject *bacula_write(PyObject *self, PyObject *args) -{ - char *text; - if (!PyArg_ParseTuple(args, "s:write", &text)) { - return NULL; - } - if (text) { - Jmsg(NULL, M_INFO, 0, "%s", text); - } - Py_INCREF(Py_None); - return Py_None; -} - - -/* - * Check that a method exists and is callable. - */ -PyObject *find_method(PyObject *eventsObject, PyObject *method, char *name) -{ - Py_XDECREF(method); /* release old method if any */ - method = PyObject_GetAttrString(eventsObject, name); - if (method == NULL) { - Dmsg1(000, "Python method %s not found\n", name); - } else if (PyCallable_Check(method) == 0) { - Dmsg1(000, "Python object %s found but not a method.\n", name); - Py_XDECREF(method); - method = NULL; - } else { - Dmsg1(000, "Got method %s\n", name); - } - return method; -} - - -/* - * Generate and process a Bacula event by importing a Python - * module and running it. - * - * Returns: 0 if Python not configured or module not found - * -1 on Python error - * 1 OK - */ -int generate_daemon_event(JCR *jcr, const char *event) -{ - PyObject *pJCR; - int stat = -1; - PyObject *result = NULL; - - PyEval_AcquireLock(); - if (strcmp(event, "JobStart") == 0) { - if (!JobStart_method) { - stat = 0; - goto bail_out; - } - /* Create JCR argument to send to function */ - pJCR = (PyObject *)PyObject_New(JCRObject, &JCRType); - if (!pJCR) { - Jmsg(jcr, M_ERROR, 0, "Could not create JCR Python Object.\n"); - goto bail_out; - } - ((JCRObject *)pJCR)->jcr = jcr; - bstrncpy(jcr->event, event, sizeof(jcr->event)); - result = PyObject_CallFunction(JobStart_method, "O", pJCR); - jcr->event[0] = 0; /* no event in progress */ - if (result == NULL) { - JobStart_method = NULL; - if (PyErr_Occurred()) { - PyErr_Print(); - } - Jmsg(jcr, M_ERROR, 0, "Python function \"%s\" not found.\n", event); - Py_XDECREF(pJCR); - goto bail_out; - } - jcr->Python_jcr = (void *)pJCR; - stat = 1; /* OK */ - - } else if (strcmp(event, "JobEnd") == 0) { - if (!JobEnd_method) { - Py_XDECREF((PyObject *)jcr->Python_jcr); - stat = 0; - goto bail_out; - } - bstrncpy(jcr->event, event, sizeof(jcr->event)); - result = PyObject_CallFunction(JobEnd_method, "O", jcr->Python_jcr); - jcr->event[0] = 0; /* no event in progress */ - if (result == NULL) { - JobEnd_method = NULL; - if (PyErr_Occurred()) { - PyErr_Print(); - } - Jmsg(jcr, M_ERROR, 0, "Python function \"%s\" not found.\n", event); - Py_XDECREF((PyObject *)jcr->Python_jcr); - goto bail_out; - } - Py_XDECREF((PyObject *)jcr->Python_jcr); - stat = 1; /* OK */ - } - -bail_out: - Py_XDECREF(result); - PyEval_ReleaseLock(); - return stat; -} - -#ifdef xxx - PyObject *pName, *pModule, *pDict, *pFunc; - PyObject *pArgs, *pJCR, *pCall; - - Dmsg1(100, "Generate event %s\n", event); - pName = PyString_FromString(event); - if (!pName) { - Jmsg(jcr, M_ERROR, 0, "Could not convert \"%s\" to Python string.\n", event); - return -1; /* Could not convert string */ - } - - pModule = PyImport_Import(pName); - Py_DECREF(pName); /* release pName */ - - if (pModule != NULL) { - pDict = PyModule_GetDict(pModule); - /* pDict is a borrowed reference */ - - pFunc = PyDict_GetItemString(pDict, (char *)event); - /* pFun: Borrowed reference */ - - if (pFunc && PyCallable_Check(pFunc)) { - /* Create JCR argument to send to function */ - pArgs = PyTuple_New(1); - pJCR = (PyObject *)PyObject_New(JCRObject, &JCRType); - ((JCRObject *)pJCR)->jcr = jcr; - if (!pJCR) { - Py_DECREF(pArgs); - Py_DECREF(pModule); - Jmsg(jcr, M_ERROR, 0, "Could not create JCR Python Object.\n"); - return -1; - } - Py_INCREF(pJCR); - /* pJCR reference stolen here: */ - PyTuple_SetItem(pArgs, 0, pJCR); - - /* Finally, we call the module here */ - bstrncpy(jcr->event, event, sizeof(jcr->event)); - pCall = PyObject_CallObject(pFunc, pArgs); - jcr->event[0] = 0; /* no event in progress */ - Py_DECREF(pArgs); - Py_DECREF(pJCR); - if (pCall != NULL) { - Py_DECREF(pCall); - } else { - Py_DECREF(pModule); - PyErr_Print(); - Jmsg(jcr, M_ERROR, 0, "Error running Python module: %s\n", event); - return 0; /* error running function */ - } - /* pDict and pFunc are borrowed and must not be Py_DECREF-ed */ - } else { - if (PyErr_Occurred()) { - PyErr_Print(); - } - Jmsg(jcr, M_ERROR, 0, "Python function \"%s\" not found in module.\n", event); - return -1; /* function not found */ - } - Py_DECREF(pModule); - } else { - return 0; /* Module not present */ - } - Dmsg0(100, "Generate event OK\n"); - return 1; -} -#endif - -#else - -/* - * No Python configured -- create external entry points and - * dummy routines so that library code can call this without - * problems even if it is not configured. - */ -int generate_daemon_event(JCR *jcr, const char *event) { return 0; } -void init_python_interpreter(const char *progname, const char *scripts, - const char *module) { } -void term_python_interpreter() { } - -#endif /* HAVE_PYTHON */ diff --git a/bacula/src/lib/pythonlib.c b/bacula/src/lib/pythonlib.c new file mode 100644 index 0000000000..7ad1030995 --- /dev/null +++ b/bacula/src/lib/pythonlib.c @@ -0,0 +1,394 @@ +/* + * + * Bacula common code library interface to Python + * + * Kern Sibbald, November MMIV + * + * Version $Id$ + * + */ + +/* + Copyright (C) 2004-2005 Kern Sibbald + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + 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 along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + */ + +#include "bacula.h" +#include "jcr.h" + +#ifdef HAVE_PYTHON + +#undef _POSIX_C_SOURCE +#include + +/* Imported subroutines */ +extern PyMethodDef JobMethods[]; + +static PyObject *bacula_module = NULL; /* We create this */ +static PyObject *StartUp_module = NULL; /* We import this */ + +/* These are the daemon events or methods that are defined */ +static PyObject *JobStart_method = NULL; +static PyObject *JobEnd_method = NULL; +static PyObject *Exit_method = NULL; + +static PyObject *set_bacula_events(PyObject *self, PyObject *args); +static PyObject *bacula_write(PyObject *self, PyObject *args); + +PyObject *find_method(PyObject *eventsObject, PyObject *method, const char *name); + +/* Define Bacula daemon method entry points */ +static PyMethodDef BaculaMethods[] = { + {"set_events", set_bacula_events, METH_VARARGS, "Define Bacula events."}, + {"write", bacula_write, METH_VARARGS, "Write output."}, + {NULL, NULL, 0, NULL} /* last item */ +}; + + +/* + * This is a Bacula Job type as defined in Python. We store a pointer + * to the jcr. That is all we need, but the user's script may keep + * local data attached to this. + */ +typedef struct s_JobObject { + PyObject_HEAD + JCR *jcr; +} JobObject; + +static PyTypeObject JobType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "bacula.jcr", /*tp_name*/ + sizeof(JobObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Job objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + JobMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +/* Return the JCR pointer from the JobObject */ +JCR *get_jcr_from_PyObject(PyObject *self) +{ + return ((JobObject *)self)->jcr; +} + + +/* Start the interpreter */ +void init_python_interpreter(const char *progname, const char *scripts, + const char *module) +{ + char buf[MAXSTRING]; + + if (!scripts || scripts[0] == 0) { + return; + } + + Py_SetProgramName((char *)progname); + Py_Initialize(); + PyEval_InitThreads(); + bacula_module = Py_InitModule("bacula", BaculaMethods); + if (!bacula_module) { + Jmsg0(NULL, M_ERROR_TERM, 0, "Could not initialize Python\n"); + } + bsnprintf(buf, sizeof(buf), "import sys\n" + "sys.path.append('%s')\n", scripts); + if (PyRun_SimpleString(buf) != 0) { + Jmsg1(NULL, M_ERROR_TERM, 0, "Could not Run Python string %s\n", buf); + } + JobType.tp_methods = JobMethods; + if(PyType_Ready(&JobType) != 0) { + Jmsg0(NULL, M_ERROR_TERM, 0, "Could not initialize Python Job type.\n"); + PyErr_Print(); + } + StartUp_module = PyImport_ImportModule((char *)module); + if (!StartUp_module) { + Emsg2(M_ERROR, 0, "Could not import Python script %s/%s. Python disabled.\n", + scripts, module); + if (PyErr_Occurred()) { + PyErr_Print(); + Dmsg0(000, "Python Import error.\n"); + } + } + PyEval_ReleaseLock(); +} + + +void term_python_interpreter() +{ + if (StartUp_module) { + Py_XDECREF(StartUp_module); + Py_Finalize(); + } +} + +static PyObject *set_bacula_events(PyObject *self, PyObject *args) +{ + PyObject *eObject; + + Dmsg0(100, "In set_bacula_events.\n"); + if (!PyArg_ParseTuple(args, "O:set_bacula_events", &eObject)) { + return NULL; + } + JobStart_method = find_method(eObject, JobStart_method, "JobStart"); + JobEnd_method = find_method(eObject, JobEnd_method, "JobEnd"); + Exit_method = find_method(eObject, Exit_method, "Exit"); + + Py_XINCREF(eObject); + Py_INCREF(Py_None); + return Py_None; +} + +/* Write text to daemon output */ +static PyObject *bacula_write(PyObject *self, PyObject *args) +{ + char *text; + if (!PyArg_ParseTuple(args, "s:write", &text)) { + return NULL; + } + if (text) { + Jmsg(NULL, M_INFO, 0, "%s", text); + } + Py_INCREF(Py_None); + return Py_None; +} + + +/* + * Check that a method exists and is callable. + */ +PyObject *find_method(PyObject *eventsObject, PyObject *method, const char *name) +{ + Py_XDECREF(method); /* release old method if any */ + method = PyObject_GetAttrString(eventsObject, (char *)name); + if (method == NULL) { + Dmsg1(000, "Python method %s not found\n", name); + } else if (PyCallable_Check(method) == 0) { + Dmsg1(000, "Python object %s found but not a method.\n", name); + Py_XDECREF(method); + method = NULL; + } else { + Dmsg1(100, "Got method %s\n", name); + } + return method; +} + + +/* + * Generate and process a Bacula event by importing a Python + * module and running it. + * + * Returns: 0 if Python not configured or module not found + * -1 on Python error + * 1 OK + */ +int generate_daemon_event(JCR *jcr, const char *event) +{ + PyObject *pJob; + int stat = -1; + PyObject *result = NULL; + + if (!StartUp_module) { + return 0; + } + + PyEval_AcquireLock(); + if (strcmp(event, "JobStart") == 0) { + if (!JobStart_method) { + stat = 0; + goto bail_out; + } + /* Create JCR argument to send to function */ + pJob = (PyObject *)PyObject_New(JobObject, &JobType); + if (!pJob) { + Jmsg(jcr, M_ERROR, 0, "Could not create Python Job Object.\n"); + goto bail_out; + } + ((JobObject *)pJob)->jcr = jcr; + bstrncpy(jcr->event, event, sizeof(jcr->event)); + result = PyObject_CallFunction(JobStart_method, "O", pJob); + jcr->event[0] = 0; /* no event in progress */ + if (result == NULL) { + JobStart_method = NULL; + if (PyErr_Occurred()) { + PyErr_Print(); + Dmsg0(000, "Python JobStart error.\n"); + } + Jmsg(jcr, M_ERROR, 0, "Python function \"%s\" not found.\n", event); + Py_XDECREF(pJob); + goto bail_out; + } + jcr->Python_job = (void *)pJob; + stat = 1; /* OK */ + goto jobstart_ok; + + } else if (strcmp(event, "JobEnd") == 0) { + if (!JobEnd_method) { + stat = 0; + goto bail_out; + } + bstrncpy(jcr->event, event, sizeof(jcr->event)); + result = PyObject_CallFunction(JobEnd_method, "O", jcr->Python_job); + jcr->event[0] = 0; /* no event in progress */ + if (result == NULL) { + JobEnd_method = NULL; + if (PyErr_Occurred()) { + PyErr_Print(); + Dmsg1(000, "Python JobEnd error. JobId=%d\n", jcr->JobId); + } + Jmsg(jcr, M_ERROR, 0, "Python function \"%s\" not found.\n", event); + goto bail_out; + } + stat = 1; /* OK */ + } else if (strcmp(event, "Exit") == 0) { + if (!Exit_method) { + stat = 0; + goto bail_out; + } + result = PyObject_CallFunction(JobEnd_method, NULL); + if (result == NULL) { + goto bail_out; + } + stat = 1; /* OK */ + } else { + Emsg1(M_ABORT, 0, "Unknown Python daemon event %s\n", event); + } + +bail_out: + Py_XDECREF((PyObject *)jcr->Python_job); + jcr->Python_job = NULL; + Py_XDECREF((PyObject *)jcr->Python_events); + jcr->Python_events = NULL; + /* Fall through */ +jobstart_ok: + Py_XDECREF(result); + PyEval_ReleaseLock(); + return stat; +} + +#ifdef xxx + PyObject *pName, *pModule, *pDict, *pFunc; + PyObject *pArgs, *pJCR, *pCall; + + Dmsg1(100, "Generate event %s\n", event); + pName = PyString_FromString(event); + if (!pName) { + Jmsg(jcr, M_ERROR, 0, "Could not convert \"%s\" to Python string.\n", event); + return -1; /* Could not convert string */ + } + + pModule = PyImport_Import(pName); + Py_DECREF(pName); /* release pName */ + + if (pModule != NULL) { + pDict = PyModule_GetDict(pModule); + /* pDict is a borrowed reference */ + + pFunc = PyDict_GetItemString(pDict, (char *)event); + /* pFun: Borrowed reference */ + + if (pFunc && PyCallable_Check(pFunc)) { + /* Create JCR argument to send to function */ + pArgs = PyTuple_New(1); + pJCR = (PyObject *)PyObject_New(JCRObject, &JCRType); + ((JCRObject *)pJCR)->jcr = jcr; + if (!pJCR) { + Py_DECREF(pArgs); + Py_DECREF(pModule); + Jmsg0(jcr, M_ERROR, 0, "Could not create JCR Python Object.\n"); + return -1; + } + Py_INCREF(pJCR); + /* pJCR reference stolen here: */ + PyTuple_SetItem(pArgs, 0, pJCR); + + /* Finally, we call the module here */ + bstrncpy(jcr->event, event, sizeof(jcr->event)); + pCall = PyObject_CallObject(pFunc, pArgs); + jcr->event[0] = 0; /* no event in progress */ + Py_DECREF(pArgs); + Py_DECREF(pJCR); + if (pCall != NULL) { + Py_DECREF(pCall); + } else { + Py_DECREF(pModule); + PyErr_Print(); + Jmsg1(jcr, M_ERROR, 0, "Error running Python module: %s\n", event); + return 0; /* error running function */ + } + /* pDict and pFunc are borrowed and must not be Py_DECREF-ed */ + } else { + if (PyErr_Occurred()) { + PyErr_Print(); + Dmsg1(000, "Python event %s function not callable.\n", event); + } + Jmsg1(jcr, M_ERROR, 0, "Python function \"%s\" not found in module.\n", event); + return -1; /* function not found */ + } + Py_DECREF(pModule); + } else { + return 0; /* Module not present */ + } + Dmsg0(100, "Generate event OK\n"); + return 1; +} +#endif + +#else + +/* + * No Python configured -- create external entry points and + * dummy routines so that library code can call this without + * problems even if it is not configured. + */ +int generate_daemon_event(JCR *jcr, const char *event) { return 0; } +void init_python_interpreter(const char *progname, const char *scripts, + const char *module) { } +void term_python_interpreter() { } + +#endif /* HAVE_PYTHON */ diff --git a/bacula/src/stored/python.c b/bacula/src/stored/python.c index 00e95f78ad..aa9e7e86a1 100644 --- a/bacula/src/stored/python.c +++ b/bacula/src/stored/python.c @@ -36,7 +36,8 @@ #include extern JCR *get_jcr_from_PyObject(PyObject *self); -extern PyObject *find_method(PyObject *eventsObject, PyObject *method, char *name); +extern PyObject *find_method(PyObject *eventsObject, PyObject *method, + const char *name); static PyObject *jcr_get(PyObject *self, PyObject *args); static PyObject *jcr_write(PyObject *self, PyObject *args); diff --git a/bacula/src/stored/stored.c b/bacula/src/stored/stored.c index d45ca8b0d3..0f40434933 100644 --- a/bacula/src/stored/stored.c +++ b/bacula/src/stored/stored.c @@ -208,8 +208,7 @@ int main (int argc, char *argv[]) Jmsg0(NULL, M_ABORT, 0, _("Volume Session Time is ZERO!\n")); } - init_python_interpreter(me->hdr.name, me->scripts_directory ? - me->scripts_directory : ".", "SDStartUp"); + init_python_interpreter(me->hdr.name, me->scripts_directory, "SDStartUp"); /* Make sure on Solaris we can run concurrent, watch dog + servers + misc */ set_thread_concurrency(me->max_concurrent_jobs * 2 + 4);