/*
* Author: Thomas Wouters <thomas@xs4all.net>
*
* Copyright (c) 2001-2002 Thomas Wouters
* All rights reserved.
*
* Loosely based on Markus Stenberg's python-patch to TinyMUSH 3.0.
*/
#include "config.h"
#include "db.h"
#include "externs.h"
#include "mudconf.h"
#include "config.h"
#ifdef USE_PYTHON
#include "Python.h"
static dbref cause = -1;
static dbref who = -1;
static long last_sec;
static long last_run;
static PyObject *MakeFakeStdout(dbref i);
static PyObject *sysmod = NULL;
static PyObject *muxmod = NULL;
static PyObject *maindict = NULL;
/* 0 = not started
-1 = not functional (something severe happened)
1 = started and ready to serve
*/
static int MUXPy_State = 0;
extern void raw_notify_raw(dbref, char *, char *);
static void init_muxmodule(void);
void MUXPy_ReportError(dbref who, int clearerr)
{
/* XXX report the traceback, somehow */
if(clearerr)
PyErr_Clear();
}
void MUXPy_Abort(char *message)
{
/* Something went massively wrong. Disable the python runtime
and report the error. */
MUXPy_State = -1;
STARTLOG(LOG_PROBLEMS, "PYTHON", NULL) {
log_text("Python Runtime disabled: ");
log_text(message);
ENDLOG;
}
if(PyErr_Occurred()) {
MUXPy_ReportError(-1, 1);
}
Py_XDECREF(maindict);
Py_XDECREF(sysmod);
Py_XDECREF(muxmod);
}
void MUXPy_Init(void)
{
PyObject *sys_path, *muxpy_path, *mainmod;
if(MUXPy_State != 0)
return;
Py_SetProgramName("netmux");
Py_Initialize();
init_muxmodule();
if((mainmod = PyImport_ImportModule("__main__")) == NULL) {
MUXPy_Abort("Can't import '__main__' module");
return;
}
if((maindict = PyModule_GetDict(mainmod)) == NULL) {
MUXPy_Abort("Can't get module dict for '__main__'");
return;
}
Py_INCREF(maindict);
if((sysmod = PyImport_ImportModule("sys")) == NULL) {
MUXPy_Abort("Can't import 'sys' module");
return;
}
sys_path = PyObject_GetAttrString(sysmod, "path");
if(sys_path == NULL) {
MUXPy_Abort("Can't find sys.path to update!");
return;
}
muxpy_path = PyString_FromString("muxpy-lib");
if(muxpy_path == NULL) {
Py_DECREF(sys_path);
MUXPy_Abort("Can't create muxpy-lib path string");
return;
}
if(PyList_Append(sys_path, muxpy_path) == -1) {
Py_DECREF(sys_path);
Py_DECREF(muxpy_path);
MUXPy_Abort("Can't append muxpy-lib path string to sys.path");
return;
}
if((muxmod = PyImport_ImportModule("mux")) == NULL) {
MUXPy_Abort("Can't import 'mux' module");
return;
}
STARTLOG(LOG_PROBLEMS, "PYTHON", NULL) {
log_text("Python Runtime Started.");
ENDLOG;
}
MUXPy_State = 1;
}
#define MAX_UPDATE_PER_SEC 3
#define TICK(x) ((x) / (1000000/MAX_UPDATE_PER_SEC))
static int shouldupdate()
{
struct timeval tv;
struct timezone tz;
int tick;
if(gettimeofday(&tv, &tz) < 0)
return 0;
if(tv.tv_sec == last_sec) {
tick = TICK(tv.tv_usec);
if(tick == last_run)
return 0;
} else
tick = TICK(tv.tv_usec);
last_sec = tv.tv_sec;
last_run = tick;
return 1;
}
void runPythonHook(char *hook)
{
PyObject *hookobj, *arglist, *result;
if(MUXPy_State != 1)
return;
hookobj = PyObject_GetAttrString(muxmod, hook);
if(hookobj) {
if(PyCallable_Check(hookobj)) {
if((arglist = PyTuple_New(0)) == NULL) {
/* Can't create/reuse empty tuple -- serious error. */
MUXPy_Abort("Can't create empty tuple");
return;
}
result = PyEval_CallObject(hookobj, arglist);
/* XXX use result */
} else {
MUXPy_ReportError(-1, 1);
}
} else {
/* attr not found, possibly log it ? */
MUXPy_ReportError(-1, 1);
}
}
void updatePython(void)
{
if(!shouldupdate())
return;
runPythonHook("update");
}
void endPython(int result)
{
MUXPy_State = 0;
Py_Exit(result);
/* XXX: Insert this, instead of regular exit, to all places (ugh) */
/* NOTREACHED */
}
static PyObject *setStdout(dbref player)
{
PyObject *stdout = MakeFakeStdout(player);
if(stdout == NULL)
return NULL;
if(PySys_SetObject("stdout", stdout) == -1) {
MUXPy_ReportError(player, 1);
return NULL;
}
if(PySys_SetObject("stderr", stdout) == -1) {
MUXPy_ReportError(player, 1);
return NULL;
}
return stdout;
}
static PyObject *evalString(char *runstring)
{
/* Run eval() on the code, and print result */
PyObject *result = PyRun_String(runstring, Py_single_input,
maindict, maindict);
return result;
}
int update_whocause(void)
{
PyObject *tmp;
if((tmp = PyInt_FromLong((long) who)) == NULL)
return -1;
if(PyDict_SetItemString(maindict, "who", tmp) == -1) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
if((tmp = PyInt_FromLong((long) cause)) == NULL)
return -1;
if(PyDict_SetItemString(maindict, "cause", tmp) == -1) {
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp);
return 0;
}
void do_python(dbref cwho, dbref ccause, int key, char *target)
{
PyObject *stdout, *result;
dbref prev_cause = cause;
dbref prev_who = who;
if(MUXPy_State != 1)
return;
if(!target)
return;
if(*target == ',')
target++; /* one-letter start */
while (*target && isspace(*target))
target++;
stdout = setStdout(cwho);
if(stdout == NULL) {
PyErr_Print();
MUXPy_ReportError(who, 1);
return;
}
cause = ccause;
who = cwho;
if(cause != prev_cause || who != prev_who)
update_whocause();
result = PyRun_String(target, Py_single_input, maindict, maindict);
if(result == NULL) {
if(PyErr_Occurred()) {
PyErr_Print();
PyErr_Clear();
}
}
if(result != NULL && result != Py_None) {
PyObject *str = PyObject_Repr(result);
if(str != NULL) {
raw_notify_raw(cwho, PyString_AsString(str), "\r\n");
Py_DECREF(str);
}
}
Py_XDECREF(result);
Py_DECREF(stdout);
cause = prev_cause;
who = prev_who;
}
void fun_python(char *buff, char **bufc, dbref cwho, dbref ccause,
char *fargs[], int nfargs, char *cargs[], int ncargs)
{
char buf[LBUF_SIZE]; /* XXX: Overflow check */
PyObject *stdout, *t;
int eval = 1;
char *c, *target = fargs[0];
dbref prev_cause = cause;
dbref prev_who = who;
if(MUXPy_State != 1)
return;
if(!target)
return;
while (*target && isspace(*target))
target++;
if(!*target)
return;
stdout = setStdout(cwho);
if(stdout == NULL) {
MUXPy_ReportError(who, 1);
return;
}
who = cwho;
cause = ccause;
if(cause != prev_cause || who != prev_who)
update_whocause();
if((t = evalString(target))) {
if(t != Py_None) {
PyObject *str;
if((str = PyObject_Str(t))) {
safe_str(PyString_AsString(str), buff, bufc);
Py_DECREF(str);
} else {
MUXPy_ReportError(who, 1);
}
}
} else {
MUXPy_ReportError(who, 1);
}
Py_DECREF(stdout);
cause = prev_cause;
who = prev_who;
}
void fun_pythoncall(char *buff, char **bufc,
dbref cwho, dbref ccause,
char *fargs[], int nfargs, char *cargs[], int ncargs)
{
char buf[LBUF_SIZE];
char *myfargs[2];
char *to, c;
int i;
/* Conveniency function that converts arguments and calls fun_python() */
if(MUXPy_State != 1)
return;
if(nfargs < 1) {
safe_str("#-1 AT LEAST ONE ARGUMENT IS REQUIRED", buff, bufc);
return;
}
strcpy(buf, fargs[0]);
to = buf + strlen(buf);
*to++ = '(';
for(i = 1; i < nfargs; i++) {
char *fr = fargs[i];
if(i > 1)
*to++ = ',';
*to++ = '"';
while (*fr && (to - buf) < (LBUF_SIZE - 10)) {
c = *fr++;
if(c == '"')
c = '\'';
*to++ = c;
}
if((to - buf) >= (LBUF_SIZE - 10)) {
safe_str("#-1 ARGUMENTS TOO LONG", buff, bufc);
return;
}
*to++ = '"';
}
*to++ = ')';
*to = 0;
myfargs[0] = buf;
myfargs[1] = NULL;
fun_python(buff, bufc, cwho, ccause, myfargs, 1, cargs, ncargs);
}
/* Fake stdout, prints to player-dbref. */
staticforward PyTypeObject PyFakeStdout_Type;
typedef struct {
PyObject_HEAD;
dbref dbref;
} fakestdout;
static PyObject *fakestdout_write(fakestdout * self, PyObject * args)
{
char *str;
if(!PyArg_ParseTuple(args, "s:write", &str))
return NULL;
raw_notify_raw(self->dbref, str, NULL);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *MakeFakeStdout(dbref dbref)
{
fakestdout *mo = PyObject_NEW(fakestdout, &PyFakeStdout_Type);
if(mo == NULL)
return NULL;
mo->dbref = dbref;
return (PyObject *) mo;
}
static PyMethodDef fakestdout_methods[] = {
{"write", (PyCFunction) fakestdout_write, METH_VARARGS},
{NULL, NULL} /* Sentinel */
};
static PyObject *fakestdout_getattr(PyObject * self, char *name)
{
return Py_FindMethod(fakestdout_methods, self, name);
}
static void fakestdout_dealloc(fakestdout * self)
{
PyMem_DEL(self);
}
static PyTypeObject PyFakeStdout_Type = {
PyObject_HEAD_INIT(&PyType_Type) 0, /*ob_size */
"fakestdout", /*tp_name */
sizeof(fakestdout), /*tp_size */
0, /*tp_itemsize */
/* methods */
(destructor) fakestdout_dealloc, /*tp_dealloc */
0, /*tp_print */
(getattrfunc) fakestdout_getattr, /*tp_getattr */
0, /*tp_setattr */
0, /*tp_compare */
0, /*tp_repr */
};
/* MUXObject */
staticforward PyTypeObject PyMUXObject_Type;
typedef struct {
PyObject_HEAD;
dbref dbref;
} MUXObject;
static PyObject *MUXObject_New(dbref dbref)
{
MUXObject *mo;
mo = PyObject_NEW(MUXObject, &PyMUXObject_Type);
if(mo == NULL)
return NULL;
mo->dbref = dbref;
return (PyObject *) mo;
}
static void MUXObject_Del(MUXObject * self)
{
PyMem_DEL(self);
}
static PyObject *MUXObject_keys(MUXObject * self, PyObject * args)
{
PyObject *p;
int ca;
char *as;
ATTR *attr;
if(!PyArg_ParseTuple(args, ":keys"))
return NULL;
p = PyList_New(0);
PyList_Append(p, PyString_FromString("Dbref"));
PyList_Append(p, PyString_FromString("Location"));
for(ca = atr_head(self->dbref, &as); ca; ca = atr_next(&as)) {
attr = atr_num(ca);
PyList_Append(p, PyString_FromString(attr->name));
}
return p;
}
static PyMethodDef MUXObject_methods[] = {
{"keys", (PyCFunction) MUXObject_keys, METH_VARARGS},
{NULL, NULL} /* Sentinel */
};
static PyObject *MUXObject_GetAttr(MUXObject * self, char *name)
{
char *ptr;
int len;
ATTR *a;
char *buf;
int ao, af; /* Attribute owner, attribute flags */
PyObject *p;
PyObject *v = NULL;
if(strcasecmp(name, "location") == 0)
return PyInt_FromLong(Location(self->dbref));
if(strcasecmp(name, "dbref") == 0)
return PyInt_FromLong(self->dbref);
if(!(a = atr_str(name)))
return Py_FindMethod(MUXObject_methods, (PyObject *) self, name);
buf = alloc_lbuf("python_getattr"); /* XXX: Overflow check */
atr_get_str(buf, self->dbref, a->number, &ao, &af);
p = PyString_FromString(buf);
free_lbuf(buf);
return p;
}
static int MUXObject_SetAttr(MUXObject * self, char *name, PyObject * v)
{
ATTR *atr;
int attnum;
atr = atr_str(name);
if(v == NULL) {
/* delAttr */
if(atr) {
atr_clr(self->dbref, atr->number);
return 0;
}
PyErr_SetString(PyExc_AttributeError, "Nonexistent attribute");
return -1;
}
if(!PyString_Check(v)) {
PyErr_SetString(PyExc_ValueError,
"Invalid value: only string accepted");
return -1;
}
attnum = atr ? atr->number : mkattr(name);
atr_add_raw(self->dbref, attnum, PyString_AsString(v));
return 0;
}
/* Functions in MUX module */
static PyObject *muxc_getobject(PyObject * self, PyObject * args)
{
dbref dbref;
if(!PyArg_ParseTuple(args, "i:getobject", &dbref))
return NULL;
return MUXObject_New(dbref);
}
static PyObject *mux_eval(dbref dbref, char *str)
{
char *buf = alloc_lbuf("objeval");
char *endMarker = buf;
PyObject *rv;
exec(buf, &endMarker, 0, dbref, cause,
EV_FCHECK | EV_STRIP | EV_EVAL, &str, NULL, 0);
*endMarker = 0;
if(*buf)
rv = PyString_FromString(buf);
else {
Py_INCREF(Py_None);
rv = Py_None;
}
free_lbuf(buf);
return rv;
}
static PyObject *muxc_muxeval(PyObject * self, PyObject * args)
{
dbref dbref;
char *str;
if(!PyArg_ParseTuple(args, "is:muxeval", &dbref, &str))
return NULL;
return mux_eval(dbref, str);
}
static PyObject *muxc_notify(PyObject * self, PyObject * args)
{
dbref dbref;
char *str;
if(!PyArg_ParseTuple(args, "is:notify", &dbref, &str))
return NULL;
notify(dbref, str);
Py_INCREF(Py_None);
return Py_None;
}
/* Initialization */ static PyTypeObject PyMUXObject_Type = {
PyObject_HEAD_INIT(&PyType_Type) 0, /*ob_size */
"MUXobject", /*tp_name */
sizeof(MUXObject), /*tp_basicsize */
0, /*tp_itemsize */
/* methods */
(destructor) MUXObject_Del, /*tp_dealloc */
0, /*tp_print */
(getattrfunc) MUXObject_GetAttr, /*tp_getattr */
(setattrfunc) MUXObject_SetAttr, /*tp_setattr */
0, /*tp_compare */
0, /*tp_repr */
0, /*tp_hash (=as_number) */
};
static PyMethodDef MUXMethods[] = {
{"getobject", muxc_getobject, METH_VARARGS},
{"muxeval", muxc_muxeval, METH_VARARGS},
{"notify", muxc_notify, METH_VARARGS},
{NULL, NULL} /* Sentinel */
};
static void init_muxmodule()
{
PyImport_AddModule("muxc");
Py_InitModule("muxc", MUXMethods);
}
#endif /* USE_PYTHON */