/* Implement entry point to select system call.
 *
 * The entry points into this file are
 *   do_select:	       perform the SELECT system call
 *   select_callback:  notify select system of possible fd operation 
 *   select_notified:  low-level entry for device notifying select
 *   select_unsuspend_by_endpt: cancel a blocking select on exiting driver
 */

#define DEBUG_SELECT 0

#include "fs.h"
#include "select.h"
#include "file.h"
#include "vnode.h"

#include <sys/time.h>
#include <sys/select.h>
#include <minix/com.h>
#include <minix/u64.h>
#include <string.h>

/* max. number of simultaneously pending select() calls */
#define MAXSELECTS 25
#define FROM_PROC 0
#define TO_PROC   1

PRIVATE struct selectentry {
  struct fproc *requestor;	/* slot is free iff this is NULL */
  int req_endpt;
  fd_set readfds, writefds, errorfds;
  fd_set ready_readfds, ready_writefds, ready_errorfds;
  fd_set *vir_readfds, *vir_writefds, *vir_errorfds;
  struct filp *filps[OPEN_MAX];
  int type[OPEN_MAX];
  int deferred;		/* awaiting initial reply from driver */
  int deferred_fd;	/* fd awaiting initial reply from driver */
  int nfds, nreadyfds;
  char block;
  clock_t expiry;
  timer_t timer;	/* if expiry > 0 */
} selecttab[MAXSELECTS];

FORWARD _PROTOTYPE(int copy_fdsets, (struct selectentry *se, int nfds,
				      int direction)			);
FORWARD _PROTOTYPE(void filp_status, (struct filp *fp, int status)	);
FORWARD _PROTOTYPE(void restart_proc, (int slot)			);
FORWARD _PROTOTYPE(void ops2tab, (int ops, int fd, struct selectentry *e));
FORWARD _PROTOTYPE(int select_reevaluate, (struct filp *fp)		);
FORWARD _PROTOTYPE(int select_request_file, (struct filp *f, int *ops,
					     int block)			);
FORWARD _PROTOTYPE(int select_match_file, (struct filp *f)		);
FORWARD _PROTOTYPE(int select_request_general, (struct filp *f, int *ops,
					        int block)		);
FORWARD _PROTOTYPE(int select_request_asynch, (struct filp *f, int *ops,
					       int block)		);
FORWARD _PROTOTYPE(int select_major_match, (int match_major,
					    struct filp *file)		);
FORWARD _PROTOTYPE(void select_cancel_all, (struct selectentry *e)	);
FORWARD _PROTOTYPE(void select_wakeup, (struct selectentry *e, int r)	);
FORWARD _PROTOTYPE(void select_return, (struct selectentry *, int)	);
FORWARD _PROTOTYPE(void sel_restart_dev, (void)				);
FORWARD _PROTOTYPE(int tab2ops, (int fd, struct selectentry *e)		);
FORWARD _PROTOTYPE(void wipe_select, (struct selectentry *s)		);

PRIVATE struct fdtype {
	int (*select_request)(struct filp *, int *ops, int block);	
	int (*select_match)(struct filp *);
	int select_major;
} fdtypes[] = {
	{ select_request_file, select_match_file, 0 },
	{ select_request_general, NULL, TTY_MAJOR },
	{ select_request_general, NULL, INET_MAJOR },
	{ select_request_general, NULL, UDS_MAJOR },
	{ select_request_pipe, select_match_pipe, 0 },
	{ select_request_asynch, NULL, LOG_MAJOR },
};
#define SEL_FDS		(sizeof(fdtypes) / sizeof(fdtypes[0]))

/*===========================================================================*
 *				do_select				      *
 *===========================================================================*/
