2105 lines
39 KiB
C
2105 lines
39 KiB
C
#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);
|
|
}
|
|
|
|
if(strcmp(f->dir.mid, dir->mid) != 0){
|
|
vtfree(f->dir.mid);
|
|
f->dir.mid = vtstrdup(dir->mid);
|
|
}
|
|
|
|
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(prefix == nil || 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;
|
|
}
|
|
|