This is xnu-10002.1.13. See this file in:
/*
 * Copyright (c) 2000-2019 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*
 * Copyright 1997,1998 Julian Elischer.  All rights reserved.
 * julian@freebsd.org
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * devfs_vnops.c
 */

/*
 * HISTORY
 *  Clark Warner (warner_c@apple.com) Tue Feb 10 2000
 *  - Added err_copyfile to the vnode operations table
 *  Dieter Siegmund (dieter@apple.com) Thu Apr  8 14:08:19 PDT 1999
 *  - instead of duplicating specfs here, created a vnode-ops table
 *    that redirects most operations to specfs (as is done with ufs);
 *  - removed routines that made no sense
 *  - cleaned up reclaim: replaced devfs_vntodn() with a macro VTODN()
 *  - cleaned up symlink, link locking
 *  - added the devfs_lock to protect devfs data structures against
 *    driver's calling devfs_add_devswf()/etc.
 *  Dieter Siegmund (dieter@apple.com) Wed Jul 14 13:37:59 PDT 1999
 *  - free the devfs devnode in devfs_inactive(), not just in devfs_reclaim()
 *    to free up kernel memory as soon as it's available
 *  - got rid of devfsspec_{read, write}
 *  Dieter Siegmund (dieter@apple.com) Fri Sep 17 09:58:38 PDT 1999
 *  - update the mod/access times
 */
/*
 * NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce
 * support for mandatory and extensible security protections.  This notice
 * is included in support of clause 2.2 (b) of the Apple Public License,
 * Version 2.0.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/kernel.h>
#include <sys/fcntl.h>
#include <sys/conf.h>
#include <sys/disklabel.h>
#include <sys/lock.h>
#include <sys/stat.h>
#include <sys/mount_internal.h>
#include <sys/proc.h>
#include <sys/kauth.h>
#include <sys/time.h>
#include <sys/vnode_internal.h>
#include <miscfs/specfs/specdev.h>
#include <sys/dirent.h>
#include <sys/vmmeter.h>
#include <sys/vm.h>
#include <sys/uio_internal.h>

#if CONFIG_MACF
#include <security/mac_framework.h>
#endif

#include "devfsdefs.h"
#include "devfs.h"

#if FDESC
#include "fdesc.h"
#endif /* FDESC */

static int              devfs_update(struct vnode *vp, struct timeval *access,
    struct timeval *modify);
void                    devfs_rele_node(devnode_t *);
static void             devfs_consider_time_update(devnode_t *dnp, uint32_t just_changed_flags);
static boolean_t        devfs_update_needed(long now_s, long last_s);
static boolean_t        devfs_is_name_protected(struct vnode *dvp, const char *name);
void                    dn_times_locked(devnode_t * dnp, struct timeval *t1, struct timeval *t2, struct timeval *t3, uint32_t just_changed_flags);
void                    dn_times_now(devnode_t *dnp, uint32_t just_changed_flags);
void                    dn_mark_for_delayed_times_update(devnode_t *dnp, uint32_t just_changed_flags);

void
dn_times_locked(devnode_t * dnp, struct timeval *t1, struct timeval *t2, struct timeval *t3, uint32_t just_changed_flags)
{
	lck_mtx_assert(&devfs_attr_mutex, LCK_MTX_ASSERT_OWNED);

	if (just_changed_flags & DEVFS_UPDATE_ACCESS) {
		dnp->dn_atime.tv_sec = t1->tv_sec;
		dnp->dn_atime.tv_nsec = t1->tv_usec * 1000;
		dnp->dn_access = 0;
	} else if (dnp->dn_access) {
		dnp->dn_atime.tv_sec = MIN(t1->tv_sec, dnp->dn_atime.tv_sec + DEVFS_LAZY_UPDATE_SECONDS);
		dnp->dn_atime.tv_nsec = t1->tv_usec * 1000;
		dnp->dn_access = 0;
	}

	if (just_changed_flags & DEVFS_UPDATE_MOD) {
		dnp->dn_mtime.tv_sec = t2->tv_sec;
		dnp->dn_mtime.tv_nsec = t2->tv_usec * 1000;
		dnp->dn_update = 0;
	} else if (dnp->dn_update) {
		dnp->dn_mtime.tv_sec = MIN(t2->tv_sec, dnp->dn_mtime.tv_sec + DEVFS_LAZY_UPDATE_SECONDS);
		dnp->dn_mtime.tv_nsec = t2->tv_usec * 1000;
		dnp->dn_update = 0;
	}

	if (just_changed_flags & DEVFS_UPDATE_CHANGE) {
		dnp->dn_ctime.tv_sec = t3->tv_sec;
		dnp->dn_ctime.tv_nsec = t3->tv_usec * 1000;
		dnp->dn_change = 0;
	} else if (dnp->dn_change) {
		dnp->dn_ctime.tv_sec = MIN(t3->tv_sec, dnp->dn_ctime.tv_sec + DEVFS_LAZY_UPDATE_SECONDS);
		dnp->dn_ctime.tv_nsec = t3->tv_usec * 1000;
		dnp->dn_change = 0;
	}
}

void
dn_mark_for_delayed_times_update(devnode_t *dnp, uint32_t just_changed_flags)
{
	if (just_changed_flags & DEVFS_UPDATE_CHANGE) {
		dnp->dn_change = 1;
	}
	if (just_changed_flags & DEVFS_UPDATE_ACCESS) {
		dnp->dn_access = 1;
	}
	if (just_changed_flags & DEVFS_UPDATE_MOD) {
		dnp->dn_update = 1;
	}
}

/*
 * Update times based on pending updates and optionally a set of new changes.
 */
void
dn_times_now(devnode_t * dnp, uint32_t just_changed_flags)
{
	struct timeval now;

	DEVFS_ATTR_LOCK_SPIN();
	microtime(&now);
	dn_times_locked(dnp, &now, &now, &now, just_changed_flags);
	DEVFS_ATTR_UNLOCK();
}

/*
 * Critical devfs devices cannot be renamed or removed.
 * However, links to them may be moved/unlinked. So we block
 * remove/rename on a per-name basis, rather than per-node.
 */
static boolean_t
devfs_is_name_protected(struct vnode *dvp, const char *name)
{
	/*
	 * Only names in root are protected. E.g. /dev/null is protected,
	 * but /dev/foo/null isn't.
	 */
	if (!vnode_isvroot(dvp)) {
		return FALSE;
	}

	if ((strcmp("console", name) == 0) ||
	    (strcmp("tty", name) == 0) ||
	    (strcmp("null", name) == 0) ||
	    (strcmp("zero", name) == 0) ||
	    (strcmp("klog", name) == 0)) {
		return TRUE;
	}

	return FALSE;
}


/*
 * Convert a component of a pathname into a pointer to a locked node.
 * This is a very central and rather complicated routine.
 * If the file system is not maintained in a strict tree hierarchy,
 * this can result in a deadlock situation (see comments in code below).
 *
 * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on
 * whether the name is to be looked up, created, renamed, or deleted.
 * When CREATE, RENAME, or DELETE is specified, information usable in
 * creating, renaming, or deleting a directory entry may be calculated.
 * If flag has LOCKPARENT or'ed into it and the target of the pathname
 * exists, lookup returns both the target and its parent directory locked.
 * When creating or renaming and LOCKPARENT is specified, the target may
 * not be ".".  When deleting and LOCKPARENT is specified, the target may
 * be "."., but the caller must check to ensure it does an vrele and DNUNLOCK
 * instead of two DNUNLOCKs.
 *
 * Overall outline of devfs_lookup:
 *
 *	check accessibility of directory
 *	null terminate the component (lookup leaves the whole string alone)
 *	look for name in cache, if found, then if at end of path
 *	  and deleting or creating, drop it, else return name
 *	search for name in directory, to found or notfound
 * notfound:
 *	if creating, return locked directory,
 *	else return error
 * found:
 *	if at end of path and deleting, return information to allow delete
 *	if at end of path and rewriting (RENAME and LOCKPARENT), lock target
 *	  node and return info to allow rewrite
 *	if not at end, add name to cache; if at end and neither creating
 *	  nor deleting, add name to cache
 * On return to lookup, remove the null termination we put in at the start.
 *
 * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent node unlocked.
 */
