/*	
 *	ilwrite() : write driver
 *		1. copy Lock request info to lockbuf
 *		2. follow action in l_act
 *		3. Error return conditions
 *			-1: lockrequest fails(only on act=1)
 *			-2: attempt to release a lock not set
 *			    by calling program
 *			-3: illegal action requested
 */

# include	<stdio.h>
# include	<sys/param.h>
# include	<sys/socket.h>
# include	<netinet/in.h>
# include	<sys/ioctl.h>
# include	<errno.h>
# include	<ildr.h>
# include	<sys/time.h>
# include	<netdb.h>
# include	<sccs.h>

# define	TRUE	1
# define	FALSE	0

# ifdef	DEBUG
static int ildebug = TRUE;
# endif	DEBUG

SCCSID(@(#)ildr.c	8.2	6/12/88)

extern  int     init_sokeet();
int             new_proc();
int             ilwrite();
int             ilrma();
int             set_lock();
int             send_info();
int             wait_on();
int             wakeup();
int             close_up();
void            abnormal();
static  int     ilunique();
static  int     ilfind();
static  int     ilrm();
static  int     ilenter();
static  int     ilcomp();
static  int     ilclose();

int	From_server;		/* read connection from socket server */
long	Read_fmt;		/* bit mask for select */
char	*Prog_name;		/* program name for abnormal errors */
extern	int	errno;		/* error number */

/*
** main
** initilize the socket to the socket server, and then sit and on
** a select waiting for input.
*/
main(ac,av)
int	ac;
char	**av;
{
	long		read_fd,		/* bit mask of useable descriptors*/
			num_des;		/* number of readable descriptors */
	void		abnormal();		/* error function */
	register	int	i;		/* index */
        struct timeval        tv;               /* added by K.Okamoto  */

	/*
	** close all the files descriptors, so we can have more INGRES
	** processes. We are lmited by file descriptors.
	*/
# ifdef	DEBUG
	setbuf(stdout,NULL);
	printf("DRIVER: starting up\n");
	for ( i = 3 ; i < NOFILE ; i++ )
# else
	for ( i = 1 ; i < NOFILE ; i++ )
# endif	DEBUG
		close(i);

	/*
	** set up all the signals. Catching any signal
	** is an error. We do a "warm start" in this condition
	*/
	for ( i = 0 ; i < NSIG ; i++ )
		signal(i, abnormal);
       	signal(SIGPIPE,SIG_IGN);	/* ignore this one, in case a process simply dies in mid stride */
        Prog_name = *av;
	/*
	** if ac == 2, then we are restarted from the the lock driver
	** itself, and we don't have to reattach ourselves to the magic
	** ingres socket.
	*/
	if ( ac == 2 )
		From_server = 0;
	else {
		/* guarantee that 0 is what is attached to the socket server */
		close(0);
		From_server = init_socket();
	}
	Read_fmt = (1<<From_server);
        tv.tv_sec = 0;
        tv.tv_usec = 0; 

	/*
	** infinite loop waiting for something to happen
	*/
	for ( ;; )
	{
		read_fd = Read_fmt;

		/*
		** wake up whenever something happens
		*/

		num_des = select(NOFILE,&read_fd,0,0,0);

		while ( (num_des = select(NOFILE,&read_fd,0,0,0)) == 0 )
/*
		while ( (num_des = select(NOFILE,&read_fd,NULL,NULL,&tv)) == 0 )
*/
		{
# ifdef	DEBUG
		printf("select returns 0 (%o)\n",read_fd);
# endif	DEBUG
			read_fd = Read_fmt;
		}
# ifdef	DEBUG
		printf("select returns %d (%o)\n",num_des,read_fd);
# endif	DEBUG


		if ( num_des == -1 )
		{
# ifdef	DEBUG
			perror("DRIVER:num_des = -1");
# endif DEBUG
			/*
			** a bit of defensive programming.
			** If there is an EBADF (bad file descriptor) error 
			** then we assume that a file descriptor has shut down,
			** without tellng us. We go to a function to figure
			** out what has died.
			*/
			if ( errno == EBADF )
				close_up(Read_fmt);
			sleep(1);
			continue;
		}
		if ( (read_fd & (1<<From_server)) )
		{
			num_des--;
			new_proc();
			read_fd &= ~(1<<From_server);
		}
		if ( num_des > 0 )
		{
			for ( i = 0 ; i < NOFILE ; i++ )
				if ( (read_fd & (1<<i)) )
					ilwrite(i);
		}
	}
}/* main */

/*
** new_proc
** start up a new connection to an Ingres process
*/
int
new_proc()
{
	register	int	fd;
	auto		int	to_ioctl = 1;
	struct	sockaddr_in	addr;
	auto		int	len;

	len = sizeof (addr);
	if ( (fd = accept(From_server,(struct sockaddr *)&addr,&len)) != -1 )
	{
		Read_fmt |= (1<<fd);
		ioctl(fd,FIONBIO,&to_ioctl);   /* set sockets to nonblocking */
	}
# ifdef	DEBUG
	else
	{
		perror("accept");
		sleep(1);
	}
	printf("DRIVER: new file %d (%o)\n",fd,(1<<fd));
# endif	DEBUG
}/* new_proc */


int
ilwrite(read_desc)
register	int	read_desc;
{
	struct Lockreq	lockbuf;
	register int i;
	register int blockflag;
	extern	int	errno;

	errno = 0;
# ifdef	DEBUG
	printf("DRIVER: entering ilwrite, read_desc = %d\n",read_desc);
# endif	DEBUG
	if ( read(read_desc,&lockbuf, sizeof ( lockbuf)) != sizeof ( lockbuf ) )
	{
# ifdef	DEBUG
		printf("Read error, errno = %d\n",errno);
# endif	DEBUG
		if ( errno == EWOULDBLOCK )
			return;
		if ( errno == ECONNRESET )
		{
			ilrma(read_desc,TRUE);
			close(read_desc);
			Read_fmt &= ~(1<<read_desc);
			return;
		}
		send_info(read_desc,-5);
		return;
	}

# ifdef	DEBUG
	if (ildebug)
		printf("ildr: act %d, type %d, mode %d, read_desc %d\n",
			lockbuf.lr_act, lockbuf.lr_type, lockbuf.lr_mod, read_desc);
# endif	DEBUG
	if (( lockbuf.lr_act < A_RLS1)
	&& ((lockbuf.lr_type < T_CS) || (lockbuf.lr_type > T_DB )
	   || (lockbuf.lr_mod < M_EXCL) || (lockbuf.lr_mod > M_SHARE )))
	{
# ifdef	DEBUG
		printf("Illegal request\n");
# endif	DEBUG
		send_info(read_desc,-5);
		return;
	}
/*
 *		follow action from lock request
 */
	switch(lockbuf.lr_act)
	{
	  case A_RTN:
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: A_RTN\n");
# endif	DEBUG

		/*
		** attempt to set lock.
		** error return if failure.
		*/
		for ( i = 0; i <= lockbuf.lr_type; i++) 
		{
			if (Lockset[i] == 0) 
			{
# ifdef	DEBUG
				if (ildebug)
					printf("ildr: lock %d not available\n", i);
# endif	DEBUG
				send_info(read_desc,-1);
				return;
			}
		}
		if (ilunique(&lockbuf) >= 0) 
		{
			send_info(read_desc,-1);
			return;
		}
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: lock assigned\n");
# endif	DEBUG
		ilenter(&lockbuf,read_desc);
		break;

	  case A_SLP:
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: A_SLP\n");
# endif	DEBUG
		if ( set_lock(read_desc,lockbuf) == -1 )
			return;
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: got lock\n");
# endif	DEBUG
		break;
	  case A_RLS1:
				/* remove 1 lock */
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: A_RLS1\n");
# endif	DEBUG
		if ((i = ilfind(&lockbuf,read_desc)) >= 0)
		{
			ilrm(i,read_desc);
		}
		else
		{
			send_info(read_desc,-2);
			return;
		}
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: released\n");
# endif	DEBUG
		break;

	  case A_RLSA:
				/* remove all locks for this process id*/
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: A_RLSA\n");
# endif	DEBUG
		ilrma(read_desc,FALSE);
		break;

	  case A_ABT:		/* remove all locks */
# ifdef	DEBUG
		if ( ildebug )
			printf("Driver: A_ABT\n");
# endif	DEBUG
		ilclose();
		break;

	  default :
# ifdef	DEBUG
		if ( ildebug )
			printf("DRIVER: garbage\n");
# endif	DEBUG
		send_info(read_desc,-3);
	}
	send_info(read_desc,0);
}

/*
 *	ilunique- check for match on key
 *	
 *	return index of Locktab if match found
 *	else return -1
 */

static int
ilunique(ll)
register struct Lockreq *ll;
{
	register int	k;
	register struct Lockform	*p;
	register struct Lockreq	*q;

	for (k = 0; k < NLOCKS; k++)
	{
		p = &Locktab[k];
		if ((p->l_mod != M_EMTY)
		&& (ilcomp(p->l_key,ll->lr_key) == 0)
		&& (p->l_type == ll->lr_type)
		&& ( (p->l_mod == M_EXCL) || (ll->lr_mod == M_EXCL)) ) {
# ifdef	DEBUG
			if (ildebug) {
				register int i;

				printf("ildr: lock ");
				for (i = 0; i < KEYSIZE; i++)
					printf("%c", ll->lr_key[i]);
				printf(" busy\n");
			}
# endif	DEBUG
			return(k);
		}
	}
	return(-1);
}

static int
ilfind(ll,key)
register struct Lockreq *ll;
int	key;
{
	register int	k;
	register struct Lockform	*p;
	register struct Lockreq	*q;

	for (k = 0; k < NLOCKS; k++)
	{
		p = &Locktab[k];
		if ((p->l_mod != M_EMTY)
		&& (ilcomp(p->l_key,ll->lr_key) == 0)
		&& (p->l_type == ll->lr_type)
		&& (p->l_pid == key))
			return(k);
	}
	return(-1);
}/* ilfind */

/*
 *	remove the lth Lock
 *		if the correct user is requesting the move.
 */

static int
ilrm(l,key,remove_all)
int l;
int	key;
int	remove_all;
{
	register struct Lockform *a;
	register	k;


	a = &Locktab[l];
	if (a->l_pid == key && a->l_mod != M_EMTY)
	{
		if ( !remove_all && a->l_type == T_DB )
			return;
		a->l_mod = M_EMTY;
		a->l_pid = 0;
		if (a->l_wflag == W_ON)
		{
			a->l_wflag = W_OFF;
			wakeup(&Locktab[l]);
		}
		for (k = 0; k <= a->l_type; k++)
		{
			Lockset[k]++;
			if (Lockset[k] == 1)
				wakeup(&Lockset[k]);
		}
	}
}/* ilrm */

/*
 *	ilrma releases all locks for a given process id(pd)
 *	-- called from sys1.c$exit() code.
 */
int
ilrma(key,remove_all)
int key;
int	remove_all;
{
	register int	i;

# ifdef	DEBUG
	printf("DRVIER: Removing all, key = %d\n",key);
# endif	DEBUG
	for ( i = 0; i < NLOCKS; i++ )
		ilrm(i,key,remove_all);
}

/*
 *	enter Lockbuf in locktable
 *	return position in Locktable
 *	error return of -1
 */

static int
ilenter(ll,key)
register struct Lockreq *ll;
int key;
{
	int	k,l;
	register char	*f,*t;
	register struct Lockform	*p;

	for (k = 0; k < NLOCKS; k++)
	{
		p = &Locktab[k];
		if (p->l_mod == M_EMTY)
		{
			p->l_pid = key;
			p->l_type = ll->lr_type;
			Locktab[k].l_mod = p->l_mod = ll->lr_mod;
			f = ll->lr_key;
			t = p->l_key;
			for (l = 0; l < KEYSIZE; l++)
				*t++ = *f++;
			for (l = 0; l <= ll->lr_type; l++)
				Lockset[l]--;
# ifdef	DEBUG
			if ( ildebug )
				printf("DRIVER: ilenter %d, mod %d, omod = %d\n",k,p->l_mod,Locktab[k].l_mod);
# endif	DEBUG
			return(k);
		}
	}
# ifdef	DEBUG
	if ( ildebug )
		printf("DRIVER: ilenter -1\n");
# endif	DEBUG
	return (-1);
}

/*
 *	ilcomp- string compare
 *	  	returns 0 if strings match
 *		returns -1 otherwise
 */

static int
ilcomp(s1,s2)
register char *s1,*s2;
{
	register int	k;

	for (k = 0; k < KEYSIZE; k++)
		if ( *s1++ != *s2++)
# ifdef	DEBUG
		{
			if ( ildebug )
				printf("DRIVER: ilcomp returning -1\n");
			return ( -1 );
		}
# else	DEBUG
			return (-1);
# endif	DEBUG
	return (0);
}

/*
 *	ilclose- releases all locks
 */

static int
ilclose()
{
	register int	k;
	register caddr_t c;
# ifdef	DEBUG
	printf("DRIVER: entered close\n");
# endif	DEBUG

	for (k = 0; k < NLOCKS; k++)
		wakeup( &Locktab[k] );
	for (k = 0; k < 4; k++)
		wakeup( &Lockset[k]);
	for (c = (caddr_t)&Locktab[0].l_pid; c < (caddr_t)&Locktab[NLOCKS];)
		*c++ = 0;
	Lockset[0] = NLOCKS;
	Lockset[1] = PLOCKS;
	Lockset[2] = RLOCKS;
	Lockset[3] = DLOCKS;
}/* ilclose */

/*
** set_lock
** attempt to set a lock. If we can't, block the process and
** return -1, if we can than set the lock and return 0.
*/

int
set_lock(read_desc,lockbuf)
register	int	read_desc;
struct	Lockreq	lockbuf;
{
	register	int	blockflag;
	register	int	i;

	/*
	** attempt to set lock.
	** sleep on blocking address if failure.
	*/

	do
	{
		do
		{
			blockflag = TRUE;
			for ( i = 0; i <= lockbuf.lr_type; i++)
				if (Lockset[i] == 0)
				{
# ifdef	DEBUG
					if (ildebug)
						printf("ildr: lock %d not available\n", i);
# endif	DEBUG
					wait_on(read_desc,&Lockset[i],lockbuf);
					return(-1);
				}
		} while (!blockflag);

		if (( i = ilunique(&lockbuf)) >= 0 )
		{
			blockflag = FALSE;
			Locktab[i].l_wflag = W_ON;
			wait_on(read_desc,&Locktab[i],lockbuf);
			return(-1);
		}
	} while (!blockflag);
	ilenter(&lockbuf,read_desc);
	return ( 0 );
}/* set_lock */

/*
** send_info
** Send the data down the socket. Don't do it if it would cause the driver
** to block.
*/

int
send_info(fd,data)
register	int	fd;
int	data;
{
	auto	int	wdes = ( 1<<fd );
	struct	timeval	time;

	errno = 0;
	time.tv_sec = 10;
	time.tv_usec = 0;

	if ( select(NOFILE,NULL,&wdes,NULL,&time) != 1 )
	{
		Read_fmt &= ~(1<<fd);
		ilrma(fd,TRUE);
		close(fd);
	}
	else
		if ( write(fd,&data,sizeof (int)) != sizeof (int) )
		{
			if ( errno == 0 )
				return;
			Read_fmt &= ~(1<<fd);
			ilrma(fd,TRUE);
			close(fd);
		}
}/* send_info */

struct	Wait {
	int	wait_fd;		/* file descriptor to send lock info to off of */
	int	wait_lock;		/* what lock we are waiting for */
	struct	Lockreq	wait_req;	/* the lock request */
	struct	Wait	*next;
};
struct	Wait	*Wait_queue = NULL;

/*
** wait_on
** Set up to wait for a free lock.
*/

int
wait_on(fd,lock,req)
register	int	fd, lock;
struct		Lockreq	req;
{
	register	struct	Wait	*ptr;
	char	*calloc();

	ptr = (struct Wait *)calloc(1,sizeof (struct Wait));
	ptr->wait_fd = fd;
	ptr->wait_lock = lock;
	ptr->wait_req = req;
	ptr->next = Wait_queue;
	Wait_queue = ptr;
}/* wait_on */

/*
** wakeup
** See if there is anythng waiting on the newly freed lock. If there is,
** tell it it can have the lock now.
*/

int
wakeup(lock)
register	int	lock;
{
	register	struct	Wait	*ptr,*back;

	for ( back = NULL, ptr = Wait_queue ; ptr != NULL ; back = ptr, ptr = ptr->next )
	{
		if ( ptr->wait_lock == lock )
		{
			if ( set_lock(ptr->wait_fd,ptr->wait_req) == 0 )
			{
				send_info(ptr->wait_fd,0);
				if ( back != NULL )
					back->next = ptr->next;
				else
					Wait_queue = Wait_queue->next;
				cfree(ptr);
				return;
			}
		}
	}
}/* wakeup */

/*
** abnormal
** a signal has come down and hit us. We restart the entire
** program, and hope it goes away
*/

void
abnormal(sig)
int	sig;
{
	extern	int	errno;

# ifdef	DEBUG
	printf("DRIVER: error Signal No. = %d, restarting\n",sig);
# endif

/*	execl("/etc/lock_driver","lock_driver","restart",0);   */
	execl("/etc/ingreslock","lock_driver","restart",0);
	execl(Prog_name,Prog_name,"restart",0);
	execlp("ingreslock","lock_driver","restart",0);
	exit(4);
}/* abnormal */

/*
** close_up
** try and find a closed up file descriptor.
*/

int
close_up(fmt)
long	fmt;
{
	long	rdesc;
	register	int	i;
	struct	timeval	time;

	errno = 0;
	time.tv_sec  = 0;
	time.tv_usec = 0;

	for ( i = 0 ; i < NOFILE ; i++ )
	{
		if ( (1<<i) & fmt )
		{
			rdesc = (1<<i);
			if ( select(NOFILE,&rdesc,0,0,&time) == -1 )
			{
				/*
				** the server socket has closed down.
				** BOY ARE WE IN TROUBLE
				*/
				if ( i == From_server )
				{
					sleep(1);
# ifdef	DEBUG
					printf("Restarting socket\n");
# endif	DEBUG
					init_socket();
				}
				if ( errno == EBADF )
				{
					shutdown(i,2);
					close(i);
					Read_fmt &= ~(1<<i);
					ilrma(i,TRUE);
				}
			}
		}
	}
}/* close_up */
