main.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 }