/* xenix.c */

#include "copyright.h"

/* Hacked by Lawrence Foard to make a Xenix interface for TinyMUD */

/*#include <stdio.h>*/
#include <prototypes.h>
#include <sys/signal.h>
#include <stdio.h>
#include <sys_2.3/types.h>
#include <sys/file.h>
#include <sys/timeb.h>
#include <sys/times.h>
/*#include <signal.h>*/
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/errno.h>
#include <ctype.h>

#include "config.h"
#include "db.h"
#include "interface.h"
#include "fifo.h"
char ccom[1204];
dbref cplr;

extern int errno;
int shutdown_flag = 0;

static const char *connect_fail = "Either that player does not exist, or has a different password.\n";
static const char *create_fail = "Either there is already a player with that name, or that name is illegal.\n";
static const char *flushed_message = "<Output Flushed>\n";
static const char *shutdown_message = "Going down - Bye\n";

/* size of user input and output buffers */
#define IBUF 1024
#define OBUF 4096
/* how many I/O descripters? */
#define MAXDES 32
/* max prefix+suffix length */
#define MAXSUF 100
void set_signals ();
int bailout ();
void shovechars ();
void make_nonblocking ();
void welcome_user ();
void check_connect ();
void parse_connect ();
void close_sockets ();
void dump_users ();
void announce_connect ();
void announce_disconnect ();
void process_commands ();

struct descriptor_data
{
  int descriptor;
  int connected;
  dbref player;
  char output_prefix[MAXSUF];
  char output_suffix[MAXSUF];
  long connected_at;
  long last_time;
  FIFO in;
  FIFO out;
};

struct descriptor_data des[MAXDES];
int topdes = 0;

#ifndef BOOLEXP_DEBUGGING
void 
main (argc, argv)
     int argc;
     char **argv;
{
  char buff[100];
  FILE *fi;
  int a;
  if (argc < 3)
    {
      fprintf (stderr, "Usage: %s infile dumpfile [port]\n", *argv);
      exit (1);
    }
  if (init_game (argv[1], argv[2]) < 0)
    {
      fprintf (stderr, "Couldn't load %s!\n", argv[1]);
      exit (2);
    }
  set_signals ();
  /* go do it */
  if (!(fi = fopen ("ports.dat", "r")))
    {
      fprintf (stderr, "ports.dat was not found\n");
      exit (1);
    }
  while (fgets (buff, 99, fi))
    {
      int fd;
      /* get rid of \n */
      if (*buff)
	buff[strlen (buff) - 1] = 0;
      if ((fd = open (buff, O_RDWR)) == -1)
	fprintf (stderr, "Warning-Couldn't open I/O device %s\n", buff);
      else
	{
	  initializesock (&des[topdes++], fd);
	  fprintf (stderr, "Inited: %s\n", buff);
	}
    }
  fclose (fi);
  shovechars ();
  close_sockets ();
  dump_database ();
  exit (0);
}

#endif /*BOOLEXP_DEBUGGING*/

void 
set_signals ()
{
  int dump_status (void);

  /* we don't care about SIGPIPE, we notice it in select() and write() */
/*    signal (SIGPIPE, SIG_IGN);*/

  /* standard termination signals */
  signal (SIGINT, bailout);
  signal (SIGTERM, bailout);

  /* catch these because we might as well */
  signal (SIGQUIT, bailout);
  signal (SIGILL, bailout);
  signal (SIGTRAP, bailout);
  signal (SIGIOT, bailout);
  signal (SIGEMT, bailout);
  signal (SIGFPE, bailout);
  signal (SIGBUS, bailout);
  signal (SIGSEGV, bailout);
  signal (SIGSYS, bailout);
  signal (SIGTERM, bailout);
/*    signal (SIGXCPU, bailout);*/
/*    signal (SIGXFSZ, bailout);*/
/*    signal (SIGVTALRM, bailout);*/
  signal (SIGUSR2, bailout);

  /* status dumper (predates "WHO" command) */
  signal (SIGUSR1, dump_status);
}

