/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/param.h>
#include <sys/inttypes.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/session.h>
#include <sys/var.h>
#include <sys/utsname.h>
#include <sys/utssys.h>
#include <sys/ustat.h>
#include <sys/statvfs.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/pathname.h>
#include <sys/modctl.h>
#include <sys/fs/snode.h>
#include <sys/sunldi_impl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/ddipropdefs.h>
#include <sys/ddi_impldefs.h>
#include <sys/modctl.h>
#include <sys/flock.h>
#include <sys/share.h>
#include <vm/as.h>
#include <vm/seg.h>
#include <vm/seg_vn.h>
#include <util/qsort.h>
#include <sys/zone.h>

/*
 * utssys()
 */
static int		uts_fusers(char *, int, intptr_t);
static int		_statvfs64_by_dev(dev_t, struct statvfs64 *);

#if defined(_ILP32) || defined(_SYSCALL32_IMPL)

static int utssys_uname32(caddr_t, rval_t *);
static int utssys_ustat32(dev_t, struct ustat32 *);

int64_t
utssys32(void *buf, int arg, int type, void *outbp)
{
	int error;
	rval_t rv;

	rv.r_vals = 0;

	switch (type) {
	case UTS_UNAME:
		/*
		 * This is an obsolete way to get the utsname structure
		 * (it only gives you the first 8 characters of each field!)
		 * uname(2) is the preferred and better interface.
		 */
		error = utssys_uname32(buf, &rv);
		break;
	case UTS_USTAT:
		error = utssys_ustat32(expldev((dev32_t)arg), buf);
		break;
	case UTS_FUSERS:
		error = uts_fusers(buf, arg, (intptr_t)outbp);
		break;
	default:
		error = EINVAL;
		break;
	}

	return (error == 0 ? rv.r_vals : (int64_t)set_errno(error));
}

static int
utssys_uname32(caddr_t buf, rval_t *rvp)
{
	if (copyout(utsname.sysname, buf, 8))
		return (EFAULT);
	buf += 8;
	if (subyte(buf, 0) < 0)
		return (EFAULT);
	buf++;
	if (copyout(uts_nodename(), buf, 8))
		return (EFAULT);
	buf += 8;
	if (subyte(buf, 0) < 0)
		return (EFAULT);
	buf++;
	if (copyout(utsname.release, buf, 8))
		return (EFAULT);
	buf += 8;
	if (subyte(buf, 0) < 0)
		return (EFAULT);
	buf++;
	if (copyout(utsname.version, buf, 8))
		return (EFAULT);
	buf += 8;
	if (subyte(buf, 0) < 0)
		return (EFAULT);
	buf++;
	if (copyout(utsname.machine, buf, 8))
		return (EFAULT);
	buf += 8;
	if (subyte(buf, 0) < 0)
		return (EFAULT);
	rvp->r_val1 = 1;
	return (0);
}

static int
utssys_ustat32(dev_t dev, struct ustat32 *cbuf)
{
	struct ustat32 ust32;
	struct statvfs64 stvfs;
	fsblkcnt64_t	fsbc64;
	char *cp, *cp2;
	int i, error;

	if ((error = _statvfs64_by_dev(dev, &stvfs)) != 0)
		return (error);

	fsbc64 = stvfs.f_bfree * (stvfs.f_frsize / 512);
	/*
	 * Check to see if the number of free blocks can be expressed
	 * in 31 bits or whether the number of free files is more than
	 * can be expressed in 32 bits and is not -1 (UINT64_MAX).  NFS
	 * Version 2 does not support the number of free files and
	 * hence will return -1.  -1, when translated from a 32 bit
	 * quantity to an unsigned 64 bit quantity, turns into UINT64_MAX.
	 */
	if (fsbc64 > INT32_MAX ||
	    (stvfs.f_ffree > UINT32_MAX && stvfs.f_ffree != UINT64_MAX))
		return (EOVERFLOW);

	ust32.f_tfree = (daddr32_t)fsbc64;
	ust32.f_tinode = (ino32_t)stvfs.f_ffree;

	cp = stvfs.f_fstr;
	cp2 = ust32.f_fname;
	i = 0;
	while (i++ < sizeof (ust32.f_fname))
		if (*cp != '\0')
			*cp2++ = *cp++;
		else
			*cp2++ = '\0';
	while (*cp != '\0' &&
	    (i++ < sizeof (stvfs.f_fstr) - sizeof (ust32.f_fpack)))
		cp++;
	(void) strncpy(ust32.f_fpack, cp + 1, sizeof (ust32.f_fpack));

	if (copyout(&ust32, cbuf, sizeof (ust32)))
		return (EFAULT);
	return (0);
}