PUBLIC int do_select(void)
{
/* Implement the select(nfds, readfds, writefds, errorfds, timeout) system
 * call. First we copy the arguments and verify their sanity. Then we check
 * whether there are file descriptors that satisfy the select call right of the
 * bat. If so, or if there are no ready file descriptors but the process 
 * requested to return immediately, we return the result. Otherwise we set a
 * timeout and wait for either the file descriptors to become ready or the 
 * timer to go off. If no timeout value was provided, we wait indefinitely. */

  int r, nfds, do_timeout = 0, fd, s;
  struct timeval timeout;
  struct selectentry *se;

  nfds = m_in.SEL_NFDS;

  /* Sane amount of file descriptors? */
  if (nfds < 0 || nfds > OPEN_MAX) return(EINVAL);

  /* Find a slot to store this select request */
  for (s = 0; s < MAXSELECTS; s++)
	if (selecttab[s].requestor == NULL) /* Unused slot */
		break;
  if (s >= MAXSELECTS) return(ENOSPC);

  se = &selecttab[s];
  wipe_select(se);	/* Clear results of previous usage.*/
  se->req_endpt = who_e;
  se->vir_readfds = (fd_set *) m_in.SEL_READFDS;
  se->vir_writefds = (fd_set *) m_in.SEL_WRITEFDS;
  se->vir_errorfds = (fd_set *) m_in.SEL_ERRORFDS;

  /* Copy fdsets from the process */
  if ((r = copy_fdsets(se, nfds, FROM_PROC)) != OK) return(r); 

  /* Did the process set a timeout value? If so, retrieve it. */
  if (m_in.SEL_TIMEOUT != NULL) {
  	do_timeout = 1;
	r = sys_vircopy(who_e, D, (vir_bytes) m_in.SEL_TIMEOUT,	SELF, D,
			(vir_bytes) &timeout, sizeof(timeout));
	if (r != OK) return(r);
  }

  /* No nonsense in the timeval. */
  if(do_timeout && (timeout.tv_sec < 0 || timeout.tv_usec < 0)) return(EINVAL);

  /* If there is no timeout, we block forever. Otherwise, we block up to the
   * specified time interval.  
   */
  if(!do_timeout)	/* No timeout value set */
	se->block = 1;
  else if (do_timeout && (timeout.tv_sec > 0 || timeout.tv_usec > 0))
  	se->block = 1;
  else			/* timeout set as (0,0) - this effects a poll */
	se->block = 0;	
  se->expiry = 0;	/* no timer set (yet) */

  /* Check all file descriptors in the set whether one is 'ready' now. */
  for (fd = 0; fd < nfds; fd++) {
	int ops, t, type = -1, r;
	struct filp *filp;
	
	if (!(ops = tab2ops(fd, se)))
		continue; /* No operations set; nothing to do for this fd */

	/* Get filp belonging to this fd */
	filp = se->filps[fd] = get_filp(fd);
	if (filp == NULL) {
		if (err_code == EBADF) {
			select_cancel_all(se);
			return(EBADF);
		}

		/* File descriptor is 'ready' to return EIO */
		printf("VFS do_select: EIO after driver failure\n");
		ops2tab(SEL_RD|SEL_WR|SEL_ERR, fd, se);
		continue;
	}

	/* Figure out what type of file we're dealing with */
	for(t = 0; t < SEL_FDS; t++) {
		if (fdtypes[t].select_match) {
			if (fdtypes[t].select_match(filp)) {
				type = t;
			}
	 	} else if (select_major_match(fdtypes[t].select_major, filp)) {
			type = t;
		}
	}

	if (type == -1)	return(EBADF);
	se->type[fd] = type;

	/* Test filp for select operations if not already done so. e.g., files
	 * sharing a filp and both doing a select on that filp. */
	if ((se->filps[fd]->filp_select_ops & ops) != ops) {
		int wantops;
		
		wantops = (se->filps[fd]->filp_select_ops |= ops);
		r = fdtypes[type].select_request(filp, &wantops, se->block);
		if (r != SEL_OK) {
			if (r == SEL_DEFERRED) {
				se->deferred = TRUE;
				se->deferred_fd = 0;
				continue;
			}
			
			/* Error or bogus return code; cancel select. */
			select_cancel_all(se);
			return(EINVAL);
		}
		
		/* The select request above might have turned on/off some
		 * operations because they were 'ready' or not meaningful.
		 * Either way, we might have a result and we need to store them
		 * in the select table entry. */
		if (wantops & ops) ops2tab(wantops, fd, se);
	} 

	se->nfds = fd+1;
	se->filps[fd]->filp_selectors++;
  }

  if (se->nreadyfds > 0 || (!se->block && !se->deferred)) {
	/* fd's were found that were ready to go right away, and/or
	 * we were instructed not to block at all. Must return
	 * immediately.
	 */
	r = copy_fdsets(se, se->nfds, TO_PROC);
	select_cancel_all(se);
	se->requestor = NULL;

	if (r != OK) return(r);
	else return(se->nreadyfds);
  }
 
  /* Convert timeval to ticks and set the timer. If it fails, undo
   * all, return error.
   */
  if (do_timeout) {
	int ticks;
	/* Open Group:
	 * "If the requested timeout interval requires a finer
	 * granularity than the implementation supports, the
	 * actual timeout interval shall be rounded up to the next
	 * supported value."
	 */
#define USECPERSEC 1000000
	while(timeout.tv_usec >= USECPERSEC) {
		/* this is to avoid overflow with *system_hz below */
		timeout.tv_usec -= USECPERSEC;
		timeout.tv_sec++;
	}
	ticks = timeout.tv_sec * system_hz +
		(timeout.tv_usec * system_hz + USECPERSEC-1) / USECPERSEC;
	se->expiry = ticks;
	set_timer(&se->timer, ticks, select_timeout_check, s);
  }

  /* if we're blocking, the table entry is now valid. */
  se->requestor = fp;

  /* process now blocked */
  suspend(FP_BLOCKED_ON_SELECT);
  return(SUSPEND);
}


