empiremud/cnf/
empiremud/doc/
empiremud/lib/boards/
empiremud/lib/etc/
empiremud/lib/misc/
empiremud/lib/plralias/F-J/
empiremud/lib/plralias/K-O/
empiremud/lib/plralias/P-T/
empiremud/lib/plralias/U-Z/
empiremud/lib/plrobjs/
empiremud/lib/plrobjs/F-J/
empiremud/lib/plrobjs/K-O/
empiremud/lib/plrobjs/P-T/
empiremud/lib/plrobjs/U-Z/
empiremud/lib/world/
empiremud/lib/world/mob/
empiremud/lib/world/obj/
empiremud/log/
/* ************************************************************************
*   File: comm.c                                         EmpireMUD AD 1.0 *
*  Usage: Communication, socket handling, main(), central game loop       *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Code base by Paul Clarke.  EmpireMUD Project, a tbgMUD Production.     *
*  Based upon CircleMUD 3.0, beta patch level 17, by Jeremy Elson.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

#define __COMM_C__

#include "conf.h"
#include "sysdep.h"

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
#include "handler.h"
#include "db.h"
#include "empire.h"
#include "skills.h"

#ifdef HAVE_ARPA_TELNET_H
#include <arpa/telnet.h>
#else
#include "telnet.h"
#endif

#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif

/* externs */
extern struct ban_list_element *ban_list;
extern int num_invalid;
extern char *GREETINGS;
extern const char *version;
extern int wizlock_level;
extern int no_rent_check;
extern FILE *player_fl;
extern ush_int DFLT_PORT;
extern const char *DFLT_DIR;
extern char *LOGNAME;
extern int max_playing;
extern int nameserver_is_slow;

extern struct time_info_data time_info;		/* In db.c */
extern char *help;

/* local globals (I majored in oxymoronism) */
Descr descriptor_list = NULL;			/* master desc list					*/
struct txt_block *bufpool = 0;			/* pool of large output buffers		*/
int buf_largecount = 0;					/* # of large buffers which exist	*/
int buf_overflows = 0;					/* # of overflows of output			*/
int buf_switches = 0;					/* # of switches from small to large buf*/
int empire_shutdown = 0;				/* clean shutdown					*/
int max_players = 0;					/* max descriptors available		*/
int tics = 0;							/* for extern checkpointing			*/
int scheck = 0;							/* for syntax checking mode			*/
struct timeval null_time;				/* zero-valued time structure		*/
FILE *logfile = NULL;					/* Where to send the log messages	*/
bool gain_cond_messsage = FALSE;		/* gain cond send messages			*/

/* Reboot data */
struct reboot_data reboot = { SCMD_REBOOT, 720, 0, 0 };

static bool SoftReboot;
int mother_desc;
ush_int port;

/* functions in this file */
RETSIGTYPE reread_wizlists(int sig);
RETSIGTYPE unrestrict_game(int sig);
RETSIGTYPE reap(int sig);
RETSIGTYPE checkpointing(int sig);
RETSIGTYPE hupsig(int sig);
ssize_t perform_socket_read(socket_t desc, char *read_point,size_t space_left);
ssize_t perform_socket_write(socket_t desc, const char *txt,size_t length);
void echo_off(Descr d);
void echo_on(Descr d);
void empire_sleep(struct timeval *timeout);
int get_from_q(struct txt_q *queue, char *dest, int *aliased);
void init_game(ush_int port);
void signal_setup(void);
void game_loop(socket_t mother_desc);
socket_t init_socket(ush_int port);
int new_descriptor(socket_t s);
int get_max_players(void);
int process_output(Descr t);
int process_input(Descr t);
void timediff(struct timeval *diff, struct timeval *a, struct timeval *b);
void timeadd(struct timeval *sum, struct timeval *a, struct timeval *b);
void flush_queues(Descr d);
void nonblock(socket_t s);
int perform_subst(Descr t, char *orig, char *subst);
int perform_alias(Descr d, char *orig);
char *make_prompt(Descr point);
void heartbeat(int pulse);
void init_descriptor(Descr newd, int desc);
struct in_addr *get_bind_addr(void);
int set_sendbuf(socket_t s);
void setup_log(const char *filename, int fd);
int open_logfile(const char *filename, FILE *stderr_fp);
#if defined(POSIX)
sigfunc *my_signal(int signo, sigfunc * func);
#endif

/* extern fcnts */
void boot_world(void);
void mobile_activity(void);
void perform_violence(void);
void show_string(Descr d, char *input);
int isbanned(char *hostname);

#ifdef __CXREF__
#undef FD_ZERO
#undef FD_SET
#undef FD_ISSET
#undef FD_CLR
#define FD_ZERO(x)
#define FD_SET(x, y) 0
#define FD_ISSET(x, y) 0
#define FD_CLR(x, y)
#endif


/***********************************************************************
*  main game loop and related stuff                                    *
***********************************************************************/

#define plant_magic(x)	do { (x)[sizeof(x) - 1] = MAGIC_NUMBER; } while (0)
#define test_magic(x)	((x)[sizeof(x) - 1])

int main(int argc, char **argv) {
	int pos = 1;
	const char *dir;

	/* Initialize these to check for overruns later. */
	plant_magic(buf);
	plant_magic(buf1);
	plant_magic(buf2);
	plant_magic(arg);

	port = DFLT_PORT;
	dir = DFLT_DIR;

	while ((pos < argc) && (*(argv[pos]) == '-')) {
		switch (*(argv[pos] + 1)) {
			case 'C':
				SoftReboot = TRUE;
				mother_desc = atoi(argv[pos]+2);
				break;
			case 'o':
				if (*(argv[pos] + 2))
					LOGNAME = argv[pos] + 2;
				else if (++pos < argc)
					LOGNAME = argv[pos];
				else {
					puts("SYSERR: File name to log to expected after option -o.");
					exit(1);
					}
				break;
			case 'd':
				if (*(argv[pos] + 2))
					dir = argv[pos] + 2;
				else if (++pos < argc)
					dir = argv[pos];
				else {
					puts("SYSERR: Directory arg expected after option -d.");
					exit(1);
					}
				break;
			case 'c':
				scheck = 1;
				puts("Syntax check mode enabled.");
				break;
			case 'q':
				no_rent_check = 1;
				puts("Quick boot mode -- rent check supressed.");
				break;
			case 'r':
				wizlock_level = 1;
				puts("Restricting game -- no new players allowed.");
				break;
			case 'w':
				wizlock_level = LVL_START_IMM;
				puts("Restricting game.");
				break;
			case 'h':
				/* From: Anil Mahajan <amahajan@proxicom.com> */
				printf("Usage: %s [-c] [-q] [-r] [-o filename] [-d pathname] [port #]\n"
					   "  -c             Enable syntax check mode.\n"
					   "  -d <directory> Specify library directory (defaults to 'lib').\n"
					   "  -h             Print this command line argument help.\n"
					   "  -o <file>      Write log to <file> instead of stderr.\n"
					   "  -q             Quick boot (doesn't scan rent for object limits)\n"
					   "  -r             Restrict MUD -- no new players allowed.\n",
					argv[0]);
				exit(0);
			default:
				printf("SYSERR: Unknown option -%c in argument string.\n", *(argv[pos] + 1));
				break;
			}
		pos++;
		}

	if (pos < argc) {
		if (!isdigit(*argv[pos])) {
			printf("Usage: %s [-c] [-m] [-q] [-r] [-s] [-d pathname] [port #]\n", argv[0]);
			exit(1);
			}
		else if ((port = atoi(argv[pos])) <= 1024) {
			printf("SYSERR: Illegal port number %d.\n", port);
			exit(1);
			}
		}

	/* All arguments have been parsed, try to open log file. */
	setup_log(LOGNAME, STDERR_FILENO);

	/*
	 * Moved here to distinguish command line options and to show up
	 * in the log if stderr is redirected to a file.
	 */
	log(version);

	if (chdir(dir) < 0) {
		perror("SYSERR: Fatal error changing to data directory");
		exit(1);
		}
	log("Using %s as data directory.", dir);

	if (scheck) {
		boot_world();
		log("Done.");
		}
	else {
		log("Running game on port %d.", port);
		init_game(port);
		}

	return (0);
	}


void softreboot_recover(void) {
	extern int enter_player_game(Descr d, int dolog);

	Descr d;
	FILE *fp;
	char host[1024];
	struct char_file_u tmp_store;
	int desc, player_i;
	bool fOld;
	char name[MAX_INPUT_LENGTH];

	log("Soft reboot is recovering players...");

	if (!(fp = fopen(REBOOT_FILE, "r"))) {
		perror("softreboot: fopen");
		log("Reboot file not found.  Exiting.\r\n");
		exit(1);
		}

	unlink(REBOOT_FILE);

	for (;;) {
		fOld = TRUE;
		fscanf(fp, "%d %s %s\n", &desc, name, host);
		if (desc == -1)
			break;

		sprintf(buf, "\r\nRestoring %s...\r\n", name);
		if (write_to_descriptor(desc, buf) < 0) {
			close(desc);
			continue;
			}
		log (" %-20.20s - %s", name, host);
		CREATE(d, struct descriptor_data, 1);
		memset((char *) d, 0, sizeof (struct descriptor_data));
		init_descriptor(d, desc);

		strcpy(d->host, host);
		d->next = descriptor_list;
		descriptor_list = d;

		d->connected = CON_CLOSE;

		CREATE(d->character, struct char_data, 1);
		clear_char(d->character);
		CREATE(d->character->player_specials, struct player_special_data, 1);
		d->character->desc = d;

		if ((player_i = load_char(name, &tmp_store)) >= 0) {
			store_to_char(&tmp_store, d->character);
			GET_PFILEPOS(d->character) = player_i;
			if (!PLR_FLAGGED(d->character, PLR_DELETED))
				REMOVE_BIT(PLR_FLAGS(d->character), PLR_WRITING | PLR_MAILING);
			else
				fOld = FALSE;
			}
		else
			fOld = FALSE;

		if (!fOld) {
			write_to_descriptor(desc, "\r\nSomehow, your character couldn't be loaded.\r\n");
			close_socket(d);
			}
		else {
			write_to_descriptor(desc, "Recovery complete.\r\n\r\n");
			enter_player_game(d, 0);
			d->connected = CON_PLAYING;
			}
		}

	fclose(fp);
	}


