Subject: kernfs extensions: subdirs, add. types and fileops
To: None <tech-kern@netbsd.org>
From: Christian Limpach <chris@pin.lu>
List: tech-kern
Date: 04/27/2004 22:17:51
--pWyiEgJYm5f9v55/
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hello,

The attached patch adds support for additional entries in /kern.  It
also adds support for subdirectories and dynamic kfstypes which can
have actions attached to vnodeops.  Finally, with this change it's
possible to add entries to /kern without adding code in miscfs/kernfs.

Usage of these features works like this:

- add a directory node:
kernfs_parentdir_t *kernxen_pkt;

...
{
	kernfs_entry_t *dkt;

	KERNFS_ALLOCENTRY(dkt, M_TEMP, M_WAITOK);
	KERNFS_INITENTRY(dkt, DT_DIR, "xen", NULL, KFSsubdir, VDIR, DIR_MODE);
	kernfs_addentry(NULL, dkt);
	kernxen_pkt = KERNFS_ENTOPARENTDIR(dkt);
}

- add a regular node with special fileops:
...
{
	kernfs_entry_t *dkt;
	kfstype kfst;

	kfst = KERNFS_ALLOCTYPE(privcmd_fileops);

	KERNFS_ALLOCENTRY(dkt, M_TEMP, M_WAITOK);
	KERNFS_INITENTRY(dkt, DT_REG, "privcmd", NULL, kfst, VREG,
	    PRIVCMD_MODE);
	kernfs_addentry(kernxen_pkt, dkt);
}

