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