static int
devfs_lookup(struct vnop_lookup_args *ap)
/*struct vnop_lookup_args {
 *       struct vnode * a_dvp; directory vnode ptr
 *       struct vnode ** a_vpp; where to put the result
 *       struct componentname * a_cnp; the name we want
 *       vfs_context_t a_context;
 *  };*/
{
	struct componentname *cnp = ap->a_cnp;
	vfs_context_t ctx = cnp->cn_context;
	struct proc *p = vfs_context_proc(ctx);
	struct vnode *dir_vnode = ap->a_dvp;
	struct vnode **result_vnode = ap->a_vpp;
	devnode_t *   dir_node;       /* the directory we are searching */
	devnode_t *   node = NULL;       /* the node we are searching for */
	devdirent_t * nodename;
	int flags = cnp->cn_flags;
	int op = cnp->cn_nameiop;       /* LOOKUP, CREATE, RENAME, or DELETE */
	int wantparent = flags & (LOCKPARENT | WANTPARENT);
	int error = 0;
	char    heldchar;       /* the char at the end of the name componet */

retry:

	*result_vnode = NULL; /* safe not sorry */ /*XXX*/

	/*  okay to look at directory vnodes ourside devfs lock as they are not aliased */
	dir_node = VTODN(dir_vnode);

	/*
	 * Make sure that our node is a directory as well.
	 */
	if (dir_node->dn_type != DEV_DIR) {
		return ENOTDIR;
	}

	DEVFS_LOCK();
	/*
	 * temporarily terminate string component
	 */
	heldchar = cnp->cn_nameptr[cnp->cn_namelen];
	cnp->cn_nameptr[cnp->cn_namelen] = '\0';

	nodename = dev_findname(dir_node, cnp->cn_nameptr);
	/*
	 * restore saved character
	 */
	cnp->cn_nameptr[cnp->cn_namelen] = heldchar;

	if (nodename) {
		/* entry exists */
		node = nodename->de_dnp;

		/* Do potential vnode allocation here inside the lock
		 * to make sure that our device node has a non-NULL dn_vn
		 * associated with it.  The device node might otherwise
		 * get deleted out from under us (see devfs_dn_free()).
		 */
		error = devfs_dntovn(node, result_vnode, p);
	}
	DEVFS_UNLOCK();

	if (error) {
		if (error == EAGAIN) {
			goto retry;
		}
		return error;
	}
	if (!nodename) {
		/*
		 * we haven't called devfs_dntovn if we get here
		 * we have not taken a reference on the node.. no
		 * vnode_put is necessary on these error returns
		 *
		 * If it doesn't exist and we're not the last component,
		 * or we're at the last component, but we're not creating
		 * or renaming, return ENOENT.
		 */
		if (!(flags & ISLASTCN) || !(op == CREATE || op == RENAME)) {
			return ENOENT;
		}
		/*
		 * We return with the directory locked, so that
		 * the parameters we set up above will still be
		 * valid if we actually decide to add a new entry.
		 * We return ni_vp == NULL to indicate that the entry
		 * does not currently exist; we leave a pointer to
		 * the (locked) directory vnode in namei_data->ni_dvp.
		 *
		 * NB - if the directory is unlocked, then this
		 * information cannot be used.
		 */
		return EJUSTRETURN;
	}
	/*
	 * from this point forward, we need to vnode_put the reference
	 * picked up in devfs_dntovn if we decide to return an error
	 */

	/*
	 * If deleting, and at end of pathname, return
	 * parameters which can be used to remove file.
	 * If the wantparent flag isn't set, we return only
	 * the directory (in namei_data->ni_dvp), otherwise we go
	 * on and lock the node, being careful with ".".
	 */
	if (op == DELETE && (flags & ISLASTCN)) {
		/*
		 * we are trying to delete '.'.  What does this mean? XXX
		 */
		if (dir_node == node) {
			if (*result_vnode) {
				vnode_put(*result_vnode);
				*result_vnode = NULL;
			}
			if (((error = vnode_get(dir_vnode)) == 0)) {
				*result_vnode = dir_vnode;
			}
			return error;
		}
		return 0;
	}

	/*
	 * If rewriting (RENAME), return the vnode and the
	 * information required to rewrite the present directory
	 * Must get node of directory entry to verify it's a
	 * regular file, or empty directory.
	 */
	if (op == RENAME && wantparent && (flags & ISLASTCN)) {
		/*
		 * Careful about locking second node.
		 * This can only occur if the target is ".".
		 */
		if (dir_node == node) {
			error = EISDIR;
			goto drop_ref;
		}
		return 0;
	}

	/*
	 * Step through the translation in the name.  We do not unlock the
	 * directory because we may need it again if a symbolic link
	 * is relative to the current directory.  Instead we save it
	 * unlocked as "saved_dir_node" XXX.  We must get the target
	 * node before unlocking
	 * the directory to insure that the node will not be removed
	 * before we get it.  We prevent deadlock by always fetching
	 * nodes from the root, moving down the directory tree. Thus
	 * when following backward pointers ".." we must unlock the
	 * parent directory before getting the requested directory.
	 * There is a potential race condition here if both the current
	 * and parent directories are removed before the lock for the
	 * node associated with ".." returns.  We hope that this occurs
	 * infrequently since we cannot avoid this race condition without
	 * implementing a sophisticated deadlock detection algorithm.
	 * Note also that this simple deadlock detection scheme will not
	 * work if the file system has any hard links other than ".."
	 * that point backwards in the directory structure.
	 */
	if ((flags & ISDOTDOT) == 0 && dir_node == node) {
		if (*result_vnode) {
			vnode_put(*result_vnode);
			*result_vnode = NULL;
		}
		if ((error = vnode_get(dir_vnode))) {
			return error;
		}
		*result_vnode = dir_vnode;
	}
	return 0;

drop_ref:
	if (*result_vnode) {
		vnode_put(*result_vnode);
		*result_vnode = NULL;
	}
	return error;
}