/* queue write */
void 
queue_write (d, buf, size)
     struct descriptor_data *d;
     char *buf;
     int size;
{
  fi_write (&d->out, buf, size);
}

void 
queue_string (d, str)
     struct descriptor_data *d;
     char *str;
{
  fi_write (&d->out, str, strlen (str));
}

void 
raw_notify (player, msg)
     dbref player;
     const char *msg;
{
  struct descriptor_data *d;
  int a;
  for (a = 0; a < topdes; a++)
    {
      d = &des[a];
      if (d->connected && d->player == player)
	{
	  queue_string (d, msg);
	  queue_write (d, "\n", 1);
	}
    }
}

/* Everything happens here.... */
void 
shovechars ()
{
  struct descriptor_data *d, *dnext;
  int a = 0, b;
  while (!shutdown_flag)
    {
      /* The manual says Xenix has Select and the compiler claims it doesn't
         so I gave up and kludged it instead. If you can find the missing
         select call it would probably work alots better than this.*/

      /* wait 1 tenth of a second then poll the streams */
      if (!test_top ())
	nap (100);
      else
	do_top () && do_top && do_top ();
      if (shutdown_flag)
	break;
      for (b = 0; b < topdes; b++)
	{
	  d = &des[b];
	  /* only check non connected streams every tenth poll */
	  if (!d->connected && a != 10)
	    continue;
	  if (!process_input (d))
	    {
	      fprintf (stderr, "pipe input error %d err %d\n", d->descriptor, errno);
/*        shutdownsock(d);
        continue;*/
	    }
	  if (!process_output (d))
	    {
	      fprintf (stderr, "pipe output error %d\n", errno);
/*        shutdownsock(d);
        continue;*/
	    }
	}
      if (a++ == 10)
	a = 0;
      process_commands ();
      dispatch ();
    }
}

void 
shutdownsock (d)
     struct descriptor_data *d;
{
  int dd;
  if (d->connected)
    {
      fprintf (stderr, "DISCONNECT descriptor %d player %s(%d)\n",
	       d->descriptor, db[d->player].name, d->player);
      announce_disconnect (d->player);
    }
  else
    {
      fprintf (stderr, "DISCONNECT descriptor %d never connected\n",
	       d->descriptor);
    }
  fi_close (&d->in);
  fi_close (&d->out);
  initializesock (d, d->descriptor);	/* restart stream */
}

initializesock (d, s)
     struct descriptor_data *d;
     int s;
{
  d->descriptor = s;
  d->connected = 0;
  make_nonblocking (s);
  *d->output_prefix = 0;
  *d->output_suffix = 0;
  d->last_time = 0;
  fi_open (&d->in, MAX_INPUT);
  fi_open (&d->out, MAX_OUTPUT);
  welcome_user (d);
}

int 
process_output (d)
     struct descriptor_data *d;
{
  int cnt, hope;
  char *buff;
  if (!fi_readok (&d->out))
    return (1);
  /* write as much as possible */
  if (!(hope = fi_rread (&d->out, &buff)))
    return (1);
  cnt = write (d->descriptor, buff, hope);
  if (cnt < 0)
    {
      /* flush fifo */
      fi_flush (&d->out);
      return (0);
    }
  fi_munch (&d->out, cnt);
  /* if everything was written try to write some more */
  return ((cnt == hope) ? process_output (d) : 1);
}

void 
make_nonblocking (s)
     int s;
{
  if (fcntl (s, F_SETFL, FNDELAY) == -1)
    {
      perror ("make_nonblocking: fcntl");
      panic ("FNDELAY fcntl failed");
    }
}

void 
welcome_user (d)
     struct descriptor_data *d;
{
  queue_string (d, WELCOME_MESSAGE);
}

