/*
 * Copyright 1988, 1989, 1990, 1991 Massachusetts Institute of Technology
 */
#define MAINFILE
#include"structs.h"

int main(argc,argv)
     char **argv;
     int argc;
{
	fd_set justcheck;
	int cc, i, timeouts;
#ifndef SYSV
	uid_t uid;
	struct rlimit rlp;
#endif /* SYSV */
	struct timeval discsave, forward_to;
	struct sockaddr_in server;
	struct in_addr inet_address;
#ifdef UNIXCONN
	struct sockaddr_un unix_server;
#endif /* UNIXCONN */
	char localhost[60];
	struct passwd *userinfo;
	struct servent *service, *notice_service;
	struct timeval *to, timout;
	int dispatches = 0, this_run = 0;
	int prewait;
	struct timeval zero_delay;
	fd_set delay_fd_set;
	int ignore_bind = 0, bind_res = 0;
	char *unlink_reason();
	int cur_ext;
	
#ifdef OBSOLETE
	if (starts) {
		system("echo galatea restarted | mail danapple@media-lab.mit.edu");
		bsdsyslog(LOG_CRIT, "galatead restarted!  Major bug!");
		return -1;
	}
	starts++;
#endif OBSOLETE
	
	if (!gettimeofday(&timout,NULL)) start_time = timout.tv_sec;
	
	strcpy(configfile, CONFIGURATION_FILE);
	/* command line parser */
	for (i = 1; i< argc ; i++) {
		if (!strcmp(argv[i],"-devices")) {
			print_models();
			exit(0);
		}
		else if (!strcmp(argv[i],"debug") || !strcmp(argv[i],"-debug")) DEBUG = 1;
		/*    else if (!strcmp(argv[i],"restrict")) RESTRICT = 1;*/
		else if (!strcmp(argv[i],"spinup") || !strcmp(argv[i], "-spinup")) 
		  SPINUP = 1;
		else if (argv[i][0] == '/') strcpy(configfile, argv[i]);
		else if (!strcmp(argv[i],"-f")) strcpy(configfile, argv[++i]);
		else if (!strcmp(argv[i],"-i")) ignore_bind = 1;
		else if (DEBUG) printf("Unknown option: %s\n", argv[i]);
	}
	forward_to.tv_sec = FORWARD_TIMEOUT;
	forward_to.tv_usec = 0;
	GSetState(/*(int)DEBUG*/0,&forward_to ,SYNC_MODE);
	if (DEBUG) printf("Server revision: %.1f  Protocol Revision: %.1f\n",
					  (float)SERVER_REVISION / 10.0,
					  (float)PROTOCOL_REVISION / 10.0);
	
	if (DEBUG) printf("DEBUG mode: * = fatal error; > = warning.\n");
	
#ifndef SYSV
	if (DEBUG) printf("%d file descriptors\n", getdtablesize());
	
#ifdef CORE_DUMP_DIR
	if (!chdir(CORE_DUMP_DIR)) {
		if (DEBUG) printf("Core dumps to %s\n",CORE_DUMP_DIR);
		if (!getrlimit(RLIMIT_CORE, &rlp)) {
			rlp.rlim_cur = rlp.rlim_max;
			if (DEBUG) printf("Core dump limit is %d\n",rlp.rlim_cur);
			setrlimit(RLIMIT_CORE, &rlp);
		}
	}
#endif /* CORE_DUMP_DIR */
#endif /* SYSV */
	
	/* setup timeouts */
	discsave.tv_sec = QUERYTIME;
	discsave.tv_usec = 0;
	
	signal(SIGPIPE, SIG_IGN);  /* take care of this nice and early */
	signal(SIGHUP, SIG_IGN);   /* don't catch this until we're ready */
#ifdef TRAP_BUG
	signal(SIGILL, handle_fatal);
	signal(SIGTRAP, handle_fatal);
	signal(SIGIOT, handle_fatal);
	signal(SIGEMT, handle_fatal);
	signal(SIGFPE, handle_fatal);
	signal(SIGBUS, handle_fatal);
	signal(SIGSEGV, handle_fatal);
	signal(SIGSYS, handle_fatal);
#ifdef SIGXCPU
	signal(SIGXCPU, handle_fatal);
#endif /* SIGXCPU */
#ifdef SIGXFSZ
	signal(SIGXFSZ, handle_fatal);
#endif /* SIGXFSZ */
#endif /* TRAP_BUG */
	
	/* open system logging facility */
	bsdopenlog("galatead", LOG_CONS | LOG_NOWAIT | LOG_PID, LOG_LOCAL2);
	userinfo = getpwuid(getuid());
	if (userinfo)
	  bsdsyslog(LOG_INFO,"Start by %s", userinfo->pw_name);
	else bsdsyslog(LOG_INFO,"Start by uid %d",getuid());
	
	extctrl(EXT_INIT, NULL);
	for (cur_ext = 0; cur_ext < extnumexts(); cur_ext++)
	  bsdsyslog(LOG_INFO,"%s enabled.\n", extname(cur_ext));
	
#ifdef FORKLOCK
	if (DEBUG) printf("Using forking unlocker.\n");
#else
#ifdef FULLDEBUG
	if (DEBUG) printf("Using non-forking unlocker.\n");
#endif /* FULLDEBUG */
#endif /* FORKLOCK */
	
#ifndef SYSV
	uid = 0;
	setuid(uid);
#endif /* SYSV */
	/* setup video disk */
	build_volumes(0);
	need_to_rebuild_list = 0;
	
	FD_ZERO(&allavail);
	
	/* establish local id */
	gethostname(localhost,63);
	lhp = gethostbyname(localhost);
	if (lhp) {
		bcopy(lhp->h_addr, &inet_address, lhp->h_length);
		strcpy(localaddress, inet_ntoa(inet_address));    
		if (DEBUG) printf("Local host is %s, address is %s\n",lhp->h_name,
						  localaddress);
	}
	if (!lhp && DEBUG) printf("Can't resolve local host.\n");
	
	
	/* setup network connection */
	if (DEBUG) printf("Creating main listening socket.\n");
	
	sock = socket(AF_INET,SOCK_STREAM,0);
	if (sock < 0) {
		printf("> Error creating main socket.  Server is running, anyway.\n");
	}
	else {
#ifdef SOL_SOCKET
#ifdef SO_REUSEADDR
		cc = 1;
		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &cc, sizeof(cc)) == 0) {
#ifdef FULLDEBUG
			if (DEBUG) printf("sock set to SO_REUSEADDR\n");
#endif /* FULLDEBUG */
		}
		else bsdsyslog(LOG_NOTICE, "Failure setting SO_REUSEADDR");
