/* timer.c - Subroutines for (system-) timed events */
/* $Id: timer.c,v 1.9.2.2 2000/06/11 17:52:44 lwl Exp $ */
#include "copyright.h"
#include "autoconf.h"
#include <signal.h>
#include <time.h>
#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#include "command.h"
#include "powers.h"
#include "bitstring.h"
extern void NDECL(pool_reset);
extern void NDECL(do_second);
extern void FDECL(fork_and_dump, (int key));
extern unsigned int FDECL(alarm, (unsigned int seconds));
extern void NDECL(pcache_trim);
extern dbref FDECL(match_thing, (dbref, char *));
/* ---------------------------------------------------------------------------
* Cron-related things. This implementation is somewhat derivative of
* Paul Vixie's cron implementation. See bitstring.h for the copyright
* and other details.
*/
#define FIRST_MINUTE 0
#define LAST_MINUTE 59
#define MINUTE_COUNT (LAST_MINUTE - FIRST_MINUTE + 1)
#define FIRST_HOUR 0
#define LAST_HOUR 23
#define HOUR_COUNT (LAST_HOUR - FIRST_HOUR + 1)
#define FIRST_DOM 1
#define LAST_DOM 31
#define DOM_COUNT (LAST_DOM - FIRST_DOM + 1)
#define FIRST_MONTH 1
#define LAST_MONTH 12
#define MONTH_COUNT (LAST_MONTH - FIRST_MONTH + 1)
/* Note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
#define FIRST_DOW 0
#define LAST_DOW 7
#define DOW_COUNT (LAST_DOW - FIRST_DOW + 1)
#define DOM_STAR 0x01
#define DOW_STAR 0x02
typedef struct cron_entry CRONTAB;
struct cron_entry {
dbref obj;
int atr;
char *cronstr;
bitstr_t bit_decl(minute, MINUTE_COUNT);
bitstr_t bit_decl(hour, HOUR_COUNT);
bitstr_t bit_decl(dom, DOM_COUNT);
bitstr_t bit_decl(month, MONTH_COUNT);
bitstr_t bit_decl(dow, DOW_COUNT);
int flags;
CRONTAB *next;
};
CRONTAB *cron_head = NULL;
#define set_cronbits(b,l,h,n) \
if (((n) >= (l)) && ((n) <= (h))) bit_set((b), (n) - (l));
#ifndef STANDALONE
static void NDECL(check_cron)
{
struct tm *ltime;
int minute, hour, dom, month, dow;
CRONTAB *crp;
char *cmd;
dbref aowner;
int aflags, alen;
/* Convert our time to a zero basis, so the elements can be used
* as indices.
*/
ltime = localtime(&mudstate.now);
minute = ltime->tm_min - FIRST_MINUTE;
hour = ltime->tm_hour - FIRST_HOUR;
dom = ltime->tm_mday - FIRST_DOM;
month = ltime->tm_mon + 1 - FIRST_MONTH; /* must convert 0-11 to 1-12 */
dow = ltime->tm_wday - FIRST_DOW;
/* Do it if the minute, hour, and month match, plus a day selection
* matches. We handle stars and the day-of-month vs. day-of-week
* exactly like Unix (Vixie) cron does.
*/
for (crp = cron_head; crp != NULL; crp = crp->next) {
if (bit_test(crp->minute, minute) &&
bit_test(crp->hour, hour) &&
bit_test(crp->month, month) &&
(((crp->flags & DOM_STAR) || (crp->flags & DOW_STAR)) ?
(bit_test(crp->dow, dow) && bit_test(crp->dom, dom)) :
(bit_test(crp->dow, dow) || bit_test(crp->dom, dom)))) {
cmd = atr_pget(crp->obj, crp->atr, &aowner, &aflags, &alen);
if (*cmd && Good_obj(crp->obj)) {
wait_que(crp->obj, crp->obj, 0, NOTHING, 0, cmd,
(char **) NULL, 0, NULL);
}
free_lbuf(cmd);
}
}
}
static char *parse_cronlist(player, bits, low, high, bufp)
dbref player;
bitstr_t *bits;
int low, high;
char *bufp;
{
int i, n_begin, n_end, step_size;
bit_nclear(bits, 0, (high - low + 1)); /* Default is all off */
if (!bufp || !*bufp)
return NULL;
if (!*bufp)
return NULL;
/* We assume we're at the beginning of what we needed to parse.
* All leading whitespace-skipping should have been taken care of
* either before this function was called, or at the end of this
* function.
*/
while (*bufp && !isspace(*bufp)) {
if (*bufp == '*') {
n_begin = low;
n_end = high;
bufp++;
} else if (isdigit(*bufp)) {
n_begin = atoi(bufp); /* atoi() ignores trailing non-digits */
while (*bufp && isdigit(*bufp))
bufp++;
if (*bufp != '-') {
/* We have a single number, not a range. */
n_end = n_begin;
} else {
/* Eat the dash, get the range. */
bufp++;
n_end = atoi(bufp);
while (*bufp && isdigit(*bufp))
bufp++;
}
} else {
notify(player, tprintf("Cron parse error at: %s", bufp));
break;
}
/* Check for step size. */
if (*bufp == '/') {
bufp++; /* eat the slash */
step_size = atoi(bufp);
while (*bufp && isdigit(*bufp))
bufp++;
} else {
step_size = 1;
}
/* Go set it. */
for (i = n_begin; i <= n_end; i += step_size)
set_cronbits(bits, low, high, i);
/* We've made it through one pass. If the next character isn't
* a comma, we break out of this loop.
*/
if (*bufp == ',')
bufp++;
else
break;
}
/* Skip over trailing garbage. */
while (*bufp && !isspace(*bufp))
bufp++;
/* Initially, bufp pointed to the beginning of what we parsed. We have
* to return it so we know where to start the next bit of parsing.
* Skip spaces as well.
*/
while (isspace(*bufp))
bufp++;
return bufp;
}
int call_cron(player, thing, attrib, timestr)
dbref player, thing;
int attrib;
char *timestr;
{
int errcode;
CRONTAB *crp;
char *bufp;
/* Don't allow duplicate entries. */
for (crp = cron_head; crp != NULL; crp = crp->next) {
if ((crp->obj == thing) && (crp->atr == attrib) &&
!strcmp(crp->cronstr, timestr))
return -1;
}
crp = (CRONTAB *) XMALLOC(sizeof(CRONTAB), "cron_entry");
crp->obj = thing;
crp->atr = attrib;
crp->flags = 0;
crp->cronstr = (char *) strdup(timestr);
/* The time string is: <min> <hour> <day of month> <month> <day of week>
* Legal values also include asterisks, and <x>-<y> (for a range).
* We do NOT support step size.
*/
errcode = 0;
bufp = timestr;
while (isspace(*bufp))
bufp++;
bufp = parse_cronlist(player, crp->minute,
FIRST_MINUTE, LAST_MINUTE, bufp);
if (!bufp || !*bufp) {
errcode = 1;
} else {
bufp = parse_cronlist(player, crp->hour, FIRST_HOUR, LAST_HOUR, bufp);
if (!bufp || !*bufp)
errcode = 1;
}
if (!errcode) {
if (*bufp == '*')
crp->flags |= DOM_STAR;
bufp = parse_cronlist(player, crp->dom, FIRST_DOM, LAST_DOM, bufp);
if (!bufp || !*bufp)
errcode = 1;
}
if (!errcode) {
bufp = parse_cronlist(player, crp->month,
FIRST_MONTH, LAST_MONTH, bufp);
if (!bufp || !*bufp)
errcode = 1;
}
if (!errcode) {
if (*bufp == '*')
crp->flags |= DOW_STAR;
bufp = parse_cronlist(player, crp->dow, FIRST_DOW, LAST_DOW, bufp);
}
/* Sundays can be either 0 or 7. */
if (bit_test(crp->dow, 0))
bit_set(crp->dow, 7);
if (bit_test(crp->dow, 7))
bit_set(crp->dow, 0);
if (errcode) {
free(crp->cronstr);
XFREE(crp, "cron_entry");
return 0;
}
/* Relink the list, now that we know we have something good. */
crp->next = cron_head;
cron_head = crp;
return 1;
}
void do_cron(player, cause, key, objstr, timestr)
dbref player, cause;
int key;
char *objstr, *timestr;
{
dbref thing;
int attrib, retcode;
if (!timestr || !*timestr) {
notify(player, "No times given.");
return;
}
if (!parse_attrib(player, objstr, &thing, &attrib) ||
(attrib == NOTHING) || !Good_obj(thing)) {
notify(player, "No match.");
return;
}
if (!Controls(player, thing)) {
notify(player, NOPERM_MESSAGE);
return;
}
retcode = call_cron(player, thing, attrib, timestr);
if (retcode == 0)
notify(player, "Syntax errors. No cron entry made.");
else if (retcode == -1)
notify(player, "That cron entry already exists.");
else
notify(player, "Cron entry added.");
}
int cron_clr(thing, attr)
dbref thing;
int attr;
{
CRONTAB *crp, *next, *prev;
int count;
count = 0;
for (crp = cron_head, prev = NULL; crp != NULL; ) {
if ((crp->obj == thing) &&
((attr == NOTHING) || (crp->atr == attr))) {
count++;
next = crp->next;
free(crp->cronstr);
XFREE(crp, "cron_entry");
if (prev)
prev->next = next;
else
cron_head = next;
crp = next;
} else {
prev = crp;
crp = crp->next;
}
}
return count;
}
void do_crondel(player, cause, key, objstr)
dbref player, cause;
int key;
char *objstr;
{
dbref thing;
int attr, count;
if (!objstr || !*objstr) {
notify(player, "No match.");
return;
}
attr = NOTHING;
if (!parse_attrib(player, objstr, &thing, &attr) || (attr == NOTHING)) {
if ((*objstr != '#') ||
((thing = parse_dbref(objstr + 1)) == NOTHING)) {
notify(player, "No match.");
}
}
if (!Controls(player, thing)) {
notify(player, NOPERM_MESSAGE);
return;
}
count = cron_clr(thing, attr);
notify(player, tprintf("Removed %d cron entries.", count));
}
void do_crontab(player, cause, key, objstr)
dbref player, cause;
int key;
char *objstr;
{
dbref thing;
int count;
CRONTAB *crp;
char *bufp;
ATTR *ap;
if (objstr && *objstr) {
thing = match_thing(player, objstr);
if (!Good_obj(thing))
return;
if (!Controls(player, thing)) {
notify(player, NOPERM_MESSAGE);
return;
}
} else {
thing = NOTHING;
}
/* List it if it's the thing we want, or, if we didn't specify a
* thing, list things belonging to us (or everything, if we're
* normally entitled to see the entire queue).
*/
count = 0;
for (crp = cron_head; crp != NULL; crp = crp->next) {
if (((thing == NOTHING) &&
((Owner(crp->obj) == player) || See_Queue(player))) ||
(crp->obj == thing)) {
count++;
bufp = unparse_object(player, crp->obj, 0);
ap = atr_num(crp->atr);
if (!ap) {
notify(player,
tprintf("%s has a cron entry that contains bad attribute number %d.",
bufp, crp->atr));
} else {
notify(player, tprintf("%s/%s: %s", bufp, ap->name,
crp->cronstr));
}
free_lbuf(bufp);
}
}
notify(player, tprintf("Matched %d cron %s.",
count, (count == 1) ? "entry" : "entries"));
}
#endif /* STANDALONE */
/* ---------------------------------------------------------------------------
* General timer things.
*/
void NDECL(init_timer)
{
mudstate.now = time(NULL);
mudstate.dump_counter = ((mudconf.dump_offset == 0) ?
mudconf.dump_interval : mudconf.dump_offset) + mudstate.now;
mudstate.check_counter = ((mudconf.check_offset == 0) ?
mudconf.check_interval : mudconf.check_offset) + mudstate.now;
mudstate.idle_counter = mudconf.idle_interval + mudstate.now;
mudstate.mstats_counter = 15 + mudstate.now;
/* The events counter is the next time divisible by sixty (i.e.,
* that puts us at the beginning of a minute).
*/
mudstate.events_counter = mudstate.now + (60 - (mudstate.now % 60));
alarm(1);
}
void NDECL(dispatch)
{
char *cmdsave;
cmdsave = mudstate.debug_cmd;
mudstate.debug_cmd = (char *)"< dispatch >";
/*
* this routine can be used to poll from interface.c
*/
if (!mudstate.alarm_triggered)
return;
mudstate.alarm_triggered = 0;
mudstate.now = time(NULL);
do_second();
/*
* Free list reconstruction
*/
if ((mudconf.control_flags & CF_DBCHECK) &&
(mudstate.check_counter <= mudstate.now)) {
mudstate.check_counter = mudconf.check_interval + mudstate.now;
mudstate.debug_cmd = (char *)"< dbck >";
#ifndef MEMORY_BASED
cache_reset(0);
#endif /*
* MEMORY_BASED
*/
do_dbck(NOTHING, NOTHING, 0);
#ifndef MEMORY_BASED
cache_reset(0);
#endif /*
* MEMORY_BASED
*/
pcache_trim();
pool_reset();
}
/*
* Database dump routines
*/
if ((mudconf.control_flags & CF_CHECKPOINT) &&
(mudstate.dump_counter <= mudstate.now)) {
mudstate.dump_counter = mudconf.dump_interval + mudstate.now;
mudstate.debug_cmd = (char *)"< dump >";
fork_and_dump(0);
}
/*
* Idle user check
*/
if ((mudconf.control_flags & CF_IDLECHECK) &&
(mudstate.idle_counter <= mudstate.now)) {
mudstate.idle_counter = mudconf.idle_interval + mudstate.now;
mudstate.debug_cmd = (char *)"< idlecheck >";
#ifndef MEMORY_BASED
cache_reset(0);
#endif /*
* MEMORY_BASED
*/
check_idle();
}
/*
* Check for execution of attribute events
*/
if (mudconf.control_flags & CF_EVENTCHECK) {
if (mudstate.now >= mudstate.events_counter) {
mudstate.debug_cmd = (char *) "< croncheck >";
check_cron();
mudstate.events_counter += 60;
}
}
#ifdef HAVE_GETRUSAGE
/*
* Memory use stats
*/
if (mudstate.mstats_counter <= mudstate.now) {
int curr;
mudstate.mstats_counter = 15 + mudstate.now;
curr = mudstate.mstat_curr;
if (mudstate.now > mudstate.mstat_secs[curr]) {
struct rusage usage;
curr = 1 - curr;
getrusage(RUSAGE_SELF, &usage);
mudstate.mstat_ixrss[curr] = usage.ru_ixrss;
mudstate.mstat_idrss[curr] = usage.ru_idrss;
mudstate.mstat_isrss[curr] = usage.ru_isrss;
mudstate.mstat_secs[curr] = mudstate.now;
mudstate.mstat_curr = curr;
}
}
#endif
/*
* reset alarm
*/
alarm(1);
mudstate.debug_cmd = cmdsave;
}
/*
* ---------------------------------------------------------------------------
* * do_timewarp: Adjust various internal timers.
*/
void do_timewarp(player, cause, key, arg)
dbref player, cause;
int key;
char *arg;
{
int secs;
secs = atoi(arg);
if ((key == 0) || (key & TWARP_QUEUE)) /*
* Sem/Wait queues
*/
do_queue(player, cause, QUEUE_WARP, arg);
if (key & TWARP_DUMP)
mudstate.dump_counter -= secs;
if (key & TWARP_CLEAN)
mudstate.check_counter -= secs;
if (key & TWARP_IDLE)
mudstate.idle_counter -= secs;
if (key & TWARP_EVENTS)
mudstate.events_counter -= secs;
}