Index | Thread | Search

From:
ori@eigenstate.org
Subject:
gefs vs ioctls
To:
tech@openbsd.org
Date:
Mon, 02 Nov 2026 17:12:18 -0500

Download raw body.

Thread
  • ori@eigenstate.org:

    gefs vs ioctls

9front doesn't have ioctls, instead we have a synthetic
ctl file that accepts commands. For example:

	% cat /adm/status
	gefs p9
	usage 	0.00%	0.50MiB	223.57GiB
	conf retain '' '60@m 24@h @d'
	fork adm
	snap adm@day.2026.11.02_00:47:54
	snap adm@hour.2026.11.02_00:47:54
	snap adm@minute.2026.11.02_00:47:54
	snap empty
	fork main

or

	echo snap main main.cur > /adm/ctl

For the writes, it's pretty simple to map them to
ioctls:
	
	struct gefs_snap {
		char	gs_old[256];
		char	gs_new[256];
	};
	
	struct gefs_retain {
		char	gr_snap[256];
		char	gr_sched[256];
	};
	
	#define GEFS_SNAP	_IOW('G', 1, struct gefs_snap)
	#define GEFS_FORK	_IOW('G', 2, struct gefs_snap)
	#define GEFS_DEL	_IOW('G', 3, struct gefs_snap)
	#define GEFS_RETAIN	_IOW('G', 4, struct gefs_retain)
	#define GEFS_BALLAST	_IOW('G', 5, int)	/* 0 or 1 for enabling ballast */
	#define GEFS_DEBUG	_IOW('G', 6, int)	/* debug level */

And we can just allow it on any file on a gefs volume:

	gefs -f . snap blah

But, for this, we need to pass through ioctl requests
on regular files. and I'm not sure how I feel about it:

diff 0620ae0b52bc2ff8f33f33434b789f310a391745 uncommitted
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -516,9 +516,9 @@
 	struct vattr vattr;
 	int error = ENOTTY;
 
+	com, vp->v_type, VREG, VDIR);
 	KERNEL_LOCK();
 	switch (vp->v_type) {
-
 	case VREG:
 	case VDIR:
 		if (com == FIONREAD) {
@@ -526,11 +526,12 @@
 			if (error)
 				break;
 			*(int *)data = vattr.va_size - foffset(fp);
-
-		} else if (com == FIOASYNC)			/* XXX */
+			break;
+		} else if (com == FIOASYNC) {			/* XXX */
 			error = 0;				/* XXX */
-		break;
-
+			break;
+		}
+		/* wet floor */
 	case VFIFO:
 	case VCHR:
 	case VBLK:

for reding the status, I'm a bit stuck what the best
option would be, since the output isn't necessarily
staticaly sized.

The best I can come up with is some sort of iteration
over an index, but it feels very race-prone.

	ioctl(GEFS_SNAPCOUNT, &nsnap);
	for(i = 0; i < nsnap; i++){
		ioctl(GEFS_SNAPNAME, i, name);
		printf("%s\n", name);
	}

Any thoughts on a better aproach?

Appendix, the current implementation (only lightly
tested, not yet pushed)

