Blob


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:
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
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 */
20 #include <u.h>
21 #include <libc.h>
22 #include <thread.h>
23 #include <9pclient.h>
24 #include <plumb.h>
25 #include "acme.h"
27 char *root = "/n/";
29 void
30 usage(void)
31 {
32 fprint(2, "usage: Netfiles\n");
33 threadexitsall("usage");
34 }
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);
43 enum {
44 STACK = 128*1024
45 };
47 enum {
48 Put,
49 Get,
50 Del,
51 Delete,
52 Debug,
53 XXX
54 };
56 char *cmds[] = {
57 "Put",
58 "Get",
59 "Del",
60 "Delete",
61 "Debug",
62 nil
63 };
65 char *debugstr[] = {
66 "off",
67 "minimal",
68 "chatty"
69 };
71 typedef struct Arg Arg;
72 struct Arg
73 {
74 char *file;
75 char *addr;
76 Channel *c;
77 };
79 Arg*
80 arg(char *file, char *addr, Channel *c)
81 {
82 Arg *a;
84 a = emalloc(sizeof *a);
85 a->file = estrdup(file);
86 a->addr = estrdup(addr);
87 a->c = c;
88 return a;
89 }
91 Win*
92 winbyid(int id)
93 {
94 Win *w;
96 for(w=windows; w; w=w->next)
97 if(w->id == id)
98 return w;
99 return nil;
102 /*
103 * return Win* of a window named name or name/
104 * assumes name is cleaned.
105 */
106 Win*
107 nametowin(char *name)
109 char *index, *p, *next;
110 int len, n;
111 Win *w;
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;
130 free(index);
131 return nil;
135 /*
136 * look for s in list
137 */
138 int
139 lookup(char *s, char **list)
141 int i;
143 for(i=0; list[i]; i++)
144 if(strcmp(list[i], s) == 0)
145 return i;
146 return -1;
149 /*
150 * move to top of file
151 */
152 void
153 wintop(Win *w)
155 winaddr(w, "#0");
156 winctl(w, "dot=addr");
157 winctl(w, "show");
160 int
161 isdot(Win *w, uint xq0, uint xq1)
163 uint q0, q1;
165 winctl(w, "addr=dot");
166 q0 = winreadaddr(w, &q1);
167 return xq0==q0 && xq1==q1;
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)
176 uint q0, q1;
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");
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);
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");
196 /*
197 * handle a plumbing message
198 */
199 void
200 doplumb(void *vm)
202 char *addr;
203 Plumbmsg *m;
204 Win *w;
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;
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);
225 /*
226 * dispatch messages from the plumber
227 */
228 void
229 plumbthread(void *v)
231 CFid *fid;
232 Plumbmsg *m;
234 threadsetname("plumbthread");
235 fid = plumbopenfid("netfileedit", OREAD);
236 if(fid == nil){
237 fprint(2, "cannot open plumb/netfileedit: %r\n");
238 return;
240 while((m = plumbrecvfid(fid)) != nil)
241 threadcreate(doplumb, m, STACK);
242 fsclose(fid);
245 /*
246 * parse /n/system/path
247 */
248 int
249 parsename(char *name, char **server, char **path)
251 char *p, *nul;
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;
264 p = name+3;
265 if(p[0] == 0){
266 free(*path);
267 *server = *path = nil;
268 if(nul)
269 *nul = '/';
270 return -1;
272 *server = estrdup(p);
273 if(nul)
274 *nul = '/';
275 return 0;
278 /*
279 * shell out to find the type of a given file
280 */
281 char*
282 filestat(char *server, char *path)
284 char *type;
285 static struct {
286 char *server;
287 char *path;
288 char *type;
289 } cache;
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;
298 type = sysrun(2, "9 netfilestat %q %q", server, path);
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);
307 cprint("9 netfilestat %q %q => %s\n", server, path, type);
308 return type;
311 /*
312 * manage a single window
313 */
314 void
315 filethread(void *v)
317 char *arg, *name, *p, *server, *path, *type;
318 Arg *a;
319 Channel *c;
320 Event *e;
321 Win *w;
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);
330 goto caseGet;
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;
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;
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");
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;
387 free(a->file);
388 free(a->addr);
389 free(a);
390 a = nil;
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;
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");
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;
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;
433 winfree(w);
436 /*
437 * handle a button 3 click
438 */
439 int
440 do3(Win *w, char *text)
442 char *addr, *name, *type, *server, *path, *p, *q;
443 static char lastfail[1000];
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;
454 name = emalloc(strlen(p)+1+strlen(text)+1);
455 strcpy(name, p);
456 if(text[0] != ':')
457 strcat(name, "/");
458 strcat(name, text);
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;
470 if(parsename(name, &server, &path) < 0){
471 cprint("b3 \t=> parsename failed\n");
472 free(name);
473 return -1;
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");
489 winctl(w, "show");
490 free(name);
491 free(type);
492 return 0;
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;
505 Win*
506 mkwin(char *name)
508 Arg *a;
509 Channel *c;
510 Win *w;
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;
520 void
521 loopthread(void *v)
523 QLock lk;
525 threadsetname("loopthread");
526 qlock(&lk);
527 qlock(&lk);
530 void
531 threadmain(int argc, char **argv)
533 ARGBEGIN{
534 case '9':
535 chatty9pclient = 1;
536 break;
537 case 'D':
538 debug = 1;
539 break;
540 default:
541 usage();
542 }ARGEND
544 if(argc)
545 usage();
547 cprint("netfiles starting\n");
549 threadnotify(nil, 0); /* set up correct default handlers */
551 fmtinstall('E', eventfmt);
552 doquote = needsrcquote;
553 quotefmtinstall();
555 twaitinit();
556 threadcreate(plumbthread, nil, STACK);
557 threadcreate(loopthread, nil, STACK);
558 threadexits(nil);