/*
 *	$Source: /afs/athena.mit.edu/user/j/jik/sipbsrc/src/gameserver/RCS/gameserver.c,v $
 *	$Header: /afs/athena.mit.edu/user/j/jik/sipbsrc/src/gameserver/RCS/gameserver.c,v 1.20 89/11/28 21:40:02 jik Exp $
 */

#ifndef lint
static char *rcsid_gameserver_c = "$Header: /afs/athena.mit.edu/user/j/jik/sipbsrc/src/gameserver/RCS/gameserver.c,v 1.20 89/11/28 21:40:02 jik Exp $";
#endif lint

#include <sys/file.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <syslog.h>

#include "messagecodes.h"
#include "user.h"
#include "game_data.h"

#define BUFLEN 4096

#define MAX_RUNNING_GAMES 8

#define ERRLOG "/usr/potluck/errlog"

char *game_data_file = SRV_DATA_FILE;

struct game_info running_games[MAX_RUNNING_GAMES];
static struct game_info rg_netbyte[MAX_RUNNING_GAMES];
int game_sockets[MAX_RUNNING_GAMES];
int num_of_running_games = 0;

#define MAX_KNOWN_GAMES 20
#define MAX_NAME_LENGTH 100

struct game_data known_games[MAX_KNOWN_GAMES];
int num_of_known_games = 0;

#define MAX_CLIENTS 5

int clients[MAX_CLIENTS];
int num_of_clients = 0;
fd_set selectmask;

int got_a_signal = 0;
char *program_name = NULL;

char *environment[5] = {
 "USER=default",
 "HOME=/mit/d/e/default",
 "SHELL=/bin/csh",
 "PATH=/usr/games:/bin:/etc:/usr/bin:/usr/ucb:/usr/local:/usr/athena:/usr/unsupported/sipb",
 NULL};

static unsigned long nlen;		/* for network byte order */
static unsigned short nslen;

char *rindex();

main(argc, argv)
int argc;
char **argv;
{
    extern struct game_data known_games[];
    extern fd_set selectmask;
    extern int num_of_known_games, num_of_clients, clients[];
    extern int got_a_signal;
    extern int chld_handler(), io_handler();

    fd_set readmask;
    int ear, num_of_fds, num_ready, fd, new_client, old_mask;
    struct servent *sp;
    struct sockaddr_in sin;

#ifndef DEBUG
	if (fork())
		exit(0);
	{ int s;
	  for (s = 0; s < 3; s++)
	     (void) close(s);
	  (void) open("/dev/null", O_RDONLY);
	  (void) open("/dev/null", O_WRONLY);
	  (void) open(ERRLOG, O_WRONLY|O_APPEND|O_CREAT, 0666);
	  s = open("/dev/tty", O_RDWR);
	  if (s >= 0) {
		ioctl(s, TIOCNOTTY, 0);
		(void) close(s);
	  }
	  (void) dup2(2, 1);
	}
#endif
    program_name = rindex(argv[0], '/');
    if (!program_name) program_name = argv[0];
    else program_name++;
#ifdef mips
    openlog(program_name, LOG_PID);
#else
    openlog(program_name, LOG_PID, LOG_LOCAL3);
#endif
    syslog(LOG_ERR, "starting");

    if ((num_of_known_games = get_all_game_data(known_games, MAX_KNOWN_GAMES)) <= 0) {
        syslog(LOG_ERR, "cannot read game data\n");
        exit(1);
    }

    signal(SIGCHLD, chld_handler);
    signal(SIGIO, io_handler);

#ifndef GAMESERVER_PORT
    sp = getservbyname("gameserver", "tcp");
    if (sp == NULL) {
        syslog(LOG_ERR, "cannot locate service port\n");
        exit(1);
    }

    sin.sin_port = sp->s_port;
#else
    sin.sin_port = GAMESERVER_PORT;
#endif

    sin.sin_addr.s_addr = 0;
    bzero(sin.sin_zero, sizeof(sin.sin_zero));

    ear = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(ear, SOL_SOCKET, SO_REUSEADDR, (int *) NULL, (int *) NULL);
    if (bind(ear, (caddr_t) &sin, sizeof(sin)) < 0) {
        syslog(LOG_ERR, "cannot bind socket to port\n");
        close(ear);
        exit(1);
    }

    syslog(LOG_INFO,"started");
    listen(ear, 5);
    FD_ZERO(&selectmask);
    FD_SET(ear, &selectmask);
    num_of_fds = ear + 1;

    while (1) {
        readmask = selectmask;
        num_ready = select(num_of_fds, &readmask, 0, 0, 0);
        if (num_ready > 0) {
            old_mask = sigblock(sigmask(SIGCHLD));
            for (fd = 0; fd < num_of_fds; fd++) {
                if (FD_ISSET(fd, &readmask)) {
                    if (fd == ear) {
                        new_client = accept(ear, NULL, 0);
                        if (new_client < 0)
                            syslog(LOG_ERR, "ghost client?\n");
                        else {
                            clients[num_of_clients++] = new_client;
			    FD_SET(new_client, &selectmask);
                            if (new_client >= num_of_fds)
                                num_of_fds = new_client + 1;
                        }
                    }
                    else
                        handle_request(fd);
                }
            }
            sigsetmask(old_mask);
        }

        if (got_a_signal) {
            handle_signal(got_a_signal);
            got_a_signal = 0;
        }
    }
}