/* Init sockets, run game, and cleanup sockets */
void init_game(ush_int port) {
	void empire_srandom(unsigned long initial_seed);

	empire_srandom(time(0));

	log("Finding player limit.");
	max_players = get_max_players();

	if (!SoftReboot) {
		log("Opening mother connection.");
		mother_desc = init_socket(port);
		}

	boot_db();

	log("Signal trapping.");
	signal_setup();

	if (SoftReboot)
		softreboot_recover();

	log("Entering game loop.");
	game_loop(mother_desc);

	Crash_save_all();

	log("Closing all sockets.");
	while (descriptor_list)
		close_socket(descriptor_list);

	CLOSE_SOCKET(mother_desc);
	fclose(player_fl);

	log("Normal termination of game.");
	}


/*
 * init_socket sets up the mother descriptor - creates the socket, sets
 * its options up, binds it, and listens.
 */
socket_t init_socket(ush_int port) {
	socket_t s;
	struct sockaddr_in sa;
	int opt;

	/*
	 * Should the first argument to socket() be AF_INET or PF_INET?  I don't
	 * know, take your pick.  PF_INET seems to be more widely adopted, and
	 * Comer (_Internetworking with TCP/IP_) even makes a point to say that
	 * people erroneously use AF_INET with socket() when they should be using
	 * PF_INET.  However, the man pages of some systems indicate that AF_INET
	 * is correct; some such as ConvexOS even say that you can use either one.
	 * All implementations I've seen define AF_INET and PF_INET to be the same
	 * number anyway, so the point is (hopefully) moot.
	 */

	if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		perror("SYSERR: Error creating socket");
		exit(1);
		}

#if defined(SO_REUSEADDR)
	opt = 1;
	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0) {
		perror("SYSERR: setsockopt REUSEADDR");
		exit(1);
		}
#endif

	set_sendbuf(s);

	/*
	 * The GUSI sockets library is derived from BSD, so it defines
	 * SO_LINGER, even though setsockopt() is unimplimented.
	 *	(from Dean Takemori <dean@UHHEPH.PHYS.HAWAII.EDU>)
	 */
#if defined(SO_LINGER)
	{
		struct linger ld;

		ld.l_onoff = 0;
		ld.l_linger = 0;
		if (setsockopt(s, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld)) < 0)
			perror("SYSERR: setsockopt SO_LINGER");	/* Not fatal I suppose. */
		}
#endif

	/* Clear the structure */
	memset((char *)&sa, 0, sizeof(sa));

	sa.sin_family = AF_INET;
	sa.sin_port = htons(port);
	sa.sin_addr = *(get_bind_addr());

	if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
		perror("SYSERR: bind");
		CLOSE_SOCKET(s);
		exit(1);
		}
	nonblock(s);
	listen(s, 5);
	return (s);
	}


int get_max_players(void) {
	int max_descs = 0;
	const char *method;

	/*
	 * First, we'll try using getrlimit/setrlimit.  This will probably work
	 * on most systems.  HAS_RLIMIT is defined in sysdep.h.
	 */
#ifdef HAS_RLIMIT
	{
		struct rlimit limit;

		/* find the limit of file descs */
		method = "rlimit";
		if (getrlimit(RLIMIT_NOFILE, &limit) < 0) {
			perror("SYSERR: calling getrlimit");
			exit(1);
			}

		/* set the current to the maximum */
		limit.rlim_cur = limit.rlim_max;
		if (setrlimit(RLIMIT_NOFILE, &limit) < 0) {
			perror("SYSERR: calling setrlimit");
			exit(1);
			}

#ifdef RLIM_INFINITY
		if (limit.rlim_max == RLIM_INFINITY)
			max_descs = max_playing + NUM_RESERVED_DESCS;
		else
			max_descs = MIN(max_playing + NUM_RESERVED_DESCS, limit.rlim_max);
#else
		max_descs = MIN(max_playing + NUM_RESERVED_DESCS, limit.rlim_max);
#endif
		}

#elif defined (OPEN_MAX) || defined(FOPEN_MAX)
#if !defined(OPEN_MAX)
#define OPEN_MAX FOPEN_MAX
#endif
	method = "OPEN_MAX";
	max_descs = OPEN_MAX;		/* Uh oh.. rlimit didn't work, but we have
				 * OPEN_MAX */
#elif defined (_SC_OPEN_MAX)
	/*
	 * Okay, you don't have getrlimit() and you don't have OPEN_MAX.  Time to
	 * try the POSIX sysconf() function.  (See Stevens' _Advanced Programming
	 * in the UNIX Environment_).
	 */
	method = "POSIX sysconf";
	errno = 0;

	if ((max_descs = sysconf(_SC_OPEN_MAX)) < 0) {
		if (errno == 0)
			max_descs = max_playing + NUM_RESERVED_DESCS;
		else {
			perror("SYSERR: Error calling sysconf");
			exit(1);
			}
		}
#else
	/* if everything has failed, we'll just take a guess */
	method = "random guess";
	max_descs = max_playing + NUM_RESERVED_DESCS;
#endif

	/* now calculate max _players_ based on max descs */
	max_descs = MIN(max_playing, max_descs - NUM_RESERVED_DESCS);

	if (max_descs <= 0) {
		log("SYSERR: Non-positive max player limit!  (Set at %d using %s).", max_descs, method);
		exit(1);
		}
	log("   Setting player limit to %d using %s.", max_descs, method);
	return (max_descs);
	}


char *prompt_str(Creature ch) {
	extern struct time_info_data time_info;
	extern char *parse_color(char *txt, Descr t);
	extern const char *health_levels[];
	extern const char *move_levels[];

	static char pbuf[MAX_STRING_LENGTH];
	char *str = GET_PROMPT(ch);
	char *cp, *tmp;
	char i[256], spare[10];

	if (!ch->desc)
		return (">");

	if (!str || !*str) {
		if (IS_VAMPIRE(ch))
			str = "&0|%h/%v &1%b&0b>";
		else
			str = "&0|%h/%v>";
		}

	if (!strchr(str, '%'))
		return (parse_color(str, ch->desc));

	cp = pbuf;

	for (;;) {
		if (*str == '%') {
			switch (*(++str)) {
				case 'c':	/* Conditions */
					sprintf(i, "%s%s%s%s",
							   	GET_COND(ch, FULL) >= 600 ? "H" : "",
							   	GET_COND(ch, THIRST) >= 315 ? "T" : "",
							   	GET_COND(ch, DRUNK) >= 45 ? "D" : "",
							   	(GET_COND(ch, TIRED) >= 315 || (GET_COND(ch, TIRED) >= 15 && GET_POS(ch) <= POS_SLEEPING)) ? "E" : ""
							   	);
					tmp = i;
					break;
				case 'n':
				case 'N':
					if (IS_IMMORTAL(ch))
						sprintf(i, "%d", world[ch->in_room].number);
					else
						sprintf(i, "%%%c", *str);
					tmp = i;
					break;
				case 'h': /* current health level */
				case 'H':
					sprintf(i, "%s", health_levels[GET_DAMAGE(ch)]);
					tmp = i;
					break;
				case 'v': /* exhaust condition */
				case 'V':
					sprintf(i, "%s", move_levels[GET_MOVE(ch) * 10 / GET_MAX_MOVE(ch)]);
					tmp = i;
					break;
				case 'b':
				case 'B':
					if (IS_VAMPIRE(ch))
						sprintf(i, "%d", GET_BLOOD(ch));
					else
						sprintf(i, "%%%c", *str);
					tmp = i;
					break;
				case 't':	/* Sun timer */
				case 'T':
					if (time_info.hours >= 12)
						strcpy(spare, "pm");
					else
						strcpy(spare, "am");

					if (time_info.hours == 6)
						sprintf(i, "&3%d%s&0", time_info.hours > 12 ? time_info.hours - 12 : time_info.hours, spare);
					else if (time_info.hours < 19 && time_info.hours > 6)
						sprintf(i, "&1%d%s&0", time_info.hours > 12 ? time_info.hours - 12 : time_info.hours, spare);
					else if (time_info.hours == 19)
						sprintf(i, "&3%d%s&0", time_info.hours > 12 ? time_info.hours - 12 : time_info.hours, spare);
					else if (time_info.hours == 0)
						sprintf(i, "&212am&0");
					else
						sprintf(i, "&2%d%s&0", time_info.hours > 12 ? time_info.hours - 12 : time_info.hours, spare);
					tmp = i;
					break;
				case 'w':
				case 'W':
					/* Add werewolf forms to the prompt */
					if(GET_MORPH(ch) == MORPH_NONE){
						sprintf(i, "&2Homid&0");
					} else if(GET_MORPH(ch) == MORPH_GLABRO){
						sprintf(i, "&2Glabro&0");
					} else if(GET_MORPH(ch) == MORPH_CRINOS){
						sprintf(i, "&1&bCrinos&0");
					} else if(GET_MORPH(ch) == MORPH_HISPO){
						sprintf(i, "&3&bHispo&0");
					} else if(GET_MORPH(ch) == MORPH_LUPUS){
						sprintf(i, "&3&bLupus&0");
					} else {
						sprintf(i, " ");
					}
					tmp = i;
					break;
				case '_':
					tmp = "\r\n";
					break;
				case '%':
					*(cp++) = '%';
					str++;
					continue;
					break;
				default :
					*(cp++) = '%';
					str++;
					continue;
					break;
				}

			while ((*cp = *(tmp++)))
				cp++;
			str++;
			}
		else if (!(*(cp++) = *(str++)))
			break;
		}

	*cp = '\0';

	strcat(pbuf, " &0");
	return (pbuf);
	}