static int
devfs_getattr(struct vnop_getattr_args *ap)
/*struct vnop_getattr_args {
 *       struct vnode *a_vp;
 *       struct vnode_attr *a_vap;
 *       kauth_cred_t a_cred;
 *       struct proc *a_p;
 *  } */
{
	struct vnode *vp = ap->a_vp;
	struct vnode_attr *vap = ap->a_vap;
	devnode_t *     file_node;
	struct timeval now;


	DEVFS_LOCK();
	file_node = VTODN(vp);

	VATTR_RETURN(vap, va_mode, file_node->dn_mode);

	/*
	 * Note: for DEV_CDEV and DEV_BDEV, we return the device from
	 * the vp, not the file_node; if we getting information on a
	 * cloning device, we want the cloned information, not the template.
	 */
	switch (file_node->dn_type) {
	case    DEV_DIR:
#if FDESC
	case    DEV_DEVFD:      /* Like a directory */
#endif /* FDESC */
		VATTR_RETURN(vap, va_rdev, 0);
		vap->va_mode |= (S_IFDIR);
		break;
	case    DEV_CDEV:
		VATTR_RETURN(vap, va_rdev, vp->v_rdev);
		vap->va_mode |= (S_IFCHR);
		break;
	case    DEV_BDEV:
		VATTR_RETURN(vap, va_rdev, vp->v_rdev);
		vap->va_mode |= (S_IFBLK);
		break;
	case    DEV_SLNK:
		VATTR_RETURN(vap, va_rdev, 0);
		vap->va_mode |= (S_IFLNK);
		break;
	default:
		VATTR_RETURN(vap, va_rdev, 0);  /* default value only */
	}
	VATTR_RETURN(vap, va_type, vp->v_type);
	VATTR_RETURN(vap, va_nlink, file_node->dn_links);
	VATTR_RETURN(vap, va_uid, file_node->dn_uid);
	VATTR_RETURN(vap, va_gid, file_node->dn_gid);
	VATTR_RETURN(vap, va_fsid, (uint32_t)VM_KERNEL_ADDRHASH(file_node->dn_dvm));
	VATTR_RETURN(vap, va_fileid, (uintptr_t)file_node->dn_ino);
	VATTR_RETURN(vap, va_data_size, file_node->dn_len);

	/* return an override block size (advisory) */
	if (vp->v_type == VBLK) {
		VATTR_RETURN(vap, va_iosize, BLKDEV_IOSIZE);
	} else if (vp->v_type == VCHR) {
		VATTR_RETURN(vap, va_iosize, MAXPHYSIO);
	} else {
		VATTR_RETURN(vap, va_iosize, (uint32_t)vp->v_mount->mnt_vfsstat.f_iosize);
	}


	DEVFS_ATTR_LOCK_SPIN();

	microtime(&now);
	dn_times_locked(file_node, &now, &now, &now, 0);

	/* if the time is bogus, set it to the boot time */
	if (file_node->dn_ctime.tv_sec == 0) {
		file_node->dn_ctime.tv_sec = boottime_sec();
		file_node->dn_ctime.tv_nsec = 0;
	}
	if (file_node->dn_mtime.tv_sec == 0) {
		file_node->dn_mtime = file_node->dn_ctime;
	}
	if (file_node->dn_atime.tv_sec == 0) {
		file_node->dn_atime = file_node->dn_ctime;
	}
	VATTR_RETURN(vap, va_change_time, file_node->dn_ctime);
	VATTR_RETURN(vap, va_modify_time, file_node->dn_mtime);
	VATTR_RETURN(vap, va_access_time, file_node->dn_atime);

	DEVFS_ATTR_UNLOCK();

	VATTR_RETURN(vap, va_gen, 0);
	VATTR_RETURN(vap, va_filerev, 0);
	VATTR_RETURN(vap, va_acl, NULL);

	/* Hide the root so Finder doesn't display it */
	if (vnode_isvroot(vp)) {
		VATTR_RETURN(vap, va_flags, UF_HIDDEN);
	} else {
		VATTR_RETURN(vap, va_flags, 0);
	}

	DEVFS_UNLOCK();

	return 0;
}

static int
devfs_setattr(struct vnop_setattr_args *ap)
/*struct vnop_setattr_args  {
 *  struct vnode *a_vp;
 *  struct vnode_attr *a_vap;
 *  vfs_context_t a_context;
 *  } */
{
	struct vnode *vp = ap->a_vp;
	struct vnode_attr *vap = ap->a_vap;
	int error = 0;
	devnode_t *     file_node;
	struct timeval atimeval, mtimeval;

	DEVFS_LOCK();

	file_node = VTODN(vp);
	/*
	 * Go through the fields and update if set.
	 */
	if (VATTR_IS_ACTIVE(vap, va_access_time) || VATTR_IS_ACTIVE(vap, va_modify_time)) {
		if (VATTR_IS_ACTIVE(vap, va_access_time)) {
			file_node->dn_access = 1;
		}
		if (VATTR_IS_ACTIVE(vap, va_modify_time)) {
			file_node->dn_change = 1;
			file_node->dn_update = 1;
		}
		atimeval.tv_sec = vap->va_access_time.tv_sec;
		atimeval.tv_usec = (suseconds_t)(vap->va_access_time.tv_nsec / 1000);
		mtimeval.tv_sec = vap->va_modify_time.tv_sec;
		mtimeval.tv_usec = (suseconds_t)(vap->va_modify_time.tv_nsec / 1000);

		if ((error = devfs_update(vp, &atimeval, &mtimeval))) {
			goto exit;
		}
	}
	VATTR_SET_SUPPORTED(vap, va_access_time);
	VATTR_SET_SUPPORTED(vap, va_change_time);

	/*
	 * Change the permissions.
	 */
	if (VATTR_IS_ACTIVE(vap, va_mode)) {
		file_node->dn_mode &= ~07777;
		file_node->dn_mode |= vap->va_mode & 07777;
	}
	VATTR_SET_SUPPORTED(vap, va_mode);

	/*
	 * Change the owner.
	 */
	if (VATTR_IS_ACTIVE(vap, va_uid)) {
		file_node->dn_uid = vap->va_uid;
	}
	VATTR_SET_SUPPORTED(vap, va_uid);

	/*
	 * Change the group.
	 */
	if (VATTR_IS_ACTIVE(vap, va_gid)) {
		file_node->dn_gid = vap->va_gid;
	}
	VATTR_SET_SUPPORTED(vap, va_gid);
exit:
	DEVFS_UNLOCK();

	return error;
}

#if CONFIG_MACF
static int
devfs_setlabel(struct vnop_setlabel_args *ap)
/* struct vnop_setlabel_args {
 *               struct vnodeop_desc *a_desc;
 *               struct vnode *a_vp;
 *               struct label *a_vl;
 *       vfs_context_t a_context;
 *       } */
{
	struct vnode *vp;
	struct devnode *de;

	vp = ap->a_vp;
	de = VTODN(vp);

	mac_vnode_label_update(ap->a_context, vp, ap->a_vl);
	mac_devfs_label_update(vp->v_mount, de, vp);

	return 0;
}
#endif

static int
devfs_read(struct vnop_read_args *ap)
/* struct vnop_read_args {
 *       struct vnode *a_vp;
 *       struct uio *a_uio;
 *       int  a_ioflag;
 *       vfs_context_t a_context;
 *  } */
{
	devnode_t * dn_p = VTODN(ap->a_vp);

	switch (ap->a_vp->v_type) {
	case VDIR: {
		dn_p->dn_access = 1;

		return VNOP_READDIR(ap->a_vp, ap->a_uio, 0, NULL, NULL, ap->a_context);
	}
	default: {
		printf("devfs_read(): bad file type %d", ap->a_vp->v_type);
		return EINVAL;
	}
	}
}

static int
devfs_close(struct vnop_close_args *ap)
/* struct vnop_close_args {
 *       struct vnode *a_vp;
 *       int  a_fflag;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnode *          vp = ap->a_vp;
	devnode_t *     dnp;

	if (vnode_isinuse(vp, 1)) {
		DEVFS_LOCK();
		dnp = VTODN(vp);
		if (dnp) {
			dn_times_now(dnp, 0);
		}
		DEVFS_UNLOCK();
	}
	return 0;
}

static int
devfsspec_close(struct vnop_close_args *ap)
/* struct vnop_close_args {
 *       struct vnode *a_vp;
 *       int  a_fflag;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnode *          vp = ap->a_vp;
	devnode_t *     dnp;

	if (vnode_isinuse(vp, 0)) {
		DEVFS_LOCK();
		dnp = VTODN(vp);
		if (dnp) {
			dn_times_now(dnp, 0);
		}
		DEVFS_UNLOCK();
	}

	return VOCALL(spec_vnodeop_p, VOFFSET(vnop_close), ap);
}

static boolean_t
devfs_update_needed(long now_s, long last_s)
{
	if (now_s > last_s) {
		if (now_s - last_s >= DEVFS_LAZY_UPDATE_SECONDS) {
			return TRUE;
		}
	}

	return FALSE;
}

/*
 * Given a set of time updates required [to happen at some point], check
 * either make those changes (and resolve other pending updates) or mark
 * the devnode for a subsequent update.
 */