#endif /* SO_REUSEADDR */
#endif /* SOL_SOCKET */
		server.sin_family = AF_INET;
		server.sin_addr.s_addr = INADDR_ANY;
		
		service = getservbyname("galatea",NULL);
		
		if (service == 0) {
			server.sin_port = htons(4001);
		}
		else {
			server.sin_port = service->s_port;
		}
		if (DEBUG) printf("Main listening port is %d.\n",ntohs(server.sin_port));
		bind_res = bind(sock, &server, sizeof(server));
		if (bind_res == -1  && ignore_bind == 0) {
			printf("* Error binding socket.  This could mean that %s\n",argv[0]);
			printf("* is already running or that UNIX has not yet figured out\n");
			printf("* that an old one has already died.  Server is NOT running.\n");
			exit(-2);
		}
		if (bind_res == -1 && ignore_bind != 0) {
			printf("> Error binding socket.  This could mean that %s\n",argv[0]);
			printf("> is already running or that UNIX has not yet figured out\n");
			printf("> that an old one has already died.  Server is running, anyway.\n");
		}
		
		if (bind_res == 0) FD_SET(sock,&allavail);
	}
	
#ifdef UNIXCONN
	/* make sure nobody thinks we have unix sockets yet */
	unix_sock = old_unix_sock = -1;
	
	/* setup unix listening socket */
	if (DEBUG) printf("Creating UNIX socket: %s\n", G_UNIX_PATH);
	unix_sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (unix_sock < 0) {
		if (DEBUG) 
		  printf("> Error creating UNIX socket.  Server is running, anyway.\n");
	}
	else {
		unix_server.sun_family = AF_UNIX;
		strcpy (unix_server.sun_path, G_UNIX_PATH);
		if(unlink(unix_server.sun_path) && errno != ENOENT)
		  bsdsyslog(LOG_NOTICE, "failed to unlink '%s': because %s.\n",
					G_UNIX_PATH,unlink_reason(errno));
		if (bind(unix_sock, (struct sockaddr *)&unix_server, 
				 strlen(unix_server.sun_path) + 2) < 0)
		  {
			  bsdsyslog(LOG_NOTICE, "failed to bind UNIX socket '%s'.\n",
						G_UNIX_PATH);
			  if (DEBUG) 
				printf("> Error binding UNIX socket.  Server is running, anyway.\n");
		  }
		else {
			listen(unix_sock, 5);
			FD_SET(unix_sock,&allavail);
		}
	}
