exit with exit()
[Tsunagari.git] / src / python.cpp
blob897e53a3a4d73b5778c43cbb6a98e36dfe90ee6a
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 "client-conf.h"
14 #include "log.h"
15 #include "python.h"
16 #include "python-bindings.h" // for pythonInitBindings
17 #include "resourcer.h"
18 #include "window.h"
20 #include <Python-ast.h>
23 namespace bp = boost::python;
25 static bp::object modMain, modBltin;
26 static bp::object dictMain, dictBltin;
28 int inPythonScript = 0;
30 //! List of known safe Python modules allowed for importing.
31 static std::string moduleWhitelist[] = {
32 "__builtin__",
33 "__main__",
34 "math",
35 "time",
36 "traceback",
37 "",
40 static bool inWhitelist(const std::string& name)
42 for (int i = 0; moduleWhitelist[i].size(); i++)
43 if (name == moduleWhitelist[i])
44 return true;
45 return false;
49 static void pythonIncludeModule(const char* name)
51 bp::object module(bp::handle<>(PyImport_ImportModule(name)));
52 dictMain[name] = module;
55 static void pythonSetDefaultEncoding(const char* enc)
57 if (PyUnicode_SetDefaultEncoding(enc) != 0) {
58 PyErr_Format(PyExc_SystemError,
59 "encoding %s not found", enc);
60 bp::throw_error_already_set();
65 static PyObject*
66 safeImport(PyObject*, PyObject* args, PyObject* kwds)
68 static const char* kwlist[] = {"name", "globals", "locals", "fromlist",
69 "level", 0};
70 char* _name;
71 std::string name;
72 PyObject* globals, *locals, *fromList;
73 int level = -1;
75 // Validate args from Python.
76 if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__",
77 (char**)kwlist, &_name, &globals, &locals, &fromList,
78 &level))
79 return NULL;
80 name = _name;
82 // Search whitelisted Python modules.
83 if (inWhitelist(name))
84 return PyImport_ImportModuleLevel(_name, globals, locals,
85 fromList, level);
87 Log::info("Python", "import " + name);
89 // Search Python scripts inside World.
90 std::replace(name.begin(), name.end(), '.', '/');
91 name += ".py";
92 Resourcer* rc = Resourcer::instance();
93 if (rc->resourceExists(name)) {
94 rc->runPythonScript(name);
95 return modMain.ptr(); // We have to return a module...
98 // Nothing acceptable found.
99 std::string msg = std::string("Module '") + _name + "' not found or "
100 "not allowed. Note that Tsunagari runs in a sandbox and does "
101 "not allow most external modules.";
102 PyErr_Format(PyExc_ImportError, msg.c_str());
103 return NULL;
106 static PyObject* nullExecfile(PyObject*, PyObject*)
108 PyErr_SetString(PyExc_RuntimeError,
109 "file(): Tsunagari runs scripts in a sandbox and "
110 "does not allow accessing the standard filesystem");
111 return NULL;
114 static PyObject* nullFile(PyObject*, PyObject*)
116 PyErr_SetString(PyExc_RuntimeError,
117 "file(): Tsunagari runs scripts in a sandbox and "
118 "does not allow accessing the standard filesystem");
119 return NULL;
122 static PyObject* nullOpen(PyObject*, PyObject*)
124 PyErr_SetString(PyExc_RuntimeError,
125 "open(): Tsunagari runs scripts in a sandbox and "
126 "does not allow accessing the standard filesystem");
127 return NULL;
130 static PyObject* nullPrint(PyObject*, PyObject*)
132 PyErr_SetString(PyExc_RuntimeError,
133 "print(): Tsunagari does not allow scripts to print");
134 return NULL;
137 static PyObject* nullReload(PyObject*, PyObject*)
139 PyErr_SetString(PyExc_RuntimeError,
140 "reload(): Tsunagari does not allow module reloading");
141 return NULL;
144 PyMethodDef nullMethods[] = {
145 {"__import__", (PyCFunction)safeImport, METH_VARARGS | METH_KEYWORDS, ""},
146 {"execfile", nullExecfile, METH_VARARGS, ""},
147 {"file", nullFile, METH_VARARGS, ""},
148 {"open", nullOpen, METH_VARARGS, ""},
149 {"print", nullPrint, METH_VARARGS | METH_KEYWORDS, ""},
150 {"reload", nullReload, METH_O, ""},
151 {NULL, NULL, 0, NULL},
154 bool pythonInit()
156 try {
157 PyImport_AppendInittab("tsunagari", &pythonInitBindings);
158 Py_Initialize();
159 pythonSetDefaultEncoding("utf-8");
161 modMain = bp::import("__main__");
162 dictMain = modMain.attr("__dict__");
163 modBltin = bp::import("__builtin__");
164 dictBltin = modBltin.attr("__dict__");
166 pythonIncludeModule("tsunagari");
168 // Hack in some rough safety. Disable external scripts and IO.
169 // InitModule doesn't remove existing modules, so we can use it to
170 // insert new methods into a pre-existing module.
171 PyObject* module = Py_InitModule("__builtin__", nullMethods);
172 if (module == NULL)
173 bp::throw_error_already_set();
175 // Restore the default SIGINT handler.
176 // Python messes with it. >:(
177 PyOS_setsig(SIGINT, SIG_DFL);
178 } catch (bp::error_already_set) {
179 Log::fatal("Python", "An error occured while populating the "
180 "Python modules:");
181 Log::setVerbosity(V_NORMAL); // Assure message can be seen.
182 pythonErr();
183 return false;
185 return true;
188 void pythonFinalize()
190 Py_Finalize();
193 static std::string extractException(PyObject* exc, PyObject* val, PyObject* tb)
195 using namespace boost::python;
197 handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
198 if (!hval) {
199 return extract<std::string>(str(hexc));
201 else {
202 bp::object traceback(import("traceback"));
203 bp::object format_exception(traceback.attr("format_exception"));
204 bp::object formatted_list(format_exception(hexc, hval, htb));
205 bp::object formatted(str("").join(formatted_list));
206 return extract<std::string>(formatted);
211 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
213 char* type = PyExceptionClass_Name(exc);
214 char* dot = strrchr(type, '.');
215 if (dot)
216 type = dot + 1;
217 char* value = PyString_AsString(val);
219 std::string msg = "";
220 msg += type ? type : "<unknown type>";
221 if (value) {
222 msg += ": ";
223 msg += value;
225 return msg;
229 void pythonErr()
231 // Something bad happened. Error is already set in Python.
232 PyObject* exc, *val, *tb;
233 PyErr_Fetch(&exc, &val, &tb);
235 if (!exc) {
236 Log::err("Python",
237 "pythonErr() called, but no exception set");
238 return;
241 PyErr_NormalizeException(&exc, &val, &tb);
243 if (conf.scriptHalt) {
244 Log::fatal("Python", extractException(exc, val, tb));
245 exit(1);
247 else
248 Log::err("Python", extractException(exc, val, tb));
251 bp::object pythonGlobals()
253 return dictMain;
256 PyCodeObject* pythonCompile(const char* fn, const char* code)
258 PyArena *arena = PyArena_New();
259 if (!arena) {
260 return NULL;
263 mod_ty mod = PyParser_ASTFromString(code, fn, Py_file_input, NULL, arena);
264 if (!mod) {
265 Log::err("Python",
266 std::string(fn) + ": failed to parse");
267 pythonErr();
268 return NULL;
271 PyCodeObject* co = PyAST_Compile(mod, fn, NULL, arena);
272 if (!co) {
273 Log::err("Python",
274 std::string(fn) + ": failed to compile");
275 pythonErr();
276 return NULL;
279 PyArena_Free(arena);
280 return co;
283 bool pythonExec(PyCodeObject* code)
285 if (!code)
286 return false;
288 try {
289 inPythonScript++;
291 // FIXME: locals, globals
292 PyObject* globals = dictMain.ptr();
293 PyObject* result = PyEval_EvalCode(code, globals, globals);
295 inPythonScript--;
296 if (!result)
297 pythonErr();
298 return result;
299 } catch (boost::python::error_already_set) {
300 inPythonScript--;
301 pythonErr();
302 return NULL;