static void
devfs_consider_time_update(devnode_t *dnp, uint32_t just_changed_flags)
{
	struct timeval          now;
	long now_s;

	microtime(&now);
	now_s = now.tv_sec;

	if (dnp->dn_change || (just_changed_flags & DEVFS_UPDATE_CHANGE)) {
		if (devfs_update_needed(now_s, dnp->dn_ctime.tv_sec)) {
			dn_times_now(dnp, just_changed_flags);
			return;
		}
	}
	if (dnp->dn_access || (just_changed_flags & DEVFS_UPDATE_ACCESS)) {
		if (devfs_update_needed(now_s, dnp->dn_atime.tv_sec)) {
			dn_times_now(dnp, just_changed_flags);
			return;
		}
	}
	if (dnp->dn_update || (just_changed_flags & DEVFS_UPDATE_MOD)) {
		if (devfs_update_needed(now_s, dnp->dn_mtime.tv_sec)) {
			dn_times_now(dnp, just_changed_flags);
			return;
		}
	}

	/* Not going to do anything now--mark for later update */
	dn_mark_for_delayed_times_update(dnp, just_changed_flags);

	return;
}

static int
devfsspec_read(struct vnop_read_args *ap)
/* struct vnop_read_args {
 *       struct vnode *a_vp;
 *       struct uio *a_uio;
 *       int  a_ioflag;
 *       kauth_cred_t a_cred;
 *  } */
{
	devnode_t *     dnp = VTODN(ap->a_vp);

	devfs_consider_time_update(dnp, DEVFS_UPDATE_ACCESS);

	return VOCALL(spec_vnodeop_p, VOFFSET(vnop_read), ap);
}

static int
devfsspec_write(struct vnop_write_args *ap)
/* struct vnop_write_args  {
 *       struct vnode *a_vp;
 *       struct uio *a_uio;
 *       int  a_ioflag;
 *       vfs_context_t a_context;
 *  } */
{
	devnode_t *     dnp = VTODN(ap->a_vp);

	devfs_consider_time_update(dnp, DEVFS_UPDATE_CHANGE | DEVFS_UPDATE_MOD);

	return VOCALL(spec_vnodeop_p, VOFFSET(vnop_write), ap);
}

/*
 *  Write data to a file or directory.
 */
static int
devfs_write(struct vnop_write_args *ap)
/* struct vnop_write_args  {
 *       struct vnode *a_vp;
 *       struct uio *a_uio;
 *       int  a_ioflag;
 *       kauth_cred_t a_cred;
 *  } */
{
	switch (ap->a_vp->v_type) {
	case VDIR:
		return EISDIR;
	default:
		printf("devfs_write(): bad file type %d", ap->a_vp->v_type);
		return EINVAL;
	}
}

/*
 * Deviates from UFS naming convention because there is a KPI function
 * called devfs_remove().
 */
static int
devfs_vnop_remove(struct vnop_remove_args *ap)
/* struct vnop_remove_args  {
 *       struct vnode *a_dvp;
 *       struct vnode *a_vp;
 *       struct componentname *a_cnp;
 *  } */
{
	struct vnode *vp = ap->a_vp;
	struct vnode *dvp = ap->a_dvp;
	struct componentname *cnp = ap->a_cnp;
	devnode_t *  tp;
	devnode_t *  tdp;
	devdirent_t * tnp;
	int doingdirectory = 0;
	int error = 0;

	/*
	 * assume that the name is null terminated as they
	 * are the end of the path. Get pointers to all our
	 * devfs structures.
	 */

	DEVFS_LOCK();

	tp = VTODN(vp);
	tdp = VTODN(dvp);


	tnp = dev_findname(tdp, cnp->cn_nameptr);

	if (tnp == NULL) {
		error = ENOENT;
		goto abort;
	}

	/*
	 * Don't allow removing critical devfs devices
	 */
	if (devfs_is_name_protected(dvp, cnp->cn_nameptr)) {
		error = EINVAL;
		goto abort;
	}

	/*
	 * Make sure that we don't try do something stupid
	 */
	if ((tp->dn_type) == DEV_DIR) {
		/*
		 * Avoid ".", "..", and aliases of "." for obvious reasons.
		 */
		if ((cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.')
		    || (cnp->cn_flags & ISDOTDOT)) {
			error = EINVAL;
			goto abort;
		}
		doingdirectory++;
	}

	/***********************************
	* Start actually doing things.... *
	***********************************/
	devfs_consider_time_update(tdp, DEVFS_UPDATE_CHANGE | DEVFS_UPDATE_MOD);

	/*
	 * Target must be empty if a directory and have no links
	 * to it. Also, ensure source and target are compatible
	 * (both directories, or both not directories).
	 */
	if ((doingdirectory) && (tp->dn_links > 2)) {
		error = ENOTEMPTY;
		goto abort;
	}
	dev_free_name(tnp);
abort:
	DEVFS_UNLOCK();

	return error;
}

/*
 */
static int
devfs_link(struct vnop_link_args *ap)
/*struct vnop_link_args  {
 *       struct vnode *a_tdvp;
 *       struct vnode *a_vp;
 *       struct componentname *a_cnp;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnode *vp = ap->a_vp;
	struct vnode *tdvp = ap->a_tdvp;
	struct componentname *cnp = ap->a_cnp;
	devnode_t * fp;
	devnode_t * tdp;
	devdirent_t * tnp;
	int error = 0;

	/*
	 * First catch an arbitrary restriction for this FS
	 */
	if (cnp->cn_namelen > DEVMAXNAMESIZE) {
		error = ENAMETOOLONG;
		goto out1;
	}

	/*
	 * Lock our directories and get our name pointers
	 * assume that the names are null terminated as they
	 * are the end of the path. Get pointers to all our
	 * devfs structures.
	 */
	/* can lookup dnode safely for tdvp outside of devfs lock as it is not aliased */
	tdp = VTODN(tdvp);

	if (tdvp->v_mount != vp->v_mount) {
		return EXDEV;
	}
	DEVFS_LOCK();

	fp = VTODN(vp);

	/***********************************
	* Start actually doing things.... *
	***********************************/
	dn_times_now(fp, DEVFS_UPDATE_CHANGE);

	if (!error) {
		error = dev_add_name(cnp->cn_nameptr, tdp, NULL, fp, &tnp);
	}
out1:
	DEVFS_UNLOCK();

	return error;
}

/*
 * Rename system call. Seems overly complicated to me...
 *      rename("foo", "bar");
 * is essentially
 *	unlink("bar");
 *	link("foo", "bar");
 *	unlink("foo");
 * but ``atomically''.
 *
 * When the target exists, both the directory
 * and target vnodes are locked.
 * the source and source-parent vnodes are referenced
 *
 *
 * Basic algorithm is:
 *
 * 1) Bump link count on source while we're linking it to the
 *    target.  This also ensure the inode won't be deleted out
 *    from underneath us while we work (it may be truncated by
 *    a concurrent `trunc' or `open' for creation).
 * 2) Link source to destination.  If destination already exists,
 *    delete it first.
 * 3) Unlink source reference to node if still around. If a
 *    directory was moved and the parent of the destination
 *    is different from the source, patch the ".." entry in the
 *    directory.
 */
