blob: ac7242513af3f692c53814f9d4cea331b67461d3 [file] [log] [blame]
#include "stdinc.h"
#include <bio.h>
#include "dat.h"
#include "fns.h"
#include "9.h"
struct Fsys {
QLock lock;
char* name; /* copy here & Fs to ease error reporting */
char* dev;
char* venti;
Fs* fs;
VtConn* session;
int ref;
int noauth;
int noperm;
int wstatallow;
Fsys* next;
};
int mempcnt; /* from fossil.c */
int fsGetBlockSize(Fs *fs);
static struct {
RWLock lock;
Fsys* head;
Fsys* tail;
char* curfsys;
} sbox;
static char *_argv0;
#define argv0 _argv0
static char FsysAll[] = "all";
static char EFsysBusy[] = "fsys: '%s' busy";
static char EFsysExists[] = "fsys: '%s' already exists";
static char EFsysNoCurrent[] = "fsys: no current fsys";
static char EFsysNotFound[] = "fsys: '%s' not found";
static char EFsysNotOpen[] = "fsys: '%s' not open";
static char *
ventihost(char *host)
{
if(host != nil)
return vtstrdup(host);
host = getenv("venti");
if(host == nil)
host = vtstrdup("$venti");
return host;
}
static void
prventihost(char *host)
{
char *vh;
vh = ventihost(host);
fprint(2, "%s: dialing venti at %s\n",
argv0, netmkaddr(vh, 0, "venti"));
free(vh);
}
static VtConn *
myDial(char *host)
{
prventihost(host);
return vtdial(host);
}
static int
myRedial(VtConn *z, char *host)
{
prventihost(host);
return vtredial(z, host);
}
static Fsys*
_fsysGet(char* name)
{
Fsys *fsys;
if(name == nil || name[0] == '\0')
name = "main";
rlock(&sbox.lock);
for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
if(strcmp(name, fsys->name) == 0){
fsys->ref++;
break;
}
}
runlock(&sbox.lock);
if(fsys == nil)
werrstr(EFsysNotFound, name);
return fsys;
}
static int
cmdPrintConfig(int argc, char* argv[])
{
Fsys *fsys;
char *usage = "usage: printconfig";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc)
return cliError(usage);
rlock(&sbox.lock);
for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
consPrint("\tfsys %s config %s\n", fsys->name, fsys->dev);
if(fsys->venti && fsys->venti[0])
consPrint("\tfsys %s venti %q\n", fsys->name,
fsys->venti);
}
runlock(&sbox.lock);
return 1;
}
Fsys*
fsysGet(char* name)
{
Fsys *fsys;
if((fsys = _fsysGet(name)) == nil)
return nil;
qlock(&fsys->lock);
if(fsys->fs == nil){
werrstr(EFsysNotOpen, fsys->name);
qunlock(&fsys->lock);
fsysPut(fsys);
return nil;
}
qunlock(&fsys->lock);
return fsys;
}
char*
fsysGetName(Fsys* fsys)
{
return fsys->name;
}
Fsys*
fsysIncRef(Fsys* fsys)
{
wlock(&sbox.lock);
fsys->ref++;
wunlock(&sbox.lock);
return fsys;
}
void
fsysPut(Fsys* fsys)
{
wlock(&sbox.lock);
assert(fsys->ref > 0);
fsys->ref--;
wunlock(&sbox.lock);
}
Fs*
fsysGetFs(Fsys* fsys)
{
assert(fsys != nil && fsys->fs != nil);
return fsys->fs;
}
void
fsysFsRlock(Fsys* fsys)
{
rlock(&fsys->fs->elk);
}
void
fsysFsRUnlock(Fsys* fsys)
{
runlock(&fsys->fs->elk);
}
int
fsysNoAuthCheck(Fsys* fsys)
{
return fsys->noauth;
}
int
fsysNoPermCheck(Fsys* fsys)
{
return fsys->noperm;
}
int
fsysWstatAllow(Fsys* fsys)
{
return fsys->wstatallow;
}
static char modechars[] = "YUGalLdHSATs";
static ulong modebits[] = {
ModeSticky,
ModeSetUid,
ModeSetGid,
ModeAppend,
ModeExclusive,
ModeLink,
ModeDir,
ModeHidden,
ModeSystem,
ModeArchive,
ModeTemporary,
ModeSnapshot,
0
};
char*
fsysModeString(ulong mode, char *buf)
{
int i;
char *p;
p = buf;
for(i=0; modebits[i]; i++)
if(mode & modebits[i])
*p++ = modechars[i];
sprint(p, "%luo", mode&0777);
return buf;
}
int
fsysParseMode(char* s, ulong* mode)
{
ulong x, y;
char *p;
x = 0;
for(; *s < '0' || *s > '9'; s++){
if(*s == 0)
return 0;
p = strchr(modechars, *s);
if(p == nil)
return 0;
x |= modebits[p-modechars];
}
y = strtoul(s, &p, 8);
if(*p != '\0' || y > 0777)
return 0;
*mode = x|y;
return 1;
}
File*
fsysGetRoot(Fsys* fsys, char* name)
{
File *root, *sub;
assert(fsys != nil && fsys->fs != nil);
root = fsGetRoot(fsys->fs);
if(name == nil || strcmp(name, "") == 0)
return root;
sub = fileWalk(root, name);
fileDecRef(root);
return sub;
}
static Fsys*
fsysAlloc(char* name, char* dev)
{
Fsys *fsys;
wlock(&sbox.lock);
for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
if(strcmp(fsys->name, name) != 0)
continue;
werrstr(EFsysExists, name);
wunlock(&sbox.lock);
return nil;
}
fsys = vtmallocz(sizeof(Fsys));
fsys->name = vtstrdup(name);
fsys->dev = vtstrdup(dev);
fsys->ref = 1;
if(sbox.tail != nil)
sbox.tail->next = fsys;
else
sbox.head = fsys;
sbox.tail = fsys;
wunlock(&sbox.lock);
return fsys;
}
static int
fsysClose(Fsys* fsys, int argc, char* argv[])
{
char *usage = "usage: [fsys name] close";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc)
return cliError(usage);
return cliError("close isn't working yet; halt %s and then kill fossil",
fsys->name);
/*
* Oooh. This could be hard. What if fsys->ref != 1?
* Also, fsClose() either does the job or panics, can we
* gracefully detect it's still busy?
*
* More thought and care needed here.
fsClose(fsys->fs);
fsys->fs = nil;
vtfreeconn(fsys->session);
fsys->session = nil;
if(sbox.curfsys != nil && strcmp(fsys->name, sbox.curfsys) == 0){
sbox.curfsys = nil;
consPrompt(nil);
}
return 1;
*/
}
static int
fsysVac(Fsys* fsys, int argc, char* argv[])
{
uchar score[VtScoreSize];
char *usage = "usage: [fsys name] vac path";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc != 1)
return cliError(usage);
if(!fsVac(fsys->fs, argv[0], score))
return 0;
consPrint("vac:%V\n", score);
return 1;
}
static int
fsysSnap(Fsys* fsys, int argc, char* argv[])
{
int doarchive;
char *usage = "usage: [fsys name] snap [-a] [-s /active] [-d /archive/yyyy/mmmm]";
char *src, *dst;
src = nil;
dst = nil;
doarchive = 0;
ARGBEGIN{
default:
return cliError(usage);
case 'a':
doarchive = 1;
break;
case 'd':
if((dst = ARGF()) == nil)
return cliError(usage);
break;
case 's':
if((src = ARGF()) == nil)
return cliError(usage);
break;
}ARGEND
if(argc)
return cliError(usage);
if(!fsSnapshot(fsys->fs, src, dst, doarchive))
return 0;
return 1;
}
static int
fsysSnapClean(Fsys *fsys, int argc, char* argv[])
{
u32int arch, snap, life;
char *usage = "usage: [fsys name] snapclean [maxminutes]\n";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc > 1)
return cliError(usage);
if(argc == 1)
life = atoi(argv[0]);
else
snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
fsSnapshotCleanup(fsys->fs, life);
return 1;
}
static int
fsysSnapTime(Fsys* fsys, int argc, char* argv[])
{
char buf[128], *x;
int hh, mm, changed;
u32int arch, snap, life;
char *usage = "usage: [fsys name] snaptime [-a hhmm] [-s snapminutes] [-t maxminutes]";
changed = 0;
snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
ARGBEGIN{
case 'a':
changed = 1;
x = ARGF();
if(x == nil)
return cliError(usage);
if(strcmp(x, "none") == 0){
arch = ~(u32int)0;
break;
}
if(strlen(x) != 4 || strspn(x, "0123456789") != 4)
return cliError(usage);
hh = (x[0]-'0')*10 + x[1]-'0';
mm = (x[2]-'0')*10 + x[3]-'0';
if(hh >= 24 || mm >= 60)
return cliError(usage);
arch = hh*60+mm;
break;
case 's':
changed = 1;
x = ARGF();
if(x == nil)
return cliError(usage);
if(strcmp(x, "none") == 0){
snap = ~(u32int)0;
break;
}
snap = atoi(x);
break;
case 't':
changed = 1;
x = ARGF();
if(x == nil)
return cliError(usage);
if(strcmp(x, "none") == 0){
life = ~(u32int)0;
break;
}
life = atoi(x);
break;
default:
return cliError(usage);
}ARGEND
if(argc > 0)
return cliError(usage);
if(changed){
snapSetTimes(fsys->fs->snap, arch, snap, life);
return 1;
}
snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
if(arch != ~(u32int)0)
sprint(buf, "-a %02d%02d", arch/60, arch%60);
else
sprint(buf, "-a none");
if(snap != ~(u32int)0)
sprint(buf+strlen(buf), " -s %d", snap);
else
sprint(buf+strlen(buf), " -s none");
if(life != ~(u32int)0)
sprint(buf+strlen(buf), " -t %ud", life);
else
sprint(buf+strlen(buf), " -t none");
consPrint("\tsnaptime %s\n", buf);
return 1;
}
static int
fsysSync(Fsys* fsys, int argc, char* argv[])
{
char *usage = "usage: [fsys name] sync";
int n;
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc > 0)
return cliError(usage);
n = cacheDirty(fsys->fs->cache);
fsSync(fsys->fs);
consPrint("\t%s sync: wrote %d blocks\n", fsys->name, n);
return 1;
}
static int
fsysHalt(Fsys *fsys, int argc, char* argv[])
{
char *usage = "usage: [fsys name] halt";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc > 0)
return cliError(usage);
fsHalt(fsys->fs);
return 1;
}
static int
fsysUnhalt(Fsys *fsys, int argc, char* argv[])
{
char *usage = "usage: [fsys name] unhalt";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc > 0)
return cliError(usage);
if(!fsys->fs->halted)
return cliError("file system %s not halted", fsys->name);
fsUnhalt(fsys->fs);
return 1;
}
static int
fsysRemove(Fsys* fsys, int argc, char* argv[])
{
File *file;
char *usage = "usage: [fsys name] remove path ...";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc == 0)
return cliError(usage);
rlock(&fsys->fs->elk);
while(argc > 0){
if((file = fileOpen(fsys->fs, argv[0])) == nil)
consPrint("%s: %r\n", argv[0]);
else{
if(!fileRemove(file, uidadm))
consPrint("%s: %r\n", argv[0]);
fileDecRef(file);
}
argc--;
argv++;
}
runlock(&fsys->fs->elk);
return 1;
}
static int
fsysClri(Fsys* fsys, int argc, char* argv[])
{
char *usage = "usage: [fsys name] clri path ...";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc == 0)
return cliError(usage);
rlock(&fsys->fs->elk);
while(argc > 0){
if(!fileClriPath(fsys->fs, argv[0], uidadm))
consPrint("clri %s: %r\n", argv[0]);
argc--;
argv++;
}
runlock(&fsys->fs->elk);
return 1;
}
/*
* Inspect and edit the labels for blocks on disk.
*/
static int
fsysLabel(Fsys* fsys, int argc, char* argv[])
{
Fs *fs;
Label l;
int n, r;
u32int addr;
Block *b, *bb;
char *usage = "usage: [fsys name] label addr [type state epoch epochClose tag]";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc != 1 && argc != 6)
return cliError(usage);
r = 0;
rlock(&fsys->fs->elk);
fs = fsys->fs;
addr = strtoul(argv[0], 0, 0);
b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
if(b == nil)
goto Out0;
l = b->l;
consPrint("%slabel %#ux %ud %ud %ud %ud %#x\n",
argc==6 ? "old: " : "", addr, l.type, l.state,
l.epoch, l.epochClose, l.tag);
if(argc == 6){
if(strcmp(argv[1], "-") != 0)
l.type = atoi(argv[1]);
if(strcmp(argv[2], "-") != 0)
l.state = atoi(argv[2]);
if(strcmp(argv[3], "-") != 0)
l.epoch = strtoul(argv[3], 0, 0);
if(strcmp(argv[4], "-") != 0)
l.epochClose = strtoul(argv[4], 0, 0);
if(strcmp(argv[5], "-") != 0)
l.tag = strtoul(argv[5], 0, 0);
consPrint("new: label %#ux %ud %ud %ud %ud %#x\n",
addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
bb = _blockSetLabel(b, &l);
if(bb == nil)
goto Out1;
n = 0;
for(;;){
if(blockWrite(bb, Waitlock)){
while(bb->iostate != BioClean){
assert(bb->iostate == BioWriting);
rsleep(&bb->ioready);
}
break;
}
consPrint("blockWrite: %r\n");
if(n++ >= 5){
consPrint("giving up\n");
break;
}
sleep(5*1000);
}
blockPut(bb);
}
r = 1;
Out1:
blockPut(b);
Out0:
runlock(&fs->elk);
return r;
}
/*
* Inspect and edit the blocks on disk.
*/
static int
fsysBlock(Fsys* fsys, int argc, char* argv[])
{
Fs *fs;
char *s;
Block *b;
uchar *buf;
u32int addr;
int c, count, i, offset;
char *usage = "usage: [fsys name] block addr offset [count [data]]";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc < 2 || argc > 4)
return cliError(usage);
fs = fsys->fs;
addr = strtoul(argv[0], 0, 0);
offset = strtoul(argv[1], 0, 0);
if(offset < 0 || offset >= fs->blockSize){
werrstr("bad offset");
return 0;
}
if(argc > 2)
count = strtoul(argv[2], 0, 0);
else
count = 100000000;
if(offset+count > fs->blockSize)
count = fs->blockSize - count;
rlock(&fs->elk);
b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
if(b == nil){
werrstr("cacheLocal %#ux: %r", addr);
runlock(&fs->elk);
return 0;
}
consPrint("\t%sblock %#ux %ud %ud %.*H\n",
argc==4 ? "old: " : "", addr, offset, count, count, b->data+offset);
if(argc == 4){
s = argv[3];
if(strlen(s) != 2*count){
werrstr("bad data count");
goto Out;
}
buf = vtmallocz(count);
for(i = 0; i < count*2; i++){
if(s[i] >= '0' && s[i] <= '9')
c = s[i] - '0';
else if(s[i] >= 'a' && s[i] <= 'f')
c = s[i] - 'a' + 10;
else if(s[i] >= 'A' && s[i] <= 'F')
c = s[i] - 'A' + 10;
else{
werrstr("bad hex");
vtfree(buf);
goto Out;
}
if((i & 1) == 0)
c <<= 4;
buf[i>>1] |= c;
}
memmove(b->data+offset, buf, count);
consPrint("\tnew: block %#ux %ud %ud %.*H\n",
addr, offset, count, count, b->data+offset);
blockDirty(b);
}
Out:
blockPut(b);
runlock(&fs->elk);
return 1;
}
/*
* Free a disk block.
*/
static int
fsysBfree(Fsys* fsys, int argc, char* argv[])
{
Fs *fs;
Label l;
char *p;
Block *b;
u32int addr;
char *usage = "usage: [fsys name] bfree addr ...";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc == 0)
return cliError(usage);
fs = fsys->fs;
rlock(&fs->elk);
while(argc > 0){
addr = strtoul(argv[0], &p, 0);
if(*p != '\0'){
consPrint("bad address - '%ud'\n", addr);
/* syntax error; let's stop */
runlock(&fs->elk);
return 0;
}
b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
if(b == nil){
consPrint("loading %#ux: %r\n", addr);
continue;
}
l = b->l;
if(l.state == BsFree)
consPrint("%#ux is already free\n", addr);
else{
consPrint("label %#ux %ud %ud %ud %ud %#x\n",
addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
l.state = BsFree;
l.type = BtMax;
l.tag = 0;
l.epoch = 0;
l.epochClose = 0;
if(!blockSetLabel(b, &l, 0))
consPrint("freeing %#ux: %r\n", addr);
}
blockPut(b);
argc--;
argv++;
}
runlock(&fs->elk);
return 1;
}
static int
fsysDf(Fsys *fsys, int argc, char* argv[])
{
char *usage = "usage: [fsys name] df";
u32int used, tot, bsize;
Fs *fs;
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc != 0)
return cliError(usage);
fs = fsys->fs;
cacheCountUsed(fs->cache, fs->elo, &used, &tot, &bsize);
consPrint("\t%s: %,llud used + %,llud free = %,llud (%.1f%% used)\n",
fsys->name, used*(vlong)bsize, (tot-used)*(vlong)bsize,
tot*(vlong)bsize, used*100.0/tot);
return 1;
}
/*
* Zero an entry or a pointer.
*/
static int
fsysClrep(Fsys* fsys, int argc, char* argv[], int ch)
{
Fs *fs;
Entry e;
Block *b;
u32int addr;
int i, max, offset, sz;
uchar zero[VtEntrySize];
char *usage = "usage: [fsys name] clr%c addr offset ...";
ARGBEGIN{
default:
return cliError(usage, ch);
}ARGEND
if(argc < 2)
return cliError(usage, ch);
fs = fsys->fs;
rlock(&fsys->fs->elk);
addr = strtoul(argv[0], 0, 0);
b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
if(b == nil){
werrstr("cacheLocal %#ux: %r", addr);
Err:
runlock(&fsys->fs->elk);
return 0;
}
switch(ch){
default:
werrstr("clrep");
goto Err;
case 'e':
if(b->l.type != BtDir){
werrstr("wrong block type");
goto Err;
}
sz = VtEntrySize;
memset(&e, 0, sizeof e);
entryPack(&e, zero, 0);
break;
case 'p':
if(b->l.type == BtDir || b->l.type == BtData){
werrstr("wrong block type");
goto Err;
}
sz = VtScoreSize;
memmove(zero, vtzeroscore, VtScoreSize);
break;
}
max = fs->blockSize/sz;
for(i = 1; i < argc; i++){
offset = atoi(argv[i]);
if(offset >= max){
consPrint("\toffset %d too large (>= %d)\n", i, max);
continue;
}
consPrint("\tblock %#ux %d %d %.*H\n", addr, offset*sz, sz, sz, b->data+offset*sz);
memmove(b->data+offset*sz, zero, sz);
}
blockDirty(b);
blockPut(b);
runlock(&fsys->fs->elk);
return 1;
}
static int
fsysClre(Fsys* fsys, int argc, char* argv[])
{
return fsysClrep(fsys, argc, argv, 'e');
}
static int
fsysClrp(Fsys* fsys, int argc, char* argv[])
{
return fsysClrep(fsys, argc, argv, 'p');
}
static int
fsysEsearch1(File* f, char* s, u32int elo)
{
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){
consPrint("\tdeeRead %s/%s: %r\n", s, de.elem);
break;
}
if(r == 0)
break;
if(de.mode & ModeSnapshot){
if((ff = fileWalk(f, de.elem)) == nil)
consPrint("\tcannot walk %s/%s: %r\n", s, de.elem);
else{
if(!fileGetSources(ff, &e, &ee))
consPrint("\tcannot get sources for %s/%s: %r\n", s, de.elem);
else if(e.snap != 0 && e.snap < elo){
consPrint("\t%ud\tclri %s/%s\n", e.snap, s, de.elem);
n++;
}
fileDecRef(ff);
}
}
else if(de.mode & ModeDir){
if((ff = fileWalk(f, de.elem)) == nil)
consPrint("\tcannot walk %s/%s: %r\n", s, de.elem);
else{
t = smprint("%s/%s", s, de.elem);
n += fsysEsearch1(ff, t, elo);
vtfree(t);
fileDecRef(ff);
}
}
deCleanup(&de);
if(r < 0)
break;
}
deeClose(dee);
return n;
}
static int
fsysEsearch(Fs* fs, char* path, u32int elo)
{
int n;
File *f;
DirEntry de;
f = fileOpen(fs, path);
if(f == nil)
return 0;
if(!fileGetDir(f, &de)){
consPrint("\tfileGetDir %s failed: %r\n", path);
fileDecRef(f);
return 0;
}
if((de.mode & ModeDir) == 0){
fileDecRef(f);
deCleanup(&de);
return 0;
}
deCleanup(&de);
n = fsysEsearch1(f, path, elo);
fileDecRef(f);
return n;
}
static int
fsysEpoch(Fsys* fsys, int argc, char* argv[])
{
Fs *fs;
int force, n, remove;
u32int low, old;
char *usage = "usage: [fsys name] epoch [[-ry] low]";
force = 0;
remove = 0;
ARGBEGIN{
case 'y':
force = 1;
break;
case 'r':
remove = 1;
break;
default:
return cliError(usage);
}ARGEND
if(argc > 1)
return cliError(usage);
if(argc > 0)
low = strtoul(argv[0], 0, 0);
else
low = ~(u32int)0;
if(low == 0)
return cliError("low epoch cannot be zero");
fs = fsys->fs;
rlock(&fs->elk);
consPrint("\tlow %ud hi %ud\n", fs->elo, fs->ehi);
if(low == ~(u32int)0){
runlock(&fs->elk);
return 1;
}
n = fsysEsearch(fsys->fs, "/archive", low);
n += fsysEsearch(fsys->fs, "/snapshot", low);
consPrint("\t%d snapshot%s found with epoch < %ud\n", n, n==1 ? "" : "s", low);
runlock(&fs->elk);
/*
* There's a small race here -- a new snapshot with epoch < low might
* get introduced now that we unlocked fs->elk. Low has to
* be <= fs->ehi. Of course, in order for this to happen low has
* to be equal to the current fs->ehi _and_ a snapshot has to
* run right now. This is a small enough window that I don't care.
*/
if(n != 0 && !force){
consPrint("\tnot setting low epoch\n");
return 1;
}
old = fs->elo;
if(!fsEpochLow(fs, low))
consPrint("\tfsEpochLow: %r\n");
else{
consPrint("\told: epoch%s %ud\n", force ? " -y" : "", old);
consPrint("\tnew: epoch%s %ud\n", force ? " -y" : "", fs->elo);
if(fs->elo < low)
consPrint("\twarning: new low epoch < old low epoch\n");
if(force && remove)
fsSnapshotRemove(fs);
}
return 1;
}
static int
fsysCreate(Fsys* fsys, int argc, char* argv[])
{
int r;
ulong mode;
char *elem, *p, *path;
char *usage = "usage: [fsys name] create path uid gid perm";
DirEntry de;
File *file, *parent;
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc != 4)
return cliError(usage);
if(!fsysParseMode(argv[3], &mode))
return cliError(usage);
if(mode&ModeSnapshot)
return cliError("create - cannot create with snapshot bit set");
if(strcmp(argv[1], uidnoworld) == 0)
return cliError("permission denied");
rlock(&fsys->fs->elk);
path = vtstrdup(argv[0]);
if((p = strrchr(path, '/')) != nil){
*p++ = '\0';
elem = p;
p = path;
if(*p == '\0')
p = "/";
}
else{
p = "/";
elem = path;
}
r = 0;
if((parent = fileOpen(fsys->fs, p)) == nil)
goto out;
file = fileCreate(parent, elem, mode, argv[1]);
fileDecRef(parent);
if(file == nil){
werrstr("create %s/%s: %r", p, elem);
goto out;
}
if(!fileGetDir(file, &de)){
werrstr("stat failed after create: %r");
goto out1;
}
if(strcmp(de.gid, argv[2]) != 0){
vtfree(de.gid);
de.gid = vtstrdup(argv[2]);
if(!fileSetDir(file, &de, argv[1])){
werrstr("wstat failed after create: %r");
goto out2;
}
}
r = 1;
out2:
deCleanup(&de);
out1:
fileDecRef(file);
out:
vtfree(path);
runlock(&fsys->fs->elk);
return r;
}
static void
fsysPrintStat(char *prefix, char *file, DirEntry *de)
{
char buf[64];
if(prefix == nil)
prefix = "";
consPrint("%sstat %q %q %q %q %s %llud\n", prefix,
file, de->elem, de->uid, de->gid, fsysModeString(de->mode, buf), de->size);
}
static int
fsysStat(Fsys* fsys, int argc, char* argv[])
{
int i;
File *f;
DirEntry de;
char *usage = "usage: [fsys name] stat files...";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc == 0)
return cliError(usage);
rlock(&fsys->fs->elk);
for(i=0; i<argc; i++){
if((f = fileOpen(fsys->fs, argv[i])) == nil){
consPrint("%s: %r\n", argv[i]);
continue;
}
if(!fileGetDir(f, &de)){
consPrint("%s: %r\n", argv[i]);
fileDecRef(f);
continue;
}
fsysPrintStat("\t", argv[i], &de);
deCleanup(&de);
fileDecRef(f);
}
runlock(&fsys->fs->elk);
return 1;
}
static int
fsysWstat(Fsys *fsys, int argc, char* argv[])
{
File *f;
char *p;
DirEntry de;
char *usage = "usage: [fsys name] wstat file elem uid gid mode length\n"
"\tuse - for any field to mean don't change";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc != 6)
return cliError(usage);
rlock(&fsys->fs->elk);
if((f = fileOpen(fsys->fs, argv[0])) == nil){
werrstr("console wstat - walk - %r");
runlock(&fsys->fs->elk);
return 0;
}
if(!fileGetDir(f, &de)){
werrstr("console wstat - stat - %r");
fileDecRef(f);
runlock(&fsys->fs->elk);
return 0;
}
fsysPrintStat("\told: w", argv[0], &de);
if(strcmp(argv[1], "-") != 0){
if(!validFileName(argv[1])){
werrstr("console wstat - bad elem");
goto error;
}
vtfree(de.elem);
de.elem = vtstrdup(argv[1]);
}
if(strcmp(argv[2], "-") != 0){
if(!validUserName(argv[2])){
werrstr("console wstat - bad uid");
goto error;
}
vtfree(de.uid);
de.uid = vtstrdup(argv[2]);
}
if(strcmp(argv[3], "-") != 0){
if(!validUserName(argv[3])){
werrstr("console wstat - bad gid");
goto error;
}
vtfree(de.gid);
de.gid = vtstrdup(argv[3]);
}
if(strcmp(argv[4], "-") != 0){
if(!fsysParseMode(argv[4], &de.mode)){
werrstr("console wstat - bad mode");
goto error;
}
}
if(strcmp(argv[5], "-") != 0){
de.size = strtoull(argv[5], &p, 0);
if(argv[5][0] == '\0' || *p != '\0' || (vlong)de.size < 0){
werrstr("console wstat - bad length");
goto error;
}
}
if(!fileSetDir(f, &de, uidadm)){
werrstr("console wstat - %r");
goto error;
}
deCleanup(&de);
if(!fileGetDir(f, &de)){
werrstr("console wstat - stat2 - %r");
goto error;
}
fsysPrintStat("\tnew: w", argv[0], &de);
deCleanup(&de);
fileDecRef(f);
runlock(&fsys->fs->elk);
return 1;
error:
deCleanup(&de); /* okay to do this twice */
fileDecRef(f);
runlock(&fsys->fs->elk);
return 0;
}
static void
fsckClri(Fsck *fsck, char *name, MetaBlock *mb, int i, Block *b)
{
USED(name);
if((fsck->flags&DoClri) == 0)
return;
mbDelete(mb, i);
mbPack(mb);
blockDirty(b);
}
static void
fsckClose(Fsck *fsck, Block *b, u32int epoch)
{
Label l;
if((fsck->flags&DoClose) == 0)
return;
l = b->l;
if(l.state == BsFree || (l.state&BsClosed)){
consPrint("%#ux is already closed\n", b->addr);
return;
}
if(epoch){
l.state |= BsClosed;
l.epochClose = epoch;
}else
l.state = BsFree;
if(!blockSetLabel(b, &l, 0))
consPrint("%#ux setlabel: %r\n", b->addr);
}
static void
fsckClre(Fsck *fsck, Block *b, int offset)
{
Entry e;
if((fsck->flags&DoClre) == 0)
return;
if(offset<0 || offset*VtEntrySize >= fsck->bsize){
consPrint("bad clre\n");
return;
}
memset(&e, 0, sizeof e);
entryPack(&e, b->data, offset);
blockDirty(b);
}
static void
fsckClrp(Fsck *fsck, Block *b, int offset)
{
if((fsck->flags&DoClrp) == 0)
return;
if(offset<0 || offset*VtScoreSize >= fsck->bsize){
consPrint("bad clre\n");
return;
}
memmove(b->data+offset*VtScoreSize, vtzeroscore, VtScoreSize);
blockDirty(b);
}
static int
fsysCheck(Fsys *fsys, int argc, char *argv[])
{
int i, halting;
char *usage = "usage: [fsys name] check [-v] [options]";
Fsck fsck;
Block *b;
Super super;
memset(&fsck, 0, sizeof fsck);
fsck.fs = fsys->fs;
fsck.clri = fsckClri;
fsck.clre = fsckClre;
fsck.clrp = fsckClrp;
fsck.close = fsckClose;
fsck.print = consPrint;
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
for(i=0; i<argc; i++){
if(strcmp(argv[i], "pblock") == 0)
fsck.printblocks = 1;
else if(strcmp(argv[i], "pdir") == 0)
fsck.printdirs = 1;
else if(strcmp(argv[i], "pfile") == 0)
fsck.printfiles = 1;
else if(strcmp(argv[i], "bclose") == 0)
fsck.flags |= DoClose;
else if(strcmp(argv[i], "clri") == 0)
fsck.flags |= DoClri;
else if(strcmp(argv[i], "clre") == 0)
fsck.flags |= DoClre;
else if(strcmp(argv[i], "clrp") == 0)
fsck.flags |= DoClrp;
else if(strcmp(argv[i], "fix") == 0)
fsck.flags |= DoClose|DoClri|DoClre|DoClrp;
else if(strcmp(argv[i], "venti") == 0)
fsck.useventi = 1;
else if(strcmp(argv[i], "snapshot") == 0)
fsck.walksnapshots = 1;
else{
consPrint("unknown option '%s'\n", argv[i]);
return cliError(usage);
}
}
halting = fsys->fs->halted==0;
if(halting)
fsHalt(fsys->fs);
if(fsys->fs->arch){
b = superGet(fsys->fs->cache, &super);
if(b == nil){
consPrint("could not load super block\n");
goto Out;
}
blockPut(b);
if(super.current != NilBlock){
consPrint("cannot check fs while archiver is running; "
"wait for it to finish\n");
goto Out;
}
}
fsCheck(&fsck);
consPrint("fsck: %d clri, %d clre, %d clrp, %d bclose\n",
fsck.nclri, fsck.nclre, fsck.nclrp, fsck.nclose);
Out:
if(halting)
fsUnhalt(fsys->fs);
return 1;
}
static int
fsysVenti(char* name, int argc, char* argv[])
{
int r;
char *host;
char *usage = "usage: [fsys name] venti [address]";
Fsys *fsys;
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc == 0)
host = nil;
else if(argc == 1)
host = argv[0];
else
return cliError(usage);
if((fsys = _fsysGet(name)) == nil)
return 0;
qlock(&fsys->lock);
if(host == nil)
host = fsys->venti;
else{
vtfree(fsys->venti);
if(host[0])
fsys->venti = vtstrdup(host);
else{
host = nil;
fsys->venti = nil;
}
}
/* already open: do a redial */
if(fsys->fs != nil){
if(fsys->session == nil){
werrstr("file system was opened with -V");
r = 0;
goto out;
}
r = 1;
if(myRedial(fsys->session, host) < 0
|| vtconnect(fsys->session) < 0)
r = 0;
goto out;
}
/* not yet open: try to dial */
if(fsys->session)
vtfreeconn(fsys->session);
r = 1;
if((fsys->session = myDial(host)) == nil
|| vtconnect(fsys->session) < 0)
r = 0;
out:
qunlock(&fsys->lock);
fsysPut(fsys);
return r;
}
static ulong
freemem(void)
{
int nf, pgsize = 0;
uvlong size, userpgs = 0, userused = 0;
char *ln, *sl;
char *fields[2];
Biobuf *bp;
size = 64*1024*1024;
bp = Bopen("#c/swap", OREAD);
if (bp != nil) {
while ((ln = Brdline(bp, '\n')) != nil) {
ln[Blinelen(bp)-1] = '\0';
nf = tokenize(ln, fields, nelem(fields));
if (nf != 2)
continue;
if (strcmp(fields[1], "pagesize") == 0)
pgsize = atoi(fields[0]);
else if (strcmp(fields[1], "user") == 0) {
sl = strchr(fields[0], '/');
if (sl == nil)
continue;
userpgs = atoll(sl+1);
userused = atoll(fields[0]);
}
}
Bterm(bp);
if (pgsize > 0 && userpgs > 0)
size = (userpgs - userused) * pgsize;
}
/* cap it to keep the size within 32 bits */
if (size >= 3840UL * 1024 * 1024)
size = 3840UL * 1024 * 1024;
return size;
}
static int
fsysOpen(char* name, int argc, char* argv[])
{
char *p, *host;
Fsys *fsys;
int noauth, noventi, noperm, rflag, wstatallow, noatimeupd;
long ncache;
char *usage = "usage: fsys name open [-APVWr] [-c ncache]";
ncache = 1000;
noauth = noperm = wstatallow = noventi = noatimeupd = 0;
rflag = OReadWrite;
ARGBEGIN{
default:
return cliError(usage);
case 'A':
noauth = 1;
break;
case 'P':
noperm = 1;
break;
case 'V':
noventi = 1;
break;
case 'W':
wstatallow = 1;
break;
case 'a':
noatimeupd = 1;
break;
case 'c':
p = ARGF();
if(p == nil)
return cliError(usage);
ncache = strtol(argv[0], &p, 0);
if(ncache <= 0 || p == argv[0] || *p != '\0')
return cliError(usage);
break;
case 'r':
rflag = OReadOnly;
break;
}ARGEND
if(argc)
return cliError(usage);
if((fsys = _fsysGet(name)) == nil)
return 0;
/* automatic memory sizing? */
if(mempcnt > 0) {
/* TODO: 8K is a hack; use the actual block size */
ncache = (((vlong)freemem() * mempcnt) / 100) / (8*1024);
if (ncache < 100)
ncache = 100;
}
qlock(&fsys->lock);
if(fsys->fs != nil){
werrstr(EFsysBusy, fsys->name);
qunlock(&fsys->lock);
fsysPut(fsys);
return 0;
}
if(noventi){
if(fsys->session){
vtfreeconn(fsys->session);
fsys->session = nil;
}
}
else if(fsys->session == nil){
if(fsys->venti && fsys->venti[0])
host = fsys->venti;
else
host = nil;
if((fsys->session = myDial(host)) == nil
|| vtconnect(fsys->session) < 0 && !noventi)
fprint(2, "warning: connecting to venti: %r\n");
}
if((fsys->fs = fsOpen(fsys->dev, fsys->session, ncache, rflag)) == nil){
werrstr("fsOpen: %r");
qunlock(&fsys->lock);
fsysPut(fsys);
return 0;
}
fsys->fs->name = fsys->name; /* for better error messages */
fsys->noauth = noauth;
fsys->noperm = noperm;
fsys->wstatallow = wstatallow;
fsys->fs->noatimeupd = noatimeupd;
qunlock(&fsys->lock);
fsysPut(fsys);
if(strcmp(name, "main") == 0)
usersFileRead(nil);
return 1;
}
static int
fsysUnconfig(char* name, int argc, char* argv[])
{
Fsys *fsys, **fp;
char *usage = "usage: fsys name unconfig";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc)
return cliError(usage);
wlock(&sbox.lock);
fp = &sbox.head;
for(fsys = *fp; fsys != nil; fsys = fsys->next){
if(strcmp(fsys->name, name) == 0)
break;
fp = &fsys->next;
}
if(fsys == nil){
werrstr(EFsysNotFound, name);
wunlock(&sbox.lock);
return 0;
}
if(fsys->ref != 0 || fsys->fs != nil){
werrstr(EFsysBusy, fsys->name);
wunlock(&sbox.lock);
return 0;
}
*fp = fsys->next;
wunlock(&sbox.lock);
if(fsys->session != nil)
vtfreeconn(fsys->session);
if(fsys->venti != nil)
vtfree(fsys->venti);
if(fsys->dev != nil)
vtfree(fsys->dev);
if(fsys->name != nil)
vtfree(fsys->name);
vtfree(fsys);
return 1;
}
static int
fsysConfig(char* name, int argc, char* argv[])
{
Fsys *fsys;
char *part;
char *usage = "usage: fsys name config [dev]";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc > 1)
return cliError(usage);
if(argc == 0)
part = foptname;
else
part = argv[0];
if((fsys = _fsysGet(part)) != nil){
qlock(&fsys->lock);
if(fsys->fs != nil){
werrstr(EFsysBusy, fsys->name);
qunlock(&fsys->lock);
fsysPut(fsys);
return 0;
}
vtfree(fsys->dev);
fsys->dev = vtstrdup(part);
qunlock(&fsys->lock);
}
else if((fsys = fsysAlloc(name, part)) == nil)
return 0;
fsysPut(fsys);
return 1;
}
static struct {
char* cmd;
int (*f)(Fsys*, int, char**);
int (*f1)(char*, int, char**);
} fsyscmd[] = {
{ "close", fsysClose, },
{ "config", nil, fsysConfig, },
{ "open", nil, fsysOpen, },
{ "unconfig", nil, fsysUnconfig, },
{ "venti", nil, fsysVenti, },
{ "bfree", fsysBfree, },
{ "block", fsysBlock, },
{ "check", fsysCheck, },
{ "clre", fsysClre, },
{ "clri", fsysClri, },
{ "clrp", fsysClrp, },
{ "create", fsysCreate, },
{ "df", fsysDf, },
{ "epoch", fsysEpoch, },
{ "halt", fsysHalt, },
{ "label", fsysLabel, },
{ "remove", fsysRemove, },
{ "snap", fsysSnap, },
{ "snaptime", fsysSnapTime, },
{ "snapclean", fsysSnapClean, },
{ "stat", fsysStat, },
{ "sync", fsysSync, },
{ "unhalt", fsysUnhalt, },
{ "wstat", fsysWstat, },
{ "vac", fsysVac, },
{ nil, nil, },
};
static int
fsysXXX1(Fsys *fsys, int i, int argc, char* argv[])
{
int r;
qlock(&fsys->lock);
if(fsys->fs == nil){
qunlock(&fsys->lock);
werrstr(EFsysNotOpen, fsys->name);
return 0;
}
if(fsys->fs->halted
&& fsyscmd[i].f != fsysUnhalt && fsyscmd[i].f != fsysCheck){
werrstr("file system %s is halted", fsys->name);
qunlock(&fsys->lock);
return 0;
}
r = (*fsyscmd[i].f)(fsys, argc, argv);
qunlock(&fsys->lock);
return r;
}
static int
fsysXXX(char* name, int argc, char* argv[])
{
int i, r;
Fsys *fsys;
for(i = 0; fsyscmd[i].cmd != nil; i++){
if(strcmp(fsyscmd[i].cmd, argv[0]) == 0)
break;
}
if(fsyscmd[i].cmd == nil){
werrstr("unknown command - '%s'", argv[0]);
return 0;
}
/* some commands want the name... */
if(fsyscmd[i].f1 != nil){
if(strcmp(name, FsysAll) == 0){
werrstr("cannot use fsys %#q with %#q command", FsysAll, argv[0]);
return 0;
}
return (*fsyscmd[i].f1)(name, argc, argv);
}
/* ... but most commands want the Fsys */
if(strcmp(name, FsysAll) == 0){
r = 1;
rlock(&sbox.lock);
for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
fsys->ref++;
r = fsysXXX1(fsys, i, argc, argv) && r;
fsys->ref--;
}
runlock(&sbox.lock);
}else{
if((fsys = _fsysGet(name)) == nil)
return 0;
r = fsysXXX1(fsys, i, argc, argv);
fsysPut(fsys);
}
return r;
}
static int
cmdFsysXXX(int argc, char* argv[])
{
char *name;
if((name = sbox.curfsys) == nil){
werrstr(EFsysNoCurrent, argv[0]);
return 0;
}
return fsysXXX(name, argc, argv);
}
static int
cmdFsys(int argc, char* argv[])
{
Fsys *fsys;
char *usage = "usage: fsys [name ...]";
ARGBEGIN{
default:
return cliError(usage);
}ARGEND
if(argc == 0){
rlock(&sbox.lock);
currfsysname = sbox.head->name;
for(fsys = sbox.head; fsys != nil; fsys = fsys->next)
consPrint("\t%s\n", fsys->name);
runlock(&sbox.lock);
return 1;
}
if(argc == 1){
fsys = nil;
if(strcmp(argv[0], FsysAll) != 0 && (fsys = fsysGet(argv[0])) == nil)
return 0;
sbox.curfsys = vtstrdup(argv[0]);
consPrompt(sbox.curfsys);
if(fsys)
fsysPut(fsys);
return 1;
}
return fsysXXX(argv[0], argc-1, argv+1);
}
int
fsysInit(void)
{
int i;
fmtinstall('H', encodefmt);
fmtinstall('V', scoreFmt);
fmtinstall('L', labelFmt);
cliAddCmd("fsys", cmdFsys);
for(i = 0; fsyscmd[i].cmd != nil; i++){
if(fsyscmd[i].f != nil)
cliAddCmd(fsyscmd[i].cmd, cmdFsysXXX);
}
/* the venti cmd is special: the fs can be either open or closed */
cliAddCmd("venti", cmdFsysXXX);
cliAddCmd("printconfig", cmdPrintConfig);
return 1;
}