1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2011-2014 Planets Communications B.V.
5    Copyright (C) 2013-2018 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation, which is
10    listed in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    Affero General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /*
23  * Marco van Wieringen, August 2012
24  */
25 /**
26  * @file
27  * Python Director Plugin program
28  */
29 
30 #include "include/bareos.h"
31 #include <Python.h>
32 #include "dird/dird.h"
33 
34 #if (PY_VERSION_HEX <  0x02060000)
35 #error "Need at least Python version 2.6 or newer"
36 #endif
37 
38 #include "python-dir.h"
39 #include "lib/edit.h"
40 
41 namespace directordaemon {
42 
43 static const int debuglevel = 150;
44 
45 #define PLUGIN_LICENSE      "Bareos AGPLv3"
46 #define PLUGIN_AUTHOR       "Marco van Wieringen"
47 #define PLUGIN_DATE         "October 2013"
48 #define PLUGIN_VERSION      "3"
49 #define PLUGIN_DESCRIPTION  "Python Director Daemon Plugin"
50 #define PLUGIN_USAGE        "python:instance=<instance_id>:module_path=<path-to-python-modules>:module_name=<python-module-to-load>"
51 
52 #define Dmsg(context, level,  ...) bfuncs->DebugMessage(context, __FILE__, __LINE__, level, __VA_ARGS__ )
53 #define Jmsg(context, type,  ...) bfuncs->JobMessage(context, __FILE__, __LINE__, type, 0, __VA_ARGS__ )
54 
55 /* Forward referenced functions */
56 static bRC newPlugin(bpContext *ctx);
57 static bRC freePlugin(bpContext *ctx);
58 static bRC getPluginValue(bpContext *ctx, pDirVariable var, void *value);
59 static bRC setPluginValue(bpContext *ctx, pDirVariable var, void *value);
60 static bRC handlePluginEvent(bpContext *ctx, bDirEvent *event, void *value);
61 static bRC parse_plugin_definition(bpContext *ctx, void *value, PoolMem &plugin_options);
62 
63 static void PyErrorHandler(bpContext *ctx, int msgtype);
64 static bRC PyLoadModule(bpContext *ctx, void *value);
65 static bRC PyParsePluginDefinition(bpContext *ctx, void *value);
66 static bRC PyGetPluginValue(bpContext *ctx, pDirVariable var, void *value);
67 static bRC PySetPluginValue(bpContext *ctx, pDirVariable var, void *value);
68 static bRC PyHandlePluginEvent(bpContext *ctx, bDirEvent *event, void *value);
69 
70 /* Pointers to Bareos functions */
71 static bDirFuncs *bfuncs = NULL;
72 static bDirInfo  *binfo = NULL;
73 
74 static genpInfo pluginInfo = {
75    sizeof(pluginInfo),
76    DIR_PLUGIN_INTERFACE_VERSION,
77    DIR_PLUGIN_MAGIC,
78    PLUGIN_LICENSE,
79    PLUGIN_AUTHOR,
80    PLUGIN_DATE,
81    PLUGIN_VERSION,
82    PLUGIN_DESCRIPTION,
83    PLUGIN_USAGE
84 };
85 
86 static pDirFuncs pluginFuncs = {
87    sizeof(pluginFuncs),
88    DIR_PLUGIN_INTERFACE_VERSION,
89 
90    /* Entry points into plugin */
91    newPlugin,                         /* New plugin instance */
92    freePlugin,                        /* Free plugin instance */
93    getPluginValue,
94    setPluginValue,
95    handlePluginEvent
96 };
97 
98 /**
99  * Plugin private context
100  */
101 struct plugin_ctx {
102    int64_t instance;                  /* Instance number of plugin */
103    bool python_loaded;                /* Plugin has python module loaded ? */
104    bool python_path_set;              /* Python plugin search path is set ? */
105    char *module_path;                 /* Plugin Module Path */
106    char *module_name;                 /* Plugin Module Name */
107    PyThreadState *interpreter;        /* Python interpreter for this instance of the plugin */
108    PyObject *pInstance;               /* Python Module instance */
109    PyObject *pModule;                 /* Python Module entry point */
110    PyObject *pDict;                   /* Python Dictionary */
111    PyObject *bpContext;               /* Python representation of plugin context */
112 };
113 
114 /**
115  * We don't actually use this but we need it to tear down the
116  * final python interpreter on unload of the plugin. Each instance of
117  * the plugin get its own interpreter.
118  */
119 static PyThreadState *mainThreadState;
120 
121 #ifdef __cplusplus
122 extern "C" {
123 #endif
124 
125 /**
126  * loadPlugin() and unloadPlugin() are entry points that are
127  *  exported, so Bareos can directly call these two entry points
128  *  they are common to all Bareos plugins.
129  *
130  * External entry point called by Bareos to "load" the plugin
131  */
loadPlugin(bDirInfo * lbinfo,bDirFuncs * lbfuncs,genpInfo ** pinfo,pDirFuncs ** pfuncs)132 bRC loadPlugin(bDirInfo *lbinfo,
133                            bDirFuncs *lbfuncs,
134                            genpInfo **pinfo,
135                            pDirFuncs **pfuncs)
136 {
137    bfuncs = lbfuncs;                  /* Set Bareos funct pointers */
138    binfo  = lbinfo;
139 
140    *pinfo  = &pluginInfo;             /* Return pointer to our info */
141    *pfuncs = &pluginFuncs;            /* Return pointer to our functions */
142 
143    /*
144     * Setup Python
145     */
146    Py_InitializeEx(0);
147    PyEval_InitThreads();
148    mainThreadState = PyEval_SaveThread();
149 
150    return bRC_OK;
151 }
152 
153 /**
154  * External entry point to unload the plugin
155  */
unloadPlugin()156 bRC unloadPlugin()
157 {
158    /*
159     * Terminate Python
160     */
161    PyEval_RestoreThread(mainThreadState);
162    Py_Finalize();
163 
164    return bRC_OK;
165 }
166 
167 #ifdef __cplusplus
168 }
169 #endif
170 
newPlugin(bpContext * ctx)171 static bRC newPlugin(bpContext *ctx)
172 {
173    struct plugin_ctx *p_ctx;
174 
175    p_ctx = (struct plugin_ctx *)malloc(sizeof(struct plugin_ctx));
176    if (!p_ctx) {
177       return bRC_Error;
178    }
179    memset(p_ctx, 0, sizeof(struct plugin_ctx));
180    ctx->pContext = (void *)p_ctx;        /* set our context pointer */
181 
182    /*
183     * For each plugin instance we instantiate a new Python interpreter.
184     */
185    PyEval_AcquireLock();
186    p_ctx->interpreter = Py_NewInterpreter();
187    PyEval_ReleaseThread(p_ctx->interpreter);
188 
189    /*
190     * Always register some events the python plugin itself can register
191     * any other events it is interested in.
192     */
193    bfuncs->registerBareosEvents(ctx,
194                                 1,
195                                 bDirEventNewPluginOptions);
196 
197    return bRC_OK;
198 }
199 
freePlugin(bpContext * ctx)200 static bRC freePlugin(bpContext *ctx)
201 {
202    struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
203 
204    if (!p_ctx) {
205       return bRC_Error;
206    }
207 
208    /*
209     * Stop any sub interpreter started per plugin instance.
210     */
211    PyEval_AcquireThread(p_ctx->interpreter);
212 
213    /*
214     * Do python cleanup calls.
215     */
216    if (p_ctx->bpContext) {
217       Py_DECREF(p_ctx->bpContext);
218    }
219 
220    if (p_ctx->pModule) {
221       Py_DECREF(p_ctx->pModule);
222    }
223 
224    Py_EndInterpreter(p_ctx->interpreter);
225    PyEval_ReleaseLock();
226 
227    free(p_ctx);
228    ctx->pContext = NULL;
229 
230    return bRC_OK;
231 }
232 
getPluginValue(bpContext * ctx,pDirVariable var,void * value)233 static bRC getPluginValue(bpContext *ctx, pDirVariable var, void *value)
234 {
235    struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
236    bRC retval = bRC_Error;
237 
238    PyEval_AcquireThread(p_ctx->interpreter);
239    retval = PyGetPluginValue(ctx, var, value);
240    PyEval_ReleaseThread(p_ctx->interpreter);
241 
242    return retval;
243 }
244 
setPluginValue(bpContext * ctx,pDirVariable var,void * value)245 static bRC setPluginValue(bpContext *ctx, pDirVariable var, void *value)
246 {
247    struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
248    bRC retval = bRC_Error;
249 
250    PyEval_AcquireThread(p_ctx->interpreter);
251    retval = PySetPluginValue(ctx, var, value);
252    PyEval_ReleaseThread(p_ctx->interpreter);
253 
254    return retval;
255 }
256 
handlePluginEvent(bpContext * ctx,bDirEvent * event,void * value)257 static bRC handlePluginEvent(bpContext *ctx, bDirEvent *event, void *value)
258 {
259    bRC retval = bRC_Error;
260    bool event_dispatched = false;
261    PoolMem plugin_options(PM_FNAME);
262    plugin_ctx *p_ctx = (plugin_ctx *)ctx->pContext;
263 
264    if (!p_ctx) {
265       goto bail_out;
266    }
267 
268    /*
269     * First handle some events internally before calling python if it
270     * want to do some special handling on the event triggered.
271     */
272    switch (event->eventType) {
273    case bDirEventNewPluginOptions:
274       event_dispatched = true;
275       retval = parse_plugin_definition(ctx, value, plugin_options);
276       break;
277    default:
278       break;
279    }
280 
281    /*
282     * See if we have been triggered in the previous switch if not we have to
283     * always dispatch the event. If we already processed the event internally
284     * we only do a dispatch to the python entry point when that internal processing
285     * was successfull (e.g. retval == bRC_OK).
286     */
287    if (!event_dispatched || retval == bRC_OK) {
288       PyEval_AcquireThread(p_ctx->interpreter);
289 
290       /*
291        * Now dispatch the event to Python.
292        * First the calls that need special handling.
293        */
294       switch (event->eventType) {
295       case bDirEventNewPluginOptions:
296          /*
297           * See if we already loaded the Python modules.
298           */
299          if (!p_ctx->python_loaded) {
300             retval = PyLoadModule(ctx, plugin_options.c_str());
301          }
302 
303          /*
304           * Only try to call when the loading succeeded.
305           */
306          if (retval == bRC_OK) {
307             retval = PyParsePluginDefinition(ctx, plugin_options.c_str());
308          }
309          break;
310       default:
311          /*
312           * Handle the generic events e.g. the ones which are just passed on.
313           * We only try to call Python when we loaded the right module until
314           * that time we pretend the call succeeded.
315           */
316          if (p_ctx->python_loaded) {
317             retval = PyHandlePluginEvent(ctx, event, value);
318          } else {
319             retval = bRC_OK;
320          }
321          break;
322       }
323 
324       PyEval_ReleaseThread(p_ctx->interpreter);
325    }
326 
327 bail_out:
328    return retval;
329 }
330 
331 /**
332  * Strip any backslashes in the string.
333  */
StripBackSlashes(char * value)334 static inline void StripBackSlashes(char *value)
335 {
336    char *bp;
337 
338    bp = value;
339    while (*bp) {
340       switch (*bp) {
341       case '\\':
342          bstrinlinecpy(bp, bp + 1);
343          break;
344       default:
345          break;
346       }
347 
348       bp++;
349    }
350 }
351 
352 /**
353  * Parse a integer value.
354  */
parse_integer(const char * argument_value)355 static inline int64_t parse_integer(const char *argument_value)
356 {
357    return str_to_int64(argument_value);
358 }
359 
360 /**
361  * Parse a boolean value e.g. check if its yes or true anything else translates to false.
362  */
ParseBoolean(const char * argument_value)363 static inline bool ParseBoolean(const char *argument_value)
364 {
365    if (Bstrcasecmp(argument_value, "yes") ||
366        Bstrcasecmp(argument_value, "true")) {
367       return true;
368    } else {
369       return false;
370    }
371 }
372 
373 /**
374  * Always set destination to value and clean any previous one.
375  */
SetString(char ** destination,char * value)376 static inline void SetString(char **destination, char *value)
377 {
378    if (*destination) {
379       free(*destination);
380    }
381 
382    *destination = bstrdup(value);
383    StripBackSlashes(*destination);
384 }
385 
386 /**
387  * Parse the plugin definition passed in.
388  *
389  * The definition is in this form:
390  *
391  * python:module_path=<path>:module_name=<python_module_name>:...
392  */
parse_plugin_definition(bpContext * ctx,void * value,PoolMem & plugin_options)393 static bRC parse_plugin_definition(bpContext *ctx, void *value, PoolMem &plugin_options)
394 {
395    bool found;
396    int i, cnt;
397    PoolMem plugin_definition(PM_FNAME);
398    char *bp, *argument, *argument_value;
399    plugin_ctx *p_ctx = (plugin_ctx *)ctx->pContext;
400 
401    if (!value) {
402       return bRC_Error;
403    }
404 
405    /*
406     * Parse the plugin definition.
407     * Make a private copy of the whole string.
408     */
409    PmStrcpy(plugin_definition, (char *)value);
410 
411    bp = strchr(plugin_definition.c_str(), ':');
412    if (!bp) {
413       Jmsg(ctx, M_FATAL, "python-dir: Illegal plugin definition %s\n", plugin_definition.c_str());
414       Dmsg(ctx, debuglevel, "python-dir: Illegal plugin definition %s\n", plugin_definition.c_str());
415       goto bail_out;
416    }
417 
418    /*
419     * Skip the first ':'
420     */
421    bp++;
422 
423    cnt = 0;
424    while (bp) {
425       if (strlen(bp) == 0) {
426          break;
427       }
428 
429       /*
430        * Each argument is in the form:
431        *    <argument> = <argument_value>
432        *
433        * So we setup the right pointers here, argument to the beginning
434        * of the argument, argument_value to the beginning of the argument_value.
435        */
436       argument = bp;
437       argument_value = strchr(bp, '=');
438       if (!argument_value) {
439          Jmsg(ctx, M_FATAL, "python-dir: Illegal argument %s without value\n", argument);
440          Dmsg(ctx, debuglevel, "python-dir: Illegal argument %s without value\n", argument);
441          goto bail_out;
442       }
443       *argument_value++ = '\0';
444 
445       /*
446        * See if there are more arguments and setup for the next run.
447        */
448       bp = argument_value;
449       do {
450          bp = strchr(bp, ':');
451          if (bp) {
452             if (*(bp - 1) != '\\') {
453                *bp++ = '\0';
454                break;
455             } else {
456                bp++;
457             }
458          }
459       } while (bp);
460 
461       found = false;
462       for (i = 0; plugin_arguments[i].name; i++) {
463          if (Bstrcasecmp(argument, plugin_arguments[i].name)) {
464             int64_t *int_destination = NULL;
465             char **str_destination = NULL;
466             bool *bool_destination = NULL;
467 
468             switch (plugin_arguments[i].type) {
469             case argument_instance:
470                int_destination = &p_ctx->instance;
471                break;
472             case argument_module_path:
473                str_destination = &p_ctx->module_path;
474                break;
475             case argument_module_name:
476                str_destination = &p_ctx->module_name;
477                break;
478             default:
479                break;
480             }
481 
482             if (int_destination) {
483                *int_destination = parse_integer(argument_value);
484             }
485 
486             if (str_destination) {
487                SetString(str_destination, argument_value);
488             }
489 
490             if (bool_destination) {
491                *bool_destination = ParseBoolean(argument_value);
492             }
493 
494             /*
495              * When we have a match break the loop.
496              */
497             found = true;
498             break;
499          }
500       }
501 
502       /*
503        * If we didn't consume this parameter we add it to the plugin_options list.
504        */
505       if (!found) {
506          PoolMem option(PM_FNAME);
507 
508          if (cnt) {
509             Mmsg(option, ":%s=%s", argument, argument_value);
510             PmStrcat(plugin_options, option.c_str());
511          } else {
512             Mmsg(option, "%s=%s", argument, argument_value);
513             PmStrcat(plugin_options, option.c_str());
514          }
515          cnt++;
516       }
517    }
518 
519    if (cnt > 0) {
520       PmStrcat(plugin_options, ":");
521    }
522 
523    return bRC_OK;
524 
525 bail_out:
526    return bRC_Error;
527 }
528 
529 /**
530  * Work around API changes in Python versions.
531  * These function abstract the storage and retrieval of the bpContext
532  * which is passed to the Python methods and which the method can pass
533  * back and which allow the callback function to understand what bpContext
534  * its talking about.
535  */
536 #if ((PY_VERSION_HEX < 0x02070000) || \
537      ((PY_VERSION_HEX >= 0x03000000) && \
538       (PY_VERSION_HEX < 0x03010000)))
539 /**
540  * Python version before 2.7 and 3.0.
541  */
PyCreatebpContext(bpContext * ctx)542 static PyObject *PyCreatebpContext(bpContext *ctx)
543 {
544    /*
545     * Setup a new CObject which holds the bpContext structure used here internally.
546     */
547    return PyCObject_FromVoidPtr((void *)ctx, NULL);
548 }
549 
PyGetbpContext(PyObject * pyCtx)550 static bpContext *PyGetbpContext(PyObject *pyCtx)
551 {
552    return (bpContext *)PyCObject_AsVoidPtr(pyCtx);
553 }
554 #else
555 /**
556  * Python version after 2.6 and 3.1.
557  */
PyCreatebpContext(bpContext * ctx)558 static PyObject *PyCreatebpContext(bpContext *ctx)
559 {
560    /*
561     * Setup a new Capsule which holds the bpContext structure used here internally.
562     */
563    return PyCapsule_New((void *)ctx, "bareos.bpContext", NULL);
564 }
565 
PyGetbpContext(PyObject * pyCtx)566 static bpContext *PyGetbpContext(PyObject *pyCtx)
567 {
568    return (bpContext *)PyCapsule_GetPointer(pyCtx, "bareos.bpContext");
569 }
570 #endif
571 
572 /**
573  * Convert a return value into a bRC enum value.
574  */
conv_python_retval(PyObject * pRetVal)575 static inline bRC conv_python_retval(PyObject *pRetVal)
576 {
577    return (bRC)PyInt_AsLong(pRetVal);
578 }
579 
580 /**
581  * Convert a return value from bRC enum value into Python Object.
582  */
conv_retval_python(bRC retval)583 static inline PyObject *conv_retval_python(bRC retval)
584 {
585    return (PyObject *)PyInt_FromLong((int)retval);
586 }
587 
588 /**
589  * Handle a Python error.
590  *
591  * Python equivalent:
592  *
593  * import traceback, sys
594  * return "".join(traceback.format_exception(sys.exc_type,
595  *    sys.exc_value, sys.exc_traceback))
596  */
PyErrorHandler(bpContext * ctx,int msgtype)597 static void PyErrorHandler(bpContext *ctx, int msgtype)
598 {
599    PyObject *type, *value, *traceback;
600    PyObject *tracebackModule;
601    char *error_string;
602 
603    PyErr_Fetch(&type, &value, &traceback);
604 
605    tracebackModule = PyImport_ImportModule("traceback");
606    if (tracebackModule != NULL) {
607       PyObject *tbList, *emptyString, *strRetval;
608 
609       tbList = PyObject_CallMethod(tracebackModule,
610                                    (char *)"format_exception",
611                                    (char *)"OOO",
612                                    type,
613                                    value == NULL ? Py_None : value,
614                                    traceback == NULL ? Py_None : traceback);
615 
616       emptyString = PyString_FromString("");
617       strRetval = PyObject_CallMethod(emptyString,
618                                       (char *)"join",
619                                       (char *)"O", tbList);
620 
621       error_string = bstrdup(PyString_AsString(strRetval));
622 
623       Py_DECREF(tbList);
624       Py_DECREF(emptyString);
625       Py_DECREF(strRetval);
626       Py_DECREF(tracebackModule);
627    } else {
628       error_string = bstrdup("Unable to import traceback module.");
629    }
630 
631    Py_DECREF(type);
632    Py_XDECREF(value);
633    Py_XDECREF(traceback);
634 
635    Dmsg(ctx, debuglevel, "python-dir: %s\n", error_string);
636    if (msgtype) {
637       Jmsg(ctx, msgtype, "python-dir: %s\n", error_string);
638    }
639 
640    free(error_string);
641 }
642 
643 /**
644  * Initial load of the Python module.
645  *
646  * Based on the parsed plugin options we set some prerequisits like the
647  * module path and the module to load. We also load the dictionary used
648  * for looking up the Python methods.
649  */
PyLoadModule(bpContext * ctx,void * value)650 static bRC PyLoadModule(bpContext *ctx, void *value)
651 {
652    bRC retval = bRC_Error;
653    struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
654    PyObject *sysPath,
655             *mPath,
656             *pName,
657             *pFunc;
658 
659    /*
660     * See if we already setup the python search path.
661     */
662    if (!p_ctx->python_path_set) {
663       /*
664        * Extend the Python search path with the given module_path.
665        */
666       if (p_ctx->module_path) {
667          sysPath = PySys_GetObject((char *)"path");
668          mPath = PyString_FromString(p_ctx->module_path);
669          PyList_Append(sysPath, mPath);
670          Py_DECREF(mPath);
671          p_ctx->python_path_set = true;
672       }
673    }
674 
675    /*
676     * See if we already setup the module structure.
677     */
678    if (!p_ctx->pInstance) {
679       /*
680        * Make our callback methods available for Python.
681        */
682       p_ctx->pInstance = Py_InitModule("bareosdir", BareosDIRMethods);
683    }
684 
685    /*
686     * Try to load the Python module by name.
687     */
688    if (p_ctx->module_name) {
689       Dmsg(ctx, debuglevel, "python-dir: Trying to load module with name %s\n", p_ctx->module_name);
690       pName = PyString_FromString(p_ctx->module_name);
691       p_ctx->pModule = PyImport_Import(pName);
692       Py_DECREF(pName);
693 
694       if (!p_ctx->pModule) {
695          Dmsg(ctx, debuglevel, "python-dir: Failed to load module with name %s\n", p_ctx->module_name);
696          goto bail_out;
697       }
698 
699       Dmsg(ctx, debuglevel, "python-dir: Successfully loaded module with name %s\n", p_ctx->module_name);
700 
701       /*
702        * Get the Python dictionary for lookups in the Python namespace.
703        */
704       p_ctx->pDict = PyModule_GetDict(p_ctx->pModule); /* Borrowed reference */
705 
706       /*
707        * Encode the bpContext so a Python method can pass it in on calling back.
708        */
709       p_ctx->bpContext = PyCreatebpContext(ctx);
710 
711       /*
712        * Lookup the load_bareos_plugin() function in the python module.
713        */
714       pFunc = PyDict_GetItemString(p_ctx->pDict, "load_bareos_plugin"); /* Borrowed reference */
715       if (pFunc && PyCallable_Check(pFunc)) {
716          PyObject *pPluginDefinition,
717                   *pRetVal;
718 
719          pPluginDefinition = PyString_FromString((char *)value);
720          if (!pPluginDefinition) {
721             goto bail_out;
722          }
723 
724          pRetVal = PyObject_CallFunctionObjArgs(pFunc, p_ctx->bpContext, pPluginDefinition, NULL);
725          Py_DECREF(pPluginDefinition);
726 
727          if (!pRetVal) {
728             goto bail_out;
729          } else {
730             retval = conv_python_retval(pRetVal);
731             Py_DECREF(pRetVal);
732          }
733       } else {
734          Dmsg(ctx, debuglevel, "python-dir: Failed to find function named load_bareos_plugins()\n");
735          goto bail_out;
736       }
737 
738       /*
739        * Keep track we successfully loaded.
740        */
741       p_ctx->python_loaded = true;
742    }
743 
744    return retval;
745 
746 bail_out:
747    if (PyErr_Occurred()) {
748       PyErrorHandler(ctx, M_FATAL);
749    }
750 
751    return retval;
752 }
753 
754 /**
755  * Any plugin options which are passed in are dispatched here to a Python method and it
756  * can parse the plugin options. This function is also called after PyLoadModule() has
757  * loaded the Python module and made sure things are operational.
758  */
PyParsePluginDefinition(bpContext * ctx,void * value)759 static bRC PyParsePluginDefinition(bpContext *ctx, void *value)
760 {
761    bRC retval = bRC_Error;
762    struct plugin_ctx *p_ctx = (struct plugin_ctx *)ctx->pContext;
763    PyObject *pFunc;
764 
765    /*
766     * Lookup the parse_plugin_definition() function in the python module.
767     */
768    pFunc = PyDict_GetItemString(p_ctx->pDict, "parse_plugin_definition"); /* Borrowed reference */
769    if (pFunc && PyCallable_Check(pFunc)) {
770       PyObject *pPluginDefinition,
771                *pRetVal;
772 
773       pPluginDefinition = PyString_FromString((char *)value);
774       if (!pPluginDefinition) {
775          goto bail_out;
776       }
777 
778       pRetVal = PyObject_CallFunctionObjArgs(pFunc, p_ctx->bpContext, pPluginDefinition, NULL);
779       Py_DECREF(pPluginDefinition);
780 
781       if (!pRetVal) {
782          goto bail_out;
783       } else {
784          retval = conv_python_retval(pRetVal);
785          Py_DECREF(pRetVal);
786       }
787 
788       return retval;
789    } else {
790       Dmsg(ctx, debuglevel, "python-dir: Failed to find function named parse_plugin_definition()\n");
791       return bRC_Error;
792    }
793 
794 bail_out:
795    if (PyErr_Occurred()) {
796       PyErrorHandler(ctx, M_FATAL);
797    }
798 
799    return retval;
800 }
801 
PyGetPluginValue(bpContext * ctx,pDirVariable var,void * value)802 static bRC PyGetPluginValue(bpContext *ctx, pDirVariable var, void *value)
803 {
804    return bRC_OK;
805 }
806 
PySetPluginValue(bpContext * ctx,pDirVariable var,void * value)807 static bRC PySetPluginValue(bpContext *ctx, pDirVariable var, void *value)
808 {
809    return bRC_OK;
810 }
811 
PyHandlePluginEvent(bpContext * ctx,bDirEvent * event,void * value)812 static bRC PyHandlePluginEvent(bpContext *ctx, bDirEvent *event, void *value)
813 {
814    bRC retval = bRC_Error;
815    plugin_ctx *p_ctx = (plugin_ctx *)ctx->pContext;
816    PyObject *pFunc;
817 
818    /*
819     * Lookup the handle_plugin_event() function in the python module.
820     */
821    pFunc = PyDict_GetItemString(p_ctx->pDict, "handle_plugin_event"); /* Borrowed reference */
822    if (pFunc && PyCallable_Check(pFunc)) {
823       PyObject *pEventType,
824                *pRetVal;
825 
826       pEventType = PyInt_FromLong(event->eventType);
827 
828       pRetVal = PyObject_CallFunctionObjArgs(pFunc, p_ctx->bpContext, pEventType, NULL);
829       Py_DECREF(pEventType);
830 
831       if (!pRetVal) {
832          goto bail_out;
833       } else {
834          retval = conv_python_retval(pRetVal);
835          Py_DECREF(pRetVal);
836       }
837    } else {
838       Dmsg(ctx, debuglevel, "python-dir: Failed to find function named handle_plugin_event()\n");
839    }
840 
841    return retval;
842 
843 bail_out:
844    if (PyErr_Occurred()) {
845       PyErrorHandler(ctx, M_FATAL);
846    }
847 
848    return retval;
849 }
850 
851 /**
852  * Callback function which is exposed as a part of the additional methods which allow
853  * a Python plugin to get certain internal values of the current Job.
854  */
PyBareosGetValue(PyObject * self,PyObject * args)855 static PyObject *PyBareosGetValue(PyObject *self, PyObject *args)
856 {
857    int var;
858    bpContext *ctx = NULL;
859    PyObject *pyCtx;
860    PyObject *pRetVal = NULL;
861 
862    if (!PyArg_ParseTuple(args, "Oi:BareosGetValue", &pyCtx, &var)) {
863       return NULL;
864    }
865 
866    switch (var) {
867    case bDirVarJobId:
868    case bDirVarLevel:
869    case bDirVarType:
870    case bDirVarNumVols:
871    case bDirVarJobStatus:
872    case bDirVarPriority:
873    case bDirVarFDJobStatus:
874    case bDirVarSDJobStatus: {
875       int value = 0;
876 
877       ctx = PyGetbpContext(pyCtx);
878       if (bfuncs->getBareosValue(ctx, (brDirVariable)var, &value) == bRC_OK) {
879          pRetVal = PyInt_FromLong(value);
880       }
881       break;
882    }
883    case bDirVarJobErrors:
884    case bDirVarSDErrors:
885    case bDirVarJobFiles:
886    case bDirVarSDJobFiles:
887    case bDirVarLastRate:
888    case bDirVarJobBytes:
889    case bDirVarReadBytes: {
890       uint64_t value = 0;
891 
892       ctx = PyGetbpContext(pyCtx);
893       if (bfuncs->getBareosValue(ctx, (brDirVariable)var, &value) == bRC_OK) {
894          pRetVal = PyLong_FromUnsignedLong(value);
895       }
896       break;
897    }
898    case bDirVarJobName:
899    case bDirVarJob:
900    case bDirVarClient:
901    case bDirVarPool:
902    case bDirVarStorage:
903    case bDirVarWriteStorage:
904    case bDirVarReadStorage:
905    case bDirVarCatalog:
906    case bDirVarMediaType:
907    case bDirVarVolumeName: {
908       char *value = NULL;
909 
910       ctx = PyGetbpContext(pyCtx);
911       if (bfuncs->getBareosValue(ctx, (brDirVariable)var, &value) == bRC_OK) {
912          if (value) {
913             pRetVal = PyString_FromString(value);
914          }
915       }
916       break;
917    }
918    case bDirVarPluginDir: {
919       char *value = NULL;
920 
921       if (bfuncs->getBareosValue(NULL, (brDirVariable)var, &value) == bRC_OK) {
922          if (value) {
923             pRetVal = PyString_FromString(value);
924          }
925       }
926       break;
927    }
928    default:
929       ctx = PyGetbpContext(pyCtx);
930       Dmsg(ctx, debuglevel, "python-dir: PyBareosGetValue unknown variable requested %d\n", var);
931       break;
932    }
933 
934    if (!pRetVal) {
935       Py_INCREF(Py_None);
936       pRetVal = Py_None;
937    }
938 
939    return pRetVal;
940 }
941 
942 /**
943  * Callback function which is exposed as a part of the additional methods which allow
944  * a Python plugin to set certain internal values of the current Job.
945  */
PyBareosSetValue(PyObject * self,PyObject * args)946 static PyObject *PyBareosSetValue(PyObject *self, PyObject *args)
947 {
948    int var;
949    bpContext *ctx = NULL;
950    bRC retval = bRC_Error;
951    PyObject *pyCtx, *pyValue;
952 
953    if (!PyArg_ParseTuple(args, "OiO:BareosSetValue", &pyCtx, &var, &pyValue)) {
954       goto bail_out;
955    }
956 
957    switch (var) {
958    case bwDirVarVolumeName: {
959       char *value;
960 
961       ctx = PyGetbpContext(pyCtx);
962       value = PyString_AsString(pyValue);
963       if (value) {
964          retval = bfuncs->setBareosValue(ctx, (bwDirVariable)var, value);
965       }
966 
967       break;
968    }
969    case bwDirVarPriority:
970    case bwDirVarJobLevel: {
971       int value;
972 
973       ctx = PyGetbpContext(pyCtx);
974       value = PyInt_AsLong(pyValue);
975       if (value >= 0) {
976          retval = bfuncs->setBareosValue(ctx, (bwDirVariable)var, &value);
977       }
978       break;
979    }
980    default:
981       ctx = PyGetbpContext(pyCtx);
982       Dmsg(ctx, debuglevel, "python-dir: PyBareosSetValue unknown variable requested %d\n", var);
983       break;
984    }
985 
986 bail_out:
987    return conv_retval_python(retval);
988 }
989 
990 /**
991  * Callback function which is exposed as a part of the additional methods which allow
992  * a Python plugin to issue debug messages using the Bareos debug message facility.
993  */
PyBareosDebugMessage(PyObject * self,PyObject * args)994 static PyObject *PyBareosDebugMessage(PyObject *self, PyObject *args)
995 {
996    int level;
997    char *dbgmsg = NULL;
998    bpContext *ctx;
999    PyObject *pyCtx;
1000 
1001    if (!PyArg_ParseTuple(args, "Oi|z:BareosDebugMessage", &pyCtx, &level, &dbgmsg)) {
1002       return NULL;
1003    }
1004 
1005    if (dbgmsg) {
1006       ctx = PyGetbpContext(pyCtx);
1007       Dmsg(ctx, level, "python-dir: %s", dbgmsg);
1008    }
1009 
1010    Py_INCREF(Py_None);
1011    return Py_None;
1012 }
1013 
1014 /**
1015  * Callback function which is exposed as a part of the additional methods which allow
1016  * a Python plugin to issue Job messages using the Bareos Job message facility.
1017  */
PyBareosJobMessage(PyObject * self,PyObject * args)1018 static PyObject *PyBareosJobMessage(PyObject *self, PyObject *args)
1019 {
1020    int type;
1021    char *jobmsg = NULL;
1022    bpContext *ctx;
1023    PyObject *pyCtx;
1024 
1025    if (!PyArg_ParseTuple(args, "Oi|z:BareosJobMessage", &pyCtx, &type, &jobmsg)) {
1026       return NULL;
1027    }
1028 
1029    if (jobmsg) {
1030       ctx = PyGetbpContext(pyCtx);
1031       Jmsg(ctx, type, "python-dir: %s", jobmsg);
1032    }
1033 
1034    Py_INCREF(Py_None);
1035    return Py_None;
1036 }
1037 
1038 /**
1039  * Callback function which is exposed as a part of the additional methods which allow
1040  * a Python plugin to issue a Register Event to register additional events it wants
1041  * to receive.
1042  */
PyBareosRegisterEvents(PyObject * self,PyObject * args)1043 static PyObject *PyBareosRegisterEvents(PyObject *self, PyObject *args)
1044 {
1045    int len, event;
1046    bpContext *ctx;
1047    bRC retval = bRC_Error;
1048    PyObject *pyCtx, *pyEvents, *pySeq, *pyEvent;
1049 
1050    if (!PyArg_ParseTuple(args, "OO:BareosRegisterEvents", &pyCtx, &pyEvents)) {
1051       goto bail_out;
1052    }
1053 
1054    pySeq = PySequence_Fast(pyEvents, "Expected a sequence of events");
1055    if (!pySeq) {
1056       goto bail_out;
1057    }
1058 
1059    len = PySequence_Fast_GET_SIZE(pySeq);
1060 
1061    ctx = PyGetbpContext(pyCtx);
1062    for (int i = 0; i < len; i++) {
1063       pyEvent = PySequence_Fast_GET_ITEM(pySeq, i);
1064       event = PyInt_AsLong(pyEvent);
1065 
1066       if (event >= bDirEventJobStart && event <= bDirEventGetScratch) {
1067          Dmsg(ctx, debuglevel, "python-dir: PyBareosRegisterEvents registering event %d\n", event);
1068          retval = bfuncs->registerBareosEvents(ctx, 1, event);
1069 
1070          if (retval != bRC_OK) {
1071             break;
1072          }
1073       }
1074    }
1075 
1076    Py_DECREF(pySeq);
1077 
1078 bail_out:
1079    return conv_retval_python(retval);
1080 }
1081 
1082 /**
1083  * Callback function which is exposed as a part of the additional methods which allow
1084  * a Python plugin to issue an Unregister Event to unregister events it doesn't want to
1085  * receive anymore.
1086  */
PyBareosUnRegisterEvents(PyObject * self,PyObject * args)1087 static PyObject *PyBareosUnRegisterEvents(PyObject *self, PyObject *args)
1088 {
1089    int len, event;
1090    bpContext *ctx;
1091    bRC retval = bRC_Error;
1092    PyObject *pyCtx, *pyEvents, *pySeq, *pyEvent;
1093 
1094    if (!PyArg_ParseTuple(args, "OO:BareosUnRegisterEvents", &pyCtx, &pyEvents)) {
1095       goto bail_out;
1096    }
1097 
1098    pySeq = PySequence_Fast(pyEvents, "Expected a sequence of events");
1099    if (!pySeq) {
1100       goto bail_out;
1101    }
1102 
1103    len = PySequence_Fast_GET_SIZE(pySeq);
1104 
1105    ctx = PyGetbpContext(pyCtx);
1106    for (int i = 0; i < len; i++) {
1107       pyEvent = PySequence_Fast_GET_ITEM(pySeq, i);
1108       event = PyInt_AsLong(pyEvent);
1109 
1110       if (event >= bDirEventJobStart && event <= bDirEventGetScratch) {
1111          Dmsg(ctx, debuglevel, "PyBareosUnRegisterEvents: registering event %d\n", event);
1112          retval = bfuncs->unregisterBareosEvents(ctx, 1, event);
1113 
1114          if (retval != bRC_OK) {
1115             break;
1116          }
1117       }
1118    }
1119 
1120    Py_DECREF(pySeq);
1121 
1122 bail_out:
1123    return conv_retval_python(retval);
1124 }
1125 
1126 /**
1127  * Callback function which is exposed as a part of the additional methods which allow
1128  * a Python plugin to issue a GetInstanceCount to retrieve the number of instances of
1129  * the current plugin being loaded into the daemon.
1130  */
PyBareosGetInstanceCount(PyObject * self,PyObject * args)1131 static PyObject *PyBareosGetInstanceCount(PyObject *self, PyObject *args)
1132 {
1133    int value;
1134    bpContext *ctx = NULL;
1135    PyObject *pyCtx;
1136    PyObject *pRetVal = NULL;
1137 
1138    if (!PyArg_ParseTuple(args, "O:BareosGetInstanceCount", &pyCtx)) {
1139       return NULL;
1140    }
1141 
1142    ctx = PyGetbpContext(pyCtx);
1143    if (bfuncs->getInstanceCount(ctx, &value) == bRC_OK) {
1144       pRetVal = PyInt_FromLong(value);
1145    }
1146 
1147    if (!pRetVal) {
1148       Py_INCREF(Py_None);
1149       pRetVal = Py_None;
1150    }
1151 
1152    return pRetVal;
1153 }
1154 } /* namespace directordaemon */
1155