pgplus/bin/
pgplus/help_files/
pgplus/port_redirector/
pgplus/src/configure/makefiles/
/*
 * Playground+ - reboot.c
 * Code to perform a seamless reboot (c) phypor 1998
 * ---------------------------------------------------------------------------
 *
 *  Changes to original:
 *    - Change of #define's to point to correct path
 *    - Replacement of ez_wall, ez_tellplayer and ez_log to PG+ formats
 *    - All errors are sent to SU channel
 *    - Code will wait till people out of editor before rebooting
 *    - Slight changes to error messages
 *    - Some uninformative and badly spelt comments removed
 *    - Reset several "time" stats on reboot
 *    - Change of include file locations
 *    - Code tidely indented
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#include "include/config.h"
#include "include/player.h"
#include "include/proto.h"


/* Only add all this is we want seamless rebooting */
#ifdef SEAMLESS_REBOOT


#define REBOOTING_DIR		"junk/rebooting"
#define PLAYER_LIST_FILE	"junk/rebooting/_plist"
#define TALKER_SYSINFO_FILE	"junk/rebooting/_sysinfo"
#define CHILDS_PID_FILE		"junk/rebooting/_child_pid"


extern int main_descriptor;
extern int alive_descriptor;
#ifdef IDENT
extern void kill_ident_server(void);
#endif /* IDENT */


char rebooter[MAX_NAME];	/* use a global for nicer looking code */


typedef struct talker_system_info
{
  int
    main_fd, angel_fd, up_date, logins, sys_flags, session_reset;

  char
    session[MAX_SESSION], sess_name[MAX_NAME], rebooter[MAX_NAME];
}
talker_system_info;


int build_sysinfo(void)
{
  talker_system_info tsi;
  FILE *f;

  memset(&tsi, 0, sizeof(talker_system_info));

  tsi.main_fd = main_descriptor;
  tsi.angel_fd = alive_descriptor;
  tsi.up_date = up_date;
  tsi.logins = logins;
  tsi.sys_flags = sys_flags;
  tsi.session_reset = session_reset;
  strncpy(tsi.session, session, MAX_SESSION - 1);
  strncpy(tsi.sess_name, sess_name, MAX_NAME - 1);
  if (current_player)
    strncpy(tsi.rebooter, current_player->lower_name, MAX_NAME - 1);

  f = fopen(TALKER_SYSINFO_FILE, "w");
  if (!f)
  {
    SUWALL(" -=*> Failed to open reboot system info file [%s]\n", strerror(errno));
    LOGF("error", "Failed to open reboot system info file [%s]", strerror(errno));
    return -1;
  }
  fwrite((void *) &tsi, sizeof(talker_system_info), 1, f);
  fclose(f);
  return 0;
}

int build_loggedin_players_list(void)
{
  player *scan;
  char *oldstack = stack;
  int fd;

  /* build players list */

  /* get to the end of the flatlist */
  for (scan = flatlist_start; scan->flat_next; scan = scan->flat_next);

  /* work backwards through it, adding names to stack */
  for (; scan; scan = scan->flat_previous)
    if (scan->location)
      stack += sprintf(stack, "%s\n", scan->lower_name);

  stack = end_string(stack);

  fd = open(PLAYER_LIST_FILE, (O_WRONLY | O_CREAT |
			       O_NONBLOCK | O_TRUNC), (S_IRUSR | S_IWUSR));

  if (fd < 0)
  {
    SUWALL(" -=*> Failed to open reboot player list file [%s]\n", strerror(errno));
    LOGF("error", "Failed to open reboot player list file [%s]", strerror(errno));
    stack = oldstack;
    return -1;
  }
  write(fd, oldstack, strlen(oldstack));
  close(fd);
  stack = oldstack;
  return 0;
}

