diff --git a/sys/src/cmd/ip/mkfile b/sys/src/cmd/ip/mkfile index 82b592299..0d3fea656 100644 --- a/sys/src/cmd/ip/mkfile +++ b/sys/src/cmd/ip/mkfile @@ -18,6 +18,7 @@ TARG = 6in4\ telnet\ telnetd\ tftpd\ + tftpfs\ traceroute\ udpecho\ wol\ diff --git a/sys/src/cmd/ip/tftpfs.c b/sys/src/cmd/ip/tftpfs.c new file mode 100644 index 000000000..6e2264982 --- /dev/null +++ b/sys/src/cmd/ip/tftpfs.c @@ -0,0 +1,477 @@ +#include +#include +#include +#include +#include +#include <9p.h> +#include + +enum { + Qdata = 1, + + Tftp_READ = 1, + Tftp_WRITE = 2, + Tftp_DATA = 3, + Tftp_ACK = 4, + Tftp_ERROR = 5, + Tftp_OACK = 6, + + TftpPort = 69, + + Segsize = 512, + Maxpath = 2+2+Segsize-8, +}; + +typedef struct Tfile Tfile; +struct Tfile +{ + int id; + uchar addr[IPaddrlen]; + char path[Maxpath]; + Channel *c; + Tfile *next; + Ref; +}; + +uchar ipaddr[IPaddrlen]; +static ulong time0; +Tfile *files; + + +static Tfile* +tfileget(uchar *addr, char *path) +{ + Tfile *f; + static int id; + + for(f = files; f; f = f->next){ + if(memcmp(addr, f->addr, IPaddrlen) == 0 && strcmp(path, f->path) == 0){ + incref(f); + return f; + } + } + f = emalloc9p(sizeof *f); + memset(f, 0, sizeof(*f)); + ipmove(f->addr, addr); + strncpy(f->path, path, sizeof(f->path)); + f->ref = 1; + f->id = id++; + f->next = files; + files = f; + + return f; +} + +static void +tfileput(Tfile *f) +{ + Channel *c; + Tfile **pp; + + if(f==nil || decref(f)) + return; + if(c = f->c){ + f->c = nil; + sendp(c, nil); + } + for(pp = &files; *pp; pp = &(*pp)->next){ + if(*pp == f){ + *pp = f->next; + break; + } + } + free(f); +} + +static char* +basename(char *p) +{ + char *b; + + for(b = p; *p; p++) + if(*p == '/') + b = p+1; + return b; +} + +static void +tfilestat(Req *r, char *path, vlong length) +{ + memset(&r->d, 0, sizeof(r->d)); + r->d.uid = estrdup9p("tftp"); + r->d.gid = estrdup9p("tftp"); + r->d.name = estrdup9p(basename(path)); + r->d.atime = r->d.mtime = time0; + r->d.length = length; + r->d.qid.path = r->fid->qid.path; + if(r->fid->qid.path & Qdata){ + r->d.qid.type = 0; + r->d.mode = 0555; + } else { + r->d.qid.type = QTDIR; + r->d.mode = DMDIR|0555; + } + respond(r, nil); +} + +static void +catch(void *, char *msg) +{ + if(strstr(msg, "alarm")) + noted(NCONT); + noted(NDFLT); +} + +static int +newport(void) +{ + static int port; + return 5000+(port++)%64; +} + +static int +filereq(uchar *buf, char *path) +{ + uchar *p; + int n; + + hnputs(buf, Tftp_READ); + p = buf+2; + n = strlen(path); + + /* hack: remove the trailing dot */ + if(path[n-1] == '.') + n--; + + memcpy(p, path, n); + p += n; + *p++ = 0; + memcpy(p, "octet", 6); + p += 6; + return p - buf; +} + +static void +download(void *aux) +{ + int fd, cfd, last, block, n, ndata; + char *err, addr[40], adir[40]; + uchar *data; + Channel *c; + Tfile *f; + Req *r; + + struct { + Udphdr; + uchar buf[2+2+Segsize+1]; + } msg; + + c = nil; + r = nil; + fd = cfd = -1; + err = nil; + data = nil; + ndata = 0; + + if((f = aux) == nil) + goto out; + if((c = f->c) == nil) + goto out; + + threadsetname(f->path); + + for(n=0; n<10; n++){ + snprint(addr, sizeof(addr), "udp!*!%d", newport()); + if((cfd = announce(addr, adir)) >= 0) + break; + } + if(cfd < 0){ + err = "announce: %r"; + goto out; + } + if(write(cfd, "headers", 7) < 0){ + err = "write ctl: %r"; + goto out; + } + strcat(adir, "/data"); + if((fd = open(adir, ORDWR)) < 0){ + err = "open: %r"; + goto out; + } + + n = filereq(msg.buf, f->path); + ipmove(msg.raddr, f->addr); + hnputs(msg.rport, TftpPort); + if(write(fd, &msg, sizeof(Udphdr) + n) < 0){ + err = "send read request: %r"; + goto out; + } + + notify(catch); + + last = 0; + while(!last){ + alarm(5000); + if((n = read(fd, &msg, sizeof(Udphdr) + sizeof(msg.buf)-1)) < 0){ + err = "receive response: %r"; + goto out; + } + alarm(0); + + n -= sizeof(Udphdr); + msg.buf[n] = 0; + switch(nhgets(msg.buf)){ + case Tftp_ERROR: + werrstr((char*)msg.buf+4); + err = "%r"; + goto out; + + case Tftp_DATA: + if(n < 4) + continue; + block = nhgets(msg.buf+2); + if((n -= 4) > 0){ + data = erealloc9p(data, ndata + n); + memcpy(data + ndata, msg.buf+4, n); + ndata += n; + +rloop: /* hanlde read request while downloading */ + if((r != nil) && (r->ifcall.type == Tread) && (r->ifcall.offset < ndata)){ + readbuf(r, data, ndata); + respond(r, nil); + r = nil; + } + if((r == nil) && (nbrecv(c, &r) == 1)){ + if(r == nil){ + chanfree(c); + c = nil; + goto out; + } + goto rloop; + } + } + if(n < Segsize) + last = 1; + hnputs(msg.buf, Tftp_ACK); + hnputs(msg.buf+2, block); + if(write(fd, &msg, sizeof(Udphdr) + 4) < 0){ + err = "send acknowledge: %r"; + goto out; + } + break; + } + } + +out: + alarm(0); + if(cfd >= 0) + close(cfd); + if(fd >= 0) + close(fd); + + if(c){ + while((r != nil) || (r = recvp(c))){ + if(err){ + char buf[ERRMAX]; + + snprint(buf, sizeof(buf), err); + respond(r, buf); + } else { + switch(r->ifcall.type){ + case Tread: + readbuf(r, data, ndata); + respond(r, nil); + break; + case Tstat: + tfilestat(r, f->path, ndata); + break; + default: + respond(r, "bug in fs"); + } + } + r = nil; + } + chanfree(c); + } + free(data); +} + +static void +fsattach(Req *r) +{ + Tfile *f; + + if(r->ifcall.aname && r->ifcall.aname[0]){ + uchar addr[IPaddrlen]; + + if(parseip(addr, r->ifcall.aname) < 0){ + respond(r, "bad ip specified"); + return; + } + f = tfileget(addr, "/"); + } else { + if(ipcmp(ipaddr, IPnoaddr) == 0){ + respond(r, "no ipaddr specified"); + return; + } + f = tfileget(ipaddr, "/"); + } + r->fid->aux = f; + r->fid->qid.type = QTDIR; + r->fid->qid.path = f->id<<1; + r->fid->qid.vers = 0; + r->ofcall.qid = r->fid->qid; + respond(r, nil); +} + +static char* +fswalk1(Fid *fid, char *name, Qid *qid) +{ + Tfile *f; + char *t; + + f = fid->aux; + t = smprint("%s/%s", f->path, name); + f = tfileget(f->addr, cleanname(t)); + free(t); + tfileput(fid->aux); fid->aux = f; + fid->qid.type = QTDIR; + fid->qid.path = f->id<<1; + + /* hack: + * a dot in the path means the path element is not + * a directory. to force download of files containing + * no dot, a trailing dot can be appended that will + * be stripped out in the tftp read request. + */ + if(strchr(f->path, '.') != nil){ + fid->qid.type = 0; + fid->qid.path |= Qdata; + } + + if(qid) + *qid = fid->qid; + return nil; +} + +static char* +fsclone(Fid *oldfid, Fid *newfid) +{ + Tfile *f; + + f = oldfid->aux; + incref(f); + newfid->aux = f; + return nil; +} + +static void +fsdestroyfid(Fid *fid) +{ + tfileput(fid->aux); + fid->aux = nil; +} + +static void +fsopen(Req *r) +{ + int m; + + m = r->ifcall.mode & 3; + if(m != OREAD && m != OEXEC){ + respond(r, "permission denied"); + return; + } + respond(r, nil); +} + +static void +dispatch(Req *r) +{ + Tfile *f; + + f = r->fid->aux; + if(f->c == nil){ + f->c = chancreate(sizeof(r), 0); + proccreate(download, f, 16*1024); + } + sendp(f->c, r); +} + +static void +fsread(Req *r) +{ + if(r->fid->qid.path & Qdata){ + dispatch(r); + } else { + respond(r, nil); + } +} + +static void +fsstat(Req *r) +{ + if(r->fid->qid.path & Qdata){ + dispatch(r); + } else { + tfilestat(r, ((Tfile*)r->fid->aux)->path, 0); + } +} + +Srv fs = +{ +.attach= fsattach, +.destroyfid= fsdestroyfid, +.walk1= fswalk1, +.clone= fsclone, +.open= fsopen, +.read= fsread, +.stat= fsstat, +}; + +void +usage(void) +{ + fprint(2, "usage: tftpfs [-D] [-s srvname] [-m mtpt] [ipaddr]\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char **argv) +{ + char *srvname = nil; + char *mtpt = nil; + + time0 = time(0); + ipmove(ipaddr, IPnoaddr); + + ARGBEGIN{ + case 'D': + chatty9p++; + break; + case 's': + srvname = EARGF(usage()); + break; + case 'm': + mtpt = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + switch(argc){ + case 0: + break; + case 1: + if(parseip(ipaddr, *argv) < 0) + usage(); + break; + default: + usage(); + } + + if(srvname==nil && mtpt==nil) + usage(); + + threadpostmountsrv(&fs, srvname, mtpt, MREPL|MCREATE); +}