/* $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: vfs_vnode.c 1.20 90/05/29 $"

/*
 * vfs_vnode.c
 *	Various virtual file-system vnode manipulation procedures.
 */

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

#include "sys/signal.h"
#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/mutex.h"
#include "sys/uio.h"
#include "sys/file.h"
#include "sys/pathname.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/vmmeter.h"

#include "sys/vmparam.h"
#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/plocal.h"

/*
 * read or write a vnode
 */

int
vn_rdwr(rw, vp, base, len, offset, seg, ioflag, aresid)
	enum uio_rw rw;
	struct vnode *vp;
	caddr_t base;
	int len;
	int offset;
	int seg;
	int ioflag;
	int *aresid;
{
	struct uio auio;
	struct iovec aiov;
	int error;
/*printf("vn_rdwr: rw=%x vp=%x base=%x len=%x offset=%x seg=%x ioflag=%x areasid=%x\n",
rw, vp, base, len, offset, seg, ioflag, aresid);*/

	if (rw == UIO_WRITE && (vp->v_vfsp->vfs_flag & VFS_RDONLY)) {
		return (EROFS);
	}

	aiov.iov_base = base;
	aiov.iov_len = len;
	auio.uio_iov = &aiov;
	auio.uio_iovcnt = 1;
	auio.uio_offset = offset;
	auio.uio_segflg = seg;
	auio.uio_resid = len;
	error = VOP_RDWR(vp, &auio, rw, ioflag, u.u_cred);
/*printf("vn_rdwr: VOP_RDWR ret error=%d\n", error);*/
	if (aresid)
		*aresid = auio.uio_resid;
	else
		if (auio.uio_resid)
			error = EIO;
	return (error);
}

/*
 * Open/create a vnode.
 * This may be callable by the kernel, the only known side effect being that
 * the current user uid and gid are used for permissions.
 * *vpp is returned referenced, with a locked node.
 */

int
vn_open(pnamep, seg, filemodep, createmode, vpp)
	char *pnamep;
	int *filemodep;
	int createmode;
	struct vnode **vpp;
{
	struct vnode *vp;		/* ptr to file vnode */
	register int mode;
	register int error;
	register int filemode = *filemodep;

	mode = 0;

	if (filemode & FREAD)
		mode |= VREAD;
	if (filemode & (FWRITE | FTRUNC))
		mode |= VWRITE;
 
	if (filemode & FCREAT) {
		struct vattr vattr;
		enum vcexcl excl;

		/*
		 * Wish to create a file.
		 */
		vattr_null(&vattr);
		vattr.va_type = VREG;
		vattr.va_mode = createmode;
		if (filemode & FTRUNC)
			vattr.va_size = 0;
		if (filemode & FEXCL)
			excl = EXCL;
		else
			excl = NONEXCL;
		filemode &= ~(FCREAT | FTRUNC | FEXCL);

		error = vn_create(pnamep, seg, &vattr, excl, mode, &vp);
		if (error)
			return (error);
	} else {
		/*
		 * Wish to open a file.
		 * Just look it up.
		 */
		error =
		    lookupname(pnamep, seg, FOLLOW_LINK,
			(struct vnode **)0, &vp);
		if (error)
			return (error);
		/*
		 * cannnot write directories, active texts or
		 * read only filesystems
		 */
		if (filemode & (FWRITE | FTRUNC)) {
			if (vp->v_type == VDIR) {
				error = EISDIR;
				goto out;
			}
			if (vp->v_vfsp->vfs_flag & VFS_RDONLY) {
				error = EROFS;
				goto out;
			}
			/*
			 * If there's shared text associated with
			 * the vnode, try to free it up once.
			 * If we fail, we can't allow writing.
			 */
			if (vp->v_flag & VTEXT) {
				if (!xrele(vp)) {
					error = ETXTBSY;
					goto out;
				}
			}
		}
		/*
		 * check permissions
		 */
		error = VOP_ACCESS(vp, mode, u.u_cred);
		if (error)
			goto out;
		/*
		 * Sockets in filesystem name space are not supported (yet?)
		 */
		if (vp->v_type == VSOCK) {
			error = ENXIO;
			goto out;
		}
	}
out:
	if (error) {
		VN_PUT(vp);
	} else {
		*vpp = vp;
		*filemodep = filemode;
	}
	return (error);
}

/*
 * create a vnode (makenode)
 *	Returns a referenced node-locked vnode.
 */

int
vn_create(pnamep, seg, vap, excl, mode, vpp)
	char *pnamep;
	int seg;
	struct vattr *vap;
	enum vcexcl excl;
	int mode;
	struct vnode **vpp;
{
	struct vnode *dvp;	/* ptr to parent dir vnode */
	struct pathname pn;
	register int error;

