/*
* Copyright (c) 2013, Coraid, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Coraid 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 COPYRIGHT HOLDERS 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 CORAID 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 "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
typedef struct Snap Snap;
typedef struct Store Store;
typedef struct SDB SDB;
enum {
Magic = 1000006,
SecSize = 512,
PperSec = SecSize/sizeof(uvlong),
BlkSize = 1024*1024,
PperBlk = BlkSize/sizeof(uvlong),
MaxStores = 128,
MaxSnap = 1024,
QTdot = 0,
QTctl,
QTdata,
CMbind = 0,
CMream,
CMrevert,
CMsnap,
CMunbind,
SFvalid = 1<<0,
SFronly = 1<<1,
};
struct Snap {
QLock;
Qid qid;
Store *st;
char *name;
uvlong flags;
uvlong size;
uvlong inuse;
uvlong alpha;
uvlong beta;
uvlong nmap;
uvlong next;
uvlong mapmap[1024];
uvlong mblk;
uvlong *pi;
};
struct Store {
Chan *dchan;
char *bpath;
int nsnaps;
Snap *snaps;
};
struct SDB {
uvlong magic;
uvlong flags;
uvlong size;
uvlong inuse;
uvlong alpha;
uvlong beta;
uvlong nmap;
uvlong next;
char name[128];
uvlong mapmap[1024]; /* Good for 128TB */
};
enum {
SDBSec = (sizeof(SDB) + SecSize - 1) / SecSize,
};
static Store stores[MaxStores];
static Qid dotqid = {QTdot, 0, QTDIR};
static Qid ctlqid = {QTctl, 0, QTFILE};
static Cmdtab snapcmd[] = {
{CMbind, "bind", 2},
{CMream, "ream", 3},
{CMrevert, "revert", 3},
{CMsnap, "snap", 3},
{CMunbind, "unbind", 2},
};
static void
writesdb(Snap *s)
{
SDB *sblk;
sblk = malloc(SDBSec * SecSize);
if(waserror()) {
free(sblk);
nexterror();
}
sblk->magic = Magic;
sblk->flags = s->flags;
sblk->size = s->size;
sblk->inuse = s->inuse;
sblk->alpha = s->alpha;
sblk->beta = s->beta;
sblk->nmap = s->nmap;
sblk->next = s->next;
memmove(sblk->mapmap, s->mapmap, nelem(s->mapmap) * sizeof(uvlong));
strncpy(sblk->name, s->name, 127);
devtab[s->st->dchan->type]->write(s->st->dchan, sblk, SDBSec * SecSize, s->alpha * BlkSize);
poperror();
free(sblk);
}
static uvlong
updmap(Snap *s, uvlong blkno, uvlong mblk)
{
uvlong *msec;
uvlong idx, sec;
idx = blkno % PperBlk;
if(mblk < s->alpha) {
mblk = s->beta;
s->mblk = mblk;
++s->beta;
s->mapmap[idx/PperBlk] = mblk;
s->pi[idx] = s->beta;
devtab[s->st->dchan->type]->write(s->st->dchan,
s->pi, BlkSize, mblk * BlkSize);
}
else {
s->pi[idx] = s->beta;
sec = idx / PperSec;
msec = s->pi + sec * PperSec;
devtab[s->st->dchan->type]->write(s->st->dchan,
msec, SecSize, mblk * BlkSize + sec * SecSize);
}
return mblk;
}
static uvlong
l2p(Snap *s, uvlong l, int doalloc)
{
uchar *tblk;
uvlong blkno, mblk, p;
blkno = l / BlkSize;
mblk = s->mapmap[blkno/PperBlk];
qlock(s);
if(waserror()) {
qunlock(s);
nexterror();
}
if(s->pi == nil)
s->pi = malloc(BlkSize);
if(mblk == 0) {
if(doalloc) {
mblk = s->beta;
s->mapmap[blkno/PperBlk] = mblk;
s->mblk = mblk;
++s->beta;
memset(s->pi, 0, BlkSize);
}
else {
poperror();
qunlock(s);
return 0;
}
}
if(mblk != s->mblk) {
devtab[s->st->dchan->type]->read(s->st->dchan, s->pi, BlkSize, mblk * BlkSize);
s->mblk = mblk;
}
p = s->pi[blkno % PperBlk];
if(p == 0) {
if(!doalloc) {
poperror();
qunlock(s);
return 0;
}
updmap(s, blkno, mblk);
p = s->beta;
++s->beta;
++s->inuse;
writesdb(s);
}
else if(p < s->alpha && doalloc) {
/*
* Do COW
*/
updmap(s, blkno, mblk);
tblk = malloc(BlkSize);
if(waserror()) {
free(tblk);
nexterror();
}
devtab[s->st->dchan->type]->read(s->st->dchan, tblk, BlkSize, p * BlkSize);
devtab[s->st->dchan->type]->write(s->st->dchan, tblk, BlkSize, s->beta * BlkSize);
poperror();
free(tblk);
p = s->beta;
++s->beta;
writesdb(s);
}
poperror();
qunlock(s);
p *= BlkSize;
p += l % BlkSize;
return p;
}
static int
snapgen(Chan *c, char *name, Dirtab *, int, int i, Dir *dp)
{
int j, mode;
if(i == DEVDOTDOT)
devdir(c, dotqid, ".", 0, eve, 0777, dp);
else {
if(name) {
if(strcmp(name, "ctl") == 0)
devdir(c, ctlqid, "ctl", 0, eve, 0664, dp);
else {
for(j = 0; j < stores[0].nsnaps && strcmp(name, stores[0].snaps[j].name) != 0; ++j) ;
if(j >= stores[0].nsnaps)
return -1;
if(stores[0].snaps[j].flags & SFronly)
mode = 0444;
else
mode = 0664;
devdir(c, stores[0].snaps[j].qid, stores[0].snaps[j].name,
stores[0].snaps[j].size, eve, mode, dp);
}
}
else {
++i;
if(i == 1)
devdir(c, ctlqid, "ctl", 0, eve, 0664, dp);
else {
i -= QTdata;
if(i >= stores[0].nsnaps)
return -1;
if(!(stores[0].snaps[i].flags & SFvalid))
return 0;
if(stores[0].snaps[i].flags & SFronly)
mode = 0444;
else
mode = 0664;
devdir(c, stores[0].snaps[i].qid, stores[0].snaps[i].name,
stores[0].snaps[i].size, eve, mode, dp);
}
}
}
return 1;
}
static Chan *
snapattach(char *spec)
{
return devattach(L'ℙ', spec);
}
static Walkqid *
snapwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, nil, 0, snapgen);
}
static int
snapstat(Chan *c, uchar *dp, int n)
{
return devstat(c, dp, n, nil, 0, snapgen);
}
static Chan *
snapopen(Chan *c, int omode)
{
return devopen(c, omode, nil, 0, snapgen);
}
static void
snapclose(Chan *)
{
}
static long
snaprctl(char *a, long n, vlong off)
{
char *p, *e;
int i;
if(off)
return 0;
p = a;
e = a + n;
for(i = 0; i < stores[0].nsnaps; ++i)
p = seprint(p, e, "%d %s %s %ullx %ulld %ulld %ulld\n", i,
stores[0].snaps[i].name, stores[0].bpath, stores[0].snaps[i].flags,
stores[0].snaps[i].inuse, stores[0].snaps[i].alpha, stores[0].snaps[i].beta);
return p-a;
}
static long
snapread(Chan *c, void *a, long n, vlong off)
{
Chan *sc;
char *p;
uvlong qpath, poff, loff;
long tot, m;
qpath = c->qid.path;
if(qpath == QTdot)
return devdirread(c, a, n, nil, 0, snapgen);
if(qpath == QTctl)
return snaprctl(a, n, off);
qpath -= QTdata;
if(qpath >= stores[0].nsnaps)
error(Enonexist);
if(!(stores[0].snaps[qpath].flags & SFvalid))
error(Enonexist);
if(off >= stores[0].snaps[qpath].size)
return 0;
if(n + off >= stores[0].snaps[qpath].size)
n = stores[0].snaps[qpath].size - off;
sc = stores[0].dchan;
p = a;
loff = off;
tot = 0;
while(loff < off + n) {
m = BlkSize - (loff % BlkSize);
if(m > n - tot)
m = n - tot;
poff = l2p(&stores[0].snaps[qpath], loff, 0);
if(poff == 0)
memset(p, 0, m);
else
m = devtab[sc->type]->read(sc, p, m, poff);
if(m == 0)
break;
tot += m;
loff += m;
p += m;
}
return tot;
}
static void
snapbind(Cmdbuf *cb)
{
Chan *c;
SDB *s;
Store *st;
uchar *buf;
uvlong sb;
if(stores[0].nsnaps != 0)
error("multiple binds not yet supported");
c = namec(cb->f[1], Aopen, ORDWR, 0);
buf = malloc(SDBSec * SecSize);
if(waserror()) {
cclose(c);
free(buf);
nexterror();
}
st = &stores[0];
st->dchan = c;
st->bpath = malloc(strlen(cb->f[1]) + 1);
strcpy(st->bpath, cb->f[1]);
st->nsnaps = 0;
st->snaps = malloc(MaxSnap * sizeof(Snap));
sb = 0;
do {
devtab[c->type]->read(c, buf, SDBSec * SecSize, sb * BlkSize);
s = (SDB *)buf;
if(s->magic != Magic)
break;
if(s->flags & SFvalid) {
st->snaps[st->nsnaps].st = st;
st->snaps[st->nsnaps].name = malloc(strlen(s->name) + 1);
strcpy(st->snaps[st->nsnaps].name, s->name);
st->snaps[st->nsnaps].flags = s->flags;
st->snaps[st->nsnaps].size = s->size;
st->snaps[st->nsnaps].inuse = s->inuse;
st->snaps[st->nsnaps].alpha = s->alpha;
st->snaps[st->nsnaps].beta = s->beta;
st->snaps[st->nsnaps].nmap = s->nmap;
st->snaps[st->nsnaps].next = s->next;
memmove(st->snaps[st->nsnaps].mapmap, s->mapmap,
nelem(s->mapmap) * sizeof(uvlong));
st->snaps[st->nsnaps].qid.path = st->nsnaps + QTdata;
st->snaps[st->nsnaps].qid.vers = 0;
st->snaps[st->nsnaps].qid.type = QTFILE;
st->snaps[st->nsnaps].pi = nil;
st->snaps[st->nsnaps].mblk = 0;
++st->nsnaps;
}
sb = s->next;
} while(sb != 0);
poperror();
free(buf);
}
static void
snapream(Cmdbuf *cb)
{
Dir d;
Chan *c;
Store *st;
uchar *buf;
int i;
if(stores[0].nsnaps != 0)
error("multiple binds not yet supported");
/* Open the channel */
st = &stores[0];
st->snaps = malloc(MaxSnap * sizeof(Snap));
c = st->dchan = namec(cb->f[2], Aopen, ORDWR, 0);
buf = malloc(65536);
if(waserror()) {
cclose(c);
free(buf);
nexterror();
}
/* Get the length */
i = devtab[c->type]->stat(c, buf, 65536);
convM2D(buf, i, &d, nil);
/* Set the internal store struct */
st->snaps[0].st = st;
st->snaps[0].name = malloc(strlen(cb->f[1]) + 1);
strcpy(st->snaps[0].name, cb->f[1]);
st->bpath = malloc(strlen(cb->f[2]) + 1);
strcpy(st->bpath, cb->f[2]);
st->snaps[0].qid.path = QTdata;
st->snaps[0].qid.vers = 0;
st->snaps[0].qid.type = QTFILE;
st->snaps[0].flags = SFvalid;
st->snaps[0].size = d.length;
st->snaps[0].alpha = 0;
st->snaps[0].nmap = ((st->snaps[0].size + BlkSize - 1)/ BlkSize + PperBlk - 1) / PperBlk;
st->snaps[0].beta = 1;
st->snaps[0].inuse = st->snaps[0].beta;
memset(st->snaps[0].mapmap, 0, nelem(st->snaps[0].mapmap) * sizeof(uvlong));
writesdb(&st->snaps[0]);
poperror();
free(buf);
st->nsnaps = 1;
}
static void
snaprevert(Cmdbuf *cb)
{
Store *st;
int i, j, k, l;
st = &stores[0];
for(i = 0; i < st->nsnaps && strcmp(st->snaps[i].name, cb->f[1]) != 0; ++i) ;
for(j = 0; j < st->nsnaps && strcmp(st->snaps[j].name, cb->f[2]) != 0; ++j) ;
if(i >= st->nsnaps || j >= st->nsnaps)
error(Enonexist);
qlock(&st->snaps[i]);
qlock(&st->snaps[j]);
if(waserror()) {
qunlock(&st->snaps[i]);
qunlock(&st->snaps[j]);
nexterror();
}
for(k = i; ; ) {
st->snaps[k].next = 0;
st->snaps[k].flags &= ~SFvalid;
writesdb(&st->snaps[k]);
if(k == j)
break;
for(l = 0; l < st->nsnaps && st->snaps[l].next != st->snaps[k].alpha; ++l) ;
if(l >= st->nsnaps)
break;
k = l;
}
st->snaps[i].inuse = st->snaps[j].inuse;
st->snaps[i].alpha = st->snaps[j].alpha;
st->snaps[i].beta = st->snaps[j].beta;
memmove(st->snaps[i].mapmap, st->snaps[j].mapmap,
nelem(st->snaps[i].mapmap) * sizeof(uvlong));
st->snaps[i].mblk = 0;
st->snaps[i].flags = SFvalid;
writesdb(&st->snaps[i]);
poperror();
qunlock(&st->snaps[j]);
qunlock(&st->snaps[i]);
}
static void
snapsnap(Cmdbuf *cb)
{
Store *st;
int i, slot;
st = &stores[0];
for(i = 0; i < st->nsnaps && strcmp(st->snaps[i].name, cb->f[1]) != 0; ++i) ;
if(i >= st->nsnaps)
error(Enonexist);
for(slot = 0; slot < st->nsnaps && (st->snaps[slot].flags & SFvalid); ++slot) ;
qlock(&st->snaps[i]);
qlock(&st->snaps[slot]);
if(waserror()) {
qunlock(&st->snaps[i]);
qunlock(&st->snaps[slot]);
nexterror();
}
/* make a new store */
st->snaps[slot].st = st;
st->snaps[slot].qid.path = slot + QTdata;
st->snaps[slot].qid.vers = 0;
st->snaps[slot].qid.type = QTFILE;
free(st->snaps[slot].name);
st->snaps[slot].name = malloc(strlen(cb->f[2]) + 1);
strcpy(st->snaps[slot].name, cb->f[2]);
st->snaps[slot].inuse = st->snaps[i].inuse;
st->snaps[slot].size = st->snaps[i].inuse * BlkSize;
st->snaps[slot].flags = st->snaps[i].flags | SFronly;
st->snaps[slot].alpha = st->snaps[i].alpha;
st->snaps[slot].beta = st->snaps[i].beta;
st->snaps[slot].nmap = st->snaps[i].nmap;
st->snaps[slot].next = st->snaps[i].beta;
memmove(st->snaps[slot].mapmap, st->snaps[i].mapmap,
nelem(st->snaps[i].mapmap) * sizeof(uvlong));
st->snaps[slot].mblk = 0;
free(st->snaps[slot].pi);
st->snaps[slot].pi = nil;
/* adjust the original to point to the new space */
st->snaps[i].alpha = st->snaps[i].beta;
st->snaps[i].beta = st->snaps[i].alpha + 1;
/* write the sdb blocks */
writesdb(&st->snaps[i]);
writesdb(&st->snaps[slot]);
poperror();
qunlock(&stores[0].snaps[i]);
qunlock(&stores[0].snaps[slot]);
if(slot == st->nsnaps)
++st->nsnaps;
}
static void
snapunbind(Cmdbuf *cb)
{
Store *st;
int i;
for(i = 0; i < MaxStores && (stores[i].bpath == nil || strcmp(stores[i].bpath, cb->f[1]) !=0); ++i) ;
if(i >= MaxStores)
error(Enonexist);
st = &stores[i];
cclose(st->dchan);
free(st->bpath);
for(i = 0; i < st->nsnaps; ++i) {
free(st->snaps[i].name);
free(st->snaps[i].pi);
}
free(st->snaps);
st->nsnaps = 0;
}
static long
snapwctl(char *a, long n)
{
Cmdbuf *cb;
Cmdtab *ct;
cb = parsecmd(a, n);
ct = lookupcmd(cb, snapcmd, nelem(snapcmd));
switch(ct->index) {
case CMbind:
snapbind(cb);
break;
case CMream:
snapream(cb);
break;
case CMrevert:
snaprevert(cb);
break;
case CMsnap:
snapsnap(cb);
break;
case CMunbind:
snapunbind(cb);
break;
}
return n;
}
static long
snapwrite(Chan *c, void *a, long n, vlong off)
{
Chan *sc;
Store *st;
char *p;
uvlong qpath, poff, loff;
long tot, m;
qpath = c->qid.path;
if(qpath == QTdot)
error(Eisdir);
if(qpath == QTctl)
return snapwctl(a, n);
qpath -= QTdata;
if(qpath >= stores[0].nsnaps)
error(Enonexist);
st = &stores[0];
if(!(st->snaps[qpath].flags & SFvalid))
error(Enonexist);
if(st->snaps[qpath].flags & SFronly)
error(Eperm);
if(off >= st->snaps[qpath].size)
return 0;
if(n + off >= st->snaps[qpath].size)
n = st->snaps[qpath].size - off;
sc = st->dchan;
p = a;
loff = off;
tot = 0;
while(loff < off + n) {
m = BlkSize - (loff % BlkSize);
if(m > n - tot)
m = n - tot;
poff = l2p(&st->snaps[qpath], loff, 1);
if(poff == 0)
error("no space");
m = devtab[sc->type]->write(sc, p, m, poff);
if(m == 0)
break;
tot += m;
loff += m;
p += m;
}
return tot;
}
Dev snapdevtab = {
L'ℙ',
"snap",
devreset,
devinit,
devshutdown,
snapattach,
snapwalk,
snapstat,
snapopen,
devcreate,
snapclose,
snapread,
devbread,
snapwrite,
devbwrite,
devremove,
devwstat,
};
|