struct game_data *find_game_data(game_type)
int game_type;
{
    extern int num_of_known_games;
    extern struct game_data known_games[];

    int i;

    for (i = 0; i < num_of_known_games; i++)
        if (known_games[i].type == game_type) return(&known_games[i]);

    return(NULL);
}


int create_game(client, game_type)
int client;
int game_type;
{
    extern int num_of_running_games;
    extern struct game_info running_games[];
    extern int game_sockets[];
    extern char *environment[];

    unsigned char message_code;
    int sp[2], game_pid;
    struct game_data *game;

    if (num_of_running_games >= MAX_RUNNING_GAMES) {
        syslog(LOG_ERR, "too many games\n");
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        return(0);
    }

    if ((game = find_game_data(game_type)) == NULL) {
        syslog(LOG_ERR, "unknown game type: %d\n", game_type);
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        return(0);
    }

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) < 0) {
        perror("gameserver: cannot open socketpair to subfork");
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        return(0);
    }

    if (game_pid = vfork()) {
        if (game_pid == -1) {
            syslog(LOG_ERR, "cannot vfork\n");
            message_code = MC_ERROR;
            write(client, &message_code, sizeof(message_code));
            return(0);
        }
        else {
            game_sockets[num_of_running_games] = sp[0];
            running_games[num_of_running_games].type = game_type;
            running_games[num_of_running_games].id = game_pid;
            num_of_running_games++;
            close(sp[1]);
            return(join_game(client, game_pid));
        }
    }
    else {
        if (dup2(sp[1], GAMESERVER_FD) < 0) {
	    syslog(LOG_ERR, "child couldn't dup2 socket to gameserver\n");
            exit(1);
        }

        close(sp[0]);
        close(sp[1]);

        /* Do it. */
        execle(game->master_path, game->master_path, 0, environment);
        syslog(LOG_ERR, "couldn't execl %s\n", game->master_path);
        return /* XXX */ _exit(1);
    }
}


