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>
13 #include <Python-ast.h>
17 #include "python-bindings.h" // for pythonInitBindings
18 #include "resourcer.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
* nullReload(PyObject
*, PyObject
*)
130 PyErr_SetString(PyExc_RuntimeError
,
131 "reload(): Tsunagari does not allow module reloading");
135 PyMethodDef nullMethods
[] = {
136 {"__import__", (PyCFunction
)safeImport
, METH_VARARGS
| METH_KEYWORDS
, ""},
137 {"execfile", nullExecfile
, METH_VARARGS
, ""},
138 {"file", nullFile
, METH_VARARGS
, ""},
139 {"open", nullOpen
, METH_VARARGS
, ""},
140 {"reload", nullReload
, METH_O
, ""},
141 {NULL
, NULL
, 0, NULL
},
147 PyImport_AppendInittab("tsunagari", &pythonInitBindings
);
149 pythonSetDefaultEncoding("utf-8");
151 modMain
= bp::import("__main__");
152 dictMain
= modMain
.attr("__dict__");
153 modBltin
= bp::import("__builtin__");
154 dictBltin
= modBltin
.attr("__dict__");
156 pythonIncludeModule("tsunagari");
158 // Hack in some rough safety. Disable external scripts and IO.
159 // InitModule doesn't remove existing modules, so we can use it to
160 // insert new methods into a pre-existing module.
161 PyObject
* module
= Py_InitModule("__builtin__", nullMethods
);
163 bp::throw_error_already_set();
165 // Restore the default SIGINT handler.
166 // Python messes with it. >:(
167 PyOS_setsig(SIGINT
, SIG_DFL
);
168 } catch (bp::error_already_set
) {
169 Log::fatal("Python", "An error occured while populating the "
171 Log::setVerbosity(V_NORMAL
); // Assure message can be seen.
178 void pythonFinalize()
183 static std::string
extractException(PyObject
* exc
, PyObject
* val
, PyObject
* tb
)
185 using namespace boost::python
;
187 handle
<> hexc(exc
), hval(allow_null(val
)), htb(allow_null(tb
));
189 return extract
<std::string
>(str(hexc
));
192 bp::object
traceback(import("traceback"));
193 bp::object
format_exception(traceback
.attr("format_exception"));
194 bp::object
formatted_list(format_exception(hexc
, hval
, htb
));
195 bp::object
formatted(str("").join(formatted_list
));
196 return extract
<std::string
>(formatted
);
201 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
203 char* type = PyExceptionClass_Name(exc);
204 char* dot = strrchr(type, '.');
207 char* value = PyString_AsString(val);
209 std::string msg = "";
210 msg += type ? type : "<unknown type>";
221 // Something bad happened. Error is already set in Python.
222 PyObject
* exc
, *val
, *tb
;
223 PyErr_Fetch(&exc
, &val
, &tb
);
227 "pythonErr() called, but no exception set");
231 PyErr_NormalizeException(&exc
, &val
, &tb
);
233 Log::err("Python", extractException(exc
, val
, tb
));
236 bp::object
pythonGlobals()
241 PyCodeObject
* pythonCompile(const char* fn
, const char* code
)
243 PyArena
*arena
= PyArena_New();
248 mod_ty mod
= PyParser_ASTFromString(code
, fn
, Py_file_input
, NULL
, arena
);
251 std::string(fn
) + ": failed to parse");
256 PyCodeObject
* co
= PyAST_Compile(mod
, fn
, NULL
, arena
);
259 std::string(fn
) + ": failed to compile");
268 bool pythonExec(PyCodeObject
* code
)
276 // FIXME: locals, globals
277 PyObject
* globals
= dictMain
.ptr();
278 PyObject
* result
= PyEval_EvalCode(code
, globals
, globals
);
284 } catch (boost::python::error_already_set
) {