Py log()
[Tsunagari.git] / src / python.cpp
blobc99fc051466ef38552456da03c27e67b2c3c9729
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** python.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 #include <algorithm> // for std::replace
8 #include <signal.h> // for SIGINT and SIG_DFL
9 #include <string.h> // for strrchr
11 #include <boost/python.hpp>
13 #include "log.h"
14 #include "python.h"
15 #include "python-bindings.h" // for pythonInitBindings
16 #include "resourcer.h"
18 #include <Python-ast.h>
21 namespace bp = boost::python;
23 static bp::object modMain, modBltin;
24 static bp::object dictMain, dictBltin;
26 int inPythonScript = 0;
28 //! List of known safe Python modules allowed for importing.
29 static std::string moduleWhitelist[] = {
30 "__builtin__",
31 "__main__",
32 "math",
33 "time",
34 "traceback",
35 "",
38 static bool inWhitelist(const std::string& name)
40 for (int i = 0; moduleWhitelist[i].size(); i++)
41 if (name == moduleWhitelist[i])
42 return true;
43 return false;
47 static void pythonIncludeModule(const char* name)
49 bp::object module(bp::handle<>(PyImport_ImportModule(name)));
50 dictMain[name] = module;
53 static void pythonSetDefaultEncoding(const char* enc)
55 if (PyUnicode_SetDefaultEncoding(enc) != 0) {
56 PyErr_Format(PyExc_SystemError,
57 "encoding %s not found", enc);
58 bp::throw_error_already_set();
63 static PyObject*
64 safeImport(PyObject*, PyObject* args, PyObject* kwds)
66 static const char* kwlist[] = {"name", "globals", "locals", "fromlist",
67 "level", 0};
68 char* _name;
69 std::string name;
70 PyObject* globals, *locals, *fromList;
71 int level = -1;
73 // Validate args from Python.
74 if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__",
75 (char**)kwlist, &_name, &globals, &locals, &fromList,
76 &level))
77 return NULL;
78 name = _name;
80 // Search whitelisted Python modules.
81 if (inWhitelist(name))
82 return PyImport_ImportModuleLevel(_name, globals, locals,
83 fromList, level);
85 Log::info("Python", "import " + name);
87 // Search Python scripts inside World.
88 std::replace(name.begin(), name.end(), '.', '/');
89 name += ".py";
90 Resourcer* rc = Resourcer::instance();
91 if (rc->resourceExists(name)) {
92 rc->runPythonScript(name);
93 return modMain.ptr(); // We have to return a module...
96 // Nothing acceptable found.
97 std::string msg = std::string("Module '") + _name + "' not found or "
98 "not allowed. Note that Tsunagari runs in a sandbox and does "
99 "not allow most external modules.";
100 PyErr_Format(PyExc_ImportError, msg.c_str());
101 return NULL;
104 static PyObject* nullExecfile(PyObject*, PyObject*)
106 PyErr_SetString(PyExc_RuntimeError,
107 "file(): Tsunagari runs scripts in a sandbox and "
108 "does not allow accessing the standard filesystem");
109 return NULL;
112 static PyObject* nullFile(PyObject*, PyObject*)
114 PyErr_SetString(PyExc_RuntimeError,
115 "file(): Tsunagari runs scripts in a sandbox and "
116 "does not allow accessing the standard filesystem");
117 return NULL;
120 static PyObject* nullOpen(PyObject*, PyObject*)
122 PyErr_SetString(PyExc_RuntimeError,
123 "open(): Tsunagari runs scripts in a sandbox and "
124 "does not allow accessing the standard filesystem");
125 return NULL;
128 static PyObject* nullPrint(PyObject*, PyObject*)
130 PyErr_SetString(PyExc_RuntimeError,
131 "print(): Tsunagari does not allow scripts to print");
132 return NULL;
135 static PyObject* nullReload(PyObject*, PyObject*)
137 PyErr_SetString(PyExc_RuntimeError,
138 "reload(): Tsunagari does not allow module reloading");
139 return NULL;
142 PyMethodDef nullMethods[] = {
143 {"__import__", (PyCFunction)safeImport, METH_VARARGS | METH_KEYWORDS, ""},
144 {"execfile", nullExecfile, METH_VARARGS, ""},
145 {"file", nullFile, METH_VARARGS, ""},
146 {"open", nullOpen, METH_VARARGS, ""},
147 {"print", nullPrint, METH_VARARGS | METH_KEYWORDS, ""},
148 {"reload", nullReload, METH_O, ""},
149 {NULL, NULL, 0, NULL},
152 bool pythonInit()
154 try {
155 PyImport_AppendInittab("tsunagari", &pythonInitBindings);
156 Py_Initialize();
157 pythonSetDefaultEncoding("utf-8");
159 modMain = bp::import("__main__");
160 dictMain = modMain.attr("__dict__");
161 modBltin = bp::import("__builtin__");
162 dictBltin = modBltin.attr("__dict__");
164 pythonIncludeModule("tsunagari");
166 // Hack in some rough safety. Disable external scripts and IO.
167 // InitModule doesn't remove existing modules, so we can use it to
168 // insert new methods into a pre-existing module.
169 PyObject* module = Py_InitModule("__builtin__", nullMethods);
170 if (module == NULL)
171 bp::throw_error_already_set();
173 // Restore the default SIGINT handler.
174 // Python messes with it. >:(
175 PyOS_setsig(SIGINT, SIG_DFL);
176 } catch (bp::error_already_set) {
177 Log::fatal("Python", "An error occured while populating the "
178 "Python modules:");
179 Log::setVerbosity(V_NORMAL); // Assure message can be seen.
180 pythonErr();
181 return false;
183 return true;
186 void pythonFinalize()
188 Py_Finalize();
191 static std::string extractException(PyObject* exc, PyObject* val, PyObject* tb)
193 using namespace boost::python;
195 handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
196 if (!hval) {
197 return extract<std::string>(str(hexc));
199 else {
200 bp::object traceback(import("traceback"));
201 bp::object format_exception(traceback.attr("format_exception"));
202 bp::object formatted_list(format_exception(hexc, hval, htb));
203 bp::object formatted(str("").join(formatted_list));
204 return extract<std::string>(formatted);
209 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
211 char* type = PyExceptionClass_Name(exc);
212 char* dot = strrchr(type, '.');
213 if (dot)
214 type = dot + 1;
215 char* value = PyString_AsString(val);
217 std::string msg = "";
218 msg += type ? type : "<unknown type>";
219 if (value) {
220 msg += ": ";
221 msg += value;
223 return msg;
227 void pythonErr()
229 // Something bad happened. Error is already set in Python.
230 PyObject* exc, *val, *tb;
231 PyErr_Fetch(&exc, &val, &tb);
233 if (!exc) {
234 Log::err("Python",
235 "pythonErr() called, but no exception set");
236 return;
239 PyErr_NormalizeException(&exc, &val, &tb);
241 Log::err("Python", extractException(exc, val, tb));
244 bp::object pythonGlobals()
246 return dictMain;
249 PyCodeObject* pythonCompile(const char* fn, const char* code)
251 PyArena *arena = PyArena_New();
252 if (!arena) {
253 return NULL;
256 mod_ty mod = PyParser_ASTFromString(code, fn, Py_file_input, NULL, arena);
257 if (!mod) {
258 Log::err("Python",
259 std::string(fn) + ": failed to parse");
260 pythonErr();
261 return NULL;
264 PyCodeObject* co = PyAST_Compile(mod, fn, NULL, arena);
265 if (!co) {
266 Log::err("Python",
267 std::string(fn) + ": failed to compile");
268 pythonErr();
269 return NULL;
272 PyArena_Free(arena);
273 return co;
276 bool pythonExec(PyCodeObject* code)
278 if (!code)
279 return false;
281 try {
282 inPythonScript++;
284 // FIXME: locals, globals
285 PyObject* globals = dictMain.ptr();
286 PyObject* result = PyEval_EvalCode(code, globals, globals);
288 inPythonScript--;
289 if (!result)
290 pythonErr();
291 return result;
292 } catch (boost::python::error_already_set) {
293 inPythonScript--;
294 pythonErr();
295 return NULL;