static int
devfs_rename(struct vnop_rename_args *ap)
/*struct vnop_rename_args  {
 *       struct vnode *a_fdvp;
 *       struct vnode *a_fvp;
 *       struct componentname *a_fcnp;
 *       struct vnode *a_tdvp;
 *       struct vnode *a_tvp;
 *       struct componentname *a_tcnp;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnode *tvp = ap->a_tvp;
	struct vnode *tdvp = ap->a_tdvp;
	struct vnode *fvp = ap->a_fvp;
	struct vnode *fdvp = ap->a_fdvp;
	struct componentname *tcnp = ap->a_tcnp;
	struct componentname *fcnp = ap->a_fcnp;
	devnode_t *fp, *fdp, *tp, *tdp;
	devdirent_t *fnp, *tnp;
	int doingdirectory = 0;
	int error = 0;

	DEVFS_LOCK();
	/*
	 * First catch an arbitrary restriction for this FS
	 */
	if (tcnp->cn_namelen > DEVMAXNAMESIZE) {
		error = ENAMETOOLONG;
		goto out;
	}

	/*
	 * assume that the names are null terminated as they
	 * are the end of the path. Get pointers to all our
	 * devfs structures.
	 */
	tdp = VTODN(tdvp);
	fdp = VTODN(fdvp);
	fp = VTODN(fvp);

	fnp = dev_findname(fdp, fcnp->cn_nameptr);

	if (fnp == NULL) {
		error = ENOENT;
		goto out;
	}
	tp = NULL;
	tnp = NULL;

	if (tvp) {
		tnp = dev_findname(tdp, tcnp->cn_nameptr);

		if (tnp == NULL) {
			error = ENOENT;
			goto out;
		}
		tp = VTODN(tvp);
	}

	/*
	 * Make sure that we don't try do something stupid
	 */
	if ((fp->dn_type) == DEV_DIR) {
		/*
		 * Avoid ".", "..", and aliases of "." for obvious reasons.
		 */
		if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.')
		    || (fcnp->cn_flags & ISDOTDOT)
		    || (tcnp->cn_namelen == 1 && tcnp->cn_nameptr[0] == '.')
		    || (tcnp->cn_flags & ISDOTDOT)
		    || (tdp == fp)) {
			error = EINVAL;
			goto out;
		}
		doingdirectory++;
	}

	/*
	 * Don't allow renaming critical devfs devices
	 */
	if (devfs_is_name_protected(fdvp, fcnp->cn_nameptr) ||
	    devfs_is_name_protected(tdvp, tcnp->cn_nameptr)) {
		error = EINVAL;
		goto out;
	}

	/*
	 * If ".." must be changed (ie the directory gets a new
	 * parent) then the source directory must not be in the
	 * directory hierarchy above the target, as this would
	 * orphan everything below the source directory. Also
	 * the user must have write permission in the source so
	 * as to be able to change "..".
	 */
	if (doingdirectory && (tdp != fdp)) {
		devnode_t * tmp, *ntmp;
		tmp = tdp;
		do {
			if (tmp == fp) {
				/* XXX unlock stuff here probably */
				error = EINVAL;
				goto out;
			}
			ntmp = tmp;
		} while ((tmp = tmp->dn_typeinfo.Dir.parent) != ntmp);
	}

	/***********************************
	* Start actually doing things.... *
	***********************************/
	dn_times_now(fp, DEVFS_UPDATE_CHANGE);

	/*
	 * Check if just deleting a link name.
	 */
	if (fvp == tvp) {
		if (fvp->v_type == VDIR) {
			error = EINVAL;
			goto out;
		}
		/* Release destination completely. */
		dev_free_name(fnp);

		DEVFS_UNLOCK();
		return 0;
	}
	/*
	 * 1) Bump link count while we're moving stuff
	 *    around.  If we crash somewhere before
	 *    completing our work,  too bad :)
	 */
	fp->dn_links++;
	/*
	 * If the target exists zap it (unless it's a non-empty directory)
	 * We could do that as well but won't
	 */
	if (tp) {
		/*
		 * Target must be empty if a directory and have no links
		 * to it. Also, ensure source and target are compatible
		 * (both directories, or both not directories).
		 */
		if ((doingdirectory) && (tp->dn_links > 2)) {
			error = ENOTEMPTY;
			goto bad;
		}
		dev_free_name(tnp);
		tp = NULL;
	}
	dev_add_name(tcnp->cn_nameptr, tdp, NULL, fp, &tnp);
	fnp->de_dnp = NULL;
	fp->dn_links--; /* one less link to it.. */

	dev_free_name(fnp);
bad:
	fp->dn_links--; /* we added one earlier*/
out:
	DEVFS_UNLOCK();
	return error;
}

static int
devfs_mkdir(struct vnop_mkdir_args *ap)
/*struct vnop_mkdir_args {
 *       struct vnode *a_dvp;
 *       struct vnode **a_vpp;
 *       struct componentname *a_cnp;
 *       struct vnode_attr *a_vap;
 *       vfs_context_t a_context;
 *  } */
{
	struct componentname * cnp = ap->a_cnp;
	vfs_context_t ctx = cnp->cn_context;
	struct proc *p = vfs_context_proc(ctx);
	int error = 0;
	devnode_t * dir_p;
	devdirent_t * nm_p;
	devnode_t * dev_p;
	struct vnode_attr *     vap = ap->a_vap;
	struct vnode * * vpp = ap->a_vpp;

	DEVFS_LOCK();

	dir_p = VTODN(ap->a_dvp);
	error = dev_add_entry(cnp->cn_nameptr, dir_p, DEV_DIR,
	    NULL, NULL, NULL, &nm_p);
	if (error) {
		goto failure;
	}
	dev_p = nm_p->de_dnp;
	dev_p->dn_uid = dir_p->dn_uid;
	dev_p->dn_gid = dir_p->dn_gid;
	dev_p->dn_mode = vap->va_mode;
	dn_copy_times(dev_p, dir_p);

	error = devfs_dntovn(dev_p, vpp, p);
failure:
	DEVFS_UNLOCK();

	return error;
}

/*
 * An rmdir is a special type of remove, which we already support; we wrap
 * and reexpress the arguments to call devfs_remove directly.  The only
 * different argument is flags, which we do not set, since it's ignored.
 */
static int
devfs_rmdir(struct vnop_rmdir_args *ap)
/* struct vnop_rmdir_args {
 *       struct vnode *a_dvp;
 *       struct vnode *a_vp;
 *       struct componentname *a_cnp;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnop_remove_args ra;

	ra.a_dvp = ap->a_dvp;
	ra.a_vp = ap->a_vp;
	ra.a_cnp = ap->a_cnp;
	ra.a_flags = 0;         /* XXX */
	ra.a_context = ap->a_context;

	return devfs_vnop_remove(&ra);
}


static int
devfs_symlink(struct vnop_symlink_args *ap)
/*struct vnop_symlink_args {
 *       struct vnode *a_dvp;
 *       struct vnode **a_vpp;
 *       struct componentname *a_cnp;
 *       struct vnode_attr *a_vap;
 *       char *a_target;
 *       vfs_context_t a_context;
 *  } */
{
	int error;
	devdirent_t *newent;

	DEVFS_LOCK();
	error = devfs_make_symlink(VTODN(ap->a_dvp), ap->a_cnp->cn_nameptr, ap->a_vap->va_mode, ap->a_target, &newent);

	if (error == 0) {
		error = devfs_dntovn(newent->de_dnp, ap->a_vpp, vfs_context_proc(ap->a_context));
	}

	DEVFS_UNLOCK();

	return error;
}

