pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file timer.c
 *
 * \brief Timed events in PennMUSH.
 *
 *
 */
#include "copyrite.h"
#include "config.h"

#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#ifdef WIN32
#include <windows.h>
#endif
#ifdef I_UNISTD
#include <unistd.h>
#endif

#include "conf.h"
#include "externs.h"
#include "dbdefs.h"
#include "lock.h"
#include "extmail.h"
#include "match.h"
#include "flags.h"
#include "access.h"
#include "log.h"
#include "game.h"
#include "help.h"
#include "parse.h"
#include "confmagic.h"


static sig_atomic_t hup_triggered = 0;
static sig_atomic_t usr1_triggered = 0;

extern void inactivity_check(void);
extern void reopen_logs(void);
static void migrate_stuff(int amount);

#ifndef WIN32
void hup_handler(int);
void usr1_handler(int);

#endif
void dispatch(void);

#ifndef WIN32

/** Handler for HUP signal.
 * Do the minimal work here - set a global variable and reload the handler.
 * \param x unused.
 */
void
hup_handler(int x __attribute__ ((__unused__)))
{
  hup_triggered = 1;
  reload_sig_handler(SIGHUP, hup_handler);
}

/** Handler for USR1 signal.
 * Do the minimal work here - set a global variable and reload the handler.
 * \param x unused.
 */
void
usr1_handler(int x __attribute__ ((__unused__)))
{
  usr1_triggered = 1;
  reload_sig_handler(SIGUSR1, usr1_handler);
}

#endif				/* WIN32 */

/** Set up signal handlers.
 */
void
init_timer(void)
{
#ifndef WIN32
  install_sig_handler(SIGHUP, hup_handler);
  install_sig_handler(SIGUSR1, usr1_handler);
#endif
#ifndef PROFILING
#ifdef HAS_ITIMER
  install_sig_handler(SIGPROF, signal_cpu_limit);
#endif
#endif
}

/** Migrate some number of chunks.
 * The requested amount is only a guideline; the actual amount
 * migrated will be more or less due to always migrating all the
 * attributes, locks, and mail on any given object together.
 * \param amount the suggested number of attributes to migrate.
 */
static void
migrate_stuff(int amount)
{
  static int start_obj = 0;
  static chunk_reference_t **refs = NULL;
  static int refs_size = 0;
  int end_obj;
  int actual;
  ATTR *aptr;
  lock_list *lptr;
  MAIL *mp;

  if (db_top == 0)
    return;

  end_obj = start_obj;
  actual = 0;
  do {
    for (aptr = List(end_obj); aptr; aptr = AL_NEXT(aptr))
      if (aptr->data != NULL_CHUNK_REFERENCE)
	actual++;
    for (lptr = Locks(end_obj); lptr; lptr = L_NEXT(lptr))
      if (L_KEY(lptr) != NULL_CHUNK_REFERENCE)
	actual++;
    if (IsPlayer(end_obj)) {
      for (mp = find_exact_starting_point(end_obj); mp; mp = mp->next)
	if (mp->msgid != NULL_CHUNK_REFERENCE)
	  actual++;
    }
    end_obj = (end_obj + 1) % db_top;
  } while (actual < amount && end_obj != start_obj);

  if (actual == 0)
    return;

  if (!refs || actual > refs_size) {
    if (refs)
      mush_free((Malloc_t) refs, "migration reference array");
    refs =
      (chunk_reference_t **) mush_malloc(actual * sizeof(chunk_reference_t *),
					 "migration reference array");
    refs_size = actual;
    if (!refs)
      mush_panic("Could not allocate migration reference array");
  }
#ifdef DEBUG_MIGRATE
  do_rawlog(LT_TRACE, "Migrate asked %d, actual objects #%d to #%d for %d",
	    amount, start_obj, (end_obj + db_top - 1) % db_top, actual);
#endif

  actual = 0;
  do {
    for (aptr = List(start_obj); aptr; aptr = AL_NEXT(aptr))
      if (aptr->data != NULL_CHUNK_REFERENCE) {
	refs[actual] = &(aptr->data);
	actual++;
      }
    for (lptr = Locks(start_obj); lptr; lptr = L_NEXT(lptr))
      if (L_KEY(lptr) != NULL_CHUNK_REFERENCE) {
	refs[actual] = &(lptr->key);
	actual++;
      }
    if (IsPlayer(start_obj)) {
      for (mp = find_exact_starting_point(start_obj); mp; mp = mp->next)
	if (mp->msgid != NULL_CHUNK_REFERENCE) {
	  refs[actual] = &(mp->msgid);
	  actual++;
	}
    }
    start_obj = (start_obj + 1) % db_top;
  } while (start_obj != end_obj);

  chunk_migration(actual, refs);
}

/** Handle events that may need handling.
 * This routine is polled from bsd.c. At any call, it can handle
 * the HUP and USR1 signals. At calls that are 'on the second',
 * it goes on to perform regular every-second processing and to
 * check whether it's time to do other periodic processes like
 * purge, dump, or inactivity checks.
 */