int count_chars(char *txt, char character) {
	int i, cnt = 0;

	for(i = 0; txt[i]; i++)
		if(txt[i] == character)
			cnt++;

	return cnt;
	}


/* Color code */
char *parse_color(char *txt, Descr t) {
	char *new_txt;
	char *toret;
	register int i, j = 0;
	int color = (t->character && !IS_NPC(t->character) && PRF_FLAGGED(t->character, PRF_COLOR) ? 1 : 0);

	i = count_chars(txt, '&'); /* count how many control-chars there
								  are in the string */


	new_txt = malloc(i * 5 + strlen(txt) + 1); /* no ansi-escape code is larger
												  than 5 bytes so a 5 * times
												  the '&' appears + strlen(txt)
												  + 1 character big buffer
												  should be enough */
	/* the parser.. Huge but fast */
	for (i = 0; txt[i]; i++) {
		if (txt[i] == '&') {
			i++;

			/* This solves lines that end in & */
			if (txt[i] < 'A' && txt[i] < '0' && txt[i] != '&') {
				new_txt[j] = '&';
				new_txt[j+1] = txt[i];
				j += 2;
				continue;
				}

			switch(txt[i]) {
				case '0':
					if (color) {
						strcpy(new_txt + j, "\033[0m");
						j += 4;
						}
					break;
				case '1':
					if (color) {
						strcpy(new_txt + j, "\033[31m");
						j += 5;
						}
					break;
				case '2':
					if (color) {
						strcpy(new_txt + j, "\033[32m");
						j += 5;
						}
					break;
				case '3':
					if (color) {
						strcpy(new_txt + j, "\033[33m");
						j += 5;
						}
					break;
				case '4':
					if (color) {
						strcpy(new_txt + j, "\033[34m");
						j += 5;
						}
					break;
				case '5':
					if (color) {
						strcpy(new_txt + j, "\033[35m");
						j += 5;
						}
					break;
				case '6':
					if (color) {
						strcpy(new_txt + j, "\033[36m");
						j += 5;
						}
					break;
				case '7':
					if (color) {
						strcpy(new_txt + j, "\033[37m");
						j += 5;
						}
					break;
				case 'u':
					if (color) {
						strcpy(new_txt + j, "\033[4m");
						j += 4;
						}
					break;
				case 'd':
					if (color) {
						strcpy(new_txt + j, "\033[2m");
						j += 4;
						}
					break;
				case 'b':
					if (color) {
						strcpy(new_txt + j, "\033[1m");
						j += 4;
						}
					break;
				case 'R':
					if (color) {
						strcpy(new_txt + j, "\033[41m");
						j += 5;
						}
					break;
				case 'G':
					if (color) {
						strcpy(new_txt + j, "\033[42m");
						j += 5;
						}
					break;
				case 'Y':
					if (color) {
						strcpy(new_txt + j, "\033[43m");
						j += 5;
						}
					break;
				case 'B':
					if (color) {
						strcpy(new_txt + j, "\033[44m");
						j += 5;
						}
					break;
				case 'M':
					if (color) {
						strcpy(new_txt + j, "\033[45m");
						j += 5;
						}
					break;
				case 'C':
					if (color) {
						strcpy(new_txt + j, "\033[46m");
						j += 5;
						}
					break;
				case 'W':
					if (color) {
						strcpy(new_txt + j, "\033[47m");
						j += 5;
						}
					break;
				case 'S':
					if (color) {
						strcpy(new_txt + j, "\033[40m");
						j += 5;
						}
					break;
				case '&':
					new_txt[j] = txt[i];
					j++;
					break;

				default:
					new_txt[j] = '&';
					new_txt[j+1] = txt[i];
					j += 2;
					break;
				}
			}
		else {
			new_txt[j] = txt[i];
			j++;
			}
		}

	new_txt[j] = '\0'; /* terminate the string */
	toret = strdup(new_txt); /* create a new string with no eventual memoryloss */
	free(new_txt); /* free the old buffer */

	return toret; /* the colorized buffer */
	}

/*
 * game_loop contains the main loop which drives the entire MUD.  It
 * cycles once every 0.10 seconds and is responsible for accepting new
 * new connections, polling existing connections for input, dequeueing
 * output and sending it out to players, and calling "heartbeat" functions
 * such as mobile_activity().
 */
void game_loop(socket_t mother_desc) {
	fd_set input_set, output_set, exc_set, null_set;
	struct timeval last_time, opt_time, process_time, temp_time;
	struct timeval before_sleep, now, timeout;
	char comm[MAX_INPUT_LENGTH];
	Descr d, next_d;
	int pulse = 0, missed_pulses, maxdesc, aliased;

	/* initialize various time values */
	null_time.tv_sec = 0;
	null_time.tv_usec = 0;
	opt_time.tv_usec = OPT_USEC;
	opt_time.tv_sec = 0;
	FD_ZERO(&null_set);

	gettimeofday(&last_time, (struct timezone *) 0);

	/* The Main Loop.  The Big Cheese.  The Top Dog.  The Head Honcho.  The.. */
	while (!empire_shutdown) {

		/* Sleep if we don't have any connections */
		if (descriptor_list == NULL) {
			log("No connections.  Going to sleep.");
			FD_ZERO(&input_set);
			FD_SET(mother_desc, &input_set);
			if (select(mother_desc + 1, &input_set, (fd_set *) 0, (fd_set *) 0, NULL) < 0) {
				if (errno == EINTR)
					log("Waking up to process signal.");
				else
					perror("SYSERR: Select coma");
				}
			else
				log("New connection.  Waking up.");
			gettimeofday(&last_time, (struct timezone *) 0);
			}
		/* Set up the input, output, and exception sets for select(). */
		FD_ZERO(&input_set);
		FD_ZERO(&output_set);
		FD_ZERO(&exc_set);
		FD_SET(mother_desc, &input_set);

		maxdesc = mother_desc;
		for (d = descriptor_list; d; d = d->next) {
			if (d->descriptor > maxdesc)
				maxdesc = d->descriptor;
			FD_SET(d->descriptor, &input_set);
			FD_SET(d->descriptor, &output_set);
			FD_SET(d->descriptor, &exc_set);
			}

		/*
		 * At this point, we have completed all input, output and heartbeat
		 * activity from the previous iteration, so we have to put ourselves
		 * to sleep until the next 0.1 second tick.  The first step is to
		 * calculate how long we took processing the previous iteration.
		 */

		gettimeofday(&before_sleep, (struct timezone *) 0); /* current time */
		timediff(&process_time, &before_sleep, &last_time);

		/*
		 * If we were asleep for more than one pass, count missed pulses and sleep
		 * until we're resynchronized with the next upcoming pulse.
		 */
		if (process_time.tv_sec == 0 && process_time.tv_usec < OPT_USEC) {
			missed_pulses = 0;
			}
		else {
			missed_pulses = process_time.tv_sec * PASSES_PER_SEC;
			missed_pulses += process_time.tv_usec / OPT_USEC;
			process_time.tv_sec = 0;
			process_time.tv_usec = process_time.tv_usec % OPT_USEC;
			}

		/* Calculate the time we should wake up */
		timediff(&temp_time, &opt_time, &process_time);
		timeadd(&last_time, &before_sleep, &temp_time);

		/* Now keep sleeping until that time has come */
		gettimeofday(&now, (struct timezone *) 0);
		timediff(&timeout, &last_time, &now);

		/* Go to sleep */
		do {
			empire_sleep(&timeout);
			gettimeofday(&now, (struct timezone *) 0);
			timediff(&timeout, &last_time, &now);
			} while (timeout.tv_usec || timeout.tv_sec);

		/* Poll (without blocking) for new input, output, and exceptions */
		if (select(maxdesc + 1, &input_set, &output_set, &exc_set, &null_time) < 0) {
			perror("SYSERR: Select poll");
			return;
			}
		/* If there are new connections waiting, accept them. */
		if (FD_ISSET(mother_desc, &input_set))
			new_descriptor(mother_desc);

		/* Kick out the freaky folks in the exception set and marked for close */
		for (d = descriptor_list; d; d = next_d) {
			next_d = d->next;
			if (FD_ISSET(d->descriptor, &exc_set)) {
				FD_CLR(d->descriptor, &input_set);
				FD_CLR(d->descriptor, &output_set);
				close_socket(d);
				}
			}

		/* Process descriptors with input pending */
		for (d = descriptor_list; d; d = next_d) {
			next_d = d->next;
			if (FD_ISSET(d->descriptor, &input_set))
				if (process_input(d) < 0)
					close_socket(d);
			}

		/* Process commands we just read from process_input */
		for (d = descriptor_list; d; d = next_d) {
			next_d = d->next;

			/*
			 * Not combined to retain --(d->wait) behavior. -gg 2/20/98
			 * If no wait state, no subtraction.  If there is a wait
			 * state then 1 is subtracted. Therefore we don't go less
			 * than 0 ever and don't require an 'if' bracket. -gg 2/27/99
			 */
			if (d->character) {
				GET_WAIT_STATE(d->character) -= (GET_WAIT_STATE(d->character) > 0);

				if (GET_WAIT_STATE(d->character))
					continue;
				}

			if (!get_from_q(&d->input, comm, &aliased))
				continue;

			if (d->character) {
				/* Reset the idle timer & pull char back from void if necessary */
				d->character->char_specials.timer = 0;
				GET_WAIT_STATE(d->character) = 1;
				}
			d->has_prompt = 0;

			if (d->showstr_count) /* Reading something w/ pager */
				show_string(d, comm);
			else if (d->str)		/* Writing boards, mail, etc. */
				string_add(d, comm);
			else if (STATE(d) != CON_PLAYING) /* In menus, etc. */
				nanny(d, comm);
			else {			/* else: we're playing normally. */
				if (aliased)		/* To prevent recursive aliases. */
					d->has_prompt = 1;	/* To get newline before next cmd output. */
				else if (perform_alias(d, comm))    /* Run it through aliasing system */
					get_from_q(&d->input, comm, &aliased);
				command_interpreter(d->character, comm); /* Send it to interpreter */
				}
			}

		/* Send queued output out to the operating system (ultimately to user). */
		for (d = descriptor_list; d; d = next_d) {
			next_d = d->next;
			if (*(d->output) && FD_ISSET(d->descriptor, &output_set)) {
				/* Output for this player is ready */
				if (process_output(d) < 0)
					close_socket(d);
				else
					d->has_prompt = 1;
				}
			}

		/* Print prompts for other descriptors who had no other output */
		for (d = descriptor_list; d; d = d->next) {
			if (!d->has_prompt) {
				write_to_descriptor(d->descriptor, make_prompt(d));
				d->has_prompt = 1;
				}
			}

		/* Kick out folks in the CON_CLOSE or CON_DISCONNECT state */
		for (d = descriptor_list; d; d = next_d) {
			next_d = d->next;
			if (STATE(d) == CON_CLOSE || STATE(d) == CON_DISCONNECT)
				close_socket(d);
			}

		/*
		 * Now, we execute as many pulses as necessary--just one if we haven't
		 * missed any pulses, or make up for lost time if we missed a few
		 * pulses by sleeping for too long.
		 */
		missed_pulses++;

		if (missed_pulses <= 0) {
			log("SYSERR: **BAD** MISSED_PULSES NONPOSITIVE (%d), TIME GOING BACKWARDS!!", missed_pulses);
			missed_pulses = 1;
			}

		/* If we missed more than 30 seconds worth of pulses, just do 30 secs */
		if (missed_pulses > (30 * PASSES_PER_SEC)) {
			log("SYSERR: Missed %d seconds worth of pulses.", missed_pulses / PASSES_PER_SEC);
			missed_pulses = 30 * PASSES_PER_SEC;
			}

		/* Now execute the heartbeat functions */
		while (missed_pulses--)
			heartbeat(++pulse);

		/* Roll pulse over after 10 hours */
		if (pulse >= (600 * 60 * PASSES_PER_SEC))
			pulse = 0;

		/* Update tics for deadlock protection */
		tics++;
		}
	}