/*===========================================================================*
 *				select_request_file			     *
 *===========================================================================*/
PRIVATE int select_request_file(struct filp *f, int *ops, int block)
{
  /* output *ops is input *ops */
  return(SEL_OK);
}


/*===========================================================================*
 *				select_match_file			     *
 *===========================================================================*/
PRIVATE int select_match_file(struct filp *file)
{
  return(file && file->filp_vno && (file->filp_vno->v_mode & I_REGULAR));
}


/*===========================================================================*
 *				select_request_general			     *
 *===========================================================================*/
PRIVATE int select_request_general(struct filp *f, int *ops, int block)
{
  int rops = *ops;
  if (block) rops |= SEL_NOTIFY;
  *ops = dev_io(VFS_DEV_SELECT, f->filp_vno->v_sdev, rops, NULL,
		cvu64(0), 0, 0, FALSE);
  if (*ops < 0)
	return(SEL_ERR);

  return(SEL_OK);
}


/*===========================================================================*
 *				select_request_asynch			     *
 *===========================================================================*/
PRIVATE int select_request_asynch(struct filp *f, int *ops, int block)
{
  int r, rops;
  struct dmap *dp;

  rops = *ops;
  f->filp_select_flags |= FSF_UPDATE;
  if (block) {
	rops |= SEL_NOTIFY;
	f->filp_select_flags |= FSF_BLOCK;
  }

  if (f->filp_select_flags & FSF_BUSY)
	return(SEL_DEFERRED);

  dp = &dmap[((f->filp_vno->v_sdev) >> MAJOR) & BYTE];
  if (dp->dmap_sel_filp)
	return(SEL_DEFERRED);

  f->filp_select_flags &= ~FSF_UPDATE;
  r = dev_io(VFS_DEV_SELECT, f->filp_vno->v_sdev, rops, NULL,
	     cvu64(0), 0, 0, FALSE);
  if (r < 0 && r != SUSPEND)
	return(SEL_ERR);

  if (r != SUSPEND) 
	panic("select_request_asynch: expected SUSPEND got: %d", r);

  f->filp_count++;
  dp->dmap_sel_filp = f;
  f->filp_select_flags |= FSF_BUSY;

  return(SEL_DEFERRED);
}


