| /* |
| * 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); |
| } |
| |