/*
// ColdMUD was created and is copyright 1993, 1994 by Greg Hudson
//
// ColdX is a derivitive work, and is copyright 1995 by the ColdX Project.
// Full copyright information can be found in the file doc/CREDITS
//
// File:        sysop.c
// Version:     0.1-5
// Last Edited: 2 Aug 1995
//
// ---
//
// Operators for administrative and system functions
//
// 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.
*/

#define _sysop_

#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>          /* getrusage()  25-Jan-95 BJG */
#include <sys/resource.h>      /* getrusage()  25-Jan-95 BJG */
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "defs.h"
#include "x.tab.h"
#include "operator.h"
#include "opcodes.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 "ident.h"
#include "memory.h"
#include "net.h"
#include "lookup.h"
#include "main.h"             /* load variables */

/* ----------------------------------------------------------------- */
/* header information */

#ifdef USE_VFORK
extern pid_t vfork(void);
#endif

extern char *sys_errlist[];
#define strerror(n) (sys_errlist[n])

#ifdef ULTRIX
extern void getrusage(int who, struct rusage *rusage);
#endif

/* ----------------------------------------------------------------- */
void op_create(void) {
    Data *args, *d;
    List *parents;
    Object *obj;

    /* Accept a list of parents. */
    if (!func_init_1(&args, LIST))
	return;

    /* Get parents list from second argument. */
    parents = args[0].u.list;

    /* Verify that all parents are dbrefs. */
    for (d = list_first(parents); d; d = list_next(parents, d)) {
	if (d->type != DBREF) {
	    cthrow(type_id, "Parent %D is not a dbref.", d);
	    return;
	} else if (!cache_check(d->u.dbref)) {
	    cthrow(objnf_id, "Parent %D does not refer to an object.", d);
	    return;
	}
    }

    /* Create the new object. */
    obj = object_new(-1, parents);

    pop(1);
    push_dbref(obj->dbref);
    cache_discard(obj);
}

