text: /*	$NetBSD: kernfs_subr.c,v 1.1 2003/09/08 06:51:53 itojun Exp $	*/
  
  /*
   * Copyright (c) 1993
   *	The Regents of the University of California.  All rights reserved.
   *
   * This code is derived from software contributed to Berkeley by
   * Jan-Simon Pendry.
   *
   * 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.
   * 3. Neither the name of the University nor the names of its contributors
   *    may be used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS 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.
   *
   *	@(#)kernfs_subr.c	8.6 (Berkeley) 5/14/95
   */
  
  /*
   * Copyright (c) 1994 Christopher G. Demetriou.  All rights reserved.
   * Copyright (c) 1993 Jan-Simon Pendry
   *
   * This code is derived from software contributed to Berkeley by
   * Jan-Simon Pendry.
   *
   * 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.
   * 3. All advertising materials mentioning features or use of this software
   *    must display the following acknowledgement:
   *	This product includes software developed by the University of
   *	California, Berkeley and its contributors.
   * 4. Neither the name of the University nor the names of its contributors
   *    may be used to endorse or promote products derived from this software
   *    without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS 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.
   *
   *	@(#)kernfs_subr.c	8.6 (Berkeley) 5/14/95
   */
  
  #include <sys/cdefs.h>
  __KERNEL_RCSID(0, "$NetBSD: kernfs_subr.c,v 1.1 2003/09/08 06:51:53 itojun Exp $");
  
  #ifdef _KERNEL_OPT
  #include "opt_ipsec.h"
  #endif
  
  #include <sys/param.h>
  #include <sys/systm.h>
  #include <sys/time.h>
  #include <sys/kernel.h>
  #include <sys/proc.h>
  #include <sys/vnode.h>
  #include <sys/malloc.h>
  #include <sys/stat.h>
  #include <sys/file.h>
  #include <sys/filedesc.h>
  #include <sys/mount.h>
  
  #include <miscfs/kernfs/kernfs.h>
  
  #ifdef IPSEC
  #include <sys/mbuf.h>
  #include <net/route.h>
  #include <netinet/in.h>
  #include <netinet6/ipsec.h>
  #include <netkey/keydb.h>
  #include <netkey/key.h>
  #endif
  
  void kernfs_hashins __P((struct kernfs_node *));
  void kernfs_hashrem __P((struct kernfs_node *));
  struct vnode *kernfs_hashget __P((kfstype, struct mount *,
      const struct kern_target *, u_int32_t));
  
  static LIST_HEAD(kfs_hashhead, kernfs_node) *kfs_hashtbl;
  static u_long	kfs_ihash;	/* size of hash table - 1 */
  #define KFSVALUEHASH(v)	((v) & kfs_ihash)
  
  static struct lock kfs_hashlock;
  static struct simplelock kfs_hash_slock;
  
  #define	ISSET(t, f)	((t) & (f))
  
  /*
   * allocate a kfsnode/vnode pair.  the vnode is
   * referenced, and locked.
   *
   * the kfs_type, kfs_value and mount point uniquely
   * identify a kfsnode.  the mount point is needed
   * because someone might mount this filesystem
   * twice.
   *
   * all kfsnodes are maintained on a singly-linked
   * list.  new nodes are only allocated when they cannot
   * be found on this list.  entries on the list are
   * removed when the vfs reclaim entry is called.
   *
   * a single lock is kept for the entire list.  this is
   * needed because the getnewvnode() function can block
   * waiting for a vnode to become free, in which case there
   * may be more than one process trying to get the same
   * vnode.  this lock is only taken if we are going to
   * call getnewvnode, since the kernel itself is single-threaded.
   *
   * if an entry is found on the list, then call vget() to
   * take a reference.  this is done because there may be
   * zero references to it and so it needs to removed from
   * the vnode free list.
   */
  int
  kernfs_allocvp(mp, vpp, kfs_type, kt, value)
  	struct mount *mp;
  	struct vnode **vpp;
  	kfstype kfs_type;
  	const struct kern_target *kt;
  	u_int32_t value;
  {
  	struct kernfs_node *kfs = NULL, *kfsp;
  	struct vnode *vp = NULL;
  	int error;
  	long *cookie;
  
  	do {
  		if ((*vpp = kernfs_hashget(kfs_type, mp, kt, value)) != NULL)
  			return (0);
  	} while (lockmgr(&kfs_hashlock, LK_EXCLUSIVE|LK_SLEEPFAIL, 0));
  
  	if (kfs_type == KFSdevice) {
  			/* /kern/rootdev = look for device and obey */
  			/* /kern/rrootdev = look for device and obey */
  		dev_t *dp;
  		struct vnode *fvp;
  
  #ifdef DIAGNOSTIC
  		if (!kt)
  			panic("kernfs: kt == NULL for KFSdevice");
  #endif
  		dp = kt->kt_data;
  	loop:
  		if (*dp == NODEV || !vfinddev(*dp, kt->kt_vtype, &fvp)) {
  			lockmgr(&kfs_hashlock, LK_RELEASE, NULL);
  			return (ENOENT);
  		}
  		vp = fvp;
  		if (vget(fvp, LK_EXCLUSIVE))
  			goto loop;
  		*vpp = vp;
  		lockmgr(&kfs_hashlock, LK_RELEASE, NULL);
  		return (0);
  	}
  
  	if ((error = getnewvnode(VT_KERNFS, mp, kernfs_vnodeop_p, &vp)) != 0) {
  		*vpp = NULL;
  		lockmgr(&kfs_hashlock, LK_RELEASE, NULL);
  		return (error);
  	}
  
  	MALLOC(kfs, void *, sizeof(struct kernfs_node), M_TEMP, M_WAITOK);
  	memset(kfs, 0, sizeof(*kfs));
  	vp->v_data = kfs;
  	cookie = &(VFSTOKERNFS(mp)->fileno_cookie); 
  again:
  	TAILQ_FOREACH(kfsp, &VFSTOKERNFS(mp)->nodelist, kfs_list) {
  		if (kfsp->kfs_cookie == *cookie) {
  			(*cookie) ++;
  			goto again;
  		}
  		if (TAILQ_NEXT(kfsp, kfs_list)) {
  			if (kfsp->kfs_cookie < *cookie &&
  			    *cookie < TAILQ_NEXT(kfsp, kfs_list)->kfs_cookie)
  				break;
  			if (kfsp->kfs_cookie + 1 <
  			    TAILQ_NEXT(kfsp, kfs_list)->kfs_cookie) {
  				*cookie = kfsp->kfs_cookie + 1;
  				break;
  			}
  		}
  	}
  
  	kfs->kfs_cookie = *cookie;
  
  	if (kfsp)
  		TAILQ_INSERT_AFTER(&VFSTOKERNFS(mp)->nodelist, kfsp, kfs,
  		    kfs_list);
  	else
  		TAILQ_INSERT_TAIL(&VFSTOKERNFS(mp)->nodelist, kfs, kfs_list);
  
  	kfs->kfs_type = kfs_type;
  	kfs->kfs_vnode = vp;
  	kfs->kfs_fileno = KERNFS_FILENO(kt, kfs_type, kfs->kfs_cookie);
  	kfs->kfs_value = value;
  	kfs->kfs_kt = kt;
  
  	switch (kfs_type) {
  	case KFSkern:	/* /kern = dr-xr-xr-x */
  		kfs->kfs_mode = S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
  		vp->v_type = VDIR;
  		vp->v_flag = VROOT;
  		break;
  
  	case KFSnull:	/* /kern/?? = -r--r--r-- */
  	case KFStime:	/* /kern/time = -r--r--r-- */
  	case KFSint:	/* /kern/?? = -r--r--r-- */
  	case KFSstring:	/* /kern/?? = -r--r--r-- */
  	case KFSavenrun:	/* /kern/loadavg = -r--r--r-- */
  	case KFSmsgbuf:	/* /kern/msgbuf = -r--r--r-- */
  		kfs->kfs_mode = S_IRUSR|S_IRGRP|S_IROTH;
  		vp->v_type = VREG;
  		break;
  
  	case KFSipsecsadir:	/* /kern/ipsecsa = dr-x------ */
  	case KFSipsecspdir:	/* /kern/ipsecsp = dr-x------ */
  		kfs->kfs_mode = S_IRUSR|S_IXUSR;
  		vp->v_type = VDIR;
  		break;
  
  	case KFSipsecsa:	/* /kern/ipsecsa/N = -r-------- */
  	case KFSipsecsp:	/* /kern/ipsecsp/N = -r-------- */
  		kfs->kfs_mode = S_IRUSR;
  		vp->v_type = VREG;
  		break;
  
  	case KFShostname: /* /kern/hostname = -rw-r-r--- */
  		kfs->kfs_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
  		vp->v_type = VREG;
  		break;
  
  	default:
  		panic("kernfs_allocvp");
  	}
  
  	kernfs_hashins(kfs);
  	uvm_vnp_setsize(vp, 0);
  	lockmgr(&kfs_hashlock, LK_RELEASE, NULL);
  
  	*vpp = vp;
  	return (0);
  }
  
  int
  kernfs_freevp(vp)
  	struct vnode *vp;
  {
  	struct kernfs_node *kfs = VTOKERN(vp);
  
  	kernfs_hashrem(kfs);
  	TAILQ_REMOVE(&VFSTOKERNFS(vp->v_mount)->nodelist, kfs, kfs_list);
  
  	FREE(vp->v_data, M_TEMP);
  	vp->v_data = 0;
  	return (0);
  }
  
  /*
   * Initialize kfsnode hash table.
   */
  void
  kernfs_hashinit()
  {
  
  	lockinit(&kfs_hashlock, PINOD, "kfs_hashlock", 0, 0);
  	kfs_hashtbl = hashinit(desiredvnodes / 4, HASH_LIST, M_UFSMNT,
  	    M_WAITOK, &kfs_ihash);
  	simple_lock_init(&kfs_hash_slock);
  }
  
  void
  kernfs_hashreinit()
  {
  	struct kernfs_node *pp;
  	struct kfs_hashhead *oldhash, *hash;
  	u_long i, oldmask, mask, val;
  
  	hash = hashinit(desiredvnodes / 4, HASH_LIST, M_UFSMNT, M_WAITOK,
  	    &mask);
  
  	simple_lock(&kfs_hash_slock);
  	oldhash = kfs_hashtbl;
  	oldmask = kfs_ihash;
  	kfs_hashtbl = hash;
  	kfs_ihash = mask;
  	for (i = 0; i <= oldmask; i++) {
  		while ((pp = LIST_FIRST(&oldhash[i])) != NULL) {
  			LIST_REMOVE(pp, kfs_hash);
  			val = KFSVALUEHASH(pp->kfs_value);
  			LIST_INSERT_HEAD(&hash[val], pp, kfs_hash);
  		}
  	}
  	simple_unlock(&kfs_hash_slock);
  	hashdone(oldhash, M_UFSMNT);
  }
  
  /*
   * Free kfsnode hash table.
   */
  void
  kernfs_hashdone()
  {
  
  	hashdone(kfs_hashtbl, M_UFSMNT);
  }
  
  struct vnode *
  kernfs_hashget(type, mp, kt, value)
  	kfstype type;
  	struct mount *mp;
  	const struct kern_target *kt;
  	u_int32_t value;
  {
  	struct kfs_hashhead *ppp;
  	struct kernfs_node *pp;
  	struct vnode *vp;
  
  loop:
  	simple_lock(&kfs_hash_slock);
  	ppp = &kfs_hashtbl[KFSVALUEHASH(value)];
  	LIST_FOREACH(pp, ppp, kfs_hash) {
  		vp = KERNFSTOV(pp);
  		if (pp->kfs_type == type && vp->v_mount == mp &&
  		    pp->kfs_kt == kt && pp->kfs_value == value) {
  			simple_lock(&vp->v_interlock);
  			simple_unlock(&kfs_hash_slock);
  			if (vget(vp, LK_EXCLUSIVE | LK_INTERLOCK))
  				goto loop;
  			return (vp);
  		}
  	}
  	simple_unlock(&kfs_hash_slock);
  	return (NULL);
  }
  
  /*
   * Insert the kfsnode into the hash table and lock it.
   */
  void
  kernfs_hashins(pp)
  	struct kernfs_node *pp;
  {
  	struct kfs_hashhead *ppp;
  
  	/* lock the kfsnode, then put it on the appropriate hash list */
  	lockmgr(&pp->kfs_vnode->v_lock, LK_EXCLUSIVE, (struct simplelock *)0);
  
  	simple_lock(&kfs_hash_slock);
  	ppp = &kfs_hashtbl[KFSVALUEHASH(pp->kfs_value)];
  	LIST_INSERT_HEAD(ppp, pp, kfs_hash);
  	simple_unlock(&kfs_hash_slock);
  }
  
  /*
   * Remove the kfsnode from the hash table.
   */
  void
  kernfs_hashrem(pp)
  	struct kernfs_node *pp;
  {
  	simple_lock(&kfs_hash_slock);
  	LIST_REMOVE(pp, kfs_hash);
  	simple_unlock(&kfs_hash_slock);
  }
  
  #ifdef IPSEC
  void
  kernfs_revoke_sa(sav)
  	struct secasvar *sav;
  {
  	struct kernfs_node *kfs, *pnext;
  	struct vnode *vp;
  	struct kfs_hashhead *ppp;
  	struct mbuf *m;
  
  	ppp = &kfs_hashtbl[KFSVALUEHASH(ntohl(sav->spi))];
  	for (kfs = LIST_FIRST(ppp); kfs; kfs = pnext) {
  		vp = KERNFSTOV(kfs);
  		pnext = LIST_NEXT(kfs, kfs_hash);
  		if (vp->v_usecount > 0 && kfs->kfs_type == KFSipsecsa &&
  		    kfs->kfs_value == ntohl(sav->spi)) {
  			m = key_setdumpsa_spi(sav->spi);
  			if (!m)
  				VOP_REVOKE(vp, REVOKEALL);
  			else
  				m_freem(m);
  			break;
  		}
  	}
  }
  
  void
  kernfs_revoke_sp(sp)
  	struct secpolicy *sp;
  {
  	struct kernfs_node *kfs, *pnext;
  	struct vnode *vp;
  	struct kfs_hashhead *ppp;
  
  	ppp = &kfs_hashtbl[KFSVALUEHASH(sp->id)];
  	for (kfs = LIST_FIRST(ppp); kfs; kfs = pnext) {
  		vp = KERNFSTOV(kfs);
  		pnext = LIST_NEXT(kfs, kfs_hash);
  		if (vp->v_usecount > 0 && kfs->kfs_type == KFSipsecsa &&
  		    kfs->kfs_value == sp->id)
  			VOP_REVOKE(vp, REVOKEALL);
  	}
  }
  #endif
  