Add documentation on placing tiles.
[Tsunagari.git] / src / python.cpp
blob2e1a098f3ac4dc3b12d0e44b1e892aed4f7253af
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** python.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 // **********
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // **********
27 #include <algorithm> // for std::replace
28 #include <signal.h> // for SIGINT and SIG_DFL
29 #include <string.h> // for strrchr
31 #include <boost/python.hpp>
33 #include "client-conf.h"
34 #include "log.h"
35 #include "python.h"
36 #include "python-bindings.h" // for pythonInitBindings
37 #include "resourcer.h"
38 #include "window.h"
40 #include <Python-ast.h>
43 namespace bp = boost::python;
45 static bp::object modMain, modBltin;
46 static bp::object dictMain, dictBltin;
48 int inPythonScript = 0;
50 //! List of known safe Python modules allowed for importing.
51 static std::string moduleWhitelist[] = {
52 "__builtin__",
53 "__main__",
54 "math",
55 "time",
56 "traceback",
57 "",
60 static bool inWhitelist(const std::string& name)
62 for (int i = 0; moduleWhitelist[i].size(); i++)
63 if (name == moduleWhitelist[i])
64 return true;
65 return false;
69 static void pythonIncludeModule(const char* name)
71 bp::object module(bp::handle<>(PyImport_ImportModule(name)));
72 dictMain[name] = module;
75 static void pythonSetDefaultEncoding(const char* enc)
77 if (PyUnicode_SetDefaultEncoding(enc) != 0) {
78 PyErr_Format(PyExc_SystemError,
79 "encoding %s not found", enc);
80 bp::throw_error_already_set();
85 static PyObject*
86 safeImport(PyObject*, PyObject* args, PyObject* kwds)
88 static const char* kwlist[] = {"name", "globals", "locals", "fromlist",
89 "level", 0};
90 char* _name;
91 std::string name;
92 PyObject* globals, *locals, *fromList;
93 int level = -1;
95 // Validate args from Python.
96 if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__",
97 (char**)kwlist, &_name, &globals, &locals, &fromList,
98 &level))
99 return NULL;
100 name = _name;
102 // Search whitelisted Python modules.
103 if (inWhitelist(name))
104 return PyImport_ImportModuleLevel(_name, globals, locals,
105 fromList, level);
107 Log::info("Python", "import " + name);
109 // Search Python scripts inside World.
110 std::replace(name.begin(), name.end(), '.', '/');
111 name += ".py";
112 Resourcer* rc = Resourcer::instance();
113 if (rc->resourceExists(name)) {
114 rc->runPythonScript(name);
115 return modMain.ptr(); // We have to return a module...
118 // Nothing acceptable found.
119 std::string msg = std::string("Module '") + _name + "' not found or "
120 "not allowed. Note that Tsunagari runs in a sandbox and does "
121 "not allow most external modules.";
122 PyErr_Format(PyExc_ImportError, msg.c_str());
123 return NULL;
126 static PyObject* nullExecfile(PyObject*, PyObject*)
128 PyErr_SetString(PyExc_RuntimeError,
129 "file(): Tsunagari runs scripts in a sandbox and "
130 "does not allow accessing the standard filesystem");
131 return NULL;
134 static PyObject* nullFile(PyObject*, PyObject*)
136 PyErr_SetString(PyExc_RuntimeError,
137 "file(): Tsunagari runs scripts in a sandbox and "
138 "does not allow accessing the standard filesystem");
139 return NULL;
142 static PyObject* nullOpen(PyObject*, PyObject*)
144 PyErr_SetString(PyExc_RuntimeError,
145 "open(): Tsunagari runs scripts in a sandbox and "
146 "does not allow accessing the standard filesystem");
147 return NULL;
150 static PyObject* nullPrint(PyObject*, PyObject*)
152 PyErr_SetString(PyExc_RuntimeError,
153 "print(): Tsunagari does not allow scripts to print");
154 return NULL;
157 static PyObject* nullReload(PyObject*, PyObject*)
159 PyErr_SetString(PyExc_RuntimeError,
160 "reload(): Tsunagari does not allow module reloading");
161 return NULL;
164 PyMethodDef nullMethods[] = {
165 {"__import__", (PyCFunction)safeImport, METH_VARARGS | METH_KEYWORDS, ""},
166 {"execfile", nullExecfile, METH_VARARGS, ""},
167 {"file", nullFile, METH_VARARGS, ""},
168 {"open", nullOpen, METH_VARARGS, ""},
169 {"print", nullPrint, METH_VARARGS | METH_KEYWORDS, ""},
170 {"reload", nullReload, METH_O, ""},
171 {NULL, NULL, 0, NULL},
174 bool pythonInit()
176 try {
177 PyImport_AppendInittab("tsunagari", &pythonInitBindings);
178 Py_Initialize();
179 pythonSetDefaultEncoding("utf-8");
181 modMain = bp::import("__main__");
182 dictMain = modMain.attr("__dict__");
183 modBltin = bp::import("__builtin__");
184 dictBltin = modBltin.attr("__dict__");
186 pythonIncludeModule("tsunagari");
188 // Hack in some rough safety. Disable external scripts and IO.
189 // InitModule doesn't remove existing modules, so we can use it to
190 // insert new methods into a pre-existing module.
191 PyObject* module = Py_InitModule("__builtin__", nullMethods);
192 if (module == NULL)
193 bp::throw_error_already_set();
195 // Restore the default SIGINT handler.
196 // Python messes with it. >:(
197 PyOS_setsig(SIGINT, SIG_DFL);
198 } catch (bp::error_already_set) {
199 Log::fatal("Python", "An error occured while populating the "
200 "Python modules:");
201 Log::setVerbosity(V_NORMAL); // Assure message can be seen.
202 pythonErr();
203 return false;
205 return true;
208 void pythonFinalize()
210 Py_Finalize();
213 static std::string extractException(PyObject* exc, PyObject* val, PyObject* tb)
215 using namespace boost::python;
217 handle<> hexc(exc), hval(allow_null(val)), htb(allow_null(tb));
218 if (!hval) {
219 return extract<std::string>(str(hexc));
221 else {
222 bp::object traceback(import("traceback"));
223 bp::object format_exception(traceback.attr("format_exception"));
224 bp::object formatted_list(format_exception(hexc, hval, htb));
225 bp::object formatted(str("").join(formatted_list));
226 return extract<std::string>(formatted);
231 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
233 char* type = PyExceptionClass_Name(exc);
234 char* dot = strrchr(type, '.');
235 if (dot)
236 type = dot + 1;
237 char* value = PyString_AsString(val);
239 std::string msg = "";
240 msg += type ? type : "<unknown type>";
241 if (value) {
242 msg += ": ";
243 msg += value;
245 return msg;
249 void pythonErr()
251 // Something bad happened. Error is already set in Python.
252 PyObject* exc, *val, *tb;
253 PyErr_Fetch(&exc, &val, &tb);
255 if (!exc) {
256 Log::err("Python",
257 "pythonErr() called, but no exception set");
258 return;
261 PyErr_NormalizeException(&exc, &val, &tb);
263 if (conf.halting == HALT_SCRIPT) {
264 Log::fatal("Python", extractException(exc, val, tb));
265 exit(1);
267 else
268 Log::err("Python", extractException(exc, val, tb));
271 bp::object pythonGlobals()
273 return dictMain;
276 PyCodeObject* pythonCompile(const char* fn, const char* code)
278 PyArena *arena = PyArena_New();
279 if (!arena) {
280 return NULL;
283 mod_ty mod = PyParser_ASTFromString(code, fn, Py_file_input, NULL, arena);
284 if (!mod) {
285 Log::err("Python",
286 std::string(fn) + ": failed to parse");
287 pythonErr();
288 return NULL;
291 PyCodeObject* co = PyAST_Compile(mod, fn, NULL, arena);
292 if (!co) {
293 Log::err("Python",
294 std::string(fn) + ": failed to compile");
295 pythonErr();
296 return NULL;
299 PyArena_Free(arena);
300 return co;
303 bool pythonExec(PyCodeObject* code)
305 if (!code)
306 return false;
308 try {
309 inPythonScript++;
311 // FIXME: locals, globals
312 PyObject* globals = dictMain.ptr();
313 PyObject* result = PyEval_EvalCode(code, globals, globals);
315 inPythonScript--;
316 if (!result)
317 pythonErr();
318 return result;
319 } catch (boost::python::error_already_set) {
320 inPythonScript--;
321 pythonErr();
322 return NULL;