/*===========================================================================*
 *				select_major_match			     *
 *===========================================================================*/
PRIVATE int select_major_match(int match_major, struct filp *file)
{
  int major;
  if (!(file && file->filp_vno &&
      (file->filp_vno->v_mode & I_TYPE) == I_CHAR_SPECIAL))
	return(0);
  major = (file->filp_vno->v_sdev >> MAJOR) & BYTE;
  if (major == match_major) return 1;
  return 0;
}


/*===========================================================================*
 *				tab2ops					     *
 *===========================================================================*/
PRIVATE int tab2ops(int fd, struct selectentry *e)
{
  int ops = 0;
  if (FD_ISSET(fd, &e->readfds))  ops |= SEL_RD;
  if (FD_ISSET(fd, &e->writefds)) ops |= SEL_WR;
  if (FD_ISSET(fd, &e->errorfds)) ops |= SEL_ERR;
 
  return(ops); 
}


/*===========================================================================*
 *				ops2tab					     *
 *===========================================================================*/
PRIVATE void ops2tab(int ops, int fd, struct selectentry *e)
{
  if ((ops & SEL_RD) && e->vir_readfds && FD_ISSET(fd, &e->readfds) &&
      !FD_ISSET(fd, &e->ready_readfds)) {
	FD_SET(fd, &e->ready_readfds);
	e->nreadyfds++;
  }

  if ((ops & SEL_WR) && e->vir_writefds && FD_ISSET(fd, &e->writefds) &&
      !FD_ISSET(fd, &e->ready_writefds)) {
	FD_SET(fd, &e->ready_writefds);
	e->nreadyfds++;
  }

  if ((ops & SEL_ERR) && e->vir_errorfds && FD_ISSET(fd, &e->errorfds) &&
      !FD_ISSET(fd, &e->ready_errorfds)) {
	FD_SET(fd, &e->ready_errorfds);
	e->nreadyfds++;
  }
}


/*===========================================================================*
 *				copy_fdsets				     *
 *===========================================================================*/
PRIVATE int copy_fdsets(struct selectentry *se, int nfds, int direction)
{
  int fd_setsize, r;
  endpoint_t src_e, dst_e;
  fd_set *src_fds, *dst_fds;

  if(nfds < 0 || nfds > OPEN_MAX)
	panic("select copy_fdsets: nfds wrong: %d", nfds);

  /* Only copy back as many bits as the user expects. */
  fd_setsize = _FDSETWORDS(nfds) * _FDSETBITSPERWORD/8;

  /* Set source and destination endpoints */
  src_e = (direction == FROM_PROC) ? se->req_endpt : SELF;
  dst_e = (direction == FROM_PROC) ? SELF : se->req_endpt;

  /* read set */
  src_fds = (direction == FROM_PROC) ? se->vir_readfds : &se->ready_readfds;
  dst_fds = (direction == FROM_PROC) ? &se->readfds : se->vir_readfds;
  if (se->vir_readfds) {
	r = sys_vircopy(src_e, D, (vir_bytes) src_fds, dst_e, D,
		    (vir_bytes) dst_fds, fd_setsize);
	if (r != OK) return(r);
  }

  /* write set */
  src_fds = (direction == FROM_PROC) ? se->vir_writefds : &se->ready_writefds;
  dst_fds = (direction == FROM_PROC) ? &se->writefds : se->vir_writefds;
  if (se->vir_writefds) {
	r = sys_vircopy(src_e, D, (vir_bytes) src_fds, dst_e, D,
		    (vir_bytes) dst_fds, fd_setsize);
	if (r != OK) return(r);
  }

  /* error set */
  src_fds = (direction == FROM_PROC) ? se->vir_errorfds : &se->ready_errorfds;
  dst_fds = (direction == FROM_PROC) ? &se->errorfds : se->vir_errorfds;
  if (se->vir_errorfds) {
	r = sys_vircopy(src_e, D, (vir_bytes) src_fds, dst_e, D,
		    (vir_bytes) dst_fds, fd_setsize);
	if (r != OK) return(r);
  }

  return(OK);
}


