Support multiple python module instances

This commit adds proper support for multiple instances of the python
module: When more than one instance is added to the module list, the
first instance loads the first script specified in the `python:`
configuration section. The second instance loads the second script,
and so on.

When there are more module instances in the module list than there are
scripts in the `python:` section, an error is raised during
initialization and unbound won't start. When more scripts than module
instances are provided, the surplus scripts are ignored.
This commit is contained in:
Philipp Serr 2017-01-22 11:58:28 +01:00
parent dfe8e0dfa2
commit b248654aab
6 changed files with 93 additions and 36 deletions

View file

@ -1026,7 +1026,7 @@ struct config_file {
char* control_key_file;
char* control_cert_file;
int do_daemonize;
char* python_script;
struct config_strlist* python_script;
};
/* ************************************************************************************ *

View file

@ -64,6 +64,15 @@ typedef struct PyThreadState PyThreadState;
typedef void* PyGILState_STATE;
#endif
/**
* counter for python module instances
* incremented by pythonmod_init(...)
*/
int py_mod_count = 0;
/** Python main thread */
PyThreadState* mainthr;
/**
* Global state for the module.
*/
@ -72,8 +81,6 @@ struct pythonmod_env {
/** Python script filename. */
const char* fname;
/** Python main thread */
PyThreadState* mainthr;
/** Python module. */
PyObject* module;
@ -242,11 +249,15 @@ cleanup:
int pythonmod_init(struct module_env* env, int id)
{
int py_mod_idx = py_mod_count++;
/* Initialize module */
FILE* script_py = NULL;
PyObject* py_init_arg, *res;
PyGILState_STATE gil;
int init_standard = 1;
int init_standard = 1, i = 0;
struct config_strlist* cfg_item = env->cfg->python_script;
struct pythonmod_env* pe = (struct pythonmod_env*)calloc(1, sizeof(struct pythonmod_env));
if (!pe)
@ -258,14 +269,21 @@ int pythonmod_init(struct module_env* env, int id)
env->modinfo[id] = (void*) pe;
/* Initialize module */
pe->fname = env->cfg->python_script;
pe->fname=NULL; i = 0;
while (cfg_item!=NULL) {
if (py_mod_idx==i++) {
pe->fname=cfg_item->str;
break;
}
cfg_item = cfg_item->next;
}
if(pe->fname==NULL || pe->fname[0]==0) {
log_err("pythonmod: no script given.");
log_err("pythonmod[%d]: no script given.", py_mod_idx);
return 0;
}
/* Initialize Python libraries */
if (!Py_IsInitialized())
if (py_mod_count==1 && !Py_IsInitialized())
{
#if PY_MAJOR_VERSION >= 3
wchar_t progname[8];
@ -281,29 +299,31 @@ int pythonmod_init(struct module_env* env, int id)
Py_Initialize();
PyEval_InitThreads();
SWIG_init();
pe->mainthr = PyEval_SaveThread();
mainthr = PyEval_SaveThread();
}
gil = PyGILState_Ensure();
/* Initialize Python */
PyRun_SimpleString("import sys \n");
PyRun_SimpleString("sys.path.append('.') \n");
if(env->cfg->directory && env->cfg->directory[0]) {
char wdir[1524];
snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n",
env->cfg->directory);
PyRun_SimpleString(wdir);
}
PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n");
PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n");
PyRun_SimpleString("import distutils.sysconfig \n");
PyRun_SimpleString("sys.path.append(distutils.sysconfig.get_python_lib(1,0)) \n");
if (PyRun_SimpleString("from unboundmodule import *\n") < 0)
{
log_err("pythonmod: cannot initialize core module: unboundmodule.py");
PyGILState_Release(gil);
return 0;
if (py_mod_count==1) {
/* Initialize Python */
PyRun_SimpleString("import sys \n");
PyRun_SimpleString("sys.path.append('.') \n");
if(env->cfg->directory && env->cfg->directory[0]) {
char wdir[1524];
snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n",
env->cfg->directory);
PyRun_SimpleString(wdir);
}
PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n");
PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n");
PyRun_SimpleString("import distutils.sysconfig \n");
PyRun_SimpleString("sys.path.append(distutils.sysconfig.get_python_lib(1,0)) \n");
if (PyRun_SimpleString("from unboundmodule import *\n") < 0)
{
log_err("pythonmod: cannot initialize core module: unboundmodule.py");
PyGILState_Release(gil);
return 0;
}
}
/* Check Python file load */
@ -341,6 +361,7 @@ int pythonmod_init(struct module_env* env, int id)
(void)PyParser_SimpleParseFile(script_py, pe->fname, Py_file_input);
log_py_err();
PyGILState_Release(gil);
fclose(script_py);
return 0;
}
fclose(script_py);
@ -425,9 +446,11 @@ void pythonmod_deinit(struct module_env* env, int id)
Py_XDECREF(pe->data);
PyGILState_Release(gil);
PyEval_RestoreThread(pe->mainthr);
Py_Finalize();
pe->mainthr = NULL;
if(--py_mod_count==0) {
PyEval_RestoreThread(mainthr);
Py_Finalize();
mainthr = NULL;
}
}
pe->fname = NULL;
free(pe);