#define HEARTBEAT(x)		if (!(pulse % ((x) * PASSES_PER_SEC)))

void heartbeat(int pulse) {
	void weather_and_time(int mode);
	void update_biting();
	void real_update();
	void room_update();
	void update_rooms();
	void update_reboot();
	void update_guard_towers();
	void update_actions();
	void sanity_check();
	void check_idle_passwords();
	void affect_update();
	void affect_update_room();
	void point_update();
	void run_idling();

	static int mins_since_crashsave = 0;

	/* Turn this on here */
	HEARTBEAT(SECS_PER_MUD_HOUR)
		gain_cond_messsage = TRUE;

	HEARTBEAT(1)
		update_actions();

	HEARTBEAT(3)
		update_guard_towers();

	HEARTBEAT(5)
		update_biting();

	HEARTBEAT(5)
		real_update();

	HEARTBEAT(30)
		sanity_check();

	HEARTBEAT(15)
		check_idle_passwords();

	if (!(pulse % PULSE_ZONE)) {
		update_rooms();
		zone_update(0);
		}

	if (!(pulse % PULSE_MOBILE))
		mobile_activity();

	if (!(pulse % PULSE_VIOLENCE))
		perform_violence();

	HEARTBEAT(SECS_PER_MUD_HOUR) {
		weather_and_time(1);
		affect_update();
		affect_update_room();
		point_update();
		room_update();
		fflush(player_fl);
		}

	HEARTBEAT(60) {
		update_reboot();
		if (++mins_since_crashsave >= 5) {
			mins_since_crashsave = 0;
			Crash_save_all();
			}
		}

	/* This should be done last, it extracts characters */
	HEARTBEAT(SECS_PER_MUD_HOUR)
		run_idling();

	/* Turn this off */
	gain_cond_messsage = FALSE;
	}


/* ******************************************************************
*  general utility stuff (for local use)                            *
****************************************************************** */

/*
 *  new code to calculate time differences, which works on systems
 *  for which tv_usec is unsigned (and thus comparisons for something
 *  being < 0 fail).  Based on code submitted by ss@sirocco.cup.hp.com.
 */

/*
 * code to return the time difference between a and b (a-b).
 * always returns a nonnegative value (floors at 0).
 */
void timediff(struct timeval *rslt, struct timeval *a, struct timeval *b) {
	if (a->tv_sec < b->tv_sec)
		*rslt = null_time;
	else if (a->tv_sec == b->tv_sec) {
		if (a->tv_usec < b->tv_usec)
			*rslt = null_time;
		else {
			rslt->tv_sec = 0;
			rslt->tv_usec = a->tv_usec - b->tv_usec;
			}
		}
	else {			/* a->tv_sec > b->tv_sec */
		rslt->tv_sec = a->tv_sec - b->tv_sec;
		if (a->tv_usec < b->tv_usec) {
			rslt->tv_usec = a->tv_usec + 1000000 - b->tv_usec;
			rslt->tv_sec--;
			}
		else
			rslt->tv_usec = a->tv_usec - b->tv_usec;
		}
	}

/*
 * Add 2 time values.
 *
 * Patch sent by "d. hall" <dhall@OOI.NET> to fix 'static' usage.
 */
void timeadd(struct timeval *rslt, struct timeval *a, struct timeval *b) {
	rslt->tv_sec = a->tv_sec + b->tv_sec;
	rslt->tv_usec = a->tv_usec + b->tv_usec;

	while (rslt->tv_usec >= 1000000) {
		rslt->tv_usec -= 1000000;
		rslt->tv_sec++;
		}
	}


/*
 * Turn off echoing (specific to telnet client)
 */
void echo_off(Descr d) {
	char off_string[] = {
		(char) IAC,
		(char) WILL,
		(char) TELOPT_ECHO,
		(char) 0,
		};

	SEND_TO_Q(off_string, d);
	}


/*
 * Turn on echoing (specific to telnet client)
 */
void echo_on(Descr d) {
	char on_string[] = {
		(char) IAC,
		(char) WONT,
		(char) TELOPT_ECHO,
		(char) 0
		};

	SEND_TO_Q(on_string, d);
	}


char *make_prompt(Descr d) {
	static char prompt[MAX_STRING_LENGTH];

	/* Note, prompt is truncated at MAX_PROMPT_LENGTH chars (structs.h )*/

	if (d->showstr_count)
		sprintf(prompt, "\r[ Return to continue, (q)uit, (r)efresh, (b)ack, or page number (%d/%d) ]", d->showstr_page, d->showstr_count);
	else if (d->str)
		strcpy(prompt, "] ");
	else if (STATE(d) == CON_PLAYING) {
		*prompt = '\0';

		if (!IS_NPC(d->character) && GET_INVIS_LEV(d->character))
			sprintf(prompt + strlen(prompt), "&1(i%d) ", GET_INVIS_LEV(d->character));

		if (wizlock_level > 0 && GET_LEVEL(d->character) >= LVL_START_IMM)
			sprintf(prompt + strlen(prompt), "&2(w%d) ", wizlock_level);

		if (!IS_NPC(d->character) && IS_AFK(d->character)) {
			if (d->character->player_specials->isafk)
				sprintf(prompt + strlen(prompt), "&1[AFK%s] ", AFK_MSGS(d->character) ? "*" : "");
			else
				sprintf(prompt + strlen(prompt), "&1[IDLE%s] ", AFK_MSGS(d->character) ? "*" : "");
			}

		if (!IS_NPC(d->character) && PRF_FLAGGED(d->character, PRF_RP))
			sprintf(prompt + strlen(prompt), "&5[RP] ");

		strcat(prompt, "&0");

		strcat(prompt, prompt_str(d->character));
		}
	else
		*prompt = '\0';

	return (parse_color(prompt, d));
	}