#endif	/* _ILP32 || _SYSCALL32_IMPL */

#ifdef _LP64

static int uts_ustat64(dev_t, struct ustat *);

int64_t
utssys64(void *buf, long arg, int type, void *outbp)
{
	int error;
	rval_t rv;

	rv.r_vals = 0;

	switch (type) {
	case UTS_USTAT:
		error = uts_ustat64((dev_t)arg, buf);
		break;
	case UTS_FUSERS:
		error = uts_fusers(buf, (int)arg, (intptr_t)outbp);
		break;
	default:
		error = EINVAL;
		break;
	}

	return (error == 0 ? rv.r_vals : (int64_t)set_errno(error));
}

static int
uts_ustat64(dev_t dev, struct ustat *cbuf)
{
	struct ustat ust;
	struct statvfs64 stvfs;
	fsblkcnt64_t	fsbc64;
	char *cp, *cp2;
	int i, error;

	if ((error = _statvfs64_by_dev(dev, &stvfs)) != 0)
		return (error);

	fsbc64 = stvfs.f_bfree * (stvfs.f_frsize / 512);
	ust.f_tfree = (daddr_t)fsbc64;
	ust.f_tinode = (ino_t)stvfs.f_ffree;

	cp = stvfs.f_fstr;
	cp2 = ust.f_fname;
	i = 0;
	while (i++ < sizeof (ust.f_fname))
		if (*cp != '\0')
			*cp2++ = *cp++;
		else
			*cp2++ = '\0';
	while (*cp != '\0' &&
	    (i++ < sizeof (stvfs.f_fstr) - sizeof (ust.f_fpack)))
		cp++;
	(void) strncpy(ust.f_fpack, cp + 1, sizeof (ust.f_fpack));

	if (copyout(&ust, cbuf, sizeof (ust)))
		return (EFAULT);
	return (0);
}

#endif	/* _LP64 */

/*
 * Utility routine for the ustat implementations.
 * (If it wasn't for the 'find-by-dev_t' semantic of ustat(2), we could push
 * this all out into userland, sigh.)
 */
static int
_statvfs64_by_dev(dev_t dev, struct statvfs64 *svp)
{
	vfs_t *vfsp;
	int error;

	if ((vfsp = vfs_dev2vfsp(dev)) == NULL) {
		/*
		 * See if it's the root of our zone.
		 */
		vfsp = curproc->p_zone->zone_rootvp->v_vfsp;
		if (vfsp->vfs_dev == dev) {
			VFS_HOLD(vfsp);
		} else {
			vfsp = NULL;
		}
	}
	if (vfsp == NULL)
		return (EINVAL);
	error = VFS_STATVFS(vfsp, svp);
	VFS_RELE(vfsp);
	return (error);
}

/*
 * Check if this pid has an NBMAND lock or share reservation
 * on this vp. llp is a snapshoted list of all NBMAND locks
 * set by this pid. Return 1 if there is an NBMAND lock else
 * return 0.
 */
static int
proc_has_nbmand_on_vp(vnode_t *vp, pid_t pid, locklist_t *llp)
{
	/*
	 * Any NBMAND lock held by the process on this vp?
	 */
	while (llp) {
		if (llp->ll_vp == vp) {
			return (1);
		}
		llp = llp->ll_next;
	}
	/*
	 * Any NBMAND share reservation on the vp for this process?
	 */
	return (proc_has_nbmand_share_on_vp(vp, pid));
}

