ref: c53182c3ef7d2360eb9de535e217e1d30fb15002
parent: 831c8b619cf7731d747a9b0f6c3d2548a28b74ea
author: Ori Bernstein <ori@eigenstate.org>
date: Sat Mar 8 18:13:29 EST 2025
gefs: allow custom snapshot schedules
--- a/sys/man/8/gefs
+++ b/sys/man/8/gefs
@@ -22,9 +22,22 @@
.B save trace
.I filename
.PP
+.B set
+[
+.I snap
+]
+.I key
+.I val
+.PP
+.B clr
+[
+.I snap
+]
+.I key
+.PP
.B snap
[
--Smdl
+-mdl
]
[
.I old
@@ -83,6 +96,46 @@
to modify the owner of the file.
This may be useful during file system initialization.
.PP
+.I Set
+or
+.I clr
+sets or clears a configuration key, respectively.
+If the
+.I snap
+name is passed, then the key is set in that snapshot.
+If not, then it is set globally.
+This key is a free form string which controls
+some aspect of file system behavior on that snapshot.
+All changes take effect after the file system is restarted.
+Currently, one configuration option is supported:
+.TP
+.BI retain 'timespec'
+sets the schedule for which snapshots are retained.
+The timespec is a sequence of counts and intervals,
+in the form <
+.BR count >@<
+.BR interval ><
+.BR scale >.
+Every
+.BR interval ,
+a new snapshot is taken.
+Up to
+.B count
+snapshots are retained in the history, with old ones aging out.
+If the
+.B count
+is omitted, then old snapshots are not trimmed.
+If the
+.B interval
+is omitted, it defaults to 1.
+.IP
+Snapshot timing is taken from the config key attached to the snapshot.
+If there is no key attached to the snapshot,
+the global config key is used.
+If there is no global config key, the default value
+.B `60@1m 24@1h @1d'
+is used.
+.PP
.B Snap
manages snapshots.
It can be invoked as
@@ -123,10 +176,6 @@
This flag only has an effect when tagging a new snapshot,
and is ignored otherwise.
.TP
-.B -S
-Disables automatic timed snapshots.
-This flag only has an effect when tagging a new snapshot,
-and is ignored otherwise.
.PP
.I Sync
writes dirty blocks in memory to the disk.
@@ -239,7 +288,8 @@
checkpoints:
.IP
.EX
-gefs# snap -Sm main mymutable
+gefs# snap -m main mymutable
+gefs# set mymutable retain ''
.EE
.PP
To delete a snapshot:
@@ -246,6 +296,22 @@
.IP
.EX
gefs# snap -d mysnap
+.EE
+.PP
+To set the snapshot retention on the main snapshot
+to 12 snapshots taken every five minutes,
+along with 48 snapshots taken every 2 hours,
+and a daily snapshot with no trimming:
+.IP
+.EX
+gefs# set main retain '12@5m 48@2h @d'
+.EE
+.PP
+To set the default snapshot retention to daily,
+with no minute or hour snapshots:
+.IP
+.EX
+gefs# set retain @d
.EE
.PP
To create a new user:
--- a/sys/src/cmd/gefs/cons.c
+++ b/sys/src/cmd/gefs/cons.c
@@ -67,16 +67,8 @@
break;
flg = UNPACK32(s.kv.v+1+8);
fprint(fd, "snap %.*s", s.kv.nk-1, s.kv.k+1);
- if(flg != 0)
- fprint(fd, " [");
if(flg & Lmut)
- fprint(fd, " mutable");
- if(flg & Lauto)
- fprint(fd, " auto");
- if(flg & Ltsnap)
- fprint(fd, " tsnap");
- if(flg != 0)
- fprint(fd, " ]");
+ fprint(fd, " [mutable]");
fprint(fd, "\n");
}
btexit(&s);
@@ -88,17 +80,12 @@
Amsg *a;
int i;
- if((a = mallocz(sizeof(Amsg), 1)) == nil){
- fprint(fd, "alloc sync msg: %r\n");
- return;
- }
+ a = emalloc(sizeof(Amsg), 1);
a->op = AOsnap;
a->fd = fd;
- a->flag = Ltsnap;
while(ap[0][0] == '-'){
for(i = 1; ap[0][i]; i++){
switch(ap[0][i]){
- case 'S': a->flag &= ~Ltsnap; break;
case 'm': a->flag |= Lmut; break;
case 'd': a->delete++; break;
case 'l':
@@ -364,11 +351,42 @@
}
static void
+setcfg(int fd, char **ap, int n)
+{
+ Amsg *a;
+
+ a = emalloc(sizeof(Amsg), 1);
+ a->op = AOsetcfg;
+ a->fd = fd;
+ if(n == 3)
+ strecpy(a->snap, a->snap+sizeof(a->snap), *ap++);
+ strecpy(a->key, a->key+sizeof(a->key), *ap++);
+ strecpy(a->val, a->val+sizeof(a->val), *ap);
+ chsend(fs->admchan, a);
+}
+
+static void
+clrcfg(int fd, char **ap, int n)
+{
+ Amsg *a;
+
+ a = emalloc(sizeof(Amsg), 1);
+ a->op = AOclrcfg;
+ a->fd = fd;
+ if(n == 2)
+ strecpy(a->snap, a->snap+sizeof(a->snap), *ap++);
+ strecpy(a->key, a->key+sizeof(a->key), *ap);
+ chsend(fs->admchan, a);
+}
+
+static void
help(int fd, char**, int)
{
char *msg =
"help -- show this help\n"
"check -- check for consistency\n"
+ "set [snap] key val -- set configuration key\n"
+ "clear [snap] key -- clear configuration key\n"
"df -- show disk usage\n"
"halt -- stop all writers, sync, and go read-only\n"
"permit [on|off] -- switch to/from permissive mode\n"
@@ -387,6 +405,8 @@
Cmd cmdtab[] = {
/* admin */
{.name="check", .sub=nil, .minarg=0, .maxarg=0, .fn=fsckfs, .epoch=1},
+ {.name="set", .sub=nil, .minarg=2, .maxarg=3, .fn=setcfg},
+ {.name="clear", .sub=nil, .minarg=1, .maxarg=2, .fn=clrcfg},
{.name="df", .sub=nil, .minarg=0, .maxarg=0, .fn=showdf},
{.name="halt", .sub=nil, .minarg=0, .maxarg=0, .fn=haltfs},
{.name="help", .sub=nil, .minarg=0, .maxarg=0, .fn=help},
--- a/sys/src/cmd/gefs/dat.h
+++ b/sys/src/cmd/gefs/dat.h
@@ -1,32 +1,33 @@
-typedef struct Blk Blk;
typedef struct Amsg Amsg;
-typedef struct Gefs Gefs;
+typedef struct Arange Arange;
+typedef struct Arena Arena;
+typedef struct Bfree Bfree;
+typedef struct Blk Blk;
+typedef struct Bptr Bptr;
+typedef struct Bucket Bucket;
+typedef struct Chan Chan;
+typedef struct Conn Conn;
+typedef struct Cron Cron;
+typedef struct Dent Dent;
+typedef struct Dlist Dlist;
typedef struct Errctx Errctx;
-typedef struct Fmsg Fmsg;
typedef struct Fid Fid;
-typedef struct Msg Msg;
+typedef struct Fmsg Fmsg;
+typedef struct Gefs Gefs;
typedef struct Key Key;
-typedef struct Val Val;
typedef struct Kvp Kvp;
-typedef struct Xdir Xdir;
-typedef struct Bptr Bptr;
typedef struct Limbo Limbo;
-typedef struct Bfree Bfree;
+typedef struct Mount Mount;
+typedef struct Msg Msg;
+typedef struct Qent Qent;
typedef struct Scan Scan;
-typedef struct Dent Dent;
typedef struct Scanp Scanp;
-typedef struct Arena Arena;
-typedef struct Arange Arange;
-typedef struct Bucket Bucket;
-typedef struct Chan Chan;
typedef struct Syncq Syncq;
-typedef struct Qent Qent;
typedef struct Trace Trace;
typedef struct Tree Tree;
-typedef struct Dlist Dlist;
-typedef struct Mount Mount;
typedef struct User User;
-typedef struct Conn Conn;
+typedef struct Val Val;
+typedef struct Xdir Xdir;
enum {
KiB = 1024ULL,
@@ -107,6 +108,9 @@
Klabel, /* name[] => snapid[]: snapshot label */
Ksnap, /* sid[8] => ref[8], tree[52]: snapshot root */
Kdlist, /* snap[8] gen[8] => hd[ptr],tl[ptr] deadlist */
+
+ /* configuration */
+ Kconf, /* name[] => value[] */
};
enum {
@@ -121,12 +125,13 @@
enum {
Lmut = 1 << 0, /* can we modify snaps via this label */
- Lauto = 1 << 1, /* was this label generated automatically */
- Ltsnap = 1 << 2, /* should we skip the timed snapshots */
+ _Lauto = 1 << 1, /* deprecated: was auto snap */
+ _Ltsnap = 1 << 2, /* deprecated: was skipping timed snaps */
};
enum {
- Qdump = 1ULL << 63,
+ Qdump = 1ULL << 63,
+ Qctl = ~0ULL,
};
#define Zb (Bptr){-1, -1, -1}
@@ -326,6 +331,8 @@
AOhalt,
AOclear,
AOrclose,
+ AOsetcfg,
+ AOclrcfg,
};
enum {
@@ -405,8 +412,12 @@
char new[128];
int flag;
char delete;
-
};
+ struct { /* AOsetcfg AOclrcfg */
+ char snap[128];
+ char key[32];
+ char val[128];
+ };
struct { /* AOclear, AOrclose */
Mount *mnt;
Dent *dent;
@@ -635,6 +646,15 @@
};
};
+struct Cron {
+ int i;
+ int cnt;
+ vlong div;
+ vlong last;
+ char *tag;
+ char (*lbl)[128];
+};
+
struct Mount {
Limbo;
Lock;
@@ -643,7 +663,6 @@
vlong gen;
char name[64];
Tree *root; /* EBR protected */
-
int flag;
/* open directory entries */
@@ -650,9 +669,8 @@
Lock dtablk;
Dent *dtab[Ndtab];
- /* snapshot history */
- char minutely[60][128];
- char hourly[24][128];
+ /* snapshot scheduling */
+ Cron cron[3];
};
struct Conn {
--- a/sys/src/cmd/gefs/dump.c
+++ b/sys/src/cmd/gefs/dump.c
@@ -43,6 +43,9 @@
n = fmtprint(fmt, "dlist gen:%lld, bgen:%lld",
UNPACK64(k->k+1), UNPACK64(k->k+9));
break;
+ case Kconf:
+ n = fmtprint(fmt, "Conf %.*s", k->nk-1, k->k+1);
+ break;
default:
n = fmtprint(fmt, "??? %.*H", k->nk, k->k);
break;
@@ -165,6 +168,9 @@
n = fmtprint(fmt, "hd:%B, tl:%B",
unpackbp(v->v, v->nv),
unpackbp(v->v+Ptrsz, v->nv-Ptrsz));
+ break;
+ case Kconf:
+ n = fmtprint(fmt, "%.*s", v->nv, v->v);
break;
default:
n = fmtprint(fmt, "??? %.*H", v->nk, v->k);
--- a/sys/src/cmd/gefs/fs.c
+++ b/sys/src/cmd/gefs/fs.c
@@ -11,7 +11,7 @@
static void respond(Fmsg*, Fcall*);
static void rerror(Fmsg*, char*, ...);
static void clunkfid(Conn*, Fid*, Amsg**);
-
+static void snapmsg(char*, char*);
static void authfree(AuthRpc*);
int
@@ -573,49 +573,98 @@
return de;
}
-static void
-loadautos(Mount *mnt)
+/*
+ * Load snapshots into history, trimming old ones if we have more
+ * snapshots than expected (eg, reducing the count in the config).
+ */
+void
+loadhist(Mount *mnt, Cron *c)
{
char pfx[128];
- int m, h, ns;
- uint flg;
+ int i, ns;
Scan s;
- m = 0;
- h = 0;
+ if(c->cnt == 0)
+ return;
+ i = 0;
pfx[0] = Klabel;
- ns = snprint(pfx+1, sizeof(pfx)-1, "%s@minute.", mnt->name);
+ ns = snprint(pfx+1, sizeof(pfx)-1, "%s@%s.", mnt->name, c->tag);
btnewscan(&s, pfx, ns+1);
btenter(&fs->snap, &s);
while(1){
if(!btnext(&s, &s.kv))
break;
- flg = UNPACK32(s.kv.v+1+8);
- if(flg & Lauto){
- memcpy(mnt->minutely[m], s.kv.k+1, s.kv.nk-1);
- mnt->minutely[m][s.kv.nk-1] = 0;
- m = (m+1)%60;
- continue;
- }
+ if(c->lbl[i][0] != 0)
+ snapmsg(c->lbl[i], nil);
+ assert(s.kv.nk-1 < sizeof(c->lbl[i]));
+ memcpy(c->lbl[i], s.kv.k+1, s.kv.nk-1);
+ c->lbl[i][s.kv.nk-1] = 0;
+ i = (i+1) % c->cnt;
}
btexit(&s);
+ c->last = time(nil);
+ c->i = i;
+}
- pfx[0] = Klabel;
- ns = snprint(pfx+1, sizeof(pfx)-1, "%s@hour.", mnt->name);
- btnewscan(&s, pfx, ns+1);
- btenter(&fs->snap, &s);
- while(1){
- if(!btnext(&s, &s.kv))
- break;
- flg = UNPACK32(s.kv.v+1+8);
- if(flg & Lauto){
- memcpy(mnt->hourly[h], s.kv.k+1, s.kv.nk-1);
- mnt->hourly[h][s.kv.nk-1] = 0;
- h = (h+1)%24;
- continue;
+/*
+ * Load up automatic snapshots. Don't error on bad config,
+ * so that the user can mount the snap and fix it.
+ */
+static void
+loadautos(Mount *mnt)
+{
+ static char *tagname[] = {"minute", "hour", "day"};
+ static int scale[] = {60, 3600, 24*3600};
+ char *p, pfx[128], rbuf[128];
+ int i, n, div, cnt, op;
+ Kvp kv;
+
+ pfx[0] = Kconf;
+ n = snprint(pfx+1, sizeof(pfx)-1, "retain");
+ kv.k = pfx;
+ kv.nk = n+1;
+ if(btlookup(mnt->root, &kv, &kv, rbuf, sizeof(rbuf)-1)
+ || btlookup(&fs->snap, &kv, &kv, rbuf, sizeof(rbuf)-1)){
+ p[kv.nv] = 0;
+ p = kv.v;
+ }else
+ p = "60@m 24@h @d";
+ while(*p){
+ cnt = 0;
+ div = 1;
+ op = -1;
+
+ /* parse the conf. */
+ if(*p >= '0' && *p <= '9')
+ cnt = strtol(p, &p, 10);
+ if(*p++ != '@')
+ goto Bad;
+ if(*p >= '0' && *p <= '9')
+ div = strtol(p, &p, 10);
+ if(*p == 'm' || *p == 'h' || *p == 'd')
+ op = *p++;
+ while(*p == ' ' || *p == '\t')
+ p++;
+ if(cnt < 0 || div <= 0){
+Bad: memset(mnt->cron, 0, sizeof(mnt->cron));
+ fprint(2, "invalid time spec\n");
+ return;
}
+
+ switch(op){
+ case 'm': i = 0; break;
+ case 'h': i = 1; break;
+ case 'd': i = 2; break;
+ default: abort();
+ }
+
+ mnt->cron[i].tag = tagname[i];
+ mnt->cron[i].div = scale[i]*div;
+ mnt->cron[i].cnt = cnt;
+ mnt->cron[i].lbl = emalloc(cnt*sizeof(char[128]), 1);
}
- btexit(&s);
+ for(i = 0; i < nelem(mnt->cron); i++)
+ loadhist(mnt, &mnt->cron[i]);
}
Mount *
@@ -650,10 +699,10 @@
snprint(mnt->name, sizeof(mnt->name), "%s", name);
if((t = opensnap(name, &flg)) == nil)
error(Enosnap);
- loadautos(mnt);
mnt->flag = flg;
mnt->root = t;
mnt->next = fs->mounts;
+ loadautos(mnt);
asetp(&fs->mounts, mnt);
poperror();
@@ -2655,6 +2704,43 @@
}
void
+setconf(int fd, int op, char *snap, char *key, char *val)
+{
+ char kbuf[128];
+ Mount *mnt;
+ Tree *t;
+ Msg m;
+
+ mnt = nil;
+ t = &fs->snap;
+ if(waserror()){
+ fprint(fd, "error setting config: %s\n", errmsg());
+ return;
+ }
+ if(snap[0] != 0){
+ mnt = getmount(snap);
+ t = mnt->root;
+ }
+ kbuf[0] = Kconf;
+ strecpy(kbuf+1, kbuf+sizeof(kbuf), key);
+ m.op = op;
+ m.k = kbuf;
+ m.nk = strlen(key)+1;
+ m.v = val;
+ m.nv = strlen(val);
+ qlock(&fs->mutlk);
+ if(!waserror()){
+ btupsert(t, &m, 1);
+ poperror();
+ }else
+ fprint(fd, "error setting config: %s\n", errmsg());
+ qunlock(&fs->mutlk);
+ if(mnt != nil)
+ clunkmount(mnt);
+ poperror();
+}
+
+void
runsweep(int id, void*)
{
char buf[Kvmax];
@@ -2672,6 +2758,12 @@
while(1){
am = chrecv(fs->admchan);
switch(am->op){
+ case AOsetcfg:
+ setconf(am->fd, Oinsert, am->snap, am->key, am->val);
+ break;
+ case AOclrcfg:
+ setconf(am->fd, Odelete, am->snap, am->key, "");
+ break;
case AOhalt:
if(!agetl(&fs->rdonly)){
ainc(&fs->rdonly);
@@ -2871,8 +2963,8 @@
}
}
-void
-snapmsg(char *old, char *new, int flg)
+static void
+snapmsg(char *old, char *new)
{
Amsg *a;
@@ -2879,7 +2971,6 @@
a = emalloc(sizeof(Amsg), 1);
a->op = AOsnap;
a->fd = -1;
- a->flag = flg;
strecpy(a->old, a->old+sizeof(a->old), old);
if(new == nil)
a->delete = 1;
@@ -2888,19 +2979,43 @@
chsend(fs->admchan, a);
}
+static void
+cronsync(char *name, Cron *c, Tm *tm, vlong now)
+{
+ char *p, *e, buf[128];
+
+ if(c->div == 0)
+ return;
+ if(now/c->div == c->last/c->div)
+ return;
+
+ if(c->cnt == 0){
+ p = buf;
+ e = p + sizeof(buf);
+ }else{
+ if(c->lbl[c->i][0] != 0)
+ snapmsg(c->lbl[c->i], nil);
+ p = c->lbl[c->i];
+ e = p + sizeof(c->lbl[c->i]);
+ c->i = (c->i+1)%c->cnt;
+ }
+ seprint(p, e, "%s@%s.%τ",
+ name, c->tag,
+ tmfmt(tm, "YYYY.MM.DD[_]hh:mm:ss"));
+ snapmsg(name, p);
+ c->last = now;
+}
+
void
runtasks(int, void *)
{
- char buf[128];
- Tm now, then;
+ vlong now;
Mount *mnt;
- int m, h;
Amsg *a;
+ Tm tm;
+ int i;
- m = 0;
- h = 0;
- tmnow(&then, nil);
- tmnow(&now, nil);
+ tmnow(&tm, nil);
while(1){
sleep(5000);
if(fs->rdonly)
@@ -2914,35 +3029,14 @@
a->fd = -1;
chsend(fs->admchan, a);
- tmnow(&now, nil);
+ tmnow(&tm, nil);
+ now = tmnorm(&tm);
for(mnt = agetp(&fs->mounts); mnt != nil; mnt = mnt->next){
- if(!(mnt->flag & Ltsnap))
+ if(!(mnt->flag & Lmut))
continue;
- if(now.yday != then.yday){
- snprint(buf, sizeof(buf),
- "%s@day.%τ", mnt->name, tmfmt(&now, "YYYY.MM.DD[_]hh:mm:ss"));
- snapmsg(mnt->name, buf, Lauto);
- }
- if(now.hour != then.hour){
- if(mnt->hourly[h][0] != 0)
- snapmsg(mnt->hourly[h], nil, 0);
- snprint(mnt->hourly[h], sizeof(mnt->hourly[h]),
- "%s@hour.%τ", mnt->name, tmfmt(&now, "YYYY.MM.DD[_]hh:mm:ss"));
- snapmsg(mnt->name, mnt->hourly[h], Lauto);
- }
- if(now.min != then.min){
- if(mnt->minutely[m][0] != 0)
- snapmsg(mnt->minutely[m], nil, 0);
- snprint(mnt->minutely[m], sizeof(mnt->minutely[m]),
- "%s@minute.%τ", mnt->name, tmfmt(&now, "YYYY.MM.DD[_]hh:mm:ss"));
- snapmsg(mnt->name, mnt->minutely[m], Lauto);
- }
+ for(i = 0; i < nelem(mnt->cron); i++)
+ cronsync(mnt->name, &mnt->cron[i], &tm, now);
}
- if(now.hour != then.hour)
- h = (h+1)%24;
- if(now.min != then.min)
- m = (m+1)%60;
- then = now;
poperror();
}
}
--- a/sys/src/cmd/gefs/ream.c
+++ b/sys/src/cmd/gefs/ream.c
@@ -91,11 +91,11 @@
Tree t;
Kvp kv;
- lbl2kv("adm", 1, Lmut|Ltsnap, &kv, buf, sizeof(buf));
+ lbl2kv("adm", 1, Lmut, &kv, buf, sizeof(buf));
setval(s, &kv);
lbl2kv("empty", 0, 0, &kv, buf, sizeof(buf));
setval(s, &kv);
- lbl2kv("main", 2, Lmut|Ltsnap, &kv, buf, sizeof(buf));
+ lbl2kv("main", 2, Lmut, &kv, buf, sizeof(buf));
setval(s, &kv);
p = buf;
--
⑨