/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: kern_descrip.c 1.36 90/03/26 $"

/*
 * kern_descrip.c
 *	Descriptor management.
 *
 * TODO:
 *	eliminate u.u_error side effects
 */

/* $Log:	kern_descrip.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/signal.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/buf.h"
#include "sys/conf.h"
#include "sys/file.h"
#include "sys/stat.h"
#include "sys/fcntl.h"
#include "sys/flock.h"
#include "sys/vm.h"
#include "sys/var.h"
#include "sys/vmmeter.h"

#include "sys/ioctl.h"

#include "sys/pte.h"
#include "sys/gate.h"
#include "sys/intctl.h"
#include "sys/cmn_err.h"

/*
 * getdtablesize()
 *	Return max number of per-process file-descriptors.
 *
 * Also return high-water mark (highest in-use file-descriptor index).
 */

getdtablesize()
{
	u.u_r.r_val1 = OFILE_NOFILE(u.u_ofile_tab);
	u.u_r.r_val2 = OFILE_LASTFILE(u.u_ofile_tab);
}

/*
 * setdtablesize()
 *	System call to alter size of the calling process's open file
 *	descriptor table.
 */

setdtablesize()
{
	struct a {
		int	nfds;			/* # fd's desired */
	} *uap = (struct a *) u.u_ap;

	u.u_r.r_val1 = csetdtablesize(uap->nfds);
}

/*
 * kernel-callable version of setdtablesize().
 */
csetdtablesize(nfds)
int nfds;
{
	u.u_error = ofile_capacity(u.u_ofile_tab, nfds);
	return(OFILE_NOFILE(u.u_ofile_tab));
}

/*
 * dup()
 *	System call.  Duplicate a given file-descriptor.
 */

dup()
{
	struct a {
		int	i;
	} *uap = (struct a *) u.u_ap;
	struct	file	*fp;
	int	j;

	GETF(fp, uap->i);
	if ((j = ofile_alloc(u.u_ofile_tab, 0)) < 0) {
		u.u_error = EMFILE;
		return;
	}
	u.u_error = ofile_dup(u.u_ofile_tab, j, fp);
	u.u_r.r_val1 = j;
}

/*
 * dup2()
 *	System call.  Duplicate a given file-descriptor to given file #.
 *	Make this a system call, rather than a library as in standard
 *	system V, due to POSIX requirements for atomicity across
 *	signal handler execution.  i.e., a signal handler which
 *	opened a new file at "just the wrong time" during execution
 *	of standrd system V's dup2 library would break the library
 *	implementation.
 */

dup2()
{
	struct a {
		int	i, j;
	} *uap = (struct a *) u.u_ap;
	struct	file	*fp;

	GETF(fp, uap->i);
	u.u_r.r_val1 = uap->j;
	u.u_error = ofile_dup(u.u_ofile_tab, uap->j, fp);
}

/*
 * fcntl()
 *	The file control system call.
 */

fcntl()
{
	register struct a {
		int	fdes;
		int	cmd;
		int	arg;
	} *uap = (struct a *) u.u_ap;
	register struct file *fp;
	register i;
	spl_t	s_ipl;

	GETF(fp, uap->fdes);

	switch(uap->cmd) {
	case F_DUPFD:
		/*
		 * Test of arg range is heuristic if shared ofile table
		 * (vs concurrent setdtablesize()).  Test is only necessary to
		 * preserve EINVAL return code; ofile_alloc() is
		 * self-protecting, returns error if arg out of range.
		 */
		if (uap->arg < 0 || uap->arg >= OFILE_NOFILE(u.u_ofile_tab)) {
			u.u_error = EINVAL;
			return;
		}
		if ((i = ofile_alloc(u.u_ofile_tab, uap->arg)) < 0) {
			u.u_error = EMFILE;
			return;
		}
		u.u_error = ofile_dup(u.u_ofile_tab, i, fp);
		u.u_r.r_val1 = i;
		break;

	case F_GETFD:
		u.u_error = ofile_get_flags(u.u_ofile_tab, uap->fdes, &u.u_r.r_val1);
		break;

	case F_SETFD:
		u.u_error = ofile_set_flags(u.u_ofile_tab, uap->fdes, uap->arg);
		break;

	case F_GETFL:
		u.u_r.r_val1 = fp->f_flag + FOPEN;
		break;

	case F_SETFL:

		/*
		 * This operation is kept atomic (FDLOCK) to avoid race with
		 * concurrent F_SETFL.
		 */
		s_ipl = FDLOCK(fp);
		uap->arg &= FMASK;
		fp->f_flag &= (FREAD|FWRITE);
		fp->f_flag |= (uap->arg-FOPEN) & ~(FREAD|FWRITE);
		FDUNLOCK(fp, s_ipl);
		break;

	default:
		/*
		 * No races accessing fp information here?
		 */
		u.u_error = VOP_FCNTL(fp->f_vnode,
				uap->cmd, uap->arg, fp->f_flag, fp->f_offset);
	}
}

