2 7c709434 2005-03-18 devnull * Remote file system editing client.
3 7c709434 2005-03-18 devnull * Only talks to acme - external programs do all the hard work.
5 7c709434 2005-03-18 devnull * If you add a plumbing rule:
7 7c709434 2005-03-18 devnull # /n/ paths go to simulator in acme
8 7c709434 2005-03-18 devnull kind is text
9 7c709434 2005-03-18 devnull data matches '[a-zA-Z0-9_\-./]+('$addr')?'
10 7c709434 2005-03-18 devnull data matches '(/n/[a-zA-Z0-9_\-./]+)('$addr')?'
11 7c709434 2005-03-18 devnull plumb to netfileedit
12 7c709434 2005-03-18 devnull plumb client Netfiles
14 7c709434 2005-03-18 devnull * then plumbed paths starting with /n/ will find their way here.
16 7c709434 2005-03-18 devnull * Perhaps on startup should look for windows named /n/ and attach to them?
17 7c709434 2005-03-18 devnull * Or might that be too aggressive?
20 7c709434 2005-03-18 devnull #include <u.h>
21 7c709434 2005-03-18 devnull #include <libc.h>
22 7c709434 2005-03-18 devnull #include <thread.h>
23 7c709434 2005-03-18 devnull #include <9pclient.h>
24 7c709434 2005-03-18 devnull #include <plumb.h>
25 7c709434 2005-03-18 devnull #include "acme.h"
27 7c709434 2005-03-18 devnull char *root = "/n/";
30 7c709434 2005-03-18 devnull usage(void)
32 7c709434 2005-03-18 devnull fprint(2, "usage: Netfiles\n");
33 7c709434 2005-03-18 devnull threadexitsall("usage");
36 7c709434 2005-03-18 devnull extern int chatty9pclient;
37 7c709434 2005-03-18 devnull int debug;
38 590c5b34 2005-03-21 devnull #define dprint if(debug>1)print
39 590c5b34 2005-03-21 devnull #define cprint if(debug)print
40 7c709434 2005-03-18 devnull Win *mkwin(char*);
41 7c709434 2005-03-18 devnull int do3(Win *w, char *arg);
44 cbeb0b26 2006-04-01 devnull STACK = 128*1024
56 7c709434 2005-03-18 devnull char *cmds[] = {
60 7c709434 2005-03-18 devnull "Delete",
65 590c5b34 2005-03-21 devnull char *debugstr[] = {
67 590c5b34 2005-03-21 devnull "minimal",
71 7c709434 2005-03-18 devnull typedef struct Arg Arg;
72 7c709434 2005-03-18 devnull struct Arg
74 7c709434 2005-03-18 devnull char *file;
75 7c709434 2005-03-18 devnull char *addr;
76 7c709434 2005-03-18 devnull Channel *c;
80 7c709434 2005-03-18 devnull arg(char *file, char *addr, Channel *c)
84 7c709434 2005-03-18 devnull a = emalloc(sizeof *a);
85 7c709434 2005-03-18 devnull a->file = estrdup(file);
86 7c709434 2005-03-18 devnull a->addr = estrdup(addr);
87 7c709434 2005-03-18 devnull a->c = c;
88 7c709434 2005-03-18 devnull return a;
92 5a396071 2005-03-21 devnull winbyid(int id)
96 5a396071 2005-03-21 devnull for(w=windows; w; w=w->next)
97 5a396071 2005-03-21 devnull if(w->id == id)
98 5a396071 2005-03-21 devnull return w;
99 5a396071 2005-03-21 devnull return nil;
103 5a396071 2005-03-21 devnull * return Win* of a window named name or name/
104 7c709434 2005-03-18 devnull * assumes name is cleaned.
107 5a396071 2005-03-21 devnull nametowin(char *name)
109 7c709434 2005-03-18 devnull char *index, *p, *next;
110 7c709434 2005-03-18 devnull int len, n;
113 7c709434 2005-03-18 devnull index = winindex();
114 7c709434 2005-03-18 devnull len = strlen(name);
115 7c709434 2005-03-18 devnull for(p=index; p && *p; p=next){
116 7c709434 2005-03-18 devnull if((next = strchr(p, '\n')) != nil)
117 5a396071 2005-03-21 devnull *next++ = 0;
118 7c709434 2005-03-18 devnull if(strlen(p) <= 5*12)
119 7c709434 2005-03-18 devnull continue;
120 7c709434 2005-03-18 devnull if(memcmp(p+5*12, name, len)!=0)
121 7c709434 2005-03-18 devnull continue;
122 7c709434 2005-03-18 devnull if(p[5*12+len]!=' ' && (p[5*12+len]!='/' || p[5*12+len+1]!=' '))
123 7c709434 2005-03-18 devnull continue;
124 7c709434 2005-03-18 devnull n = atoi(p);
125 5a396071 2005-03-21 devnull if((w = winbyid(n)) != nil){
126 5a396071 2005-03-21 devnull free(index);
127 5a396071 2005-03-21 devnull return w;
130 7c709434 2005-03-18 devnull free(index);
131 7c709434 2005-03-18 devnull return nil;
136 7c709434 2005-03-18 devnull * look for s in list
139 7c709434 2005-03-18 devnull lookup(char *s, char **list)
143 7c709434 2005-03-18 devnull for(i=0; list[i]; i++)
144 7c709434 2005-03-18 devnull if(strcmp(list[i], s) == 0)
145 7c709434 2005-03-18 devnull return i;
146 7c709434 2005-03-18 devnull return -1;
150 7c709434 2005-03-18 devnull * move to top of file
153 7c709434 2005-03-18 devnull wintop(Win *w)
155 7c709434 2005-03-18 devnull winaddr(w, "#0");
156 7c709434 2005-03-18 devnull winctl(w, "dot=addr");
157 7c709434 2005-03-18 devnull winctl(w, "show");
161 b1e32ef3 2005-03-21 devnull isdot(Win *w, uint xq0, uint xq1)
163 b1e32ef3 2005-03-21 devnull uint q0, q1;
165 b1e32ef3 2005-03-21 devnull winctl(w, "addr=dot");
166 b1e32ef3 2005-03-21 devnull q0 = winreadaddr(w, &q1);
167 b1e32ef3 2005-03-21 devnull return xq0==q0 && xq1==q1;
171 7c709434 2005-03-18 devnull * Expand the click further than acme usually does -- all non-white space is okay.
174 7c709434 2005-03-18 devnull expandarg(Win *w, Event *e)
176 b8c9f317 2005-03-21 devnull uint q0, q1;
178 b1e32ef3 2005-03-21 devnull if(e->c2 == 'l') /* in tag - no choice but to accept acme's expansion */
179 7c709434 2005-03-18 devnull return estrdup(e->text);
180 b8c9f317 2005-03-21 devnull winaddr(w, ",");
181 b8c9f317 2005-03-21 devnull winctl(w, "addr=dot");
183 b8c9f317 2005-03-21 devnull q0 = winreadaddr(w, &q1);
184 b8c9f317 2005-03-21 devnull cprint("acme expanded %d-%d into %d-%d (dot %d-%d)\n",
185 b8c9f317 2005-03-21 devnull e->oq0, e->oq1, e->q0, e->q1, q0, q1);
187 b8c9f317 2005-03-21 devnull if(e->oq0 == e->oq1 && e->q0 != e->q1 && !isdot(w, e->q0, e->q1)){
188 7c709434 2005-03-18 devnull winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1);
189 b8c9f317 2005-03-21 devnull q0 = winreadaddr(w, &q1);
190 fa325e9b 2020-01-10 cross cprint("\tre-expand to %d-%d\n", q0, q1);
192 7c709434 2005-03-18 devnull winaddr(w, "#%ud,#%ud", e->q0, e->q1);
193 7c709434 2005-03-18 devnull return winmread(w, "xdata");
197 7c709434 2005-03-18 devnull * handle a plumbing message
200 7c709434 2005-03-18 devnull doplumb(void *vm)
202 7c709434 2005-03-18 devnull char *addr;
203 7c709434 2005-03-18 devnull Plumbmsg *m;
207 7c709434 2005-03-18 devnull if(m->ndata >= 1024){
208 7c709434 2005-03-18 devnull fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n",
209 7c709434 2005-03-18 devnull m->ndata, m->data);
210 7c709434 2005-03-18 devnull plumbfree(m);
214 7c709434 2005-03-18 devnull addr = plumblookup(m->attr, "addr");
215 7c709434 2005-03-18 devnull w = nametowin(m->data);
216 7c709434 2005-03-18 devnull if(w == nil)
217 7c709434 2005-03-18 devnull w = mkwin(m->data);
218 7c709434 2005-03-18 devnull winaddr(w, "%s", addr);
219 7c709434 2005-03-18 devnull winctl(w, "dot=addr");
220 7c709434 2005-03-18 devnull winctl(w, "show");
221 cbeb0b26 2006-04-01 devnull /* windecref(w); */
222 7c709434 2005-03-18 devnull plumbfree(m);
226 7c709434 2005-03-18 devnull * dispatch messages from the plumber
229 7c709434 2005-03-18 devnull plumbthread(void *v)
231 7c709434 2005-03-18 devnull CFid *fid;
232 7c709434 2005-03-18 devnull Plumbmsg *m;
234 78b5635d 2005-03-24 devnull threadsetname("plumbthread");
235 7c709434 2005-03-18 devnull fid = plumbopenfid("netfileedit", OREAD);
236 7c709434 2005-03-18 devnull if(fid == nil){
237 7c709434 2005-03-18 devnull fprint(2, "cannot open plumb/netfileedit: %r\n");
240 7c709434 2005-03-18 devnull while((m = plumbrecvfid(fid)) != nil)
241 7c709434 2005-03-18 devnull threadcreate(doplumb, m, STACK);
242 7c709434 2005-03-18 devnull fsclose(fid);
246 7c709434 2005-03-18 devnull * parse /n/system/path
249 7c709434 2005-03-18 devnull parsename(char *name, char **server, char **path)
251 7c709434 2005-03-18 devnull char *p, *nul;
253 7c709434 2005-03-18 devnull cleanname(name);
254 7c709434 2005-03-18 devnull if(strncmp(name, "/n/", 3) != 0 && name[3] == 0)
255 7c709434 2005-03-18 devnull return -1;
256 7c709434 2005-03-18 devnull nul = nil;
257 7c709434 2005-03-18 devnull if((p = strchr(name+3, '/')) == nil)
258 7c709434 2005-03-18 devnull *path = estrdup("/");
260 7c709434 2005-03-18 devnull *path = estrdup(p);
262 7c709434 2005-03-18 devnull nul = p;
264 7c709434 2005-03-18 devnull p = name+3;
265 7c709434 2005-03-18 devnull if(p[0] == 0){
266 7c709434 2005-03-18 devnull free(*path);
267 7c709434 2005-03-18 devnull *server = *path = nil;
269 7c709434 2005-03-18 devnull *nul = '/';
270 7c709434 2005-03-18 devnull return -1;
272 7c709434 2005-03-18 devnull *server = estrdup(p);
274 7c709434 2005-03-18 devnull *nul = '/';
275 7c709434 2005-03-18 devnull return 0;
279 7c709434 2005-03-18 devnull * shell out to find the type of a given file
282 7c709434 2005-03-18 devnull filestat(char *server, char *path)
284 590c5b34 2005-03-21 devnull char *type;
285 590c5b34 2005-03-21 devnull static struct {
286 590c5b34 2005-03-21 devnull char *server;
287 590c5b34 2005-03-21 devnull char *path;
288 590c5b34 2005-03-21 devnull char *type;
289 590c5b34 2005-03-21 devnull } cache;
291 590c5b34 2005-03-21 devnull if(cache.server && strcmp(server, cache.server) == 0)
292 590c5b34 2005-03-21 devnull if(cache.path && strcmp(path, cache.path) == 0){
293 590c5b34 2005-03-21 devnull type = estrdup(cache.type);
294 590c5b34 2005-03-21 devnull cprint("9 netfilestat %q %q => %s (cached)\n", server, path, type);
295 590c5b34 2005-03-21 devnull return type;
298 590c5b34 2005-03-21 devnull type = sysrun(2, "9 netfilestat %q %q", server, path);
300 590c5b34 2005-03-21 devnull free(cache.server);
301 590c5b34 2005-03-21 devnull free(cache.path);
302 590c5b34 2005-03-21 devnull free(cache.type);
303 590c5b34 2005-03-21 devnull cache.server = estrdup(server);
304 590c5b34 2005-03-21 devnull cache.path = estrdup(path);
305 590c5b34 2005-03-21 devnull cache.type = estrdup(type);
307 590c5b34 2005-03-21 devnull cprint("9 netfilestat %q %q => %s\n", server, path, type);
308 590c5b34 2005-03-21 devnull return type;
312 7c709434 2005-03-18 devnull * manage a single window
315 7c709434 2005-03-18 devnull filethread(void *v)
317 7c709434 2005-03-18 devnull char *arg, *name, *p, *server, *path, *type;
319 7c709434 2005-03-18 devnull Channel *c;
320 7c709434 2005-03-18 devnull Event *e;
324 7c709434 2005-03-18 devnull threadsetname("file %s", a->file);
325 7c709434 2005-03-18 devnull w = newwin();
326 7c709434 2005-03-18 devnull winname(w, a->file);
327 7c709434 2005-03-18 devnull winprint(w, "tag", "Get Put Look ");
328 7c709434 2005-03-18 devnull c = wineventchan(w);
330 7c709434 2005-03-18 devnull goto caseGet;
332 7c709434 2005-03-18 devnull while((e=recvp(c)) != nil){
333 7c709434 2005-03-18 devnull if(e->c1!='K')
334 7c709434 2005-03-18 devnull dprint("acme %E\n", e);
335 7c709434 2005-03-18 devnull if(e->c1=='M')
336 7c709434 2005-03-18 devnull switch(e->c2){
337 7c709434 2005-03-18 devnull case 'x':
338 7c709434 2005-03-18 devnull case 'X':
339 7c709434 2005-03-18 devnull switch(lookup(e->text, cmds)){
340 7c709434 2005-03-18 devnull caseGet:
341 7c709434 2005-03-18 devnull case Get:
342 7c709434 2005-03-18 devnull server = nil;
343 7c709434 2005-03-18 devnull path = nil;
344 7c709434 2005-03-18 devnull if(parsename(name=wingetname(w), &server, &path) < 0){
345 7c709434 2005-03-18 devnull fprint(2, "Netfiles: bad name %s\n", name);
346 7c709434 2005-03-18 devnull goto out;
348 7c709434 2005-03-18 devnull type = filestat(server, path);
349 7c709434 2005-03-18 devnull if(type == nil)
350 7c709434 2005-03-18 devnull type = estrdup("");
351 7c709434 2005-03-18 devnull if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
352 7c709434 2005-03-18 devnull winaddr(w, ",");
353 7c709434 2005-03-18 devnull winprint(w, "data", "[reading...]");
354 7c709434 2005-03-18 devnull winaddr(w, ",");
355 fa325e9b 2020-01-10 cross cprint("9 netfileget %s%q %q\n",
356 590c5b34 2005-03-21 devnull strcmp(type, "file") == 0 ? "" : "-d", server, path);
357 7c709434 2005-03-18 devnull if(strcmp(type, "file")==0)
358 b6364661 2005-03-18 devnull twait(pipetowin(w, "data", 2, "9 netfileget %q %q", server, path));
360 b6364661 2005-03-18 devnull twait(pipetowin(w, "data", 2, "9 netfileget -d %q %q | winid=%d mc", server, path, w->id));
361 7c709434 2005-03-18 devnull cleanname(name);
362 7c709434 2005-03-18 devnull if(strcmp(type, "directory")==0){
363 7c709434 2005-03-18 devnull p = name+strlen(name);
364 7c709434 2005-03-18 devnull if(p[-1] != '/'){
365 7c709434 2005-03-18 devnull p[0] = '/';
366 7c709434 2005-03-18 devnull p[1] = 0;
369 7c709434 2005-03-18 devnull winname(w, name);
370 7c709434 2005-03-18 devnull wintop(w);
371 7c709434 2005-03-18 devnull winctl(w, "clean");
372 7c709434 2005-03-18 devnull if(a && a->addr){
373 7c709434 2005-03-18 devnull winaddr(w, "%s", a->addr);
374 7c709434 2005-03-18 devnull winctl(w, "dot=addr");
375 7c709434 2005-03-18 devnull winctl(w, "show");
378 7c709434 2005-03-18 devnull free(type);
380 7c709434 2005-03-18 devnull free(server);
381 7c709434 2005-03-18 devnull free(path);
383 7c709434 2005-03-18 devnull if(a->c){
384 7c709434 2005-03-18 devnull sendp(a->c, w);
385 7c709434 2005-03-18 devnull a->c = nil;
387 7c709434 2005-03-18 devnull free(a->file);
388 7c709434 2005-03-18 devnull free(a->addr);
389 7c709434 2005-03-18 devnull free(a);
390 7c709434 2005-03-18 devnull a = nil;
393 7c709434 2005-03-18 devnull case Put:
394 7c709434 2005-03-18 devnull server = nil;
395 7c709434 2005-03-18 devnull path = nil;
396 7c709434 2005-03-18 devnull if(parsename(name=wingetname(w), &server, &path) < 0){
397 7c709434 2005-03-18 devnull fprint(2, "Netfiles: bad name %s\n", name);
398 7c709434 2005-03-18 devnull goto out;
400 590c5b34 2005-03-21 devnull cprint("9 netfileput %q %q\n", server, path);
401 b6364661 2005-03-18 devnull if(twait(pipewinto(w, "body", 2, "9 netfileput %q %q", server, path)) >= 0){
402 7c709434 2005-03-18 devnull cleanname(name);
403 7c709434 2005-03-18 devnull winname(w, name);
404 7c709434 2005-03-18 devnull winctl(w, "clean");
406 7c709434 2005-03-18 devnull free(server);
407 7c709434 2005-03-18 devnull free(path);
409 7c709434 2005-03-18 devnull case Del:
410 7c709434 2005-03-18 devnull winctl(w, "del");
412 7c709434 2005-03-18 devnull case Delete:
413 7c709434 2005-03-18 devnull winctl(w, "delete");
415 7c709434 2005-03-18 devnull case Debug:
416 590c5b34 2005-03-21 devnull debug = (debug+1)%3;
417 590c5b34 2005-03-21 devnull print("Netfiles debug %s\n", debugstr[debug]);
419 7c709434 2005-03-18 devnull default:
420 7c709434 2005-03-18 devnull winwriteevent(w, e);
424 7c709434 2005-03-18 devnull case 'l':
425 7c709434 2005-03-18 devnull case 'L':
426 7c709434 2005-03-18 devnull arg = expandarg(w, e);
427 7c709434 2005-03-18 devnull if(arg!=nil && do3(w, arg) < 0)
428 7c709434 2005-03-18 devnull winwriteevent(w, e);
429 7c709434 2005-03-18 devnull free(arg);
433 7c709434 2005-03-18 devnull winfree(w);
437 7c709434 2005-03-18 devnull * handle a button 3 click
440 7c709434 2005-03-18 devnull do3(Win *w, char *text)
442 7c709434 2005-03-18 devnull char *addr, *name, *type, *server, *path, *p, *q;
443 7c709434 2005-03-18 devnull static char lastfail[1000];
445 590c5b34 2005-03-21 devnull if(text[0] == '/'){
446 590c5b34 2005-03-21 devnull p = nil;
447 7c709434 2005-03-18 devnull name = estrdup(text);
449 7c709434 2005-03-18 devnull p = wingetname(w);
450 5a396071 2005-03-21 devnull if(text[0] != ':'){
451 5a396071 2005-03-21 devnull q = strrchr(p, '/');
452 5a396071 2005-03-21 devnull *(q+1) = 0;
454 7c709434 2005-03-18 devnull name = emalloc(strlen(p)+1+strlen(text)+1);
455 7c709434 2005-03-18 devnull strcpy(name, p);
456 5a396071 2005-03-21 devnull if(text[0] != ':')
457 5a396071 2005-03-21 devnull strcat(name, "/");
458 7c709434 2005-03-18 devnull strcat(name, text);
460 590c5b34 2005-03-21 devnull cprint("button3 %s %s => %s\n", p, text, name);
461 7c709434 2005-03-18 devnull if((addr = strchr(name, ':')) != nil)
462 7c709434 2005-03-18 devnull *addr++ = 0;
463 7c709434 2005-03-18 devnull cleanname(name);
464 590c5b34 2005-03-21 devnull cprint("b3 \t=> name=%s addr=%s\n", name, addr);
465 7c709434 2005-03-18 devnull if(strcmp(name, lastfail) == 0){
466 65fb6fb7 2005-03-24 devnull cprint("b3 \t=> nonexistent (cached)\n");
467 7c709434 2005-03-18 devnull free(name);
468 7c709434 2005-03-18 devnull return -1;
470 7c709434 2005-03-18 devnull if(parsename(name, &server, &path) < 0){
471 590c5b34 2005-03-21 devnull cprint("b3 \t=> parsename failed\n");
472 7c709434 2005-03-18 devnull free(name);
473 7c709434 2005-03-18 devnull return -1;
475 7c709434 2005-03-18 devnull type = filestat(server, path);
476 7c709434 2005-03-18 devnull free(server);
477 7c709434 2005-03-18 devnull free(path);
478 7c709434 2005-03-18 devnull if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
479 7c709434 2005-03-18 devnull w = nametowin(name);
480 590c5b34 2005-03-21 devnull if(w == nil){
481 7c709434 2005-03-18 devnull w = mkwin(name);
482 590c5b34 2005-03-21 devnull cprint("b3 \t=> creating new window %d\n", w->id);
484 590c5b34 2005-03-21 devnull cprint("b3 \t=> reusing window %d\n", w->id);
485 590c5b34 2005-03-21 devnull if(addr){
486 590c5b34 2005-03-21 devnull winaddr(w, "%s", addr);
487 590c5b34 2005-03-21 devnull winctl(w, "dot=addr");
489 7c709434 2005-03-18 devnull winctl(w, "show");
490 7c709434 2005-03-18 devnull free(name);
491 7c709434 2005-03-18 devnull free(type);
492 7c709434 2005-03-18 devnull return 0;
495 7c709434 2005-03-18 devnull * remember last name that didn't exist so that
496 7c709434 2005-03-18 devnull * only the first right-click is slow when searching for text.
498 590c5b34 2005-03-21 devnull cprint("b3 caching %s => type %s\n", name, type);
499 7c709434 2005-03-18 devnull strecpy(lastfail, lastfail+sizeof lastfail, name);
500 7c709434 2005-03-18 devnull free(name);
501 590c5b34 2005-03-21 devnull free(type);
502 7c709434 2005-03-18 devnull return -1;
506 7c709434 2005-03-18 devnull mkwin(char *name)
509 7c709434 2005-03-18 devnull Channel *c;
512 7c709434 2005-03-18 devnull c = chancreate(sizeof(void*), 0);
513 7c709434 2005-03-18 devnull a = arg(name, nil, c);
514 7c709434 2005-03-18 devnull threadcreate(filethread, a, STACK);
515 7c709434 2005-03-18 devnull w = recvp(c);
516 7c709434 2005-03-18 devnull chanfree(c);
517 7c709434 2005-03-18 devnull return w;
521 7c709434 2005-03-18 devnull loopthread(void *v)
523 7c709434 2005-03-18 devnull QLock lk;
525 78b5635d 2005-03-24 devnull threadsetname("loopthread");
526 7c709434 2005-03-18 devnull qlock(&lk);
527 7c709434 2005-03-18 devnull qlock(&lk);
531 7c709434 2005-03-18 devnull threadmain(int argc, char **argv)
533 7c709434 2005-03-18 devnull ARGBEGIN{
534 7c709434 2005-03-18 devnull case '9':
535 7c709434 2005-03-18 devnull chatty9pclient = 1;
537 7c709434 2005-03-18 devnull case 'D':
538 7c709434 2005-03-18 devnull debug = 1;
540 7c709434 2005-03-18 devnull default:
541 7c709434 2005-03-18 devnull usage();
544 7c709434 2005-03-18 devnull if(argc)
545 7c709434 2005-03-18 devnull usage();
547 78b5635d 2005-03-24 devnull cprint("netfiles starting\n");
549 7c709434 2005-03-18 devnull threadnotify(nil, 0); /* set up correct default handlers */
551 7c709434 2005-03-18 devnull fmtinstall('E', eventfmt);
552 7c709434 2005-03-18 devnull doquote = needsrcquote;
553 7c709434 2005-03-18 devnull quotefmtinstall();
555 7c709434 2005-03-18 devnull twaitinit();
556 7c709434 2005-03-18 devnull threadcreate(plumbthread, nil, STACK);
557 7c709434 2005-03-18 devnull threadcreate(loopthread, nil, STACK);
558 7c709434 2005-03-18 devnull threadexits(nil);