#ifdef G_OLD_UNIX_PATH
	if (DEBUG) printf("Creating backward compatibility UNIX socket: %s\n",
					  G_OLD_UNIX_PATH);
	/* setup unix listening socket */
	old_unix_sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (old_unix_sock < 0) {
		if (DEBUG)
		  printf("> Error creating old UNIX socket.  Server is running, anyway.\n");
	}
	else {
		unix_server.sun_family = AF_UNIX;
		strcpy (unix_server.sun_path, G_OLD_UNIX_PATH);
#ifdef G_OLD_UNIX_DIR
		if(unlink(unix_server.sun_path) && errno != ENOENT && errno != ENOTDIR)
		  bsdsyslog(LOG_NOTICE, "failed to unlink '%s': because %s.\n",
					G_OLD_UNIX_DIR,unlink_reason(errno));
		
		mkdir(G_OLD_UNIX_DIR, 0777);
		chmod(G_OLD_UNIX_DIR, 0777);  /* prevent interference of umask */
#endif /* G_OLD_UNIX_DIR */
		
		if (bind(old_unix_sock, (struct sockaddr *)&unix_server, 
				 strlen(unix_server.sun_path) + 2) < 0)
		  {
			  bsdsyslog(LOG_NOTICE, "failed to bind old UNIX socket '%s'.\n",
						G_OLD_UNIX_PATH);
			  if (DEBUG)
				printf("> Error binding old UNIX socket.  Server is running, anyway.\n");
		  }
		else {
			listen(old_unix_sock, 5);
			FD_SET(old_unix_sock,&allavail);
		}
	}
#endif /* G_OLD_UNIX_PATH */
#endif /* UNIXCONN */
	
	/* setup server recovery socket */
	if ((notice_socket = socket(AF_INET, SOCK_DGRAM, 0)) >= 0) {
		notice_service = getservbyname("galanotice","udp");
		if (notice_service == 0) {
			notice.sin_port = htons(4002);
		}
		else {
			notice.sin_port = notice_service->s_port;
		}
		
		notice.sin_addr.s_addr = INADDR_ANY;
		notice.sin_family = AF_INET;
		
		if (DEBUG) printf("Notification port is %d.\n",ntohs(notice.sin_port));
		if (bind(notice_socket, &notice, sizeof(notice)) < 0) {
			if (DEBUG) printf("> Bad bind for notification.\n");
			notice_socket = -1;
		}
	}
	if (notice_socket == -1 && DEBUG) 
	  printf("> No notification service.\n");
	if (notice_socket > -1 ) {
		FD_SET(notice_socket, &allavail);
		if (DEBUG) printf("Notification enabled.\n");
		notify_hosts();  /* notify previous connected hosts */
	}
	
	/* misc bullshit */
	notify_list = (char **)calloc(MAXSOCKETS, sizeof(char *));
	for ( i = 0 ; i < MAXSOCKETS; i++) {
		clients[i].sock = -1;
		clients[i].address = (char *)NULL;
		clients[i].rev = -1;
		clients[i].local = 0;
		clients[i].output = -1;
		clients[i].seglist = (RPD_SEGLIST *)NULL;
	}
	
#ifdef DBM
	g_dbminit();
