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
usage(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*
arg(char * file,char * addr,Channel * c)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*
winbyid(int id)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*
nametowin(char * name)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
lookup(char * s,char ** list)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
wintop(Win * w)153 wintop(Win *w)
154 {
155 	winaddr(w, "#0");
156 	winctl(w, "dot=addr");
157 	winctl(w, "show");
158 }
159 
160 int
isdot(Win * w,uint xq0,uint xq1)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*
expandarg(Win * w,Event * e)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
doplumb(void * vm)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
plumbthread(void * v)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
parsename(char * name,char ** server,char ** path)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*
filestat(char * server,char * path)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
filethread(void * v)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
do3(Win * w,char * text)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*
mkwin(char * name)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
loopthread(void * v)521 loopthread(void *v)
522 {
523 	QLock lk;
524 
525 	threadsetname("loopthread");
526 	qlock(&lk);
527 	qlock(&lk);
528 }
529 
530 void
threadmain(int argc,char ** argv)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 }
560