/* adminop.c: Operators for administrative functions. */
#define _POSIX_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "x.tab.h"
#include "operator.h"
#include "execute.h"
#include "data.h"
#include "object.h"
#include "dump.h"
#include "io.h"
#include "log.h"
#include "cache.h"
#include "util.h"
#include "config.h"
#include "ident.h"
#include "memory.h"
#include "net.h"
#ifdef BSD_FEATURES
/* vfork() is not POSIX. */
extern pid_t vfork(void);
#endif
extern int running;
extern long heartbeat_freq;
/* All of the functions in this file are interpreter function operators, so
* they require that the interpreter data (the globals in execute.c) be in a
* state consistent with interpretation, and that a stack position has been
* pushed onto the arg_starts stack using op_start_args(). They will pop a
* value off the argument starts stack, and may affect the interpreter data by
* popping and pushing the data stack or throwing exceptions. */
void op_create(void)
{
Data *args;
List *parents;
Object *obj;
int i;
/* Accept a dbref of an object to create, and a list of parents. */
if (!func_init_2(&args, DBREF, LIST))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
return;
}
/* Check that dbref doesn't already exist. */
if (cache_check(args[0].u.dbref)) {
throw(perm_id, "Object $%I already exists.", args[0].u.dbref);
return;
}
/* Get parents list from second argument. */
if (args[1].u.sublist.span == args[1].u.sublist.list->len)
parents = list_dup(args[1].u.sublist.list);
else
parents = list_from_data(data_dptr(&args[1]), args[1].u.sublist.span);
/* Verify that all parents are dbrefs. */
for (i = 0; i < parents->len; i++) {
if (parents->el[i].type != DBREF) {
throw(type_id, "Element %d of parents list (%D) is not a dbref.",
i, &parents->el[i]);
list_discard(parents);
return;
} else if (!cache_check(parents->el[i].u.dbref)) {
throw(objnf_id, "Parent dbref %D does not refer to an object.",
&parents->el[i]);
list_discard(parents);
return;
}
}
/* Create the new object. */
obj = object_new(args[0].u.dbref, parents);
list_discard(parents);
pop(2);
push_dbref(obj->dbref);
cache_discard(obj);
}
void op_chparents(void)
{
Data *args, *d;
Object *obj;
int wrong, id;
char *reason;
/* Accept a dbref and a list of parents to change to. */
if (!func_init_2(&args, DBREF, LIST))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
return;
}
obj = cache_retrieve(args[0].u.dbref);
if (!obj) {
throw(objnf_id, "Object $%I not found.", args[0].u.dbref);
return;
}
if (cur_frame->object->dbref == root_id) {
throw(perm_id, "You can't change the root object's parents.");
} else if (!args[1].u.sublist.span) {
throw(perm_id, "You must have at least one parent.");
} else {
/* Call object_change_parents(). This will return the number of a
* parent which was invalid, or -1 if they were all okay. */
wrong = object_change_parents(obj, &args[1].u.sublist);
if (wrong >= 0) {
d = data_dptr(&args[0]) + wrong;
if (d->type != DBREF) {
id = type_id;
reason = "is not a dbref.";
} else if (d->u.dbref == cur_frame->object->dbref) {
id = parent_id;
reason = "is the same as the current object.";
} else if (!cache_check(d->u.dbref)) {
id = objnf_id;
reason = "does not exist.";
} else {
id = parent_id;
reason = "has the current object as an ancestor.";
}
throw(id, "The parent %D %s", d, reason);
} else {
pop(2);
push_int(1);
}
}
cache_discard(obj);
}
void op_destroy(void)
{
Data *args;
Object *obj;
/* Accept a dbref to destroy. */
if (!func_init_1(&args, DBREF))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
} else if (args[0].u.dbref == root_id) {
throw(perm_id, "You can't destroy the root object.");
} else if (args[0].u.dbref == sys_id) {
throw(perm_id, "You can't destroy the system object.");
} else {
obj = cache_retrieve(args[0].u.dbref);
if (!obj) {
throw(objnf_id, "Object $%I not found.", args[0].u.dbref);
return;
}
/* Set the object dead, so it will go away when nothing is holding onto
* it. cache_discard() will notice the dead flag, and call
* object_destroy(). */
obj->dead = 1;
cache_discard(obj);
pop(1);
push_int(1);
}
}
/* Effects: If called by the system object with a string argument, logs it to
* standard error using write_log(), and returns 1. */
void op_log(void)
{
Data *args;
/* Accept a string. */
if (!func_init_1(&args, STRING))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
} else {
write_log("> %S", data_sptr(&args[0]), args[0].u.substr.span);
pop(1);
push_int(1);
}
}
/* Modifies: cur_player, contents of cur_conn.
* Effects: If called by the system object with a dbref argument, assigns that
* dbref to cur_conn->dbref and to cur_player and returns 1, unless
* there is no current connection, in which case it returns 0. */
void op_conn_assign(void)
{
Data *args;
/* Accept a dbref. */
if (!func_init_1(&args, DBREF))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
} else if (cur_conn) {
ident_discard(cur_conn->dbref);
cur_conn->dbref = ident_dup(args[0].u.dbref);
pop(1);
push_int(1);
} else {
pop(1);
push_int(0);
}
}
/* Modifies: The object cache, identifier table, and binary database files via
* cache_sync() and ident_dump().
* Effects: If called by the sytem object with no arguments, performs a binary
* dump, ensuring that the files db and db.* are consistent. Returns
* 1 if the binary dump succeeds, or 0 if it fails. */
void op_binary_dump(void)
{
/* Accept no arguments. */
if (!func_init_0())
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
} else {
push_int(binary_dump());
}
}
/* Modifies: The object cache and binary database files via cache_sync() and
* two sweeps through the database. Modifies the internal dbm state
* use by dbm_firstkey() and dbm_nextkey().
* Effects: If called by the system object with no arguments, performs a text
* dump, creating a file 'textdump' which contains a representation
* of the database in terms of a few simple commands and the C--
* language. Returns 1 if the text dump succeeds, or 0 if it
* fails.*/
void op_text_dump(void)
{
/* Accept no arguments. */
if (!func_init_0())
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
} else {
push_int(text_dump());
}
}
void op_run_script(void)
{
Data *args, *base;
int num_args, i, fd, status;
pid_t pid;
char *fname, **argv;
/* Accept a name of a script to run, a list of arguments to give it, and
* an optional flag signifying that we should not wait for completion. */
if (!func_init_2_or_3(&args, &num_args, STRING, LIST, INTEGER))
return;
/* Verify that all items in argument list are strings. */
base = data_dptr(&args[1]);
for (i = 0; i < args[1].u.sublist.span; i++) {
if (base[i].type != STRING) {
throw(type_id, "Argument %d (%D) is not a string.",
i + 1, data_dptr(&args[1]) + i);
return;
}
}
/* Restrict to system object. */
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
return;
}
/* Construct the name of the script. */
fname = TMALLOC(char, args[0].u.substr.span + 9);
memcpy(fname, "scripts/", 8);
memcpy(fname + 8, data_sptr(&args[0]), args[0].u.substr.span);
fname[args[0].u.substr.span + 8] = 0;
/* Don't walking back up the directory tree. */
if (strstr(fname, "../")) {
tfree_chars(fname);
throw(perm_id, "Filename %D is not legal.", &args[0]);
return;
}
/* Build an argument list. */
argv = TMALLOC(char *, args[1].u.substr.span + 2);
argv[0] = tstrdup(fname);
for (i = 0; i < args[1].u.sublist.span; i++)
argv[1] = tstrndup(data_sptr(&base[i]), base[i].u.substr.span);
argv[args[1].u.sublist.span + 1] = NULL;
pop(num_args);
/* Fork off a process using vfork() (not POSIX). */
#ifdef BSD_FEATURES
pid = vfork();
#else
pid = fork();
#endif
if (pid == 0) {
/* Pipe stdin and stdout to /dev/null, keep stderr. */
fd = open("/dev/null", O_RDWR);
if (fd == -1) {
write_log("EXEC: Failed to open /dev/null: %s.", strerror(errno));
exit(-1);
}
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
execv(fname, argv);
write_log("EXEC: Failed to exec \"%s\": %s.", fname, strerror(errno));
exit(-1);
} else if (pid > 0) {
if (num_args == 3 && args[2].u.val) {
if (waitpid(pid, &status, WNOHANG) == 0)
status = 0;
} else {
waitpid(pid, &status, 0);
}
} else {
write_log("EXEC: Failed to vfork: %s.", strerror(errno));
status = -1;
}
push_int(status);
}
/* Modifies: The 'running' global may be set to 0.
* Effects: If called by the system object with no arguments, sets 'running'
* to 0, causing the program to exit after this iteration of the main
* loop finishes. Returns 1. */
void op_shutdown(void)
{
/* Accept no arguments. */
if (!func_init_0())
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is not the system object.",
cur_frame->object->dbref);
} else {
running = 0;
push_int(1);
}
}
void op_bind(void)
{
Data *args;
/* Accept a port to bind to, and a dbref to handle connections. */
if (!func_init_2(&args, INTEGER, DBREF))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is the system object.",
cur_frame->object->dbref);
return;
}
if (add_server(args[0].u.val, args[1].u.dbref))
push_int(1);
else if (server_failure_reason == socket_id)
throw(socket_id, "Couldn't create server socket.");
else /* (server_failure_reason == bind_id) */
throw(bind_id, "Couldn't bind to port %d.", args[0].u.val);
}
void op_unbind(void)
{
Data *args;
/* Accept a port number. */
if (!func_init_1(&args, INTEGER))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is the system object.",
cur_frame->object->dbref);
return;
}
if (!remove_server(args[0].u.val))
throw(servnf_id, "No server socket on port %d.", args[0].u.val);
else
push_int(1);
}
void op_connect(void)
{
Data *args;
long r;
if (!func_init_3(&args, STRING, INTEGER, DBREF))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is the system object.",
cur_frame->object->dbref);
return;
}
substring_truncate(&args[0].u.substr);
r = make_connection(data_sptr(&args[0]), args[1].u.val, args[2].u.dbref);
if (r == address_id)
throw(address_id, "Invalid address");
else if (r == socket_id)
throw(socket_id, "Couldn't create socket for connection");
pop(3);
push_int(1);
}
void op_set_heartbeat_freq(void)
{
Data *args;
if (!func_init_1(&args, INTEGER))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is the system object.",
cur_frame->object->dbref);
return;
}
if (args[0].u.val <= 0)
args[0].u.val = -1;
heartbeat_freq = args[0].u.val;
pop(1);
}
void op_data(void)
{
Data *args, key, value;
Object *obj;
Dict *dict;
int i;
if (!func_init_1(&args, DBREF))
return;
if (cur_frame->object->dbref != sys_id) {
throw(perm_id, "Current object ($%I) is the system object.",
cur_frame->object->dbref);
return;
}
obj = cache_retrieve(args[0].u.dbref);
if (!obj) {
throw(objnf_id, "No such object $%I", args[0].u.dbref);
return;
}
/* Construct the dictionary. */
dict = dict_new_empty();
for (i = 0; i < obj->vars.size; i++) {
if (obj->vars.tab[i].name == -1)
continue;
key.type = DBREF;
key.u.dbref = obj->vars.tab[i].class;
if (dict_find(dict, &key, &value) == keynf_id) {
value.type = DICT;
value.u.dict = dict_new_empty();
dict = dict_add(dict, &key, &value);
}
key.type = SYMBOL;
key.u.symbol = obj->vars.tab[i].name;
value.u.dict = dict_add(value.u.dict, &key, &obj->vars.tab[i].val);
key.type = DBREF;
key.u.dbref = obj->vars.tab[i].class;
dict = dict_add(dict, &key, &value);
dict_discard(value.u.dict);
}
pop(1);
push_dict(dict);
dict_discard(dict);
}