---
diff 279e65a41d45eadcc2fa793ae23b7c82ef328062 8aa73e9954ab329ac5a589618dff9ba768dea4e0
--- /dev/null
+++ b/sbin/gefs/Makefile
@@ -1,0 +1,11 @@
+#	$OpenBSD: Makefile,v 1.4 2001/01/10 20:02:29 deraadt Exp $
+
+PROG=	gefs
+SRCS=	gefs.c
+MAN=	gefs.8
+
+MOUNT=	${.CURDIR}/../mount
+CFLAGS+= -I${MOUNT}
+.PATH:	${MOUNT}
+
+.include <bsd.prog.mk>
--- /dev/null
+++ b/sbin/gefs/gefs.8
@@ -1,0 +1,65 @@
+.\" Copyright (c) 2026 Ori Bernstein
+.\" All rights reserved.
+.\"
+.\" 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 AUTHOR(S) ``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 AUTHOR(S) 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.
+.Dd $Mdocdate: Oct 2 2026 $
+.Dt GEFS 8
+.Os
+.Sh NAME
+.Nm gefs
+.Nd Administration commands for GEFS
+.Sh SYNOPSIS
+.Nm gefs
+.Op Ar debug
+.Op Ar usereserve
+.Op Ar snap
+.Op Ar fork
+.Op Ar delsnap
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+allows access to gefs administrative operations such as snapshotting and
+debugging.
+The commands are as follows:
+.It Ar debug level
+Enable debugging at given level
+.It Ar usereserve [0|1]
+GEFS may need space in order to delete files.
+As a result, some space is held in reserve so that a full disk is recoverable.
+This releases the reserved space.
+If you run out of disk space with this option enabled,
+no files can be deleted.
+.It Ar snap old new
+Takes a snapshot
+.Ar new
+based off the contents of
+.Ar old.
+.It Ar fork old new
+Takes a mutable fork
+.Ar new
+based off the contents of
+.Ar old.
+.It Ar delsnap snap
+Removes the snapshot or fork
+.Ar snap
--- /dev/null
+++ b/sbin/gefs/gefs.c
@@ -1,0 +1,128 @@
+/*	$OpenBSD$	*/
+
+/*
+ * Copyright (c) 2026 Ori Bernstein <ori@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/gefs.h>
+#include <sys/ioctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+
+void
+usage(void)
+{
+	fprintf(stderr, "usage: gefs [-f fs] debug [0|1]\n");
+	fprintf(stderr, "       gefs [-f fs] snap old new\n");
+	fprintf(stderr, "       gefs [-f fs] fork old new\n");
+	fprintf(stderr, "       gefs [-f fs] retain fork timespec\n");
+	fprintf(stderr, "       gefs [-f fs] usereserve\n");
+	exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+	struct gefs_snap gs;
+	struct gefs_retain gr;
+	int r, v, ch, fd;
+	const char *e;
+
+	fd = -1;
+	while ((ch = getopt(argc, argv, "o:")) != -1) {
+		switch (ch) {
+		case 'f':
+			if ((fd = open(optarg, O_RDONLY)) == -1)
+				err(1, "open %s:", optarg);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	if (fd == -1) {
+		if ((fd = open(".", O_RDONLY)) == -1)
+			err(1, "open .:");
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc == 0)
+		usage();
+
+	if (strcmp(argv[0], "debug") == 0) {
+		if (argc != 2) 
+			usage();
+		v = strtonum(argv[1], 0, 1, &e);
+		if (e != NULL) {
+			fprintf(stderr, "debug: invalid argument: %s", e);
+			exit(1);
+		}
+		r = ioctl(fd, GEFS_DEBUG, &v);
+	} else if (strcmp(argv[0], "usereserve") == 0) {
+		if (argc != 3)
+			usage();
+		v = strtonum(argv[1], 0, 1, &e);
+		if (e != NULL) {
+			fprintf(stderr, "debug: invalid argument: %s", e);
+			exit(1);
+		}
+		r = ioctl(fd, GEFS_BALLAST, &v);
+	} else if (strcmp(argv[0], "retain") == 0) {
+		if (argc != 2)
+			usage();
+		if (strlcpy(gr.gr_snap, argv[1], sizeof(gr.gr_snap)) >= sizeof(gr.gr_snap))
+			errx(1, "overlong snap name");
+		if (strlcpy(gr.gr_sched, argv[2], sizeof(gr.gr_sched)) >= sizeof(gr.gr_sched))
+			errx(1, "overlong schedule");
+		r = ioctl(fd, GEFS_RETAIN, &gr);
+	} else if (strcmp(argv[0], "snap") == 0) {
+		if (argc != 3)
+			usage();
+		if (strlcpy(gs.gs_old, argv[1], sizeof(gs.gs_old)) >= sizeof(gs.gs_old))
+			errx(1, "overlong old snap name");
+		if (strlcpy(gs.gs_new, argv[2], sizeof(gs.gs_new)) >= sizeof(gs.gs_new))
+			errx(1, "overlong new snap name");
+		r = ioctl(fd, GEFS_SNAP, &gs);
+	} else if (strcmp(argv[0], "fork") == 0) {
+		if (argc != 3)
+			usage();
+		if (strlcpy(gs.gs_old, argv[1], sizeof(gs.gs_old)) >= sizeof(gs.gs_old))
+			errx(1, "overlong old snap name");
+		if (strlcpy(gs.gs_new, argv[2], sizeof(gs.gs_new)) >= sizeof(gs.gs_new))
+			errx(1, "overlong new snap name");
+		r = ioctl(fd, GEFS_FORK, &gs);
+	} else if (strcmp(argv[0], "delsnap") == 0) {
+		if (argc != 2)
+			usage();
+		if (strlcpy(gs.gs_old, argv[1], sizeof(gs.gs_old)) >= sizeof(gs.gs_old))
+			errx(1, "overlong old snap name");
+		gs.gs_new[0] = 0;
+		r = ioctl(fd, GEFS_DEL, &gs);
+	
+	}
+	if (r == -1)
+		err(1, "ioctl %s:", argv[0]);
+	return 0;
+}
--- a/sys/gefs/dat.h
+++ b/sys/gefs/dat.h
@@ -11,7 +11,6 @@
 typedef struct Dent	Dent;
 typedef struct Dlist	Dlist;
 typedef struct Errctx	Errctx;
-typedef struct Fmsg	Fmsg;
 typedef struct Gefs	Gefs;
 typedef struct Key	Key;
 typedef struct Kvp	Kvp;
@@ -455,7 +454,7 @@
 			char	new[128];
 			int	flag;
 			char	delete;
-			Fmsg	*m;
+			void	*rz;
 		};
 		struct { /* AOsetcfg AOclrcfg */
 			char	snap[128];