void write_to_q(const char *txt, struct txt_q *queue, int aliased) {
	struct txt_block *newt;

	CREATE(newt, struct txt_block, 1);
	newt->text = str_dup(txt);
	newt->aliased = aliased;

	/* queue empty? */
	if (!queue->head) {
		newt->next = NULL;
		queue->head = queue->tail = newt;
		}
	else {
		queue->tail->next = newt;
		queue->tail = newt;
		newt->next = NULL;
		}
	}


int get_from_q(struct txt_q *queue, char *dest, int *aliased) {
	struct txt_block *tmp;

	/* queue empty? */
	if (!queue->head)
		return (0);

	tmp = queue->head;
	strcpy(dest, queue->head->text);
	*aliased = queue->head->aliased;
	queue->head = queue->head->next;

	free(tmp->text);
	free(tmp);

	return (1);
	}


/* Empty the queues before closing connection */
void flush_queues(Descr d) {
	int dummy;

	if (d->large_outbuf) {
		d->large_outbuf->next = bufpool;
		bufpool = d->large_outbuf;
 		}
	while (get_from_q(&d->input, buf2, &dummy));
	}


/* Add a new string to a player's output queue */
void write_to_output(const char *txt, Descr t) {
	int size;
	char *new_txt;

	new_txt = parse_color((char *) txt, t);

	size = strlen(new_txt);

	/* if we're in the overflow state already, ignore this new output */
	if (t->bufptr < 0)
		return;

	/* if we have enough space, just write to buffer and that's it! */
	if (t->bufspace >= size) {
		strcpy(t->output + t->bufptr, new_txt);
		t->bufspace -= size;
		t->bufptr += size;
		free(new_txt);
		return;
		}
	/*
	 * If the text is too big to fit into even a large buffer, chuck the
	 * new text and switch to the overflow state.
	 */
	if (size + t->bufptr > LARGE_BUFSIZE - 1) {
		t->bufptr = -1;
		buf_overflows++;
		free(new_txt);
		return;
		}
	buf_switches++;

	/* if the pool has a buffer in it, grab it */
	if (bufpool != NULL) {
		t->large_outbuf = bufpool;
		bufpool = bufpool->next;
		}
	else {			/* else create a new one */
   		CREATE(t->large_outbuf, struct txt_block, 1);
		CREATE(t->large_outbuf->text, char, LARGE_BUFSIZE);
		buf_largecount++;
		}

	strcpy(t->large_outbuf->text, t->output);	/* copy to big buffer */
	t->output = t->large_outbuf->text;	/* make big buffer primary */
	strcat(t->output, new_txt);	/* now add new text */

	/* set the pointer for the next write */
	t->bufptr = strlen(t->output);

	/* calculate how much space is left in the buffer */
	t->bufspace = LARGE_BUFSIZE - 1 - t->bufptr;
	}


/* ******************************************************************
*  socket handling                                                  *
****************************************************************** */

/*
 * get_bind_addr: Return a struct in_addr that should be used in our
 * call to bind().  If the user has specified a desired binding
 * address, we try to bind to it; otherwise, we bind to INADDR_ANY.
 * Note that inet_aton() is preferred over inet_addr() so we use it if
 * we can.  If neither is available, we always bind to INADDR_ANY.
 */

struct in_addr *get_bind_addr() {
	static struct in_addr bind_addr;

	/* Clear the structure */
	memset((char *) &bind_addr, 0, sizeof(bind_addr));

	/* If DLFT_IP is unspecified, use INADDR_ANY */
	bind_addr.s_addr = htonl(INADDR_ANY);

	/* Put the address that we've finally decided on into the logs */
	if (bind_addr.s_addr == htonl(INADDR_ANY))
		log("Binding to all IP interfaces on this host.");
	else
		log("Binding only to IP address %s", inet_ntoa(bind_addr));

	return (&bind_addr);
	}


/* Sets the kernel's send buffer size for the descriptor */
int set_sendbuf(socket_t s) {
#if defined(SO_SNDBUF)
	int opt = MAX_SOCK_BUF;

	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *) &opt, sizeof(opt)) < 0) {
		perror("SYSERR: setsockopt SNDBUF");
		return (-1);
		}

#if 0
	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *) &opt, sizeof(opt)) < 0) {
		perror("SYSERR: setsockopt RCVBUF");
		return (-1);
		}
#endif

#endif

	return (0);
	}


void init_descriptor(Descr newd, int desc) {
	static int last_desc = 0;

	newd->descriptor = desc;
	newd->connected = CON_GET_NAME;
	newd->idle_tics = 0;
	newd->output = newd->small_outbuf;
	newd->bufspace = SMALL_BUFSIZE - 1;
	newd->next = descriptor_list;
	newd->login_time = time(0);
	*newd->output = '\0';
	newd->bufptr = 0;
	newd->has_prompt = 0;

	CREATE(newd->history, char *, HISTORY_SIZE);

	if (++last_desc == 1000)
		last_desc = 1;
	newd->desc_num = last_desc;
	}


int new_descriptor(int s) {
	socket_t desc;
	int sockets_connected = 0;
	socklen_t i;
	Descr newd;
	struct sockaddr_in peer;
	struct hostent *from;

	/* accept the new connection */
	i = sizeof(peer);
	if ((desc = accept(s, (struct sockaddr *) &peer, &i)) == INVALID_SOCKET) {
		perror("SYSERR: accept");
		return (-1);
		}
	/* keep it from blocking */
	nonblock(desc);

	/* set the send buffer size */
	if (set_sendbuf(desc) < 0) {
		CLOSE_SOCKET(desc);
		return (0);
		}

	/* make sure we have room for it */
	for (newd = descriptor_list; newd; newd = newd->next)
		sockets_connected++;

	if (sockets_connected >= max_players) {
		write_to_descriptor(desc, "Sorry, EmpireMUD is full right now... please try again later!\r\n");
		CLOSE_SOCKET(desc);
		return (0);
		}
	/* create a new descriptor */
	CREATE(newd, struct descriptor_data, 1);
	memset((char *) newd, 0, sizeof(struct descriptor_data));

	/* find the sitename */
	if (nameserver_is_slow || !(from = gethostbyaddr((char *) &peer.sin_addr, sizeof(peer.sin_addr), AF_INET))) {
		/* resolution failed */
		if (!nameserver_is_slow)
			perror("SYSERR: gethostbyaddr");

		/* find the numeric site address */
		strncpy(newd->host, (char *)inet_ntoa(peer.sin_addr), HOST_LENGTH);
		*(newd->host + HOST_LENGTH) = '\0';
		}
	else {
		strncpy(newd->host, from->h_name, HOST_LENGTH);
		*(newd->host + HOST_LENGTH) = '\0';
		}

	/* determine if the site is banned */
	if (isbanned(newd->host) == BAN_ALL) {
		CLOSE_SOCKET(desc);
		syslog(0, FALSE, "Connection attempt denied from [%s]", newd->host);
		free(newd);
		return (0);
		}
#if 0
  /*
   * Log new connections - probably unnecessary, but you may want it.
   * Note that your immortals may wonder if they see a connection from
   * your site, but you are wizinvis upon login.
   */
	syslog(0, FALSE, "New connection from [%s]", newd->host);
#endif

	init_descriptor(newd, desc);
	newd->has_prompt = 1;

	/* prepend to list */
	newd->next = descriptor_list;
	descriptor_list = newd;

	SEND_TO_Q(GREETINGS, newd);

	return (0);
	}


/*
 * Send all of the output that we've accumulated for a player out to
 * the player's descriptor.
 * FIXME - This will be rewritten before 3.1, this code is dumb.
 */
int process_output(Descr t) {
	char i[MAX_SOCK_BUF];
	int result;

	/* we may need this \r\n for later -- see below */
	strcpy(i, "\r\n");

	/* now, append the 'real' output */
	strcpy(i + 2, t->output);

	/* if we're in the overflow state, notify the user */
	if (t->bufptr < 0)
		strcat(i, "**OVERFLOW**\r\n");

	/* add the extra CRLF if the person isn't in compact mode */
	if (STATE(t) == CON_PLAYING && t->character && !IS_NPC(t->character) && !PRF_FLAGGED(t->character, PRF_COMPACT))
		strcat(i + 2, "\r\n");

	/* add a prompt */
	strncat(i + 2, make_prompt(t), MAX_PROMPT_LENGTH);

	/*
	 * now, send the output.  If this is an 'interruption', use the prepended
	 * CRLF, otherwise send the straight output sans CRLF.
	 */
	if (t->has_prompt)		/* && !t->connected) */
		result = write_to_descriptor(t->descriptor, i);
	else
		result = write_to_descriptor(t->descriptor, i + 2);

	/* handle snooping: prepend "% " and send to snooper */
	if (t->snoop_by) {
		SEND_TO_Q("% ", t->snoop_by);
		SEND_TO_Q(t->output, t->snoop_by);
		SEND_TO_Q("%%", t->snoop_by);
		}
	/*
	 * if we were using a large buffer, put the large buffer on the buffer pool
	 * and switch back to the small one
	 */
	if (t->large_outbuf) {
		t->large_outbuf->next = bufpool;
		bufpool = t->large_outbuf;
		t->large_outbuf = NULL;
		t->output = t->small_outbuf;
		}
	/* reset total bufspace back to that of a small buffer */
	t->bufspace = SMALL_BUFSIZE - 1;
	t->bufptr = 0;
	*(t->output) = '\0';

	return (result);
	}