static fu_data_t *
dofusers(vnode_t *fvp, int flags)
{
	fu_data_t	*fu_data;
	proc_t		*prp;
	vfs_t		*cvfsp;
	pid_t		npids, pidx, *pidlist;
	int		v_proc = v.v_proc;	/* max # of procs */
	int		pcnt = 0;
	int		contained = (flags & F_CONTAINED);
	int		nbmandonly = (flags & F_NBMANDLIST);
	int		dip_usage = (flags & F_DEVINFO);
	int		fvp_isdev = vn_matchops(fvp, spec_getvnodeops());
	zone_t *zone = curproc->p_zone;
	int inglobal = INGLOBALZONE(curproc);

	/* get a pointer to the file system containing this vnode */
	cvfsp = fvp->v_vfsp;
	ASSERT(cvfsp);

	/* allocate the data structure to return our results in */
	fu_data = kmem_alloc(fu_data_size(v_proc), KM_SLEEP);
	fu_data->fud_user_max = v_proc;
	fu_data->fud_user_count = 0;

	/* get a snapshot of all the pids we're going to check out */
	pidlist = kmem_alloc(v_proc * sizeof (pid_t), KM_SLEEP);
	mutex_enter(&pidlock);
	for (npids = 0, prp = practive; prp != NULL; prp = prp->p_next) {
		if (inglobal || prp->p_zone == zone)
			pidlist[npids++] = prp->p_pid;
	}
	mutex_exit(&pidlock);

	/* grab each process and check its file usage */
	for (pidx = 0; pidx < npids; pidx++) {
		locklist_t	*llp = NULL;
		uf_info_t	*fip;
		vnode_t		*vp;
		user_t		*up;
		sess_t		*sp;
		uid_t		uid;
		pid_t		pid = pidlist[pidx];
		int		i, use_flag = 0;

		/*
		 * grab prp->p_lock using sprlock()
		 * if sprlock() fails the process does not exists anymore
		 */
		prp = sprlock(pid);
		if (prp == NULL)
			continue;

		/* get the processes credential info in case we need it */
		mutex_enter(&prp->p_crlock);
		uid = crgetruid(prp->p_cred);
		mutex_exit(&prp->p_crlock);

		/*
		 * it's safe to drop p_lock here because we
		 * called sprlock() before and it set the SPRLOCK
		 * flag for the process so it won't go away.
		 */
		mutex_exit(&prp->p_lock);

		/*
		 * now we want to walk a processes open file descriptors
		 * to do this we need to grab the fip->fi_lock.  (you
		 * can't hold p_lock when grabbing the fip->fi_lock.)
		 */
		fip = P_FINFO(prp);
		mutex_enter(&fip->fi_lock);

		/*
		 * Snapshot nbmand locks for pid
		 */
		llp = flk_active_nbmand_locks(prp->p_pid);
		for (i = 0; i < fip->fi_nfiles; i++) {
			uf_entry_t	*ufp;
			file_t		*fp;

			UF_ENTER(ufp, fip, i);
			if (((fp = ufp->uf_file) == NULL) ||
			    ((vp = fp->f_vnode) == NULL)) {
				UF_EXIT(ufp);
				continue;
			}

			/*
			 * if the target file (fvp) is not a device
			 * and corrosponds to the root of a filesystem
			 * (cvfsp), then check if it contains the file
			 * is use by this process (vp).
			 */
			if (contained && (vp->v_vfsp == cvfsp))
				use_flag |= F_OPEN;

			/*
			 * if the target file (fvp) is not a device,
			 * then check if it matches the file in use
			 * by this process (vp).
			 */
			if (!fvp_isdev && VN_CMP(fvp, vp))
				use_flag |= F_OPEN;

			/*
			 * if the target file (fvp) is a device,
			 * then check if the current file in use
			 * by this process (vp) maps to the same device
			 * minor node.
			 */
			if (fvp_isdev &&
			    vn_matchops(vp, spec_getvnodeops()) &&
			    (fvp->v_rdev == vp->v_rdev))
				use_flag |= F_OPEN;

			/*
			 * if the target file (fvp) is a device,
			 * and we're checking for device instance
			 * usage, then check if the current file in use
			 * by this process (vp) maps to the same device
			 * instance.
			 */
			if (dip_usage &&
			    vn_matchops(vp, spec_getvnodeops()) &&
			    (VTOCS(fvp)->s_dip == VTOCS(vp)->s_dip))
				use_flag |= F_OPEN;

			/*
			 * if the current file in use by this process (vp)
			 * doesn't match what we're looking for, move on
			 * to the next file in the process.
			 */
			if ((use_flag & F_OPEN) == 0) {
				UF_EXIT(ufp);
				continue;
			}

			if (proc_has_nbmand_on_vp(vp, prp->p_pid, llp)) {
				/* A nbmand found so we're done.  */
				use_flag |= F_NBM;
				UF_EXIT(ufp);
				break;
			}
			UF_EXIT(ufp);
		}
		if (llp)
			flk_free_locklist(llp);

		mutex_exit(&fip->fi_lock);

		/*
		 * If nbmand usage tracking is desired and no nbmand was
		 * found for this process, then no need to do further
		 * usage tracking for this process.
		 */
		if (nbmandonly && (!(use_flag & F_NBM))) {
			/*
			 * grab the process lock again, clear the SPRLOCK
			 * flag, release the process, and continue.
			 */
			mutex_enter(&prp->p_lock);
			sprunlock(prp);
			continue;
		}

		/*
		 * All other types of usage.
		 * For the next few checks we need to hold p_lock.
		 */
		mutex_enter(&prp->p_lock);
		up = PTOU(prp);
		if (fvp_isdev) {
			/*
			 * if the target file (fvp) is a device
			 * then check if it matches the processes tty
			 *
			 * we grab s_lock to protect ourselves against
			 * freectty() freeing the vnode out from under us.
			 */
			sp = prp->p_sessp;
			mutex_enter(&sp->s_lock);
			vp = prp->p_sessp->s_vp;
			if (vp != NULL) {
				if (fvp->v_rdev == vp->v_rdev)
					use_flag |= F_TTY;

				if (dip_usage &&
				    (VTOCS(fvp)->s_dip == VTOCS(vp)->s_dip))
					use_flag |= F_TTY;
			}
			mutex_exit(&sp->s_lock);
		} else {
			/* check the processes current working directory */
			if (up->u_cdir &&
			    (VN_CMP(fvp, up->u_cdir) ||
			    (contained && (up->u_cdir->v_vfsp == cvfsp))))
				use_flag |= F_CDIR;

			/* check the processes root directory */
			if (up->u_rdir &&
			    (VN_CMP(fvp, up->u_rdir) ||
			    (contained && (up->u_rdir->v_vfsp == cvfsp))))
				use_flag |= F_RDIR;

			/* check the program text vnode */
			if (prp->p_exec &&
			    (VN_CMP(fvp, prp->p_exec) ||
			    (contained && (prp->p_exec->v_vfsp == cvfsp))))
				use_flag |= F_TEXT;
		}

		/* Now we can drop p_lock again */
		mutex_exit(&prp->p_lock);

		/*
		 * now we want to walk a processes memory mappings.
		 * to do this we need to grab the prp->p_as lock.  (you
		 * can't hold p_lock when grabbing the prp->p_as lock.)
		 */
		if (prp->p_as != &kas) {
			struct seg	*seg;
			struct as	*as = prp->p_as;

			AS_LOCK_ENTER(as, &as->a_lock, RW_READER);
			for (seg = AS_SEGFIRST(as); seg;
			    seg = AS_SEGNEXT(as, seg)) {
				/*
				 * if we can't get a backing vnode for this
				 * segment then skip it
				 */
				vp = NULL;
				if ((SEGOP_GETVP(seg, seg->s_base, &vp)) ||
				    (vp == NULL))
					continue;

				/*
				 * if the target file (fvp) is not a device
				 * and corrosponds to the root of a filesystem
				 * (cvfsp), then check if it contains the
				 * vnode backing this segment (vp).
				 */
				if (contained && (vp->v_vfsp == cvfsp)) {
					use_flag |= F_MAP;
					break;
				}

				/*
				 * if the target file (fvp) is not a device,
				 * check if it matches the the vnode backing
				 * this segment (vp).
				 */
				if (!fvp_isdev && VN_CMP(fvp, vp)) {
					use_flag |= F_MAP;
					break;
				}

				/*
				 * if the target file (fvp) isn't a device,
				 * or the the vnode backing this segment (vp)
				 * isn't a device then continue.
				 */
				if (!fvp_isdev ||
				    !vn_matchops(vp, spec_getvnodeops()))
					continue;

				/*
				 * check if the vnode backing this segment
				 * (vp) maps to the same device minor node
				 * as the target device (fvp)
				 */
				if (fvp->v_rdev == vp->v_rdev) {
					use_flag |= F_MAP;
					break;
				}

				/*
				 * if we're checking for device instance
				 * usage, then check if the vnode backing
				 * this segment (vp) maps to the same device
				 * instance as the target device (fvp).
				 */
				if (dip_usage &&
				    (VTOCS(fvp)->s_dip == VTOCS(vp)->s_dip)) {
					use_flag |= F_MAP;
					break;
				}
			}
			AS_LOCK_EXIT(as, &as->a_lock);
		}

		if (use_flag) {
			ASSERT(pcnt < fu_data->fud_user_max);
			fu_data->fud_user[pcnt].fu_flags = use_flag;
			fu_data->fud_user[pcnt].fu_pid = pid;
			fu_data->fud_user[pcnt].fu_uid = uid;
			pcnt++;
		}

		/*
		 * grab the process lock again, clear the SPRLOCK
		 * flag, release the process, and continue.
		 */
		mutex_enter(&prp->p_lock);
		sprunlock(prp);
	}

	kmem_free(pidlist, v_proc * sizeof (pid_t));

	fu_data->fud_user_count = pcnt;
	return (fu_data);
}