#endif /* DBM */
	
	/* eliminate error messages */
	if (!DEBUG) {
		for (i = 0; i < 3; ++i)
		  close(i);
		open("/",O_RDONLY);
		dup2(0,1);
		dup2(0,2);
		
		i = open("/dev/tty",O_RDWR);
		if (i >= 0) {
#ifndef SYSV
			ioctl(i,TIOCNOTTY,0);
#else
			/* cc = 0;*/
			/*ioctl(i, TIOCSPGRP, &cc);*/  /* Where is the definition???? */
#endif /* SYSV */
			
			close(i);
		}
	}
	
	/* fork and start infinite loop */
#ifndef TRAP_BUG
	if (!DEBUG && fork()) exit(0);
#endif /* TRAP_BUG */
	if (!DEBUG) bsdsyslog(LOG_INFO,"Forked");
	
#ifdef FORKLOCK
	signal(SIGUSR1, unlock_self);
	timer_pid = fork();
	if (!timer_pid) {
		supervisor();  /* should never return */
		exit(0);
	}
#else /* FORKLOCK */
	signal(SIGALRM, unlock_self);
#endif /* FORKLOCK */
	/* write process ID */
	write_pid();
	
	/* prepare to die */
	signal(SIGTERM,dienow);
	signal(SIGINT,dienow);
	
	/* handle requests to rebuild volume list */
	signal(SIGHUP, sigrebuild);
	
	listen(sock,5);
	
	justcheck = allavail;
	unlock_all();  /* just to make sure; its a cheap call */
	spinupplayers();
	timeouts = 0;   /* we haven't had any timeouts yet */
	run_state = 1;
	if (DEBUG) printf("/// Initialization complete ///\n");
	while(1) {
		/* check in case previous dealwithit inspired a rebuild */
		if (need_to_rebuild_list) rebuildvolumes();
		
		signal(SIGPIPE, sigrebuild);  /* handle rebuilds only when there is no
										 chance of getting the signal from a 
										 client connection */
		unlock_all();   /* just to make sure we have 
						   released all remote servers */
		do {
			/* figure out whether to use all the file descripters, or just
			   the one for the locking client (if any) */
			if (LOCKED) justcheck = lockavail;
			else justcheck = allavail;
			timout = discsave;
			to = &timout;
			
			delay_fd_set = justcheck;
			zero_delay.tv_sec = 0;
			zero_delay.tv_usec = 0;
			prewait = select(MAXSOCKETS, &delay_fd_set, 0, 0, &zero_delay);
			if (prewait > 0) delayed_requests += prewait;  /* prewait can be -1 */
			
			cc = select(MAXSOCKETS,&justcheck,0,0,to);  /* anybody want anything? */
			if (need_to_rebuild_list) rebuildvolumes(); /* if there was a SIGHUP */
		} while (cc < 0);
		if (cc == 0) {
			if (timeouts < SPINMULT) {
				/* if we couldn't connect to a server before, see if we can now */
				check_remote_servers();
				check_devices();
				extctrl(EXT_PERIODIC, NULL);
				timeouts++;  /* we've had another timeout */
			}
			else {
				/* spin down players and wait for something to be requested */
				saveplayers();
				timeouts = 0;
			}
		}
		if (cc > 0) {
			/* make sure we can't get a SIGPIPE from clients */
			signal(SIGPIPE, SIG_IGN);
			
			/* We have to rebuild volume tables */
			if ((notice_socket >= 0 && FD_ISSET(notice_socket, &justcheck)) || 
				need_to_rebuild_list)
			  rebuildvolumes();
			
			/* This is the main listening socket */
			if (sock >= 0 && FD_ISSET(sock,&justcheck)) acceptnew(sock);
#ifdef UNIXCONN
			if (unix_sock >= 0 && FD_ISSET(unix_sock, &justcheck)) 
			  acceptnew(unix_sock);
#ifdef G_OLD_UNIX_PATH
			if (old_unix_sock >= 0 && FD_ISSET(old_unix_sock, &justcheck)) {
				if (DEBUG) printf("Got a connection on the old unix socket.\n");
				acceptnew(old_unix_sock);
			}
#endif /* G_OLD_UNIX_PATH */
#endif /* UNIXCONN */
			
			/* Check previously connected sockets */
			this_run = 0;
			for (i = 0; i < MAXSOCKETS; i++) {
				if (need_to_rebuild_list) rebuildvolumes();
				if (clients[i].sock != -1 && FD_ISSET(clients[i].sock,&justcheck)) {
#ifdef FULLDEBUG
					if (DEBUG) printf ("Dispatching on connection %d\n",i);
#endif /* FULLDEBUG */
					dispatches++;
					this_run++;
					dealwithit(i);
					if (LOCKED) break; 
				}
			}
			if (this_run > 0) timeouts = 0;
			if (this_run > 1) {
				num_collisions++;
			}
		}
		if (dispatches > 300) {
			check_remote_servers();
			check_devices();
			extctrl(EXT_PERIODIC, NULL);
			dispatches = 0;
		}
	}
}