	/*
	 * Lookup directory.
	 * If new object is a file, call lower level to create it.
	 * Note that it is up to the lower level to enforce exclusive
	 * creation, if the file is already there.
	 * This allows the lower level to do whatever
	 * locking or protocol that is needed to prevent races.
	 * If the new object is directory call lower level to make
	 * the new directory, with "." and "..".
	 */
	*vpp = NULLVP;
	dvp = NULLVP;
	error = pn_get(pnamep, seg, &pn);
	if (error)
		return (error);
	/*
	 * lookup will find the parent directory for the vnode.
	 * When it is done the pn hold the name of the entry
	 * in the directory.
	 * If this is a non-exclusive create we also find the node itself.
	 */
	error = lookuppn(&pn, FOLLOW_LINK, &dvp,
				(excl == NONEXCL? vpp: (struct vnode **)0));
	if (error) {
		pn_free(&pn);
		return (error);
	}
	/*
	 * Make sure filesystem is writeable
	 */
	if (dvp->v_vfsp->vfs_flag & VFS_RDONLY) {
		if (*vpp) {
			VN_PUT(*vpp);
		}
		error = EROFS;
	} else if (excl == NONEXCL && *vpp != NULLVP) {
		/*
		 * The file is already there.
		 * If we are writing, and there's a shared text
		 * associated with the vnode, try to free it up once.
		 * If we fail, we can't allow writing.
		 */
		if ((mode & VWRITE) && ((*vpp)->v_flag & VTEXT)) {
			if (!xrele(*vpp))
				error = ETXTBSY;
		}
		/*
		 * we throw the vnode away to let VOP_CREATE truncate the
		 * file in a non-racy manner.
		 */
		VN_PUT(*vpp);
	}
	if (error == 0) {
		/*
		 * call mkdir if directory or create if other
		 */
		if ((vap->va_type == VDIR) && !(mode & VWRITE)) {
			error = VOP_MKDIR(dvp, pn.pn_path, vap, vpp, u.u_cred);
		} else {
			error = VOP_CREATE(
			    dvp, pn.pn_path, vap, excl, mode, vpp, u.u_cred);
		}
	}
	pn_free(&pn);
	VN_RELE(dvp);
	return (error);
}

/*
 * close a vnode
 */

int
vn_close(vp, flag)
register struct vnode *vp;
int flag;
{
	return (VOP_CLOSE(vp, flag, u.u_cred));
}

/*
 * Link.
 */

int
vn_link(from_p, to_p, seg)
	char *from_p;
	char *to_p;
	int seg;
{
	struct vnode *fvp;		/* from vnode ptr */
	struct vnode *tdvp;		/* to directory vnode ptr */
	struct pathname pn;
	register int error;

	fvp = tdvp = NULLVP;
	error = pn_get(to_p, seg, &pn);
	if (error)
		return (error);

	/*
	 * After the following lookups, fvp is node-locked, 
	 * tdvp is node-unlocked. Both are referenced.
	 * Unlock fvp, as the code below VOP_LINK expects.
	 * This could possibly be optimized in the future.
	 */

	error = lookupname(from_p, seg, FOLLOW_LINK, (struct vnode **)0, &fvp);
	if (error)
		goto out;
	VN_UNLOCKNODE(fvp);

	error = lookuppn(&pn, FOLLOW_LINK, &tdvp, (struct vnode **)0);
	if (error)
		goto out;

	/*
	 * Make sure both source vnode and target directory vnode are
	 * in the same vfs and that it is writeable.
	 */

	if (fvp->v_vfsp != tdvp->v_vfsp) {
		error = EXDEV;
		goto out;
	}
	if (tdvp->v_vfsp->vfs_flag & VFS_RDONLY) {
		error = EROFS;
		goto out;
	}

	/*
	 * Do the link.
	 */

	error = VOP_LINK(fvp, tdvp, pn.pn_path, u.u_cred);
out:
	pn_free(&pn);
	if (fvp)
		VN_RELE(fvp);
	if (tdvp)
		VN_RELE(tdvp);
	return (error);
}

/*
 * vn_rename()
 *	Rename a vnode.
 */

