/* // 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_