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