/* ----------------------------------------------------------------- */
void op_chparents(void) {
    Data *args, *d;
    Object *obj;
    int wrong;

    /* Accept a dbref and a list of parents to change to. */
    if (!func_init_2(&args, DBREF, LIST))
	return;

    if (args[0].u.dbref == ROOT_DBREF) {
	cthrow(perm_id, "You cannot change the root object's parents.");
	return;
    }

    obj = cache_retrieve(args[0].u.dbref);
    if (!obj) {
	cthrow(objnf_id, "Object #%l not found.", args[0].u.dbref);
	return;
    }

    if (!list_length(args[1].u.list)) {
	cthrow(perm_id, "You must specify at least one parent.");
	return;
    }

    /* 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.list);
    if (wrong >= 0) {
	d = list_elem(args[1].u.list, wrong);
	if (d->type != DBREF) {
	    cthrow(type_id, "New parent %D is not a dbref.", d);
	} else if (d->u.dbref == args[0].u.dbref) {
	    cthrow(parent_id, "New parent %D is the same as %D.", d, &args[0]);
	} else if (!cache_check(d->u.dbref)) {
	    cthrow(objnf_id, "New parent %D does not exist.", d);
	} else {
	    cthrow(parent_id, "New parent %D is a descendent of %D.", d,
		  &args[0]);
	}
    } 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 (args[0].u.dbref == ROOT_DBREF) {
	cthrow(perm_id, "You can't destroy the root object.");
    } else if (args[0].u.dbref == SYSTEM_DBREF) {
	cthrow(perm_id, "You can't destroy the system object.");
    } else {
	obj = cache_retrieve(args[0].u.dbref);
	if (!obj) {
	    cthrow(objnf_id, "Object #%l 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;

    write_log("%S", args[0].u.str);
    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_reassign_connection(void) {
    Data *args;

    /* Accept a dbref. */
    if (!func_init_1(&args, DBREF))
	return;

    if (cur_conn) {
	cur_conn->dbref = 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;

    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 ColdC 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;

    push_int(text_dump());
}

/* ----------------------------------------------------------------- */
/* run an executable from the filesystem                             */
void op_execute(void) {
    Data *args, *d;
    List *script_args;
    int num_args, argc, len, 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;

    script_args = args[1].u.list;

    /* Verify that all items in argument list are strings. */
    for (d = list_first(script_args), i=0;
         d;
         d = list_next(script_args, d), i++) {
	if (d->type != STRING) {
	    cthrow(type_id,
                   "Execute argument %d (%D) is not a string.",
                   i+1, d);
	    return;
	}
    }

    /* Don't allow walking back up the directory tree. */
    if (strstr(string_chars(args[0].u.str), "../")) {
	cthrow(perm_id, "Filename %D is not legal.", &args[0]);
	return;
    }

    /* Construct the name of the script. */
    len = string_length(args[0].u.str);
    fname = TMALLOC(char, len + 9);
    memcpy(fname, "scripts/", 8);
    memcpy(fname + 8, string_chars(args[0].u.str), len);
    fname[len + 8] = 0;

    /* Build an argument list. */
    argc = list_length(script_args) + 1;
    argv = TMALLOC(char *, argc + 1);
    argv[0] = tstrdup(fname);
    for (d = list_first(script_args), i = 0;
         d;
         d = list_next(script_args, d), i++)
	argv[i + 1] = tstrdup(string_chars(d->u.str));
    argv[argc] = NULL;

    pop(num_args);

    /* Fork off a process. */
#ifdef USE_VFORK
    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_err("EXEC: Failed to open /dev/null: %s.", strerror(errno));
	    exit(-1);
	}
	dup2(fd, STDIN_FILENO);
	dup2(fd, STDOUT_FILENO);
	execv(fname, argv);
	write_err("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_err("EXEC: Failed to fork: %s.", strerror(errno));
	status = -1;
    }

    /* Free the argument list. */
    for (i = 0; i < argc; i++)
	tfree_chars(argv[i]);
    TFREE(argv, argc + 1);

    push_int(status);
}

/* ----------------------------------------------------------------- */
/* Modifies: The 'running' global (main.h) 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;

    running = 0;
    push_int(1);
}

/* ----------------------------------------------------------------- */
void op_bind_port(void) {
    Data *args;

    /* Accept a port to bind to, and a dbref to handle connections. */
    if (!func_init_2(&args, INTEGER, DBREF))
	return;

    if (add_server(args[0].u.val, args[1].u.dbref))
	push_int(1);
    else if (server_failure_reason == socket_id)
	cthrow(socket_id, "Couldn't create server socket.");
    else /* (server_failure_reason == bind_id) */
	cthrow(bind_id, "Couldn't bind to port %d.", args[0].u.val);
}

/* ----------------------------------------------------------------- */
void op_unbind_port(void) {
    Data *args;

    /* Accept a port number. */
    if (!func_init_1(&args, INTEGER))
	return;

    if (!remove_server(args[0].u.val))
	cthrow(servnf_id, "No server socket on port %d.", args[0].u.val);
    else
	push_int(1);
}

/* ----------------------------------------------------------------- */
void op_open_connection(void) {
    Data *args;
    char *address;
    int port;
    Dbref receiver;
    long r;

    if (!func_init_3(&args, STRING, INTEGER, DBREF))
	return;

    address = string_chars(args[0].u.str);
    port = args[1].u.val;
    receiver = args[2].u.dbref;

    r = make_connection(address, port, receiver);
    if (r == address_id)
	cthrow(address_id, "Invalid address");
    else if (r == socket_id)
	cthrow(socket_id, "Couldn't create socket for connection");
    pop(3);
    push_int(1);
}

/* ----------------------------------------------------------------- */
/* Modifies: heartbeat_freq (main.h)                                 */