@@ -585,6 +584,8 @@
 	struct rwlock	quitlk;
 	short		quitrz;
 
+	short		ioctlrz;
+
 	struct rwlock	mountlk;
 	Aptr	mounts;	/* Mount* */
 	Mount	*snapmnt;
@@ -607,6 +608,7 @@
 	Along	rdonly;
 	int	noauth;
 	int	usereserve;
+	int	debug;
 
 	/* slow block io */
 	struct rwlock	blklk[32];
--- a/sys/gefs/fns.h
+++ b/sys/gefs/fns.h
@@ -66,6 +66,7 @@
 void	gefs_clunkdent(Mount*, Dent*);
 void	gefs_touch(Dent*, Msg*);
 int	gefs_vget(struct mount*, ino_t, struct vnode**);
+int	gefs_ioctl(void*);
 
 Tree*	gefs_updatesnap(Tree*, char*, int);
 int	gefs_tagsnap(Tree*, char*, int);
--- /dev/null
+++ b/sys/gefs/ioctl.c
@@ -1,0 +1,325 @@
+/*
+ * Copyright (c) 2026
+ *	Ori Bernstein.  All rights reserved.
+ *
+ * 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.
+ *
+ */
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/namei.h>
+#include <sys/proc.h>
+#include <sys/kernel.h>
+#include <sys/vnode.h>
+#include <sys/lock.h>
+#include <sys/rwlock.h>
+#include <sys/mount.h>
+#include <sys/malloc.h>
+#include <sys/pool.h>
+#include <sys/specdev.h>
+#include <sys/gefs.h>
+#include <miscfs/fifofs/fifo.h>
+
+#include "u.h"
+#include "avl.h"
+
+#include "dat.h"
+#include "fns.h"
+
+#ifdef NOTYET
+static void
+fmtdf(Fmt *fmt)
+{
+	char *units[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", nil};
+	vlong size, used;
+	double hsize, hused;
+	Arena *a;
+	int i, us, uu;
+
+	size = 0;
+	used = 0;
+	for(i = 0; i < fs->narena; i++){
+		a = &fs->arenas[i];
+		qlock(a);
+		size += a->size;
+		used += a->used;
+		qunlock(a);
+	}
+	hsize = size;
+	hused = used;
+	for(us = 0; us < nelem(units)-1 && hsize >= 500 ; us++)
+		hsize /= 1024;
+	for(uu = 0; uu < nelem(units)-1 && hused >= 500 ; uu++)
+		hused /= 1024;
+	fmtprint(fmt, "usage \t%.2f%%\t%.2f%s\t%.2f%s\n",
+		100.0*used/size,
+		hused, units[uu],
+		hsize, units[us]);
+}
+
+static void
+showconf(Fmt *fmt, Tree *t, char *name)
+{
+	char pfx[1];
+	int shown;
+	Scan s;
+
+	shown = 0;
+	pfx[0] = Kconf;
+	btnewscan(&s, pfx, 1);
+	btenter(t, &s);
+	while(1){
+		if(!btnext(&s, &s.kv))
+			break;
+		fmtprint(fmt, "conf %q %.*q %.*q\n",
+			name, (int)s.kv.nk-1, s.kv.k+1, (int)s.kv.nv, s.kv.v);
+		shown = 1;
+	}
+	btexit(&s);
+	if(name[0] == 0 && !shown)
+		fmtprint(fmt, "conf retain '' '60@m 24@h @d'\n");
+}
+
+static void
+iterconf(Fmt *fmt, int flg, char *name)
+{
+	Mount *mnt;
+	Tree *t;
+
+	if((flg & Lmut) && !waserror()){
+		mnt = getmount(name);
+		t = agetp(&mnt->root);
+		showconf(fmt, t, name);
+		clunkmount(mnt);
+		poperror();
+	}
+}
+
+static void
+showsnap(Fmt *fmt, int flg, char *name)
+{
+	if(flg & Lmut)
+		fmtprint(fmt, "fork %q\n", name);
+	else
+		fmtprint(fmt, "snap %q\n", name);
+}
+
+static void
+itersnaps(Fmt *fmt, void(*show)(Fmt*, int, char*))
+{
+	char pfx[Snapsz], name[Keymax];
+	Scan s;
+	uint flg;
+	int sz;
+
+	pfx[0] = Klabel;
+	sz = 1;
+	btnewscan(&s, pfx, sz);
+	btenter(&fs->snap, &s);
+	while(1){
+		if(!btnext(&s, &s.kv))
+			break;
+		flg = UNPACK32(s.kv.v+1+8);
+		memcpy(name, s.kv.k+1, s.kv.nk-1);
+		name[s.kv.nk-1] = 0;
+		show(fmt, flg, name);
+	}
+	btexit(&s);
+}
+
+void
+readstatus(Fmsg *m, Fid *f, Fcall *r)
+{
+	Fmt fmt;
+	int n;
+
+	if(f->aux == nil || m->offset == 0){
+		free(f->aux);
+		f->aux = nil;
+		if((fmtstrinit(&fmt)) == -1)
+			error("%r");
+		if(waserror()){
+			free(fmtstrflush(&fmt));
+			nexterror();
+		}
+		fmtprint(&fmt, "gefs p9\n");
+		fmtdf(&fmt);
+		showconf(&fmt, &fs->snap, "");
+		itersnaps(&fmt, iterconf);
+		itersnaps(&fmt, showsnap);
+		f->aux = fmtstrflush(&fmt);
+		poperror();
+	}
+	if(f->aux == nil)
+		error(Efs);
+
+	n = strlen(f->aux);
+	if(m->offset >= n)
+		r->count = 0;
+	else if(m->offset + m->count > n)
+		r->count = n - m->offset;
+	else
+		r->count = m->count;
+	memcpy(r->data, (char*)f->aux+m->offset, r->count);
+}
+#endif
+
+void
+gefs_asend(Gefs *fs, int op, void *rz)
+{
+	Amsg *a;
+
+	a = emalloc(sizeof(Amsg), 1);
+	a->op = op;
+	a->rz = rz;
+	gefs_chsend(fs->admchan, a);
+	if (rz != NULL) {
+	}
+}
+
+int
+gefs_setretain(Gefs *fs, char *snap, char *spec)
+{
+	Amsg *a;
+
+	a = emalloc(sizeof(Amsg), 1);
+	a->rz = &fs->ioctlrz;
+	if(strlcpy(a->key, "retain", sizeof(a->key)) >= sizeof(a->key))
+		panic("wtf");
+	if(strcmp(snap, "-") != 0)
+		if(strlcpy(a->snap, snap, sizeof(a->snap)))
+			goto error;
+	if(strcmp(spec, "default") == 0){
+		a->op = AOclrcfg;
+		a->val[0] = 0;
+	}else{
+		a->op = AOsetcfg;
+		if(strlcpy(a->val, spec, sizeof(a->val)) >= sizeof(a->val))
+			goto error;
+	}
+	gefs_chsend(fs->admchan, a);
+	return 0;
+error:
+	free(a, M_TEMP, sizeof(Amsg));
+	return EINVAL;
+}
+
+int
+gefs_clrsnap(Gefs *fs, char *tag)
+{
+	Amsg *a;
+
+	a = emalloc(sizeof(Amsg), 1);
+	a->op = AOsnap;
+	a->delete = 1;
+	a->rz = &fs->ioctlrz;
+	if(strlcpy(a->old, tag, sizeof(a->old)) >= sizeof(a->old)){
+		free(a, M_TEMP, sizeof(Amsg));
+		return EINVAL;
+	}
+	gefs_chsend(fs->admchan, a);
+	return 0;
+}
+
+int
+gefs_takesnap(Gefs *fs, char *old, char *new, int flag)
+{
+	Amsg *a;
+
+	a = emalloc(sizeof(Amsg), 1);
+	a->op = AOsnap;
+	a->delete = 0;
+	a->flag = flag;
+	a->rz = &fs->ioctlrz;
+	if(strlcpy(a->old, old, sizeof(a->old)) >= sizeof(a->old))
+		goto error;
+	if(strlcpy(a->new, new, sizeof(a->new)) >= sizeof(a->new))
+		goto error;
+	gefs_chsend(fs->admchan, a);
+	return 0;
+error:
+	free(a, M_TEMP, sizeof(Amsg));
+	return EINVAL;
+}
+
+int
+gefs_ioctl(void *p)
+{
+	struct vop_ioctl_args *ap = p;
+	struct gefs_snap *gs;
+	struct gefs_retain *gr;
+	int v, err, res;
+
+	res = -1;
+	rw_enter(&fs->mutlk, RW_WRITE);
+	switch(ap->a_command){
+	default:
+		return EINVAL;
+	case GEFS_SNAP:
+	case GEFS_FORK:
+	case GEFS_DEL:
+		gs = ap->a_data;
+		if(memchr(gs->gs_old, 0, sizeof(gs->gs_old)) == NULL)
+		    return EINVAL;
+		if(memchr(gs->gs_new, 0, sizeof(gs->gs_new)) == NULL)
+		    return EINVAL;
+		switch(ap->a_command){
+		case GEFS_SNAP:
+			err = gefs_takesnap(fs, gs->gs_old, gs->gs_new, 0);
+			break;
+		case GEFS_FORK:
+			err = gefs_takesnap(fs, gs->gs_old, gs->gs_new, Lmut);
+			break;
+		case GEFS_DEL:
+			err = gefs_clrsnap(fs, gs->gs_old);
+			break;
+		}
+		if(err != 0)
+			return err;
+		rwsleep(&fs->ioctlrz, &fs->mutlk, IPL_NONE, "cmdwait", 0);
+		break;
+	case GEFS_RETAIN:
+		gr = ap->a_data;
+		if(memchr(gr->gr_snap, 0, sizeof(gr->gr_snap)) == NULL)
+			return EINVAL;
+		if(memchr(gr->gr_sched, 0, sizeof(gr->gr_sched)) == NULL)
+			return EINVAL;
+		if ((err = gefs_setretain(fs, gr->gr_snap, gr->gr_sched)) != 0)
+			return err;
+		rwsleep(&fs->ioctlrz, &fs->mutlk, IPL_NONE, "cmdwait", 0);
+		break;
+	case GEFS_BALLAST:
+		v = *(int*)ap->a_data;
+		if(v & ~1)
+			return EINVAL;
+		fs->usereserve = v;
+		break;
+	case GEFS_DEBUG:
+		fs->debug =  *(int*)ap->a_data;
+		break;
+	}
+	rw_exit(&fs->mutlk);
+	return 0;
+}
--- a/sys/gefs/sweep.c
+++ b/sys/gefs/sweep.c
@@ -561,22 +561,11 @@
 			break;
 		}
 Next:
-// delayed responses; do we have any?
-//		if(am->m != NULL){
-//			switch(am->m->type){
-//			case Twstat:
-//				r.type = Rwstat;
-//				break;
-//			case Twrite:
-//				r.type = Rwrite;
-//				r.count = am->m->count;
-//				break;
-//			default:
-//				abort();
-//			}
-//			respond(am->m, &r);
-//		}
-//		assert(estacksz() == 0);
+		if(am->rz != NULL){
+			rw_enter(&fs->mutlk, RW_WRITE);
+			wakeup(am->rz);
+			rw_exit(&fs->mutlk);
+		}
 		gefs_freeamsg(am);
 	}
 }
--- a/sys/gefs/vnops.c
+++ b/sys/gefs/vnops.c
@@ -1643,7 +1643,7 @@
 	.vop_setattr	= gefs_setattr,
 	.vop_read	= gefs_read,
 	.vop_write	= gefs_write,
-	.vop_ioctl	= (void *)eopnotsupp,
+	.vop_ioctl	= gefs_ioctl,
 	.vop_kqfilter	= (void *)eopnotsupp,
 	.vop_revoke	= vop_generic_revoke,
 	.vop_fsync	= gefs_fsync,
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -516,9 +516,9 @@
 	struct vattr vattr;
 	int error = ENOTTY;
 