- define file operations for a node:
static int
privcmd_ioctl(void *v)
{
	struct vop_ioctl_args /* {
		const struct vnodeop_desc *a_desc;
		struct vnode *a_vp;
		u_long a_command;
		void *a_data;
		int a_fflag;
		struct ucred *a_cred;
		struct proc *a_p;
	} */ *ap = v;
	int error = 0;

	switch (ap->a_command) {
	case ...

}

static const struct kernfs_fileop privcmd_fileops[] = {
  { .kf_fileop = KERNFS_FILEOP_IOCTL, .kf_vop = privcmd_ioctl },
};


My main use for these extensions is a couple of nodes at /kern/xen
on the NetBSD/Xen port.  The nodes provide access to kernel functions
used by the domain management userland tools.  Unless I'm mistaken,
it wouldn't be possible to use regular devices for all the nodes since
one node provides mmap of some memory but the control offered by the
mmap hook for character devices isn't flexible enough.  It's also
likely that future features will need more nodes since the tools
originate on Linux and there it seems common to use /proc nodes
for ~everything...

The patch below is in addition to the patches I posted in:
http://mail-index.netbsd.org/tech-kern/2004/04/27/0002.html

I've only added hooks for the vnodeops I've used, it's trivial to
add support for additional vnodeops and this can be done on demand. 

I'd like to commit these changes soon since it's the only bit missing
to make Xen work with NetBSD without requiring Linux.

    christian


--pWyiEgJYm5f9v55/
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="kernfs_new.patch"

diff -x CVS -pruN ../../src-current-xen3/sys/miscfs/kernfs/kernfs.h miscfs/kernfs/kernfs.h
--- ../../src-current-xen3/sys/miscfs/kernfs/kernfs.h	2004-04-27 19:08:40.000000000 +0200
+++ miscfs/kernfs/kernfs.h	2004-04-27 19:24:04.000000000 +0200
@@ -57,10 +57,13 @@ typedef enum {
 	KFSipsecspdir,	/* ipsec security policy (top dir) */
 	KFSipsecsa,		/* ipsec security association entry */
 	KFSipsecsp,		/* ipsec security policy entry */
+	KFSsubdir,		/* directory */
+	KFSlasttype,		/* last used type */
+	KFSmaxtype = (1<<6) - 1	/* last possible type */
 } kfstype;
 
 /*
- * control data for the kern file system.
+ * Control data for the kern file system.
  */
 struct kern_target {
 	u_char		kt_type;
@@ -72,6 +75,18 @@ struct kern_target {
 	mode_t		kt_mode;
 };
 
+struct dyn_kern_target {
+	struct kern_target		dkt_kt;
+	SIMPLEQ_ENTRY(dyn_kern_target)	dkt_queue;
+};
+
+struct kernfs_subdir {
+	SIMPLEQ_HEAD(,dyn_kern_target)	ks_entries;
+	unsigned int			ks_nentries;
+	unsigned int			ks_dirs;
+	const struct kern_target	*ks_parent;
+};
+
 struct kernfs_node {
 	LIST_ENTRY(kernfs_node) kfs_hash; /* hash chain */
 	TAILQ_ENTRY(kernfs_node) kfs_list; /* flat list */
@@ -93,9 +108,11 @@ struct kernfs_mount {
 #define UIO_MX	32
 
 #define KERNFS_FILENO(kt, typ, cookie) \
-	((kt >= &kern_targets[0] && kt < &kern_targets[nkern_targets]) ? \
-	    2 + ((kt) - &kern_targets[0]) \
+	((kt >= &kern_targets[0] && kt < &kern_targets[static_nkern_targets]) \
+	    ? 2 + ((kt) - &kern_targets[0]) \
 	      : (((cookie + 1) << 6) | (typ)))
+#define KERNFS_TYPE_FILENO(typ, cookie) \
+	(((cookie + 1) << 6) | (typ))
 
 #define VFSTOKERNFS(mp)	((struct kernfs_mount *)((mp)->mnt_data))
 #define	VTOKERN(vp)	((struct kernfs_node *)(vp)->v_data)
@@ -103,6 +120,7 @@ struct kernfs_mount {
 
 extern const struct kern_target kern_targets[];
 extern int nkern_targets;
+extern const int static_nkern_targets;
 extern int (**kernfs_vnodeop_p) __P((void *));
 extern struct vfsops kernfs_vfsops;
 extern dev_t rrootdev;
@@ -121,4 +139,55 @@ int kernfs_allocvp __P((struct mount *, 
 
 void kernfs_revoke_sa __P((struct secasvar *));
 void kernfs_revoke_sp __P((struct secpolicy *));
+
+/*
+ * Data types for the kernfs file operations.
+ */
+typedef enum {
+	KERNFS_XWRITE,
+	KERNFS_FILEOP_GETATTR,
+	KERNFS_FILEOP_IOCTL,
+	KERNFS_FILEOP_MMAP,
+	KERNFS_FILEOP_WRITE,
+} kfsfileop;
+
+struct kernfs_fileop {
+	kfstype				kf_type;
+	kfsfileop			kf_fileop;
+	union {
+		void			*_kf_genop;
+		int			(*_kf_vop)(void *);
+		int			(*_kf_xwrite)
+			(const struct kernfs_node *, char *, size_t);
+	} _kf_opfn;
+	SPLAY_ENTRY(kernfs_fileop)	kf_node;
+};
+#define	kf_genop	_kf_opfn
+#define	kf_vop		_kf_opfn._kf_vop
+#define	kf_xwrite	_kf_opfn._kf_xwrite
+
+typedef struct kern_target kernfs_parentdir_t;
+typedef struct dyn_kern_target kernfs_entry_t;
+
+/*
+ * Functions for adding kernfs datatypes and nodes.
+ */
+kfstype kernfs_alloctype(int, const struct kernfs_fileop *);
+#define	KERNFS_ALLOCTYPE(kf) kernfs_alloctype(sizeof((kf)) / \
+	sizeof((kf)[0]), (kf))
+#define	KERNFS_ALLOCENTRY(dkt, m_type, m_flags)				\
+	dkt = (struct dyn_kern_target *)malloc(				\
+		sizeof(struct dyn_kern_target), (m_type), (m_flags))
+#define	KERNFS_INITENTRY(dkt, type, name, data, tag, vtype, mode) do {	\
+	(dkt)->dkt_kt.kt_type = (type);					\
+	(dkt)->dkt_kt.kt_namlen = strlen((name));			\
+	(dkt)->dkt_kt.kt_name = (name);					\
+	(dkt)->dkt_kt.kt_data = (data);					\
+	(dkt)->dkt_kt.kt_tag = (tag);					\
+	(dkt)->dkt_kt.kt_vtype = (vtype);				\
+	(dkt)->dkt_kt.kt_mode = (mode);					\
+} while (/*CONSTCOND*/0)
+#define	KERNFS_ENTOPARENTDIR(dkt) &(dkt)->dkt_kt
+int kernfs_addentry __P((kernfs_parentdir_t *, kernfs_entry_t *));
+
 #endif /* _KERNEL */
diff -x CVS -pruN ../../src-current-xen3/sys/miscfs/kernfs/kernfs_vnops.c miscfs/kernfs/kernfs_vnops.c
--- ../../src-current-xen3/sys/miscfs/kernfs/kernfs_vnops.c	2004-04-27 19:08:40.000000000 +0200
+++ miscfs/kernfs/kernfs_vnops.c	2004-04-22 16:28:00.000000000 +0200
@@ -80,8 +80,8 @@ __KERNEL_RCSID(0, "$NetBSD: kernfs_vnops
 #define	READ_MODE	(S_IRUSR|S_IRGRP|S_IROTH)
 #define	WRITE_MODE	(S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
 #define	UREAD_MODE	(S_IRUSR)
-#define DIR_MODE	(S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
-#define UDIR_MODE	(S_IRUSR|S_IXUSR)
+#define	DIR_MODE	(S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
+#define	UDIR_MODE	(S_IRUSR|S_IXUSR)
 
 #define N(s) sizeof(s)-1, s
 const struct kern_target kern_targets[] = {
@@ -113,6 +113,12 @@ const struct kern_target kern_targets[] 
      { DT_REG, N("version"),   (void *)version,
      					     KFSstring,      VREG, READ_MODE  },
 };
+const struct kern_target subdir_targets[] = {
+/* NOTE: The name must be less than UIO_MX-16 chars in length */
+     /*        name            data          tag           type  ro/rw */
+     { DT_DIR, N("."),         0,            KFSsubdir,      VDIR, DIR_MODE   },
+     { DT_DIR, N(".."),        0,            KFSkern,        VDIR, DIR_MODE   },
+};
 #ifdef IPSEC
 const struct kern_target ipsecsa_targets[] = {
 /* NOTE: The name must be less than UIO_MX-16 chars in length */
@@ -132,12 +138,34 @@ const struct kern_target ipsecsp_kt =
      { DT_DIR, N(""),          0,            KFSipsecsp,     VREG, UREAD_MODE };
 #endif
 #undef N
+SIMPLEQ_HEAD(,dyn_kern_target) dyn_kern_targets =
+	SIMPLEQ_HEAD_INITIALIZER(dyn_kern_targets);
 int nkern_targets = sizeof(kern_targets) / sizeof(kern_targets[0]);
+const int static_nkern_targets = sizeof(kern_targets) / sizeof(kern_targets[0]);
 #ifdef IPSEC
 int nipsecsa_targets = sizeof(ipsecsa_targets) / sizeof(ipsecsa_targets[0]);
 int nipsecsp_targets = sizeof(ipsecsp_targets) / sizeof(ipsecsp_targets[0]);
+int nkern_dirs = 4; /* 2 extra subdirs */
+#else
+int nkern_dirs = 2;
 #endif
 
+int kernfs_try_fileop(kfstype, kfsfileop, void *, int);
+int kernfs_try_xwrite(kfstype, const struct kernfs_node *, char *,
+    size_t, int);
+
+static int kernfs_default_xwrite(void *v);
+static int kernfs_default_fileop_getattr(void *);
+
+/* must include all fileop's */
+const struct kernfs_fileop kernfs_default_fileops[] = {
+  { .kf_fileop = KERNFS_XWRITE },
+  { .kf_fileop = KERNFS_FILEOP_GETATTR,
+    .kf_vop = kernfs_default_fileop_getattr },
+  { .kf_fileop = KERNFS_FILEOP_IOCTL },
+  { .kf_fileop = KERNFS_FILEOP_MMAP },
+  { .kf_fileop = KERNFS_FILEOP_WRITE, .kf_vop = kernfs_default_xwrite },
+};
 
 int	kernfs_lookup	__P((void *));
 #define	kernfs_create	genfs_eopnotsupp
@@ -150,9 +178,10 @@ int	kernfs_setattr	__P((void *));
 int	kernfs_read	__P((void *));
 int	kernfs_write	__P((void *));
 #define	kernfs_fcntl	genfs_fcntl
-#define	kernfs_ioctl	genfs_enoioctl
+int	kernfs_ioctl	__P((void *));
 #define	kernfs_poll	genfs_poll
 #define kernfs_revoke	genfs_revoke
+int	kernfs_mmap	__P((void *));
 #define	kernfs_fsync	genfs_nullop
 #define	kernfs_seek	genfs_nullop
 #define	kernfs_remove	genfs_eopnotsupp
@@ -202,6 +231,7 @@ const struct vnodeopv_entry_desc kernfs_
 	{ &vop_ioctl_desc, kernfs_ioctl },		/* ioctl */
 	{ &vop_poll_desc, kernfs_poll },		/* poll */
 	{ &vop_revoke_desc, kernfs_revoke },		/* revoke */
+	{ &vop_mmap_desc, kernfs_mmap },		/* mmap */
 	{ &vop_fsync_desc, kernfs_fsync },		/* fsync */
 	{ &vop_seek_desc, kernfs_seek },		/* seek */
 	{ &vop_remove_desc, kernfs_remove },		/* remove */
@@ -235,6 +265,109 @@ const struct vnodeopv_entry_desc kernfs_
 const struct vnodeopv_desc kernfs_vnodeop_opv_desc =
 	{ &kernfs_vnodeop_p, kernfs_vnodeop_entries };
 
+static __inline int
+kernfs_fileop_compare(struct kernfs_fileop *a, struct kernfs_fileop *b)
+{
+	if (a->kf_type < b->kf_type)
+		return -1;
+	if (a->kf_type > b->kf_type)
+		return 1;
+	if (a->kf_fileop < b->kf_fileop)
+		return -1;
+	if (a->kf_fileop > b->kf_fileop)
+		return 1;
+	return (0);
+}
+
+SPLAY_HEAD(kfsfileoptree, kernfs_fileop) kfsfileoptree =
+	SPLAY_INITIALIZER(kfsfileoptree);
+SPLAY_PROTOTYPE(kfsfileoptree, kernfs_fileop, kf_node, kernfs_fileop_compare);
+SPLAY_GENERATE(kfsfileoptree, kernfs_fileop, kf_node, kernfs_fileop_compare);
+
+kfstype
+kernfs_alloctype(int nkf, const struct kernfs_fileop *kf)
+{
+	static u_char nextfreetype = KFSlasttype;
+	struct kernfs_fileop *dkf, *fkf, skf;
+	int i;
+
+	/* XXX need to keep track of dkf's memory if we support
+           deallocating types */
+	dkf = malloc(sizeof(kernfs_default_fileops), M_TEMP, M_WAITOK);
+	memcpy(dkf, kernfs_default_fileops, sizeof(kernfs_default_fileops));
+
+	for (i = 0; i < sizeof(kernfs_default_fileops) /
+		     sizeof(kernfs_default_fileops[0]); i++) {
+		dkf[i].kf_type = nextfreetype;
+		SPLAY_INSERT(kfsfileoptree, &kfsfileoptree, &dkf[i]);
+	}
+
+	for (i = 0; i < nkf; i++) {
+		skf.kf_type = nextfreetype;
+		skf.kf_fileop = kf[i].kf_fileop;
+		if ((fkf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
+			fkf->kf_genop = kf[i].kf_genop;
+	}
+
+	return nextfreetype++;
+}
+
+int
+kernfs_try_fileop(kfstype type, kfsfileop fileop, void *v, int error)
+{
+	struct kernfs_fileop *kf, skf;
+
+	skf.kf_type = type;
+	skf.kf_fileop = fileop;
+	if ((kf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
+		if (kf->kf_vop)
+			return kf->kf_vop(v);
+	return error;
+}
+
+int
+kernfs_try_xwrite(kfstype type, const struct kernfs_node *kfs, char *buf,
+    size_t len, int error)
+{
+	struct kernfs_fileop *kf, skf;
+
+	skf.kf_type = type;
+	skf.kf_fileop = KERNFS_XWRITE;
+	if ((kf = SPLAY_FIND(kfsfileoptree, &kfsfileoptree, &skf)))
+		if (kf->kf_xwrite)
+			return kf->kf_xwrite(kfs, buf, len);
+	return error;
+}
+
+int
+kernfs_addentry(kernfs_parentdir_t *pkt, kernfs_entry_t *dkt)
+{
+	struct kernfs_subdir *ks, *parent;
+
+	if (pkt == NULL) {
+		SIMPLEQ_INSERT_TAIL(&dyn_kern_targets, dkt, dkt_queue);
+		nkern_targets++;
+		if (dkt->dkt_kt.kt_vtype == VDIR)
+			nkern_dirs++;
+	} else {
+		parent = (struct kernfs_subdir *)pkt->kt_data;
+		SIMPLEQ_INSERT_TAIL(&parent->ks_entries, dkt, dkt_queue);
+		parent->ks_nentries++;
+		if (dkt->dkt_kt.kt_vtype == VDIR)
+			parent->ks_dirs++;
+	}
+	if (dkt->dkt_kt.kt_vtype == VDIR && dkt->dkt_kt.kt_data == NULL) {
+		ks = malloc(sizeof(struct kernfs_subdir),
+		    M_TEMP, M_WAITOK);
+		SIMPLEQ_INIT(&ks->ks_entries);
+		ks->ks_nentries = 2; /* . and .. */
+		ks->ks_dirs = 2;
+		ks->ks_parent = pkt ? pkt : &kern_targets[0];
+		dkt->dkt_kt.kt_data = ks;
+	}
+	return 0;
+}
+
 static int
 kernfs_xread(kfs, off, bufp, len, wrlen)
 	struct kernfs_node *kfs;
@@ -412,7 +545,7 @@ kernfs_xwrite(kfs, buf, len)
 		return (0);
 
 	default:
-		return (EIO);
+		return kernfs_try_xwrite(kfs->kfs_type, kfs, buf, len, EIO);
 	}
 }
 
@@ -436,6 +569,8 @@ kernfs_lookup(v)
 	const char *pname = cnp->cn_nameptr;
 	const struct kernfs_node *kfs;
 	const struct kern_target *kt;
+	const struct dyn_kern_target *dkt;
+	const struct kernfs_subdir *ks;
 	int error, i, wantpunlock;
 #ifdef IPSEC
 	char *ep;
@@ -464,12 +599,19 @@ kernfs_lookup(v)
 		if (cnp->cn_flags & ISDOTDOT)
 			return (EIO);
 
-		for (i = 0; i < nkern_targets; i++) {
+		for (i = 0; i < static_nkern_targets; i++) {
 			kt = &kern_targets[i];
 			if (cnp->cn_namelen == kt->kt_namlen &&
 			    memcmp(kt->kt_name, pname, cnp->cn_namelen) == 0)
 				goto found;
 		}
+		SIMPLEQ_FOREACH(dkt, &dyn_kern_targets, dkt_queue) {
+			if (cnp->cn_namelen == dkt->dkt_kt.kt_namlen &&
+			    memcmp(dkt->dkt_kt.kt_name, pname, cnp->cn_namelen) == 0) {
+				kt = &dkt->dkt_kt;
+				goto found;
+			}
+		}
 		break;
 
 	found:
@@ -480,6 +622,22 @@ kernfs_lookup(v)
 		}
 		return (error);
 
+	case KFSsubdir:
+		ks = (struct kernfs_subdir *)kfs->kfs_kt->kt_data;
+		if (cnp->cn_flags & ISDOTDOT) {
+			kt = ks->ks_parent;
+			goto found;
+		}
+
+		SIMPLEQ_FOREACH(dkt, &ks->ks_entries, dkt_queue) {
+			if (cnp->cn_namelen == dkt->dkt_kt.kt_namlen &&
+			    memcmp(dkt->dkt_kt.kt_name, pname, cnp->cn_namelen) == 0) {
+				kt = &dkt->dkt_kt;
+				goto found;
+			}
+		}
+		break;
+
 #ifdef IPSEC
 	case KFSipsecsadir:
 		if (cnp->cn_flags & ISDOTDOT) {
@@ -625,6 +783,24 @@ kernfs_access(v)
 	    ap->a_mode, ap->a_cred));
 }
 
+static int
+kernfs_default_fileop_getattr(v)
+	void *v;
+{
+	struct vop_getattr_args /* {
+		struct vnode *a_vp;
+		struct vattr *a_vap;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct vattr *vap = ap->a_vap;
+
+	vap->va_nlink = 1;
+	vap->va_bytes = vap->va_size = 0;
+
+	return 0;
+}
+
 int
 kernfs_getattr(v)
 	void *v;
@@ -636,6 +812,7 @@ kernfs_getattr(v)
 		struct proc *a_p;
 	} */ *ap = v;
 	struct kernfs_node *kfs = VTOKERN(ap->a_vp);
+	struct kernfs_subdir *ks;
 	struct vattr *vap = ap->a_vap;
 	int error = 0;
 	char strbuf[KSTRING], *buf;
@@ -670,11 +847,7 @@ kernfs_getattr(v)
 
 	switch (kfs->kfs_type) {
 	case KFSkern:
-#ifdef IPSEC
-		vap->va_nlink = 4; /* 2 extra subdirs */
-#else
-		vap->va_nlink = 2;
-#endif
+		vap->va_nlink = nkern_dirs;
 		vap->va_bytes = vap->va_size = DEV_BSIZE;
 		break;
 
@@ -683,6 +856,12 @@ kernfs_getattr(v)
 		vap->va_bytes = vap->va_size = DEV_BSIZE;
 		break;
 
+	case KFSsubdir:
+		ks = (struct kernfs_subdir *)kfs->kfs_kt->kt_data;
+		vap->va_nlink = ks->ks_dirs;
+		vap->va_bytes = vap->va_size = DEV_BSIZE;
+		break;
+
 	case KFSnull:
 	case KFStime:
 	case KFSint:
@@ -715,7 +894,8 @@ kernfs_getattr(v)
 #endif
 
 	default:
-		error = EINVAL;
+		error = kernfs_try_fileop(kfs->kfs_type,
+		    KERNFS_FILEOP_GETATTR, v, EINVAL);
 		break;
 	}
 
@@ -764,8 +944,8 @@ kernfs_read(v)
 	return (error);
 }
 
-int
-kernfs_write(v)
+static int
+kernfs_default_xwrite(v)
 	void *v;
 {
 	struct vop_write_args /* {
@@ -794,6 +974,56 @@ kernfs_write(v)
 	return (kernfs_xwrite(kfs, strbuf, xlen));
 }
 
+int
+kernfs_write(v)
+	void *v;
+{
+	struct vop_write_args /* {
+		struct vnode *a_vp;
+		struct uio *a_uio;
+		int  a_ioflag;
+		struct ucred *a_cred;
+	} */ *ap = v;
+	struct kernfs_node *kfs = VTOKERN(ap->a_vp);
+
+	return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_WRITE, v, 0);
+}
+
+int
+kernfs_ioctl(v)
+	void *v;
+{
+	struct vop_ioctl_args /* {
+		const struct vnodeop_desc *a_desc;
+		struct vnode *a_vp;
+		u_long a_command;
+		void *a_data;
+		int a_fflag;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct kernfs_node *kfs = VTOKERN(ap->a_vp);
+
+	return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_IOCTL, v,
+	    EPASSTHROUGH);
+}
+
+int
+kernfs_mmap(v)
+	void *v;
+{
+	struct vop_mmap_args /* {
+		const struct vnodeop_desc *a_desc;
+		struct vnode *a_vp;
+		int a_fflags;
+		struct ucred *a_cred;
+		struct proc *a_p;
+	} */ *ap = v;
+	struct kernfs_node *kfs = VTOKERN(ap->a_vp);
+
+	return kernfs_try_fileop(kfs->kfs_type, KERNFS_FILEOP_MMAP, v, 0);
+}
+
 static int
 kernfs_setdirentfileno_kt(struct dirent *d, const struct kern_target *kt,
     u_int32_t value, struct vop_readdir_args *ap)
@@ -863,7 +1093,9 @@ kernfs_readdir(v)
 	struct dirent d;
 	struct kernfs_node *kfs = VTOKERN(ap->a_vp);
 	const struct kern_target *kt;
-	off_t i;
+	const struct dyn_kern_target *dkt = NULL;
+	const struct kernfs_subdir *ks;
+	off_t i, j;
 	int error;
 	off_t *cookies = NULL;
 	int ncookies = 0, n;
@@ -897,7 +1129,23 @@ kernfs_readdir(v)
 
 		n = 0;
 		for (; i < nkern_targets && uio->uio_resid >= UIO_MX; i++) {
-			kt = &kern_targets[i];
+			if (i < static_nkern_targets)
+				kt = &kern_targets[i];
+			else {
+				if (dkt == NULL) {
+					dkt = SIMPLEQ_FIRST(&dyn_kern_targets);
+					for (j = static_nkern_targets; j < i &&
+						     dkt != NULL; j++)
+						dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
+					if (j != i)
+						break;
+				} else {
+					dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
+					if (dkt == NULL)
+						break;
+				}
+				kt = &dkt->dkt_kt;
+			}
 			if (kt->kt_tag == KFSdevice) {
 				dev_t *dp = kt->kt_data;
 				struct vnode *fvp;
@@ -948,6 +1196,55 @@ kernfs_readdir(v)
 		ncookies = n;
 		break;
 
+	case KFSsubdir:
+		ks = (struct kernfs_subdir *)kfs->kfs_kt->kt_data;
+		if (i >= ks->ks_nentries)
+			return (0);
+
+		if (ap->a_ncookies) {
+			ncookies = min(ncookies, (ks->ks_nentries - i));
+			cookies = malloc(ncookies * sizeof(off_t), M_TEMP,
+			    M_WAITOK);
+			*ap->a_cookies = cookies;
+		}
+
+		dkt = SIMPLEQ_FIRST(&ks->ks_entries);
+		for (j = 0; j < i && dkt != NULL; j++)
+			dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
+		n = 0;
+		for (; i < ks->ks_nentries && uio->uio_resid >= UIO_MX; i++) {
+			if (i < 2)
+				kt = &subdir_targets[i];
+			else {
+				/* check if ks_nentries lied to us */
+				if (dkt == NULL)
+					break;
+				kt = &dkt->dkt_kt;
+				dkt = SIMPLEQ_NEXT(dkt, dkt_queue);
+			}
+			if (kt->kt_tag == KFSdevice) {
+				dev_t *dp = kt->kt_data;
+				struct vnode *fvp;
+
+				if (*dp == NODEV ||
+				    !vfinddev(*dp, kt->kt_vtype, &fvp))
+					continue;
+			}
+			d.d_namlen = kt->kt_namlen;
+			if ((error = kernfs_setdirentfileno(&d, i, kfs,
+			    ks->ks_parent, kt, ap)) != 0)
+				break;
+			memcpy(d.d_name, kt->kt_name, kt->kt_namlen + 1);
+			d.d_type = kt->kt_type;
+			if ((error = uiomove((caddr_t)&d, UIO_MX, uio)) != 0)
+				break;
+			if (cookies)
+				*cookies++ = i + 1;
+			n++;
+		}
+		ncookies = n;
+		break;
+
 #ifdef IPSEC
 	case KFSipsecsadir:
 		/* count SA in the system */

--pWyiEgJYm5f9v55/--