/*
 * perform_socket_write: takes a descriptor, a pointer to text, and a
 * text length, and tries once to send that text to the OS.  This is
 * where we stuff all the platform-dependent stuff that used to be
 * ugly #ifdef's in write_to_descriptor().
 *
 * This function must return:
 *
 * -1  If a fatal error was encountered in writing to the descriptor.
 *  0  If a transient failure was encountered (e.g. socket buffer full).
 * >0  To indicate the number of bytes successfully written, possibly
 *     fewer than the number the caller requested be written.
 *
 * Right now there are two versions of this function: one for Windows,
 * and one for all other platforms.
 */

/* perform_socket_write for all Non-Windows platforms */
ssize_t perform_socket_write(socket_t desc, const char *txt, size_t length) {
	ssize_t result;

	result = write(desc, txt, length);

	if (result > 0) {
		/* Write was successful. */
		return (result);
		}

	if (result == 0) {
		/* This should never happen! */
		log("SYSERR: Huh??  write() returned 0???  Please report this!");
		return (-1);
		}

	/*
	 * result < 0, so an error was encountered - is it transient?
	 * Unfortunately, different systems use different constants to
	 * indicate this.
	 */

#ifdef EAGAIN		/* POSIX */
	if (errno == EAGAIN)
		return (0);
#endif

#ifdef EWOULDBLOCK	/* BSD */
	if (errno == EWOULDBLOCK)
		return (0);
#endif

#ifdef EDEADLK		/* Macintosh */
	if (errno == EDEADLK)
		return (0);
#endif

	/* Looks like the error was fatal.  Too bad. */
	return (-1);
	}


/*
 * write_to_descriptor takes a descriptor, and text to write to the
 * descriptor.  It keeps calling the system-level write() until all
 * the text has been delivered to the OS, or until an error is
 * encountered.
 *
 * Returns:
 *  0  If all is well and good,
 * -1  If an error was encountered, so that the player should be cut off
 */
int write_to_descriptor(socket_t desc, const char *txt) {
	size_t total;
	ssize_t bytes_written;

	total = strlen(txt);

	while (total > 0) {
		bytes_written = perform_socket_write(desc, txt, total);

		if (bytes_written < 0) {
			/* Fatal error.  Disconnect the player. */
			perror("SYSERR: Write to socket");
			return (-1);
			}
		else if (bytes_written == 0) {
			/*
			 * Temporary failure -- socket buffer full.  For now we'll just
			 * cut off the player, but eventually we'll stuff the unsent
			 * text into a buffer and retry the write later.  JE 30 June 98.
			 */
			log("WARNING: write_to_descriptor: socket write would block, about to close");
			return (-1);
			}
		else {
			txt += bytes_written;
			total -= bytes_written;
			}
		}

	return (0);
	}


/*
 * Same information about perform_socket_write applies here. I like
 * standards, there are so many of them. -gg 6/30/98
 */
ssize_t perform_socket_read(socket_t desc, char *read_point, size_t space_left) {
	ssize_t ret;

	ret = read(desc, read_point, space_left);

	/* Read was successful. */
	if (ret > 0)
		return (ret);

	/* read() returned 0, meaning we got an EOF. */
	if (ret == 0) {
		log("WARNING: EOF on socket read (connection broken by peer)");
		return (-1);
		}

	/*
	 * read returned a value < 0: there was an error
	 */

#ifdef EINTR		/* Interrupted system call - various platforms */
	if (errno == EINTR)
		return (0);
#endif

#ifdef EAGAIN		/* POSIX */
	if (errno == EAGAIN)
		return (0);
#endif

#ifdef EWOULDBLOCK	/* BSD */
	if (errno == EWOULDBLOCK)
		return (0);
#endif /* EWOULDBLOCK */

#ifdef EDEADLK		/* Macintosh */
	if (errno == EDEADLK)
		return (0);
#endif

	/*
	 * We don't know what happened, cut them off. This qualifies for
	 * a SYSERR because we have no idea what happened at this point.
	 */
	perror("perform_socket_read: about to lose connection");
	return (-1);
	}

/*
 * ASSUMPTION: There will be no newlines in the raw input buffer when this
 * function is called.  We must maintain that before returning.
 *
 * Ever wonder why 'tmp' had '+8' on it?  The crusty old code could write
 * MAX_INPUT_LENGTH+1 bytes to 'tmp' if there was a '$' as the final
 * character in the input buffer.  This would also cause 'space_left' to
 * drop to -1, which wasn't very happy in an unsigned variable.  Argh.
 * So to fix the above, 'tmp' lost the '+8' since it doesn't need it
 * and the code has been changed to reserve space by accepting one less
 * character. (Do you really need 256 characters on a line?)
 * -gg 1/21/2000
 */
int process_input(Descr t) {
	int buf_length, failed_subst;
	ssize_t bytes_read;
	size_t space_left;
	char *ptr, *read_point, *write_point, *nl_pos = NULL;
	char tmp[MAX_INPUT_LENGTH];

	/* first, find the point where we left off reading data */
	buf_length = strlen(t->inbuf);
	read_point = t->inbuf + buf_length;
	space_left = MAX_RAW_INPUT_LENGTH - buf_length - 1;

	do {
		if (space_left <= 0) {
			log("WARNING: process_input: about to close connection: input overflow");
			return (-1);
			}

		bytes_read = perform_socket_read(t->descriptor, read_point, space_left);

		if (bytes_read < 0)	/* Error, disconnect them. */
			return (-1);
		else if (bytes_read == 0)	/* Just blocking, no problems. */
			return (0);

		/* at this point, we know we got some data from the read */

		*(read_point + bytes_read) = '\0';	/* terminate the string */

		/* search for a newline in the data we just read */
		for (ptr = read_point; *ptr && !nl_pos; ptr++)
			if (ISNEWL(*ptr))
				nl_pos = ptr;

		read_point += bytes_read;
		space_left -= bytes_read;

		/*
		 * on some systems such as AIX, POSIX-standard nonblocking I/O is broken,
		 * causing the MUD to hang when it encounters input not terminated by a
		 * newline.  This was causing hangs at the Password: prompt, for example.
		 * I attempt to compensate by always returning after the _first_ read, instead
		 * of looping forever until a read returns -1.  This simulates non-blocking
		 * I/O because the result is we never call read unless we know from select()
		 * that data is ready (process_input is only called if select indicates that
		 * this descriptor is in the read set).  JE 2/23/95.
		 */
#if !defined(POSIX_NONBLOCK_BROKEN)
		} while (nl_pos == NULL);
#else
		} while (0);

	if (nl_pos == NULL)
		return (0);
#endif /* POSIX_NONBLOCK_BROKEN */

	/*
	 * okay, at this point we have at least one newline in the string; now we
	 * can copy the formatted data to a new array for further processing.
	 */

	read_point = t->inbuf;

	while (nl_pos != NULL) {
		write_point = tmp;
		space_left = MAX_INPUT_LENGTH - 1;

		/* The '> 1' reserves room for a '$ => $$' expansion. */
		for (ptr = read_point; (space_left > 1) && (ptr < nl_pos); ptr++) {
			if (*ptr == '\b' || *ptr == 127) { /* handle backspacing or delete key */
				if (write_point > tmp) {
					if (*(--write_point) == '$') {
						write_point--;
						space_left += 2;
						}
					else
						space_left++;
					}
				}
			else if (isascii(*ptr) && isprint(*ptr)) {
				if ((*(write_point++) = *ptr) == '$') {		/* copy one character */
					*(write_point++) = '$';	/* if it's a $, double it */
					space_left -= 2;
					}
				else
					space_left--;
				}
			}

		*write_point = '\0';

		if ((space_left <= 0) && (ptr < nl_pos)) {
			char buffer[MAX_INPUT_LENGTH + 64];

			sprintf(buffer, "Line too long.  Truncated to:\r\n%s\r\n", tmp);
			if (write_to_descriptor(t->descriptor, buffer) < 0)
				return (-1);
			}
		if (t->snoop_by) {
			SEND_TO_Q("% ", t->snoop_by);
			SEND_TO_Q(tmp, t->snoop_by);
			SEND_TO_Q("\r\n", t->snoop_by);
			}
		failed_subst = 0;

		if (*tmp == '!' && !(*(tmp + 1)))	/* Redo last command. */
			strcpy(tmp, t->last_input);
		else if (*tmp == '!' && *(tmp + 1)) {
			char *commandln = (tmp + 1);
			int starting_pos = t->history_pos, cnt = (t->history_pos == 0 ? HISTORY_SIZE - 1 : t->history_pos - 1);

			skip_spaces(&commandln);
			for (; cnt != starting_pos; cnt--) {
				if (t->history[cnt] && is_abbrev(commandln, t->history[cnt])) {
					strcpy(tmp, t->history[cnt]);
					strcpy(t->last_input, tmp);
					SEND_TO_Q(tmp, t); SEND_TO_Q("\r\n", t);
					break;
					}
				if (cnt == 0)	/* At top, loop to bottom. */
					cnt = HISTORY_SIZE;
				}
			}
		else if (*tmp == '^') {
			if (!(failed_subst = perform_subst(t, t->last_input, tmp)))
				strcpy(t->last_input, tmp);
			}
		else {
			strcpy(t->last_input, tmp);
			if (t->history[t->history_pos])
				free(t->history[t->history_pos]);	/* Clear the old line. */
			t->history[t->history_pos] = str_dup(tmp);	/* Save the new. */
			if (++t->history_pos >= HISTORY_SIZE)	/* Wrap to top. */
				t->history_pos = 0;
			}

		if (!failed_subst)
			write_to_q(tmp, &t->input, 0);

		/* find the end of this line */
		while (ISNEWL(*nl_pos))
			nl_pos++;

		/* see if there's another newline in the input buffer */
		read_point = ptr = nl_pos;
		for (nl_pos = NULL; *ptr && !nl_pos; ptr++)
			if (ISNEWL(*ptr))
				nl_pos = ptr;
		}

	/* now move the rest of the buffer up to the beginning for the next pass */
	write_point = t->inbuf;
	while (*read_point)
		*(write_point++) = *(read_point++);
	*write_point = '\0';

	return (1);
	}