int join_game(client, game_id)
int client;
int game_id;
{
    extern struct game_info running_games[];
    extern int num_of_running_games;

    int game_sock;
    unsigned char message_code;
    struct sockaddr_in sockaddr;
    int i;

    for (i = 0; i < num_of_running_games; i++) {
        if (running_games[i].id == game_id) break;
    }

    if (i >= num_of_running_games) {
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        return(0);
    }

    game_sock = game_sockets[i];
    message_code = MC_NEW_PLAYER;
    write(game_sock, &message_code, sizeof(message_code));
    if (safe_read(game_sock, &message_code, sizeof(message_code)) < 0) {
        remove_game_by_socket(game_sock);
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        return(0);
    }
    switch (message_code) {
    case MC_ERROR:
        write(client, &message_code, sizeof(message_code));
        return(0);
    case MC_OK:
        write(client, &message_code, sizeof(message_code));
	/* here we just pass it along; it's already in net byte order */
        safe_read(client, &sockaddr, sizeof(sockaddr));
        write(game_sock, &sockaddr, sizeof(sockaddr));
        remove_client(client);
        return(1);
    default:
    	syslog(LOG_ERR, "bogus new_player reply %d\n", message_code);
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        return(0);
    }
}

find_game(client, game_type)
int client, game_type;
{
    extern struct game_info running_games[];

    register int i;

    for (i = 0; i < num_of_running_games; i++) {
	if (running_games[i].type == game_type) {
	    if (join_game(client, running_games[i].id)) return(1);
	}
    }
    return(create_game(client, game_type));
}


handle_request(client)
int client;
{
    int game_id, game_type;
    unsigned char message_code;

    if (safe_read(client, &message_code, sizeof(message_code)) < 0) {
        remove_client(client);
        return;
    }

    switch (message_code) {
    case MC_LIST_GAMES:
#ifdef DEBUG
        syslog(LOG_DEBUG, "list_games request from client %d\n", client);
#endif
        list_games(client);
        break;
    case MC_QUERY_GAME:
        if (safe_read(client, &game_id, sizeof(game_id)) < 0) {
	    remove_client(client);
	    return;
	}
	nlen = ntohl(game_id);
#ifdef DEBUG
        syslog(LOG_DEBUG, "query_game request, client %d, game_id %d\n",
        client, nlen);
#endif
        query_game(client, nlen);
        break;
    case MC_JOIN_GAME:
        if (safe_read(client, &game_id, sizeof(game_id)) < 0) {
	    remove_client(client);
	    return;
	}
	nlen = ntohl(game_id);
#ifdef DEBUG
	syslog(LOG_DEBUG, "join_game request, client %d, game_id %d\n",
        client, nlen);
#endif
        join_game(client, nlen);
        break;
    case MC_CREATE_GAME:
        if (safe_read(client, &game_type, sizeof(game_type)) < 0) {
	    remove_client(client);
	    return;
	}
	nlen = ntohl(game_type);
#ifdef DEBUG
        syslog(LOG_DEBUG, "create_game request, client %d, game_type %d\n",
        client, nlen);
#endif
        create_game(client, nlen);
        break;
    case MC_FIND_GAME:
        if (safe_read(client, &game_type, sizeof(game_type)) < 0) {
	    remove_client(client);
	    return;
	}
	nlen = ntohl(game_type);
#ifdef DEBUG
	syslog(LOG_DEBUG, "find_game request, client %d, game_type %d\n",
	    client, nlen);
#endif
	find_game(client, nlen);
	break;
    default:
        syslog(LOG_ERR, "bad message code: %d\n",
        message_code);
        message_code = MC_ERROR;
        write(client, &message_code, sizeof(message_code));
        break;
    }
}

int query_game(client, game_id)
int client, game_id;
{
    static char buf[BUFLEN];
    int i, game_sock;
    unsigned char message_code;
    unsigned short message_length;

    for (i = 0; i < num_of_running_games; i++) {
        if (running_games[i].id == game_id) {
            game_sock = game_sockets[i];
            message_code = MC_GAME_QUERY;
            write(game_sock, &message_code, sizeof(message_code));
            if (safe_read(game_sock, &message_code, sizeof(message_code)) < 0) {
                remove_game_by_socket(game_sock);
            }
            if (message_code != MC_QUERY_RESPONSE) {
                message_code = MC_ERROR;
                write(client, &message_code, sizeof(message_code));
                return(-1);
            }
            if (safe_read(game_sock, &message_length, sizeof(message_length)) < 0) {
		remove_game_by_socket(game_sock);
                message_code = MC_ERROR;
                write(client, &message_code, sizeof(message_code));
                return(-1);
	    }
	    nslen = ntohs(message_length);
            if (safe_read(game_sock, buf, nslen) < 0) {
		remove_game_by_socket(game_sock);
                message_code = MC_ERROR;
                write(client, &message_code, sizeof(message_code));
                return(-1);
	    }
            write(client, &message_code, sizeof(message_code));
	    /* message_length is still net order */
            write(client, &message_length, sizeof(message_length));
            write(client, buf, nslen);
            return(0);
        }
    }
    return(-1);
}