void
dispatch(void)
{
  static int idle_counter = 0;

  /* A HUP reloads configuration and reopens logs */
  if (hup_triggered) {
    do_rawlog(LT_ERR, T("SIGHUP received: reloading .txt and .cnf files"));
    config_file_startup(NULL, 0);
    config_file_startup(NULL, 1);
    fcache_load(NOTHING);
    help_reindex(NOTHING);
    read_access_file();
    reopen_logs();
    hup_triggered = 0;
  }
  /* A USR1 does a shutdown/reboot */
  if (usr1_triggered) {
    do_reboot(NOTHING, 0);	/* We don't return from this */
    usr1_triggered = 0;		/* But just in case */
  }
  if (!globals.on_second)
    return;
  globals.on_second = 0;

  mudtime = time(NULL);

  do_second();

  migrate_stuff(CHUNK_MIGRATE_AMOUNT);

  if (options.purge_counter <= mudtime) {
    /* Free list reconstruction */
    options.purge_counter = options.purge_interval + mudtime;
    global_eval_context.cplr = NOTHING;
    strcpy(global_eval_context.ccom, "purge");
    purge();
  }

  if (options.dbck_counter <= mudtime) {
    /* Database consistency check */
    options.dbck_counter = options.dbck_interval + mudtime;
    global_eval_context.cplr = NOTHING;
    strcpy(global_eval_context.ccom, "dbck");
    dbck();
  }

  if (idle_counter <= mudtime) {
    /* Inactivity check */
    idle_counter = 60 + mudtime;
    inactivity_check();
  }

  /* Database dump routines */
  if (options.dump_counter <= mudtime) {
    log_mem_check();
    options.dump_counter = options.dump_interval + mudtime;
    strcpy(global_eval_context.ccom, "dump");
    fork_and_dump(1);
    flag_broadcast(0, "ON-VACATION", "%s",
		   T
		   ("Your ON-VACATION flag is set! If you're back, clear it."));
  } else if (NO_FORK &&
	     (options.dump_counter - 60 == mudtime) &&
	     *options.dump_warning_1min) {
    flag_broadcast(0, 0, "%s", options.dump_warning_1min);
  } else if (NO_FORK &&
	     (options.dump_counter - 300 == mudtime) &&
	     *options.dump_warning_5min) {
    flag_broadcast(0, 0, "%s", options.dump_warning_5min);
  }
  if (options.warn_interval && (options.warn_counter <= mudtime)) {
    options.warn_counter = options.warn_interval + mudtime;
    strcpy(global_eval_context.ccom, "warnings");
    run_topology();
  }

  local_timer();
}

sig_atomic_t cpu_time_limit_hit = 0;  /** Was the cpu time limit hit? */
int cpu_limit_warning_sent = 0;	 /** Have we issued a cpu limit warning? */

#ifndef PROFILING
#if defined(HAS_ITIMER)
/** Handler for PROF signal.
 * Do the minimal work here - set a global variable and reload the handler.
 * \param signo unused.
 */
void
signal_cpu_limit(int signo __attribute__ ((__unused__)))
{
  cpu_time_limit_hit = 1;
  reload_sig_handler(SIGPROF, signal_cpu_limit);
}
#elif defined(WIN32)
#if _MSC_VER <= 1100 && !defined(UINT_PTR)
#define UINT_PTR UINT
#endif
UINT_PTR timer_id;
VOID CALLBACK
win32_timer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
  cpu_time_limit_hit = 1;
}
#endif
#endif
int timer_set = 0;	/**< Is a CPU timer set? */

/** Start the cpu timer (before running a command).
 */
void
start_cpu_timer(void)
{
#ifndef PROFILING
  cpu_time_limit_hit = 0;
  cpu_limit_warning_sent = 0;
  timer_set = 1;
#if defined(HAS_ITIMER)		/* UNIX way */
  {
    struct itimerval time_limit;
    if (options.queue_entry_cpu_time > 0) {
      ldiv_t t;
      /* Convert from milliseconds */
      t = ldiv(options.queue_entry_cpu_time, 1000);
      time_limit.it_value.tv_sec = t.quot;
      time_limit.it_value.tv_usec = t.rem * 1000;
      time_limit.it_interval.tv_sec = 0;
      time_limit.it_interval.tv_usec = 0;
      if (setitimer(ITIMER_PROF, &time_limit, NULL)) {
	perror("setitimer");
	timer_set = 0;
      }
    } else
      timer_set = 0;
  }
#elif defined(WIN32)		/* Windoze way */
  if (options.queue_entry_cpu_time > 0)
    timer_id = SetTimer(NULL, 0, (unsigned) options.queue_entry_cpu_time,
			(TIMERPROC) win32_timer);
  else
    timer_set = 0;
#endif
#endif
}

/** Reset the cpu timer (after running a command).
 */
void
reset_cpu_timer(void)
{
#ifndef PROFILING
  if (timer_set) {
#if defined(HAS_ITIMER)
    struct itimerval time_limit, time_left;
    time_limit.it_value.tv_sec = 0;
    time_limit.it_value.tv_usec = 0;
    time_limit.it_interval.tv_sec = 0;
    time_limit.it_interval.tv_usec = 0;
    if (setitimer(ITIMER_PROF, &time_limit, &time_left))
      perror("setitimer");
#elif defined(WIN32)
    KillTimer(NULL, timer_id);
#endif
  }
  cpu_time_limit_hit = 0;
  cpu_limit_warning_sent = 0;
  timer_set = 0;
#endif
}