+	com, vp->v_type, VREG, VDIR);
 	KERNEL_LOCK();
 	switch (vp->v_type) {
-
 	case VREG:
 	case VDIR:
 		if (com == FIONREAD) {
@@ -526,11 +526,12 @@
 			if (error)
 				break;
 			*(int *)data = vattr.va_size - foffset(fp);
-
-		} else if (com == FIOASYNC)			/* XXX */
+			break;
+		} else if (com == FIOASYNC) {			/* XXX */
 			error = 0;				/* XXX */
-		break;
-
+			break;
+		}
+		/* wet floor */
 	case VFIFO:
 	case VCHR:
 	case VBLK:
--- /dev/null
+++ b/sys/sys/gefs.h
@@ -1,0 +1,24 @@
+#ifndef _SYS_GEFS_H_
+#define _SYS_GEFS_H_
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioccom.h>
+
+struct gefs_snap {
+	char	gs_old[256];
+	char	gs_new[256];
+};
+
+struct gefs_retain {
+	char	gr_snap[256];
+	char	gr_sched[256];
+};
+
+#define GEFS_SNAP	_IOW('G', 1, struct gefs_snap)
+#define GEFS_FORK	_IOW('G', 2, struct gefs_snap)
+#define GEFS_DEL	_IOW('G', 3, struct gefs_snap)
+#define GEFS_RETAIN	_IOW('G', 4, struct gefs_retain)
+#define GEFS_BALLAST	_IOW('G', 5, int)	/* 0 or 1 for enabling ballast */
+#define GEFS_DEBUG	_IOW('G', 6, int)	/* debug level */
+
+#endif
-- 
⑨