Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#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;
}