/* perform substitution for the '^..^' csh-esque syntax orig is the
 * orig string, i.e. the one being modified.  subst contains the
 * substition string, i.e. "^telm^tell"
 */
int perform_subst(Descr t, char *orig, char *subst) {
	char newsub[MAX_INPUT_LENGTH + 5];

	char *first, *second, *strpos;

	/*
	 * first is the position of the beginning of the first string (the one
	 * to be replaced
	 */
	first = subst + 1;

	/* now find the second '^' */
	if (!(second = strchr(first, '^'))) {
		SEND_TO_Q("Invalid substitution.\r\n", t);
		return (1);
		}
	/* terminate "first" at the position of the '^' and make 'second' point
	 * to the beginning of the second string
	 */
	*(second++) = '\0';

	/* now, see if the contents of the first string appear in the original */
	if (!(strpos = strstr(orig, first))) {
		SEND_TO_Q("Invalid substitution.\r\n", t);
		return (1);
		}
	/* now, we construct the new string for output. */

	/* first, everything in the original, up to the string to be replaced */
	strncpy(newsub, orig, (strpos - orig));
	newsub[(strpos - orig)] = '\0';

	/* now, the replacement string */
	strncat(newsub, second, (MAX_INPUT_LENGTH - strlen(newsub) - 1));

	/* now, if there's anything left in the original after the string to
	 * replaced, copy that too.
	 */
	if (((strpos - orig) + strlen(first)) < strlen(orig))
		strncat(newsub, strpos + strlen(first), (MAX_INPUT_LENGTH - strlen(newsub) - 1));

	/* terminate the string in case of an overflow from strncat */
	newsub[MAX_INPUT_LENGTH - 1] = '\0';
	strcpy(subst, newsub);

	return (0);
	}


void close_socket(Descr d) {
	Descr temp;

	REMOVE_FROM_LIST(d, descriptor_list, next);
	CLOSE_SOCKET(d->descriptor);
	flush_queues(d);

	/* Forget snooping */
	if (d->snooping)
		d->snooping->snoop_by = NULL;

	if (d->snoop_by) {
		SEND_TO_Q("Your victim is no longer among us.\r\n", d->snoop_by);
		d->snoop_by->snooping = NULL;
		}

	if (d->character) {
		/*
		 * Plug memory leak, from Eric Green.
		 */
		if (!IS_NPC(d->character) && PLR_FLAGGED(d->character, PLR_MAILING) && d->str) {
			if (*(d->str))
				free(*(d->str));
			free(d->str);
			}
		if (STATE(d) == CON_PLAYING || STATE(d) == CON_DISCONNECT) {
			act("$n has lost $s link.", TRUE, d->character, 0, 0, TO_ROOM);
			if (!IS_NPC(d->character)) {
				SAVE_CHAR(d->character);
				syslog(GET_INVIS_LEV(d->character), TRUE, "Closing link to: %s.", GET_NAME(d->character));
				}
			d->character->desc = NULL;
			}
		else {
			syslog(0, TRUE, "Losing player: %s.", GET_NAME(d->character) ? GET_NAME(d->character) : "<null>");
			free_char(d->character);
			}
		}
	else
		/* removed to prevent auto reconnect spam */
		/*syslog(0, TRUE, "Losing descriptor without char.");*/

	/* JE 2/22/95 -- part of my unending quest to make switch stable */
	if (d->original && d->original->desc)
		d->original->desc = NULL;

	/* Clear the command history. */
	if (d->history) {
		int cnt;
		for (cnt = 0; cnt < HISTORY_SIZE; cnt++)
			if (d->history[cnt])
				free(d->history[cnt]);
		free(d->history);
		}

	if (d->showstr_head)
		free(d->showstr_head);
	if (d->showstr_count)
		free(d->showstr_vector);

	free(d);
	}


void check_idle_passwords(void) {
	Descr d, next_d;

	for (d = descriptor_list; d; d = next_d) {
		next_d = d->next;
		if (STATE(d) != CON_PASSWORD && STATE(d) != CON_GET_NAME)
			continue;
		if (!d->idle_tics) {
			d->idle_tics++;
			continue;
			}
		else {
			echo_on(d);
			SEND_TO_Q("\r\nTimed out... goodbye.\r\n", d);
			STATE(d) = CON_CLOSE;
			}
		}
	}



/*
 * I tried to universally convert Circle over to POSIX compliance, but
 * alas, some systems are still straggling behind and don't have all the
 * appropriate defines.  In particular, NeXT 2.x defines O_NDELAY but not
 * O_NONBLOCK.  Krusty old NeXT machines!  (Thanks to Michael Jones for
 * this and various other NeXT fixes.)
 */

#ifndef O_NONBLOCK
#define O_NONBLOCK O_NDELAY
#endif

void nonblock(socket_t s) {
	int flags;

	flags = fcntl(s, F_GETFL, 0);
	flags |= O_NONBLOCK;
	if (fcntl(s, F_SETFL, flags) < 0) {
		perror("SYSERR: Fatal error executing nonblock (comm.c)");
		exit(1);
		}
	}


/* ******************************************************************
*  signal-handling functions (formerly signals.c).  UNIX only.      *
****************************************************************** */

RETSIGTYPE reread_wizlists(int sig) {
	void reload_wizlists(void);
	syslog(0, TRUE, "Signal received - rereading wizlists.");
	reload_wizlists();
	}


RETSIGTYPE unrestrict_game(int sig) {
	syslog(0, TRUE, "Received SIGUSR2 - completely unrestricting game (emergent)");
	ban_list = NULL;
	wizlock_level = 0;
	num_invalid = 0;
	}

/* clean up our zombie kids to avoid defunct processes */
RETSIGTYPE reap(int sig) {
	while (waitpid(-1, NULL, WNOHANG) > 0);

	my_signal(SIGCHLD, reap);
	}

RETSIGTYPE checkpointing(int sig) {
	if (!tics) {
		log("SYSERR: CHECKPOINT shutdown: tics not updated. (Infinite loop suspected)");
		abort();
		}
	else
		tics = 0;
	}

RETSIGTYPE hupsig(int sig) {
	log("SYSERR: Received SIGHUP, SIGINT, or SIGTERM.  Shutting down...");
	exit(1);			/* perhaps something more elegant should
						 * substituted */
	}

/*
 * This is an implementation of signal() using sigaction() for portability.
 * (sigaction() is POSIX; signal() is not.)  Taken from Stevens' _Advanced
 * Programming in the UNIX Environment_.  We are specifying that all system
 * calls _not_ be automatically restarted for uniformity, because BSD systems
 * do not restart select(), even if SA_RESTART is used.
 *
 * Note that NeXT 2.x is not POSIX and does not have sigaction; therefore,
 * I just define it to be the old signal.  If your system doesn't have
 * sigaction either, you can use the same fix.
 *
 * SunOS Release 4.0.2 (sun386) needs this too, according to Tim Aldric.
 */

#ifndef POSIX
#define my_signal(signo, func) signal(signo, func)
#else
sigfunc *my_signal(int signo, sigfunc * func) {
	struct sigaction act, oact;

	act.sa_handler = func;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
#ifdef SA_INTERRUPT
	act.sa_flags |= SA_INTERRUPT;	/* SunOS */
#endif

	if (sigaction(signo, &act, &oact) < 0)
		return (SIG_ERR);

	return (oact.sa_handler);
	}
#endif				/* POSIX */


void signal_setup(void) {
	struct itimerval itime;
	struct timeval interval;

	/* user signal 1: reread wizlists.  Used by autowiz system. */
	my_signal(SIGUSR1, reread_wizlists);

	/*
	 * user signal 2: unrestrict game.  Used for emergencies if you lock
	 * yourself out of the MUD somehow.  (Duh...)
	 */
	my_signal(SIGUSR2, unrestrict_game);

	/*
	 * set up the deadlock-protection so that the MUD aborts itself if it gets
	 * caught in an infinite loop for more than 3 minutes.
	 */
	interval.tv_sec = 180;
	interval.tv_usec = 0;
	itime.it_interval = interval;
	itime.it_value = interval;
	setitimer(ITIMER_VIRTUAL, &itime, NULL);
	my_signal(SIGVTALRM, checkpointing);

	/* just to be on the safe side: */
	my_signal(SIGHUP, hupsig);
	my_signal(SIGCHLD, reap);
	my_signal(SIGINT, hupsig);
	my_signal(SIGTERM, hupsig);
	my_signal(SIGPIPE, SIG_IGN);
	my_signal(SIGALRM, SIG_IGN);
	}


/* ****************************************************************
*       Public routines for system-to-player-communication        *
**************************************************************** */

void send_to_char(const char *messg, Creature ch) {
	if (ch->desc && messg)
		SEND_TO_Q(messg, ch->desc);
	}


