1 /*********************************
2 ** Tsunagari Tile Engine **
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>
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
[] = {
38 static bool inWhitelist(const std::string
& name
)
40 for (int i
= 0; moduleWhitelist
[i
].size(); i
++)
41 if (name
== moduleWhitelist
[i
])
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();
64 safeImport(PyObject
*, PyObject
* args
, PyObject
* kwds
)
66 static const char* kwlist
[] = {"name", "globals", "locals", "fromlist",
70 PyObject
* globals
, *locals
, *fromList
;
73 // Validate args from Python.
74 if (!PyArg_ParseTupleAndKeywords(args
, kwds
, "s|OOOi:__import__",
75 (char**)kwlist
, &_name
, &globals
, &locals
, &fromList
,
80 // Search whitelisted Python modules.
81 if (inWhitelist(name
))
82 return PyImport_ImportModuleLevel(_name
, globals
, locals
,
85 Log::info("Python", "import " + name
);
87 // Search Python scripts inside World.
88 std::replace(name
.begin(), name
.end(), '.', '/');
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());
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");
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");
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");
128 static PyObject
* nullPrint(PyObject
*, PyObject
*)
130 PyErr_SetString(PyExc_RuntimeError
,
131 "print(): Tsunagari does not allow scripts to print");
135 static PyObject
* nullReload(PyObject
*, PyObject
*)
137 PyErr_SetString(PyExc_RuntimeError
,
138 "reload(): Tsunagari does not allow module reloading");
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
},
155 PyImport_AppendInittab("tsunagari", &pythonInitBindings
);
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
);
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 "
179 Log::setVerbosity(V_NORMAL
); // Assure message can be seen.
186 void pythonFinalize()
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
));
197 return extract
<std::string
>(str(hexc
));
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, '.');
215 char* value = PyString_AsString(val);
217 std::string msg = "";
218 msg += type ? type : "<unknown type>";
229 // Something bad happened. Error is already set in Python.
230 PyObject
* exc
, *val
, *tb
;
231 PyErr_Fetch(&exc
, &val
, &tb
);
235 "pythonErr() called, but no exception set");
239 PyErr_NormalizeException(&exc
, &val
, &tb
);
241 Log::err("Python", extractException(exc
, val
, tb
));
244 bp::object
pythonGlobals()
249 PyCodeObject
* pythonCompile(const char* fn
, const char* code
)
251 PyArena
*arena
= PyArena_New();
256 mod_ty mod
= PyParser_ASTFromString(code
, fn
, Py_file_input
, NULL
, arena
);
259 std::string(fn
) + ": failed to parse");
264 PyCodeObject
* co
= PyAST_Compile(mod
, fn
, NULL
, arena
);
267 std::string(fn
) + ": failed to compile");
276 bool pythonExec(PyCodeObject
* code
)
284 // FIXME: locals, globals
285 PyObject
* globals
= dictMain
.ptr();
286 PyObject
* result
= PyEval_EvalCode(code
, globals
, globals
);
292 } catch (boost::python::error_already_set
) {