void op_set_heartbeat(void) {
    Data *args;

    if (!func_init_1(&args, INTEGER))
	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;

    obj = cache_retrieve(args[0].u.dbref);
    if (!obj) {
	cthrow(objnf_id, "No such object #%l", 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].cclass;
	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].cclass;
	dict = dict_add(dict, &key, &value);
	dict_discard(value.u.dict);
    }

    cache_discard(obj);
    pop(1);
    push_dict(dict);
    dict_discard(dict);
}

/* ----------------------------------------------------------------- */
void op_add_objname(void) {
    Data *args;
    int result;

    if (!func_init_2(&args, SYMBOL, DBREF))
	return;

    result = lookup_store_name(args[0].u.symbol, args[1].u.dbref);
    pop(2);
    push_int(result);
}

/* ----------------------------------------------------------------- */
void op_del_objname(void) {
    Data *args;

    if (!func_init_1(&args, SYMBOL))
	return;

    if (!lookup_remove_name(args[0].u.symbol)) {
	cthrow(namenf_id, "Can't find object name %I.", args[0].u.symbol);
	return;
    }

    pop(1);
    push_int(1);
}

/* ----------------------------------------------------------------- */
/* cancel a suspended task                                           */
void op_cancel(void) {
    Data *args;
  
    if (!func_init_1(&args, INTEGER))
        return;
  

    if (!task_lookup(args[0].u.val)) {
        cthrow(type_id, "No such task");
    } else {
        task_cancel(args[0].u.val);
        pop(1);
        push_int(1);
    }
}

/* ----------------------------------------------------------------- */
/* suspend a task                                                    */
void op_suspend(void) {

    if (!func_init_0())
        return;

    task_suspend();

    /* we'll let task_resume push something onto the stack for us */
}

/* ----------------------------------------------------------------- */
void op_resume(void) {
    Data *args;
    int nargs;
    long tid;
  
    if (!func_init_1_or_2(&args, &nargs, INTEGER, 0))
        return;

    tid = args[0].u.val;

    if (!task_lookup(tid)) {
        cthrow(type_id, "No such task");
    } else {
        if (nargs == 1) 
            task_resume(tid, NULL);
        else
            task_resume(tid, &args[1]);
        pop(nargs);
        push_int(0);
    }
}

/* ----------------------------------------------------------------- */
void op_pause(void) {

    if (!func_init_0())
        return;

    push_int(0);

    task_pause();
}

/* ----------------------------------------------------------------- */
void op_tasks(void) {

    if (!func_init_0())
        return;

    push_list(task_list());
}

/* ----------------------------------------------------------------- */
/* Modifies: db_top (object.h)                                       */
void op_next_dbref(void) {

    if (!func_init_0())
	return;

    push_dbref(db_top);
}

/* ----------------------------------------------------------------- */
void op_tick(void) {
    if (!func_init_0())
        return;
    push_int(tick);
}

/* ----------------------------------------------------------------- */
void op_hostname(void) {
    Data *args;
    String *r;

    /* Accept a port number. */
    if (!func_init_1(&args, STRING))
        return;

    r = hostname(args[0].u.str->s);

    pop(1);
    push_string(r);
}

/* ----------------------------------------------------------------- */
void op_ip(void) {
    Data *args;
    String *r;

    /* Accept a hostname. */
    if (!func_init_1(&args, STRING))
        return;

    r = ip(args[0].u.str->s);

    pop(1);
    push_string(r);
}

/* ----------------------------------------------------------------- */
void op_callers(void) {

    if (!func_init_0())
        return;

    push_list(task_callers());
}