saveplayers()
{
	int i, cc;
	fd_set justcheck;
	struct timeval waitaround;
	
	if (DEBUG) printf("Spinning down RPD's.\n");
	run_state = 0;
	for (i = 0 ; i < num_switchers; i++)
	  (*switchers[i]->reset)(switchers[i]);
	unlock_all();
	spindownplayers();
	do {
		waitaround.tv_sec = QUERYTIME;
		waitaround.tv_usec = 0;
		if (LOCKED) justcheck = lockavail;
		else justcheck = allavail;
		cc = select(MAXSOCKETS,&justcheck,0,0,&waitaround);
		
		if (cc == 0) {
			check_remote_servers();
			check_devices();
			extctrl(EXT_PERIODIC, NULL);
		}
		/* We have to rebuild volume tables */
		if ((notice_socket >= 0 && FD_ISSET(notice_socket, &justcheck)) || 
			need_to_rebuild_list) {
			rebuildvolumes();
			cc = 0;
		}
	} while( cc < 1);
	if (DEBUG) printf("Spinning up RPD's.\n");
	run_state = 1;
	spinupplayers();
}
#ifdef FORKSPIN
spinupplayers()
{
	int i, num_forked = 0;
	RPD_ptr rpd;
	signal(SIGTERM,SIG_IGN);
	signal(SIGINT,SIG_IGN);
	for (i = 0; i < num_players; i++) {
		rpd = players[i];
		if ((*rpd->cmd)(rpd,FORKLOAD) == 0) num_forked++;
	}
	while(num_forked-- > 0)
	  wait(0);
	signal(SIGTERM,dienow);
	signal(SIGINT,dienow);
	unlock_all();
}  
#else /* FORKSPIN */
spinupplayers()
{
	int i, max_time = -1, new_time;
	RPD_ptr rpd;
	for (i = 0; i < num_players; i++) {
		rpd = players[i];
		/* MAX() is a macro; can't put function calls as arguments! */
		new_time = (*rpd->cmd)(rpd,STARTLOAD);
		if (new_time == -1) continue;
		max_time = MAX(max_time, new_time);
	}
	if (DEBUG) printf("Waiting %d milliseconds for spinup.\n",max_time);
	mpause(max_time);
	max_time = -1;
	for (i = 0; i < num_players; i++) {
		rpd = players[i];
		new_time = (*rpd->cmd)(rpd,ENDLOAD);
		if (new_time == -1) continue;
		max_time = MAX(max_time, new_time);
	}
	if (DEBUG) printf("Waiting %d seconds for final part of spinup.\n",max_time);
	sleep(max_time);
	unlock_all();
}  
#endif /* FORKSPIN */
spindownplayers()
{
	int i;
	RPD_ptr rpd;
	for (i = 0; i < num_players; i++) {
		rpd = players[i];
		(*rpd->cmd)(rpd,UNLOAD);
	}
	unlock_all();
}  

dienow(sig)
     int sig;
{
	bsdsyslog(LOG_NOTICE, "Shutting down on signal %d ...", sig);
	if (sig == 15) shutoff_server(1);
	else shutoff_server(0);
	/*NOTREACHED*/
}

char *
  unlink_reason(num)
int num;
{
	switch(num)
	  {
		case ENOTDIR:
		  return "dir doesn't exist";
		  break;
		case ENOENT:
		  return "file doesn't exist";
		  break;
		case EACCES:
		  return "don't have permission";
		  break;
		case EPERM:
		  return "not owner of dir";
		  break;
		default:
		  return "some strange reason";
		  break;
	  }
}