list_games(client)
int client;
{
    extern struct game_info running_games[MAX_RUNNING_GAMES];
/* global to this file...
  extern struct game_info rg_netbyte[MAX_RUNNING_GAMES];
*/
    unsigned char message_code;
    unsigned short message_length;

    netfix_games(running_games, rg_netbyte);

    message_code = MC_GAME_LIST;
    message_length = num_of_running_games * sizeof(struct game_info);
    write(client, &message_code, sizeof(message_code));
    nslen = htonl(message_length);
    write(client, &nslen, sizeof(nslen));
    write(client, running_games, message_length);
}

netfix_games(hostg, netg)
struct game_info hostg[], netg[];
{
	register int i;
	for (i = 0; i < MAX_RUNNING_GAMES; i++) {
		netg[i].type = htonl(hostg[i].type);
		netg[i].id = htonl(hostg[i].id);
	}
	return(0);
}

chld_handler() {
    extern int got_a_signal;
    got_a_signal = SIGCHLD;
}

io_handler() {
    extern int got_a_signal;
    got_a_signal = SIGIO;
}

handle_signal(the_signal)
int the_signal;
{
    extern int num_of_running_games, num_of_known_games;
    extern struct game_data known_games[];    
    extern struct game_info running_games[];

    int i, game_pid;
    union wait status;

    switch (the_signal) {
    case SIGCHLD:
        if ((game_pid = wait3(&status, WNOHANG | WUNTRACED, 0)) > 0) {
            if (WIFEXITED(status) || WIFSIGNALED(status)) {
                for (i = 0; i < num_of_running_games; i++) {
                    if (running_games[i].id = game_pid) {
#ifdef DEBUG
                        syslog(LOG_DEBUG, "game died: %d\n", game_pid);
#endif
			close(game_sockets[i]);
                        num_of_running_games--;
                        running_games[i] = running_games[num_of_running_games];
                        game_sockets[i] = game_sockets[num_of_running_games];
                        break;
                    }
		}
            }
        }
        break;
    case SIGIO:
        if ((num_of_known_games = get_all_game_data(known_games, MAX_KNOWN_GAMES)) <= 0) {
            syslog(LOG_ERR, "reparse of game data file failed\n");
            exit(1);
        }	
        break;
    default:
        break;
    }
}

remove_game_by_socket(game_sock)
int game_sock;
{
    extern struct game_info running_games[];
    extern int game_sockets[];

    int i;

#ifdef DEBUG
    syslog(LOG_DEBUG, "removing game by socket %d\n", game_sock);
#endif
    for (i = 0; i < num_of_running_games; i++) {
        if (game_sockets[i] == game_sock) {
            num_of_running_games--;
            running_games[i] = running_games[num_of_running_games];
            game_sockets[i] = game_sockets[num_of_running_games];
            break;
        }
    }
    close(game_sock);
}

remove_client(client)
int client;
{
    extern fd_set selectmask;
    extern int num_of_clients, clients[];

    int i;

#ifdef DEBUG
    syslog(LOG_DEBUG, "removing client %d\n", client);
#endif
    for (i = 0; i < num_of_clients; i++)
        if (clients[i] == client) {
            num_of_clients--;
            clients[i] = clients[num_of_clients];
	    FD_CLR(client, &selectmask);
            break;
        }
    close(client);
}

