Home | History | Annotate | Line # | Download | only in mount_sysctlfs
      1 /*	$NetBSD: sysctlfs.c,v 1.21 2023/04/02 18:23:02 ryo Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2006, 2007  Antti Kantee.  All Rights Reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
     16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     18  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     25  * SUCH DAMAGE.
     26  */
     27 
     28 /*
     29  * sysctlfs: mount sysctls as a file system tree.  Supports query and
     30  * modify of nodes in the sysctl namespace in addition to namespace
     31  * traversal.
     32  */
     33 
     34 #include <sys/cdefs.h>
     35 #ifndef lint
     36 __RCSID("$NetBSD: sysctlfs.c,v 1.21 2023/04/02 18:23:02 ryo Exp $");
     37 #endif /* !lint */
     38 
     39 #include <sys/types.h>
     40 #include <sys/sysctl.h>
     41 
     42 #include <stdio.h>
     43 #include <assert.h>
     44 #include <err.h>
     45 #include <errno.h>
     46 #include <mntopts.h>
     47 #include <paths.h>
     48 #include <puffs.h>
     49 #include <stdlib.h>
     50 #include <string.h>
     51 #include <unistd.h>
     52 #include <util.h>
     53 
     54 #ifdef RUMP_ACTION
     55 #include <rump/rump.h>
     56 #include <rump/rump_syscalls.h>
     57 
     58 #define sysctl(a,b,c,d,e,f) rump_sys___sysctl(a,b,c,d,e,f)
     59 #endif
     60 
     61 PUFFSOP_PROTOS(sysctlfs)
     62 
     63 struct sfsnode {
     64 	int sysctl_flags;
     65 	ino_t myid;
     66 };
     67 
     68 #define SFSPATH_DOTDOT 0
     69 #define SFSPATH_NORMAL 1
     70 
     71 #define N_HIERARCHY 10
     72 typedef int SfsName[N_HIERARCHY];
     73 
     74 struct sfsfid {
     75 	int len;
     76 	SfsName path;
     77 };
     78 
     79 static struct sfsnode rn;
     80 static SfsName sname_root;
     81 static struct timespec fstime;
     82 
     83 static ino_t nextid = 3;
     84 static mode_t fileperms;
     85 static uid_t fileuid;
     86 static gid_t filegid;
     87 
     88 static int rflag;
     89 
     90 #define ISADIR(a) ((SYSCTL_TYPE(a->sysctl_flags) == CTLTYPE_NODE))
     91 #define SFS_MAXFILE 32768
     92 #define SFS_NODEPERDIR 128
     93 
     94 static int sysctlfs_domount(struct puffs_usermount *);
     95 
     96 /*
     97  * build paths.  doesn't support rename (but neither does the fs)
     98  */
     99 static int
    100 sysctlfs_pathbuild(struct puffs_usermount *pu,
    101     const struct puffs_pathobj *parent, const struct puffs_pathobj *comp,
    102     size_t offset, struct puffs_pathobj *res)
    103 {
    104 	SfsName *sname;
    105 	size_t clen;
    106 
    107 	assert(parent->po_len < N_HIERARCHY); /* code uses +1 */
    108 
    109 	sname = malloc(sizeof(SfsName));
    110 	assert(sname != NULL);
    111 
    112 	clen = parent->po_len;
    113 	if (comp->po_len == SFSPATH_DOTDOT) {
    114 		assert(clen != 0);
    115 		clen--;
    116 	}
    117 
    118 	memcpy(sname, parent->po_path, clen * sizeof(int));
    119 
    120 	res->po_path = sname;
    121 	res->po_len = clen;
    122 
    123 	return 0;
    124 }
    125 
    126 static int
    127 sysctlfs_pathtransform(struct puffs_usermount *pu,
    128     const struct puffs_pathobj *p, const struct puffs_cn *pcn,
    129     struct puffs_pathobj *res)
    130 {
    131 
    132 	res->po_path = NULL;
    133 	/*
    134 	 * XXX: overload.  prevents us from doing rename, but the fs
    135 	 * (and sysctl(3)) doesn't support it, so no biggie
    136 	 */
    137 	if (PCNISDOTDOT(pcn)) {
    138 		res->po_len = SFSPATH_DOTDOT;
    139 	}else {
    140 		res->po_len = SFSPATH_NORMAL;
    141 	}
    142 
    143 	return 0;
    144 }
    145 
    146 static int
    147 sysctlfs_pathcmp(struct puffs_usermount *pu, struct puffs_pathobj *po1,
    148     struct puffs_pathobj *po2, size_t clen, int checkprefix)
    149 {
    150 
    151 	if (memcmp(po1->po_path, po2->po_path, clen * sizeof(int)) == 0)
    152 		return 0;
    153 	return 1;
    154 }
    155 
    156 static void
    157 sysctlfs_pathfree(struct puffs_usermount *pu, struct puffs_pathobj *po)
    158 {
    159 
    160 	free(po->po_path);
    161 }
    162 
    163 static struct puffs_node *
    164 getnode(struct puffs_usermount *pu, struct puffs_pathobj *po, int nodetype)
    165 {
    166 	struct sysctlnode sn[SFS_NODEPERDIR];
    167 	struct sysctlnode qnode;
    168 	struct puffs_node *pn;
    169 	struct sfsnode *sfs;
    170 	SfsName myname, *sname;
    171 	size_t sl, i;
    172 
    173 	/*
    174 	 * Check if we need to create a new in-memory node or if we
    175 	 * already have one for this path.  Shortcut for the rootnode.
    176 	 * Also, memcmp against zero-length would be quite true always.
    177 	 */
    178 	if (po->po_len == 0)
    179 		pn = puffs_getroot(pu);
    180 	else
    181 		pn = puffs_pn_nodewalk(pu, puffs_path_walkcmp, po);
    182 
    183 	if (pn != NULL)
    184 		return pn;
    185 	/*
    186 	 * don't know nodetype?  query...
    187 	 *
    188 	 * XXX1: nothing really guarantees 0 is an invalid nodetype
    189 	 * XXX2: is there really no easier way of doing this?  we
    190 	 *       know the whole mib path
    191 	 */
    192 	if (!nodetype) {
    193 		sname = po->po_path;
    194 		memcpy(myname, po->po_path, po->po_len * sizeof(myname[0]));
    195 
    196 		memset(&qnode, 0, sizeof(qnode));
    197 		qnode.sysctl_flags = SYSCTL_VERSION;
    198 		myname[po->po_len-1] = CTL_QUERY;
    199 
    200 		sl = sizeof(sn);
    201 		if (sysctl(myname, po->po_len, sn, &sl,
    202 		    &qnode, sizeof(qnode)) == -1)
    203 			abort();
    204 
    205 		for (i = 0; i < sl / sizeof(struct sysctlnode); i++) {
    206 			 if (sn[i].sysctl_num == (*sname)[po->po_len-1]) {
    207 				nodetype = sn[i].sysctl_flags;
    208 				break;
    209 			}
    210 		}
    211 		if (!nodetype)
    212 			return NULL;
    213 	}
    214 
    215 	sfs = emalloc(sizeof(*sfs));
    216 	sfs->sysctl_flags = nodetype;
    217 	sfs->myid = nextid++;
    218 
    219 	pn = puffs_pn_new(pu, sfs);
    220 	assert(pn);
    221 
    222 	return pn;
    223 }
    224 
    225 static void __dead
    226 usage(void)
    227 {
    228 
    229 	fprintf(stderr, "Usage: %s [-o <mntopts>] sysctlfs mountpath\n",
    230 	    getprogname());
    231 	exit(1);
    232 }
    233 
    234 int
    235 main(int argc, char *argv[])
    236 {
    237 	struct puffs_usermount *pu;
    238 	struct puffs_ops *pops;
    239 	mntoptparse_t mp;
    240 	int mntflags, pflags;
    241 	int detach;
    242 	int ch;
    243 
    244 	setprogname(argv[0]);
    245 
    246 	if (argc < 2)
    247 		usage();
    248 
    249 	mntflags = pflags = 0;
    250 	detach = 1;
    251 	while ((ch = getopt(argc, argv, "o:rs")) != -1) {
    252 		switch (ch) {
    253 		case 'o':
    254 			mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
    255 			if (mp == NULL)
    256 				err(EXIT_FAILURE, "getmntopts");
    257 			freemntopts(mp);
    258 			break;
    259 		case 'r':
    260 			rflag = 1;
    261 			break;
    262 		case 's':
    263 			detach = 0;
    264 			break;
    265 		}
    266 	}
    267 	argv += optind;
    268 	argc -= optind;
    269 	pflags |= PUFFS_FLAG_BUILDPATH | PUFFS_KFLAG_NOCACHE;
    270 
    271 	if (pflags & PUFFS_FLAG_OPDUMP)
    272 		detach = 0;
    273 
    274 	if (argc != 2)
    275 		usage();
    276 
    277 	PUFFSOP_INIT(pops);
    278 
    279 	PUFFSOP_SETFSNOP(pops, unmount);
    280 	PUFFSOP_SETFSNOP(pops, sync);
    281 	PUFFSOP_SETFSNOP(pops, statvfs);
    282 	PUFFSOP_SET(pops, sysctlfs, fs, nodetofh);
    283 	PUFFSOP_SET(pops, sysctlfs, fs, fhtonode);
    284 
    285 	PUFFSOP_SET(pops, sysctlfs, node, lookup);
    286 	PUFFSOP_SET(pops, sysctlfs, node, getattr);
    287 	PUFFSOP_SET(pops, sysctlfs, node, setattr);
    288 	PUFFSOP_SET(pops, sysctlfs, node, readdir);
    289 	PUFFSOP_SET(pops, sysctlfs, node, read);
    290 	PUFFSOP_SET(pops, sysctlfs, node, write);
    291 	PUFFSOP_SET(pops, puffs_genfs, node, reclaim);
    292 
    293 	pu = puffs_init(pops, _PATH_PUFFS, "sysctlfs", NULL, pflags);
    294 	if (pu == NULL)
    295 		err(EXIT_FAILURE, "puffs_init");
    296 
    297 	puffs_set_pathbuild(pu, sysctlfs_pathbuild);
    298 	puffs_set_pathtransform(pu, sysctlfs_pathtransform);
    299 	puffs_set_pathcmp(pu, sysctlfs_pathcmp);
    300 	puffs_set_pathfree(pu, sysctlfs_pathfree);
    301 
    302 	puffs_setfhsize(pu, sizeof(struct sfsfid), PUFFS_FHFLAG_NFSV3);
    303 
    304 	if (sysctlfs_domount(pu) != 0)
    305 		errx(EXIT_FAILURE, "domount");
    306 
    307 	if (detach)
    308 		if (puffs_daemon(pu, 1, 1) == -1)
    309 			err(EXIT_FAILURE, "puffs_daemon");
    310 
    311 #ifdef RUMP_ACTION
    312 	{
    313 		extern int puffs_fakecc;
    314 		puffs_fakecc = 1;
    315 		rump_init();
    316 	}
    317 #endif
    318 
    319 	if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1)
    320 		err(EXIT_FAILURE, "puffs_mount");
    321 	if (puffs_mainloop(pu) == -1)
    322 		err(EXIT_FAILURE, "mainloop");
    323 
    324 	return 0;
    325 }
    326 
    327 static int
    328 sysctlfs_domount(struct puffs_usermount *pu)
    329 {
    330 	struct puffs_pathobj *po_root;
    331 	struct puffs_node *pn_root;
    332 	struct timeval tv_now;
    333 
    334 	rn.myid = 2;
    335 	rn.sysctl_flags = CTLTYPE_NODE;
    336 
    337 	gettimeofday(&tv_now, NULL);
    338 	TIMEVAL_TO_TIMESPEC(&tv_now, &fstime);
    339 
    340 	pn_root = puffs_pn_new(pu, &rn);
    341 	assert(pn_root != NULL);
    342 	puffs_setroot(pu, pn_root);
    343 
    344 	po_root = puffs_getrootpathobj(pu);
    345 	po_root->po_path = &sname_root;
    346 	po_root->po_len = 0;
    347 
    348 	fileuid = geteuid();
    349 	filegid = getegid();
    350 
    351 	if (fileuid == 0)
    352 		fileperms = 0644;
    353 	else
    354 		fileperms = 0444;
    355 
    356 	return 0;
    357 }
    358 
    359 int
    360 sysctlfs_fs_fhtonode(struct puffs_usermount *pu, void *fid, size_t fidsize,
    361 	struct puffs_newinfo *pni)
    362 {
    363 	struct puffs_pathobj po;
    364 	struct puffs_node *pn;
    365 	struct sfsnode *sfs;
    366 	struct sfsfid *sfid;
    367 
    368 	sfid = fid;
    369 
    370 	po.po_len = sfid->len;
    371 	po.po_path = &sfid->path;
    372 
    373 	pn = getnode(pu, &po, 0);
    374 	if (pn == NULL)
    375 		return EINVAL;
    376 	sfs = pn->pn_data;
    377 
    378 	puffs_newinfo_setcookie(pni, pn);
    379 	if (ISADIR(sfs))
    380 		puffs_newinfo_setvtype(pni, VDIR);
    381 	else
    382 		puffs_newinfo_setvtype(pni, VREG);
    383 
    384 	return 0;
    385 }
    386 
    387 int
    388 sysctlfs_fs_nodetofh(struct puffs_usermount *pu, void *cookie,
    389 	void *fid, size_t *fidsize)
    390 {
    391 	struct puffs_node *pn = cookie;
    392 	struct sfsfid *sfid;
    393 
    394 	sfid = fid;
    395 	sfid->len = PNPLEN(pn);
    396 	memcpy(&sfid->path, PNPATH(pn), sfid->len * sizeof(int));
    397 
    398 	return 0;
    399 }
    400 
    401 static void
    402 getnodedata(struct sfsnode *sfs, struct puffs_pathobj *po,
    403 	char *buf, size_t *bufsize)
    404 {
    405 	size_t sz;
    406 	int error = 0;
    407 
    408 	assert(!ISADIR(sfs));
    409 
    410 	memset(buf, 0, *bufsize);
    411 	switch (SYSCTL_TYPE(sfs->sysctl_flags)) {
    412 	case CTLTYPE_BOOL: {
    413 		bool b;
    414 		sz = sizeof(bool);
    415 		assert(sz <= *bufsize);
    416 		if (sysctl(po->po_path, po->po_len, &b, &sz, NULL, 0) == -1) {
    417 			error = errno;
    418 			break;
    419 		}
    420 		if (rflag)
    421 			memcpy(buf, &b, sz);
    422 		else
    423 			snprintf(buf, *bufsize, "%s", b ? "true" : "false");
    424 		break;
    425 	}
    426 	case CTLTYPE_INT: {
    427 		int i;
    428 		sz = sizeof(int);
    429 		assert(sz <= *bufsize);
    430 		if (sysctl(po->po_path, po->po_len, &i, &sz, NULL, 0) == -1) {
    431 			error = errno;
    432 			break;
    433 		}
    434 		if (rflag)
    435 			memcpy(buf, &i, sz);
    436 		else
    437 			snprintf(buf, *bufsize, "%d", i);
    438 		break;
    439 	}
    440 	case CTLTYPE_QUAD: {
    441 		quad_t q;
    442 		sz = sizeof(q);
    443 		assert(sz <= *bufsize);
    444 		if (sysctl(po->po_path, po->po_len, &q, &sz, NULL, 0) == -1) {
    445 			error = errno;
    446 			break;
    447 		}
    448 		if (rflag)
    449 			memcpy(buf, &q, sz);
    450 		else
    451 			snprintf(buf, *bufsize, "%" PRId64, q);
    452 		break;
    453 	}
    454 	case CTLTYPE_STRUCT: {
    455 		uint8_t snode[SFS_MAXFILE/2-1];
    456 		unsigned i;
    457 
    458 		sz = sizeof(snode);
    459 		assert(sz <= *bufsize);
    460 		if (sysctl(po->po_path, po->po_len, snode, &sz, NULL, 0) == -1){
    461 			error = errno;
    462 			break;
    463 		}
    464 		if (rflag) {
    465 			memcpy(buf, &snode, sz);
    466 		} else {
    467 			for (i = 0; i < sz && 2*i < *bufsize; i++) {
    468 				sprintf(&buf[2*i], "%02x", snode[i]);
    469 			}
    470 			buf[2*i] = '\0';
    471 		}
    472 		break;
    473 	}
    474 	case CTLTYPE_STRING: {
    475 		sz = *bufsize;
    476 		assert(sz <= *bufsize);
    477 		if (sysctl(po->po_path, po->po_len, buf, &sz, NULL, 0) == -1) {
    478 			error = errno;
    479 			break;
    480 		}
    481 		break;
    482 	}
    483 	default:
    484 		snprintf(buf, *bufsize, "invalid sysctl CTLTYPE %d",
    485 		    SYSCTL_TYPE(sfs->sysctl_flags));
    486 		break;
    487 	}
    488 
    489 	if (error) {
    490 		*bufsize = 0;
    491 		return;
    492 	}
    493 
    494 	if (rflag)
    495 		*bufsize = sz;
    496 	else
    497 		*bufsize = strlen(buf);
    498 }
    499 
    500 static int
    501 getlinks(struct sfsnode *sfs, struct puffs_pathobj *po)
    502 {
    503 	struct sysctlnode sn[SFS_NODEPERDIR];
    504 	struct sysctlnode qnode;
    505 	SfsName *sname;
    506 	size_t sl;
    507 
    508 	if (!ISADIR(sfs))
    509 		return 1;
    510 
    511 	memset(&qnode, 0, sizeof(qnode));
    512 	sl = sizeof(sn);
    513 	qnode.sysctl_flags = SYSCTL_VERSION;
    514 	sname = po->po_path;
    515 	(*sname)[po->po_len] = CTL_QUERY;
    516 
    517 	if (sysctl(*sname, po->po_len + 1, sn, &sl,
    518 	    &qnode, sizeof(qnode)) == -1)
    519 		return 0;
    520 
    521 	return (sl / sizeof(sn[0])) + 2;
    522 }
    523 
    524 static int
    525 getsize(struct sfsnode *sfs, struct puffs_pathobj *po)
    526 {
    527 	char buf[SFS_MAXFILE];
    528 	size_t sz = sizeof(buf);
    529 
    530 	if (ISADIR(sfs))
    531 		return getlinks(sfs, po) * 16; /* totally arbitrary */
    532 
    533 	getnodedata(sfs, po, buf, &sz);
    534 	if (rflag)
    535 		return sz;
    536 	else
    537 		return sz + 1; /* for \n, not \0 */
    538 }
    539 
    540 int
    541 sysctlfs_node_lookup(struct puffs_usermount *pu, void *opc,
    542 	struct puffs_newinfo *pni, const struct puffs_cn *pcn)
    543 {
    544 	struct puffs_cn *p2cn = __UNCONST(pcn); /* XXX: fix the interface */
    545 	struct sysctlnode sn[SFS_NODEPERDIR];
    546 	struct sysctlnode qnode;
    547 	struct puffs_node *pn_dir = opc;
    548 	struct puffs_node *pn_new;
    549 	struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_new;
    550 	SfsName *sname = PCNPATH(pcn);
    551 	size_t sl, i;
    552 	int nodetype;
    553 
    554 	assert(ISADIR(sfs_dir));
    555 
    556 	/*
    557 	 * If we're looking for dotdot, we already have the entire pathname
    558 	 * in sname, courtesy of pathbuild, so we can skip this step.
    559 	 */
    560 	if (!PCNISDOTDOT(pcn)) {
    561 		memset(&qnode, 0, sizeof(qnode));
    562 		sl = SFS_NODEPERDIR * sizeof(struct sysctlnode);
    563 		qnode.sysctl_flags = SYSCTL_VERSION;
    564 		(*sname)[PCNPLEN(pcn)] = CTL_QUERY;
    565 
    566 		if (sysctl(*sname, PCNPLEN(pcn) + 1, sn, &sl,
    567 		    &qnode, sizeof(qnode)) == -1)
    568 			return ENOENT;
    569 
    570 		for (i = 0; i < sl / sizeof(struct sysctlnode); i++)
    571 			if (strcmp(sn[i].sysctl_name, pcn->pcn_name) == 0)
    572 				break;
    573 		if (i == sl / sizeof(struct sysctlnode))
    574 			return ENOENT;
    575 
    576 		(*sname)[PCNPLEN(pcn)] = sn[i].sysctl_num;
    577 		p2cn->pcn_po_full.po_len++;
    578 		nodetype = sn[i].sysctl_flags;
    579 	} else
    580 		nodetype = CTLTYPE_NODE;
    581 
    582 	pn_new = getnode(pu, &p2cn->pcn_po_full, nodetype);
    583 	if (pn_new == NULL)
    584 		return ENOENT;
    585 	sfs_new = pn_new->pn_data;
    586 
    587 	puffs_newinfo_setcookie(pni, pn_new);
    588 	if (ISADIR(sfs_new))
    589 		puffs_newinfo_setvtype(pni, VDIR);
    590 	else
    591 		puffs_newinfo_setvtype(pni, VREG);
    592 
    593 	return 0;
    594 }
    595 
    596 int
    597 sysctlfs_node_getattr(struct puffs_usermount *pu, void *opc, struct vattr *va,
    598 	const struct puffs_cred *pcr)
    599 {
    600 	struct puffs_node *pn = opc;
    601 	struct sfsnode *sfs = pn->pn_data;
    602 
    603 	memset(va, 0, sizeof(struct vattr));
    604 
    605 	if (ISADIR(sfs)) {
    606 		va->va_type = VDIR;
    607 		va->va_mode = 0555;
    608 	} else {
    609 		va->va_type = VREG;
    610 		va->va_mode = fileperms;
    611 	}
    612 	va->va_uid = fileuid;
    613 	va->va_gid = filegid;
    614 	va->va_nlink = getlinks(sfs, &pn->pn_po);
    615 	va->va_fileid = sfs->myid;
    616 	va->va_size = getsize(sfs, &pn->pn_po);
    617 	va->va_gen = 1;
    618 	va->va_rdev = PUFFS_VNOVAL;
    619 	va->va_blocksize = 512;
    620 	va->va_filerev = 1;
    621 
    622 	va->va_atime = va->va_mtime = va->va_ctime = va->va_birthtime = fstime;
    623 
    624 	return 0;
    625 }
    626 
    627 int
    628 sysctlfs_node_setattr(struct puffs_usermount *pu, void *opc,
    629 	const struct vattr *va, const struct puffs_cred *pcr)
    630 {
    631 
    632 	/* dummy, but required for write */
    633 	/* XXX: we could return EOPNOTSUPP or something */
    634 	return 0;
    635 }
    636 
    637 int
    638 sysctlfs_node_readdir(struct puffs_usermount *pu, void *opc,
    639 	struct dirent *dent, off_t *readoff, size_t *reslen,
    640 	const struct puffs_cred *pcr, int *eofflag,
    641 	off_t *cookies, size_t *ncookies)
    642 {
    643 	struct sysctlnode sn[SFS_NODEPERDIR];
    644 	struct sysctlnode qnode;
    645 	struct puffs_node *pn_dir = opc;
    646 	struct puffs_node *pn_res;
    647 	struct puffs_pathobj po;
    648 	struct sfsnode *sfs_dir = pn_dir->pn_data, *sfs_ent;
    649 	SfsName *sname;
    650 	size_t sl, i;
    651 	enum vtype vt;
    652 	ino_t id;
    653 
    654 	*ncookies = 0;
    655 
    656  again:
    657 	if (*readoff == DENT_DOT || *readoff == DENT_DOTDOT) {
    658 		puffs_gendotdent(&dent, sfs_dir->myid, *readoff, reslen);
    659 		(*readoff)++;
    660 		PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff);
    661 		goto again;
    662 	}
    663 
    664 	memset(&qnode, 0, sizeof(qnode));
    665 	sl = SFS_NODEPERDIR * sizeof(struct sysctlnode);
    666 	qnode.sysctl_flags = SYSCTL_VERSION;
    667 	sname = PNPATH(pn_dir);
    668 	(*sname)[PNPLEN(pn_dir)] = CTL_QUERY;
    669 
    670 	if (sysctl(*sname, PNPLEN(pn_dir) + 1, sn, &sl,
    671 	    &qnode, sizeof(qnode)) == -1)
    672 		return ENOENT;
    673 
    674 	po.po_path = sname;
    675 	po.po_len = PNPLEN(pn_dir)+1;
    676 
    677 	for (i = DENT_ADJ(*readoff); i < sl / sizeof(struct sysctlnode); i++) {
    678 		if (SYSCTL_TYPE(sn[i].sysctl_flags) == CTLTYPE_NODE)
    679 			vt = VDIR;
    680 		else
    681 			vt = VREG;
    682 
    683 		/*
    684 		 * check if the node exists.  if so, give it the real
    685 		 * inode number.  otherwise just fake it.
    686 		 */
    687 		(*sname)[PNPLEN(pn_dir)] = sn[i].sysctl_num;
    688 		pn_res = puffs_pn_nodewalk(pu, puffs_path_walkcmp, &po);
    689 		if (pn_res) {
    690 			sfs_ent = pn_res->pn_data;
    691 			id = sfs_ent->myid;
    692 		} else {
    693 			id = nextid++;
    694 		}
    695 
    696 		if (!puffs_nextdent(&dent, sn[i].sysctl_name, id,
    697 		    puffs_vtype2dt(vt), reslen))
    698 			return 0;
    699 
    700 		(*readoff)++;
    701 		PUFFS_STORE_DCOOKIE(cookies, ncookies, *readoff);
    702 	}
    703 
    704 	*eofflag = 1;
    705 	return 0;
    706 }
    707 
    708 int
    709 sysctlfs_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
    710 	off_t offset, size_t *resid, const struct puffs_cred *pcr,
    711 	int ioflag)
    712 {
    713 	char localbuf[SFS_MAXFILE];
    714 	struct puffs_node *pn = opc;
    715 	struct sfsnode *sfs = pn->pn_data;
    716 	size_t sz = sizeof(localbuf);
    717 	int xfer;
    718 
    719 	if (ISADIR(sfs))
    720 		return EISDIR;
    721 
    722 	getnodedata(sfs, &pn->pn_po, localbuf, &sz);
    723 	if ((ssize_t)sz < offset)
    724 		xfer = 0;
    725 	else
    726 		xfer = MIN(*resid, sz - offset);
    727 
    728 	if (xfer <= 0)
    729 		return 0;
    730 
    731 	memcpy(buf, localbuf + offset, xfer);
    732 	*resid -= xfer;
    733 
    734 	if (*resid && !rflag) {
    735 		buf[xfer] = '\n';
    736 		(*resid)--;
    737 	}
    738 
    739 	return 0;
    740 }
    741 
    742 int
    743 sysctlfs_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
    744 	off_t offset, size_t *resid, const struct puffs_cred *cred,
    745 	int ioflag)
    746 {
    747 	struct puffs_node *pn = opc;
    748 	struct sfsnode *sfs = pn->pn_data;
    749 	long long ll;
    750 	int i, rv;
    751 	bool b;
    752 
    753 	/*
    754 	 * I picked the wrong day to ... um, the wrong place to return errors
    755 	 */
    756 
    757 	/* easy to support, but just unavailable now */
    758 	if (rflag)
    759 		return EOPNOTSUPP;
    760 
    761 	if (puffs_cred_isjuggernaut(cred) == 0)
    762 		return EACCES;
    763 
    764 	if (ISADIR(sfs))
    765 		return EISDIR;
    766 
    767 	if (offset != 0)
    768 		return EINVAL;
    769 
    770 	if (ioflag & PUFFS_IO_APPEND)
    771 		return EINVAL;
    772 
    773 	switch (SYSCTL_TYPE(sfs->sysctl_flags)) {
    774 	case CTLTYPE_BOOL:
    775 		if (strcasestr((const char *)buf, "true"))
    776 			b = true;
    777 		else if (strcasestr((const char *)buf, "false"))
    778 			b = false;
    779 		else
    780 			return EINVAL;
    781 		rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL,
    782 		    &b, sizeof(b));
    783 		break;
    784 	case CTLTYPE_INT:
    785 		if (sscanf((const char *)buf, "%d", &i) != 1)
    786 			return EINVAL;
    787 		rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL,
    788 		    &i, sizeof(int));
    789 		break;
    790 	case CTLTYPE_QUAD:
    791 		if (sscanf((const char *)buf, "%lld", &ll) != 1)
    792 			return EINVAL;
    793 		rv =  sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL,
    794 		    &ll, sizeof(long long));
    795 		break;
    796 	case CTLTYPE_STRING:
    797 		rv = sysctl(PNPATH(pn), PNPLEN(pn), NULL, NULL, buf, *resid);
    798 		break;
    799 	default:
    800 		rv = EINVAL;
    801 		break;
    802 	}
    803 
    804 	if (rv)
    805 		return rv;
    806 
    807 	*resid = 0;
    808 	return 0;
    809 }
    810