shutoff_server(spindown)
     int spindown;
{
	int j;
	char *unlink_reason();
	
#ifdef FORKLOCK
	if (timer_pid > 0)
	  kill(timer_pid, SIGTERM); /* get rid of supervisor */
#endif /* FORKLOCK */
	extctrl(EXT_SHUTOFF, NULL);
	listen(sock,0);  /* don't allow new connections */
	shutdown(sock, 2);
	notify_hosts();  /* tell the Requesting hosts to try to reconnect
						The Requesting hosts attempt to connect will fail */
	for (j = 0; j < MAXSOCKETS; j++) {
		if (clients[j].sock != -1) {
			shutdown(clients[j].sock, 2);
			close(clients[j].sock);
		}
	}
	if (spindown) spindownplayers();
#ifdef DBM
	g_dbmsave();
#endif /* DBM */
#ifdef OBSOLETE
	sleep(10);  /* give the clients a bit to close the connection */
#endif OBSOLETE
	close(sock);
	if(unlink(PID_FILE))
	  bsdsyslog(LOG_NOTICE, "failed to unlink '%s': because %s.\n",
				PID_FILE,unlink_reason(errno));
#ifdef UNIXCONN
	/* don't leave those unix sockets lying around */
	if(unix_sock >= 0)
	  {
		  shutdown(unix_sock, 2);
		  close(unix_sock);
		  if(unlink(G_UNIX_PATH))
			bsdsyslog(LOG_NOTICE, "failed to unlink '%s': because %s.\n",
					  G_UNIX_PATH,unlink_reason(errno));
	  }
#ifdef G_OLD_UNIX_PATH
	if(old_unix_sock >= 0)
	  {
		  shutdown(old_unix_sock, 2);
		  close(old_unix_sock);
		  if(unlink(G_OLD_UNIX_PATH))
			bsdsyslog(LOG_NOTICE, "failed to unlink '%s': because %s.\n",
					  G_OLD_UNIX_PATH,unlink_reason(errno));
#ifdef G_OLD_UNIX_DIR
#ifdef SYSV
		  if(unlink(G_OLD_UNIX_DIR))
			bsdsyslog(LOG_NOTICE, "failed to unlink '%s': because %s.\n",
					  G_OLD_UNIX_DIR,unlink_reason(errno));
#else /* SYSV */
		  if(rmdir(G_OLD_UNIX_DIR))
			bsdsyslog(LOG_NOTICE, "failed to rmdir '%s': because %s.\n",
					  G_OLD_UNIX_DIR,unlink_reason(errno));
#endif /* SYSV */
#endif /* G_OLD_UNIX_DIR */
	  }
#endif /* G_OLD_UNIX_PATH */
#endif /* UNIXCONN */
	bsdsyslog(LOG_NOTICE, "fully shutdown: exiting");
	exit(0);
	/*NOTREACHED*/
}

handle_fatal(sig)
     int sig;
{
	signal(SIGIOT, SIG_DFL);
	switch (sig) {
	  case SIGILL:
		bsdsyslog(LOG_CRIT,"illegal instruction");
		break;
	  case SIGTRAP:
		bsdsyslog(LOG_CRIT,"trace trap");
		break;
	  case SIGIOT:
		bsdsyslog(LOG_CRIT,"IOT instruction");
		break;
	  case SIGEMT:
		bsdsyslog(LOG_CRIT,"EMT instruction");
		break;
	  case SIGFPE:
		bsdsyslog(LOG_CRIT,"floating point exception");
		break;
	  case SIGBUS:
		bsdsyslog(LOG_CRIT,"bus error");
		break;
	  case SIGSEGV:
		bsdsyslog(LOG_CRIT,"segmentation violation");
		break;
	  case SIGSYS:
		bsdsyslog(LOG_CRIT,"bad argument to system call");
		break;
#ifdef SIGXCPU
	  case SIGXCPU:
		bsdsyslog(LOG_CRIT,"cpu time limit exceeded");
		break;
#endif /* SIGXCPU */
#ifdef SIGXFSZ
	  case SIGXFSZ:
		bsdsyslog(LOG_CRIT,"file size limit exceeded");
		break;
#endif /* SIGXFSZ */
	}
	abort();
	exit(3);
}
