python_plugin.cc
1 /* This is a component of LinuxCNC 2 * Copyright 2011, 2012, 2013 Jeff Epler <jepler@dsndata.com>, Michael 3 * Haberler <git@mah.priv.at>, Sebastian Kuzminsky <seb@highlab.com> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 #include "python_plugin.hh" 20 #include "inifile.hh" 21 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <unistd.h> 25 #include <set> 26 27 #define BOOST_PYTHON_MAX_ARITY 4 28 #include <boost/python/exec.hpp> 29 #include <boost/python/extract.hpp> 30 #include <boost/python/import.hpp> 31 32 namespace bp = boost::python; 33 34 #define MAX_ERRMSG_SIZE 256 35 36 #define ERRMSG(fmt, args...) \ 37 do { \ 38 char msgbuf[MAX_ERRMSG_SIZE]; \ 39 snprintf(msgbuf, sizeof(msgbuf) -1, fmt, ##args); \ 40 error_msg = std::string(msgbuf); \ 41 } while(0) 42 43 #define logPP(level, fmt, ...) \ 44 do { \ 45 ERRMSG(fmt, ## __VA_ARGS__); \ 46 if (log_level >= level) { \ 47 fprintf(stderr, fmt, ## __VA_ARGS__); \ 48 fprintf(stderr,"\n"); \ 49 } \ 50 } while (0) 51 52 static const char *strstore(const char *s); 53 54 // boost python versions from 1.58 to 1.61 (the latest at the time of 55 // writing) all have a bug in boost::python::execfile that results in a 56 // double free. Work around it by using the Python implementation of 57 // execfile instead. 58 // The bug was introduced at https://github.com/boostorg/python/commit/fe24ab9dd5440562e27422cd38f7de03356bfd16 59 bp::object working_execfile(const char *filename, bp::object globals, bp::object locals) { 60 return bp::import("__builtin__").attr("execfile")(filename, globals, locals); 61 } 62 63 int PythonPlugin::run_string(const char *cmd, bp::object &retval, bool as_file) 64 { 65 reload(); 66 try { 67 if (as_file) 68 retval = working_execfile(cmd, main_namespace, main_namespace); 69 else 70 retval = bp::exec(cmd, main_namespace, main_namespace); 71 status = PLUGIN_OK; 72 } 73 catch (bp::error_already_set) { 74 if (PyErr_Occurred()) { 75 exception_msg = handle_pyerror(); 76 } else 77 exception_msg = "unknown exception"; 78 status = PLUGIN_EXCEPTION; 79 bp::handle_exception(); 80 PyErr_Clear(); 81 } 82 if (status == PLUGIN_EXCEPTION) { 83 logPP(0, "run_string(%s): \n%s", 84 cmd, exception_msg.c_str()); 85 } 86 return status; 87 } 88 89 int PythonPlugin::call_method(bp::object method, bp::object &retval) 90 { 91 92 logPP(1, "call_method()"); 93 if (status < PLUGIN_OK) 94 return status; 95 96 try { 97 retval = method(); 98 status = PLUGIN_OK; 99 } 100 catch (bp::error_already_set) { 101 if (PyErr_Occurred()) { 102 exception_msg = handle_pyerror(); 103 } else 104 exception_msg = "unknown exception"; 105 status = PLUGIN_EXCEPTION; 106 bp::handle_exception(); 107 PyErr_Clear(); 108 } 109 if (status == PLUGIN_EXCEPTION) { 110 logPP(0, "call_method(): %s", exception_msg.c_str()); 111 } 112 return status; 113 114 } 115 116 int PythonPlugin::call(const char *module, const char *callable, 117 bp::object tupleargs, bp::object kwargs, bp::object &retval) 118 { 119 bp::object function; 120 121 if (callable == NULL) 122 return PLUGIN_NO_CALLABLE; 123 124 reload(); 125 126 if (status < PLUGIN_OK) 127 return status; 128 129 try { 130 if (module == NULL) { // default to function in toplevel module 131 function = main_namespace[callable]; 132 } else { 133 bp::object submod = main_namespace[module]; 134 bp::object submod_namespace = submod.attr("__dict__"); 135 function = submod_namespace[callable]; 136 } 137 // this wont work with boost-python1.34 - needs 1.40 138 //retval = function(*tupleargs, **kwargs); 139 140 // this does 141 PyObject *rv = PyObject_Call(function.ptr(), tupleargs.ptr(), kwargs.ptr()); 142 if (PyErr_Occurred()) 143 bp::throw_error_already_set(); 144 if (rv) 145 retval = bp::object(bp::borrowed(rv)); 146 else 147 retval = bp::object(); 148 status = PLUGIN_OK; 149 } 150 catch (bp::error_already_set) { 151 if (PyErr_Occurred()) { 152 exception_msg = handle_pyerror(); 153 } else 154 exception_msg = "unknown exception"; 155 status = PLUGIN_EXCEPTION; 156 bp::handle_exception(); 157 PyErr_Clear(); 158 } 159 if (status == PLUGIN_EXCEPTION) { 160 logPP(0, "call(%s%s%s): \n%s", 161 module ? module : "", 162 module ? "." : "", 163 callable, exception_msg.c_str()); 164 } 165 return status; 166 } 167 168 bool PythonPlugin::is_callable(const char *module, 169 const char *funcname) 170 { 171 bool unexpected = false; 172 bool result = false; 173 bp::object function; 174 175 reload(); 176 if ((status != PLUGIN_OK) || 177 (funcname == NULL)) { 178 return false; 179 } 180 try { 181 if (module == NULL) { // default to function in toplevel module 182 function = main_namespace[funcname]; 183 } else { 184 bp::object submod = main_namespace[module]; 185 bp::object submod_namespace = submod.attr("__dict__"); 186 function = submod_namespace[funcname]; 187 } 188 result = PyCallable_Check(function.ptr()); 189 } 190 catch (bp::error_already_set) { 191 // KeyError expected if not callable 192 if (!PyErr_ExceptionMatches(PyExc_KeyError)) { 193 // something else, strange 194 exception_msg = handle_pyerror(); 195 unexpected = true; 196 } 197 result = false; 198 PyErr_Clear(); 199 } 200 if (unexpected) 201 logPP(0, "is_callable(%s%s%s): unexpected exception:\n%s", 202 module ? module : "", module ? "." : "", 203 funcname,exception_msg.c_str()); 204 205 if (log_level) 206 logPP(4, "is_callable(%s%s%s) = %s", 207 module ? module : "", module ? "." : "", 208 funcname,result ? "TRUE":"FALSE"); 209 return result; 210 } 211 212 // this should be moved to an inotify-based solution and be done with it 213 int PythonPlugin::reload() 214 { 215 struct stat st; 216 if (!reload_on_change) 217 return PLUGIN_OK; 218 219 if (stat(abs_path, &st)) { 220 logPP(0, "reload: stat(%s) returned %s", abs_path, strerror(errno)); 221 status = PLUGIN_STAT_FAILED; 222 return status; 223 } 224 if (st.st_mtime > module_mtime) { 225 module_mtime = st.st_mtime; 226 initialize(); 227 logPP(1, "reload(): %s reloaded, status=%d", toplevel, status); 228 } else { 229 logPP(5, "reload: no-op"); 230 status = PLUGIN_OK; 231 } 232 return status; 233 } 234 235 // decode a Python exception into a string. 236 // Free function usable without working plugin instance. 237 std::string handle_pyerror() 238 { 239 PyObject *exc, *val, *tb; 240 bp::object formatted_list, formatted; 241 242 PyErr_Fetch(&exc, &val, &tb); 243 bp::handle<> hexc(exc), hval(bp::allow_null(val)), htb(bp::allow_null(tb)); 244 bp::object traceback(bp::import("traceback")); 245 if (!tb) { 246 bp::object format_exception_only(traceback.attr("format_exception_only")); 247 formatted_list = format_exception_only(hexc, hval); 248 } else { 249 bp::object format_exception(traceback.attr("format_exception")); 250 formatted_list = format_exception(hexc, hval, htb); 251 } 252 formatted = bp::str("\n").join(formatted_list); 253 return bp::extract<std::string>(formatted); 254 } 255 256 int PythonPlugin::initialize() 257 { 258 std::string msg; 259 if (Py_IsInitialized()) { 260 try { 261 bp::object module = bp::import("__main__"); 262 main_namespace = module.attr("__dict__"); 263 264 for(unsigned i = 0; i < inittab_entries.size(); i++) { 265 main_namespace[inittab_entries[i]] = bp::import(inittab_entries[i].c_str()); 266 } 267 if (toplevel) // only execute a file if there's one configured. 268 bp::object result = working_execfile(abs_path, 269 main_namespace, 270 main_namespace); 271 status = PLUGIN_OK; 272 } 273 catch (bp::error_already_set) { 274 if (PyErr_Occurred()) { 275 exception_msg = handle_pyerror(); 276 } else 277 exception_msg = "unknown exception"; 278 bp::handle_exception(); 279 status = PLUGIN_INIT_EXCEPTION; 280 PyErr_Clear(); 281 } 282 if (status == PLUGIN_INIT_EXCEPTION) { 283 logPP(-1, "initialize: module '%s' init failed: \n%s", 284 abs_path, exception_msg.c_str()); 285 } 286 } else { 287 logPP(-1, "initialize: Plugin not initialized"); 288 status = PLUGIN_PYTHON_NOT_INITIALIZED; 289 } 290 return status; 291 } 292 293 PythonPlugin::PythonPlugin(struct _inittab *inittab) : 294 status(0), 295 module_mtime(0), 296 reload_on_change(0), 297 toplevel(0), 298 abs_path(0), 299 log_level(0) 300 { 301 Py_SetProgramName((char *) abs_path); 302 303 if ((inittab != NULL) && 304 PyImport_ExtendInittab(inittab)) { 305 logPP(-1, "cant extend inittab"); 306 status = PLUGIN_INITTAB_FAILED; 307 return; 308 } 309 Py_Initialize(); 310 initialize(); 311 } 312 313 314 int PythonPlugin::configure(const char *iniFilename, 315 const char *section) 316 { 317 IniFile inifile; 318 const char *inistring; 319 320 if (section == NULL) { 321 logPP(1, "no section"); 322 status = PLUGIN_NO_SECTION; 323 return status; 324 } 325 if ((iniFilename == NULL) && 326 ((iniFilename = getenv("INI_FILE_NAME")) == NULL)) { 327 logPP(-1, "no inifile"); 328 status = PLUGIN_NO_INIFILE; 329 return status; 330 } 331 if (inifile.Open(iniFilename) == false) { 332 logPP(-1, "Unable to open inifile:%s:\n", iniFilename); 333 status = PLUGIN_BAD_INIFILE; 334 return status; 335 } 336 337 char real_path[PATH_MAX]; 338 if ((inistring = inifile.Find("TOPLEVEL", section)) != NULL) { 339 toplevel = strstore(inistring); 340 341 if ((inistring = inifile.Find("RELOAD_ON_CHANGE", section)) != NULL) 342 reload_on_change = (atoi(inistring) > 0); 343 344 if (realpath(toplevel, real_path) == NULL) { 345 logPP(-1, "cant resolve path to '%s'", toplevel); 346 status = PLUGIN_BAD_PATH; 347 return status; 348 } 349 struct stat st; 350 if (stat(real_path, &st)) { 351 logPP(1, "stat(%s) returns %s", real_path, strerror(errno)); 352 status = PLUGIN_STAT_FAILED; 353 return status; 354 } 355 abs_path = strstore(real_path); 356 module_mtime = st.st_mtime; // record timestamp 357 358 } else { 359 if (getcwd(real_path, PATH_MAX) == NULL) { 360 logPP(1, "path too long"); 361 status = PLUGIN_PATH_TOO_LONG; 362 return status; 363 } 364 abs_path = strstore(real_path); 365 } 366 367 if ((inistring = inifile.Find("LOG_LEVEL", section)) != NULL) 368 log_level = atoi(inistring); 369 else log_level = 0; 370 371 char pycmd[PATH_MAX]; 372 int n = 1; 373 int lineno; 374 while (NULL != (inistring = inifile.Find("PATH_PREPEND", "PYTHON", 375 n, &lineno))) { 376 sprintf(pycmd, "import sys\nsys.path.insert(0,\"%s\")", inistring); 377 logPP(1, "%s:%d: executing '%s'",iniFilename, lineno, pycmd); 378 379 if (PyRun_SimpleString(pycmd)) { 380 logPP(-1, "%s:%d: exception running '%s'",iniFilename, lineno, pycmd); 381 exception_msg = "exception running:" + std::string((const char*)pycmd); 382 status = PLUGIN_EXCEPTION_DURING_PATH_PREPEND; 383 return status; 384 } 385 n++; 386 } 387 n = 1; 388 while (NULL != (inistring = inifile.Find("PATH_APPEND", "PYTHON", 389 n, &lineno))) { 390 sprintf(pycmd, "import sys\nsys.path.append(\"%s\")", inistring); 391 logPP(1, "%s:%d: executing '%s'",iniFilename, lineno, pycmd); 392 if (PyRun_SimpleString(pycmd)) { 393 logPP(-1, "%s:%d: exception running '%s'",iniFilename, lineno, pycmd); 394 exception_msg = "exception running " + std::string((const char*)pycmd); 395 status = PLUGIN_EXCEPTION_DURING_PATH_APPEND; 396 return status; 397 } 398 n++; 399 } 400 logPP(3,"PythonPlugin: Python '%s'", Py_GetVersion()); 401 return initialize(); 402 } 403 404 // the externally visible singleton instance 405 PythonPlugin *python_plugin; 406 407 408 // first caller wins 409 // this splits instantiation from configuring PYTHONPATH, imports etc 410 PythonPlugin *PythonPlugin::instantiate(struct _inittab *inittab) 411 { 412 if (python_plugin == NULL) { 413 python_plugin = new PythonPlugin(inittab); 414 } 415 return (python_plugin->usable()) ? python_plugin : NULL; 416 } 417 418 419 static const char *strstore(const char *s) 420 { 421 static std::set<std::string> stringtable; 422 using namespace std; 423 424 if (s == NULL) 425 throw invalid_argument("strstore(): NULL argument"); 426 pair< set<string>::iterator, bool > pair = stringtable.insert(s); 427 return pair.first->c_str(); 428 }