/*===========================================================================*
 *				select_cancel_all			     *
 *===========================================================================*/
PRIVATE void select_cancel_all(struct selectentry *e)
{
	int fd;

	for(fd = 0; fd < e->nfds; fd++) {
		struct filp *fp;
		fp = e->filps[fd];
		if (!fp) {
#if DEBUG_SELECT
			printf("[ fd %d/%d NULL ] ", fd, e->nfds);
#endif
			continue;
		}
		if (fp->filp_selectors < 1) {
#if DEBUG_SELECT
			printf("select: %d selectors?!\n", fp->filp_selectors);
#endif
			continue;
		}
		fp->filp_selectors--;
		e->filps[fd] = NULL;
		select_reevaluate(fp);
	}

	if (e->expiry > 0) {
#if DEBUG_SELECT
		printf("cancelling timer %d\n", e - selecttab);
#endif
		cancel_timer(&e->timer); 
		e->expiry = 0;
	}

	return;
}


/*===========================================================================*
 *				select_wakeup				     *
 *===========================================================================*/
PRIVATE void select_wakeup(struct selectentry *e, int r)
{
  revive(e->req_endpt, r);
}


/*===========================================================================*
 *				select_reevaluate			     *
 *===========================================================================*/
PRIVATE int select_reevaluate(struct filp *fp)
{
	int s, remain_ops = 0, fd;

	if (!fp) {
		printf("fs: select: reevalute NULL fp\n");
		return 0;
	}

	for(s = 0; s < MAXSELECTS; s++) {
		if (selecttab[s].requestor != NULL) continue;
		
		for(fd = 0; fd < selecttab[s].nfds; fd++)
			if (fp == selecttab[s].filps[fd]) {
				remain_ops |= tab2ops(fd, &selecttab[s]);
			}
	}

	/* If there are any select()s open that want any operations on
	 * this fd that haven't been satisfied by this callback, then we're
	 * still in the market for it.
	 */
	fp->filp_select_ops = remain_ops;
#if DEBUG_SELECT
	printf("remaining operations on fp are %d\n", fp->filp_select_ops);
#endif

	return remain_ops;
}


/*===========================================================================*
 *				select_return				     *
 *===========================================================================*/
PRIVATE void select_return(struct selectentry *se, int r)
{
  select_cancel_all(se);
  copy_fdsets(se, se->nfds, TO_PROC); /* FIXME, return error status */
  select_wakeup(se, r ? r : se->nreadyfds);
  se->requestor = NULL;
}


/*===========================================================================*
 *				select_callback			             *
 *===========================================================================*/
PUBLIC int select_callback(struct filp *fp, int ops)
{
	int s, fd;

	/* We are being notified that file pointer fp is available for
	 * operations 'ops'. We must re-register the select for
	 * operations that we are still interested in, if any.
	 */

	for(s = 0; s < MAXSELECTS; s++) {
		int wakehim = 0;
		if (selecttab[s].requestor == NULL) continue;
		
		for(fd = 0; fd < selecttab[s].nfds; fd++) {
			if (!selecttab[s].filps[fd])
				continue;
			if (selecttab[s].filps[fd] == fp) {
				int this_want_ops;
				this_want_ops = tab2ops(fd, &selecttab[s]);
				if (this_want_ops & ops) {
					/* this select() has been satisfied. */
					ops2tab(ops, fd, &selecttab[s]);
					wakehim = 1;
				}
			}
		}
		if (wakehim)
			select_return(&selecttab[s], 0);
	}

	return 0;
}


