plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

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 }