ref: f28f9f58f7a733f91fca9145569b90f9bdaea27f
parent: 56b5223ad352fcef003925e8400a3ce1e49c3fbd
author: Ori Bernstein <ori@eigenstate.org>
date: Fri Oct 31 13:55:31 EDT 2025
gefs: add basic fuzzer
--- a/sys/src/cmd/gefs/fns.h
+++ b/sys/src/cmd/gefs/fns.h
@@ -17,6 +17,8 @@
extern int noneid;
extern int nogroupid;
extern int admid;
+extern int fuzzonly;
+extern ulong fuzzseed;
#define UNPACK8(p) (((uchar*)(p))[0])
#define UNPACK16(p) ((((uchar*)(p))[0]<<8)|(((uchar*)(p))[1]))
@@ -209,3 +211,9 @@
void runsync(int, void*);
void runsweep(int, void*);
void runsnap(int, void*);
+
+/* fuzzing and testing */
+void fzinit(void);
+void fzwrite(int, void*);
+void fzscan(int, void*);
+
--- /dev/null
+++ b/sys/src/cmd/gefs/fuzz.c
@@ -1,0 +1,603 @@
+#include <u.h>
+#include <libc.h>
+#include <avl.h>
+#include <fcall.h>
+#include <bio.h>
+
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Fuzz Fuzz;
+typedef struct Shadow Shadow;
+
+struct Shadow {+ Avl;
+ Kvp;
+ char buf[Kvmax];
+};
+
+struct Fuzz {+ Mount *mnt;
+ int nops;
+};
+
+Fuzz *fz;
+Avltree *shadow;
+Lock shadowlk;
+int nshadow;
+
+static uvlong
+vrand(void)
+{+ uvlong v;
+ v = nrand(1<<4);
+ v = (v<<30) | nrand(1<<30);
+ v = (v<<30) | nrand(1<<30);
+ return v;
+}
+
+static void
+fail(char *fmt, ...)
+{+ Biobuf *bfd;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+ fprint(2, "seed: %lux\n", fuzzseed);
+ fprint(2, "trace: /tmp/fuzz.trace");
+ bfd = Bopen("/tmp/fuzz.trace", OWRITE);+ writetrace(bfd, -1);
+ Bterm(bfd);
+ postnote(PNGROUP, getpid(), "kill");
+ abort();
+}
+
+static void
+genname(char *name, int *nname)
+{+ int n, i;
+ char c;
+
+ n = 1 + nrand(244);
+ for(i = 0; i < n; i++){+ do {+ c = nrand(256);
+ } while(c == '/' || c == '\0');
+ name[i] = c;
+ }
+ *nname = n;
+}
+
+static void
+genent(char *k, int *nk)
+{+ char name[256], *p;
+ vlong pqid;
+ int nname;
+
+ pqid = (vlong)nrand(1<<30) << 32 | nrand(1<<30);
+ genname(name, &nname);
+ name[nname] = '\0';
+ p = packdkey(k, Keymax, pqid, name);
+ *nk = p - k;
+}
+
+static void
+gendir(char *v, short *nv)
+{+ char *p;
+ Xdir d;
+
+ memset(&d, 0, sizeof(d));
+ d.flag = 0;
+ d.qid.path = vrand();
+ d.qid.vers = nrand(1<<20);
+ d.qid.type = nrand(2) ? QTFILE : QTDIR;
+ d.mode = nrand(2) ? 0644 : (DMDIR|0755);
+ d.atime = vrand();
+ d.mtime = vrand();
+ d.length = nrand(1<<20);
+ d.uid = nrand(100);
+ d.gid = nrand(100);
+ d.muid = nrand(100);
+
+ p = packdval(v, Inlmax, &d);
+ *nv = p - v;
+}
+
+static void
+genins(Msg *m, char *kbuf, char *vbuf)
+{+ m->op = Oinsert;
+ m->k = kbuf;
+ genent(kbuf, &m->nk);
+ m->v = vbuf;
+ gendir(vbuf, &m->nv);
+}
+
+static void
+gendel(Msg *m, Key *k, char *kbuf)
+{+ memcpy(kbuf, k->k, k->nk);
+ m->op = Odelete;
+ m->k = kbuf;
+ m->nk = k->nk;
+ m->v = nil;
+ m->nv = 0;
+}
+
+static void
+genwstat(Msg *m, Key *k, char *kbuf, char *vbuf)
+{+ char *p, flags;
+ vlong size, mtime, atime;
+ ulong mode;
+ int uid, gid, muid;
+
+ memcpy(kbuf, k->k, k->nk);
+ m->op = Owstat;
+ m->k = kbuf;
+ m->nk = k->nk;
+ m->v = vbuf;
+
+ p = vbuf;
+ do {+ flags = nrand(128);
+ } while(flags == 0);
+ *p++ = flags;
+ if(flags & Owsize){+ size = nrand(1<<20);
+ PACK64(p, size);
+ p += 8;
+ }
+ if(flags & Owmode){+ mode = nrand(2) ? 0644 : (DMDIR|0755);
+ PACK32(p, mode);
+ p += 4;
+ }
+ if(flags & Owmtime){+ mtime = vrand();
+ PACK64(p, mtime);
+ p += 8;
+ }
+ if(flags & Owatime){+ atime = vrand();
+ PACK64(p, atime);
+ p += 8;
+ }
+ if(flags & Owuid){+ uid = nrand(100);
+ PACK32(p, uid);
+ p += 4;
+ }
+ if(flags & Owgid){+ gid = nrand(100);
+ PACK32(p, gid);
+ p += 4;
+ }
+ if(flags & Owmuid){+ muid = nrand(100);
+ PACK32(p, muid);
+ p += 4;
+ }
+
+ m->nv = p - vbuf;
+}
+
+static int
+shadowcmp(Avl *a, Avl *b)
+{+ Shadow *sa, *sb;
+
+ sa = (Shadow*)a;
+ sb = (Shadow*)b;
+ return keycmp(sa, sb);
+}
+
+static Shadow*
+pickrand(void)
+{+ Avl *a, *c;
+ int n, i;
+
+ if(shadow->root == nil)
+ return nil;
+ n = nrand(nshadow);
+ a = shadow->root;
+ i = nshadow;
+ while(i > 1){+ i /= 2;
+ c = (n < i) ? a->c[0] : a->c[1];
+ if(c == nil)
+ break;
+ a = c;
+ }
+ return (Shadow*)a;
+}
+
+static int
+shadowins(Avltree *t, Msg *m)
+{+ Shadow probe, *s, *old;
+
+ probe.k = probe.buf;
+ probe.v = probe.buf;
+ cpkey(&probe, m, probe.buf, sizeof(probe.buf));
+ old = (Shadow*)avllookup(t, &probe, 0);
+ if(old != nil){+ memmove(old->v, m->v, m->nv);
+ old->nv = m->nv;
+ return 0;
+ }
+ s = malloc(sizeof(Shadow));
+ s->k = s->buf;
+ s->v = s->buf;
+ cpkvp(s, m, s->buf, sizeof(s->buf));
+ avlinsert(t, s);
+ return 1;
+}
+
+void
+shadowdel(Avltree *t, Msg *m)
+{+ Shadow probe, *s;
+
+ probe.k = probe.buf;
+ probe.v = probe.buf;
+ cpkey(&probe, m, probe.buf, sizeof(probe.buf));
+ s = (Shadow*)avllookup(t, &probe, 0);
+ if(s != nil){+ avldelete(t, s);
+ free(s);
+ }
+}
+
+void
+shadowwstat(Avltree *t, Msg *m)
+{+ Shadow probe, *s;
+ char *p, *v, flags;
+ vlong qidpath, qidvers, length, atime, mtime;
+ ulong mode, qidtype;
+ int uid, gid, muid;
+
+ probe.k = probe.buf;
+ probe.v = probe.buf;
+ cpkey(&probe, m, probe.buf, sizeof(probe.buf));
+ s = (Shadow*)avllookup(t, &probe, 0);
+ v = s->v;
+ v += 8;
+ qidpath = UNPACK64(v); v += 8;
+ qidvers = UNPACK32(v); v += 4;
+ qidtype = UNPACK8(v); v += 1;
+ mode = UNPACK32(v); v += 4;
+ atime = UNPACK64(v); v += 8;
+ mtime = UNPACK64(v); v += 8;
+ length = UNPACK64(v); v += 8;
+ uid = UNPACK32(v); v += 4;
+ gid = UNPACK32(v); v += 4;
+ muid = UNPACK32(v); v += 4;
+ assert(v == s->v + s->nv);
+
+ p = m->v;
+ flags = *p++;
+ if(flags & Owsize){+ length = UNPACK64(p);
+ p += 8;
+ }
+ if(flags & Owmode){+ mode = UNPACK32(p);
+ qidtype = mode>>24;
+ p += 4;
+ }
+ if(flags & Owmtime){+ mtime = UNPACK64(p);
+ p += 8;
+ }
+ if(flags & Owatime){+ atime = UNPACK64(p);
+ p += 8;
+ }
+ if(flags & Owuid){+ uid = UNPACK32(p);
+ p += 4;
+ }
+ if(flags & Owgid){+ gid = UNPACK32(p);
+ p += 4;
+ }
+ if(flags & Owmuid){+ muid = UNPACK32(p);
+ p += 4;
+ }
+ assert(p == m->v + m->nv);
+
+ qidvers++;
+
+ v = s->v;
+ PACK64(v, 0); v += 8;
+ PACK64(v, qidpath); v += 8;
+ PACK32(v, qidvers); v += 4;
+ PACK8(v, qidtype); v += 1;
+ PACK32(v, mode); v += 4;
+ PACK64(v, atime); v += 8;
+ PACK64(v, mtime); v += 8;
+ PACK64(v, length); v += 8;
+ PACK32(v, uid); v += 4;
+ PACK32(v, gid); v += 4;
+ PACK32(v, muid); v += 4;
+
+ s->nv = v - s->v;
+}
+
+static void
+shadowapply(Msg *m)
+{+ switch(m->op){+ case Oinsert:
+ if(shadowins(shadow, m))
+ nshadow++;
+ break;
+ case Odelete:
+ shadowdel(shadow, m);
+ nshadow--;
+ break;
+ case Owstat:
+ shadowwstat(shadow, m);
+ break;
+ }
+}
+
+static int
+deleted(Msg *m, int nm, Key *k)
+{+ int i, del;
+
+ if(k == nil)
+ return 1;
+ del = 0;
+ for(i = 0; i < nm; i++){+ if(keycmp(&m[i], k) != 0)
+ continue;
+ if(m[i].op == Odelete)
+ del = 1;
+ if(m[i].op == Oinsert)
+ del = 0;
+ }
+ return del;
+}
+
+static void
+genrand(Msg *m, char *kbuf, char *vbuf, Msg *batch, int nbatch)
+{+ Shadow *s;
+ int op, d;
+
+ s = pickrand();
+ op = nrand(100);
+ /* If deleted, we need to do an insert or a replacement */
+ d = deleted(batch, nbatch, s);
+ if(d) d += nrand(2);
+ /* 40% chance of new insertion */
+ if(s == nil || op < 40 || d == 1){+ genins(m, kbuf, vbuf);
+ return;
+ }
+ /* 20% chance of clobber */
+ if(op < 60 || d == 2){+ memmove(kbuf, s->k, s->nk);
+ m->op = Oinsert;
+ m->k = kbuf;
+ m->nk = s->nk;
+ m->v = vbuf;
+ gendir(vbuf, &m->nv);
+ return;
+ }
+ /* 20% of Owstat */
+ if(op < 80){+ genwstat(m, s, kbuf, vbuf);
+ return;
+ }
+ /* 20% chance of delete */
+ if(op < 100){+ gendel(m, s, kbuf);
+ return;
+ }
+ abort();
+}
+
+void
+fzupsert(int tid)
+{+ char kbuf[8][Keymax], vbuf[8][Inlmax];
+ Msg m[8];
+ Tree *r, t;
+ int i, nm;
+
+ /*
+ * A bit of a hack: to prevent tearing between the shadow
+ * tree and the real tree, we need to apply the messages
+ * while holding the shadow lock.
+ *
+ * However, because we want to scan concurrently with
+ * tree mutations, we can't hold the shadow lock across
+ * the upsert, so we need to keep the tree somewhere
+ * temporary as we do the update.
+ */
+ lock(&shadowlk);
+ memset(&t, 0, sizeof(Tree));
+ r = agetp(&fz->mnt->root);
+ lock(&r->lk);
+ t.bp = r->bp;
+ t.ht = r->ht;
+ unlock(&r->lk);
+ nm = 1 + nrand(nelem(m)-1);
+ for(i = 0; i < nm; i++)
+ genrand(&m[i], kbuf[i], vbuf[i], m, i);
+ unlock(&shadowlk);
+
+ if(waserror()){+ fprint(2, "btupsert failed, nm=%d: %s\n", nm, errmsg());
+ fprint(2, "nops: %d, nshadow %d\n", fz->nops, nshadow);
+ fprint(2, "current batch:\n");
+ for(i = 0; i < nm; i++)
+ fprint(2, " msg[%d]: %M\n", i, &m[i]);
+ fail("btupsert failure\n");+ }
+ qlock(&fs->mutlk);
+ epochstart(tid);
+ btupsert(&t, m, nm);
+ epochend(tid);
+ qunlock(&fs->mutlk);
+ poperror();
+
+ lock(&shadowlk);
+ for(i = 0; i < nm; i++)
+ shadowapply(&m[i]);
+ r = agetp(&fz->mnt->root);
+ lock(&r->lk);
+ r->bp = t.bp;
+ r->ht = t.ht;
+ unlock(&r->lk);
+ fz->nops += nm;
+ unlock(&shadowlk);
+ epochclean();
+}
+
+void
+fzwrite(int tid, void *)
+{+ char bksp[512];
+ int w;
+
+ w = 0;
+ memset(bksp, '\b', sizeof(bksp));
+ bksp[sizeof(bksp)-1] = 0;
+ while(1){+ fzupsert(tid);
+ if(fz->nops % 1000 == 0){+ fprint(2, "%.*s", (w>0)?w:0, bksp);
+ w = fprint(2, "[%d ops] %d entries\n", fz->nops, nshadow);
+ }
+ }
+}
+
+static void
+look1(int nsamp, int tid)
+{+ Shadow *s;
+ char buf[Kvmax];
+ Tree *t;
+ Kvp kv;
+ int i, ok;
+
+ lock(&shadowlk);
+ epochstart(tid);
+ t = agetp(&fz->mnt->root);
+ for(i = 0; i < nsamp; i++){+ if(nshadow == 0)
+ break;
+ s = pickrand();
+ ok = btlookup(t, s, &kv, buf, sizeof(buf));
+ if(!ok)
+ fail("key %K not found in tree, nops: %d\n", &s->Key, fz->nops);+ if(kv.nv != s->nv || memcmp(kv.v, s->v, s->nv) != 0)
+ fail("value mismatch for key %P <> %P \n", &kv, &s->Kvp);+ }
+ epochend(tid);
+ unlock(&shadowlk);
+}
+
+static void
+scan1(int tid)
+{+ Shadow *s;
+ Tree *t;
+ Scan sc;
+ Kvp kv;
+ int i;
+
+ lock(&shadowlk);
+ epochstart(tid);
+ t = agetp(&fz->mnt->root);
+ i = 0;
+ btnewscan(&sc, nil, 0);
+ btenter(t, &sc);
+ for(s = (Shadow*)avlmin(shadow); s != nil; s = (Shadow*)avlnext(s)){+ if(!btnext(&sc, &kv))
+ fail("shadow key %K not found in tree at i=%d\n", &s->Key, i);+ if(kv.nk != s->nk
+ || kv.nv != s->nv
+ || memcmp(kv.k, s->k, s->nk) != 0
+ || memcmp(kv.v, s->v, s->nv)){+ fprint(2, "kvp mismatch at i=%d, nops=%d, t=%B\n", i, fz->nops, t->bp);
+ fprint(2, "shadow: %P\n", &s->Kvp);
+ fprint(2, "tree: %P\n", &kv);
+ fail("kvp mismatch\n");+ }
+ i++;
+ }
+ btexit(&sc);
+ epochend(tid);
+ unlock(&shadowlk);
+}
+
+void
+fzscan(int tid, void *)
+{+ while(1){+ /*
+ * Because we hold the lock for a long time while
+ * doing scans as the tree grows, give the writer
+ * proc more time to do its thing.
+ */
+ sleep(1000);
+ look1(100, tid);
+ scan1(tid);
+ }
+}
+
+void
+fzinit(void)
+{+ char buf[Kvmax];
+ Shadow *s;
+ Tree *empty, *t;
+ Scan sc;
+ Kvp kv;
+
+ if(opensnap("fuzz", nil) != nil)+ sysfatal("fuzz snapshot already exists");+
+ if((empty = opensnap("empty", nil)) == nil)+ sysfatal("opensnap empty: %r");+ qlock(&fs->mutlk);
+ tagsnap(empty, "fuzz", Lmut);
+ qunlock(&fs->mutlk);
+ closesnap(empty);
+
+ shadow = avlcreate(shadowcmp);
+ fz = emalloc(sizeof(Fuzz), 1);
+ if((fz->mnt = getmount("fuzz")) == nil)+ sysfatal("getmount fuzz: %r");+ fz->nops = 0;
+
+ srand(fuzzseed);
+ buf[0] = Kent;
+ btnewscan(&sc, buf, 1);
+ t = agetp(&fz->mnt->root);
+ btenter(t, &sc);
+ while(btnext(&sc, &kv)){+ if(kv.k[0] != Kent)
+ break;
+ s = emalloc(sizeof(Shadow), 1);
+ s->k = s->buf;
+ s->v = s->buf;
+ cpkvp(s, &kv, s->buf, sizeof(s->buf));
+ avlinsert(shadow, s);
+ nshadow++;
+ }
+ btexit(&sc);
+}
--- a/sys/src/cmd/gefs/main.c
+++ b/sys/src/cmd/gefs/main.c
@@ -18,6 +18,7 @@
int permissive;
int usereserve;
int checkonly;
+ulong fuzzseed;
char *reamuser;
char *dev;
vlong tracesz = 16*MiB;
@@ -369,6 +370,9 @@
sysfatal("unknown suffix %s", e);}
break;
+ case 'z':
+ fuzzseed = strtoul(EARGF(usage()), &e, 0);
+ break;
case 'd':
debug++;
break;
@@ -477,6 +481,13 @@
xlaunch(runread, fs->rdchan[i], aincl(&fs->nworker, 1), "readio");
for(i = 0; i < fs->nsyncers; i++)
xlaunch(runsync, &fs->syncq[i], aincl(&fs->nworker, 1), "syncio");
+ if(fuzzseed != 0){+ print("fuzzing (seed: %lux)...\n", fuzzseed);+ fzinit();
+ xlaunch(fzscan, nil, aincl(&fs->nworker, 1), "fzcheck");
+ xlaunch(fzwrite, nil, aincl(&fs->nworker, 1), "fzwrite");
+ exits(nil);
+ }
for(i = 0; i < nann; i++)
xlaunch(runannounce, ann[i], -1, "announce");
if(srvfd != -1){--- a/sys/src/cmd/gefs/mkfile
+++ b/sys/src/cmd/gefs/mkfile
@@ -10,6 +10,7 @@
dump.$O\
error.$O\
fs.$O\
+ fuzz.$O\
hash.$O\
load.$O\
main.$O\
--
⑨