typedef struct dofkusers_arg {
	vnode_t		*fvp;
	int		flags;
	int		*error;
	fu_data_t	*fu_data;
} dofkusers_arg_t;

static int
dofkusers_walker(const ldi_usage_t *ldi_usage, void *arg)
{
	dofkusers_arg_t	*dofkusers_arg = (dofkusers_arg_t *)arg;

	vnode_t		*fvp = dofkusers_arg->fvp;
	int		flags = dofkusers_arg->flags;
	int		*error = dofkusers_arg->error;
	fu_data_t	*fu_data = dofkusers_arg->fu_data;

	modid_t		modid;
	minor_t		minor;
	int		instance;
	int		dip_usage = (flags & F_DEVINFO);

	ASSERT(*error == 0);
	ASSERT(vn_matchops(fvp, spec_getvnodeops()));

	/*
	 * check if the dev_t of the target device matches the dev_t
	 * of the device we're trying to find usage info for.
	 */
	if (fvp->v_rdev != ldi_usage->tgt_devt) {

		/*
		 * if the dev_ts don't match and we're not trying
		 * to find usage information for device instances
		 * then return
		 */
		if (!dip_usage)
			return (LDI_USAGE_CONTINUE);


		/*
		 * we're trying to find usage information for an
		 * device instance instead of just a minor node.
		 *
		 * check if the dip for the target device matches the
		 * dip of the device we're trying to find usage info for.
		 */
		if (VTOCS(fvp)->s_dip != ldi_usage->tgt_dip)
			return (LDI_USAGE_CONTINUE);
	}

	if (fu_data->fud_user_count >= fu_data->fud_user_max) {
		*error = E2BIG;
		return (LDI_USAGE_TERMINATE);
	}

	/* get the device vnode user information */
	modid = ldi_usage->src_modid;
	ASSERT(modid != -1);

	minor = instance = -1;
	if (ldi_usage->src_dip != NULL) {
		instance = DEVI(ldi_usage->src_dip)->devi_instance;
	}
	if (ldi_usage->src_devt != DDI_DEV_T_NONE) {
		minor = getminor(ldi_usage->src_devt);
	}

	/* set the device vnode user information */
	fu_data->fud_user[fu_data->fud_user_count].fu_flags = F_KERNEL;
	fu_data->fud_user[fu_data->fud_user_count].fu_modid = modid;
	fu_data->fud_user[fu_data->fud_user_count].fu_instance = instance;
	fu_data->fud_user[fu_data->fud_user_count].fu_minor = minor;

	fu_data->fud_user_count++;

	return (LDI_USAGE_CONTINUE);
}