/* Called with devfs locked */
int
devfs_make_symlink(devnode_t *dir_p, char *name, mode_t mode, char *target, devdirent_t **newent)
{
	int error = 0;
	devnode_type_t typeinfo;
	devdirent_t * nm_p;
	devnode_t * dev_p;

	typeinfo.Slnk.name = target;
	typeinfo.Slnk.namelen = strlen(target);

	error = dev_add_entry(name, dir_p, DEV_SLNK,
	    &typeinfo, NULL, NULL, &nm_p);
	if (error) {
		goto failure;
	}
	dev_p = nm_p->de_dnp;
	dev_p->dn_uid = dir_p->dn_uid;
	dev_p->dn_gid = dir_p->dn_gid;
	dev_p->dn_mode = mode;
	dn_copy_times(dev_p, dir_p);

	if (newent) {
		*newent = nm_p;
	}

failure:

	return error;
}

/*
 * Mknod vnode call
 */
static int
devfs_mknod(struct vnop_mknod_args *ap)
/* struct vnop_mknod_args {
 *       struct vnode *a_dvp;
 *       struct vnode **a_vpp;
 *       struct componentname *a_cnp;
 *       struct vnode_attr *a_vap;
 *       vfs_context_t a_context;
 *  } */
{
	struct componentname * cnp = ap->a_cnp;
	vfs_context_t ctx = cnp->cn_context;
	struct proc *p = vfs_context_proc(ctx);
	devnode_t *     dev_p;
	devdirent_t *   devent;
	devnode_t *     dir_p;  /* devnode for parent directory */
	struct vnode *  dvp = ap->a_dvp;
	int             error = 0;
	devnode_type_t  typeinfo;
	struct vnode_attr *     vap = ap->a_vap;
	struct vnode ** vpp = ap->a_vpp;

	*vpp = NULL;
	if (!(vap->va_type == VBLK) && !(vap->va_type == VCHR)) {
		return EINVAL; /* only support mknod of special files */
	}
	typeinfo.dev = vap->va_rdev;

	DEVFS_LOCK();

	dir_p = VTODN(dvp);

	error = dev_add_entry(cnp->cn_nameptr, dir_p,
	    (vap->va_type == VBLK) ? DEV_BDEV : DEV_CDEV,
	    &typeinfo, NULL, NULL, &devent);
	if (error) {
		goto failure;
	}
	dev_p = devent->de_dnp;
	error = devfs_dntovn(dev_p, vpp, p);
	if (error) {
		goto failure;
	}
	dev_p->dn_uid = vap->va_uid;
	dev_p->dn_gid = vap->va_gid;
	dev_p->dn_mode = vap->va_mode;
	VATTR_SET_SUPPORTED(vap, va_uid);
	VATTR_SET_SUPPORTED(vap, va_gid);
	VATTR_SET_SUPPORTED(vap, va_mode);
failure:
	DEVFS_UNLOCK();

	return error;
}

/*
 * Vnode op for readdir
 */
static int
devfs_readdir(struct vnop_readdir_args *ap)
/*struct vnop_readdir_args {
 *       struct vnode *a_vp;
 *       struct uio *a_uio;
 *       int a_flags;
 *       int *a_eofflag;
 *       int *a_numdirent;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnode *vp = ap->a_vp;
	struct uio *uio = ap->a_uio;
	struct dirent dirent;
	devnode_t * dir_node;
	devdirent_t *   name_node;
	const char *name;
	int error = 0;
	int reclen;
	int nodenumber;
	off_t startpos, pos;

	if (ap->a_flags & (VNODE_READDIR_EXTENDED | VNODE_READDIR_REQSEEKOFF)) {
		return EINVAL;
	}

	/*  set up refs to dir */
	dir_node = VTODN(vp);
	if (dir_node->dn_type != DEV_DIR) {
		return ENOTDIR;
	}
	pos = 0;
	startpos = uio->uio_offset;

	DEVFS_LOCK();

	name_node = dir_node->dn_typeinfo.Dir.dirlist;
	nodenumber = 0;

	while ((name_node || (nodenumber < 2)) && (uio_resid(uio) > 0)) {
		switch (nodenumber) {
		case    0:
			dirent.d_fileno = dir_node->dn_ino;
			name = ".";
			dirent.d_namlen = 1;
			dirent.d_type = DT_DIR;
			break;
		case    1:
			if (dir_node->dn_typeinfo.Dir.parent) {
				dirent.d_fileno = dir_node->dn_typeinfo.Dir.parent->dn_ino;
			} else {
				dirent.d_fileno = dir_node->dn_ino;
			}
			name = "..";
			dirent.d_namlen = 2;
			dirent.d_type = DT_DIR;
			break;
		default:
			dirent.d_fileno = name_node->de_dnp->dn_ino;
			dirent.d_namlen = (__uint8_t) strlen(name_node->de_name);
			name = name_node->de_name;
			switch (name_node->de_dnp->dn_type) {
			case DEV_BDEV:
				dirent.d_type = DT_BLK;
				break;
			case DEV_CDEV:
				dirent.d_type = DT_CHR;
				break;
			case DEV_DIR:
				dirent.d_type = DT_DIR;
				break;
			case DEV_SLNK:
				dirent.d_type = DT_LNK;
				break;
			default:
				dirent.d_type = DT_UNKNOWN;
			}
		}
#define GENERIC_DIRSIZ(dp) \
    ((sizeof (struct dirent) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3))

		reclen = dirent.d_reclen = GENERIC_DIRSIZ(&dirent);

		if (pos >= startpos) {   /* made it to the offset yet? */
			if (uio_resid(uio) < reclen) { /* will it fit? */
				break;
			}
			strlcpy(dirent.d_name, name, DEVMAXNAMESIZE);
			if ((error = uiomove((caddr_t)&dirent,
			    dirent.d_reclen, uio)) != 0) {
				break;
			}
		}
		pos += reclen;
		if ((nodenumber > 1) && name_node) {
			name_node = name_node->de_next;
		}
		nodenumber++;
	}
	DEVFS_UNLOCK();
	uio->uio_offset = pos;

	devfs_consider_time_update(dir_node, DEVFS_UPDATE_ACCESS);

	return error;
}


/*
 */
static int
devfs_readlink(struct vnop_readlink_args *ap)
/*struct vnop_readlink_args {
 *       struct vnode *a_vp;
 *       struct uio *a_uio;
 *       vfs_context_t a_context;
 *  } */
{
	struct vnode *vp = ap->a_vp;
	struct uio *uio = ap->a_uio;
	devnode_t * lnk_node;
	int error = 0;

	/*  set up refs to dir */
	lnk_node = VTODN(vp);

	if (lnk_node->dn_type != DEV_SLNK) {
		error = EINVAL;
		goto out;
	}
	error = uiomove(lnk_node->dn_typeinfo.Slnk.name,
	    (int)lnk_node->dn_typeinfo.Slnk.namelen, uio);
out:
	return error;
}

static int
devfs_reclaim(struct vnop_reclaim_args *ap)
/*struct vnop_reclaim_args {
 *       struct vnode *a_vp;
 *  } */
{
	struct vnode *      vp = ap->a_vp;
	devnode_t *         dnp;

	DEVFS_LOCK();

	dnp = VTODN(vp);

	if (dnp) {
		/* If this is a cloning device, it didn't have a dn_vn anyway */
		dnp->dn_vn = NULL;
		vnode_clearfsnode(vp);

		/* This could delete the node, if we are the last vnode */
		devfs_rele_node(dnp);
	}
	DEVFS_UNLOCK();

	return 0;
}


/*
 * Get configurable pathname variables.
 */
