#include "stdinc.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
static void fsMetaFlush(void *a);
static Snap *snapInit(Fs*);
static void snapClose(Snap*);
Fs *
fsOpen(char *file, VtSession *z, long ncache, int mode)
{
Fs *fs;
Disk *disk;
int fd;
Block *b, *bs;
Super super;
int m;
uchar oscore[VtScoreSize];
switch(mode){
default:
vtSetError(EBadMode);
return nil;
case OReadOnly:
m = OREAD;
break;
case OReadWrite:
m = ORDWR;
break;
}
fd = open(file, m);
if(fd < 0){
vtSetError("open %s: %r", file);
return nil;
}
bwatchInit();
disk = diskAlloc(fd);
if(disk == nil){
vtSetError("diskAlloc: %R");
close(fd);
return nil;
}
fs = vtMemAllocZ(sizeof(Fs));
fs->mode = mode;
fs->blockSize = diskBlockSize(disk);
fs->elk = vtLockAlloc();
fs->cache = cacheAlloc(disk, z, ncache, mode);
if(mode == OReadWrite && z)
fs->arch = archInit(fs->cache, disk, fs, z);
fs->z = z;
b = cacheLocal(fs->cache, PartSuper, 0, mode);
if(b == nil)
goto Err;
if(!superUnpack(&super, b->data)){
blockPut(b);
vtSetError("bad super block");
goto Err;
}
blockPut(b);
fs->ehi = super.epochHigh;
fs->elo = super.epochLow;
//fprint(2, "fs->ehi %d fs->elo %d active=%d\n", fs->ehi, fs->elo, super.active);
fs->source = sourceRoot(fs, super.active, mode);
if(fs->source == nil){
/*
* Perhaps it failed because the block is copy-on-write.
* Do the copy and try again.
*/
if(mode == OReadOnly || strcmp(vtGetError(), EBadRoot) != 0)
goto Err;
b = cacheLocalData(fs->cache, super.active, BtDir, RootTag, OReadWrite, 0);
if(b == nil){
vtSetError("cacheLocalData: %R");
goto Err;
}
if(b->l.epoch == fs->ehi){
blockPut(b);
vtSetError("bad root source block");
goto Err;
}
b = blockCopy(b, RootTag, fs->ehi, fs->elo);
if(b == nil)
goto Err;
localToGlobal(super.active, oscore);
super.active = b->addr;
bs = cacheLocal(fs->cache, PartSuper, 0, OReadWrite);
if(bs == nil){
blockPut(b);
vtSetError("cacheLocal: %R");
goto Err;
}
superPack(&super, bs->data);
blockDependency(bs, b, 0, oscore, nil);
blockPut(b);
blockDirty(bs);
blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
blockPut(bs);
fs->source = sourceRoot(fs, super.active, mode);
if(fs->source == nil){
vtSetError("sourceRoot: %R");
goto Err;
}
}
//fprint(2, "got fs source\n");
vtRLock(fs->elk);
fs->file = fileRoot(fs->source);
vtRUnlock(fs->elk);
if(fs->file == nil){
vtSetError("fileRoot: %R");
goto Err;
}
//fprint(2, "got file root\n");
if(mode == OReadWrite){
fs->metaFlush = periodicAlloc(fsMetaFlush, fs, 1000);
fs->snap = snapInit(fs);
}
return fs;
Err:
fprint(2, "fsOpen error\n");
fsClose(fs);
return nil;
}
void
fsClose(Fs *fs)
{
vtRLock(fs->elk);
periodicKill(fs->metaFlush);
snapClose(fs->snap);
if(fs->file){
fileMetaFlush(fs->file, 0);
if(!fileDecRef(fs->file))
vtFatal("fsClose: files still in use: %r\n");
}
fs->file = nil;
sourceClose(fs->source);
cacheFree(fs->cache);
if(fs->arch)
archFree(fs->arch);
vtRUnlock(fs->elk);
vtLockFree(fs->elk);
memset(fs, ~0, sizeof(Fs));
vtMemFree(fs);
}
int
fsRedial(Fs *fs, char *host)
{
if(!vtRedial(fs->z, host))
return 0;
if(!vtConnect(fs->z, 0))
return 0;
return 1;
}
File *
fsGetRoot(Fs *fs)
{
return fileIncRef(fs->file);
}
int
fsGetBlockSize(Fs *fs)
{
return fs->blockSize;
}
Block*
superGet(Cache *c, Super* super)
{
Block *b;
if((b = cacheLocal(c, PartSuper, 0, OReadWrite)) == nil){
fprint(2, "superGet: cacheLocal failed: %R");
return nil;
}
if(!superUnpack(super, b->data)){
fprint(2, "superGet: superUnpack failed: %R");
blockPut(b);
return nil;
}
return b;
}
void
superWrite(Block* b, Super* super, int forceWrite)
{
superPack(super, b->data);
blockDirty(b);
if(forceWrite){
while(!blockWrite(b)){
/* BUG: what should really happen here? */
fprint(2, "could not write super block; waiting 10 seconds\n");
sleep(10*000);
}
while(b->iostate != BioClean && b->iostate != BioDirty){
assert(b->iostate == BioWriting);
vtSleep(b->ioready);
}
/*
* it's okay that b might still be dirty.
* that means it got written out but with an old root pointer,
* but the other fields went out, and those are the ones
* we really care about. (specifically, epochHigh; see fsSnapshot).
*/
}
}
/*
* Prepare the directory to store a snapshot.
* Temporary snapshots go into /snapshot/yyyy/mmdd/hhmm[.#]
* Archival snapshots go into /archive/yyyy/mmdd[.#].
*
* TODO This should be rewritten to eliminate most of the duplication.
*/
static File*
fileOpenSnapshot(Fs *fs, char *dstpath, int doarchive)
{
int n;
char buf[30], *s, *p, *elem;
File *dir, *f;
Tm now;
if(dstpath){
if((p = strrchr(dstpath, '/')) != nil){
*p++ = '\0';
elem = p;
p = dstpath;
if(*p == '\0')
p = "/";
}else{
p = "/";
elem = dstpath;
}
if((dir = fileOpen(fs, p)) == nil)
return nil;
f = fileCreate(dir, elem, ModeDir|ModeSnapshot|0555, "adm");
fileDecRef(dir);
return f;
}else if(doarchive){
/*
* a snapshot intended to be archived to venti.
*/
dir = fileOpen(fs, "/archive");
if(dir == nil)
return nil;
now = *localtime(time(0));
/* yyyy */
snprint(buf, sizeof(buf), "%d", now.year+1900);
f = fileWalk(dir, buf);
if(f == nil)
f = fileCreate(dir, buf, ModeDir|0555, "adm");
fileDecRef(dir);
if(f == nil)
return nil;
dir = f;
/* mmdd[#] */
snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
s = buf+strlen(buf);
for(n=0;; n++){
if(n)
seprint(s, buf+sizeof(buf), ".%d", n);
f = fileWalk(dir, buf);
if(f != nil){
fileDecRef(f);
continue;
}
f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
break;
}
fileDecRef(dir);
return f;
}else{
/*
* Just a temporary snapshot
* We'll use /snapshot/yyyy/mmdd/hhmm.
* There may well be a better naming scheme.
* (I'd have used hh:mm but ':' is reserved in Microsoft file systems.)
*/
dir = fileOpen(fs, "/snapshot");
if(dir == nil)
return nil;
now = *localtime(time(0));
/* yyyy */
snprint(buf, sizeof(buf), "%d", now.year+1900);
f = fileWalk(dir, buf);
if(f == nil)
f = fileCreate(dir, buf, ModeDir|0555, "adm");
fileDecRef(dir);
if(f == nil)
return nil;
dir = f;
/* mmdd */
snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
f = fileWalk(dir, buf);
if(f == nil)
f = fileCreate(dir, buf, ModeDir|0555, "adm");
fileDecRef(dir);
if(f == nil)
return nil;
dir = f;
/* hhmm[.#] */
snprint(buf, sizeof buf, "%02d%02d", now.hour, now.min);
s = buf+strlen(buf);
for(n=0;; n++){
if(n)
seprint(s, buf+sizeof(buf), ".%d", n);
f = fileWalk(dir, buf);
if(f != nil){
fileDecRef(f);
continue;
}
f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
break;
}
fileDecRef(dir);
return f;
}
}
static int
fsNeedArch(Fs *fs, uint archMinute)
{
int need;
File *f;
char buf[100];
Tm now;
ulong then;
then = time(0);
now = *localtime(then);
/* back up to yesterday if necessary */
if(now.hour < archMinute/60
|| now.hour == archMinute/60 && now.min < archMinute%60)
now = *localtime(then-86400);
snprint(buf, sizeof buf, "/archive/%d/%02d%02d",
now.year+1900, now.mon+1, now.mday);
need = 1;
vtRLock(fs->elk);
f = fileOpen(fs, buf);
if(f){
need = 0;
fileDecRef(f);
}
vtRUnlock(fs->elk);
return need;
}
int
fsEpochLow(Fs *fs, u32int low)
{
Block *bs;
Super super;
vtLock(fs->elk);
if(low > fs->ehi){
vtSetError("bad low epoch (must be <= %ud)", fs->ehi);
vtUnlock(fs->elk);
return 0;
}
if((bs = superGet(fs->cache, &super)) == nil){
vtUnlock(fs->elk);
return 0;
}
super.epochLow = low;
fs->elo = low;
superWrite(bs, &super, 1);
blockPut(bs);
vtUnlock(fs->elk);
return 1;
}
static int
bumpEpoch(Fs *fs, int doarchive)
{
uchar oscore[VtScoreSize];
u32int oldaddr;
Block *b, *bs;
Entry e;
Source *r;
Super super;
/*
* Duplicate the root block.
*
* As a hint to flchk, the garbage collector,
* and any (human) debuggers, store a pointer
* to the old root block in entry 1 of the new root block.
*/
r = fs->source;
b = cacheGlobal(fs->cache, r->score, BtDir, RootTag, OReadOnly);
if(b == nil)
return 0;
memset(&e, 0, sizeof e);
e.flags = VtEntryActive | VtEntryLocal | VtEntryDir;
memmove(e.score, b->score, VtScoreSize);
e.tag = RootTag;
e.snap = b->l.epoch;
b = blockCopy(b, RootTag, fs->ehi+1, fs->elo);
if(b == nil){
fprint(2, "bumpEpoch: blockCopy: %R\n");
return 0;
}
if(0) fprint(2, "snapshot root from %d to %d\n", oldaddr, b->addr);
entryPack(&e, b->data, 1);
blockDirty(b);
/*
* Update the superblock with the new root and epoch.
*/
if((bs = superGet(fs->cache, &super)) == nil)
return 0;
fs->ehi++;
memmove(r->score, b->score, VtScoreSize);
r->epoch = fs->ehi;
super.epochHigh = fs->ehi;
oldaddr = super.active;
super.active = b->addr;
if(doarchive)
super.next = oldaddr;
/*
* Record that the new super.active can't get written out until
* the new b gets written out. Until then, use the old value.
*/
localToGlobal(oldaddr, oscore);
blockDependency(bs, b, 0, oscore, nil);
blockPut(b);
/*
* We force the super block to disk so that super.epochHigh gets updated.
* Otherwise, if we crash and come back, we might incorrectly treat as active
* some of the blocks that making up the snapshot we just created.
* Basically every block in the active file system and all the blocks in
* the recently-created snapshot depend on the super block now.
* Rather than record all those dependencies, we just force the block to disk.
*
* Note that blockWrite might actually (will probably) send a slightly outdated
* super.active to disk. It will be the address of the most recent root that has
* gone to disk.
*/
superWrite(bs, &super, 1);
blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
blockPut(bs);
return 1;
}
int
saveQid(Fs *fs)
{
Block *b;
Super super;
u64int qidMax;
if((b = superGet(fs->cache, &super)) == nil)
return 0;
qidMax = super.qid;
blockPut(b);
if(!fileSetQidSpace(fs->file, 0, qidMax))
return 0;
return 1;
}
int
fsSnapshot(Fs *fs, char *srcpath, char *dstpath, int doarchive)
{
File *src, *dst;
assert(fs->mode == OReadWrite);
dst = nil;
if(fs->halted){
vtSetError("file system is halted");
return 0;
}
/*
* Freeze file system activity.
*/
vtLock(fs->elk);
/*
* Get the root of the directory we're going to save.
*/
if(srcpath == nil)
srcpath = "/active";
src = fileOpen(fs, srcpath);
if(src == nil)
goto Err;
/*
* It is important that we maintain the invariant that:
* if both b and bb are marked as Active with start epoch e
* and b points at bb, then no other pointers to bb exist.
*
* When bb is unlinked from b, its close epoch is set to b's epoch.
* A block with epoch == close epoch is
* treated as free by cacheAllocBlock; this aggressively
* reclaims blocks after they have been stored to Venti.
*
* Let's say src->source is block sb, and src->msource is block
* mb. Let's also say that block b holds the Entry structures for
* both src->source and src->msource (their Entry structures might
* be in different blocks, but the argument is the same).
* That is, right now we have:
*
* b Active w/ epoch e, holds ptrs to sb and mb.
* sb Active w/ epoch e.
* mb Active w/ epoch e.
*
* With things as they are now, the invariant requires that
* b holds the only pointers to sb and mb. We want to record
* pointers to sb and mb in new Entries corresponding to dst,
* which breaks the invariant. Thus we need to do something
* about b. Specifically, we bump the file system's epoch and
* then rewalk the path from the root down to and including b.
* This will copy-on-write as we walk, so now the state will be:
*
* b Snap w/ epoch e, holds ptrs to sb and mb.
* new-b Active w/ epoch e+1, holds ptrs to sb and mb.
* sb Active w/ epoch e.
* mb Active w/ epoch e.
*
* In this state, it's perfectly okay to make more pointers to sb and mb.
*/
if(!bumpEpoch(fs, 0) || !fileWalkSources(src))
goto Err;
/*
* Sync to disk. I'm not sure this is necessary, but better safe than sorry.
*/
cacheFlush(fs->cache, 1);
/*
* Create the directory where we will store the copy of src.
*/
dst = fileOpenSnapshot(fs, dstpath, doarchive);
if(dst == nil)
goto Err;
/*
* Actually make the copy by setting dst's source and msource
* to be src's.
*/
if(!fileSnapshot(dst, src, fs->ehi-1, doarchive))
goto Err;
fileDecRef(src);
fileDecRef(dst);
src = nil;
dst = nil;
/*
* Make another copy of the file system. This one is for the
* archiver, so that the file system we archive has the recently
* added snapshot both in /active and in /archive/yyyy/mmdd[.#].
*/
if(doarchive){
if(!saveQid(fs))
goto Err;
if(!bumpEpoch(fs, 1))
goto Err;
}
vtUnlock(fs->elk);
/* BUG? can fs->arch fall out from under us here? */
if(doarchive && fs->arch)
archKick(fs->arch);
return 1;
Err:
fprint(2, "fsSnapshot: %R\n");
if(src)
fileDecRef(src);
if(dst)
fileDecRef(dst);
vtUnlock(fs->elk);
return 0;
}
int
fsVac(Fs *fs, char *name, uchar score[VtScoreSize])
{
int r;
DirEntry de;
Entry e, ee;
File *f;
vtRLock(fs->elk);
f = fileOpen(fs, name);
if(f == nil){
vtRUnlock(fs->elk);
return 0;
}
if(!fileGetSources(f, &e, &ee) || !fileGetDir(f, &de)){
fileDecRef(f);
vtRUnlock(fs->elk);
return 0;
}
fileDecRef(f);
r = mkVac(fs->z, fs->blockSize, &e, &ee, &de, score);
vtRUnlock(fs->elk);
return r;
}
static int
vtWriteBlock(VtSession *z, uchar *buf, uint n, uint type, uchar score[VtScoreSize])
{
if(!vtWrite(z, score, type, buf, n))
return 0;
if(!vtSha1Check(score, buf, n))
return 0;
return 1;
}
int
mkVac(VtSession *z, uint blockSize, Entry *pe, Entry *pee, DirEntry *pde, uchar score[VtScoreSize])
{
uchar buf[8192];
int i;
uchar *p;
uint n;
DirEntry de;
Entry e, ee, eee;
MetaBlock mb;
MetaEntry me;
VtRoot root;
e = *pe;
ee = *pee;
de = *pde;
if(globalToLocal(e.score) != NilBlock
|| (ee.flags&VtEntryActive && globalToLocal(ee.score) != NilBlock)){
vtSetError("can only vac paths already stored on venti");
return 0;
}
/*
* Build metadata source for root.
*/
n = deSize(&de);
if(n+MetaHeaderSize+MetaIndexSize > sizeof buf){
vtSetError("DirEntry too big");
return 0;
}
memset(buf, 0, sizeof buf);
mbInit(&mb, buf, n+MetaHeaderSize+MetaIndexSize, 1);
p = mbAlloc(&mb, n);
if(p == nil)
abort();
mbSearch(&mb, de.elem, &i, &me);
assert(me.p == nil);
me.p = p;
me.size = n;
dePack(&de, &me);
mbInsert(&mb, i, &me);
mbPack(&mb);
eee.size = n+MetaHeaderSize+MetaIndexSize;
if(!vtWriteBlock(z, buf, eee.size, VtDataType, eee.score))
return 0;
eee.psize = 8192;
eee.dsize = 8192;
eee.depth = 0;
eee.flags = VtEntryActive;
/*
* Build root source with three entries in it.
*/
entryPack(&e, buf, 0);
entryPack(&ee, buf, 1);
entryPack(&eee, buf, 2);
n = VtEntrySize*3;
memset(&root, 0, sizeof root);
if(!vtWriteBlock(z, buf, n, VtDirType, root.score))
return 0;
/*
* Save root.
*/
root.version = VtRootVersion;
strecpy(root.type, root.type+sizeof root.type, "vac");
strecpy(root.name, root.name+sizeof root.name, de.elem);
root.blockSize = blockSize;
vtRootPack(&root, buf);
if(!vtWriteBlock(z, buf, VtRootSize, VtRootType, score))
return 0;
return 1;
}
int
fsSync(Fs *fs)
{
vtLock(fs->elk);
fileMetaFlush(fs->file, 1);
cacheFlush(fs->cache, 1);
vtUnlock(fs->elk);
return 1;
}
int
fsHalt(Fs *fs)
{
vtLock(fs->elk);
fs->halted = 1;
fileMetaFlush(fs->file, 1);
cacheFlush(fs->cache, 1);
return 1;
}
int
fsUnhalt(Fs *fs)
{
if(!fs->halted)
return 0;
fs->halted = 0;
vtUnlock(fs->elk);
return 1;
}
int
fsNextQid(Fs *fs, u64int *qid)
{
Block *b;
Super super;
if((b = superGet(fs->cache, &super)) == nil)
return 0;
*qid = super.qid++;
/*
* It's okay if the super block doesn't go to disk immediately,
* since fileMetaAlloc will record a dependency between the
* block holding this qid and the super block. See file.c:/^fileMetaAlloc.
*/
superWrite(b, &super, 0);
blockPut(b);
return 1;
}
static void
fsMetaFlush(void *a)
{
Fs *fs = a;
vtRLock(fs->elk);
fileMetaFlush(fs->file, 1);
vtRUnlock(fs->elk);
cacheFlush(fs->cache, 0);
}
static int
fsEsearch1(File *f, char *path, u32int savetime, u32int *plo)
{
int n, r;
DirEntry de;
DirEntryEnum *dee;
File *ff;
Entry e, ee;
char *t;
dee = deeOpen(f);
if(dee == nil)
return 0;
n = 0;
for(;;){
r = deeRead(dee, &de);
if(r <= 0)
break;
if(de.mode & ModeSnapshot){
if((ff = fileWalk(f, de.elem)) != nil){
if(fileGetSources(ff, &e, &ee))
if(de.mtime >= savetime && e.snap != 0)
if(e.snap < *plo)
*plo = e.snap;
fileDecRef(ff);
}
}
else if(de.mode & ModeDir){
if((ff = fileWalk(f, de.elem)) != nil){
t = smprint("%s/%s", path, de.elem);
n += fsEsearch1(ff, t, savetime, plo);
vtMemFree(t);
fileDecRef(ff);
}
}
deCleanup(&de);
if(r < 0)
break;
}
deeClose(dee);
return n;
}
static int
fsEsearch(Fs *fs, char *path, u32int savetime, u32int *plo)
{
int n;
File *f;
DirEntry de;
f = fileOpen(fs, path);
if(f == nil)
return 0;
if(!fileGetDir(f, &de)){
fileDecRef(f);
return 0;
}
if((de.mode & ModeDir) == 0){
fileDecRef(f);
deCleanup(&de);
return 0;
}
deCleanup(&de);
n = fsEsearch1(f, path, savetime, plo);
fileDecRef(f);
return n;
}
void
fsSnapshotCleanup(Fs *fs, u32int age)
{
u32int lo;
/*
* Find the best low epoch we can use,
* given that we need to save all the unventied archives
* and all the snapshots younger than age.
*/
vtRLock(fs->elk);
lo = fs->ehi;
fsEsearch(fs, "/archive", 0, &lo);
fsEsearch(fs, "/snapshot", time(0)-age*60, &lo);
vtRUnlock(fs->elk);
fsEpochLow(fs, lo);
fsSnapshotRemove(fs);
}
/* remove all snapshots that have expired */
/* return number of directory entries remaining */
static int
fsRsearch1(File *f, char *s)
{
int n, r;
DirEntry de;
DirEntryEnum *dee;
File *ff;
char *t;
dee = deeOpen(f);
if(dee == nil)
return 0;
n = 0;
for(;;){
r = deeRead(dee, &de);
if(r <= 0)
break;
n++;
if(de.mode & ModeSnapshot){
if((ff = fileWalk(f, de.elem)) != nil)
fileDecRef(ff);
else if(strcmp(vtGetError(), ESnapOld) == 0){
if(fileClri(f, de.elem, "adm"))
n--;
}
}
else if(de.mode & ModeDir){
if((ff = fileWalk(f, de.elem)) != nil){
t = smprint("%s/%s", s, de.elem);
if(fsRsearch1(ff, t) == 0)
if(fileRemove(ff, "adm"))
n--;
vtMemFree(t);
fileDecRef(ff);
}
}
deCleanup(&de);
if(r < 0)
break;
}
deeClose(dee);
return n;
}
static int
fsRsearch(Fs *fs, char *path)
{
File *f;
DirEntry de;
f = fileOpen(fs, path);
if(f == nil)
return 0;
if(!fileGetDir(f, &de)){
fileDecRef(f);
return 0;
}
if((de.mode & ModeDir) == 0){
fileDecRef(f);
deCleanup(&de);
return 0;
}
deCleanup(&de);
fsRsearch1(f, path);
fileDecRef(f);
return 1;
}
void
fsSnapshotRemove(Fs *fs)
{
vtRLock(fs->elk);
fsRsearch(fs, "/snapshot");
vtRUnlock(fs->elk);
}
struct Snap
{
Fs *fs;
Periodic *tick;
VtLock *lk;
uint snapMinutes;
uint archMinute;
uint snapLife;
u32int lastSnap;
u32int lastArch;
u32int lastCleanup;
uint ignore;
};
static void
snapEvent(void *v)
{
Snap *s;
u32int now, min;
Tm tm;
int need;
s = v;
now = time(0)/60;
vtLock(s->lk);
/*
* Snapshots happen every snapMinutes minutes.
* If we miss a snapshot (for example, because we
* were down), we wait for the next one.
*/
if(s->snapMinutes != ~0 && s->snapMinutes != 0
&& now%s->snapMinutes==0 && now != s->lastSnap){
if(!fsSnapshot(s->fs, nil, nil, 0))
fprint(2, "fsSnapshot snap: %R\n");
s->lastSnap = now;
}
/*
* Archival snapshots happen at archMinute.
* If we miss an archive (for example, because we
* were down), we do it as soon as possible.
*/
tm = *localtime(now*60);
min = tm.hour*60+tm.min;
if(s->archMinute != ~0){
need = 0;
if(min == s->archMinute && now != s->lastArch)
need = 1;
if(s->lastArch == 0){
s->lastArch = 1;
if(fsNeedArch(s->fs, s->archMinute))
need = 1;
}
if(need){
fsSnapshot(s->fs, nil, nil, 1);
s->lastArch = now;
}
}
/*
* Snapshot cleanup happens every snaplife or every day.
*/
if(s->snapLife != ~0
&& (s->lastCleanup+s->snapLife < now || s->lastCleanup+24*60 < now)){
fsSnapshotCleanup(s->fs, s->snapLife);
s->lastCleanup = now;
}
vtUnlock(s->lk);
}
static Snap*
snapInit(Fs *fs)
{
Snap *s;
s = vtMemAllocZ(sizeof(Snap));
s->fs = fs;
s->tick = periodicAlloc(snapEvent, s, 10*1000);
s->lk = vtLockAlloc();
s->snapMinutes = -1;
s->archMinute = -1;
s->snapLife = -1;
s->ignore = 5*2; /* wait five minutes for clock to stabilize */
return s;
}
void
snapGetTimes(Snap *s, u32int *arch, u32int *snap, u32int *snaplen)
{
if(s == nil){
*snap = -1;
*arch = -1;
*snaplen = -1;
return;
}
vtLock(s->lk);
*snap = s->snapMinutes;
*arch = s->archMinute;
*snaplen = s->snapLife;
vtUnlock(s->lk);
}
void
snapSetTimes(Snap *s, u32int arch, u32int snap, u32int snaplen)
{
if(s == nil)
return;
vtLock(s->lk);
s->snapMinutes = snap;
s->archMinute = arch;
s->snapLife = snaplen;
vtUnlock(s->lk);
}
static void
snapClose(Snap *s)
{
if(s == nil)
return;
periodicKill(s->tick);
vtMemFree(s);
}
|