782 lines
13 KiB
C
782 lines
13 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <bio.h>
|
|
#include <auth.h>
|
|
#include <fcall.h>
|
|
#include <String.h>
|
|
#include <libsec.h>
|
|
|
|
enum
|
|
{
|
|
OPERM = 0x3, /* mask of all permission types in open mode */
|
|
Maxsize = 512*1024*1024,
|
|
Maxfdata = 8192,
|
|
NAMELEN = 28,
|
|
};
|
|
|
|
typedef struct Fid Fid;
|
|
struct Fid
|
|
{
|
|
short busy;
|
|
int fid;
|
|
Fid *next;
|
|
char *user;
|
|
String *path; /* complete path */
|
|
|
|
int fd; /* set on open or create */
|
|
Qid qid; /* set on open or create */
|
|
int attach; /* this is an attach fd */
|
|
|
|
ulong diroff; /* directory offset */
|
|
Dir *dir; /* directory entries */
|
|
int ndir; /* number of directory entries */
|
|
};
|
|
|
|
Fid *fids;
|
|
int mfd[2];
|
|
char *user;
|
|
uchar mdata[IOHDRSZ+Maxfdata];
|
|
uchar rdata[Maxfdata]; /* buffer for data in reply */
|
|
uchar statbuf[STATMAX];
|
|
Fcall thdr;
|
|
Fcall rhdr;
|
|
int messagesize = sizeof mdata;
|
|
int readonly;
|
|
char *srvname;
|
|
int debug;
|
|
|
|
Fid * newfid(int);
|
|
void io(void);
|
|
void *erealloc(void*, ulong);
|
|
void *emalloc(ulong);
|
|
char *estrdup(char*);
|
|
void usage(void);
|
|
void fidqid(Fid*, Qid*);
|
|
char* short2long(char*);
|
|
char* long2short(char*, int);
|
|
void readnames(void);
|
|
void post(char*, int);
|
|
|
|
char *rflush(Fid*), *rversion(Fid*), *rauth(Fid*),
|
|
*rattach(Fid*), *rwalk(Fid*),
|
|
*ropen(Fid*), *rcreate(Fid*),
|
|
*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
|
|
*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
|
|
|
|
char *(*fcalls[])(Fid*) = {
|
|
[Tversion] rversion,
|
|
[Tflush] rflush,
|
|
[Tauth] rauth,
|
|
[Tattach] rattach,
|
|
[Twalk] rwalk,
|
|
[Topen] ropen,
|
|
[Tcreate] rcreate,
|
|
[Tread] rread,
|
|
[Twrite] rwrite,
|
|
[Tclunk] rclunk,
|
|
[Tremove] rremove,
|
|
[Tstat] rstat,
|
|
[Twstat] rwstat,
|
|
};
|
|
|
|
char Eperm[] = "permission denied";
|
|
char Enotdir[] = "not a directory";
|
|
char Enoauth[] = "lnfs: authentication not required";
|
|
char Enotexist[] = "file does not exist";
|
|
char Einuse[] = "file in use";
|
|
char Eexist[] = "file exists";
|
|
char Eisdir[] = "file is a directory";
|
|
char Enotowner[] = "not owner";
|
|
char Eisopen[] = "file already open for I/O";
|
|
char Excl[] = "exclusive use file already open";
|
|
char Ename[] = "illegal name";
|
|
char Eversion[] = "unknown 9P version";
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: %s [-r] [-s srvname] mountpoint\n", argv0);
|
|
exits("usage");
|
|
}
|
|
|
|
void
|
|
notifyf(void *a, char *s)
|
|
{
|
|
USED(a);
|
|
if(strncmp(s, "interrupt", 9) == 0)
|
|
noted(NCONT);
|
|
noted(NDFLT);
|
|
}
|
|
|
|
void
|
|
main(int argc, char *argv[])
|
|
{
|
|
char *defmnt;
|
|
int p[2];
|
|
Dir *d;
|
|
|
|
ARGBEGIN{
|
|
case 'r':
|
|
readonly = 1;
|
|
break;
|
|
case 'd':
|
|
debug = 1;
|
|
break;
|
|
case 's':
|
|
srvname = ARGF();
|
|
if(srvname == nil)
|
|
usage();
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
|
|
if(argc < 1)
|
|
usage();
|
|
defmnt = argv[0];
|
|
d = dirstat(defmnt);
|
|
if(d == nil || !(d->qid.type & QTDIR))
|
|
sysfatal("mountpoint must be an accessible directory");
|
|
free(d);
|
|
|
|
if(pipe(p) < 0)
|
|
sysfatal("pipe failed");
|
|
mfd[0] = p[0];
|
|
mfd[1] = p[0];
|
|
|
|
user = getuser();
|
|
notify(notifyf);
|
|
|
|
if(srvname != nil)
|
|
post(srvname, p[1]);
|
|
|
|
if(debug)
|
|
fmtinstall('F', fcallfmt);
|
|
switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
|
|
case -1:
|
|
sysfatal("fork: %r");
|
|
case 0:
|
|
close(p[1]);
|
|
chdir(defmnt);
|
|
io();
|
|
break;
|
|
default:
|
|
close(p[0]); /* don't deadlock if child fails */
|
|
if(mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0)
|
|
sysfatal("mount failed: %r");
|
|
}
|
|
exits(0);
|
|
}
|
|
|
|
void
|
|
post(char *srvname, int pfd)
|
|
{
|
|
char name[128];
|
|
int fd;
|
|
|
|
snprint(name, sizeof name, "#s/%s", srvname);
|
|
fd = create(name, OWRITE, 0666);
|
|
if(fd < 0)
|
|
sysfatal("create of %s failed: %r", srvname);
|
|
sprint(name, "%d", pfd);
|
|
if(write(fd, name, strlen(name)) < 0)
|
|
sysfatal("writing %s: %r", srvname);
|
|
}
|
|
char*
|
|
rversion(Fid*)
|
|
{
|
|
Fid *f;
|
|
|
|
for(f = fids; f; f = f->next)
|
|
if(f->busy)
|
|
rclunk(f);
|
|
if(thdr.msize > sizeof mdata)
|
|
rhdr.msize = sizeof mdata;
|
|
else
|
|
rhdr.msize = thdr.msize;
|
|
messagesize = rhdr.msize;
|
|
if(strncmp(thdr.version, "9P2000", 6) != 0)
|
|
return Eversion;
|
|
rhdr.version = "9P2000";
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rauth(Fid*)
|
|
{
|
|
return Enoauth;
|
|
}
|
|
|
|
char*
|
|
rflush(Fid *f)
|
|
{
|
|
USED(f);
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rattach(Fid *f)
|
|
{
|
|
/* no authentication! */
|
|
f->busy = 1;
|
|
if(thdr.uname[0])
|
|
f->user = estrdup(thdr.uname);
|
|
else
|
|
f->user = "none";
|
|
if(strcmp(user, "none") == 0)
|
|
user = f->user;
|
|
if(f->path)
|
|
s_free(f->path);
|
|
f->path = s_copy(".");
|
|
fidqid(f, &rhdr.qid);
|
|
f->attach = 1;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
clone(Fid *f, Fid **nf)
|
|
{
|
|
if(f->fd >= 0)
|
|
return Eisopen;
|
|
*nf = newfid(thdr.newfid);
|
|
(*nf)->busy = 1;
|
|
if((*nf)->path)
|
|
s_free((*nf)->path);
|
|
(*nf)->path = s_clone(f->path);
|
|
(*nf)->fd = -1;
|
|
(*nf)->user = f->user;
|
|
(*nf)->attach = 0;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rwalk(Fid *f)
|
|
{
|
|
char *name;
|
|
Fid *nf;
|
|
char *err;
|
|
int i;
|
|
String *npath;
|
|
Dir *d;
|
|
char *cp;
|
|
Qid qid;
|
|
|
|
err = nil;
|
|
nf = nil;
|
|
rhdr.nwqid = 0;
|
|
if(rhdr.newfid != rhdr.fid){
|
|
err = clone(f, &nf);
|
|
if(err)
|
|
return err;
|
|
f = nf; /* walk the new fid */
|
|
}
|
|
readnames();
|
|
npath = s_clone(f->path);
|
|
if(thdr.nwname > 0){
|
|
for(i=0; i<thdr.nwname && i<MAXWELEM; i++){
|
|
name = long2short(thdr.wname[i], 0);
|
|
if(strcmp(name, ".") == 0){
|
|
;
|
|
} else if(strcmp(name, "..") == 0){
|
|
cp = strrchr(s_to_c(npath), '/');
|
|
if(cp != nil){
|
|
*cp = 0;
|
|
npath->ptr = cp;
|
|
}
|
|
} else {
|
|
s_append(npath, "/");
|
|
s_append(npath, name);
|
|
}
|
|
d = dirstat(s_to_c(npath));
|
|
if(d == nil)
|
|
break;
|
|
rhdr.nwqid++;
|
|
qid = d->qid;
|
|
rhdr.wqid[i] = qid;
|
|
free(d);
|
|
}
|
|
if(i==0 && err == nil)
|
|
err = Enotexist;
|
|
}
|
|
|
|
/* if there was an error and we cloned, get rid of the new fid */
|
|
if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){
|
|
f->busy = 0;
|
|
s_free(npath);
|
|
}
|
|
|
|
/* update the fid after a successful walk */
|
|
if(rhdr.nwqid == thdr.nwname){
|
|
s_free(f->path);
|
|
f->path = npath;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static char*
|
|
passerror(void)
|
|
{
|
|
static char err[256];
|
|
|
|
rerrstr(err, sizeof err);
|
|
return err;
|
|
}
|
|
|
|
char*
|
|
ropen(Fid *f)
|
|
{
|
|
if(readonly && (thdr.mode & 3))
|
|
return Eperm;
|
|
if(f->fd >= 0)
|
|
return Eisopen;
|
|
f->fd = open(s_to_c(f->path), thdr.mode);
|
|
if(f->fd < 0)
|
|
return passerror();
|
|
fidqid(f, &rhdr.qid);
|
|
f->qid = rhdr.qid;
|
|
rhdr.iounit = messagesize-IOHDRSZ;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rcreate(Fid *f)
|
|
{
|
|
char *name;
|
|
|
|
if(readonly)
|
|
return Eperm;
|
|
readnames();
|
|
name = long2short(thdr.name, 1);
|
|
if(f->fd >= 0)
|
|
return Eisopen;
|
|
s_append(f->path, "/");
|
|
s_append(f->path, name);
|
|
f->fd = create(s_to_c(f->path), thdr.mode, thdr.perm);
|
|
if(f->fd < 0)
|
|
return passerror();
|
|
fidqid(f, &rhdr.qid);
|
|
f->qid = rhdr.qid;
|
|
rhdr.iounit = messagesize-IOHDRSZ;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rreaddir(Fid *f)
|
|
{
|
|
int i;
|
|
int n;
|
|
|
|
/* reread the directory */
|
|
if(thdr.offset == 0){
|
|
if(f->dir)
|
|
free(f->dir);
|
|
f->dir = nil;
|
|
if(f->diroff != 0)
|
|
seek(f->fd, 0, 0);
|
|
f->ndir = dirreadall(f->fd, &f->dir);
|
|
f->diroff = 0;
|
|
if(f->ndir < 0)
|
|
return passerror();
|
|
readnames();
|
|
for(i = 0; i < f->ndir; i++)
|
|
f->dir[i].name = short2long(f->dir[i].name);
|
|
}
|
|
|
|
/* copy in as many directory entries as possible */
|
|
for(n = 0; f->diroff < f->ndir; n += i){
|
|
i = convD2M(&f->dir[f->diroff], rdata+n, thdr.count - n);
|
|
if(i <= BIT16SZ)
|
|
break;
|
|
f->diroff++;
|
|
}
|
|
rhdr.data = (char*)rdata;
|
|
rhdr.count = n;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rread(Fid *f)
|
|
{
|
|
long n;
|
|
|
|
if(f->fd < 0)
|
|
return Enotexist;
|
|
if(thdr.count > messagesize-IOHDRSZ)
|
|
thdr.count = messagesize-IOHDRSZ;
|
|
if(f->qid.type & QTDIR)
|
|
return rreaddir(f);
|
|
n = pread(f->fd, rdata, thdr.count, thdr.offset);
|
|
if(n < 0)
|
|
return passerror();
|
|
rhdr.data = (char*)rdata;
|
|
rhdr.count = n;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rwrite(Fid *f)
|
|
{
|
|
long n;
|
|
|
|
if(readonly || (f->qid.type & QTDIR))
|
|
return Eperm;
|
|
if(f->fd < 0)
|
|
return Enotexist;
|
|
if(thdr.count > messagesize-IOHDRSZ) /* shouldn't happen, anyway */
|
|
thdr.count = messagesize-IOHDRSZ;
|
|
n = pwrite(f->fd, thdr.data, thdr.count, thdr.offset);
|
|
if(n < 0)
|
|
return passerror();
|
|
rhdr.count = n;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rclunk(Fid *f)
|
|
{
|
|
f->busy = 0;
|
|
close(f->fd);
|
|
f->fd = -1;
|
|
f->path = s_reset(f->path);
|
|
if(f->attach){
|
|
free(f->user);
|
|
f->user = nil;
|
|
}
|
|
f->attach = 0;
|
|
if(f->dir != nil){
|
|
free(f->dir);
|
|
f->dir = nil;
|
|
}
|
|
f->diroff = f->ndir = 0;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rremove(Fid *f)
|
|
{
|
|
if(remove(s_to_c(f->path)) < 0)
|
|
return passerror();
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rstat(Fid *f)
|
|
{
|
|
int n;
|
|
Dir *d;
|
|
|
|
d = dirstat(s_to_c(f->path));
|
|
if(d == nil)
|
|
return passerror();
|
|
d->name = short2long(d->name);
|
|
n = convD2M(d, statbuf, sizeof statbuf);
|
|
free(d);
|
|
if(n <= BIT16SZ)
|
|
return passerror();
|
|
rhdr.nstat = n;
|
|
rhdr.stat = statbuf;
|
|
return nil;
|
|
}
|
|
|
|
char*
|
|
rwstat(Fid *f)
|
|
{
|
|
int n;
|
|
Dir d;
|
|
|
|
if(readonly)
|
|
return Eperm;
|
|
convM2D(thdr.stat, thdr.nstat, &d, (char*)rdata);
|
|
d.name = long2short(d.name, 1);
|
|
n = dirwstat(s_to_c(f->path), &d);
|
|
if(n < 0)
|
|
return passerror();
|
|
return nil;
|
|
}
|
|
|
|
Fid *
|
|
newfid(int fid)
|
|
{
|
|
Fid *f, *ff;
|
|
|
|
ff = 0;
|
|
for(f = fids; f; f = f->next)
|
|
if(f->fid == fid)
|
|
return f;
|
|
else if(!ff && !f->busy)
|
|
ff = f;
|
|
if(ff){
|
|
ff->fid = fid;
|
|
return ff;
|
|
}
|
|
f = emalloc(sizeof *f);
|
|
f->path = s_reset(f->path);
|
|
f->fd = -1;
|
|
f->fid = fid;
|
|
f->next = fids;
|
|
fids = f;
|
|
return f;
|
|
}
|
|
|
|
void
|
|
io(void)
|
|
{
|
|
char *err;
|
|
int n, pid;
|
|
|
|
pid = getpid();
|
|
|
|
for(;;){
|
|
/*
|
|
* reading from a pipe or a network device
|
|
* will give an error after a few eof reads.
|
|
* however, we cannot tell the difference
|
|
* between a zero-length read and an interrupt
|
|
* on the processes writing to us,
|
|
* so we wait for the error.
|
|
*/
|
|
n = read9pmsg(mfd[0], mdata, messagesize);
|
|
if(n < 0)
|
|
sysfatal("mount read");
|
|
if(n == 0)
|
|
continue;
|
|
if(convM2S(mdata, n, &thdr) == 0)
|
|
continue;
|
|
|
|
if(debug)
|
|
fprint(2, "%s %d:<-%F\n", argv0, pid, &thdr);
|
|
|
|
if(!fcalls[thdr.type])
|
|
err = "bad fcall type";
|
|
else
|
|
err = (*fcalls[thdr.type])(newfid(thdr.fid));
|
|
if(err){
|
|
rhdr.type = Rerror;
|
|
rhdr.ename = err;
|
|
}else{
|
|
rhdr.type = thdr.type + 1;
|
|
rhdr.fid = thdr.fid;
|
|
}
|
|
rhdr.tag = thdr.tag;
|
|
if(debug)
|
|
fprint(2, "%s %d:->%F\n", argv0, pid, &rhdr);/**/
|
|
n = convS2M(&rhdr, mdata, messagesize);
|
|
if(n == 0)
|
|
sysfatal("convS2M error on write");
|
|
if(write(mfd[1], mdata, n) != n)
|
|
sysfatal("mount write");
|
|
}
|
|
}
|
|
|
|
void *
|
|
emalloc(ulong n)
|
|
{
|
|
void *p;
|
|
|
|
p = malloc(n);
|
|
if(!p)
|
|
sysfatal("out of memory");
|
|
memset(p, 0, n);
|
|
return p;
|
|
}
|
|
|
|
void *
|
|
erealloc(void *p, ulong n)
|
|
{
|
|
p = realloc(p, n);
|
|
if(!p)
|
|
sysfatal("out of memory");
|
|
return p;
|
|
}
|
|
|
|
char *
|
|
estrdup(char *q)
|
|
{
|
|
char *p;
|
|
int n;
|
|
|
|
n = strlen(q)+1;
|
|
p = malloc(n);
|
|
if(!p)
|
|
sysfatal("out of memory");
|
|
memmove(p, q, n);
|
|
return p;
|
|
}
|
|
|
|
void
|
|
fidqid(Fid *f, Qid *q)
|
|
{
|
|
Dir *d;
|
|
|
|
d = dirstat(s_to_c(f->path));
|
|
if(d == nil)
|
|
*q = (Qid){0, 0, QTFILE};
|
|
else {
|
|
*q = d->qid;
|
|
free(d);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* table of name translations
|
|
*
|
|
* the file ./.longnames contains all the known long names.
|
|
* the short name is the first NAMELEN-1 bytes of the base64
|
|
* encoding of the MD5 hash of the longname.
|
|
*/
|
|
|
|
typedef struct Name Name;
|
|
struct Name
|
|
{
|
|
Name *next;
|
|
char shortname[NAMELEN];
|
|
char *longname;
|
|
};
|
|
|
|
Dir *dbstat; /* last stat of the name file */
|
|
char *namefile = "./.longnames";
|
|
char *namebuf;
|
|
Name *names;
|
|
|
|
Name*
|
|
newname(char *longname, int writeflag)
|
|
{
|
|
Name *np;
|
|
int n;
|
|
uchar digest[MD5dlen];
|
|
int fd;
|
|
|
|
/* chain in new name */
|
|
n = strlen(longname);
|
|
np = emalloc(sizeof(*np)+n+1);
|
|
np->longname = (char*)&np[1];
|
|
strcpy(np->longname, longname);
|
|
md5((uchar*)longname, n, digest, nil);
|
|
enc32(np->shortname, sizeof(np->shortname), digest, MD5dlen);
|
|
np->next = names;
|
|
names = np;
|
|
|
|
/* don't change namefile if we're read only */
|
|
if(!writeflag)
|
|
return np;
|
|
|
|
/* add to namefile */
|
|
fd = open(namefile, OWRITE);
|
|
if(fd >= 0){
|
|
seek(fd, 0, 2);
|
|
fprint(fd, "%s\n", longname);
|
|
close(fd);
|
|
}
|
|
return np;
|
|
}
|
|
|
|
void
|
|
freenames(void)
|
|
{
|
|
Name *np, *next;
|
|
|
|
for(np = names; np != nil; np = next){
|
|
next = np->next;
|
|
free(np);
|
|
}
|
|
names = nil;
|
|
}
|
|
|
|
/*
|
|
* reread the file if the qid.path has changed.
|
|
*
|
|
* read any new entries if length has changed.
|
|
*/
|
|
void
|
|
readnames(void)
|
|
{
|
|
Dir *d;
|
|
int fd;
|
|
vlong offset;
|
|
Biobuf *b;
|
|
char *p;
|
|
|
|
d = dirstat(namefile);
|
|
if(d == nil){
|
|
if(readonly)
|
|
return;
|
|
|
|
/* create file if it doesn't exist */
|
|
fd = create(namefile, OREAD, DMAPPEND|0666);
|
|
if(fd < 0)
|
|
return;
|
|
if(dbstat != nil)
|
|
free(dbstat);
|
|
dbstat = nil;
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
/* up to date? */
|
|
offset = 0;
|
|
if(dbstat != nil){
|
|
if(d->qid.path == dbstat->qid.path){
|
|
if(d->length <= dbstat->length){
|
|
free(d);
|
|
return;
|
|
}
|
|
offset = dbstat->length;
|
|
} else {
|
|
freenames();
|
|
}
|
|
free(dbstat);
|
|
dbstat = nil;
|
|
}
|
|
|
|
/* read file */
|
|
b = Bopen(namefile, OREAD);
|
|
if(b == nil){
|
|
free(d);
|
|
return;
|
|
}
|
|
Bseek(b, offset, 0);
|
|
while((p = Brdline(b, '\n')) != nil){
|
|
p[Blinelen(b)-1] = 0;
|
|
newname(p, 0);
|
|
}
|
|
Bterm(b);
|
|
dbstat = d;
|
|
}
|
|
|
|
/*
|
|
* look up a long name, if it doesn't exist in the
|
|
* file, add an entry to the file if the writeflag is
|
|
* non-zero. Return a pointer to the short name.
|
|
*/
|
|
char*
|
|
long2short(char *longname, int writeflag)
|
|
{
|
|
Name *np;
|
|
|
|
if(strlen(longname) < NAMELEN-1 && strpbrk(longname, " ")==nil)
|
|
return longname;
|
|
|
|
for(np = names; np != nil; np = np->next)
|
|
if(strcmp(longname, np->longname) == 0)
|
|
return np->shortname;
|
|
if(!writeflag)
|
|
return longname;
|
|
np = newname(longname, !readonly);
|
|
return np->shortname;
|
|
}
|
|
|
|
/*
|
|
* look up a short name, if it doesn't exist, return the
|
|
* longname.
|
|
*/
|
|
char*
|
|
short2long(char *shortname)
|
|
{
|
|
Name *np;
|
|
|
|
for(np = names; np != nil; np = np->next)
|
|
if(strcmp(shortname, np->shortname) == 0)
|
|
return np->longname;
|
|
return shortname;
|
|
}
|