int build_loggedin_players_info(void)
{
  player *scan, *oldcp;
  char *oldstack = stack;
  char location_string[MAX_NAME + MAX_ID];
  FILE *f;

  for (scan = flatlist_start; scan; scan = scan->flat_next)
  {
    /* if the player doesnt have a location, no telling whut point
       they are in the login process, (or maybe they arent even logging in)
       we wont even attempt to keep up with them
     */
    if (!scan->location)
    {
      oldcp = current_player;	/* so the tell_player goes through */
      current_player = scan;
      tell_player(scan, "\n\n   Reboot in progress, losing connection.\n"
		  "   Please relogin.\n\n\n");
      current_player = oldcp;
      shutdown(scan->fd, 2);
      close(scan->fd);
      continue;
    }

    /* do this roundabout so we cant possibly overwrite field length */
    memset(location_string, 0, MAX_NAME + MAX_ID);
    sprintf(location_string, "%s.%s", scan->location->owner->lower_name,
	    scan->location->id);
    strncpy(scan->location_string, location_string, MAX_NAME + MAX_ID - 1);

    sprintf(stack, "%s/%s", REBOOTING_DIR, scan->lower_name);
    stack = end_string(stack);
    f = fopen(oldstack, "w");
    if (!f)
    {
      SUWALL(" -=*> Failed to open reboot player info file (%s) [%s]\n", scan->lower_name, strerror(errno));
      LOGF("error", "Failed to open reboot player info (%s) [%s]", scan->lower_name, strerror(errno));
      stack = oldstack;
      return -1;
    }
    fwrite((void *) scan, sizeof(player), 1, f);
    fclose(f);
    stack = oldstack;
  }
  return 0;
}

void close_fds(void)
{
  int i, d = 0;
  player *scan;

  for (i = 4; i < (1 << 12); i++)	/* start at 4, so we dont kill stdout, etc */
  {
    if (i == main_descriptor ||
	i == alive_descriptor)
      continue;

    for (scan = flatlist_start; scan; scan = scan->flat_next)
      if (scan->fd == i)
	break;

    if (scan)
      continue;

    if (close(i) == 0)
      d++;
  }

}


void do_reboot(void)
{
  player *scan;
  int cpid;
  FILE *f;
  char server_name[256];
  struct itimerval itimer;

  sprintf(server_name, "-=> %s <=- Talk Server on port %d",
	  get_config_msg("talker_name"), atoi(get_config_msg("port")));

  SUWALL(" -=*> Starting reboot ...\n");

  if (build_sysinfo() < 0)
  {
    SUWALL(" -=*> Reboot failed (build_sysinfo) ...\n");
    return;
  }

  if (build_loggedin_players_list() < 0)
  {
    SUWALL(" -=*> Reboot failed (build_loggedin_players_list) ...\n");
    return;
  }
  if (build_loggedin_players_info() < 0)
  {
    SUWALL(" -=*> Reboot failed (build_loggedin_players_info) ...\n");
    return;
  }

  /* kill the timer */
  itimer.it_interval.tv_sec = itimer.it_interval.tv_usec = 0;
  itimer.it_value.tv_sec = itimer.it_value.tv_usec = 0;
  setitimer(ITIMER_REAL, &itimer, 0);


  for (scan = flatlist_start; scan; scan = scan->flat_next)
    save_player(scan);

  sys_flags |= SHUTDOWN;

  sync_all();
  sync_notes(0);
  sync_sitems(0);

#ifdef IDENT
  kill_ident_server();
#endif /* IDENT */

#ifdef INTERCOM
  kill_intercom();
#endif

  sync_all_news_headers();

#ifdef ALLOW_MULTIS
  reboot_save_multis();
  destroy_all_multis();
#endif

  /*
     here is where to add for any other syncing that needs to be done
     when your talker shutsdown... notes, etc
   */


  close_fds();

  cpid = getpid();
  f = fopen(CHILDS_PID_FILE, "w");
  if (f)
  {
    fprintf(f, "%d\n", cpid);
    fclose(f);
  }
  else
    LOGF("error", "Failed to fopen [%s] coz %s ... reboot fails.", CHILDS_PID_FILE, strerror(errno));


  execlp("bin/" TALKER_EXEC, server_name, (char *) NULL);

  sys_flags &= ~SHUTDOWN;

  log("error", "Failed to execlp!");
  SUWALL(" -=*> Failed to reboot ... !!!\n");

}
/* Modified by Silver to take into account that people may be in the
   editor when the code reboots */