/*
 * close()
 *	`close' system call.  Close a given file-descriptor.
 */

close()
{
	register struct a {
		int	i;
	} *uap = (struct a *) u.u_ap;

	fdclose(uap->i);
}

/*
 * fdclose()
 *	Like calling close() syscall, callable from within a driver.
 *
 * Provided for AFS drivers which get passed an fd to V_BUILD onto.
 * If the operation is a success, the caller of the ioctl needs
 * to have this fd closed.  This is also used by the close()
 * syscall.
 */

fdclose(fd)
int	fd;
{
	struct file *fp;

	fp = ofile_close(u.u_ofile_tab, fd);
	if (fp == NULL) {
		u.u_error = EBADF;
		return;
	}

	/*
	 * Finally close the file-table entry.
	 */

	closef(fp);
	/* WHAT IF u.u_error ? */
}


/*
 * fstat()
 *	System-call.  Does a "stat" on an already open file.
 */

fstat()
{
	register struct file *fp;
	register struct a {
		int	fdes;
		struct	stat *sb;
	} *uap = (struct a *) u.u_ap;
	struct stat ub;

	fp = getf(uap->fdes);
	if (fp == 0)
		return;

	/*
	 * Need locked vnode to insure consistent data.
	 */
	VN_LOCKNODE(fp->f_vnode);
	u.u_error = vno_stat((struct vnode *)fp->f_vnode, &ub);
	VN_UNLOCKNODE(fp->f_vnode);

	if (u.u_error == 0)
		u.u_error = copyout((caddr_t)&ub, (caddr_t)uap->sb, sizeof(ub));
}

/*
 * finit()
 *	Init the file-table and file entries.
 *
 * Called in main() when the rest of the world is comming alive.
 */

lock_t	file_list;			/* to lock the list */
int	ffree_head;			/* head of free-list */

finit()
{
	register struct file *fp;

	init_lock(&file_list);
	ffree_head = -1;			/* end of list indicator */
	for (fp = file; fp < v.ve_file; fp++) {
		fp->f_next = ffree_head;
		ffree_head = fp - file;
		init_lock(&fp->f_mutex);
	}

	/*
	 * Also initialize callers open-file table.
	 */

	ofile_init();
}

/*
 * falloc()
 *	Allocate a user file descriptor and a file structure.
 *
 * Initialize the descriptor to point at the file structure.
 */