/*===========================================================================*
 *				select_notified			             *
 *===========================================================================*/
PUBLIC int select_notified(int major, int minor, int selected_ops)
{
	int s, f, t;

#if DEBUG_SELECT
	printf("select callback: %d, %d: %d\n", major, minor, selected_ops);
#endif

	for(t = 0; t < SEL_FDS; t++)
		if (!fdtypes[t].select_match && fdtypes[t].select_major == major)
		    	break;

	if (t >= SEL_FDS) {
#if DEBUG_SELECT
		printf("select callback: no fdtype found for device %d\n", major);
#endif
		return OK;
	}

	/* We have a select callback from major device no.
	 * d, which corresponds to our select type t.
	 */

	for(s = 0; s < MAXSELECTS; s++) {
		int s_minor, ops;
		if (selecttab[s].requestor == NULL) continue;
		for(f = 0; f < selecttab[s].nfds; f++) {
			if (!selecttab[s].filps[f] ||
			   !select_major_match(major, selecttab[s].filps[f]))
			   	continue;
			ops = tab2ops(f, &selecttab[s]);
			s_minor =
			(selecttab[s].filps[f]->filp_vno->v_sdev >> MINOR)
				& BYTE;
			if ((s_minor == minor) &&
				(selected_ops & ops)) {
				select_callback(selecttab[s].filps[f], (selected_ops & ops));
			}
		}
	}

	return OK;
}


/*===========================================================================*
 *				init_select  				     *
 *===========================================================================*/
PUBLIC void init_select(void)
{
	int s;

	for(s = 0; s < MAXSELECTS; s++)
		init_timer(&selecttab[s].timer);
}


/*===========================================================================*
 *				select_forget			             *
 *===========================================================================*/
PUBLIC void select_forget(int proc_e)
{
	/* something has happened (e.g. signal delivered that interrupts
	 * select()). totally forget about the select().
	 */
	int s;

	for(s = 0; s < MAXSELECTS; s++) {
		if (selecttab[s].requestor != NULL &&
		    selecttab[s].req_endpt == proc_e) {
			break;
		}
	}

	if (s >= MAXSELECTS) {
#if DEBUG_SELECT
		printf("select: cancelled select() not found");
#endif
		return;
	}

	select_cancel_all(&selecttab[s]);
	selecttab[s].requestor = NULL;

	return;
}


/*===========================================================================*
 *				select_timeout_check	  	     	     *
 *===========================================================================*/
PUBLIC void select_timeout_check(timer_t *timer)
{
  int s;
  struct selectentry *se;

  s = tmr_arg(timer)->ta_int;
  if (s < 0 || s >= MAXSELECTS) {
#if DEBUG_SELECT
	printf("select: bogus slot arg to watchdog %d\n", s);
#endif
	return;
  }
  se = &selecttab[s]; /* Point to select table entry */

  if (se->requestor == NULL) {
#if DEBUG_SELECT
	printf("select: no requestor in watchdog\n");
#endif
	return;
  }

  if (se->expiry <= 0) {
#if DEBUG_SELECT
	printf("select: strange expiry value in watchdog\n", s);
#endif
	return;
  }

  se->expiry = 0;
  select_return(se, 0);

}


/*===========================================================================*
 *				select_unsuspend_by_endpt  	     	     *
 *===========================================================================*/
PUBLIC void select_unsuspend_by_endpt(endpoint_t proc_e)
{
  int fd, s, maj;

  for(s = 0; s < MAXSELECTS; s++) {
	if (selecttab[s].requestor == NULL) continue;
	  
	for(fd = 0; fd < selecttab[s].nfds; fd++) {
		if (selecttab[s].filps[fd] == NULL ||
		    selecttab[s].filps[fd]->filp_vno == NULL) {
			continue;
		}
		
		maj = (selecttab[s].filps[fd]->filp_vno->v_sdev >> MAJOR)&BYTE;
		if (dmap_driver_match(proc_e, maj))
			select_return(&selecttab[s], EAGAIN);
	    
	}
  }
}


