From: ori@eigenstate.org Subject: gefs vs ioctls To: tech@openbsd.org Date: Mon, 02 Nov 2026 17:12:18 -0500 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 --- /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 + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +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 -- ⑨