struct file *
falloc()
{
	register struct file *fp;
	register i;
	spl_t	s;

	if ((i = ofile_alloc(u.u_ofile_tab, 0)) < 0) {
		u.u_error = EMFILE;
		return (NULL);
	}

	/*
	 * Lock file-list for allocation.
	 * If there is a free element, take it; else bitch.
	 */

	s = p_lock(&file_list, SPLFS);
	if (ffree_head < 0) {
		resource_ovf.ov_file++;
		v_lock(&file_list, s);
		tablefull("file");
		u.u_error = ENFILE;
		ofile_install(u.u_ofile_tab, i, (struct file *) NULL);
		return (NULL);
	}
	fp = &file[ffree_head];
	ffree_head = fp->f_next;

	fp->f_vnode = (struct vnode *)0;
	fp->f_count = 1;

#ifdef	ns32000
	ASSERT_DEBUG(fp->f_mutex.l_state == L_UNLOCKED, "falloc: locked");
#endif	/* ns32000  */
#ifdef	i386
	ASSERT_DEBUG(fp->f_mutex == L_UNLOCKED, "falloc: locked");
#endif	/* i386  */

	v_lock(&file_list, s);
	fp->f_offset = 0;			/* ok to do outside list lock */
	crhold(u.u_cred);
	fp->f_cred = u.u_cred;
	u.u_r.r_val1 = i;			/* return value */
	return (fp);
}

/*
 * ffree()
 *	Free a file-node.
 *
 * Called various places (closef(), unwinding in copen()) to give back
 * a previously allocated file node.
 */

ffree(fp)
	register struct file *fp;
{
	struct	ucred	*cr;
	spl_t	s;

	s = p_lock(&file_list, SPLFS);

	fp->f_next = ffree_head;
	ffree_head = fp - file;
	fp->f_count = 0;		/* for cleanliness */
					/* Not redundant if copen() unwind */
	cr = fp->f_cred;
	v_lock(&file_list, s);

	crfree(cr);
}

/*
 * getf()
 *	Convert a user supplied file descriptor into a pointer
 *	to a file structure.
 *
 * Critical paths should use the GETF macro directly.
 */

struct file *
getf(fd)
	int	fd;
{
	register struct	ofile_tab *oft = u.u_ofile_tab;
	struct	file	*fp;

	if (OFILE_SHARED(oft))
		fp = ofile_getf(oft, fd);
	else if ((unsigned)(fd) < oft->oft_nofile)
		fp = oft->oft_ofile[fd].of_file;
	else
		fp = NULL;
	if (fp == NULL)
		u.u_error = EBADF;
	return fp;
}

/*
 * closef()
 *	Internal form of close.
 *
 * Decrement reference count on file structure, closes underling object
 * if count hits zero.
 */

closef(fp)
	register struct file *fp;
{
	spl_t	s;

	if (fp == NULL)
		return;

	ASSERT_DEBUG(fp->f_count > 0, "closef: f_count");
	VOP_FDCLOSE(fp->f_vnode);
	s = FDLOCK(fp);
	if (fp->f_count > 1) {
		fp->f_count--;
		FDUNLOCK(fp, s);
		return;
	}

	/*
	 * Last ref going away.  We can unlock, since nobody's going
	 * to play with it any more.  Note that the vno_close procedure
	 * must ffree() the file-node.
	 *
	 * Must also decrement the vnode writer count if appropriate.
	 */

	fp->f_count = 0;	   /* for forceclose() and cleanliness */

	FDUNLOCK(fp, s);

	u.u_error = vno_close(fp);
}

/*
 * deref_file()
 *	Clone of closef() for shared-ofile-table semantics.
 *
 * No record-lock cleanup (cleanlocks(): see closef()), since not used to
 * close open-file table entry, rather drop reference to file-table entry.
 *
 * Decrement reference count on file structure, closes underling object
 * if count hits zero.
 */
void
deref_file(fp)
	register struct file *fp;
{
	spl_t	s;

	ASSERT_DEBUG(fp != NULL, "deref_file: NULL fp");
	ASSERT_DEBUG(fp->f_count > 0, "deref_file: f_count");

	s = FDLOCK(fp);
	if (fp->f_count > 1) {
		fp->f_count--;
		FDUNLOCK(fp, s);
		return;
	}

	/*
	 * Last ref going away.  We can unlock, since nobody's going
	 * to play with it any more.  Note that the "fo_close" procedure
	 * must ffree() the file-node.
	 */

	fp->f_count = 0;	   /* for forceclose() and cleanliness */

	FDUNLOCK(fp, s);

	(void)vno_close(fp);
}