/* ----------------------------------------------------------------- */
/* added 20-Jan-95 (BJG) */
void op_load(void) {
#if 1
    String *str;
#ifdef CHECK_LOAD
    FILE   *fp, *popen(const char *, const char *);
    char   *s;
    char    buf[99];
    int     x;

    if (!func_init_0())
        return;

#ifdef SYSV
    fp = popen("/usr/bin/uptime", "r");
#else
    fp = popen("/usr/ucb/uptime", "r");
#endif

    if (fp == NULL)
          return;

    fgets(buf, 99, fp);

    pclose(fp);

    s = strrchr(buf, ':');

    /* this bumps the ':' and subsequent ' ' out of the string */
    for (x=0; x < 2; x++, s++) {
        if (s == NULL)
            return;
    }
    s[strlen(s) - 1] = '\0';

    str = string_from_chars(s, strlen(s));
#else
    if (!func_init_0())
        return;

    str = string_from_chars("", 0);
#endif
    push_string(str);
    string_discard(str);
#else
    List *load;
    Data *d;

    if (!func_init_0())
        return;

    load = list_new(3);
    d = list_empty_spaces(load, 3);
    d[0].type = d[1].type = d[2].type = INTEGER;
    d[0].u.val = (int) load_1 * 100;
    d[1].u.val = (int) load_5 * 100;
    d[2].u.val = (int) load_15 * 100;
    push_list(load);
    list_discard(load);
#endif
}

/* ----------------------------------------------------------------- */
/* added 26-Jan-95 (BJG) */
void op_status(void) {
#ifdef USE_GETRUSAGE
    struct rusage r;
#endif
    List *status;
    Data *d;
    int x;

    if (!func_init_0())
        return;

#ifdef USE_GETRUSAGE
    status = list_new(19);
    d = list_empty_spaces(status, 19);
    for (x=0; x < 19; x++)
        d[x].type = INTEGER;

    getrusage(RUSAGE_SELF, &r);
    d[0].u.val = (int) r.ru_utime.tv_sec; /* user time used (seconds) */
    d[1].u.val = (int) r.ru_utime.tv_usec; /* user time used (microseconds) */
    d[2].u.val = (int) r.ru_stime.tv_sec; /* system time used (seconds) */
    d[3].u.val = (int) r.ru_stime.tv_usec;/* system time used (microseconds) */
    d[4].u.val = (int) r.ru_maxrss;
    d[5].u.val = (int) r.ru_ixrss;       /* integral shared text size */
    d[7].u.val = (int) r.ru_idrss;       /* integral unshared data size */
    d[8].u.val = (int) r.ru_isrss;       /* integral unshared stack size */
    d[9].u.val = (int) r.ru_minflt;      /* page reclaims */
    d[10].u.val = (int) r.ru_majflt;      /* page faults */
    d[11].u.val = (int) r.ru_nswap;       /* swaps */
    d[12].u.val = (int) r.ru_inblock;     /* block input operations */
    d[13].u.val = (int) r.ru_oublock;     /* block output operations */
    d[14].u.val = (int) r.ru_msgsnd;      /* messages sent */
    d[15].u.val = (int) r.ru_msgrcv;      /* messages received */
    d[16].u.val = (int) r.ru_nsignals;    /* signals received */
    d[17].u.val = (int) r.ru_nvcsw;       /* voluntary context switches */
    d[18].u.val = (int) r.ru_nivcsw;      /* involuntary context switches */
#else
    status = list_new(0);
#endif
    push_list(status);
    list_discard(status);
}

void op_bind_function(void) {
    Data * args;
    int    opcode;

    /* accept a symbol and dbref */
    if (!func_init_2(&args, STRING, DBREF))
        return;

    opcode = find_function(string_chars(args[0].u.str));

    if (opcode != -1)
        op_table[opcode].binding = args[1].u.dbref;
    else
        cthrow(perm_id, "Attempt to bind function which does not exist.");
    pop(2);
    push_int(1);
}

void op_unbind_function(void) {
    Data *args;
    int   opcode;

    /* accept a symbol */
    if (!func_init_1(&args, SYMBOL))
        return;

    opcode = find_function(ident_name(args[0].u.symbol));

    if (opcode != -1)
        op_table[opcode].binding = INV_OBJNUM;
    else
        cthrow(perm_id, "Attempt to unbind function which does not exist.");

    pop(1);
    push_int(1);
}

#undef _sysop_