static int
devs_vnop_pathconf(
	struct vnop_pathconf_args /* {
                                   *  struct vnode *a_vp;
                                   *  int a_name;
                                   *  int *a_retval;
                                   *  vfs_context_t a_context;
                                   *  } */*ap)
{
	switch (ap->a_name) {
	case _PC_LINK_MAX:
		/* arbitrary limit matching HFS; devfs has no hard limit */
		*ap->a_retval = 32767;
		break;
	case _PC_NAME_MAX:
		*ap->a_retval = DEVMAXNAMESIZE - 1;     /* includes NUL */
		break;
	case _PC_PATH_MAX:
		*ap->a_retval = DEVMAXPATHSIZE - 1;     /* XXX nonconformant */
		break;
	case _PC_CHOWN_RESTRICTED:
		*ap->a_retval = 200112;         /* _POSIX_CHOWN_RESTRICTED */
		break;
	case _PC_NO_TRUNC:
		*ap->a_retval = 0;
		break;
	case _PC_CASE_SENSITIVE:
		*ap->a_retval = 1;
		break;
	case _PC_CASE_PRESERVING:
		*ap->a_retval = 1;
		break;
	default:
		return EINVAL;
	}

	return 0;
}



/**************************************************************************\
* pseudo ops *
\**************************************************************************/

/*
 *
 *	struct vnop_inactive_args {
 *		struct vnode *a_vp;
 *		vfs_context_t a_context;
 *	}
 */

static int
devfs_inactive(__unused struct vnop_inactive_args *ap)
{
	vnode_t vp = ap->a_vp;
	devnode_t *dnp = VTODN(vp);

	/*
	 * Cloned vnodes are not linked in anywhere, so they
	 * can just be recycled.
	 */
	if (dnp->dn_clone != NULL) {
		vnode_recycle(vp);
	}

	return 0;
}

/*
 * called with DEVFS_LOCK held
 */
static int
devfs_update(struct vnode *vp, struct timeval *access, struct timeval *modify)
{
	devnode_t * ip;
	struct timeval now;

	ip = VTODN(vp);
	if (vp->v_mount->mnt_flag & MNT_RDONLY) {
		ip->dn_access = 0;
		ip->dn_change = 0;
		ip->dn_update = 0;

		return 0;
	}

	DEVFS_ATTR_LOCK_SPIN();
	microtime(&now);
	dn_times_locked(ip, access, modify, &now, DEVFS_UPDATE_ACCESS | DEVFS_UPDATE_MOD);
	DEVFS_ATTR_UNLOCK();

	return 0;
}

#define VOPFUNC int (*)(void *)

#define devfs_default_error (void (*)(void))vn_default_error

/* The following ops are used by directories and symlinks */
int(**devfs_vnodeop_p)(void *);
const static struct vnodeopv_entry_desc devfs_vnodeop_entries[] = {
	{ .opve_op = &vnop_default_desc, .opve_impl = (VOPFUNC)devfs_default_error },
	{ .opve_op = &vnop_lookup_desc, .opve_impl = (VOPFUNC)devfs_lookup },           /* lookup */
	{ .opve_op = &vnop_create_desc, .opve_impl = (VOPFUNC)err_create },             /* create */
	{ .opve_op = &vnop_whiteout_desc, .opve_impl = (VOPFUNC)err_whiteout },         /* whiteout */
	{ .opve_op = &vnop_mknod_desc, .opve_impl = (VOPFUNC)devfs_mknod },             /* mknod */
	{ .opve_op = &vnop_open_desc, .opve_impl = (VOPFUNC)nop_open },                 /* open */
	{ .opve_op = &vnop_close_desc, .opve_impl = (VOPFUNC)devfs_close },             /* close */
	{ .opve_op = &vnop_getattr_desc, .opve_impl = (VOPFUNC)devfs_getattr },         /* getattr */
	{ .opve_op = &vnop_setattr_desc, .opve_impl = (VOPFUNC)devfs_setattr },         /* setattr */
	{ .opve_op = &vnop_read_desc, .opve_impl = (VOPFUNC)devfs_read },               /* read */
	{ .opve_op = &vnop_write_desc, .opve_impl = (VOPFUNC)devfs_write },             /* write */
	{ .opve_op = &vnop_ioctl_desc, .opve_impl = (VOPFUNC)err_ioctl },               /* ioctl */
	{ .opve_op = &vnop_select_desc, .opve_impl = (VOPFUNC)err_select },             /* select */
	{ .opve_op = &vnop_revoke_desc, .opve_impl = (VOPFUNC)err_revoke },             /* revoke */
	{ .opve_op = &vnop_mmap_desc, .opve_impl = (VOPFUNC)err_mmap },                 /* mmap */
	{ .opve_op = &vnop_fsync_desc, .opve_impl = (VOPFUNC)nop_fsync },               /* fsync */
	{ .opve_op = &vnop_remove_desc, .opve_impl = (VOPFUNC)devfs_vnop_remove },      /* remove */
	{ .opve_op = &vnop_link_desc, .opve_impl = (VOPFUNC)devfs_link },               /* link */
	{ .opve_op = &vnop_rename_desc, .opve_impl = (VOPFUNC)devfs_rename },           /* rename */
	{ .opve_op = &vnop_mkdir_desc, .opve_impl = (VOPFUNC)devfs_mkdir },             /* mkdir */
	{ .opve_op = &vnop_rmdir_desc, .opve_impl = (VOPFUNC)devfs_rmdir },             /* rmdir */
	{ .opve_op = &vnop_symlink_desc, .opve_impl = (VOPFUNC)devfs_symlink },         /* symlink */
	{ .opve_op = &vnop_readdir_desc, .opve_impl = (VOPFUNC)devfs_readdir },         /* readdir */
	{ .opve_op = &vnop_readlink_desc, .opve_impl = (VOPFUNC)devfs_readlink },       /* readlink */
	{ .opve_op = &vnop_inactive_desc, .opve_impl = (VOPFUNC)devfs_inactive },       /* inactive */
	{ .opve_op = &vnop_reclaim_desc, .opve_impl = (VOPFUNC)devfs_reclaim },         /* reclaim */
	{ .opve_op = &vnop_strategy_desc, .opve_impl = (VOPFUNC)err_strategy },         /* strategy */
	{ .opve_op = &vnop_pathconf_desc, .opve_impl = (VOPFUNC)devs_vnop_pathconf },   /* pathconf */
	{ .opve_op = &vnop_advlock_desc, .opve_impl = (VOPFUNC)err_advlock },           /* advlock */
	{ .opve_op = &vnop_bwrite_desc, .opve_impl = (VOPFUNC)err_bwrite },
	{ .opve_op = &vnop_pagein_desc, .opve_impl = (VOPFUNC)err_pagein },             /* Pagein */
	{ .opve_op = &vnop_pageout_desc, .opve_impl = (VOPFUNC)err_pageout },           /* Pageout */
	{ .opve_op = &vnop_copyfile_desc, .opve_impl = (VOPFUNC)err_copyfile },         /* Copyfile */
	{ .opve_op = &vnop_blktooff_desc, .opve_impl = (VOPFUNC)err_blktooff },         /* blktooff */
	{ .opve_op = &vnop_offtoblk_desc, .opve_impl = (VOPFUNC)err_offtoblk },         /* offtoblk */
	{ .opve_op = &vnop_blockmap_desc, .opve_impl = (VOPFUNC)err_blockmap },         /* blockmap */
#if CONFIG_MACF
	{ .opve_op = &vnop_setlabel_desc, .opve_impl = (VOPFUNC)devfs_setlabel },       /* setlabel */
#endif
	{ .opve_op = (struct vnodeop_desc*)NULL, .opve_impl = (int (*)(void *))NULL }
};
const struct vnodeopv_desc devfs_vnodeop_opv_desc =
{ .opv_desc_vector_p = &devfs_vnodeop_p, .opv_desc_ops = devfs_vnodeop_entries };