int 
process_input (d)
     struct descriptor_data *d;
{
  char buf[1024];
  int got;
  /* make sure we can accept more input first */
  if (!fi_writeok (&d->in))
    return (1);
  got = read (d->descriptor, buf, sizeof buf);
  if (got <= 0)
    {
      /* if interrupted system call ignore error */
      if ((errno == 5) || (errno == EINTR))
	return 1;
      else
	return 0;
    }
  fi_write (&d->in, buf, got);
}

void 
process_commands ()
{
  int nprocessed;
  struct descriptor_data *d, *dnext;
  struct text_block *t;
  char buff[1024];
  int a;
  for (a = 0; a < topdes; a++)
    {
      d = &des[a];
      if (fi_readok (&d->in) &&fi_gets (&d->in, buff, 1024)
	  &&!do_command (d, buff))
	shutdownsock (d);
    }
}

int 
do_command (d, command)
     struct descriptor_data *d;
     char *command;
{
  if (!*command)
    return (1);
  if (!strcmp (command, QUIT_COMMAND))
    {
      return 0;
    }
  else if (!strcmp (command, WHO_COMMAND))
    {
      if (d->output_prefix)
	{
	  queue_string (d, d->output_prefix);
	  queue_write (d, "\n", 1);
	}
      dump_users (d);
      if (d->output_suffix)
	{
	  queue_string (d, d->output_suffix);
	  queue_write (d, "\n", 1);
	}
    }
  else if (!strncmp (command, PREFIX_COMMAND, strlen (PREFIX_COMMAND)))
    {
      strcpy (d->output_prefix, command + strlen (PREFIX_COMMAND));
    }
  else if (!strncmp (command, SUFFIX_COMMAND, strlen (SUFFIX_COMMAND)))
    {
      strcpy (d->output_suffix, command + strlen (SUFFIX_COMMAND));
    }
  else
    {
      if (d->connected)
	{
	  if (*d->output_prefix)
	    {
	      queue_string (d, d->output_prefix);
	      queue_write (d, "\n", 1);
	    }
	  strcpy (ccom, command);
	  cplr = d->player;
	  process_command (d->player, command);
	  if (*d->output_suffix)
	    {
	      queue_string (d, d->output_suffix);
	      queue_write (d, "\n", 1);
	    }
	}
      else
	{
	  check_connect (d, command);
	}
    }
  return 1;
}

void 
check_connect (d, msg)
     struct descriptor_data *d;
     const char *msg;
{
  char command[MAX_COMMAND_LEN];
  char user[MAX_COMMAND_LEN];
  char password[MAX_COMMAND_LEN];
  dbref player;

  parse_connect (msg, command, user, password);

  if (!strncmp (command, "co", 2))
    {
      player = connect_player (user, password);
      if (player == NOTHING)
	{
	  queue_string (d, connect_fail);
	  fprintf (stderr, "FAILED CONNECT %s on descriptor %d\n",
		   user, d->descriptor);
	}
      else
	{
	  fprintf (stderr, "CONNECTED %s(%d) on descriptor %d\n",
		   db[player].name, player, d->descriptor);
	  d->connected = 1;
	  d->connected_at = time (0);
	  d->player = player;
	  do_look_around (player);
	  announce_connect (player);
	}
    }
  else if (!strncmp (command, "cr", 2))
    {
      player = create_player (user, password);
      if (player == NOTHING)
	{
	  queue_string (d, create_fail);
	  fprintf (stderr, "FAILED CREATE %s on descriptor %d\n",
		   user, d->descriptor);
	}
      else
	{
	  fprintf (stderr, "CREATED %s(%d) on descriptor %d\n",
		   db[player].name, player, d->descriptor);
	  d->connected = 1;
	  d->connected_at = time (0);
	  d->player = player;
	  do_look_around (player);
	  announce_connect (player);
	}
    }
  else
    {
      welcome_user (d);
    }
}

