|  | /* | 
|  | * Remote file system editing client. | 
|  | * Only talks to acme - external programs do all the hard work. | 
|  | * | 
|  | * If you add a plumbing rule: | 
|  |  | 
|  | # /n/ paths go to simulator in acme | 
|  | kind is text | 
|  | data matches '[a-zA-Z0-9_\-./]+('$addr')?' | 
|  | data matches '(/n/[a-zA-Z0-9_\-./]+)('$addr')?' | 
|  | plumb to netfileedit | 
|  | plumb client Netfiles | 
|  |  | 
|  | * then plumbed paths starting with /n/ will find their way here. | 
|  | * | 
|  | * Perhaps on startup should look for windows named /n/ and attach to them? | 
|  | * Or might that be too aggressive? | 
|  | */ | 
|  |  | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <thread.h> | 
|  | #include <9pclient.h> | 
|  | #include <plumb.h> | 
|  | #include "acme.h" | 
|  |  | 
|  | char *root = "/n/"; | 
|  |  | 
|  | void | 
|  | usage(void) | 
|  | { | 
|  | fprint(2, "usage: Netfiles\n"); | 
|  | threadexitsall("usage"); | 
|  | } | 
|  |  | 
|  | extern int chatty9pclient; | 
|  | int debug; | 
|  | #define dprint if(debug>1)print | 
|  | #define cprint if(debug)print | 
|  | Win *mkwin(char*); | 
|  | int do3(Win *w, char *arg); | 
|  |  | 
|  | enum { | 
|  | STACK = 128*1024 | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | Put, | 
|  | Get, | 
|  | Del, | 
|  | Delete, | 
|  | Debug, | 
|  | XXX | 
|  | }; | 
|  |  | 
|  | char *cmds[] = { | 
|  | "Put", | 
|  | "Get", | 
|  | "Del", | 
|  | "Delete", | 
|  | "Debug", | 
|  | nil | 
|  | }; | 
|  |  | 
|  | char *debugstr[] = { | 
|  | "off", | 
|  | "minimal", | 
|  | "chatty" | 
|  | }; | 
|  |  | 
|  | typedef struct Arg Arg; | 
|  | struct Arg | 
|  | { | 
|  | char *file; | 
|  | char *addr; | 
|  | Channel *c; | 
|  | }; | 
|  |  | 
|  | Arg* | 
|  | arg(char *file, char *addr, Channel *c) | 
|  | { | 
|  | Arg *a; | 
|  |  | 
|  | a = emalloc(sizeof *a); | 
|  | a->file = estrdup(file); | 
|  | a->addr = estrdup(addr); | 
|  | a->c = c; | 
|  | return a; | 
|  | } | 
|  |  | 
|  | Win* | 
|  | winbyid(int id) | 
|  | { | 
|  | Win *w; | 
|  |  | 
|  | for(w=windows; w; w=w->next) | 
|  | if(w->id == id) | 
|  | return w; | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * return Win* of a window named name or name/ | 
|  | * assumes name is cleaned. | 
|  | */ | 
|  | Win* | 
|  | nametowin(char *name) | 
|  | { | 
|  | char *index, *p, *next; | 
|  | int len, n; | 
|  | Win *w; | 
|  |  | 
|  | index = winindex(); | 
|  | len = strlen(name); | 
|  | for(p=index; p && *p; p=next){ | 
|  | if((next = strchr(p, '\n')) != nil) | 
|  | *next++ = 0; | 
|  | if(strlen(p) <= 5*12) | 
|  | continue; | 
|  | if(memcmp(p+5*12, name, len)!=0) | 
|  | continue; | 
|  | if(p[5*12+len]!=' ' && (p[5*12+len]!='/' || p[5*12+len+1]!=' ')) | 
|  | continue; | 
|  | n = atoi(p); | 
|  | if((w = winbyid(n)) != nil){ | 
|  | free(index); | 
|  | return w; | 
|  | } | 
|  | } | 
|  | free(index); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * look for s in list | 
|  | */ | 
|  | int | 
|  | lookup(char *s, char **list) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for(i=0; list[i]; i++) | 
|  | if(strcmp(list[i], s) == 0) | 
|  | return i; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * move to top of file | 
|  | */ | 
|  | void | 
|  | wintop(Win *w) | 
|  | { | 
|  | winaddr(w, "#0"); | 
|  | winctl(w, "dot=addr"); | 
|  | winctl(w, "show"); | 
|  | } | 
|  |  | 
|  | int | 
|  | isdot(Win *w, uint xq0, uint xq1) | 
|  | { | 
|  | uint q0, q1; | 
|  |  | 
|  | winctl(w, "addr=dot"); | 
|  | q0 = winreadaddr(w, &q1); | 
|  | return xq0==q0 && xq1==q1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Expand the click further than acme usually does -- all non-white space is okay. | 
|  | */ | 
|  | char* | 
|  | expandarg(Win *w, Event *e) | 
|  | { | 
|  | uint q0, q1; | 
|  |  | 
|  | if(e->c2 == 'l')	/* in tag - no choice but to accept acme's expansion */ | 
|  | return estrdup(e->text); | 
|  | winaddr(w, ","); | 
|  | winctl(w, "addr=dot"); | 
|  |  | 
|  | q0 = winreadaddr(w, &q1); | 
|  | cprint("acme expanded %d-%d into %d-%d (dot %d-%d)\n", | 
|  | e->oq0, e->oq1, e->q0, e->q1, q0, q1); | 
|  |  | 
|  | if(e->oq0 == e->oq1 && e->q0 != e->q1 && !isdot(w, e->q0, e->q1)){ | 
|  | winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1); | 
|  | q0 = winreadaddr(w, &q1); | 
|  | cprint("\tre-expand to %d-%d\n", q0, q1); | 
|  | }else | 
|  | winaddr(w, "#%ud,#%ud", e->q0, e->q1); | 
|  | return winmread(w, "xdata"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * handle a plumbing message | 
|  | */ | 
|  | void | 
|  | doplumb(void *vm) | 
|  | { | 
|  | char *addr; | 
|  | Plumbmsg *m; | 
|  | Win *w; | 
|  |  | 
|  | m = vm; | 
|  | if(m->ndata >= 1024){ | 
|  | fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", | 
|  | m->ndata, m->data); | 
|  | plumbfree(m); | 
|  | return; | 
|  | } | 
|  |  | 
|  | addr = plumblookup(m->attr, "addr"); | 
|  | w = nametowin(m->data); | 
|  | if(w == nil) | 
|  | w = mkwin(m->data); | 
|  | winaddr(w, "%s", addr); | 
|  | winctl(w, "dot=addr"); | 
|  | winctl(w, "show"); | 
|  | /*	windecref(w); */ | 
|  | plumbfree(m); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * dispatch messages from the plumber | 
|  | */ | 
|  | void | 
|  | plumbthread(void *v) | 
|  | { | 
|  | CFid *fid; | 
|  | Plumbmsg *m; | 
|  |  | 
|  | threadsetname("plumbthread"); | 
|  | fid = plumbopenfid("netfileedit", OREAD); | 
|  | if(fid == nil){ | 
|  | fprint(2, "cannot open plumb/netfileedit: %r\n"); | 
|  | return; | 
|  | } | 
|  | while((m = plumbrecvfid(fid)) != nil) | 
|  | threadcreate(doplumb, m, STACK); | 
|  | fsclose(fid); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * parse /n/system/path | 
|  | */ | 
|  | int | 
|  | parsename(char *name, char **server, char **path) | 
|  | { | 
|  | char *p, *nul; | 
|  |  | 
|  | cleanname(name); | 
|  | if(strncmp(name, "/n/", 3) != 0 && name[3] == 0) | 
|  | return -1; | 
|  | nul = nil; | 
|  | if((p = strchr(name+3, '/')) == nil) | 
|  | *path = estrdup("/"); | 
|  | else{ | 
|  | *path = estrdup(p); | 
|  | *p = 0; | 
|  | nul = p; | 
|  | } | 
|  | p = name+3; | 
|  | if(p[0] == 0){ | 
|  | free(*path); | 
|  | *server = *path = nil; | 
|  | if(nul) | 
|  | *nul = '/'; | 
|  | return -1; | 
|  | } | 
|  | *server = estrdup(p); | 
|  | if(nul) | 
|  | *nul = '/'; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * shell out to find the type of a given file | 
|  | */ | 
|  | char* | 
|  | filestat(char *server, char *path) | 
|  | { | 
|  | char *type; | 
|  | static struct { | 
|  | char *server; | 
|  | char *path; | 
|  | char *type; | 
|  | } cache; | 
|  |  | 
|  | if(cache.server && strcmp(server, cache.server) == 0) | 
|  | if(cache.path && strcmp(path, cache.path) == 0){ | 
|  | type = estrdup(cache.type); | 
|  | cprint("9 netfilestat %q %q => %s (cached)\n", server, path, type); | 
|  | return type; | 
|  | } | 
|  |  | 
|  | type = sysrun(2, "9 netfilestat %q %q", server, path); | 
|  |  | 
|  | free(cache.server); | 
|  | free(cache.path); | 
|  | free(cache.type); | 
|  | cache.server = estrdup(server); | 
|  | cache.path = estrdup(path); | 
|  | cache.type = estrdup(type); | 
|  |  | 
|  | cprint("9 netfilestat %q %q => %s\n", server, path, type); | 
|  | return type; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * manage a single window | 
|  | */ | 
|  | void | 
|  | filethread(void *v) | 
|  | { | 
|  | char *arg, *name, *p, *server, *path, *type; | 
|  | Arg *a; | 
|  | Channel *c; | 
|  | Event *e; | 
|  | Win *w; | 
|  |  | 
|  | a = v; | 
|  | threadsetname("file %s", a->file); | 
|  | w = newwin(); | 
|  | winname(w, a->file); | 
|  | winprint(w, "tag", "Get Put Look "); | 
|  | c = wineventchan(w); | 
|  |  | 
|  | goto caseGet; | 
|  |  | 
|  | while((e=recvp(c)) != nil){ | 
|  | if(e->c1!='K') | 
|  | dprint("acme %E\n", e); | 
|  | if(e->c1=='M') | 
|  | switch(e->c2){ | 
|  | case 'x': | 
|  | case 'X': | 
|  | switch(lookup(e->text, cmds)){ | 
|  | caseGet: | 
|  | case Get: | 
|  | server = nil; | 
|  | path = nil; | 
|  | if(parsename(name=wingetname(w), &server, &path) < 0){ | 
|  | fprint(2, "Netfiles: bad name %s\n", name); | 
|  | goto out; | 
|  | } | 
|  | type = filestat(server, path); | 
|  | if(type == nil) | 
|  | type = estrdup(""); | 
|  | if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){ | 
|  | winaddr(w, ","); | 
|  | winprint(w, "data", "[reading...]"); | 
|  | winaddr(w, ","); | 
|  | cprint("9 netfileget %s%q %q\n", | 
|  | strcmp(type, "file") == 0 ? "" : "-d", server, path); | 
|  | if(strcmp(type, "file")==0) | 
|  | twait(pipetowin(w, "data", 2, "9 netfileget %q %q", server, path)); | 
|  | else | 
|  | twait(pipetowin(w, "data", 2, "9 netfileget -d %q %q | winid=%d mc", server, path, w->id)); | 
|  | cleanname(name); | 
|  | if(strcmp(type, "directory")==0){ | 
|  | p = name+strlen(name); | 
|  | if(p[-1] != '/'){ | 
|  | p[0] = '/'; | 
|  | p[1] = 0; | 
|  | } | 
|  | } | 
|  | winname(w, name); | 
|  | wintop(w); | 
|  | winctl(w, "clean"); | 
|  | if(a && a->addr){ | 
|  | winaddr(w, "%s", a->addr); | 
|  | winctl(w, "dot=addr"); | 
|  | winctl(w, "show"); | 
|  | } | 
|  | } | 
|  | free(type); | 
|  | out: | 
|  | free(server); | 
|  | free(path); | 
|  | if(a){ | 
|  | if(a->c){ | 
|  | sendp(a->c, w); | 
|  | a->c = nil; | 
|  | } | 
|  | free(a->file); | 
|  | free(a->addr); | 
|  | free(a); | 
|  | a = nil; | 
|  | } | 
|  | break; | 
|  | case Put: | 
|  | server = nil; | 
|  | path = nil; | 
|  | if(parsename(name=wingetname(w), &server, &path) < 0){ | 
|  | fprint(2, "Netfiles: bad name %s\n", name); | 
|  | goto out; | 
|  | } | 
|  | cprint("9 netfileput %q %q\n", server, path); | 
|  | if(twait(pipewinto(w, "body", 2, "9 netfileput %q %q", server, path)) >= 0){ | 
|  | cleanname(name); | 
|  | winname(w, name); | 
|  | winctl(w, "clean"); | 
|  | } | 
|  | free(server); | 
|  | free(path); | 
|  | break; | 
|  | case Del: | 
|  | winctl(w, "del"); | 
|  | break; | 
|  | case Delete: | 
|  | winctl(w, "delete"); | 
|  | break; | 
|  | case Debug: | 
|  | debug = (debug+1)%3; | 
|  | print("Netfiles debug %s\n", debugstr[debug]); | 
|  | break; | 
|  | default: | 
|  | winwriteevent(w, e); | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case 'l': | 
|  | case 'L': | 
|  | arg = expandarg(w, e); | 
|  | if(arg!=nil && do3(w, arg) < 0) | 
|  | winwriteevent(w, e); | 
|  | free(arg); | 
|  | break; | 
|  | } | 
|  | } | 
|  | winfree(w); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * handle a button 3 click | 
|  | */ | 
|  | int | 
|  | do3(Win *w, char *text) | 
|  | { | 
|  | char *addr, *name, *type, *server, *path, *p, *q; | 
|  | static char lastfail[1000]; | 
|  |  | 
|  | if(text[0] == '/'){ | 
|  | p = nil; | 
|  | name = estrdup(text); | 
|  | }else{ | 
|  | p = wingetname(w); | 
|  | if(text[0] != ':'){ | 
|  | q = strrchr(p, '/'); | 
|  | *(q+1) = 0; | 
|  | } | 
|  | name = emalloc(strlen(p)+1+strlen(text)+1); | 
|  | strcpy(name, p); | 
|  | if(text[0] != ':') | 
|  | strcat(name, "/"); | 
|  | strcat(name, text); | 
|  | } | 
|  | cprint("button3 %s %s => %s\n", p, text, name); | 
|  | if((addr = strchr(name, ':')) != nil) | 
|  | *addr++ = 0; | 
|  | cleanname(name); | 
|  | cprint("b3 \t=> name=%s addr=%s\n", name, addr); | 
|  | if(strcmp(name, lastfail) == 0){ | 
|  | cprint("b3 \t=> nonexistent (cached)\n"); | 
|  | free(name); | 
|  | return -1; | 
|  | } | 
|  | if(parsename(name, &server, &path) < 0){ | 
|  | cprint("b3 \t=> parsename failed\n"); | 
|  | free(name); | 
|  | return -1; | 
|  | } | 
|  | type = filestat(server, path); | 
|  | free(server); | 
|  | free(path); | 
|  | if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){ | 
|  | w = nametowin(name); | 
|  | if(w == nil){ | 
|  | w = mkwin(name); | 
|  | cprint("b3 \t=> creating new window %d\n", w->id); | 
|  | }else | 
|  | cprint("b3 \t=> reusing window %d\n", w->id); | 
|  | if(addr){ | 
|  | winaddr(w, "%s", addr); | 
|  | winctl(w, "dot=addr"); | 
|  | } | 
|  | winctl(w, "show"); | 
|  | free(name); | 
|  | free(type); | 
|  | return 0; | 
|  | } | 
|  | /* | 
|  | * remember last name that didn't exist so that | 
|  | * only the first right-click is slow when searching for text. | 
|  | */ | 
|  | cprint("b3 caching %s => type %s\n", name, type); | 
|  | strecpy(lastfail, lastfail+sizeof lastfail, name); | 
|  | free(name); | 
|  | free(type); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | Win* | 
|  | mkwin(char *name) | 
|  | { | 
|  | Arg *a; | 
|  | Channel *c; | 
|  | Win *w; | 
|  |  | 
|  | c = chancreate(sizeof(void*), 0); | 
|  | a = arg(name, nil, c); | 
|  | threadcreate(filethread, a, STACK); | 
|  | w = recvp(c); | 
|  | chanfree(c); | 
|  | return w; | 
|  | } | 
|  |  | 
|  | void | 
|  | loopthread(void *v) | 
|  | { | 
|  | QLock lk; | 
|  |  | 
|  | threadsetname("loopthread"); | 
|  | qlock(&lk); | 
|  | qlock(&lk); | 
|  | } | 
|  |  | 
|  | void | 
|  | threadmain(int argc, char **argv) | 
|  | { | 
|  | ARGBEGIN{ | 
|  | case '9': | 
|  | chatty9pclient = 1; | 
|  | break; | 
|  | case 'D': | 
|  | debug = 1; | 
|  | break; | 
|  | default: | 
|  | usage(); | 
|  | }ARGEND | 
|  |  | 
|  | if(argc) | 
|  | usage(); | 
|  |  | 
|  | cprint("netfiles starting\n"); | 
|  |  | 
|  | threadnotify(nil, 0);	/* set up correct default handlers */ | 
|  |  | 
|  | fmtinstall('E', eventfmt); | 
|  | doquote = needsrcquote; | 
|  | quotefmtinstall(); | 
|  |  | 
|  | twaitinit(); | 
|  | threadcreate(plumbthread, nil, STACK); | 
|  | threadcreate(loopthread, nil, STACK); | 
|  | threadexits(nil); | 
|  | } | 
|  |  |