root/usr/src/uts/common/syscall/utssys.c
/*
 * 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   */


#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 P_PR_LOCK
                 * 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 P_PR_LOCK
                         * 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, 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);
                }

                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 P_PR_LOCK
                 * 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);
}