void 
parse_connect (msg, command, user, pass)
     const char *msg;
     char *command;
     char *user;
     char *pass;
{
  char *p;

  while (*msg && isascii (*msg) && isspace (*msg))
    msg++;
  p = command;
  while (*msg && isascii (*msg) && !isspace (*msg))
    *p++ = *msg++;
  *p = '\0';
  while (*msg && isascii (*msg) && isspace (*msg))
    msg++;
  p = user;
  while (*msg && isascii (*msg) && !isspace (*msg))
    *p++ = *msg++;
  *p = '\0';
  while (*msg && isascii (*msg) && isspace (*msg))
    msg++;
  p = pass;
  while (*msg && isascii (*msg) && !isspace (*msg))
    *p++ = *msg++;
  *p = '\0';
}

void 
close_sockets ()
{
  struct descriptor_data *d, *dnext;
  int a;
  for (a = 0; a < topdes; a++)
    {
      d = &des[a];
      write (d->descriptor, shutdown_message, strlen (shutdown_message));
      close (d->descriptor);
    }
}

void 
emergency_shutdown ()
{
  close_sockets ();
}

void 
boot_off (player)
     dbref player;
{
  struct descriptor_data *d;
  int a;
  for (a = 0; a < topdes; a++)
    {
      d = &des[a];
      if (d->connected && d->player == player)
	shutdownsock (d);
    }
}

int 
bailout (sig, code, scp)
     int sig;
     int code;
     struct sigcontext *scp;
{
  char message[1024];

  sprintf (message, "BAILOUT: caught signal %d code %d", sig, code);
  panic (message);
  _exit (7);
  return 0;
}

int 
dump_status ()
{
/*    struct descriptor_data *d;
    long now;

    (void) time (&now);
    fprintf (stderr, "STATUS REPORT:\n");
    for (d = descriptor_list; d; d = d->next) {
	if (d->connected) {
	    fprintf (stderr, "PLAYING descriptor %d player %s(%d)",
		     d->descriptor, db[d->player].name, d->player);

	    if (d->last_time)
		fprintf (stderr, " idle %d seconds\n",
			 now - d->last_time);
	    else
		fprintf (stderr, " never used\n");
	} else {
	    fprintf (stderr, "CONNECTING descriptor %d", d->descriptor);
	    if (d->last_time)
		fprintf (stderr, " idle %d seconds\n",
			 now - d->last_time);
	    else
		fprintf (stderr, " never used\n");
	}
    }
    return 0;*/
}

void 
dump_users (e)
     struct descriptor_data *e;
{
  struct descriptor_data *d;
  long now;
  int a;
  char buf[1024];

  time (&now);
  queue_string (e, "Player Name          On For Idle\n");
  for (a = 0; a < topdes; a++)
    {
      d = &des[a];
      if (d->connected)
	{
/*	    sprintf (buf, "%-16s %10s %4s",
		     db[d->player].name,
		     time_format_1(now - d->connected_at),
		     time_format_2(now - d->last_time));*/
	  sprintf (buf, "%s", db[d->player].name);
	  queue_string (e, buf);
	  queue_write (e, "\n", 1);
	}
    }
}

void 
announce_connect (player)
     dbref player;
{
  dbref loc;
  char buf[BUFFER_LEN];

  if ((loc = getloc (player)) == NOTHING)
    return;
  if (Dark (player) || Dark (loc))
    return;

  sprintf (buf, "%s has connected.", db[player].name);

  notify_except (db[loc].contents, player, buf);
  db[player].flags |= PLAYER_CONNECT;
}

void 
announce_disconnect (player)
     dbref player;
{
  dbref loc;
  int num, a;
  char buf[BUFFER_LEN];

  if ((loc = getloc (player)) == NOTHING)
    return;
  if (Dark (player) || Dark (loc))
    return;

  sprintf (buf, "%s has disconnected.", db[player].name);

  notify_except (db[loc].contents, player, buf);
  for (num = a = 0; a < MAXDES; a++)
    if (des[a].connected && (des[a].player == player))
      num++;
  if (num < 2)
    db[player].flags &= ~PLAYER_CONNECT;
}