URI:
       tmain.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tmain.c (9983B)
       ---
            1 /*
            2  * Remote file system editing client.
            3  * Only talks to acme - external programs do all the hard work.
            4  *
            5  * If you add a plumbing rule:
            6 
            7 # /n/ paths go to simulator in acme
            8 kind is text
            9 data matches '[a-zA-Z0-9_\-./]+('$addr')?'
           10 data matches '(/n/[a-zA-Z0-9_\-./]+)('$addr')?'
           11 plumb to netfileedit
           12 plumb client Netfiles
           13 
           14  * then plumbed paths starting with /n/ will find their way here.
           15  *
           16  * Perhaps on startup should look for windows named /n/ and attach to them?
           17  * Or might that be too aggressive?
           18  */
           19 
           20 #include <u.h>
           21 #include <libc.h>
           22 #include <thread.h>
           23 #include <9pclient.h>
           24 #include <plumb.h>
           25 #include "acme.h"
           26 
           27 char *root = "/n/";
           28 
           29 void
           30 usage(void)
           31 {
           32         fprint(2, "usage: Netfiles\n");
           33         threadexitsall("usage");
           34 }
           35 
           36 extern int chatty9pclient;
           37 int debug;
           38 #define dprint if(debug>1)print
           39 #define cprint if(debug)print
           40 Win *mkwin(char*);
           41 int do3(Win *w, char *arg);
           42 
           43 enum {
           44         STACK = 128*1024
           45 };
           46 
           47 enum {
           48         Put,
           49         Get,
           50         Del,
           51         Delete,
           52         Debug,
           53         XXX
           54 };
           55 
           56 char *cmds[] = {
           57         "Put",
           58         "Get",
           59         "Del",
           60         "Delete",
           61         "Debug",
           62         nil
           63 };
           64 
           65 char *debugstr[] = {
           66         "off",
           67         "minimal",
           68         "chatty"
           69 };
           70 
           71 typedef struct Arg Arg;
           72 struct Arg
           73 {
           74         char *file;
           75         char *addr;
           76         Channel *c;
           77 };
           78 
           79 Arg*
           80 arg(char *file, char *addr, Channel *c)
           81 {
           82         Arg *a;
           83 
           84         a = emalloc(sizeof *a);
           85         a->file = estrdup(file);
           86         a->addr = estrdup(addr);
           87         a->c = c;
           88         return a;
           89 }
           90 
           91 Win*
           92 winbyid(int id)
           93 {
           94         Win *w;
           95 
           96         for(w=windows; w; w=w->next)
           97                 if(w->id == id)
           98                         return w;
           99         return nil;
          100 }
          101 
          102 /*
          103  * return Win* of a window named name or name/
          104  * assumes name is cleaned.
          105  */
          106 Win*
          107 nametowin(char *name)
          108 {
          109         char *index, *p, *next;
          110         int len, n;
          111         Win *w;
          112 
          113         index = winindex();
          114         len = strlen(name);
          115         for(p=index; p && *p; p=next){
          116                 if((next = strchr(p, '\n')) != nil)
          117                         *next++ = 0;
          118                 if(strlen(p) <= 5*12)
          119                         continue;
          120                 if(memcmp(p+5*12, name, len)!=0)
          121                         continue;
          122                 if(p[5*12+len]!=' ' && (p[5*12+len]!='/' || p[5*12+len+1]!=' '))
          123                         continue;
          124                 n = atoi(p);
          125                 if((w = winbyid(n)) != nil){
          126                         free(index);
          127                         return w;
          128                 }
          129         }
          130         free(index);
          131         return nil;
          132 }
          133 
          134 
          135 /*
          136  * look for s in list
          137  */
          138 int
          139 lookup(char *s, char **list)
          140 {
          141         int i;
          142 
          143         for(i=0; list[i]; i++)
          144                 if(strcmp(list[i], s) == 0)
          145                         return i;
          146         return -1;
          147 }
          148 
          149 /*
          150  * move to top of file
          151  */
          152 void
          153 wintop(Win *w)
          154 {
          155         winaddr(w, "#0");
          156         winctl(w, "dot=addr");
          157         winctl(w, "show");
          158 }
          159 
          160 int
          161 isdot(Win *w, uint xq0, uint xq1)
          162 {
          163         uint q0, q1;
          164 
          165         winctl(w, "addr=dot");
          166         q0 = winreadaddr(w, &q1);
          167         return xq0==q0 && xq1==q1;
          168 }
          169 
          170 /*
          171  * Expand the click further than acme usually does -- all non-white space is okay.
          172  */
          173 char*
          174 expandarg(Win *w, Event *e)
          175 {
          176         uint q0, q1;
          177 
          178         if(e->c2 == 'l')        /* in tag - no choice but to accept acme's expansion */
          179                 return estrdup(e->text);
          180         winaddr(w, ",");
          181         winctl(w, "addr=dot");
          182 
          183         q0 = winreadaddr(w, &q1);
          184         cprint("acme expanded %d-%d into %d-%d (dot %d-%d)\n",
          185                 e->oq0, e->oq1, e->q0, e->q1, q0, q1);
          186 
          187         if(e->oq0 == e->oq1 && e->q0 != e->q1 && !isdot(w, e->q0, e->q1)){
          188                 winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1);
          189                 q0 = winreadaddr(w, &q1);
          190                 cprint("\tre-expand to %d-%d\n", q0, q1);
          191         }else
          192                 winaddr(w, "#%ud,#%ud", e->q0, e->q1);
          193         return winmread(w, "xdata");
          194 }
          195 
          196 /*
          197  * handle a plumbing message
          198  */
          199 void
          200 doplumb(void *vm)
          201 {
          202         char *addr;
          203         Plumbmsg *m;
          204         Win *w;
          205 
          206         m = vm;
          207         if(m->ndata >= 1024){
          208                 fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n",
          209                         m->ndata, m->data);
          210                 plumbfree(m);
          211                 return;
          212         }
          213 
          214         addr = plumblookup(m->attr, "addr");
          215         w = nametowin(m->data);
          216         if(w == nil)
          217                 w = mkwin(m->data);
          218         winaddr(w, "%s", addr);
          219         winctl(w, "dot=addr");
          220         winctl(w, "show");
          221 /*        windecref(w); */
          222         plumbfree(m);
          223 }
          224 
          225 /*
          226  * dispatch messages from the plumber
          227  */
          228 void
          229 plumbthread(void *v)
          230 {
          231         CFid *fid;
          232         Plumbmsg *m;
          233 
          234         threadsetname("plumbthread");
          235         fid = plumbopenfid("netfileedit", OREAD);
          236         if(fid == nil){
          237                 fprint(2, "cannot open plumb/netfileedit: %r\n");
          238                 return;
          239         }
          240         while((m = plumbrecvfid(fid)) != nil)
          241                 threadcreate(doplumb, m, STACK);
          242         fsclose(fid);
          243 }
          244 
          245 /*
          246  * parse /n/system/path
          247  */
          248 int
          249 parsename(char *name, char **server, char **path)
          250 {
          251         char *p, *nul;
          252 
          253         cleanname(name);
          254         if(strncmp(name, "/n/", 3) != 0 && name[3] == 0)
          255                 return -1;
          256         nul = nil;
          257         if((p = strchr(name+3, '/')) == nil)
          258                 *path = estrdup("/");
          259         else{
          260                 *path = estrdup(p);
          261                 *p = 0;
          262                 nul = p;
          263         }
          264         p = name+3;
          265         if(p[0] == 0){
          266                 free(*path);
          267                 *server = *path = nil;
          268                 if(nul)
          269                         *nul = '/';
          270                 return -1;
          271         }
          272         *server = estrdup(p);
          273         if(nul)
          274                 *nul = '/';
          275         return 0;
          276 }
          277 
          278 /*
          279  * shell out to find the type of a given file
          280  */
          281 char*
          282 filestat(char *server, char *path)
          283 {
          284         char *type;
          285         static struct {
          286                 char *server;
          287                 char *path;
          288                 char *type;
          289         } cache;
          290 
          291         if(cache.server && strcmp(server, cache.server) == 0)
          292         if(cache.path && strcmp(path, cache.path) == 0){
          293                 type = estrdup(cache.type);
          294                 cprint("9 netfilestat %q %q => %s (cached)\n", server, path, type);
          295                 return type;
          296         }
          297 
          298         type = sysrun(2, "9 netfilestat %q %q", server, path);
          299 
          300         free(cache.server);
          301         free(cache.path);
          302         free(cache.type);
          303         cache.server = estrdup(server);
          304         cache.path = estrdup(path);
          305         cache.type = estrdup(type);
          306 
          307         cprint("9 netfilestat %q %q => %s\n", server, path, type);
          308         return type;
          309 }
          310 
          311 /*
          312  * manage a single window
          313  */
          314 void
          315 filethread(void *v)
          316 {
          317         char *arg, *name, *p, *server, *path, *type;
          318         Arg *a;
          319         Channel *c;
          320         Event *e;
          321         Win *w;
          322 
          323         a = v;
          324         threadsetname("file %s", a->file);
          325         w = newwin();
          326         winname(w, a->file);
          327         winprint(w, "tag", "Get Put Look ");
          328         c = wineventchan(w);
          329 
          330         goto caseGet;
          331 
          332         while((e=recvp(c)) != nil){
          333                 if(e->c1!='K')
          334                         dprint("acme %E\n", e);
          335                 if(e->c1=='M')
          336                 switch(e->c2){
          337                 case 'x':
          338                 case 'X':
          339                         switch(lookup(e->text, cmds)){
          340                         caseGet:
          341                         case Get:
          342                                 server = nil;
          343                                 path = nil;
          344                                 if(parsename(name=wingetname(w), &server, &path) < 0){
          345                                         fprint(2, "Netfiles: bad name %s\n", name);
          346                                         goto out;
          347                                 }
          348                                 type = filestat(server, path);
          349                                 if(type == nil)
          350                                         type = estrdup("");
          351                                 if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
          352                                         winaddr(w, ",");
          353                                         winprint(w, "data", "[reading...]");
          354                                         winaddr(w, ",");
          355                                         cprint("9 netfileget %s%q %q\n",
          356                                                 strcmp(type, "file") == 0 ? "" : "-d", server, path);
          357                                         if(strcmp(type, "file")==0)
          358                                                 twait(pipetowin(w, "data", 2, "9 netfileget %q %q", server, path));
          359                                         else
          360                                                 twait(pipetowin(w, "data", 2, "9 netfileget -d %q %q | winid=%d mc", server, path, w->id));
          361                                         cleanname(name);
          362                                         if(strcmp(type, "directory")==0){
          363                                                 p = name+strlen(name);
          364                                                 if(p[-1] != '/'){
          365                                                         p[0] = '/';
          366                                                         p[1] = 0;
          367                                                 }
          368                                         }
          369                                         winname(w, name);
          370                                         wintop(w);
          371                                         winctl(w, "clean");
          372                                         if(a && a->addr){
          373                                                 winaddr(w, "%s", a->addr);
          374                                                 winctl(w, "dot=addr");
          375                                                 winctl(w, "show");
          376                                         }
          377                                 }
          378                                 free(type);
          379                         out:
          380                                 free(server);
          381                                 free(path);
          382                                 if(a){
          383                                         if(a->c){
          384                                                 sendp(a->c, w);
          385                                                 a->c = nil;
          386                                         }
          387                                         free(a->file);
          388                                         free(a->addr);
          389                                         free(a);
          390                                         a = nil;
          391                                 }
          392                                 break;
          393                         case Put:
          394                                 server = nil;
          395                                 path = nil;
          396                                 if(parsename(name=wingetname(w), &server, &path) < 0){
          397                                         fprint(2, "Netfiles: bad name %s\n", name);
          398                                         goto out;
          399                                 }
          400                                 cprint("9 netfileput %q %q\n", server, path);
          401                                 if(twait(pipewinto(w, "body", 2, "9 netfileput %q %q", server, path)) >= 0){
          402                                         cleanname(name);
          403                                         winname(w, name);
          404                                         winctl(w, "clean");
          405                                 }
          406                                 free(server);
          407                                 free(path);
          408                                 break;
          409                         case Del:
          410                                 winctl(w, "del");
          411                                 break;
          412                         case Delete:
          413                                 winctl(w, "delete");
          414                                 break;
          415                         case Debug:
          416                                 debug = (debug+1)%3;
          417                                 print("Netfiles debug %s\n", debugstr[debug]);
          418                                 break;
          419                         default:
          420                                 winwriteevent(w, e);
          421                                 break;
          422                         }
          423                         break;
          424                 case 'l':
          425                 case 'L':
          426                         arg = expandarg(w, e);
          427                         if(arg!=nil && do3(w, arg) < 0)
          428                                 winwriteevent(w, e);
          429                         free(arg);
          430                         break;
          431                 }
          432         }
          433         winfree(w);
          434 }
          435 
          436 /*
          437  * handle a button 3 click
          438  */
          439 int
          440 do3(Win *w, char *text)
          441 {
          442         char *addr, *name, *type, *server, *path, *p, *q;
          443         static char lastfail[1000];
          444 
          445         if(text[0] == '/'){
          446                 p = nil;
          447                 name = estrdup(text);
          448         }else{
          449                 p = wingetname(w);
          450                 if(text[0] != ':'){
          451                         q = strrchr(p, '/');
          452                         *(q+1) = 0;
          453                 }
          454                 name = emalloc(strlen(p)+1+strlen(text)+1);
          455                 strcpy(name, p);
          456                 if(text[0] != ':')
          457                         strcat(name, "/");
          458                 strcat(name, text);
          459         }
          460         cprint("button3 %s %s => %s\n", p, text, name);
          461         if((addr = strchr(name, ':')) != nil)
          462                 *addr++ = 0;
          463         cleanname(name);
          464         cprint("b3 \t=> name=%s addr=%s\n", name, addr);
          465         if(strcmp(name, lastfail) == 0){
          466                 cprint("b3 \t=> nonexistent (cached)\n");
          467                 free(name);
          468                 return -1;
          469         }
          470         if(parsename(name, &server, &path) < 0){
          471                 cprint("b3 \t=> parsename failed\n");
          472                 free(name);
          473                 return -1;
          474         }
          475         type = filestat(server, path);
          476         free(server);
          477         free(path);
          478         if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
          479                 w = nametowin(name);
          480                 if(w == nil){
          481                         w = mkwin(name);
          482                         cprint("b3 \t=> creating new window %d\n", w->id);
          483                 }else
          484                         cprint("b3 \t=> reusing window %d\n", w->id);
          485                 if(addr){
          486                         winaddr(w, "%s", addr);
          487                         winctl(w, "dot=addr");
          488                 }
          489                 winctl(w, "show");
          490                 free(name);
          491                 free(type);
          492                 return 0;
          493         }
          494         /*
          495          * remember last name that didn't exist so that
          496          * only the first right-click is slow when searching for text.
          497          */
          498         cprint("b3 caching %s => type %s\n", name, type);
          499         strecpy(lastfail, lastfail+sizeof lastfail, name);
          500         free(name);
          501         free(type);
          502         return -1;
          503 }
          504 
          505 Win*
          506 mkwin(char *name)
          507 {
          508         Arg *a;
          509         Channel *c;
          510         Win *w;
          511 
          512         c = chancreate(sizeof(void*), 0);
          513         a = arg(name, nil, c);
          514         threadcreate(filethread, a, STACK);
          515         w = recvp(c);
          516         chanfree(c);
          517         return w;
          518 }
          519 
          520 void
          521 loopthread(void *v)
          522 {
          523         QLock lk;
          524 
          525         threadsetname("loopthread");
          526         qlock(&lk);
          527         qlock(&lk);
          528 }
          529 
          530 void
          531 threadmain(int argc, char **argv)
          532 {
          533         ARGBEGIN{
          534         case '9':
          535                 chatty9pclient = 1;
          536                 break;
          537         case 'D':
          538                 debug = 1;
          539                 break;
          540         default:
          541                 usage();
          542         }ARGEND
          543 
          544         if(argc)
          545                 usage();
          546 
          547         cprint("netfiles starting\n");
          548 
          549         threadnotify(nil, 0);        /* set up correct default handlers */
          550 
          551         fmtinstall('E', eventfmt);
          552         doquote = needsrcquote;
          553         quotefmtinstall();
          554 
          555         twaitinit();
          556         threadcreate(plumbthread, nil, STACK);
          557         threadcreate(loopthread, nil, STACK);
          558         threadexits(nil);
          559 }