void msg_to_char(Creature ch, const char *messg, ...) {
	char output[MAX_STRING_LENGTH];
	va_list tArgList;

	if (!messg)
		return;

	va_start(tArgList, messg);
	vsprintf(output, messg, tArgList);

	if (ch->desc)
		SEND_TO_Q(output, ch->desc);

	va_end(tArgList);
	}


void send_to_all(const char *messg, ...) {
	Descr i;
	char output[MAX_STRING_LENGTH];
	va_list tArgList;

	if (!messg)
		return;

	va_start(tArgList, messg);
	vsprintf(output, messg, tArgList);

	for (i = descriptor_list; i; i = i->next)
		if (STATE(i) == CON_PLAYING)
			SEND_TO_Q(output, i);
	va_end(tArgList);
	}


void send_to_outdoor(const char *messg, ...) {
	Descr i;
	va_list tArgList;
	char output[MAX_STRING_LENGTH];

	if (!messg || !*messg)
		return;

	va_start(tArgList, messg);
	vsprintf(output, messg, tArgList);

	for (i = descriptor_list; i; i = i->next) {
		if (STATE(i) != CON_PLAYING || i->character == NULL)
			continue;
		if (!AWAKE(i->character) || !IS_OUTDOORS(i->character) || IS_WRITING(i->character))
			continue;
		SEND_TO_Q(output, i);
		}
	va_end(tArgList);
	}


void send_to_room(const char *messg, room_rnum room) {
	Creature i;

	if (messg == NULL)
		return;

	for (i = world[room].people; i; i = i->next_in_room)
		if (i->desc)
			SEND_TO_Q(messg, i->desc);
	}


const char *ACTNULL = "<NULL>";

#define CHECK_NULL(pointer, expression) \
  if ((pointer) == NULL) i = ACTNULL; else i = (expression);


/* higher-level communication: the act() function */
void perform_act(const char *orig, Creature ch, Object obj, const void *vict_obj, const Creature to) {
	const char *i = NULL;
	char *buf, lbuf[MAX_STRING_LENGTH];

	buf = lbuf;

	for (;;) {
		if (*orig == '$') {
			switch (*(++orig)) {
				case 'n':
					i = PERS(ch, (Creature)to, 0);
					break;
				case 'N':
					CHECK_NULL(vict_obj, PERS((Creature)vict_obj, to, 0));
					break;
				case 'o':
					i = PERS(ch, (Creature)to, 1);
					break;
				case 'O':
					CHECK_NULL(vict_obj, PERS((Creature)vict_obj, to, 1));
					break;
				case 'm':
					i = HMHR(ch);
					break;
				case 'M':
					CHECK_NULL(vict_obj, HMHR((Creature) vict_obj));
					break;
				case 's':
					i = HSHR(ch);
					break;
				case 'S':
					CHECK_NULL(vict_obj, HSHR((Creature) vict_obj));
					break;
				case 'e':
					i = HSSH(ch);
					break;
				case 'E':
					CHECK_NULL(vict_obj, HSSH((Creature) vict_obj));
					break;
				case 'p':
					CHECK_NULL(obj, OBJS(obj, to));
					break;
				case 'P':
					CHECK_NULL(vict_obj, OBJS((const Object) vict_obj, to));
					break;
				case 'a':
					CHECK_NULL(obj, SANA(obj));
					break;
				case 'A':
					CHECK_NULL(vict_obj, SANA((const Object) vict_obj));
					break;
				case 'T':
					CHECK_NULL(vict_obj, (const char *) vict_obj);
					break;
				case 'F':
					CHECK_NULL(vict_obj, fname((const char *) vict_obj));
					break;
				case '$':
					i = "$";
					break;
				default:
					log("SYSERR: Illegal $-code to act(): %c", *orig);
					log("SYSERR: %s", orig);
					i = "";
					break;
				}
			while ((*buf = *(i++)))
				buf++;
			orig++;
			}
		else if (!(*(buf++) = *(orig++)))
			break;
		}

	*(--buf) = '\r';
	*(++buf) = '\n';
	*(++buf) = '\0';

	SEND_TO_Q(CAP(lbuf), to->desc);
	}


#define SENDOK(ch)	((ch)->desc && (to_sleeping || AWAKE(ch)) && (IS_NPC(ch) || !PLR_FLAGGED((ch), PLR_WRITING)))

void act(const char *str, int hide_invisible, Creature ch, Object obj, const void *vict_obj, int type) {
	Creature to = NULL;
	bool to_sleeping = FALSE, no_dark = FALSE;

	if (!str || !*str)
		return;

	if (IS_SET(type, TO_SLEEP))
		to_sleeping = TRUE;

	if (IS_SET(type, TO_NODARK))
		Global_ignore_dark = no_dark = TRUE;

	/* To the character */
	if (IS_SET(type, TO_CHAR) && ch && SENDOK(ch))
		perform_act(str, ch, obj, vict_obj, ch);

	/* To the victim */
	if (IS_SET(type, TO_VICT) && (to = (Creature) vict_obj) != NULL && SENDOK(to))
		perform_act(str, ch, obj, vict_obj, to);


	if (IS_SET(type, TO_NOTVICT | TO_ROOM)) {
		if (ch && ch->in_room != NOWHERE)
			to = world[ch->in_room].people;
		else if (obj && obj->in_room != NOWHERE)
			to = world[obj->in_room].people;

		if (to)
			for (; to; to = to->next_in_room) {
				if (!SENDOK(to) || (to == ch))
					continue;
				if (hide_invisible && ch && !CAN_SEE(to, ch))
					continue;
				if (IS_SET(type, TO_NOTVICT) && to == vict_obj)
					continue;
				perform_act(str, ch, obj, vict_obj, to);
				}
		}
	Global_ignore_dark = FALSE;
	}

/*
 * This function is called every 30 seconds from heartbeat().  It checks
 * the four global buffers in EmpireMUD to ensure that no one has written
 * past their bounds.  If our check digit is not there (and the position
 * doesn't have a NUL which may result from snprintf) then we gripe that
 * someone has overwritten our buffer.  This could cause a false positive
 * if someone uses the buffer as a non-terminated character array but that
 * is not likely. -gg
 */
void sanity_check(void) {
	int ok = TRUE;

	/*
	 * If any line is false, 'ok' will become false also.
	 */
	ok &= (test_magic(buf)  == MAGIC_NUMBER || test_magic(buf)  == '\0');
	ok &= (test_magic(buf1) == MAGIC_NUMBER || test_magic(buf1) == '\0');
	ok &= (test_magic(buf2) == MAGIC_NUMBER || test_magic(buf2) == '\0');
	ok &= (test_magic(arg)  == MAGIC_NUMBER || test_magic(arg)  == '\0');

	/*
	 * This isn't exactly the safest thing to do (referencing known bad memory)
	 * but we're doomed to crash eventually, might as well try to get something
	 * useful before we go down. -gg
	 * However, lets fix the problem so we don't spam the logs. -gg 11/24/98
	 */
	if (!ok) {
		log("SYSERR: *** Buffer overflow! ***\nbuf: %s\nbuf1: %s\nbuf2: %s\narg: %s", buf, buf1, buf2, arg);

		plant_magic(buf);
		plant_magic(buf1);
		plant_magic(buf2);
		plant_magic(arg);
		}

#if 0
	log("Statistics: buf=%d buf1=%d buf2=%d arg=%d", strlen(buf), strlen(buf1), strlen(buf2), strlen(arg));
#endif
	}

/* Prefer the file over the descriptor. */
void setup_log(const char *filename, int fd) {
	FILE *s_fp;

#if defined(__MWERKS__) || defined(__GNUC__)
	s_fp = stderr;
#else
	if ((s_fp = fdopen(STDERR_FILENO, "w")) == NULL) {
		puts("SYSERR: Error opening stderr, trying stdout.");

		if ((s_fp = fdopen(STDOUT_FILENO, "w")) == NULL) {
			puts("SYSERR: Error opening stdout, trying a file.");

			/* If we don't have a file, try a default. */
			if (filename == NULL || *filename == '\0')
				filename = "log/syslog";
			}
		}
#endif

	if (filename == NULL || *filename == '\0') {
		/* No filename, set us up with the descriptor we just opened. */
		logfile = s_fp;
		puts("Using file descriptor for logging.");
		return;
		}

	/* We honor the default filename first. */
	if (open_logfile(filename, s_fp))
		return;

	/* Well, that failed but we want it logged to a file so try a default. */
	if (open_logfile("log/syslog", s_fp))
		return;

	/* Ok, one last shot at a file. */
	if (open_logfile("syslog", s_fp))
		return;

	/* Erp, that didn't work either, just die. */
	puts("SYSERR: Couldn't open anything to log to, giving up.");
	exit(1);
	}

int open_logfile(const char *filename, FILE *stderr_fp) {
	if (stderr_fp)	/* freopen() the descriptor. */
		logfile = freopen(filename, "w", stderr_fp);
	else
		logfile = fopen(filename, "w");

	if (logfile) {
		printf("Using log file '%s'%s.\n", filename, stderr_fp ? " with redirection" : "");
		return (TRUE);
		}

	printf("SYSERR: Error opening file '%s': %s\n", filename, strerror(errno));
	return (FALSE);
	}

/*
 * This may not be pretty but it keeps game_loop() neater than if it was inline.
 */
void empire_sleep(struct timeval *timeout) {
	if (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, timeout) < 0) {
		if (errno != EINTR) {
			perror("SYSERR: Select sleep");
			exit(1);
			}
		}
	}