int
vn_rename(from_p, to_p, seg)
	char *from_p;
	char *to_p;
	int seg;
{
	struct vnode *fdvp;		/* from directory vnode ptr */
	struct vnode *fvp;		/* from vnode ptr */
	struct vnode *tdvp;		/* to directory vnode ptr */
	struct vnode *tvp;		/* to vnode ptr, if it exists */
	struct pathname fpn;		/* from pathname */
	struct pathname tpn;		/* to pathname */
	register int error;

	fvp = fdvp = tdvp = tvp = NULLVP;

	/*
	 * Get to and from pathnames.
	 */

	error = pn_get(from_p, seg, &fpn);
	if (error)
		return (error);

	error = pn_get(to_p, seg, &tpn);
	if (error) {
		pn_free(&fpn);
		return (error);
	}

	/*
	 * Lookup to and from directories, and make sure "from" file exists.
	 */

	error = lookuppn(&fpn, NO_FOLLOW, &fdvp, &fvp);
	if (error)
		goto out;
	if (fvp == NULLVP) {
		error = ENOENT;
		goto out;
	}
	VN_UNLOCKNODE(fvp);		/* unlock but still referenced */

	error = lookuppn(&tpn, NO_FOLLOW, &tdvp, &tvp);
	if (error)
		goto out;

	/*
	 * Make sure both the from vnode and the to directory are
	 * in the same vfs and that it is writeable.
	 */

	if (fvp->v_vfsp != tdvp->v_vfsp) {
		error = EXDEV;
		if (tvp != NULLVP)
			VN_PUT(tvp);	/* unlock and release reference */
		goto out;
	}
	if (tdvp->v_vfsp->vfs_flag & VFS_RDONLY) {
		error = EROFS;
		if (tvp != NULLVP)
			VN_PUT(tvp);	/* unlock and release reference */
		goto out;
	}

	/*
         * If the target of the rename exists, try once to delete text ref 
	 * (if any); this can race with concurrent create, execute, release 
	 * of file by same name in this directory with very low probability 
	 * (doesn't hurt, might just leave a tacky text with no file-system 
	 * name; see xfree()).
         */

	if (tvp != NULLVP) {
		if (tvp->v_flag & VROOT) {
			/*
			 * If the target exists and is a directory and
			 * is a mount point (the root of another fs),
			 * then return EBUSY.
			 */
			error = EBUSY; 
			VN_PUT(tvp);	/* unlock and release reference */
			goto out;
		}
		if (tvp->v_flag & VTEXT) {
	                (void) xrele(tvp);
		}
		VN_PUT(tvp);		/* unlock and release reference */
	}

	/*
	 * Do the rename.
	 */

	error = VOP_RENAME(fdvp, fpn.pn_path, tdvp, tpn.pn_path, u.u_cred);
out:
	pn_free(&fpn);
	pn_free(&tpn);

	if (fvp)
		VN_RELE(fvp);
	if (fdvp)
		VN_RELE(fdvp);
	if (tdvp)
		VN_RELE(tdvp);
	return (error);
}

/*
 * vn_remove()
 *	Remove a file or directory.
 */

int
vn_remove(fnamep, seg, dirflag)
	char *fnamep;
	int seg;
	enum rm dirflag;
{
	struct vnode *vp;		/* entry vnode */
	struct vnode *dvp;		/* ptr to parent dir vnode */
	struct pathname pn;		/* name of entry */
	enum vtype vtype;
	register int error;

	error = pn_get(fnamep, seg, &pn);
	if (error)
		return (error);
	vp = NULLVP;
	error = lookuppn(&pn, NO_FOLLOW, &dvp, &vp);
	if (error) {
		pn_free(&pn);
		return (error);
	}
	/*
	 * make sure there is an entry
	 */
	if (vp == NULLVP) {
		error = ENOENT;
		goto out;
	}
	/*
	 * don't unlink where a mount is concurrently being attempted.
	 */
	if (vp->v_flag & VMOUNTING) {
		error = EBUSY;
		goto out;
	}
	/*
	 * Don't unlink across a mounted filesystem.
	 *
	 * The concern is only where a link crosses the mount point;
	 * if a link refers to the mount point but also originates
	 * within the same mounted filesystem then it's ok.
	 */
	if (dvp->v_vfsp != vp->v_vfsp) {
		error = EBUSY;
		goto out;
	}
	/*
	 * make sure filesystem is writeable
	 */
	if (vp->v_vfsp->vfs_flag & VFS_RDONLY) {
		error = EROFS;
		goto out;
	}
	/*
	 * Release vnode before removing.
	 * Also try once to delete text ref (if any); this can race with
	 * concurrent create, execute, release of file by same name in this
	 * directory with very low probability (doesn't hurt, might just
	 * leave a tacky text with no file-system name; see xfree()).
	 */
	vtype = vp->v_type;
	if (vp->v_flag & VTEXT)
		(void) xrele(vp);
	VN_PUT(vp);
	vp = NULLVP;
	/*
	 * The following code was re-arranged from the BSD version
	 * to allow unlinking of directories.
	 */
	if (dirflag == DIRECTORY) {
		if (vtype == VDIR)
			error = VOP_RMDIR(dvp, pn.pn_path, u.u_cred);
		else
			error = ENOTDIR;
	} else {
		if (vtype == VDIR && !suser())
			error = EPERM;
		else
			error = VOP_REMOVE(dvp, pn.pn_path, u.u_cred);
	}
out:
	pn_free(&pn);
	if (vp != NULLVP)
		VN_PUT(vp);
	VN_RELE(dvp);
	return (error);
}

/*
 * nullvattr_init()
 *	Initialize the nullvattr structure.
 */

struct	vattr	nullvattr;

nullvattr_init()
{
	register int n;
	register char *cp;

	n = sizeof(struct vattr);
	cp = (char *) &nullvattr;
	while (n--) {
		*cp++ = -1;
	}
}