View file

@ -602,7 +602,7 @@ int config_set_option(struct config_file* cfg, const char* opt,
else S_STR("control-key-file:", control_key_file)
else S_STR("control-cert-file:", control_cert_file)
else S_STR("module-config:", module_conf)
else S_STR("python-script:", python_script)
else S_STRLIST("python-script:", python_script)
else S_YNO("disable-dnssec-lame-check:", disable_dnssec_lame_check)
#ifdef CLIENT_SUBNET
/* Can't set max subnet prefix here, since that value is used when
@ -1054,7 +1054,7 @@ config_get_option(struct config_file* cfg, const char* opt,
else O_YNO(opt, "unblock-lan-zones", unblock_lan_zones)
else O_YNO(opt, "insecure-lan-zones", insecure_lan_zones)
else O_DEC(opt, "max-udp-size", max_udp_size)
else O_STR(opt, "python-script", python_script)
else O_LST(opt, "python-script", python_script)
else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check)
else O_DEC(opt, "ip-ratelimit", ip_ratelimit)
else O_DEC(opt, "ratelimit", ratelimit)
@ -1420,6 +1420,7 @@ config_delete(struct config_file* cfg)
free(cfg->dnstap_version);
config_deldblstrlist(cfg->ratelimit_for_domain);
config_deldblstrlist(cfg->ratelimit_below_domain);
config_delstrlist(cfg->python_script);
#ifdef USE_IPSECMOD
free(cfg->ipsecmod_hook);
config_delstrlist(cfg->ipsecmod_whitelist);
@ -1630,6 +1631,31 @@ cfg_strlist_insert(struct config_strlist** head, char* item)
return 1;
}
int
cfg_strlist_append_ex(struct config_strlist** head, char* item)
{
struct config_strlist *s;
if(!item || !head)
return 0;
s = (struct config_strlist*)calloc(1, sizeof(struct config_strlist));
if(!s)
return 0;
s->str = item;
s->next = NULL;
if (*head==NULL) {
*head = s;
} else {
struct config_strlist *last = *head;
while (last->next!=NULL) {
last = last->next;
}
last->next = s;
}
return 1;
}
int
cfg_str2list_insert(struct config_str2list** head, char* item, char* i2)
{

View file

@ -433,7 +433,7 @@ struct config_file {
char* control_cert_file;
/** Python script file */
char* python_script;
struct config_strlist* python_script;
/** Use systemd socket activation. */
int use_systemd;
@ -820,6 +820,14 @@ char* config_collate_cat(struct config_strlist* list);
*/
int cfg_strlist_append(struct config_strlist_head* list, char* item);
/**
* Searches the end of a string list and appends the given text.
* @param head: pointer to strlist head variable.
* @param item: new item. malloced by caller. if NULL the insertion fails.
* @return true on success.
*/
int cfg_strlist_append_ex(struct config_strlist** head, char* item);
/**
* Find string in strlist.
* @param head: pointer to strlist head variable.

View file

@ -5716,8 +5716,8 @@ yyreduce:
#line 2721 "./util/configparser.y" /* yacc.c:1648 */
{
OUTYY(("P(python-script:%s)\n", (yyvsp[0].str)));
free(cfg_parser->cfg->python_script);
cfg_parser->cfg->python_script = (yyvsp[0].str);
if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, (yyvsp[0].str)))
yyerror("out of memory");
}
#line 5723 "util/configparser.c" /* yacc.c:1648 */
break;

View file

@ -2720,8 +2720,8 @@ content_py: py_script
py_script: VAR_PYTHON_SCRIPT STRING_ARG
{
OUTYY(("P(python-script:%s)\n", $2));
free(cfg_parser->cfg->python_script);
cfg_parser->cfg->python_script = $2;
if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, $2))
yyerror("out of memory");
}
server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG
{