/* The following ops are used by the device nodes */
int(**devfs_spec_vnodeop_p)(void *);
const static struct vnodeopv_entry_desc devfs_spec_vnodeop_entries[] = {
	{ .opve_op = &vnop_default_desc, .opve_impl = (VOPFUNC)devfs_default_error },
	{ .opve_op = &vnop_lookup_desc, .opve_impl = (VOPFUNC)spec_lookup },            /* lookup */
	{ .opve_op = &vnop_create_desc, .opve_impl = (VOPFUNC)spec_create },            /* create */
	{ .opve_op = &vnop_mknod_desc, .opve_impl = (VOPFUNC)spec_mknod },              /* mknod */
	{ .opve_op = &vnop_open_desc, .opve_impl = (VOPFUNC)spec_open },                        /* open */
	{ .opve_op = &vnop_close_desc, .opve_impl = (VOPFUNC)devfsspec_close },         /* close */
	{ .opve_op = &vnop_getattr_desc, .opve_impl = (VOPFUNC)devfs_getattr },         /* getattr */
	{ .opve_op = &vnop_setattr_desc, .opve_impl = (VOPFUNC)devfs_setattr },         /* setattr */
	{ .opve_op = &vnop_read_desc, .opve_impl = (VOPFUNC)devfsspec_read },           /* read */
	{ .opve_op = &vnop_write_desc, .opve_impl = (VOPFUNC)devfsspec_write },         /* write */
	{ .opve_op = &vnop_ioctl_desc, .opve_impl = (VOPFUNC)spec_ioctl },              /* ioctl */
	{ .opve_op = &vnop_select_desc, .opve_impl = (VOPFUNC)spec_select },            /* select */
	{ .opve_op = &vnop_revoke_desc, .opve_impl = (VOPFUNC)spec_revoke },            /* revoke */
	{ .opve_op = &vnop_mmap_desc, .opve_impl = (VOPFUNC)spec_mmap },                        /* mmap */
	{ .opve_op = &vnop_fsync_desc, .opve_impl = (VOPFUNC)spec_fsync },              /* fsync */
	{ .opve_op = &vnop_remove_desc, .opve_impl = (VOPFUNC)devfs_vnop_remove },      /* remove */
	{ .opve_op = &vnop_link_desc, .opve_impl = (VOPFUNC)devfs_link },               /* link */
	{ .opve_op = &vnop_rename_desc, .opve_impl = (VOPFUNC)spec_rename },            /* rename */
	{ .opve_op = &vnop_mkdir_desc, .opve_impl = (VOPFUNC)spec_mkdir },              /* mkdir */
	{ .opve_op = &vnop_rmdir_desc, .opve_impl = (VOPFUNC)spec_rmdir },              /* rmdir */
	{ .opve_op = &vnop_symlink_desc, .opve_impl = (VOPFUNC)spec_symlink },          /* symlink */
	{ .opve_op = &vnop_readdir_desc, .opve_impl = (VOPFUNC)spec_readdir },          /* readdir */
	{ .opve_op = &vnop_readlink_desc, .opve_impl = (VOPFUNC)spec_readlink },                /* readlink */
	{ .opve_op = &vnop_inactive_desc, .opve_impl = (VOPFUNC)devfs_inactive },       /* inactive */
	{ .opve_op = &vnop_reclaim_desc, .opve_impl = (VOPFUNC)devfs_reclaim },         /* reclaim */
	{ .opve_op = &vnop_strategy_desc, .opve_impl = (VOPFUNC)spec_strategy },                /* strategy */
	{ .opve_op = &vnop_pathconf_desc, .opve_impl = (VOPFUNC)spec_pathconf },                /* pathconf */
	{ .opve_op = &vnop_advlock_desc, .opve_impl = (VOPFUNC)spec_advlock },          /* advlock */
	{ .opve_op = &vnop_bwrite_desc, .opve_impl = (VOPFUNC)vn_bwrite },
	{ .opve_op = &vnop_pagein_desc, .opve_impl = (VOPFUNC)err_pagein },             /* Pagein */
	{ .opve_op = &vnop_pageout_desc, .opve_impl = (VOPFUNC)err_pageout },           /* Pageout */
	{ .opve_op = &vnop_copyfile_desc, .opve_impl = (VOPFUNC)err_copyfile },         /* Copyfile */
	{ .opve_op = &vnop_blktooff_desc, .opve_impl = (VOPFUNC)spec_blktooff },        /* blktooff */
	{ .opve_op = &vnop_blktooff_desc, .opve_impl = (VOPFUNC)spec_offtoblk  },       /* blkofftoblk */
	{ .opve_op = &vnop_blockmap_desc, .opve_impl = (VOPFUNC)spec_blockmap },        /* blockmap */
#if CONFIG_MACF
	{ .opve_op = &vnop_setlabel_desc, .opve_impl = (VOPFUNC)devfs_setlabel },       /* setlabel */
#endif
	{ .opve_op = (struct vnodeop_desc*)NULL, .opve_impl = (int (*)(void *))NULL }
};
const struct vnodeopv_desc devfs_spec_vnodeop_opv_desc =
{ .opv_desc_vector_p = &devfs_spec_vnodeop_p, .opv_desc_ops = devfs_spec_vnodeop_entries };


#if FDESC
int(**devfs_devfd_vnodeop_p)(void*);
const static struct vnodeopv_entry_desc devfs_devfd_vnodeop_entries[] = {
	{ .opve_op = &vnop_default_desc, .opve_impl = (VOPFUNC)devfs_default_error },
	{ .opve_op = &vnop_lookup_desc, .opve_impl = (VOPFUNC)devfs_devfd_lookup},      /* lookup */
	{ .opve_op = &vnop_open_desc, .opve_impl = (VOPFUNC)nop_open },                 /* open */
	{ .opve_op = &vnop_close_desc, .opve_impl = (VOPFUNC)devfs_close },             /* close */
	{ .opve_op = &vnop_getattr_desc, .opve_impl = (VOPFUNC)devfs_getattr },         /* getattr */
	{ .opve_op = &vnop_setattr_desc, .opve_impl = (VOPFUNC)devfs_setattr },         /* setattr */
	{ .opve_op = &vnop_revoke_desc, .opve_impl = (VOPFUNC)err_revoke },             /* revoke */
	{ .opve_op = &vnop_fsync_desc, .opve_impl = (VOPFUNC)nop_fsync },               /* fsync */
	{ .opve_op = &vnop_readdir_desc, .opve_impl = (VOPFUNC)devfs_devfd_readdir},            /* readdir */
	{ .opve_op = &vnop_inactive_desc, .opve_impl = (VOPFUNC)devfs_inactive },       /* inactive */
	{ .opve_op = &vnop_reclaim_desc, .opve_impl = (VOPFUNC)devfs_reclaim },         /* reclaim */
	{ .opve_op = &vnop_pathconf_desc, .opve_impl = (VOPFUNC)devs_vnop_pathconf },   /* pathconf */
#if CONFIG_MACF
	{ .opve_op = &vnop_setlabel_desc, .opve_impl = (VOPFUNC)devfs_setlabel },       /* setlabel */
#endif
	{ .opve_op = (struct vnodeop_desc*)NULL, .opve_impl = (int (*)(void *))NULL }
};
const struct vnodeopv_desc devfs_devfd_vnodeop_opv_desc =
{ .opv_desc_vector_p = &devfs_devfd_vnodeop_p, .opv_desc_ops = devfs_devfd_vnodeop_entries};
#endif /* FDESC */