void reboot_command(player * p, char *str)
{
  player *scan;
  char *oldstack = stack;

  CHECK_DUTY(p);

#ifdef AUTOSHUTDOWN
  if (auto_sd)
  {
    tell_player(p, "\n Autoshutdown is active. Rebooting the talker may cause pfile corruption.\n"
                   " You are advised to perform a proper shutdown to avoid problems.\n");
    return;
  }
#endif /* AUTOSHUTDOWN */

  if (!strcasecmp(str, "-f"))
  {
    tell_player(p, " Forcing reboot ...\n");
    LOGF("reboot", "%s forces a reboot ...", p->name);
    do_reboot();
  }

  if (awaiting_reboot && strcasecmp(str, "-g"))
  {
    tell_player(p, " Already awaiting opportunity to reboot ...\n");
    return;
  }

  for (scan = flatlist_start; scan; scan = scan->flat_next)
    if (scan->location && (scan->flags & IN_EDITOR || scan->mode & (MAILEDIT | ROOMEDIT | NEWSEDIT)))
    {
      awaiting_reboot = 1;
      stack += sprintf(stack, "%s  ", scan->name);
    }

  if (!awaiting_reboot)
  {
    LOGF("reboot", "%s calls for a reboot ... and gets it.", p->name);
    do_reboot();
  }
  stack = end_string(stack);

  TELLPLAYER(p,
	     " Someone is currently in the editor (or in a mode).\n"
	     " The code will reboot when they have finished ...\n"
	     " (ppl holding up the show are: %s)\n", oldstack);

  stack = oldstack;

  SW_BUT(p, " -=*> %s requests a reboot of %s\n",
	 p->name, get_config_msg("talker_name"));
  LOGF("reboot", "%s calls for a reboot ... but has to wait.", p->name);

}



/**                                                                        ** 
        these are for the other side of a reboot, that is the child
        awakening and loading in (if needed) the reboot info 
 **                                                                        **/


   /* a coupla specilized functions as its easier to spoon them and include
      here than try to including modification procs */

void trans_to_quiet(player * p, char *str)
{
  room *r;
  player *previous, *scan;

  r = convert_room(p, str);
  if (!r)
    return;
  if (r == p->location)
    return;
  if (p->location)
  {
    previous = 0;
    scan = p->location->players_top;
    while (scan && scan != p)
    {
      previous = scan;
      scan = scan->room_next;
    }
    if (!scan)
      log("error", "Bad Location list 2");
    else if (!previous)
    {
      p->location->players_top = p->room_next;
      if (!(p->location->players_top))
	compress_room(p->location);
    }
    else
      previous->room_next = p->room_next;
  }
  p->room_next = r->players_top;
  r->players_top = p;
  p->location = r;
}

player *create_player_template(player * t)
{
  player *p;

  p = (player *) MALLOC(sizeof(player));
  memcpy(p, t, sizeof(player));

  /* reset some parseing varibles */
  memset(p->ibuffer, 0, IBUFFER_LENGTH);
  p->column = p->ibuff_pointer = 0;

  /* just in case, NULL all possible pointers */
  p->hash_next = p->flat_next = p->flat_previous =
    p->room_next = 0;
  p->saved = 0;
  p->location = 0;
  p->edit_info = 0;
  p->input_to_fn = 0;
  p->timer_fn = 0;
  p->ttt_opponent = 0;
  p->gag_top = 0;
  p->command_used = 0;
  p->social = 0;


  if (flatlist_start)
    flatlist_start->flat_previous = p;
  p->flat_next = flatlist_start;
  flatlist_start = p;

  p->hash_next = hashlist[0];
  hashlist[0] = p;
  p->hash_top = 0;
  p->timer_fn = 0;
  p->timer_count = -1;
  p->edit_info = 0;

  return p;
}

int retrieve_sysinfo(void)
{
  talker_system_info tsi;
  FILE *f;


  memset(&tsi, 0, sizeof(talker_system_info));

  f = fopen(TALKER_SYSINFO_FILE, "r");
  if (!f)
  {
    LOGF("error", "Failed to open reboot system info file for read [%s]",
	 strerror(errno));
    return -1;
  }
  fread((void *) &tsi, sizeof(talker_system_info), 1, f);
  fclose(f);

  main_descriptor = tsi.main_fd;
  alive_descriptor = tsi.angel_fd;
  up_date = tsi.up_date;
  logins = tsi.logins;
  sys_flags = tsi.sys_flags;
  session_reset = tsi.session_reset;
  strncpy(session, tsi.session, MAX_SESSION - 1);
  strncpy(sess_name, tsi.sess_name, MAX_NAME - 1);
  strncpy(rebooter, tsi.rebooter, MAX_NAME - 1);

  return 0;
}



