#include "stdinc.h"
#include "vac.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
#define debug 0
/*
* Vac file system. This is a simplified version of the same code in Fossil.
*
* The locking order in the tree is upward: a thread can hold the lock
* for a VacFile and then acquire the lock of f->up (the parent),
* but not vice-versa.
*
* A vac file is one or two venti files. Plain data files are one venti file,
* while directores are two: a venti data file containing traditional
* directory entries, and a venti directory file containing venti
* directory entries. The traditional directory entries in the data file
* contain integers indexing into the venti directory entry file.
* It's a little complicated, but it makes the data usable by standard
* tools like venti/copy.
*
*/
static int filemetaflush(VacFile*, char*);
struct VacFile
{
VacFs *fs; /* immutable */
/* meta data for file: protected by the lk in the parent */
int ref; /* holds this data structure up */
int partial; /* file was never really open */
int removed; /* file has been removed */
int dirty; /* dir is dirty with respect to meta data in block */
u32int boff; /* block offset within msource for this file's metadata */
VacDir dir; /* metadata for this file */
VacFile *up; /* parent file */
VacFile *next; /* sibling */
RWLock lk; /* lock for the following */
VtFile *source; /* actual data */
VtFile *msource; /* metadata for children in a directory */
VacFile *down; /* children */
int mode;
uvlong qidoffset; /* qid offset */
};
static VacFile*
filealloc(VacFs *fs)
{
VacFile *f;
f = vtmallocz(sizeof(VacFile));
f->ref = 1;
f->fs = fs;
f->boff = NilBlock;
f->mode = fs->mode;
return f;
}
static void
filefree(VacFile *f)
{
vtfileclose(f->source);
vtfileclose(f->msource);
vdcleanup(&f->dir);
memset(f, ~0, sizeof *f); /* paranoia */
vtfree(f);
}
static int
chksource(VacFile *f)
{
if(f->partial)
return 0;
if(f->source == nil
|| ((f->dir.mode & ModeDir) && f->msource == nil)){
werrstr(ERemoved);
return -1;
}
return 0;
}
static int
filelock(VacFile *f)
{
wlock(&f->lk);
if(chksource(f) < 0){
wunlock(&f->lk);
return -1;
}
return 0;
}
static void
fileunlock(VacFile *f)
{
wunlock(&f->lk);
}
static int
filerlock(VacFile *f)
{
rlock(&f->lk);
if(chksource(f) < 0){
runlock(&f->lk);
return -1;
}
return 0;
}
static void
filerunlock(VacFile *f)
{
runlock(&f->lk);
}
/*
* The file metadata, like f->dir and f->ref,
* are synchronized via the parent's lock.
* This is why locking order goes up.
*/
static void
filemetalock(VacFile *f)
{
assert(f->up != nil);
wlock(&f->up->lk);
}
static void
filemetaunlock(VacFile *f)
{
wunlock(&f->up->lk);
}
uvlong
vacfilegetid(VacFile *f)
{
/* immutable */
return f->qidoffset + f->dir.qid;
}
uvlong
vacfilegetqidoffset(VacFile *f)
{
return f->qidoffset;
}
ulong
vacfilegetmcount(VacFile *f)
{
ulong mcount;
filemetalock(f);
mcount = f->dir.mcount;
filemetaunlock(f);
return mcount;
}
ulong
vacfilegetmode(VacFile *f)
{
ulong mode;
filemetalock(f);
mode = f->dir.mode;
filemetaunlock(f);
return mode;
}
int
vacfileisdir(VacFile *f)
{
/* immutable */
return (f->dir.mode & ModeDir) != 0;
}
int
vacfileisroot(VacFile *f)
{
return f == f->fs->root;
}
/*
* The files are reference counted, and while the reference
* is bigger than zero, each file can be found in its parent's
* f->down list (chains via f->next), so that multiple threads
* end up sharing a VacFile* when referring to the same file.
*
* Each VacFile holds a reference to its parent.
*/
VacFile*
vacfileincref(VacFile *vf)
{
filemetalock(vf);
assert(vf->ref > 0);
vf->ref++;
filemetaunlock(vf);
return vf;
}
int
vacfiledecref(VacFile *f)
{
VacFile *p, *q, **qq;
if(f->up == nil){
/* never linked in */
assert(f->ref == 1);
filefree(f);
return 0;
}
filemetalock(f);
f->ref--;
if(f->ref > 0){
filemetaunlock(f);
return -1;
}
assert(f->ref == 0);
assert(f->down == nil);
if(f->source && vtfilelock(f->source, -1) >= 0){
vtfileflush(f->source);
vtfileunlock(f->source);
}
if(f->msource && vtfilelock(f->msource, -1) >= 0){
vtfileflush(f->msource);
vtfileunlock(f->msource);
}
/*
* Flush f's directory information to the cache.
*/
filemetaflush(f, nil);
p = f->up;
qq = &p->down;
for(q = *qq; q; q = *qq){
if(q == f)
break;
qq = &q->next;
}
assert(q != nil);
*qq = f->next;
filemetaunlock(f);
filefree(f);
vacfiledecref(p);
return 0;
}
/*
* Construct a vacfile for the root of a vac tree, given the
* venti file for the root information. That venti file is a
* directory file containing VtEntries for three more venti files:
* the two venti files making up the root directory, and a
* third venti file that would be the metadata half of the
* "root's parent".
*
* Fossil generates slightly different vac files, due to a now
* impossible-to-change bug, which contain a VtEntry
* for just one venti file, that itself contains the expected
* three directory entries. Sigh.
*/
VacFile*
_vacfileroot(VacFs *fs, VtFile *r)
{
int redirected;
char err[ERRMAX];
VtBlock *b;
VtFile *r0, *r1, *r2;
MetaBlock mb;
MetaEntry me;
VacFile *root, *mr;
redirected = 0;
Top:
b = nil;
root = nil;
mr = nil;
r1 = nil;
r2 = nil;
if(vtfilelock(r, -1) < 0)
return nil;
r0 = vtfileopen(r, 0, fs->mode);
if(debug)
fprint(2, "r0 %p\n", r0);
if(r0 == nil)
goto Err;
r2 = vtfileopen(r, 2, fs->mode);
if(debug)
fprint(2, "r2 %p\n", r2);
if(r2 == nil){
/*
* some vac files (e.g., from fossil)
* have an extra layer of indirection.
*/
rerrstr(err, sizeof err);
if(!redirected && strstr(err, "not active")){
redirected = 1;
vtfileunlock(r);
r = r0;
goto Top;
}
goto Err;
}
r1 = vtfileopen(r, 1, fs->mode);
if(debug)
fprint(2, "r1 %p\n", r1);
if(r1 == nil)
goto Err;
mr = filealloc(fs);
mr->msource = r2;
r2 = nil;
root = filealloc(fs);
root->boff = 0;
root->up = mr;
root->source = r0;
r0 = nil;
root->msource = r1;
r1 = nil;
mr->down = root;
vtfileunlock(r);
if(vtfilelock(mr->msource, VtOREAD) < 0)
goto Err1;
b = vtfileblock(mr->msource, 0, VtOREAD);
vtfileunlock(mr->msource);
if(b == nil)
goto Err1;
if(mbunpack(&mb, b->data, mr->msource->dsize) < 0)
goto Err1;
meunpack(&me, &mb, 0);
if(vdunpack(&root->dir, &me) < 0)
goto Err1;
vtblockput(b);
return root;
Err:
vtfileunlock(r);
Err1:
vtblockput(b);
if(r0)
vtfileclose(r0);
if(r1)
vtfileclose(r1);
if(r2)
vtfileclose(r2);
if(mr)
filefree(mr);
if(root)
filefree(root);
return nil;
}
/*
* Vac directories are a sequence of metablocks, each of which
* contains a bunch of metaentries sorted by file name.
* The whole sequence isn't sorted, though, so you still have
* to look at every block to find a given name.
* Dirlookup looks in f for an element name elem.
* It returns a new VacFile with the dir, boff, and mode
* filled in, but the sources (venti files) are not, and f is
* not yet linked into the tree. These details must be taken
* care of by the caller.
*
* f must be locked, f->msource must not.
*/
static VacFile*
dirlookup(VacFile *f, char *elem)
{
int i;
MetaBlock mb;
MetaEntry me;
VtBlock *b;
VtFile *meta;
VacFile *ff;
u32int bo, nb;
meta = f->msource;
b = nil;
if(vtfilelock(meta, -1) < 0)
return nil;
nb = (vtfilegetsize(meta)+meta->dsize-1)/meta->dsize;
for(bo=0; bo<nb; bo++){
b = vtfileblock(meta, bo, VtOREAD);
if(b == nil)
goto Err;
if(mbunpack(&mb, b->data, meta->dsize) < 0)
goto Err;
if(mbsearch(&mb, elem, &i, &me) >= 0){
ff = filealloc(f->fs);
if(vdunpack(&ff->dir, &me) < 0){
filefree(ff);
goto Err;
}
ff->qidoffset = f->qidoffset + ff->dir.qidoffset;
vtfileunlock(meta);
vtblockput(b);
ff->boff = bo;
ff->mode = f->mode;
return ff;
}
vtblockput(b);
b = nil;
}
werrstr(ENoFile);
/* fall through */
Err:
vtfileunlock(meta);
vtblockput(b);
return nil;
}
/*
* Open the venti file at offset in the directory f->source.
* f is locked.
*/
static VtFile *
fileopensource(VacFile *f, u32int offset, u32int gen, int dir, uint mode)
{
VtFile *r;
if((r = vtfileopen(f->source, offset, mode)) == nil)
return nil;
if(r == nil)
return nil;
if(r->gen != gen){
werrstr(ERemoved);
vtfileclose(r);
return nil;
}
if(r->dir != dir && r->mode != -1){
werrstr(EBadMeta);
vtfileclose(r);
return nil;
}
return r;
}
VacFile*
vacfilegetparent(VacFile *f)
{
if(vacfileisroot(f))
return vacfileincref(f);
return vacfileincref(f->up);
}
/*
* Given an unlocked vacfile (directory) f,
* return the vacfile named elem in f.
* Interprets . and .. as a convenience to callers.
*/
VacFile*
vacfilewalk(VacFile *f, char *elem)
{
VacFile *ff;
if(elem[0] == 0){
werrstr(EBadPath);
return nil;
}
if(!vacfileisdir(f)){
werrstr(ENotDir);
return nil;
}
if(strcmp(elem, ".") == 0)
return vacfileincref(f);
if(strcmp(elem, "..") == 0)
return vacfilegetparent(f);
if(filelock(f) < 0)
return nil;
for(ff = f->down; ff; ff=ff->next){
if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
ff->ref++;
goto Exit;
}
}
ff = dirlookup(f, elem);
if(ff == nil)
goto Err;
if(ff->dir.mode & ModeSnapshot)
ff->mode = VtOREAD;
if(vtfilelock(f->source, f->mode) < 0)
goto Err;
if(ff->dir.mode & ModeDir){
ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 1, ff->mode);
ff->msource = fileopensource(f, ff->dir.mentry, ff->dir.mgen, 0, ff->mode);
if(ff->source == nil || ff->msource == nil)
goto Err1;
}else{
ff->source = fileopensource(f, ff->dir.entry, ff->dir.gen, 0, ff->mode);
if(ff->source == nil)
goto Err1;
}
vtfileunlock(f->source);
/* link in and up parent ref count */
ff->next = f->down;
f->down = ff;
ff->up = f;
vacfileincref(f);
Exit:
fileunlock(f);
return ff;
Err1:
vtfileunlock(f->source);
Err:
fileunlock(f);
if(ff != nil)
vacfiledecref(ff);
return nil;
}
/*
* Open a path in the vac file system:
* just walk each element one at a time.
*/
VacFile*
vacfileopen(VacFs *fs, char *path)
{
VacFile *f, *ff;
char *p, elem[VtMaxStringSize], *opath;
int n;
f = fs->root;
vacfileincref(f);
opath = path;
while(*path != 0){
for(p = path; *p && *p != '/'; p++)
;
n = p - path;
if(n > 0){
if(n > VtMaxStringSize){
werrstr("%s: element too long", EBadPath);
goto Err;
}
memmove(elem, path, n);
elem[n] = 0;
ff = vacfilewalk(f, elem);
if(ff == nil){
werrstr("%.*s: %r", utfnlen(opath, p-opath), opath);
goto Err;
}
vacfiledecref(f);
f = ff;
}
if(*p == '/')
p++;
path = p;
}
return f;
Err:
vacfiledecref(f);
return nil;
}
/*
* Extract the score for the bn'th block in f.
*/
int
vacfileblockscore(VacFile *f, u32int bn, u8int *score)
{
VtFile *s;
uvlong size;
int dsize, ret;
ret = -1;
if(filerlock(f) < 0)
return -1;
if(vtfilelock(f->source, VtOREAD) < 0)
goto out;
s = f->source;
dsize = s->dsize;
size = vtfilegetsize(s);
if((uvlong)bn*dsize >= size)
goto out1;
ret = vtfileblockscore(f->source, bn, score);
out1:
vtfileunlock(f->source);
out:
filerunlock(f);
return ret;
}
/*
* Read data from f.
*/
int
vacfileread(VacFile *f, void *buf, int cnt, vlong offset)
{
int n;
if(offset < 0){
werrstr(EBadOffset);
return -1;
}
if(filerlock(f) < 0)
return -1;
if(vtfilelock(f->source, VtOREAD) < 0){
filerunlock(f);
return -1;
}
n = vtfileread(f->source, buf, cnt, offset);
vtfileunlock(f->source);
filerunlock(f);
return n;
}
static int
getentry(VtFile *f, VtEntry *e)
{
if(vtfilelock(f, VtOREAD) < 0)
return -1;
if(vtfilegetentry(f, e) < 0){
vtfileunlock(f);
return -1;
}
vtfileunlock(f);
if(vtglobaltolocal(e->score) != NilBlock){
werrstr("internal error - data not on venti");
return -1;
}
return 0;
}
/*
* Get the VtEntries for the data contained in f.
*/
int
vacfilegetentries(VacFile *f, VtEntry *e, VtEntry *me)
{
if(filerlock(f) < 0)
return -1;
if(e && getentry(f->source, e) < 0){
filerunlock(f);
return -1;
}
if(me){
if(f->msource == nil)
memset(me, 0, sizeof *me);
else if(getentry(f->msource, me) < 0){
filerunlock(f);
return -1;
}
}
filerunlock(f);
return 0;
}
/*
* Get the file's size.
*/
int
vacfilegetsize(VacFile *f, uvlong *size)
{
if(filerlock(f) < 0)
return -1;
if(vtfilelock(f->source, VtOREAD) < 0){
filerunlock(f);
return -1;
}
*size = vtfilegetsize(f->source);
vtfileunlock(f->source);
filerunlock(f);
return 0;
}
/*
* Directory reading.
*
* A VacDirEnum is a buffer containing directory entries.
* Directory entries contain malloced strings and need to
* be cleaned up with vdcleanup. The invariant in the
* VacDirEnum is that the directory entries between
* vde->i and vde->n are owned by the vde and need to
* be cleaned up if it is closed. Those from 0 up to vde->i
* have been handed to the reader, and the reader must
* take care of calling vdcleanup as appropriate.
*/
VacDirEnum*
vdeopen(VacFile *f)
{
VacDirEnum *vde;
VacFile *p;
if(!vacfileisdir(f)){
werrstr(ENotDir);
return nil;
}
/*
* There might be changes to this directory's children
* that have not been flushed out into the cache yet.
* Those changes are only available if we look at the
* VacFile structures directory. But the directory reader
* is going to read the cache blocks directly, so update them.
*/
if(filelock(f) < 0)
return nil;
for(p=f->down; p; p=p->next)
filemetaflush(p, nil);
fileunlock(f);
vde = vtmallocz(sizeof(VacDirEnum));
vde->file = vacfileincref(f);
return vde;
}
/*
* Figure out the size of the directory entry at offset.
* The rest of the metadata is kept in the data half,
* but since venti has to track the data size anyway,
* we just use that one and avoid updating the directory
* each time the file size changes.
*/
static int
direntrysize(VtFile *s, ulong offset, ulong gen, uvlong *size)
{
VtBlock *b;
ulong bn;
VtEntry e;
int epb;
epb = s->dsize/VtEntrySize;
bn = offset/epb;
offset -= bn*epb;
b = vtfileblock(s, bn, VtOREAD);
if(b == nil)
goto Err;
if(vtentryunpack(&e, b->data, offset) < 0)
goto Err;
/* dangling entries are returned as zero size */
if(!(e.flags & VtEntryActive) || e.gen != gen)
*size = 0;
else
*size = e.size;
vtblockput(b);
return 0;
Err:
vtblockput(b);
return -1;
}
/*
* Fill in vde with a new batch of directory entries.
*/
static int
vdefill(VacDirEnum *vde)
{
int i, n;
VtFile *meta, *source;
MetaBlock mb;
MetaEntry me;
VacFile *f;
VtBlock *b;
VacDir *de;
/* clean up first */
for(i=vde->i; i<vde->n; i++)
vdcleanup(vde->buf+i);
vtfree(vde->buf);
vde->buf = nil;
vde->i = 0;
vde->n = 0;
f = vde->file;
source = f->source;
meta = f->msource;
b = vtfileblock(meta, vde->boff, VtOREAD);
if(b == nil)
goto Err;
if(mbunpack(&mb, b->data, meta->dsize) < 0)
goto Err;
n = mb.nindex;
vde->buf = vtmalloc(n * sizeof(VacDir));
for(i=0; i<n; i++){
de = vde->buf + i;
meunpack(&me, &mb, i);
if(vdunpack(de, &me) < 0)
goto Err;
vde->n++;
if(!(de->mode & ModeDir))
if(direntrysize(source, de->entry, de->gen, &de->size) < 0)
goto Err;
}
vde->boff++;
vtblockput(b);
return 0;
Err:
vtblockput(b);
return -1;
}
/*
* Read a single directory entry from vde into de.
* Returns -1 on error, 0 on EOF, and 1 on success.
* When it returns 1, it becomes the caller's responsibility
* to call vdcleanup(de) to free the strings contained
* inside, or else to call vdunread to give it back.
*/
int
vderead(VacDirEnum *vde, VacDir *de)
{
int ret;
VacFile *f;
u32int nb;
f = vde->file;
if(filerlock(f) < 0)
return -1;
if(vtfilelock2(f->source, f->msource, VtOREAD) < 0){
filerunlock(f);
return -1;
}
nb = (vtfilegetsize(f->msource)+f->msource->dsize-1)/f->msource->dsize;
while(vde->i >= vde->n){
if(vde->boff >= nb){
ret = 0;
goto Return;
}
if(vdefill(vde) < 0){
ret = -1;
goto Return;
}
}
memmove(de, vde->buf + vde->i, sizeof(VacDir));
vde->i++;
ret = 1;
Return:
vtfileunlock(f->source);
vtfileunlock(f->msource);
filerunlock(f);
return ret;
}
/*
* "Unread" the last directory entry that was read,
* so that the next vderead will return the same one.
* If the caller calls vdeunread(vde) it should not call
* vdcleanup on the entry being "unread".
*/
int
vdeunread(VacDirEnum *vde)
{
if(vde->i > 0){
vde->i--;
return 0;
}
return -1;
}
/*
* Close the enumerator.
*/
void
vdeclose(VacDirEnum *vde)
{
int i;
if(vde == nil)
return;
/* free the strings */
for(i=vde->i; i<vde->n; i++)
vdcleanup(vde->buf+i);
vtfree(vde->buf);
vacfiledecref(vde->file);
vtfree(vde);
}
/*
* On to mutation. If the vac file system has been opened
* read-write, then the files and directories can all be edited.
* Changes are kept in the in-memory cache until flushed out
* to venti, so we must be careful to explicitly flush data
* that we're not likely to modify again.
*
* Each VacFile has its own copy of its VacDir directory entry
* in f->dir, but otherwise the cache is the authoratative source
* for data. Thus, for the most part, it suffices if we just
* call vtfileflushbefore and vtfileflush when we modify things.
* There are a few places where we have to remember to write
* changed VacDirs back into the cache. If f->dir *is* out of sync,
* then f->dirty should be set.
*
* The metadata in a directory is, to venti, a plain data file,
* but as mentioned above it is actually a sequence of
* MetaBlocks that contain sorted lists of VacDir entries.
* The filemetaxxx routines manipulate that stream.
*/
/*
* Find space in fp for the directory entry dir (not yet written to disk)
* and write it to disk, returning NilBlock on failure,
* or the block number on success.
*
* Start is a suggested block number to try.
* The caller must have filemetalock'ed f and have
* vtfilelock'ed f->up->msource.
*/
static u32int
filemetaalloc(VacFile *fp, VacDir *dir, u32int start)
{
u32int nb, bo;
VtBlock *b;
MetaBlock mb;
int nn;
uchar *p;
int i, n;
MetaEntry me;
VtFile *ms;
ms = fp->msource;
n = vdsize(dir, VacDirVersion);
/* Look for a block with room for a new entry of size n. */
nb = (vtfilegetsize(ms)+ms->dsize-1)/ms->dsize;
if(start == NilBlock){
if(nb > 0)
start = nb - 1;
else
start = 0;
}
if(start > nb)
start = nb;
for(bo=start; bo<nb; bo++){
if((b = vtfileblock(ms, bo, VtOREAD)) == nil)
goto Err;
if(mbunpack(&mb, b->data, ms->dsize) < 0)
goto Err;
nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free;
if(n <= nn && mb.nindex < mb.maxindex){
/* reopen for writing */
vtblockput(b);
if((b = vtfileblock(ms, bo, VtORDWR)) == nil)
goto Err;
mbunpack(&mb, b->data, ms->dsize);
goto Found;
}
vtblockput(b);
}
/* No block found, extend the file by one metablock. */
vtfileflushbefore(ms, nb*(uvlong)ms->dsize);
if((b = vtfileblock(ms, nb, VtORDWR)) == nil)
goto Err;
vtfilesetsize(ms, (nb+1)*ms->dsize);
mbinit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry);
Found:
/* Now we have a block; allocate space to write the entry. */
p = mballoc(&mb, n);
if(p == nil){
/* mballoc might have changed block */
mbpack(&mb);
werrstr(EBadMeta);
goto Err;
}
/* Figure out where to put the index entry, and write it. */
mbsearch(&mb, dir->elem, &i, &me);
assert(me.p == nil); /* not already there */
me.p = p;
me.size = n;
vdpack(dir, &me, VacDirVersion);
mbinsert(&mb, i, &me);
mbpack(&mb);
vtblockput(b);
return bo;
Err:
vtblockput(b);
return NilBlock;
}
/*
* Update f's directory entry in the block cache.
* We look for the directory entry by name;
* if we're trying to rename the file, oelem is the old name.
*
* Assumes caller has filemetalock'ed f.
*/
static int
filemetaflush(VacFile *f, char *oelem)
{
int i, n;
MetaBlock mb;
MetaEntry me, me2;
VacFile *fp;
VtBlock *b;
u32int bo;
if(!f->dirty)
return 0;
if(oelem == nil)
oelem = f->dir.elem;
/*
* Locate f's old metadata in the parent's metadata file.
* We know which block it was in, but not exactly where
* in the block.
*/
fp = f->up;
if(vtfilelock(fp->msource, -1) < 0)
return -1;
/* can happen if source is clri'ed out from under us */
if(f->boff == NilBlock)
goto Err1;
b = vtfileblock(fp->msource, f->boff, VtORDWR);
if(b == nil)
goto Err1;
if(mbunpack(&mb, b->data, fp->msource->dsize) < 0)
goto Err;
if(mbsearch(&mb, oelem, &i, &me) < 0)
goto Err;
/*
* Check whether we can resize the entry and keep it
* in this block.
*/
n = vdsize(&f->dir, VacDirVersion);
if(mbresize(&mb, &me, n) >= 0){
/* Okay, can be done without moving to another block. */
/* Remove old data */
mbdelete(&mb, i, &me);
/* Find new location if renaming */
if(strcmp(f->dir.elem, oelem) != 0)
mbsearch(&mb, f->dir.elem, &i, &me2);
/* Pack new data into new location. */
vdpack(&f->dir, &me, VacDirVersion);
vdunpack(&f->dir, &me);
mbinsert(&mb, i, &me);
mbpack(&mb);
/* Done */
vtblockput(b);
vtfileunlock(fp->msource);
f->dirty = 0;
return 0;
}
/*
* The entry must be moved to another block.
* This can only really happen on renames that
* make the name very long.
*/
/* Allocate a spot in a new block. */
if((bo = filemetaalloc(fp, &f->dir, f->boff+1)) == NilBlock){
/* mbresize above might have modified block */
mbpack(&mb);
goto Err;
}
f->boff = bo;
/* Now we're committed. Delete entry in old block. */
mbdelete(&mb, i, &me);
mbpack(&mb);
vtblockput(b);
vtfileunlock(fp->msource);
f->dirty = 0;
return 0;
Err:
vtblockput(b);
Err1:
vtfileunlock(fp->msource);
return -1;
}
/*
* Remove the directory entry for f.
*/
static int
filemetaremove(VacFile *f)
{
VtBlock *b;
MetaBlock mb;
MetaEntry me;
int i;
VacFile *fp;
b = nil;
fp = f->up;
filemetalock(f);
if(vtfilelock(fp->msource, VtORDWR) < 0)
goto Err;
b = vtfileblock(fp->msource, f->boff, VtORDWR);
if(b == nil)
goto Err;
if(mbunpack(&mb, b->data, fp->msource->dsize) < 0)
goto Err;
if(mbsearch(&mb, f->dir.elem, &i, &me) < 0)
goto Err;
mbdelete(&mb, i, &me);
mbpack(&mb);
vtblockput(b);
vtfileunlock(fp->msource);
f->removed = 1;
f->boff = NilBlock;
f->dirty = 0;
filemetaunlock(f);
return 0;
Err:
vtfileunlock(fp->msource);
vtblockput(b);
filemetaunlock(f);
return -1;
}
/*
* That was far too much effort for directory entries.
* Now we can write code that *does* things.
*/
/*
* Flush all data associated with f out of the cache and onto venti.
* If recursive is set, flush f's children too.
* Vacfiledecref knows how to flush source and msource too.
*/
int
vacfileflush(VacFile *f, int recursive)
{
int ret;
VacFile **kids, *p;
int i, nkids;
if(f->mode == VtOREAD)
return 0;
ret = 0;
filemetalock(f);
if(filemetaflush(f, nil) < 0)
ret = -1;
filemetaunlock(f);
if(filelock(f) < 0)
return -1;
/*
* Lock order prevents us from flushing kids while holding
* lock, so make a list and then flush without the lock.
*/
nkids = 0;
kids = nil;
if(recursive){
nkids = 0;
for(p=f->down; p; p=p->next)
nkids++;
kids = vtmalloc(nkids*sizeof(VacFile*));
i = 0;
for(p=f->down; p; p=p->next){
kids[i++] = p;
p->ref++;
}
}
if(nkids > 0){
fileunlock(f);
for(i=0; i<nkids; i++){
if(vacfileflush(kids[i], 1) < 0)
ret = -1;
vacfiledecref(kids[i]);
}
filelock(f);
}
free(kids);
/*
* Now we can flush our own data.
*/
vtfilelock(f->source, -1);
if(vtfileflush(f->source) < 0)
ret = -1;
vtfileunlock(f->source);
if(f->msource){
vtfilelock(f->msource, -1);
if(vtfileflush(f->msource) < 0)
ret = -1;
vtfileunlock(f->msource);
}
fileunlock(f);
return ret;
}
/*
* Create a new file named elem in fp with the given mode.
* The mode can be changed later except for the ModeDir bit.
*/
VacFile*
vacfilecreate(VacFile *fp, char *elem, ulong mode)
{
VacFile *ff;
VacDir *dir;
VtFile *pr, *r, *mr;
int type;
u32int bo;
if(filelock(fp) < 0)
return nil;
/*
* First, look to see that there's not a file in memory
* with the same name.
*/
for(ff = fp->down; ff; ff=ff->next){
if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
ff = nil;
werrstr(EExists);
goto Err1;
}
}
/*
* Next check the venti blocks.
*/
ff = dirlookup(fp, elem);
if(ff != nil){
werrstr(EExists);
goto Err1;
}
/*
* By the way, you can't create in a read-only file system.
*/
pr = fp->source;
if(pr->mode != VtORDWR){
werrstr(EReadOnly);
goto Err1;
}
/*
* Okay, time to actually create something. Lock the two
* halves of the directory and create a file.
*/
if(vtfilelock2(fp->source, fp->msource, -1) < 0)
goto Err1;
ff = filealloc(fp->fs);
ff->qidoffset = fp->qidoffset; /* hopefully fp->qidoffset == 0 */
type = VtDataType;
if(mode & ModeDir)
type = VtDirType;
mr = nil;
if((r = vtfilecreate(pr, pr->psize, pr->dsize, type)) == nil)
goto Err;
if(mode & ModeDir)
if((mr = vtfilecreate(pr, pr->psize, pr->dsize, VtDataType)) == nil)
goto Err;
/*
* Fill in the directory entry and write it to disk.
*/
dir = &ff->dir;
dir->elem = vtstrdup(elem);
dir->entry = r->offset;
dir->gen = r->gen;
if(mode & ModeDir){
dir->mentry = mr->offset;
dir->mgen = mr->gen;
}
dir->size = 0;
if(_vacfsnextqid(fp->fs, &dir->qid) < 0)
goto Err;
dir->uid = vtstrdup(fp->dir.uid);
dir->gid = vtstrdup(fp->dir.gid);
dir->mid = vtstrdup("");
dir->mtime = time(0L);
dir->mcount = 0;
dir->ctime = dir->mtime;
dir->atime = dir->mtime;
dir->mode = mode;
if((bo = filemetaalloc(fp, &ff->dir, NilBlock)) == NilBlock)
goto Err;
/*
* Now we're committed.
*/
vtfileunlock(fp->source);
vtfileunlock(fp->msource);
ff->source = r;
ff->msource = mr;
ff->boff = bo;
/* Link into tree. */
ff->next = fp->down;
fp->down = ff;
ff->up = fp;
vacfileincref(fp);
fileunlock(fp);
filelock(ff);
vtfilelock(ff->source, -1);
vtfileunlock(ff->source);
fileunlock(ff);
return ff;
Err:
vtfileunlock(fp->source);
vtfileunlock(fp->msource);
if(r){
vtfilelock(r, -1);
vtfileremove(r);
}
if(mr){
vtfilelock(mr, -1);
vtfileremove(mr);
}
Err1:
if(ff)
vacfiledecref(ff);
fileunlock(fp);
return nil;
}
/*
* Change the size of the file f.
*/
int
vacfilesetsize(VacFile *f, uvlong size)
{
if(vacfileisdir(f)){
werrstr(ENotFile);
return -1;
}
if(filelock(f) < 0)
return -1;
if(f->source->mode != VtORDWR){
werrstr(EReadOnly);
goto Err;
}
if(vtfilelock(f->source, -1) < 0)
goto Err;
if(vtfilesetsize(f->source, size) < 0){
vtfileunlock(f->source);
goto Err;
}
vtfileunlock(f->source);
fileunlock(f);
return 0;
Err:
fileunlock(f);
return -1;
}
/*
* Write data to f.
*/
int
vacfilewrite(VacFile *f, void *buf, int cnt, vlong offset)
{
if(vacfileisdir(f)){
werrstr(ENotFile);
return -1;
}
if(filelock(f) < 0)
return -1;
if(f->source->mode != VtORDWR){
werrstr(EReadOnly);
goto Err;
}
if(offset < 0){
werrstr(EBadOffset);
goto Err;
}
if(vtfilelock(f->source, -1) < 0)
goto Err;
if(f->dir.mode & ModeAppend)
offset = vtfilegetsize(f->source);
if(vtfilewrite(f->source, buf, cnt, offset) != cnt
|| vtfileflushbefore(f->source, offset) < 0){
vtfileunlock(f->source);
goto Err;
}
vtfileunlock(f->source);
fileunlock(f);
return cnt;
Err:
fileunlock(f);
return -1;
}
/*
* Set (!) the VtEntry for the data contained in f.
* This let's us efficiently copy data from one file to another.
*/
int
vacfilesetentries(VacFile *f, VtEntry *e, VtEntry *me)
{
int ret;
vacfileflush(f, 0); /* flush blocks to venti, since we won't see them again */
if(!(e->flags&VtEntryActive)){
werrstr("missing entry for source");
return -1;
}
if(me && !(me->flags&VtEntryActive))
me = nil;
if(f->msource && !me){
werrstr("missing entry for msource");
return -1;
}
if(me && !f->msource){
werrstr("no msource to set");
return -1;
}
if(filelock(f) < 0)
return -1;
if(f->source->mode != VtORDWR
|| (f->msource && f->msource->mode != VtORDWR)){
werrstr(EReadOnly);
fileunlock(f);
return -1;
}
if(vtfilelock2(f->source, f->msource, -1) < 0){
fileunlock(f);
return -1;
}
ret = 0;
if(vtfilesetentry(f->source, e) < 0)
ret = -1;
else if(me && vtfilesetentry(f->msource, me) < 0)
ret = -1;
vtfileunlock(f->source);
if(f->msource)
vtfileunlock(f->msource);
fileunlock(f);
return ret;
}
/*
* Get the directory entry for f.
*/
int
vacfilegetdir(VacFile *f, VacDir *dir)
{
if(filerlock(f) < 0)
return -1;
filemetalock(f);
vdcopy(dir, &f->dir);
filemetaunlock(f);
if(!vacfileisdir(f)){
if(vtfilelock(f->source, VtOREAD) < 0){
filerunlock(f);
return -1;
}
dir->size = vtfilegetsize(f->source);
vtfileunlock(f->source);
}
filerunlock(f);
return 0;
}
/*
* Set the directory entry for f.
*/
int
vacfilesetdir(VacFile *f, VacDir *dir)
{
VacFile *ff;
char *oelem;
u32int mask;
u64int size;
/* can not set permissions for the root */
if(vacfileisroot(f)){
werrstr(ERoot);
return -1;
}
if(filelock(f) < 0)
return -1;
filemetalock(f);
if(f->source->mode != VtORDWR){
werrstr(EReadOnly);
goto Err;
}
/* On rename, check new name does not already exist */
if(strcmp(f->dir.elem, dir->elem) != 0){
for(ff = f->up->down; ff; ff=ff->next){
if(strcmp(dir->elem, ff->dir.elem) == 0 && !ff->removed){
werrstr(EExists);
goto Err;
}
}
ff = dirlookup(f->up, dir->elem);
if(ff != nil){
vacfiledecref(ff);
werrstr(EExists);
goto Err;
}
werrstr(""); /* "failed" dirlookup poisoned it */
}
/* Get ready... */
if(vtfilelock2(f->source, f->msource, -1) < 0)
goto Err;
if(!vacfileisdir(f)){
size = vtfilegetsize(f->source);
if(size != dir->size){
if(vtfilesetsize(f->source, dir->size) < 0){
vtfileunlock(f->source);
if(f->msource)
vtfileunlock(f->msource);
goto Err;
}
}
}
/* ... now commited to changing it. */
vtfileunlock(f->source);
if(f->msource)
vtfileunlock(f->msource);
oelem = nil;
if(strcmp(f->dir.elem, dir->elem) != 0){
oelem = f->dir.elem;
f->dir.elem = vtstrdup(dir->elem);
}
if(strcmp(f->dir.uid, dir->uid) != 0){
vtfree(f->dir.uid);
f->dir.uid = vtstrdup(dir->uid);
}
if(strcmp(f->dir.gid, dir->gid) != 0){
vtfree(f->dir.gid);
f->dir.gid = vtstrdup(dir->gid);
}
f->dir.mtime = dir->mtime;
f->dir.atime = dir->atime;
mask = ~(ModeDir|ModeSnapshot);
f->dir.mode &= ~mask;
f->dir.mode |= mask & dir->mode;
f->dirty = 1;
if(filemetaflush(f, oelem) < 0){
vtfree(oelem);
goto Err; /* that sucks */
}
vtfree(oelem);
filemetaunlock(f);
fileunlock(f);
return 0;
Err:
filemetaunlock(f);
fileunlock(f);
return -1;
}
/*
* Set the qid space.
*/
int
vacfilesetqidspace(VacFile *f, u64int offset, u64int max)
{
int ret;
if(filelock(f) < 0)
return -1;
if(f->source->mode != VtORDWR){
fileunlock(f);
werrstr(EReadOnly);
return -1;
}
filemetalock(f);
f->dir.qidspace = 1;
f->dir.qidoffset = offset;
f->dir.qidmax = max;
f->dirty = 1;
ret = filemetaflush(f, nil);
filemetaunlock(f);
fileunlock(f);
return ret;
}
/*
* Check that the file is empty, returning 0 if it is.
* Returns -1 on error (and not being empty is an error).
*/
static int
filecheckempty(VacFile *f)
{
u32int i, n;
VtBlock *b;
MetaBlock mb;
VtFile *r;
r = f->msource;
n = (vtfilegetsize(r)+r->dsize-1)/r->dsize;
for(i=0; i<n; i++){
b = vtfileblock(r, i, VtOREAD);
if(b == nil)
return -1;
if(mbunpack(&mb, b->data, r->dsize) < 0)
goto Err;
if(mb.nindex > 0){
werrstr(ENotEmpty);
goto Err;
}
vtblockput(b);
}
return 0;
Err:
vtblockput(b);
return -1;
}
/*
* Remove the vac file f.
*/
int
vacfileremove(VacFile *f)
{
VacFile *ff;
/* Cannot remove the root */
if(vacfileisroot(f)){
werrstr(ERoot);
return -1;
}
if(filelock(f) < 0)
return -1;
if(f->source->mode != VtORDWR){
werrstr(EReadOnly);
goto Err1;
}
if(vtfilelock2(f->source, f->msource, -1) < 0)
goto Err1;
if(vacfileisdir(f) && filecheckempty(f)<0)
goto Err;
for(ff=f->down; ff; ff=ff->next)
assert(ff->removed);
vtfileremove(f->source);
f->source = nil;
if(f->msource){
vtfileremove(f->msource);
f->msource = nil;
}
fileunlock(f);
if(filemetaremove(f) < 0)
return -1;
return 0;
Err:
vtfileunlock(f->source);
if(f->msource)
vtfileunlock(f->msource);
Err1:
fileunlock(f);
return -1;
}
/*
* Vac file system format.
*/
static char EBadVacFormat[] = "bad format for vac file";
static VacFs *
vacfsalloc(VtConn *z, int bsize, int ncache, int mode)
{
VacFs *fs;
fs = vtmallocz(sizeof(VacFs));
fs->z = z;
fs->bsize = bsize;
fs->mode = mode;
fs->cache = vtcachealloc(z, bsize, ncache);
return fs;
}
static int
readscore(int fd, uchar score[VtScoreSize])
{
char buf[45], *pref;
int n;
n = readn(fd, buf, sizeof(buf)-1);
if(n < sizeof(buf)-1) {
werrstr("short read");
return -1;
}
buf[n] = 0;
if(vtparsescore(buf, &pref, score) < 0){
werrstr(EBadVacFormat);
return -1;
}
if(pref==nil || strcmp(pref, "vac") != 0) {
werrstr("not a vac file");
return -1;
}
return 0;
}
VacFs*
vacfsopen(VtConn *z, char *file, int mode, int ncache)
{
int fd;
uchar score[VtScoreSize];
char *prefix;
if(vtparsescore(file, &prefix, score) >= 0){
if(strcmp(prefix, "vac") != 0){
werrstr("not a vac file");
return nil;
}
}else{
fd = open(file, OREAD);
if(fd < 0)
return nil;
if(readscore(fd, score) < 0){
close(fd);
return nil;
}
close(fd);
}
return vacfsopenscore(z, score, mode, ncache);
}
VacFs*
vacfsopenscore(VtConn *z, u8int *score, int mode, int ncache)
{
VacFs *fs;
int n;
VtRoot rt;
uchar buf[VtRootSize];
VacFile *root;
VtFile *r;
VtEntry e;
n = vtread(z, score, VtRootType, buf, VtRootSize);
if(n < 0)
return nil;
if(n != VtRootSize){
werrstr("vtread on root too short");
return nil;
}
if(vtrootunpack(&rt, buf) < 0)
return nil;
if(strcmp(rt.type, "vac") != 0) {
werrstr("not a vac root");
return nil;
}
fs = vacfsalloc(z, rt.blocksize, ncache, mode);
memmove(fs->score, score, VtScoreSize);
fs->mode = mode;
memmove(e.score, rt.score, VtScoreSize);
e.gen = 0;
e.psize = rt.blocksize;
e.dsize = rt.blocksize;
e.type = VtDirType;
e.flags = VtEntryActive;
e.size = 3*VtEntrySize;
root = nil;
if((r = vtfileopenroot(fs->cache, &e)) == nil)
goto Err;
if(debug)
fprint(2, "r %p\n", r);
root = _vacfileroot(fs, r);
if(debug)
fprint(2, "root %p\n", root);
vtfileclose(r);
if(root == nil)
goto Err;
fs->root = root;
return fs;
Err:
if(root)
vacfiledecref(root);
vacfsclose(fs);
return nil;
}
int
vacfsmode(VacFs *fs)
{
return fs->mode;
}
VacFile*
vacfsgetroot(VacFs *fs)
{
return vacfileincref(fs->root);
}
int
vacfsgetblocksize(VacFs *fs)
{
return fs->bsize;
}
int
vacfsgetscore(VacFs *fs, u8int *score)
{
memmove(score, fs->score, VtScoreSize);
return 0;
}
int
_vacfsnextqid(VacFs *fs, uvlong *qid)
{
++fs->qid;
*qid = fs->qid;
return 0;
}
void
vacfsjumpqid(VacFs *fs, uvlong step)
{
fs->qid += step;
}
/*
* Set *maxqid to the maximum qid expected in this file system.
* In newer vac archives, the maximum qid is stored in the
* qidspace VacDir annotation. In older vac archives, the root
* got created last, so it had the maximum qid.
*/
int
vacfsgetmaxqid(VacFs *fs, uvlong *maxqid)
{
VacDir vd;
if(vacfilegetdir(fs->root, &vd) < 0)
return -1;
if(vd.qidspace)
*maxqid = vd.qidmax;
else
*maxqid = vd.qid;
vdcleanup(&vd);
return 0;
}
void
vacfsclose(VacFs *fs)
{
if(fs->root)
vacfiledecref(fs->root);
fs->root = nil;
vtcachefree(fs->cache);
vtfree(fs);
}
/*
* Create a fresh vac fs.
*/
VacFs *
vacfscreate(VtConn *z, int bsize, int ncache)
{
VacFs *fs;
VtFile *f;
uchar buf[VtEntrySize], metascore[VtScoreSize];
VtEntry e;
VtBlock *b;
MetaBlock mb;
VacDir vd;
MetaEntry me;
int psize;
int mbsize;
if((fs = vacfsalloc(z, bsize, ncache, VtORDWR)) == nil)
return nil;
/*
* Fake up an empty vac fs.
*/
psize = bsize;
f = vtfilecreateroot(fs->cache, psize, bsize, VtDirType);
vtfilelock(f, VtORDWR);
/* Metablocks can't be too big -- they have 16-bit offsets in them. */
mbsize = bsize;
if(mbsize >= 56*1024)
mbsize = 56*1024;
/* Write metablock containing root directory VacDir. */
b = vtcacheallocblock(fs->cache, VtDataType);
mbinit(&mb, b->data, mbsize, mbsize/BytesPerEntry);
memset(&vd, 0, sizeof vd);
vd.elem = "/";
vd.mode = 0777|ModeDir;
vd.uid = "vac";
vd.gid = "vac";
vd.mid = "";
me.size = vdsize(&vd, VacDirVersion);
me.p = mballoc(&mb, me.size);
vdpack(&vd, &me, VacDirVersion);
mbinsert(&mb, 0, &me);
mbpack(&mb);
vtblockwrite(b);
memmove(metascore, b->score, VtScoreSize);
vtblockput(b);
/* First entry: empty venti directory stream. */
memset(&e, 0, sizeof e);
e.flags = VtEntryActive;
e.psize = psize;
e.dsize = bsize;
e.type = VtDirType;
memmove(e.score, vtzeroscore, VtScoreSize);
vtentrypack(&e, buf, 0);
vtfilewrite(f, buf, VtEntrySize, 0);
/* Second entry: empty metadata stream. */
e.type = VtDataType;
e.dsize = mbsize;
vtentrypack(&e, buf, 0);
vtfilewrite(f, buf, VtEntrySize, VtEntrySize);
/* Third entry: metadata stream with root directory. */
memmove(e.score, metascore, VtScoreSize);
e.size = mbsize;
vtentrypack(&e, buf, 0);
vtfilewrite(f, buf, VtEntrySize, VtEntrySize*2);
vtfileflush(f);
vtfileunlock(f);
/* Now open it as a vac fs. */
fs->root = _vacfileroot(fs, f);
if(fs->root == nil){
werrstr("vacfileroot: %r");
vacfsclose(fs);
return nil;
}
return fs;
}
int
vacfssync(VacFs *fs)
{
uchar buf[1024];
VtEntry e;
VtFile *f;
VtRoot root;
/* Sync the entire vacfs to disk. */
if(vacfileflush(fs->root, 1) < 0)
return -1;
if(vtfilelock(fs->root->up->msource, -1) < 0)
return -1;
if(vtfileflush(fs->root->up->msource) < 0){
vtfileunlock(fs->root->up->msource);
return -1;
}
vtfileunlock(fs->root->up->msource);
/* Prepare the dir stream for the root block. */
if(getentry(fs->root->source, &e) < 0)
return -1;
vtentrypack(&e, buf, 0);
if(getentry(fs->root->msource, &e) < 0)
return -1;
vtentrypack(&e, buf, 1);
if(getentry(fs->root->up->msource, &e) < 0)
return -1;
vtentrypack(&e, buf, 2);
f = vtfilecreateroot(fs->cache, fs->bsize, fs->bsize, VtDirType);
vtfilelock(f, VtORDWR);
if(vtfilewrite(f, buf, 3*VtEntrySize, 0) < 0
|| vtfileflush(f) < 0){
vtfileunlock(f);
vtfileclose(f);
return -1;
}
vtfileunlock(f);
if(getentry(f, &e) < 0){
vtfileclose(f);
return -1;
}
vtfileclose(f);
/* Build a root block. */
memset(&root, 0, sizeof root);
strcpy(root.type, "vac");
strcpy(root.name, fs->name);
memmove(root.score, e.score, VtScoreSize);
root.blocksize = fs->bsize;
memmove(root.prev, fs->score, VtScoreSize);
vtrootpack(&root, buf);
if(vtwrite(fs->z, fs->score, VtRootType, buf, VtRootSize) < 0){
werrstr("writing root: %r");
return -1;
}
if(vtsync(fs->z) < 0)
return -1;
return 0;
}
int
vacfiledsize(VacFile *f)
{
VtEntry e;
if(vacfilegetentries(f,&e,nil) < 0)
return -1;
return e.dsize;
}
/*
* Does block b of f have the same SHA1 hash as the n bytes at buf?
*/
int
sha1matches(VacFile *f, ulong b, uchar *buf, int n)
{
uchar fscore[VtScoreSize];
uchar bufscore[VtScoreSize];
if(vacfileblockscore(f, b, fscore) < 0)
return 0;
n = vtzerotruncate(VtDataType, buf, n);
sha1(buf, n, bufscore, nil);
if(memcmp(bufscore, fscore, VtScoreSize) == 0)
return 1;
return 0;
}
|