/**
* \file cque.c
*
* \brief Queue for PennMUSH.
*
*
*/
#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#include "conf.h"
#include "command.h"
#include "mushdb.h"
#include "match.h"
#include "externs.h"
#include "parse.h"
#include "strtree.h"
#include "mymalloc.h"
#include "game.h"
#include "attrib.h"
#include "flags.h"
#include "dbdefs.h"
#include "log.h"
#include "confmagic.h"
EVAL_CONTEXT global_eval_context;
/** A queue entry.
* This structure reprsents a queue entry on a linked list of queue
* entries (a queue). It is used for all of the queues.
*/
typedef struct bque {
struct bque *next; /**< pointer to next entry on queue */
dbref player; /**< player who will do command */
dbref queued; /**< object whose QUEUE gets incremented for this command */
dbref cause; /**< player causing command (for %N) */
dbref sem; /**< semaphore object to block on */
char *semattr; /**< semaphore attribute to block on */
int left; /**< seconds left until execution */
char *env[10]; /**< environment, from wild match */
char *rval[NUMQ]; /**< environment, from setq() */
char *comm; /**< command to be executed */
} BQUE;
static BQUE *qfirst = NULL, *qlast = NULL, *qwait = NULL;
static BQUE *qlfirst = NULL, *qllast = NULL;
static BQUE *qsemfirst = NULL, *qsemlast = NULL;
static int add_to_generic(dbref player, int am, const char *name, int flags);
static int add_to(dbref player, int am);
static int add_to_sem(dbref player, int am, const char *name);
static int queue_limit(dbref player);
void free_qentry(BQUE *point);
static int pay_queue(dbref player, const char *command);
void wait_que(dbref player, int wait, char *command,
dbref cause, dbref sem, const char *semattr, int until);
int que_next(void);
static void show_queue(dbref player, dbref victim, int q_type,
int q_quiet, int q_all, BQUE *q_ptr, int *tot, int *self,
int *del);
static void do_raw_restart(dbref victim);
static int waitable_attr(dbref thing, const char *atr);
static void shutdown_a_queue(BQUE **head, BQUE **tail);
extern sig_atomic_t cpu_time_limit_hit; /**< Have we used too much CPU? */
/** Attribute flags to be set or checked on attributes to be used
* as semaphores.
*/
#define SEMAPHORE_FLAGS (AF_LOCKED | AF_PRIVATE | AF_NOCOPY | AF_NODUMP)
/* Returns true if the attribute on thing can be used as a semaphore.
* atr should be given in UPPERCASE.
*/
static int
waitable_attr(dbref thing, const char *atr)
{
ATTR *a;
if (!atr || !*atr)
return 0;
a = atr_get_noparent(thing, atr);
if (!a) { /* Attribute isn't set */
a = atr_match(atr);
if (!a) /* It's not a built in attribute */
return 1;
return !strcmp(AL_NAME(a), "SEMAPHORE"); /* Only allow SEMAPHORE for now */
} else { /* Attribute is set. Check for proper owner and flags and value */
if ((AL_CREATOR(a) == GOD) && (AL_FLAGS(a) == SEMAPHORE_FLAGS)) {
char *v = atr_value(a);
if (!*v || is_integer(v))
return 1;
else
return 0;
} else {
return 0;
}
}
return 0; /* Not reached */
}
static int
add_to_generic(dbref player, int am, const char *name, int flags)
{
int num = 0;
ATTR *a;
char buff[MAX_COMMAND_LEN];
a = atr_get_noparent(player, name);
if (a)
num = parse_integer(atr_value(a));
num += am;
if (num) {
sprintf(buff, "%d", num);
(void) atr_add(player, name, buff, GOD, flags);
} else {
(void) atr_clr(player, name, GOD);
}
return (num);
}
static int
add_to(dbref player, int am)
{
return (add_to_generic(player, am, "QUEUE", NOTHING));
}
static int
add_to_sem(dbref player, int am, const char *name)
{
return (add_to_generic
(player, am, name ? name : "SEMAPHORE", SEMAPHORE_FLAGS));
}
static int
queue_limit(dbref player)
{
/* returns 1 if player has exceeded his queue limit, and always
increments QUEUE by one. */
int nlimit;
nlimit = add_to(player, 1);
if (HugeQueue(player))
return nlimit > (QUEUE_QUOTA + db_top);
else
return nlimit > QUEUE_QUOTA;
}
/** Free a queue entry.
* \param point queue entry to free.
*/
void
free_qentry(BQUE *point)
{
int a;
for (a = 0; a < 10; a++)
if (point->env[a]) {
mush_free((Malloc_t) point->env[a], "bqueue_env");
}
for (a = 0; a < NUMQ; a++)
if (point->rval[a]) {
mush_free((Malloc_t) point->rval[a], "bqueue_rval");
}
if (point->semattr)
mush_free((Malloc_t) point->semattr, "bqueue_semattr");
if (point->comm)
mush_free((Malloc_t) point->comm, "bqueue_comm");
mush_free((Malloc_t) point, "BQUE");
}
static int
pay_queue(dbref player, const char *command)
{
int estcost;
estcost =
QUEUE_COST +
(QUEUE_LOSS ? ((get_random_long(0, QUEUE_LOSS - 1) == 0) ? 1 : 0) : 0);
if (!payfor(player, estcost)) {
notify(Owner(player), T("Not enough money to queue command."));
return 0;
}
if (queue_limit(QUEUE_PER_OWNER ? Owner(player) : player)) {
notify_format(Owner(player),
T("Runaway object: %s(%s). Commands halted."),
Name(player), unparse_dbref(player));
do_log(LT_TRACE, player, player, T("Runaway object %s executing: %s"),
unparse_dbref(player), command);
/* Refund the queue costs */
giveto(player, QUEUE_COST);
add_to(QUEUE_PER_OWNER ? Owner(player) : player, -1);
/* wipe out that object's queue and set it HALT */
do_halt(Owner(player), "", player);
set_flag_internal(player, "HALT");
return 0;
}
return 1;
}
/** Add a new entry onto the player or object command queues.
* This function adds a new entry to the back of the player or
* object command queues (depending on whether the call was
* caused by a player or an object).
* \param player the enactor for the queued command.
* \param command the command to enqueue.
* \param cause the player or object causing the command to be queued.
*/
void
parse_que(dbref player, const char *command, dbref cause)
{
int a;
BQUE *tmp;
if (!IsPlayer(player) && (Halted(player)))
return;
if (!pay_queue(player, command)) /* make sure player can afford to do it */
return;
tmp = (BQUE *) mush_malloc(sizeof(BQUE), "BQUE");
tmp->comm = mush_strdup(command, "bqueue_comm");
tmp->semattr = NULL;
tmp->player = player;
tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player;
tmp->next = NULL;
tmp->left = 0;
tmp->cause = cause;
for (a = 0; a < 10; a++)
if (!global_eval_context.wnxt[a])
tmp->env[a] = NULL;
else {
tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
}
for (a = 0; a < NUMQ; a++)
if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
tmp->rval[a] = NULL;
else {
tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
}
if (IsPlayer(cause)) {
if (qlast) {
qlast->next = tmp;
qlast = tmp;
} else
qlast = qfirst = tmp;
} else {
if (qllast) {
qllast->next = tmp;
qllast = tmp;
} else
qllast = qlfirst = tmp;
}
}
/** Enqueue the action part of an attribute.
* This function is a front-end to parse_que() that takes an attribute,
* removes ^....: or $....: from its value, and queues what's left.
* \param executor object containing the attribute.
* \param atrname attribute name.
* \param enactor the enactor.
* \param noparent if true, parents of executor are not checked for atrname.
* \retval 0 failure.
* \retval 1 success.
*/
int
queue_attribute_base(dbref executor, const char *atrname, dbref enactor,
int noparent)
{
ATTR *a;
char *start, *command;
a = (noparent ? atr_get_noparent(executor, strupper(atrname)) :
atr_get(executor, strupper(atrname)));
if (!a)
return 0;
start = safe_atr_value(a);
command = start;
/* Trim off $-command or ^-command prefix */
if (*command == '$' || *command == '^') {
do {
command = strchr(command + 1, ':');
} while (command && command[-1] == '\\');
if (!command)
/* Oops, had '$' or '^', but no ':' */
command = start;
else
/* Skip the ':' */
command++;
}
parse_que(executor, command, enactor);
free(start);
return 1;
}
/** Queue an entry on the wait or semaphore queues.
* This function creates and adds a queue entry to the wait queue
* or the semaphore queue. Wait queue entries are sorted by when
* they're due to expire; semaphore queue entries are just added
* to the back of the queue.
* \param player the enqueuing object.
* \param wait time to wait, or 0.
* \param command command to enqueue.
* \param cause object that caused command to be enqueued.
* \param sem object to serve as a semaphore, or NOTHING.
* \param semattr attribute to serve as a semaphore, or NULL (to use SEMAPHORE).
* \param until 1 if we wait until an absolute time.
*/
void
wait_que(dbref player, int wait, char *command, dbref cause, dbref sem,
const char *semattr, int until)
{
BQUE *tmp;
int a;
if (wait == 0) {
if (sem != NOTHING)
add_to_sem(sem, -1, semattr);
parse_que(player, command, cause);
return;
}
if (!pay_queue(player, command)) /* make sure player can afford to do it */
return;
tmp = (BQUE *) mush_malloc(sizeof(BQUE), "BQUE");
tmp->comm = mush_strdup(command, "bqueue_comm");
tmp->player = player;
tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player;
tmp->cause = cause;
tmp->semattr = NULL;
tmp->next = NULL;
for (a = 0; a < 10; a++) {
if (!global_eval_context.wnxt[a])
tmp->env[a] = NULL;
else {
tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
}
}
for (a = 0; a < NUMQ; a++) {
if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
tmp->rval[a] = NULL;
else {
tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
}
}
if (until) {
tmp->left = wait;
} else {
if (wait >= 0)
tmp->left = mudtime + wait;
else
tmp->left = 0; /* semaphore wait without a timeout */
}
tmp->sem = sem;
if (sem == NOTHING) {
/* No semaphore, put on normal wait queue, sorted by time */
BQUE *point, *trail = NULL;
for (point = qwait;
point && (point->left <= tmp->left); point = point->next)
trail = point;
tmp->next = point;
if (trail != NULL)
trail->next = tmp;
else
qwait = tmp;
} else {
/* Put it on the end of the semaphore queue */
tmp->semattr =
mush_strdup(semattr ? semattr : "SEMAPHORE", "bqueue_semattr");
if (qsemlast != NULL) {
qsemlast->next = tmp;
qsemlast = tmp;
} else {
qsemfirst = qsemlast = tmp;
}
}
}
/** Once-a-second check for queued commands.
* This function is called every second to check for commands
* on the wait queue or semaphore queue, and to move a command
* off the low priority object queue and onto the normal priority
* player queue.
*/
void
do_second(void)
{
BQUE *trail = NULL, *point, *next;
/* move contents of low priority queue onto end of normal one
* this helps to keep objects from getting out of control since
* its effects on other objects happen only after one second
* this should allow @halt to be typed before getting blown away
* by scrolling text.
*/
if (qlfirst) {
if (qlast)
qlast->next = qlfirst;
else
qfirst = qlfirst;
qlast = qllast;
qllast = qlfirst = NULL;
}
/* check regular wait queue */
while (qwait && qwait->left <= mudtime) {
point = qwait;
qwait = point->next;
point->next = NULL;
point->left = 0;
if (IsPlayer(point->cause)) {
if (qlast) {
qlast->next = point;
qlast = point;
} else
qlast = qfirst = point;
} else {
if (qllast) {
qllast->next = point;
qllast = point;
} else
qllast = qlfirst = point;
}
}
/* check for semaphore timeouts */
for (point = qsemfirst, trail = NULL; point; point = next) {
if (point->left == 0 || point->left > mudtime) {
next = (trail = point)->next;
continue; /* skip non-timed and those that haven't gone off yet */
}
if (trail != NULL)
trail->next = next = point->next;
else
qsemfirst = next = point->next;
if (point == qsemlast)
qsemlast = trail;
add_to_sem(point->sem, -1, point->semattr);
point->sem = NOTHING;
point->next = NULL;
if (IsPlayer(point->cause)) {
if (qlast) {
qlast->next = point;
qlast = point;
} else
qlast = qfirst = point;
} else {
if (qllast) {
qllast->next = point;
qllast = point;
} else
qllast = qlfirst = point;
}
}
}
/** Execute some commands from the top of the queue.
* This function dequeues and executes commands on the normal
* priority (player) queue.
* \param ncom number of commands to execute.
* \return number of commands executed.
*/
int
do_top(int ncom)
{
int a, i;
BQUE *entry;
char tbuf[BUFFER_LEN];
int break_count;
char *r;
char const *s;
for (i = 0; i < ncom; i++) {
if (!qfirst)
return i;
/* We must dequeue before execution, so that things like
* queued @kick or @ps get a sane queue image.
*/
entry = qfirst;
if (!(qfirst = entry->next))
qlast = NULL;
if (GoodObject(entry->player) && !IsGarbage(entry->player)) {
global_eval_context.cplr = entry->player;
giveto(global_eval_context.cplr, QUEUE_COST);
add_to(entry->queued, -1);
entry->player = 0;
if (IsPlayer(global_eval_context.cplr)
|| !Halted(global_eval_context.cplr)) {
for (a = 0; a < 10; a++)
global_eval_context.wenv[a] = entry->env[a];
for (a = 0; a < NUMQ; a++) {
if (entry->rval[a])
strcpy(global_eval_context.renv[a], entry->rval[a]);
else
global_eval_context.renv[a][0] = '\0';
}
global_eval_context.process_command_port = 0;
s = entry->comm;
global_eval_context.break_called = 0;
break_count = 100;
*(global_eval_context.break_replace) = '\0';
start_cpu_timer();
while (!cpu_time_limit_hit && *s) {
r = global_eval_context.ccom;
process_expression(global_eval_context.ccom, &r, &s,
global_eval_context.cplr, entry->cause,
entry->cause, PE_NOTHING, PT_SEMI, NULL);
*r = '\0';
if (*s == ';')
s++;
strcpy(tbuf, global_eval_context.ccom);
process_command(global_eval_context.cplr, tbuf, entry->cause, 0);
if (global_eval_context.break_called) {
global_eval_context.break_called = 0;
s = global_eval_context.break_replace;
if (!*global_eval_context.break_replace)
break;
break_count--;
if (!break_count) {
notify(global_eval_context.cplr, T("@break recursion exceeded."));
break;
}
}
}
reset_cpu_timer();
}
}
free_qentry(entry);
}
return i;
}
/** Determine whether it's time to run a queued command.
* This function returns the number of seconds we expect to wait
* before it's time to run a queued command.
* If there are commands in the player queue, that's 0.
* If there are commands in the object queue, that's 1.
* Otherwise, we check wait and semaphore queues to see what's next.
* \return seconds left before a queue entry will be ready.
*/
int
que_next(void)
{
int min, curr;
BQUE *point;
/* If there are commands in the player queue, they should be run
* immediately.
*/
if (qfirst != NULL)
return 0;
/* If there are commands in the object queue, they should be run in
* one second.
*/
if (qlfirst != NULL)
return 1;
/* Check out the wait and semaphore queues, looking for the smallest
* wait value. Return that - 1, since commands get moved to the player
* queue when they have one second to go.
*/
min = 5;
/* Wait queue is in sorted order so we only have to look at the first
item on it. Anything else is wasted time. */
if (qwait) {
curr = qwait->left - mudtime;
if (curr <= 2)
return 1;
if (curr < min)
min = curr;
}
for (point = qsemfirst; point; point = point->next) {
if (point->left == 0) /* no timeout */
continue;
curr = point->left - mudtime;
if (curr <= 2)
return 1;
if (curr < min) {
min = curr;
}
}
return (min - 1);
}
static int
drain_helper(dbref player __attribute__ ((__unused__)), dbref thing,
dbref parent __attribute__ ((__unused__)),
char const *pattern __attribute__ ((__unused__)), ATTR *atr,
void *args __attribute__ ((__unused__)))
{
if (waitable_attr(thing, AL_NAME(atr)))
(void) atr_clr(thing, AL_NAME(atr), GOD);
return 0;
}
/** Drain or notify a semaphore.
* This function dequeues an entry in the semaphore queue and either
* discards it (drain) or executes it (notify). Maybe more than one.
* \param thing object serving as semaphore.
* \param aname attribute serving as semaphore.
* \param count number of entries to dequeue.
* \param all if 1, dequeue all entries.
* \param drain if 1, drain rather than notify the entries.
*/
void
dequeue_semaphores(dbref thing, char const *aname, int count, int all,
int drain)
{
BQUE **point;
BQUE *entry;
if (all)
count = INT_MAX;
/* Go through the semaphore queue and do it */
point = &qsemfirst;
while (*point && count > 0) {
entry = *point;
if (entry->sem != thing || (aname && strcmp(entry->semattr, aname))) {
point = &(entry->next);
continue;
}
/* Remove the queue entry from the semaphore list */
*point = entry->next;
entry->next = NULL;
if (qsemlast == entry) {
qsemlast = qsemfirst;
if (qsemlast)
while (qsemlast->next)
qsemlast = qsemlast->next;
}
/* Update bookkeeping */
count--;
add_to_sem(entry->sem, -1, entry->semattr);
/* Dispose of the entry as appropriate: discard if @drain, or put
* into either the player or the object queue. */
if (drain) {
giveto(entry->player, QUEUE_COST);
add_to(entry->queued, -1);
free_qentry(entry);
} else if (IsPlayer(entry->cause)) {
if (qlast) {
qlast->next = entry;
qlast = entry;
} else {
qlast = qfirst = entry;
}
} else {
if (qllast) {
qllast->next = entry;
qllast = entry;
} else {
qllast = qlfirst = entry;
}
}
}
/* If @drain/all, clear the relevant attribute(s) */
if (drain && all) {
if (aname)
(void) atr_clr(thing, aname, GOD);
else
atr_iter_get(GOD, thing, "**", 0, drain_helper, NULL);
}
/* If @notify and count was higher than the number of queue entries,
* make the semaphore go negative. This does not apply to
* @notify/any or @notify/all. */
if (!drain && aname && !all && count > 0)
add_to_sem(thing, -count, aname);
}
COMMAND (cmd_notify_drain) {
int drain;
char *pos;
char const *aname;
dbref thing;
int count;
int all;
/* Figure out whether we're in notify or drain */
drain = (cmd->name[1] == 'D');
/* Make sure they gave an object ref */
if (!arg_left || !*arg_left) {
notify(player, T("You must specify an object to use for the semaphore."));
return;
}
/* Figure out which attribute we're using */
pos = strchr(arg_left, '/');
if (pos) {
if (SW_ISSET(sw, SWITCH_ANY)) {
notify(player,
T
("You may not specify a semaphore attribute with the ANY switch."));
return;
}
*pos++ = '\0';
upcasestr(pos);
aname = pos;
} else {
if (SW_ISSET(sw, SWITCH_ANY)) {
aname = NULL;
} else {
aname = "SEMAPHORE";
}
}
/* Figure out which object we're using */
thing = noisy_match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
if (!GoodObject(thing))
return;
/* must control something or have it link_ok in order to use it as
* as a semaphore.
*/
if ((!controls(player, thing) && !LinkOk(thing))
|| (aname && !waitable_attr(thing, aname))) {
notify(player, T("Permission denied."));
return;
}
/* Figure out how many times to notify */
all = SW_ISSET(sw, SWITCH_ALL);
if (arg_right && *arg_right) {
if (all) {
notify(player,
T("You may not specify a semaphore count with the ALL switch."));
return;
}
if (!is_uinteger(arg_right)) {
notify(player, T("The semaphore count must be an integer."));
return;
}
count = parse_integer(arg_right);
} else {
if (drain)
all = 1;
if (all)
count = INT_MAX;
else
count = 1;
}
dequeue_semaphores(thing, aname, count, all, drain);
if (drain) {
quiet_notify(player, T("Drained."));
} else {
quiet_notify(player, T("Notified."));
}
}
/** Softcode interface to add a command to the wait or semaphore queue.
* \verbatim
* This is the top-level function for @wait.
* \endverbatim
* \param player the enactor
* \param cause the object causing the command to be added.
* \param arg1 the wait time, semaphore object/attribute, or both.
* \param cmd command to queue.
* \param until if 1, wait until an absolute time.
*/
void
do_wait(dbref player, dbref cause, char *arg1, char *cmd, int until)
{
dbref thing;
char *tcount = NULL, *aname = NULL;
int waitfor, num;
ATTR *a;
char *arg2;
int j;
for (j = 0; j < 10; j++)
global_eval_context.wnxt[j] = global_eval_context.wenv[j];
for (j = 0; j < NUMQ; j++)
global_eval_context.rnxt[j] = global_eval_context.renv[j];
arg2 = strip_braces(cmd);
if (is_strict_integer(arg1)) {
/* normal wait */
wait_que(player, parse_integer(arg1), arg2, cause, NOTHING, NULL, until);
mush_free(arg2, "strip_braces.buff");
return;
}
/* semaphore wait with optional timeout */
/* find the thing to wait on */
aname = strchr(arg1, '/');
if (aname)
*aname++ = '\0';
if ((thing =
noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING)) == NOTHING) {
mush_free(arg2, "strip_braces.buff");
return;
}
/* aname is either time, attribute or attribute/time.
* After this:
* tcount will hold string timeout or NULL for none
* aname will hold attribute name.
*/
if (aname) {
tcount = strchr(aname, '/');
if (!tcount) {
if (is_strict_integer(aname)) { /* Timeout */
tcount = aname;
aname = (char *) "SEMAPHORE";
} else { /* Attribute */
upcasestr(aname);
}
} else { /* attribute/timeout */
*tcount++ = '\0';
upcasestr(aname);
}
} else {
aname = (char *) "SEMAPHORE";
}
if ((!controls(player, thing) && !LinkOk(thing))
|| (aname && !waitable_attr(thing, aname))) {
notify(player, T("Permission denied."));
mush_free(arg2, "strip_braces.buff");
return;
}
/* get timeout, default of -1 */
if (tcount && *tcount)
waitfor = atol(tcount);
else
waitfor = -1;
add_to_sem(thing, 1, aname);
a = atr_get_noparent(thing, aname);
if (a)
num = parse_integer(atr_value(a));
else
num = 0;
if (num <= 0) {
thing = NOTHING;
waitfor = -1; /* just in case there was a timeout given */
}
wait_que(player, waitfor, arg2, cause, thing, aname, until);
mush_free(arg2, "strip_braces.buff");
}
static void
show_queue(dbref player, dbref victim, int q_type, int q_quiet, int q_all,
BQUE *q_ptr, int *tot, int *self, int *del)
{
BQUE *tmp;
for (tmp = q_ptr; tmp; tmp = tmp->next) {
(*tot)++;
if (!GoodObject(tmp->player))
(*del)++;
else if (q_all || (Owner(tmp->player) == victim)) {
(*self)++;
if (!q_quiet && (LookQueue(player)
|| Owns(tmp->player, player))) {
switch (q_type) {
case 1: /* wait queue */
notify_format(player, "[%ld]%s:%s", tmp->left - mudtime,
unparse_object(player, tmp->player), tmp->comm);
break;
case 2: /* semaphore queue */
if (tmp->left != 0) {
notify_format(player, "[#%d/%s/%ld]%s:%s", tmp->sem,
tmp->semattr, tmp->left - mudtime,
unparse_object(player, tmp->player), tmp->comm);
} else {
notify_format(player, "[#%d/%s]%s:%s", tmp->sem,
tmp->semattr, unparse_object(player, tmp->player),
tmp->comm);
}
break;
default: /* player or object queue */
notify_format(player, "%s:%s", unparse_object(player,
tmp->player),
tmp->comm);
}
}
}
}
}
/** Display a player's queued commands.
* \verbatim
* This is the top-level function for @ps.
* \endverbatim
* \param player the enactor.
* \param what name of player whose queue is to be displayed.
* \param flag type of display. 0 - normal, 1 - all, 2 - summary, 3 - quick
*/
void
do_queue(dbref player, const char *what, enum queue_type flag)
{
dbref victim = NOTHING;
int all = 0;
int quick = 0;
int dpq = 0, doq = 0, dwq = 0, dsq = 0;
int pq = 0, oq = 0, wq = 0, sq = 0;
int tpq = 0, toq = 0, twq = 0, tsq = 0;
if (flag == QUEUE_SUMMARY || flag == QUEUE_QUICK)
quick = 1;
if (flag == QUEUE_ALL || flag == QUEUE_SUMMARY) {
all = 1;
victim = player;
} else if (LookQueue(player)) {
if (!what || !*what)
victim = player;
else {
victim = match_result(player, what, TYPE_PLAYER,
MAT_PLAYER | MAT_ABSOLUTE | MAT_ME);
}
} else {
victim = player;
}
switch (victim) {
case NOTHING:
notify(player, T("I couldn't find that player."));
break;
case AMBIGUOUS:
notify(player, T("I don't know who you mean!"));
break;
default:
if (!quick) {
if (all)
notify(player, T("Queue for : all"));
else
notify_format(player, T("Queue for : %s"), Name(victim));
}
victim = Owner(victim);
if (!quick)
notify(player, T("Player Queue:"));
show_queue(player, victim, 0, quick, all, qfirst, &tpq, &pq, &dpq);
if (!quick)
notify(player, T("Object Queue:"));
show_queue(player, victim, 0, quick, all, qlfirst, &toq, &oq, &doq);
if (!quick)
notify(player, T("Wait Queue:"));
show_queue(player, victim, 1, quick, all, qwait, &twq, &wq, &dwq);
if (!quick)
notify(player, T("Semaphore Queue:"));
show_queue(player, victim, 2, quick, all, qsemfirst, &tsq, &sq, &dsq);
if (!quick)
notify(player, T("------------ Queue Done ------------"));
notify_format(player,
"Totals: Player...%d/%d[%ddel] Object...%d/%d[%ddel] Wait...%d/%d Semaphore...%d/%d",
pq, tpq, dpq, oq, toq, doq, wq, twq, sq, tsq);
}
}
/** Halt an object, internal use.
* This function is used to halt objects by other hardcode.
* See do_halt1() for the softcode interface.
* \param owner the enactor.
* \param ncom command to queue after halting.
* \param victim object to halt.
*/
void
do_halt(dbref owner, const char *ncom, dbref victim)
{
BQUE *tmp, *trail = NULL, *point, *next;
int num = 0;
dbref player;
if (victim == NOTHING)
player = owner;
else
player = victim;
quiet_notify(Owner(player),
tprintf("%s: %s(#%d).", T("Halted"), Name(player), player));
for (tmp = qfirst; tmp; tmp = tmp->next)
if (GoodObject(tmp->player)
&& ((tmp->player == player)
|| (Owner(tmp->player) == player))) {
num--;
giveto(player, QUEUE_COST);
tmp->player = NOTHING;
}
for (tmp = qlfirst; tmp; tmp = tmp->next)
if (GoodObject(tmp->player)
&& ((tmp->player == player)
|| (Owner(tmp->player) == player))) {
num--;
giveto(player, QUEUE_COST);
tmp->player = NOTHING;
}
/* remove wait q stuff */
for (point = qwait; point; point = next) {
if (((point->player == player)
|| (Owner(point->player) == player))) {
num--;
giveto(player, QUEUE_COST);
if (trail)
trail->next = next = point->next;
else
qwait = next = point->next;
free_qentry(point);
} else
next = (trail = point)->next;
}
/* clear semaphore queue */
for (point = qsemfirst, trail = NULL; point; point = next) {
if (((point->player == player)
|| (Owner(point->player) == player))) {
num--;
giveto(player, QUEUE_COST);
if (trail)
trail->next = next = point->next;
else
qsemfirst = next = point->next;
if (point == qsemlast)
qsemlast = trail;
add_to_sem(point->sem, -1, point->semattr);
free_qentry(point);
} else
next = (trail = point)->next;
}
add_to(QUEUE_PER_OWNER ? Owner(player) : player, num);
if (ncom && *ncom) {
int j;
for (j = 0; j < 10; j++)
global_eval_context.wnxt[j] = global_eval_context.wenv[j];
for (j = 0; j < NUMQ; j++)
global_eval_context.rnxt[j] = global_eval_context.renv[j];
parse_que(player, ncom, player);
}
}
/** Halt an object, softcode interface.
* \verbatim
* This is the top-level function for @halt.
* \endverbatim
* \param player the enactor.
* \param arg1 string representing object to halt.
* \param arg2 option string representing command to queue after halting.
*/
void
do_halt1(dbref player, const char *arg1, const char *arg2)
{
dbref victim;
if (*arg1 == '\0')
do_halt(player, "", player);
else {
if ((victim =
noisy_match_result(player, arg1, NOTYPE,
MAT_OBJECTS | MAT_HERE)) == NOTHING)
return;
if (!Owns(player, victim) && !HaltAny(player)) {
notify(player, T("Permission denied."));
return;
}
if (arg2 && *arg2 && !controls(player, victim)) {
notify(player, T("You may not use @halt obj=command on this object."));
return;
}
/* If victim's a player, we halt all of their objects */
/* If not, we halt victim and set the HALT flag if no new command */
/* was given */
do_halt(player, arg2, victim);
if (IsPlayer(victim)) {
if (victim == player) {
notify(player, T("All of your objects have been halted."));
} else {
notify_format(player,
T("All objects for %s have been halted."), Name(victim));
notify_format(victim,
T("All of your objects have been halted by %s."),
Name(player));
}
} else {
if (Owner(victim) != player) {
notify_format(player, "%s: %s's %s(%s)", T("Halted"),
Name(Owner(victim)), Name(victim), unparse_dbref(victim));
notify_format(Owner(victim),
"%s: %s(%s), by %s", T("Halted"),
Name(victim), unparse_dbref(victim), Name(player));
}
if (*arg2 == '\0')
set_flag_internal(victim, "HALT");
}
}
}
/** Halt all objects in the database.
* \param player the enactor.
*/
void
do_allhalt(dbref player)
{
dbref victim;
if (!HaltAny(player)) {
notify(player,
T("You do not have the power to bring the world to a halt."));
return;
}
for (victim = 0; victim < db_top; victim++) {
if (IsPlayer(victim)) {
notify_format(victim,
T("Your objects have been globally halted by %s"),
Name(player));
do_halt(victim, "", victim);
}
}
}
/** Restart all objects in the database.
* \verbatim
* A restart is a halt and then triggering the @startup.
* \endverbatim
* \param player the enactor.
*/
void
do_allrestart(dbref player)
{
dbref thing;
if (!HaltAny(player)) {
notify(player, T("You do not have the power to restart the world."));
return;
}
do_allhalt(player);
for (thing = 0; thing < db_top; thing++) {
if (!IsGarbage(thing) && !(Halted(thing))) {
(void) queue_attribute_noparent(thing, "STARTUP", thing);
do_top(5);
}
if (IsPlayer(thing)) {
notify_format(thing,
T("Your objects are being globally restarted by %s"),
Name(player));
}
}
}
static void
do_raw_restart(dbref victim)
{
dbref thing;
if (IsPlayer(victim)) {
for (thing = 0; thing < db_top; thing++) {
if ((Owner(thing) == victim) && !IsGarbage(thing)
&& !(Halted(thing)))
(void) queue_attribute_noparent(thing, "STARTUP", thing);
}
} else {
/* A single object */
if (!IsGarbage(victim) && !(Halted(victim)))
(void) queue_attribute_noparent(victim, "STARTUP", victim);
}
}
/** Restart an object.
* \param player the enactor.
* \param arg1 string representing the object to restart.
*/
void
do_restart_com(dbref player, const char *arg1)
{
dbref victim;
if (*arg1 == '\0') {
do_halt(player, "", player);
do_raw_restart(player);
} else {
if ((victim =
noisy_match_result(player, arg1, NOTYPE, MAT_OBJECTS)) == NOTHING)
return;
if (!Owns(player, victim) && !HaltAny(player)) {
notify(player, T("Permission denied."));
return;
}
if (Owner(victim) != player) {
if (IsPlayer(victim)) {
notify_format(player,
T("All objects for %s are being restarted."),
Name(victim));
notify_format(victim,
T("All of your objects are being restarted by %s."),
Name(player));
} else {
notify_format(player,
"Restarting: %s's %s(%s)",
Name(Owner(victim)), Name(victim), unparse_dbref(victim));
notify_format(Owner(victim),
"Restarting: %s(%s), by %s",
Name(victim), unparse_dbref(victim), Name(player));
}
} else {
if (victim == player)
notify(player, T("All of your objects are being restarted."));
else
notify_format(player, "Restarting: %s(%s)", Name(victim),
unparse_dbref(victim));
}
do_halt(player, "", victim);
do_raw_restart(victim);
}
}
/** Dequeue all queue entries, refunding deposits.
* This function dequeues all entries in all queues, without executing
* them and refunds queue deposits. It's called at shutdown.
*/
void
shutdown_queues(void)
{
shutdown_a_queue(&qfirst, &qlast);
shutdown_a_queue(&qlfirst, &qllast);
shutdown_a_queue(&qsemfirst, &qsemlast);
shutdown_a_queue(&qwait, NULL);
}
static void
shutdown_a_queue(BQUE **head, BQUE **tail)
{
BQUE *entry;
/* Drain out a queue */
while (*head) {
entry = *head;
if (!(*head = entry->next) && tail)
*tail = NULL;
if (GoodObject(entry->player) && !IsGarbage(entry->player)) {
global_eval_context.cplr = entry->player;
giveto(global_eval_context.cplr, QUEUE_COST);
add_to(entry->queued, -1);
}
free_qentry(entry);
}
}