/*===========================================================================*
 *				select_reply1				     *
 *===========================================================================*/
PUBLIC void select_reply1()
{
	int i, minor, status;
	endpoint_t driver_e;
	dev_t dev;
	struct filp *fp;
	struct dmap *dp;
	struct vnode *vp;

	driver_e= m_in.m_source;
	minor= m_in.DEV_MINOR;
	status= m_in.DEV_SEL_OPS;

	/* Locate dmap entry */
	for (i= 0, dp= dmap; i<NR_DEVICES; i++, dp++)
	{
		if (dp->dmap_driver == driver_e)
			break;
	}
	if (i >= NR_DEVICES)
	{
		printf("select_reply1: proc %d is not a recoqnized driver\n",
			driver_e);
		return;
	}
	dev= (i << MAJOR) | (minor & BYTE);

	fp= dp->dmap_sel_filp;
	if (!fp)
	{
		printf("select_reply1: strange, no dmap_sel_filp\n");
		return;
	}

	if (!(fp->filp_select_flags & FSF_BUSY))
		panic("select_reply1: strange; not FSF_BUSY");

	vp= fp->filp_vno;
	if (!vp)
		panic("select_reply1: FSF_BUSY but no vp");

	if ((vp->v_mode & I_TYPE) != I_CHAR_SPECIAL) {
		panic("select_reply1: FSF_BUSY but not char special");
	}

	if (vp->v_sdev != dev)
	{
		printf("select_reply1: strange, reply from wrong dev\n");
		return;
	}

	dp->dmap_sel_filp= NULL;
	fp->filp_select_flags &= ~FSF_BUSY;
	if (!(fp->filp_select_flags & (FSF_UPDATE|FSF_BLOCK)))
		fp->filp_select_ops= 0;
	if (status != 0)
	{
		if (status > 0)
		{
			/* Clear the replied bits from the request mask unless
			 * FSF_UPDATE is set.
			 */
			if (!(fp->filp_select_flags & FSF_UPDATE))
				fp->filp_select_ops &= ~status;
		}
		filp_status(fp, status);
	}
	if (fp->filp_count > 1)
		fp->filp_count--;
	else
	{
		if (fp->filp_count != 1) {
			panic("select_reply1: bad filp_count: %d", fp->filp_count);
		}
		close_filp(fp);
	}
	sel_restart_dev();
}


/*===========================================================================*
 *				select_reply2				     *
 *===========================================================================*/
PUBLIC void select_reply2()
{
	int i, s, minor, status;
	endpoint_t driver_e;
	dev_t dev;
	struct filp *fp;
	struct dmap *dp;
	struct vnode *vp;

	driver_e= m_in.m_source;
	minor= m_in.DEV_MINOR;
	status= m_in.DEV_SEL_OPS;

	/* Locate dmap entry */
	for (i= 0, dp= dmap; i<NR_DEVICES; i++, dp++)
	{
		if (dp->dmap_driver == driver_e)
			break;
	}
	if (i >= NR_DEVICES)
	{
		printf("select_reply2: proc %d is not a recognized driver\n",
			driver_e);
		return;
	}
	dev= (i << MAJOR) | (minor & BYTE);

	/* Find filedescriptors for this device */
	for (s= 0; s<MAXSELECTS; s++)
	{
		if (selecttab[s].requestor == NULL)
			continue;	/* empty slot */
					 
		for (i= 0; i<OPEN_MAX; i++)
		{
			fp= selecttab[s].filps[i];
			if (!fp)
				continue;
			vp= fp->filp_vno;
			if (!vp)
				continue;
			if ((vp->v_mode & I_TYPE) != I_CHAR_SPECIAL)
				continue;

			if (vp->v_sdev != dev)
				continue;

			if (status < 0)
			{
				printf("select_reply2: should handle error\n");
			}
			else 
			{
				/* Clear the replied bits from the request
				 * mask unless FSF_UPDATE is set.
				 */
				if (!(fp->filp_select_flags & FSF_UPDATE))
					fp->filp_select_ops &= ~status;
				ops2tab(status, i, &selecttab[s]);
			}
		}
		if (selecttab[s].nreadyfds > 0)
			restart_proc(s);
	}
}