int
f_user_cmp(const void *arg1, const void *arg2)
{
	f_user_t *f_user1 = (f_user_t *)arg1;
	f_user_t *f_user2 = (f_user_t *)arg2;

	/*
	 * we should only be called for f_user_t entires that represent
	 * a kernel file consumer
	 */
	ASSERT(f_user1->fu_flags & F_KERNEL);
	ASSERT(f_user2->fu_flags & F_KERNEL);

	if (f_user1->fu_modid != f_user2->fu_modid)
		return ((f_user1->fu_modid < f_user2->fu_modid) ? -1 : 1);

	if (f_user1->fu_instance != f_user2->fu_instance)
		return ((f_user1->fu_instance < f_user2->fu_instance) ? -1 : 1);

	if (f_user1->fu_minor != f_user2->fu_minor)
		return ((f_user1->fu_minor < f_user2->fu_minor) ? -1 : 1);

	return (0);
}

static fu_data_t *
dofkusers(vnode_t *fvp, int flags, int *error)
{
	dofkusers_arg_t	dofkusers_arg;
	fu_data_t	*fu_data;
	int		user_max, i;

	/*
	 * we only keep track of kernel device consumers, so if the
	 * target vnode isn't a device then there's nothing to do here
	 */
	if (!vn_matchops(fvp, spec_getvnodeops()))
		return (NULL);

	/* allocate the data structure to return our results in */
	user_max = ldi_usage_count();
	fu_data = kmem_alloc(fu_data_size(user_max), KM_SLEEP);
	fu_data->fud_user_max = user_max;
	fu_data->fud_user_count = 0;

	/* invoke the callback to collect device usage information */
	dofkusers_arg.fvp = fvp;
	dofkusers_arg.flags = flags;
	dofkusers_arg.error = error;
	dofkusers_arg.fu_data = fu_data;
	ldi_usage_walker(&dofkusers_arg, dofkusers_walker);

	/* check for errors */
	if (*error != 0)
		return (fu_data);

	/* if there aren't any file consumers then return */
	if (fu_data->fud_user_count == 0)
		return (fu_data);

	/*
	 * since we ignore the spec_type of the target we're trying to
	 * access it's possible that we could have duplicates entries in
	 * the list of consumers.
	 *
	 * we don't want to check for duplicate in the callback because
	 * we're holding locks in the ldi when the callback is invoked.
	 *
	 * so here we need to go through the array of file consumers
	 * and remove duplicate entries.
	 */

	/* first sort the array of file consumers */
	qsort((caddr_t)fu_data->fud_user, fu_data->fud_user_count,
	    sizeof (f_user_t), f_user_cmp);

	/* then remove any duplicate entires */
	i = 1;
	while (i < fu_data->fud_user_count) {

		if (f_user_cmp(&fu_data->fud_user[i],
		    &fu_data->fud_user[i - 1]) != 0) {
			/*
			 * the current element is unique, move onto
			 * the next one
			 */
			i++;
			continue;
		}

		/*
		 * this entry is a duplicate so if it's not the last
		 * entry in the array then remove it.
		 */
		fu_data->fud_user_count--;
		if (i == fu_data->fud_user_count)
			break;

		bcopy(&fu_data->fud_user[i + 1], &fu_data->fud_user[i],
		    sizeof (f_user_t) * (fu_data->fud_user_count - i));
	}

	return (fu_data);
}

