blob: d4319c8651704ed88eb8cb37ed18bde10191bec5 [file] [log] [blame]
/*
* TO DO:
* - gc of file systems (not going to do just yet?)
* - statistics file
* - configure on amsterdam
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <thread.h>
#include <libsec.h>
#include <sunrpc.h>
#include <nfs3.h>
#include <diskfs.h>
#include <venti.h>
#include "nfs3srv.h"
#define trace if(!tracecalls){}else print
typedef struct Ipokay Ipokay;
typedef struct Config Config;
typedef struct Ctree Ctree;
typedef struct Cnode Cnode;
struct Ipokay
{
int okay;
uchar ip[IPaddrlen];
uchar mask[IPaddrlen];
};
struct Config
{
Ipokay *ok;
uint nok;
ulong mtime;
Ctree *ctree;
};
char *addr;
int blocksize;
int cachesize;
Config config;
char *configfile;
int encryptedhandles = 1;
Channel *nfschan;
Channel *mountchan;
Channel *timerchan;
Nfs3Handle root;
SunSrv *srv;
int tracecalls;
VtCache *vcache;
VtConn *z;
void cryptinit(void);
void timerthread(void*);
void timerproc(void*);
extern void handleunparse(Fsys*, Nfs3Handle*, Nfs3Handle*, int);
extern Nfs3Status handleparse(Nfs3Handle*, Fsys**, Nfs3Handle*, int);
Nfs3Status logread(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);
Nfs3Status refreshdiskread(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);
Nfs3Status refreshconfigread(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);
int readconfigfile(Config *cp);
void setrootfid(void);
int ipokay(uchar *ip, ushort port);
u64int unittoull(char*);
void
usage(void)
{
fprint(2, "usage: vnfs [-LLRVir] [-a addr] [-b blocksize] [-c cachesize] configfile\n");
threadexitsall("usage");
}
void
threadmain(int argc, char **argv)
{
fmtinstall('B', sunrpcfmt);
fmtinstall('C', suncallfmt);
fmtinstall('F', vtfcallfmt);
fmtinstall('H', encodefmt);
fmtinstall('I', eipfmt);
fmtinstall('V', vtscorefmt);
sunfmtinstall(&nfs3prog);
sunfmtinstall(&nfsmount3prog);
addr = "udp!*!2049";
blocksize = 8192;
cachesize = 400;
srv = sunsrv();
srv->ipokay = ipokay;
cryptinit();
ARGBEGIN{
default:
usage();
case 'E':
encryptedhandles = 0;
break;
case 'L':
if(srv->localonly == 0)
srv->localonly = 1;
else
srv->localparanoia = 1;
break;
case 'R':
srv->chatty++;
break;
case 'T':
tracecalls = 1;
break;
case 'V':
if(chattyventi++)
vttracelevel++;
break;
case 'a':
addr = EARGF(usage());
break;
case 'b':
blocksize = unittoull(EARGF(usage()));
break;
case 'c':
cachesize = unittoull(EARGF(usage()));
break;
case 'i':
insecure = 1;
break;
case 'r':
srv->alwaysreject++;
break;
}ARGEND
if(argc != 1)
usage();
if((z = vtdial(nil)) == nil)
sysfatal("vtdial: %r");
if(vtconnect(z) < 0)
sysfatal("vtconnect: %r");
if((vcache = vtcachealloc(z, blocksize*cachesize)) == nil)
sysfatal("vtcache: %r");
configfile = argv[0];
if(readconfigfile(&config) < 0)
sysfatal("readConfig: %r");
setrootfid();
nfschan = chancreate(sizeof(SunMsg*), 0);
mountchan = chancreate(sizeof(SunMsg*), 0);
timerchan = chancreate(sizeof(void*), 0);
if(sunsrvudp(srv, addr) < 0)
sysfatal("starting server: %r");
sunsrvthreadcreate(srv, nfs3proc, nfschan);
sunsrvthreadcreate(srv, mount3proc, mountchan);
sunsrvthreadcreate(srv, timerthread, nil);
proccreate(timerproc, nil, 32768);
sunsrvprog(srv, &nfs3prog, nfschan);
sunsrvprog(srv, &nfsmount3prog, mountchan);
threadexits(nil);
}
#define TWID64 ((u64int)~(u64int)0)
u64int
unittoull(char *s)
{
char *es;
u64int n;
if(s == nil)
return TWID64;
n = strtoul(s, &es, 0);
if(*es == 'k' || *es == 'K'){
n *= 1024;
es++;
}else if(*es == 'm' || *es == 'M'){
n *= 1024*1024;
es++;
}else if(*es == 'g' || *es == 'G'){
n *= 1024*1024*1024;
es++;
}else if(*es == 't' || *es == 'T'){
n *= 1024*1024;
n *= 1024*1024;
}
if(*es != '\0')
return TWID64;
return n;
}
/*
* Handles.
*
* We store all the state about which file a client is accessing in
* the handle, so that we don't have to maintain any per-client state
* ourselves. In order to avoid leaking handles or letting clients
* create arbitrary handles, we sign and encrypt each handle with
* AES using a key selected randomly when the server starts.
* Thus, handles cannot be used across sessions.
*
* The decrypted handles begin with the following header:
*
* sessid[8] random session id chosen at start time
* len[4] length of handle that follows
*
* If we're pressed for space in the rest of the handle, we could
* probably reduce the amount of sessid bytes. Note that the sessid
* bytes must be consistent during a run of vnfs, or else some
* clients (e.g., Linux 2.4) eventually notice that successive TLookups
* return different handles, and they return "Stale NFS file handle"
* errors to system calls in response (even though we never sent
* that error!).
*
* Security woes aside, the fact that we have to shove everything
* into the handles is quite annoying. We have to encode, in 40 bytes:
*
* - position in the synthesized config tree
* - enough of the path to do glob matching
* - position in an archived file system image
*
* and the handles need to be stable across changes in the config file
* (though not across server restarts since encryption screws
* that up nicely).
*
* We encode each of the first two as a 10-byte hash that is
* the first half of a SHA1 hash.
*/
enum
{
SessidSize = 8,
HeaderSize = SessidSize+4,
MaxHandleSize = Nfs3MaxHandleSize - HeaderSize
};
AESstate aesstate;
uchar sessid[SessidSize];
static void
hencrypt(Nfs3Handle *h)
{
uchar *p;
AESstate aes;
/*
* root handle has special encryption - a single 0 byte - so that it
* never goes stale.
*/
if(h->len == root.len && memcmp(h->h, root.h, root.len) == 0){
h->h[0] = 0;
h->len = 1;
return;
}
if(!encryptedhandles)
return;
if(h->len > MaxHandleSize){
/* oops */
fprint(2, "handle too long: %.*lH\n", h->len, h->h);
memset(h->h, 'X', Nfs3MaxHandleSize);
h->len = Nfs3MaxHandleSize;
return;
}
p = h->h;
memmove(p+HeaderSize, p, h->len);
memmove(p, sessid, SessidSize);
*(u32int*)(p+SessidSize) = h->len;
h->len += HeaderSize;
if(encryptedhandles){
while(h->len < MaxHandleSize)
h->h[h->len++] = 0;
aes = aesstate;
aesCBCencrypt(h->h, MaxHandleSize, &aes);
}
}
static Nfs3Status
hdecrypt(Nfs3Handle *h)
{
AESstate aes;
if(h->len == 1 && h->h[0] == 0){ /* single 0 byte is root */
*h = root;
return Nfs3Ok;
}
if(!encryptedhandles)
return Nfs3Ok;
if(h->len <= HeaderSize)
return Nfs3ErrBadHandle;
if(encryptedhandles){
if(h->len != MaxHandleSize)
return Nfs3ErrBadHandle;
aes = aesstate;
aesCBCdecrypt(h->h, h->len, &aes);
}
if(memcmp(h->h, sessid, SessidSize) != 0)
return Nfs3ErrStale; /* give benefit of doubt */
h->len = *(u32int*)(h->h+SessidSize);
if(h->len >= MaxHandleSize-HeaderSize)
return Nfs3ErrBadHandle;
memmove(h->h, h->h+HeaderSize, h->len);
return Nfs3Ok;
}
void
cryptinit(void)
{
uchar key[32], ivec[AESbsize];
int i;
u32int u32;
u32 = truerand();
memmove(sessid, &u32, 4);
for(i=0; i<nelem(key); i+=4) {
u32 = truerand();
memmove(key+i, &u32, 4);
}
for(i=0; i<nelem(ivec); i++)
ivec[i] = fastrand();
setupAESstate(&aesstate, key, sizeof key, ivec);
}
/*
* Config file.
*
* The main purpose of the configuration file is to define a tree
* in which the archived file system images are mounted.
* The tree is stored as Entry structures, defined below.
*
* The configuration file also allows one to define shell-like
* glob expressions matching paths that are not to be displayed.
* The matched files or directories are shown in directory listings
* (could suppress these if we cared) but they cannot be opened,
* read, or written, and getattr returns zeroed data.
*/
enum
{
/* sizes used in handles; see nfs server below */
CnodeHandleSize = 8,
FsysHandleOffset = CnodeHandleSize
};
/*
* Config file tree.
*/
struct Ctree
{
Cnode *root;
Cnode *hash[1024];
};
struct Cnode
{
char *name; /* path element */
Cnode *parent; /* in tree */
Cnode *nextsib; /* in tree */
Cnode *kidlist; /* in tree */
Cnode *nexthash; /* in hash list */
Nfs3Status (*read)(Cnode*, u32int, u64int, uchar**, u32int*, u1int*); /* synthesized read fn */
uchar handle[VtScoreSize]; /* sha1(path to here) */
ulong mtime; /* mtime for this directory entry */
/* fsys overlay on this node */
Fsys *fsys; /* cache of memory structure */
Nfs3Handle fsyshandle;
int isblackhole; /* walking down keeps you here */
/*
* mount point info.
* if a mount point is inside another file system,
* the fsys and fsyshandle above have the old fs info,
* the mfsys and mfsyshandle below have the new one.
* getattrs must use the old info for consistency.
*/
int ismtpt; /* whether there is an fsys mounted here */
uchar fsysscore[VtScoreSize]; /* score of fsys image on venti */
char *fsysimage; /* raw disk image */
Fsys *mfsys; /* mounted file system (nil until walked) */
Nfs3Handle mfsyshandle; /* handle to root of mounted fsys */
int mark; /* gc */
};
static uint
dumbhash(uchar *s)
{
return (s[0]<<2)|(s[1]>>6); /* first 10 bits */
}
static Cnode*
mkcnode(Ctree *t, Cnode *parent, char *elem, uint elen, char *path, uint plen)
{
uint h;
Cnode *n;
n = emalloc(sizeof *n + elen+1);
n->name = (char*)(n+1);
memmove(n->name, elem, elen);
n->name[elen] = 0;
n->parent = parent;
if(parent){
n->nextsib = parent->kidlist;
parent->kidlist = n;
}
n->kidlist = nil;
sha1((uchar*)path, plen, n->handle, nil);
h = dumbhash(n->handle);
n->nexthash = t->hash[h];
t->hash[h] = n;
return n;
}
void
markctree(Ctree *t)
{
int i;
Cnode *n;
for(i=0; i<nelem(t->hash); i++)
for(n=t->hash[i]; n; n=n->nexthash)
if(n->name[0] != '+')
n->mark = 1;
}
int
refreshdisk(void)
{
int i;
Cnode *n;
Ctree *t;
t = config.ctree;
for(i=0; i<nelem(t->hash); i++)
for(n=t->hash[i]; n; n=n->nexthash){
if(n->mfsys)
disksync(n->mfsys->disk);
if(n->fsys)
disksync(n->fsys->disk);
}
return 0;
}
void
sweepctree(Ctree *t)
{
int i;
Cnode *n;
/* just zero all the garbage and leave it linked into the tree */
for(i=0; i<nelem(t->hash); i++){
for(n=t->hash[i]; n; n=n->nexthash){
if(!n->mark)
continue;
n->fsys = nil;
free(n->fsysimage);
n->fsysimage = nil;
memset(n->fsysscore, 0, sizeof n->fsysscore);
n->mfsys = nil;
n->ismtpt = 0;
memset(&n->fsyshandle, 0, sizeof n->fsyshandle);
memset(&n->mfsyshandle, 0, sizeof n->mfsyshandle);
}
}
}
static Cnode*
cnodewalk(Cnode *n, char *name, uint len, int markokay)
{
Cnode *nn;
for(nn=n->kidlist; nn; nn=nn->nextsib)
if(strncmp(nn->name, name, len) == 0 && nn->name[len] == 0)
if(!nn->mark || markokay)
return nn;
return nil;
}
Cnode*
ctreewalkpath(Ctree *t, char *name, ulong createmtime)
{
Cnode *n, *nn;
char *p, *nextp;
n = t->root;
p = name;
for(; *p; p=nextp){
n->mark = 0;
assert(*p == '/');
p++;
nextp = strchr(p, '/');
if(nextp == nil)
nextp = p+strlen(p);
if((nn = cnodewalk(n, p, nextp-p, 1)) == nil){
if(createmtime == 0)
return nil;
nn = mkcnode(t, n, p, nextp-p, name, nextp-name);
nn->mtime = createmtime;
}
if(nn->mark)
nn->mark = 0;
n = nn;
}
n->mark = 0;
return n;
}
Ctree*
mkctree(void)
{
Ctree *t;
t = emalloc(sizeof *t);
t->root = mkcnode(t, nil, "", 0, "", 0);
ctreewalkpath(t, "/+log", time(0))->read = logread;
ctreewalkpath(t, "/+refreshdisk", time(0))->read = refreshdiskread;
ctreewalkpath(t, "/+refreshconfig", time(0))->read = refreshconfigread;
return t;
}
Cnode*
ctreemountfsys(Ctree *t, char *path, ulong time, uchar *score, char *file)
{
Cnode *n;
if(time == 0)
time = 1;
n = ctreewalkpath(t, path, time);
if(score){
if(n->ismtpt && (n->fsysimage || memcmp(n->fsysscore, score, VtScoreSize) != 0)){
free(n->fsysimage);
n->fsysimage = nil;
n->fsys = nil; /* leak (might be other refs) */
}
memmove(n->fsysscore, score, VtScoreSize);
}else{
if(n->ismtpt && (n->fsysimage==nil || strcmp(n->fsysimage, file) != 0)){
free(n->fsysimage);
n->fsysimage = nil;
n->fsys = nil; /* leak (might be other refs) */
}
n->fsysimage = emalloc(strlen(file)+1);
strcpy(n->fsysimage, file);
}
n->ismtpt = 1;
return n;
}
Cnode*
cnodebyhandle(Ctree *t, uchar *p)
{
int h;
Cnode *n;
h = dumbhash(p);
for(n=t->hash[h]; n; n=n->nexthash)
if(memcmp(n->handle, p, CnodeHandleSize) == 0)
return n;
return nil;
}
static int
parseipandmask(char *s, uchar *ip, uchar *mask)
{
char *p, *q;
p = strchr(s, '/');
if(p)
*p++ = 0;
if(parseip(ip, s) == ~0UL)
return -1;
if(p == nil)
memset(mask, 0xFF, IPaddrlen);
else{
if(isdigit((uchar)*p) && strtol(p, &q, 10)>=0 && *q==0)
*--p = '/';
if(parseipmask(mask, p) == ~0UL)
return -1;
if(*p != '/')
*--p = '/';
}
/*fprint(2, "parseipandmask %s => %I %I\n", s, ip, mask); */
return 0;
}
static int
parsetime(char *s, ulong *time)
{
ulong x;
char *p;
int i;
Tm tm;
/* decimal integer is seconds since 1970 */
x = strtoul(s, &p, 10);
if(x > 0 && *p == 0){
*time = x;
return 0;
}
/* otherwise expect yyyy/mmdd/hhmm */
if(strlen(s) != 14 || s[4] != '/' || s[9] != '/')
return -1;
for(i=0; i<4; i++)
if(!isdigit((uchar)s[i]) || !isdigit((uchar)s[i+5]) || !isdigit((uchar)s[i+10]))
return -1;
memset(&tm, 0, sizeof tm);
tm.year = atoi(s)-1900;
if(tm.year < 0 || tm.year > 200)
return -1;
tm.mon = (s[5]-'0')*10+s[6]-'0' - 1;
if(tm.mon < 0 || tm.mon > 11)
return -1;
tm.mday = (s[7]-'0')*10+s[8]-'0';
if(tm.mday < 0 || tm.mday > 31)
return -1;
tm.hour = (s[10]-'0')*10+s[11]-'0';
if(tm.hour < 0 || tm.hour > 23)
return -1;
tm.min = (s[12]-'0')*10+s[13]-'0';
if(tm.min < 0 || tm.min > 59)
return -1;
strcpy(tm.zone, "XXX"); /* anything but GMT */
if(0){
print("tm2sec %d/%d/%d/%d/%d\n",
tm.year, tm.mon, tm.mday, tm.hour, tm.min);
}
*time = tm2sec(&tm);
if(0) print("time %lud\n", *time);
return 0;
}
int
readconfigfile(Config *cp)
{
char *f[10], *image, *p, *pref, *q, *name;
int nf, line;
uchar scorebuf[VtScoreSize], *score;
ulong time;
Biobuf *b;
Config c;
Dir *dir;
name = configfile;
c = *cp;
if((dir = dirstat(name)) == nil)
return -1;
if(c.mtime == dir->mtime){
free(dir);
return 0;
}
c.mtime = dir->mtime;
free(dir);
if((b = Bopen(name, OREAD)) == nil)
return -1;
/*
* Reuse old tree, garbage collecting entries that
* are not mentioned in the new config file.
*/
if(c.ctree == nil)
c.ctree = mkctree();
markctree(c.ctree);
c.ok = nil;
c.nok = 0;
line = 0;
for(; (p=Brdstr(b, '\n', 1)) != nil; free(p)){
line++;
if((q = strchr(p, '#')) != nil)
*q = 0;
nf = tokenize(p, f, nelem(f));
if(nf == 0)
continue;
if(strcmp(f[0], "mount") == 0){
if(nf != 4){
werrstr("syntax error: mount /path /dev|score mtime");
goto badline;
}
if(f[1][0] != '/'){
werrstr("unrooted path %s", f[1]);
goto badline;
}
score = nil;
image = nil;
if(f[2][0] == '/'){
if(access(f[2], AEXIST) < 0){
werrstr("image %s does not exist", f[2]);
goto badline;
}
image = f[2];
}else{
if(vtparsescore(f[2], &pref, scorebuf) < 0){
werrstr("bad score %s", f[2]);
goto badline;
}
score = scorebuf;
}
if(parsetime(f[3], &time) < 0){
fprint(2, "%s:%d: bad time %s\n", name, line, f[3]);
time = 1;
}
ctreemountfsys(c.ctree, f[1], time, score, image);
continue;
}
if(strcmp(f[0], "allow") == 0 || strcmp(f[0], "deny") == 0){
if(nf != 2){
werrstr("syntax error: allow|deny ip[/mask]");
goto badline;
}
c.ok = erealloc(c.ok, (c.nok+1)*sizeof(c.ok[0]));
if(parseipandmask(f[1], c.ok[c.nok].ip, c.ok[c.nok].mask) < 0){
werrstr("bad ip[/mask]: %s", f[1]);
goto badline;
}
c.ok[c.nok].okay = (strcmp(f[0], "allow") == 0);
c.nok++;
continue;
}
werrstr("unknown verb '%s'", f[0]);
badline:
fprint(2, "%s:%d: %r\n", name, line);
}
Bterm(b);
sweepctree(c.ctree);
free(cp->ok);
*cp = c;
return 0;
}
int
ipokay(uchar *ip, ushort port)
{
int i;
uchar ipx[IPaddrlen];
Ipokay *ok;
for(i=0; i<config.nok; i++){
ok = &config.ok[i];
maskip(ip, ok->mask, ipx);
if(0) fprint(2, "%I & %I = %I (== %I?)\n",
ip, ok->mask, ipx, ok->ip);
if(memcmp(ipx, ok->ip, IPaddrlen) == 0)
return ok->okay;
}
if(config.nok == 0) /* all is permitted */
return 1;
/* otherwise default is none allowed */
return 0;
}
Nfs3Status
cnodelookup(Ctree *t, Cnode **np, char *name)
{
Cnode *n, *nn;
n = *np;
if(n->isblackhole)
return Nfs3Ok;
if((nn = cnodewalk(n, name, strlen(name), 0)) == nil){
if(n->ismtpt || n->fsys){
if((nn = cnodewalk(n, "", 0, 1)) == nil){
nn = mkcnode(t, n, "", 0, (char*)n->handle, SHA1dlen);
nn->isblackhole = 1;
}
nn->mark = 0;
}
}
if(nn == nil)
return Nfs3ErrNoEnt;
*np = nn;
return Nfs3Ok;
}
Nfs3Status
cnodegetattr(Cnode *n, Nfs3Attr *attr)
{
uint64 u64;
memset(attr, 0, sizeof *attr);
if(n->read){
attr->type = Nfs3FileReg;
attr->mode = 0444;
attr->size = 512;
attr->nlink = 1;
}else{
attr->type = Nfs3FileDir;
attr->mode = 0555;
attr->size = 1024;
attr->nlink = 10;
}
memmove(&u64, n->handle, 8);
attr->fileid = u64;
attr->atime.sec = n->mtime;
attr->mtime.sec = n->mtime;
attr->ctime.sec = n->mtime;
return Nfs3Ok;
}
Nfs3Status
cnodereaddir(Cnode *n, u32int count, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof)
{
uchar *data, *p, *ep, *np;
u64int c;
u64int u64;
Nfs3Entry ne;
n = n->kidlist;
c = cookie;
for(; c && n; c--)
n = n->nextsib;
if(n == nil){
*pdata = 0;
*pcount = 0;
*peof = 1;
return Nfs3Ok;
}
data = emalloc(count);
p = data;
ep = data+count;
while(n && p < ep){
if(n->mark || n->name[0] == '+'){
n = n->nextsib;
++cookie;
continue;
}
ne.name = n->name;
ne.namelen = strlen(n->name);
ne.cookie = ++cookie;
memmove(&u64, n->handle, 8);
ne.fileid = u64;
if(nfs3entrypack(p, ep, &np, &ne) < 0)
break;
p = np;
n = n->nextsib;
}
*pdata = data;
*pcount = p - data;
*peof = n==nil;
return Nfs3Ok;
}
void
timerproc(void *v)
{
for(;;){
sleep(60*1000);
sendp(timerchan, 0);
}
}
void
timerthread(void *v)
{
for(;;){
recvp(timerchan);
/* refreshconfig(); */
}
}
/*
* Actually serve the NFS requests. Called from nfs3srv.c.
* Each request runs in its own thread (coroutine).
*
* Decrypted handles have the form:
*
* config[20] - SHA1 hash identifying a config tree node
* glob[10] - SHA1 hash prefix identifying a glob state
* fsyshandle[<=10] - disk file system handle (usually 4 bytes)
*/
/*
* A fid represents a point in the file tree.
* There are three components, all derived from the handle:
*
* - config tree position (also used to find fsys)
* - glob state for exclusions
* - file system position
*/
enum
{
HAccess,
HAttr,
HWalk,
HDotdot,
HRead
};
typedef struct Fid Fid;
struct Fid
{
Cnode *cnode;
Fsys *fsys;
Nfs3Handle fsyshandle;
};
int
handlecmp(Nfs3Handle *h, Nfs3Handle *h1)
{
if(h->len != h1->len)
return h->len - h1->len;
return memcmp(h->h, h1->h, h->len);
}
Nfs3Status
handletofid(Nfs3Handle *eh, Fid *fid, int mode)
{
int domount;
Cnode *n;
Disk *disk, *cdisk;
Fsys *fsys;
Nfs3Status ok;
Nfs3Handle h2, *h, *fh;
memset(fid, 0, sizeof *fid);
domount = 1;
if(mode == HDotdot)
domount = 0;
/*
* Not necessary, but speeds up ls -l /dump/2005
* HAttr and HAccess must be handled the same way
* because both can be used to fetch attributes.
* Acting differently yields inconsistencies at mount points,
* and causes FreeBSD ls -l to fail.
*/
if(mode == HAttr || mode == HAccess)
domount = 0;
/*
* Decrypt handle.
*/
h2 = *eh;
h = &h2;
if((ok = hdecrypt(h)) != Nfs3Ok)
return ok;
trace("handletofid: decrypted %.*lH\n", h->len, h->h);
if(h->len < FsysHandleOffset)
return Nfs3ErrBadHandle;
/*
* Find place in config tree.
*/
if((n = cnodebyhandle(config.ctree, h->h)) == nil)
return Nfs3ErrStale;
fid->cnode = n;
if(n->ismtpt && domount){
/*
* Open fsys for mount point if needed.
*/
if(n->mfsys == nil){
trace("handletofid: mounting %V/%s\n", n->fsysscore, n->fsysimage);
if(n->fsysimage){
if(strcmp(n->fsysimage, "/dev/null") == 0)
return Nfs3ErrAcces;
if((disk = diskopenfile(n->fsysimage)) == nil){
fprint(2, "cannot open disk %s: %r\n", n->fsysimage);
return Nfs3ErrIo;
}
if((cdisk = diskcache(disk, blocksize, 64)) == nil){
fprint(2, "cannot cache disk %s: %r\n", n->fsysimage);
diskclose(disk);
}
disk = cdisk;
}else{
if((disk = diskopenventi(vcache, n->fsysscore)) == nil){
fprint(2, "cannot open venti disk %V: %r\n", n->fsysscore);
return Nfs3ErrIo;
}
}
if((fsys = fsysopen(disk)) == nil){
fprint(2, "cannot open fsys on %V: %r\n", n->fsysscore);
diskclose(disk);
return Nfs3ErrIo;
}
n->mfsys = fsys;
fsysroot(fsys, &n->mfsyshandle);
}
/*
* Use inner handle.
*/
fid->fsys = n->mfsys;
fid->fsyshandle = n->mfsyshandle;
}else{
/*
* Use fsys handle from tree or from handle.
* This assumes that fsyshandle was set by fidtohandle
* earlier, so it's not okay to reuse handles (except the root)
* across sessions. The encryption above makes and
* enforces the same restriction, so this is okay.
*/
fid->fsys = n->fsys;
fh = &fid->fsyshandle;
if(n->isblackhole){
fh->len = h->len-FsysHandleOffset;
memmove(fh->h, h->h+FsysHandleOffset, fh->len);
}else
*fh = n->fsyshandle;
trace("handletofid: fsyshandle %.*lH\n", fh->len, fh->h);
}
/*
* TO DO (maybe): some sort of path restriction here.
*/
trace("handletofid: cnode %s fsys %p fsyshandle %.*lH\n",
n->name, fid->fsys, fid->fsyshandle.len, fid->fsyshandle.h);
return Nfs3Ok;
}
void
_fidtohandle(Fid *fid, Nfs3Handle *h)
{
Cnode *n;
n = fid->cnode;
/*
* Record fsys handle in n, don't bother sending it to client
* for black holes.
*/
n->fsys = fid->fsys;
if(!n->isblackhole){
n->fsyshandle = fid->fsyshandle;
fid->fsyshandle.len = 0;
}
memmove(h->h, n->handle, CnodeHandleSize);
memmove(h->h+FsysHandleOffset, fid->fsyshandle.h, fid->fsyshandle.len);
h->len = FsysHandleOffset+fid->fsyshandle.len;
}
void
fidtohandle(Fid *fid, Nfs3Handle *h)
{
_fidtohandle(fid, h);
hencrypt(h);
}
void
setrootfid(void)
{
Fid fid;
memset(&fid, 0, sizeof fid);
fid.cnode = config.ctree->root;
_fidtohandle(&fid, &root);
}
void
fsgetroot(Nfs3Handle *h)
{
*h = root;
hencrypt(h);
}
Nfs3Status
fsgetattr(SunAuthUnix *au, Nfs3Handle *h, Nfs3Attr *attr)
{
Fid fid;
Nfs3Status ok;
trace("getattr %.*lH\n", h->len, h->h);
if((ok = handletofid(h, &fid, HAttr)) != Nfs3Ok)
return ok;
if(fid.fsys)
return fsysgetattr(fid.fsys, au, &fid.fsyshandle, attr);
else
return cnodegetattr(fid.cnode, attr);
}
/*
* Lookup is always the hard part.
*/
Nfs3Status
fslookup(SunAuthUnix *au, Nfs3Handle *h, char *name, Nfs3Handle *nh)
{
Fid fid;
Cnode *n;
Nfs3Status ok;
Nfs3Handle xh;
int mode;
trace("lookup %.*lH %s\n", h->len, h->h, name);
mode = HWalk;
if(strcmp(name, "..") == 0 || strcmp(name, ".") == 0)
mode = HDotdot;
if((ok = handletofid(h, &fid, mode)) != Nfs3Ok){
nfs3errstr(ok);
trace("lookup: handletofid %r\n");
return ok;
}
if(strcmp(name, ".") == 0){
fidtohandle(&fid, nh);
return Nfs3Ok;
}
/*
* Walk down file system and cnode simultaneously.
* If dotdot and file system doesn't move, need to walk
* up cnode. Save the corresponding fsys handles in
* the cnode as we walk down so that we'll have them
* for dotdotting back up.
*/
n = fid.cnode;
if(mode == HWalk){
/*
* Walk down config tree and file system simultaneously.
*/
if((ok = cnodelookup(config.ctree, &n, name)) != Nfs3Ok){
nfs3errstr(ok);
trace("lookup: cnodelookup: %r\n");
return ok;
}
fid.cnode = n;
if(fid.fsys){
if((ok = fsyslookup(fid.fsys, au, &fid.fsyshandle, name, &xh)) != Nfs3Ok){
nfs3errstr(ok);
trace("lookup: fsyslookup: %r\n");
return ok;
}
fid.fsyshandle = xh;
}
}else{
/*
* Walking dotdot. Ick.
*/
trace("lookup dotdot fsys=%p\n", fid.fsys);
if(fid.fsys){
/*
* Walk up file system, then try up config tree.
*/
if((ok = fsyslookup(fid.fsys, au, &fid.fsyshandle, "..", &xh)) != Nfs3Ok){
nfs3errstr(ok);
trace("lookup fsyslookup: %r\n");
return ok;
}
fid.fsyshandle = xh;
/*
* Usually just go to n->parent.
*
* If we're in a subtree of the mounted file system that
* isn't represented explicitly by the config tree (instead
* the black hole node represents the entire file tree),
* then we only go to n->parent when we've dotdotted back
* to the right handle.
*/
if(n->parent == nil)
trace("lookup dotdot: no parent\n");
else{
trace("lookup dotdot: parent %.*lH, have %.*lH\n",
n->parent->fsyshandle.len, n->parent->fsyshandle.h,
xh.len, xh.h);
}
if(n->isblackhole){
if(handlecmp(&n->parent->mfsyshandle, &xh) == 0)
n = n->parent;
}else{
if(n->parent)
n = n->parent;
}
}else{
/*
* No file system, just walk up.
*/
if(n->parent)
n = n->parent;
}
fid.fsys = n->fsys;
if(!n->isblackhole)
fid.fsyshandle = n->fsyshandle;
fid.cnode = n;
}
fidtohandle(&fid, nh);
return Nfs3Ok;
}
Nfs3Status
fsaccess(SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr)
{
Fid fid;
Nfs3Status ok;
trace("access %.*lH 0x%ux\n", h->len, h->h, want);
if((ok = handletofid(h, &fid, HAccess)) != Nfs3Ok)
return ok;
if(fid.fsys)
return fsysaccess(fid.fsys, au, &fid.fsyshandle, want, got, attr);
*got = want & (Nfs3AccessRead|Nfs3AccessLookup|Nfs3AccessExecute);
return cnodegetattr(fid.cnode, attr);
}
Nfs3Status
fsreadlink(SunAuthUnix *au, Nfs3Handle *h, char **link)
{
Fid fid;
Nfs3Status ok;
trace("readlink %.*lH\n", h->len, h->h);
if((ok = handletofid(h, &fid, HRead)) != Nfs3Ok)
return ok;
if(fid.fsys)
return fsysreadlink(fid.fsys, au, &fid.fsyshandle, link);
*link = 0;
return Nfs3ErrNotSupp;
}
Nfs3Status
fsreadfile(SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
{
Fid fid;
Nfs3Status ok;
trace("readfile %.*lH\n", h->len, h->h);
if((ok = handletofid(h, &fid, HRead)) != Nfs3Ok)
return ok;
if(fid.cnode->read)
return fid.cnode->read(fid.cnode, count, offset, data, pcount, peof);
if(fid.fsys)
return fsysreadfile(fid.fsys, au, &fid.fsyshandle, count, offset, data, pcount, peof);
return Nfs3ErrNotSupp;
}
Nfs3Status
fsreaddir(SunAuthUnix *au, Nfs3Handle *h, u32int len, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof)
{
Fid fid;
Nfs3Status ok;
trace("readdir %.*lH\n", h->len, h->h);
if((ok = handletofid(h, &fid, HRead)) != Nfs3Ok)
return ok;
if(fid.fsys)
return fsysreaddir(fid.fsys, au, &fid.fsyshandle, len, cookie, pdata, pcount, peof);
return cnodereaddir(fid.cnode, len, cookie, pdata, pcount, peof);
}
Nfs3Status
logread(Cnode *n, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
{
*pcount = 0;
*peof = 1;
return Nfs3Ok;
}
Nfs3Status
refreshdiskread(Cnode *n, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
{
char buf[128];
if(offset != 0){
*pcount = 0;
*peof = 1;
return Nfs3Ok;
}
if(refreshdisk() < 0)
snprint(buf, sizeof buf, "refreshdisk: %r\n");
else
strcpy(buf, "ok\n");
*data = emalloc(strlen(buf));
strcpy((char*)*data, buf);
*pcount = strlen(buf);
*peof = 1;
return Nfs3Ok;
}
Nfs3Status
refreshconfigread(Cnode *n, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
{
char buf[128];
if(offset != 0){
*pcount = 0;
*peof = 1;
return Nfs3Ok;
}
if(readconfigfile(&config) < 0)
snprint(buf, sizeof buf, "readconfig: %r\n");
else
strcpy(buf, "ok\n");
*data = emalloc(strlen(buf));
strcpy((char*)*data, buf);
*pcount = strlen(buf);
*peof = 1;
return Nfs3Ok;
}