PRIVATE void sel_restart_dev()
{
	int i, s;
	struct filp *fp;
	struct vnode *vp;
	struct dmap *dp;

	/* Locate filps that can be restarted */
	for (s= 0; s<MAXSELECTS; s++)
	{
		if (selecttab[s].requestor == NULL)
			continue; /* empty slot */
		if (!selecttab[s].deferred)
			continue;	/* process is not waiting for an
					 * initial reply.
					 */
		for (i= 0; i<OPEN_MAX; i++)
		{
			fp= selecttab[s].filps[i];
			if (!fp)
				continue;
			if (fp->filp_select_flags & FSF_BUSY)
				continue;
			if (!(fp->filp_select_flags & FSF_UPDATE))
				continue;

			vp= fp->filp_vno;
			if (!vp) {
				panic("sel_restart_dev: FSF_UPDATE but no vp");
			}
			if ((vp->v_mode & I_TYPE) != I_CHAR_SPECIAL) {
				panic("sel_restart_dev: FSF_UPDATE but not char special");
			}

			dp = &dmap[((vp->v_sdev) >> MAJOR) & BYTE];
			if (dp->dmap_sel_filp)
				continue;

			printf(
			"sel_restart_dev: should consider fd %d in slot %d\n",
				i, s);
		}
	}
}


PRIVATE void filp_status(fp, status)
struct filp *fp;
int status;
{
	int i, s;

	/* Locate processes that need to know about this result */
	for (s= 0; s<MAXSELECTS; s++)
	{
		if (selecttab[s].requestor == NULL) continue; /* empty slot */

		for (i= 0; i<OPEN_MAX; i++)
		{
			if (selecttab[s].filps[i] != fp)
				continue;

			if (status < 0)
			{
				printf("filp_status: should handle error\n");
			}
			else 
				ops2tab(status, i, &selecttab[s]);

			restart_proc(s);
		}
	}
}


PRIVATE void restart_proc(slot)
int slot;
{
	int fd;
	struct selectentry *se;
	struct filp *fp;

	se= &selecttab[slot];
	if (se->deferred)
	{
		for (fd= se->deferred_fd; fd < OPEN_MAX; fd++)
		{
			fp= se->filps[fd];
			if (!fp)
				continue;
			if (fp->filp_select_flags & (FSF_UPDATE|FSF_BUSY))
				break;
		}
		if (fd < OPEN_MAX)
		{
			se->deferred_fd= fd;
			return;
		}
		se->deferred= FALSE;
	}
	if (se->nreadyfds > 0 || !se->block) {
		copy_fdsets(se, se->nfds, TO_PROC); /* FIXME, return error */
		select_wakeup(se, se->nreadyfds);
		se->requestor = NULL;
	}
}

/*===========================================================================*
 *				wipe_select				     *
 *===========================================================================*/
PRIVATE void wipe_select(struct selectentry *se)
{
  se->deferred = FALSE;
  se->nfds = 0;
  se->nreadyfds = 0;
/*  memset(se->filps, 0, OPEN_MAX * sizeof(struct filp *)); */
  memset(se->filps, 0, sizeof(se->filps)); 

  FD_ZERO(&se->readfds);
  FD_ZERO(&se->writefds);
  FD_ZERO(&se->errorfds);
  FD_ZERO(&se->ready_readfds);
  FD_ZERO(&se->ready_writefds);
  FD_ZERO(&se->ready_errorfds);
}