/*
 * Determine the ways in which processes and the kernel are using a named
 * file or mounted file system (path).  Normally return 0.  In case of an
 * error appropriate errno will be returned.
 *
 * Upon success, uts_fusers will also copyout the file usage information
 * in the form of an array of f_user_t's that are contained within an
 * fu_data_t pointed to by userbp.
 */
static int
uts_fusers(char *path, int flags, intptr_t userbp)
{
	fu_data_t	*fu_data = NULL, *fuk_data = NULL;
	fu_data_t	fu_header;
	vnode_t		*fvp = NULL;
	size_t		bcount;
	int		error = 0;
	int		total_max, total_out;
	int		contained = (flags & F_CONTAINED);
	int		dip_usage = (flags & F_DEVINFO);
	int		fvp_isdev;


	/* figure out how man f_user_t's we can safetly copy out */
	if (copyin((const void *)userbp, &total_max, sizeof (total_max)))
		return (EFAULT);

	/*
	 * check if we only want a count of how many kernel device
	 * consumers exist
	 */
	if (flags & F_KINFO_COUNT) {
		fu_header.fud_user_max = total_max;
		fu_header.fud_user_count = ldi_usage_count();
		bcount = fu_data_size(0);
		if (copyout(&fu_header, (void *)userbp, bcount))
			return (EFAULT);
		return (0);
	}

	/* get the vnode for the file we want to look up usage for */
	error = lookupname(path, UIO_USERSPACE, FOLLOW, NULLVPP, &fvp);
	if (error != 0)
		return (error);
	ASSERT(fvp);
	fvp_isdev = vn_matchops(fvp, spec_getvnodeops());

	/*
	 * if we want to report usage for all files contained within a
	 * file system then the target file better correspond to the
	 * root node of a mounted file system, or the root of a zone.
	 */
	if (contained && !(fvp->v_flag & VROOT) &&
	    fvp != curproc->p_zone->zone_rootvp) {
		error = EINVAL;
		goto out;
	}

	/*
	 * if we want to report usage for all files contained within a
	 * file system then the target file better not be a device.
	 */
	if (contained && fvp_isdev) {
		error = EINVAL;
		goto out;
	}

	/*
	 * if we want to report usage for a device instance then the
	 * target file better corrospond to a device
	 */
	if (dip_usage && !fvp_isdev) {
		error = EINVAL;
		goto out;
	}

	/*
	 * if the target vnode isn't a device and it has a reference count
	 * of one then no one else is going to have it open so we don't
	 * have any work to do.
	 */
	if (!fvp_isdev && (fvp->v_count == 1)) {
		goto out;
	}

	/* look up usage information for this vnode */
	fu_data = dofusers(fvp, flags);
	fuk_data = dofkusers(fvp, flags, &error);
	if (error != 0)
		goto out;

	/* get a count of the number of f_user_t's we need to copy out */
	total_out = 0;
	if (fu_data)
		total_out += fu_data->fud_user_count;
	if (fuk_data)
		total_out += fuk_data->fud_user_count;

	/* check if there is enough space to copyout all results */
	if (total_out > total_max) {
		error = E2BIG;
		goto out;
	}

	/* copyout file usage info counts */
	fu_header.fud_user_max = total_max;
	fu_header.fud_user_count = total_out;
	bcount = fu_data_size(0);
	if (copyout(&fu_header, (void *)userbp, bcount)) {
		error = EFAULT;
		goto out;
	}

	/* copyout userland process file usage info */
	if ((fu_data != NULL) && (fu_data->fud_user_count > 0)) {
		userbp += bcount;
		bcount = fu_data->fud_user_count * sizeof (f_user_t);
		if (copyout(fu_data->fud_user, (void *)userbp, bcount)) {
			error = EFAULT;
			goto out;
		}
	}

	/* copyout kernel file usage info */
	if ((fuk_data != NULL) && (fuk_data->fud_user_count > 0)) {
		userbp += bcount;
		bcount = fuk_data->fud_user_count * sizeof (f_user_t);
		if (copyout(fuk_data->fud_user, (void *)userbp, bcount)) {
			error = EFAULT;
			goto out;
		}
	}

out:
	/* release the vnode that we were looking up usage for */
	VN_RELE(fvp);

	/* release any allocated memory */
	if (fu_data)
		kmem_free(fu_data, fu_data_size(fu_data->fud_user_max));
	if (fuk_data)
		kmem_free(fuk_data, fu_data_size(fuk_data->fud_user_max));

	return (error);
}