void reattach_player(player * info)
{
  player *p, *previous, *scan;
  saved_player *sp;
  int hash;

  p = create_player_template(info);
  load_player(p);

  /* based on code from plists.c : link_to_program() */
  previous = 0;
  scan = hashlist[0];
  while (scan && scan != p)
  {
    previous = scan;
    scan = scan->hash_next;
  }
  if (!scan)
    log("error", "Bad non-name hash list");
  else if (!previous)
    hashlist[0] = p->hash_next;
  else
    previous->hash_next = p->hash_next;


  hash = (int) (p->lower_name[0]) - (int) 'a' + 1;
  p->hash_next = hashlist[hash];
  hashlist[hash] = p;
  p->hash_top = hash;

  p->flags |= PROMPT;
  p->timer_fn = 0;
  p->timer_count = -1;


  if (p->residency != NON_RESIDENT)
  {
    p->logged_in = 1;
    sp = p->saved;
  }
  current_players++;

  trans_to_quiet(p, p->location_string);
  if (!(p->location))
    trans_to_quiet(p, sys_room_id(ENTRANCE_ROOM));

  if (p->saved)
  {
    decompress_list(p->saved);
    decompress_alias(p->saved);
    decompress_item(p->saved);
    p->system_flags = p->saved->system_flags;
    p->tag_flags = p->saved->tag_flags;
    p->custom_flags = p->saved->custom_flags;
    p->misc_flags = p->saved->misc_flags;
    if (!(p->pennies))
      p->pennies = p->saved->pennies;
  }

}

void retrieve_players(void)
{
  FILE *f, *pf;
  char namein[160];
  char *oldstack = stack;
  int ret;
  player spanky;

  f = fopen(PLAYER_LIST_FILE, "r");
  if (!f)
  {
    log("error", "When rebooting, in retrieve_players, fopen failed.");
    return;
  }
  memset(namein, 0, 160);
  ret = fscanf(f, "%s", namein);
  while (ret && ret != EOF)
  {
    sprintf(stack, "%s/%s", REBOOTING_DIR, namein);
    stack = end_string(stack);
    pf = fopen(oldstack, "r");
    stack = oldstack;

    if (!pf)
      LOGF("error", "Failed to open reboot player info read (%s) [%s]",
	   namein, strerror(errno));
    else
    {
      fread((void *) &spanky, sizeof(player), 1, pf);
      fclose(pf);
      reattach_player(&spanky);
    }
    memset(namein, 0, 160);
    ret = fscanf(f, "%s", namein);
  }
}


int possibly_reboot(void)
{
  player *p;
  FILE *f;
  char r[16];
  char reboot_msg[256] = "";	/* Set this to your reboot message (if any) */

  f = fopen(CHILDS_PID_FILE, "r");
  if (!f)
    return 0;			/* no child pid file, fuggit */

  memset(r, 0, 16);
  fgets(r, 15, f);
  if (getpid() != atoi(r))
    return 0;

  SUWALL(" -=*> Starting reboot ...\n");
  log("boot", "Rebooting ...");

  if (reboot_msg[0])
    raw_wall(reboot_msg);

  retrieve_sysinfo();
  retrieve_players();


#ifdef ALLOW_MULTIS
  reboot_load_multis();
#endif

  system("rm -f " REBOOTING_DIR "/*");

  if (sys_flags & PANIC)
  {
    sys_flags &= ~PANIC;	/* no need to panic any more */
    if (rebooter[0])
    {
      p = find_player_absolute_quiet(rebooter);
      if (p)
      {
	SW_BUT(p, " -=*> %s managed to crash the talk server.\n", p->name);
	SW_BUT(p, " -=*> %s rebooted and back on track.\n\n",
	       get_config_msg("talker_name"));
	tell_player(p, "\n\n"
		    LINE
		    "\nThis command has caused a system error to occur.\n"
	"Your connection has been terminated to prevent the code crashing.\n"
		    "Please avoid usage of this command until we get it fixed. Sorry!\n\n"
		    LINE
		    "\n\007\n");
	quit(p, 0);
      }
      else
	SUWALL(" -=*> Just crashed without a current_player.\n"
	       " -=*> %s rebooted and back on track.\n\n",
	       get_config_msg("talker_name"));
    }
  }

  SUWALL(" -=*> Reboot of %s successful.\n", get_config_msg("talker_name"));


  reboot_date = time(0);

  return 1;
}

void reboot_version(void)
{
  sprintf(stack, " -=*> Seamless Reboot v0.5b (by phypor) enabled.\n");
  stack = strchr(stack, 0);
}

#ifdef ALLOW_MULTIS
int reboot_save_multis(void)
{
  multi *mscan = all_multis;
  multiplayer *pscan;
  int multis_total;
  FILE *reboot_multi_file = fopen("junk/rebooting/multi", "w");

  if (!reboot_multi_file)
  {
    log("reboot", "Couldn't open junk/rebooting/multi for writing");
    return 1;
  }

  multis_total = multi_count();

  /* amount of multis */
  fprintf(reboot_multi_file, "%d\n", multis_total);

  while (mscan)
  {
    fprintf(reboot_multi_file, "%d,%d,%d,%d\n", mscan->number,
	    mscan->multi_flags, mscan->multi_idle, players_on_multi(mscan));
    pscan = mscan->players_list;
    while (pscan)
    {
      if (pscan->the_player)
	fprintf(reboot_multi_file, "%s\n", pscan->the_player->lower_name);
      else
	fprintf(reboot_multi_file, "%s\n", "<unknown>");

      pscan = pscan->next_player;
    }

    mscan = mscan->next_multi;
  }

  /* actually save it */
  fflush(reboot_multi_file);
  fclose(reboot_multi_file);

  return 0;
}

void reboot_load_multis(void)
{
  multi *new = NULL, *old = NULL;
  multiplayer *newp = NULL, *oldp = NULL;
  char *currpos, *nextpos;
  player *p;
  int num_multis = 0, m_number = 0, m_flags = 0, m_players = 0;
  int m_idle = 0;
  char m_playername[MAX_NAME];
  file reboot_multi_file = load_file("junk/rebooting/multi");

  if (!(reboot_multi_file.where))
  {
    log("reboot", "Couldn't open junk/rebooting/multi for reading");
    return;
  }

  /* get first line - amount of multis */
  currpos = reboot_multi_file.where;
  nextpos = strchr(currpos, '\n');
  *nextpos += 0;
  num_multis = atoi(currpos);

  while (num_multis)
  {
    old = new;
    new = (multi *) MALLOC(sizeof(multi));
    if (old)
      old->next_multi = new;
    else
      all_multis = new;

    /* get first line of multi definition */
    currpos = nextpos;
    nextpos = strchr(currpos, ',');
    *nextpos++ = 0;
    m_number = atoi(currpos);
    currpos = nextpos;
    nextpos = strchr(currpos, ',');
    *nextpos++ = 0;
    m_flags = atoi(currpos);
    currpos = nextpos;
    nextpos = strchr(currpos, ',');
    *nextpos++ = 0;
    m_idle = atoi(currpos);
    currpos = nextpos;
    nextpos = strchr(currpos, '\n');
    *nextpos++ = 0;
    m_players = atoi(currpos);

    new->number = m_number;
    new->multi_flags = m_flags;
    new->multi_idle = m_idle;
    new->players_list = NULL;
    new->next_multi = NULL;

    oldp = NULL;
    newp = NULL;

    while (m_players)
    {
      oldp = newp;
      newp = (multiplayer *) MALLOC(sizeof(multiplayer));
      if (oldp)
	oldp->next_player = newp;
      else
	new->players_list = newp;

      /* get player name */
      currpos = nextpos;
      nextpos = strchr(currpos, '\n');
      *nextpos++ = 0;
      strcpy(m_playername, currpos);

      p = find_player_global_quiet(m_playername);
      newp->the_player = p;
      newp->next_player = NULL;
      m_players--;
    }
    num_multis--;
  }
}
#endif /* ALLOW_MULTIS */

#endif /* SEAMLESS_REBOOT */