xref: /plan9-contrib/sys/src/cmd/rdbfs.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 /*
2  * Remote debugging file system
3  */
4 
5 #include <u.h>
6 #include <libc.h>
7 #include <auth.h>
8 #include <fcall.h>
9 #include <bio.h>
10 
11 /*
12  * Like the library routine, but does postnote to the group.
13  */
14 void
15 sysfatal(char *fmt, ...)
16 {
17 	char buf[1024];
18 	va_list arg;
19 
20 	va_start(arg, fmt);
21 	doprint(buf, buf+sizeof(buf), fmt, arg);
22 	va_end(arg);
23 	fprint(2, "%s: %s\n", argv0, buf);
24 	postnote(PNGROUP, getpid(), "die yankee pig dog");
25 	exits(buf);
26 }
27 
28 int dbg = 0;
29 #define DBG	if(dbg)fprint
30 
31 enum {
32 	NHASH = 4096,
33 	Readlen = 4,
34 	Pagequantum = 1024,
35 };
36 
37 /* caching memory pages: a lot of space to avoid serial communications */
38 Lock pglock;
39 typedef struct	Page	Page;
40 struct Page {	/* cached memory contents */
41 	Page *link;
42 	ulong len;
43 	ulong addr;
44 	uchar val[Readlen];
45 };
46 
47 Page *pgtab[NHASH];
48 
49 Page *freelist;
50 
51 /* called with pglock locked */
52 Page*
53 newpg(void)
54 {
55 	int i;
56 	Page *p, *q;
57 
58 	if(freelist == nil){
59 		p = malloc(sizeof(Page)*Pagequantum);
60 		if(p == nil)
61 			sysfatal("out of memory");
62 
63 		for(i=0, q=p; i<Pagequantum-1; i++, q++)
64 			q->link = q+1;
65 		q->link = nil;
66 
67 		freelist = p;
68 	}
69 	p = freelist;
70 	freelist = freelist->link;
71 	return p;
72 }
73 
74 #define PHIINV 0.61803398874989484820
75 uint
76 ahash(ulong addr)
77 {
78 	return (uint)floor(NHASH*fmod(addr*PHIINV, 1.0));
79 }
80 
81 int
82 lookup(ulong addr, uchar *val)
83 {
84 	Page *p;
85 
86 	lock(&pglock);
87 	for(p=pgtab[ahash(addr)]; p; p=p->link){
88 		if(p->addr == addr){
89 			memmove(val, p->val, Readlen);
90 			unlock(&pglock);
91 			return 1;
92 		}
93 	}
94 	unlock(&pglock);
95 	return 0;
96 }
97 
98 void
99 insert(ulong addr, uchar *val)
100 {
101 	Page *p;
102 	uint h;
103 
104 	lock(&pglock);
105 	p = newpg();
106 	p->addr = addr;
107 	memmove(p->val, val, Readlen);
108 	h = ahash(addr);
109 	p->link = pgtab[h];
110 	p->len = pgtab[h] ? pgtab[h]->len+1 : 1;
111 	pgtab[h] = p;
112 	unlock(&pglock);
113 }
114 
115 void
116 flushcache(void)
117 {
118 	int i;
119 	Page *p;
120 
121 	lock(&pglock);
122 	for(i=0; i<NHASH; i++){
123 		if(p=pgtab[i]){
124 			for(;p->link; p=p->link)
125 				;
126 			p->link = freelist;
127 			freelist = p;
128 		}
129 		pgtab[i] = nil;
130 	}
131 	unlock(&pglock);
132 }
133 
134 enum
135 {
136 	NFHASH	= 32,
137 	MSGSIZE	= 10,
138 	NPROCS = 2,	/* number of slave processes */
139 
140 	/*
141 	 * Qid 0 is root
142 	 * Subdirectory named "procname" ("1" is default)
143 	 * Files: mem, proc, and text
144 	 */
145 	Qroot	= CHDIR,
146 	Q1	= CHDIR|0x01,
147 	Qctl	= 1,
148 	Qfpregs,
149 	Qkregs,
150 	Qmem,
151 	Qproc,
152 	Qregs,
153 	Qtext,
154 	Qstatus,
155 	NFILES,
156 
157 	STACKSIZE = 4096,
158 };
159 
160 typedef struct Fid	Fid;
161 struct Fid
162 {
163 	int	fid;
164 	int	open;
165 	Fid	*hash;
166 	Qid	qid;
167 };
168 
169 /*
170  *	we only serve files needed by the debuggers
171  */
172 char*	fnames[] =
173 {
174 	[Qctl]		"ctl",
175 	[Qfpregs]	"fpregs",
176 	[Qkregs]	"kregs",
177 	[Qmem]		"mem",
178 	[Qproc]		"proc",
179 	[Qregs]		"regs",
180 	[Qtext]		"text",
181 	[Qstatus]	"status",
182 };
183 
184 int	textfd;
185 int	srvfd;
186 int	rfd;
187 Biobuf rfb;
188 int	pids[NPROCS];
189 int	nopen;
190 Fid*	hash[NFHASH];
191 char	dirbuf[DIRLEN*NFILES];
192 char 	iob[MSGSIZE];
193 char*	portname = "/dev/eia0";
194 char*	textfile = "/386/9pc";
195 char*	procname = "1";
196 char	dbuf[MAXFDATA];
197 
198 char*	Eexist = "file does not exist";
199 
200 void	mflush(Fcall*);
201 void	msession(Fcall*);
202 void	mnop(Fcall*);
203 void	mattach(Fcall*);
204 void	mclone(Fcall*);
205 void	mwalk(Fcall*);
206 void	mopen(Fcall*);
207 void	mcreate(Fcall*);
208 void	mread(Fcall*);
209 void	mwrite(Fcall*);
210 void	mclunk(Fcall*);
211 void	mremove(Fcall*);
212 void	mstat(Fcall*);
213 void	mwstat(Fcall*);
214 
215 void 	(*fcall[])(Fcall *f) =
216 {
217 	[Tflush]	mflush,
218 	[Tsession]	msession,
219 	[Tnop]		mnop,
220 	[Tattach]	mattach,
221 	[Tclone]	mclone,
222 	[Twalk]		mwalk,
223 	[Topen]		mopen,
224 	[Tcreate]	mcreate,
225 	[Tread]		mread,
226 	[Twrite]	mwrite,
227 	[Tclunk]	mclunk,
228 	[Tremove]	mremove,
229 	[Tstat]		mstat,
230 	[Twstat]	mwstat
231 };
232 
233 Fid*	newfid(int);
234 Fid*	lookfid(int);
235 int	delfid(Fcall*);
236 void	attachremote(char*);
237 void	reply(Fcall*, char*);
238 void	eiaread(void);
239 void	fsysread(void);
240 char* progname = "rdbfs";
241 
242 enum {
243 	Rendez	= 0x89ABCDEF
244 };
245 
246 void
247 usage(void)
248 {
249 	fprint(2, "Usage: rdbfs [-p procnum] [-t textfile] [serialport]\n");
250 	exits("usage");
251 }
252 
253 int
254 forkproc(void (*fn)(void))
255 {
256 	int pid;
257 	switch(pid=rfork(RFNAMEG|RFMEM|RFPROC)){
258 	case -1:
259 		sysfatal("fork: %r");
260 	case 0:
261 		fn();
262 		_exits(0);
263 	default:
264 		return pid;
265 	}
266 	return -1;	/* not reached */
267 }
268 
269 void
270 main(int argc, char **argv)
271 {
272 	int p[2];
273 
274 	rfork(RFNOTEG|RFREND);
275 
276 	ARGBEGIN{
277 	case 'd':
278 		dbg = 1;
279 		break;
280 	case 'p':
281 		if((procname=ARGF()) == nil)
282 			usage();
283 		break;
284 	case 't':
285 		if((textfile=ARGF()) == nil)
286 			usage();
287 		break;
288 	default:
289 		usage();
290 	}ARGEND;
291 
292 	switch(argc){
293 	case 0:
294 		break;
295 	case 1:
296 		portname = argv[0];
297 		break;
298 	default:
299 		usage();
300 	}
301 
302 	attachremote(portname);
303 	if(pipe(p) < 0)
304 		sysfatal("pipe: %r");
305 
306 	fmtinstall('F', fcallconv);
307 	srvfd = p[1];
308 	forkproc(eiaread);
309 	forkproc(fsysread);
310 
311 	if(mount(p[0], "/proc", MBEFORE, "") < 0)
312 		sysfatal("mount failed: %r");
313 	exits(0);
314 }
315 
316 /*
317  *	read protocol messages
318  */
319 void
320 fsysread(void)
321 {
322 	int n;
323 	Fcall f;
324 	char buf[MAXMSG+MAXFDATA];
325 
326 	pids[0] = getpid();
327 	for(;;) {
328 		n = read(srvfd, buf, sizeof(buf));
329 		if(n < 0)
330 			sysfatal("read mntpt: %r");
331 		n = convM2S(buf, &f, n);
332 		if(n < 0)
333 			continue;
334 		fcall[f.type](&f);
335 	}
336 }
337 
338 /*
339  *	write a protocol reply to the debugger
340  */
341 void
342 reply(Fcall *r, char *error)
343 {
344 	int n;
345 	char rbuf[MAXMSG+MAXFDATA];
346 
347 	if(error == nil)
348 		r->type++;
349 	else {
350 		r->type = Rerror;
351 		strncpy(r->ename, error, sizeof(r->ename));
352 	}
353 	n = convS2M(r, rbuf);
354 	if(write(srvfd, rbuf, n) < 0)
355 		sysfatal("reply: %r");
356 
357 	DBG(2, "%F\n", r);
358 }
359 
360 void
361 noalarm(void*, char *msg)
362 {
363 	if(strstr(msg, "alarm"))
364 		noted(NCONT);
365 	noted(NDFLT);
366 }
367 
368 /*
369  * 	send and receive responses on the serial line
370  */
371 void
372 eiaread(void)
373 {
374 	Fcall*	r;
375 	char *p;
376 	uchar *data;
377 	char err[ERRLEN];
378 	char buf[1000];
379 	int i, tries;
380 
381 	notify(noalarm);
382 	for(; r = (Fcall*)rendezvous(Rendez, 0); free(r)){
383 		DBG(2, "got %F: here goes...", r);
384 		if(r->count > Readlen)
385 			r->count = Readlen;
386 		if(lookup(r->offset, (uchar*)r->data)){
387 			reply(r, nil);
388 			continue;
389 		}
390 		for(tries=0; tries<5; tries++){
391 			if(r->type == Twrite){
392 				DBG(2, "w%.8lux %.8lux...", (ulong)r->offset, *(ulong*)r->data);
393 				fprint(rfd, "w%.8lux %.8lux\n", (ulong)r->offset, *(ulong*)r->data);
394 			}else if(r->type == Tread){
395 				DBG(2, "r%.8lux...", (ulong)r->offset);
396 				fprint(rfd, "r%.8lux\n", (ulong)r->offset);
397 			}else{
398 				reply(r, "oops");
399 				break;
400 			}
401 			for(;;){
402 				werrstr("");
403 				alarm(500);
404 				p=Brdline(&rfb, '\n');
405 				alarm(0);
406 				if(p == nil){
407 					err[0] = 0;
408 					errstr(err);
409 					DBG(2, "error %s\n", err);
410 					if(strstr(err, "alarm") || strstr(err, "interrupted"))
411 						break;
412 					if(Blinelen(&rfb) == 0) // true eof
413 						sysfatal("eof on serial line?");
414 					Bread(&rfb, buf, Blinelen(&rfb)<sizeof buf ? Blinelen(&rfb) : sizeof buf);
415 					continue;
416 				}
417 				p[Blinelen(&rfb)-1] = 0;
418 				if(p[0] == '\r')
419 					p++;
420 				DBG(2, "serial %s\n", p);
421 				if(p[0] == 'R'){
422 					if(strtoul(p+1, 0, 16) == (ulong)r->offset){
423 						data = (uchar*)r->data;
424 						for(i=0; i<Readlen; i++)
425 							data[i] = strtol(p+1+8+1+3*i, 0, 16);
426 						insert(r->offset, (uchar*)r->data);
427 						reply(r, nil);
428 						goto Break2;
429 					}else
430 						DBG(2, "%.8lux ≠ %.8lux\n", strtoul(p+1, 0, 16), (ulong)r->offset);
431 				}else if(p[0] == 'W'){
432 					r->count = 4;
433 					reply(r, nil);
434 					goto Break2;
435 				}else{
436 					DBG(2, "unknown message\n");
437 				}
438 			}
439 		}
440 	Break2:;
441 	}
442 }
443 
444 void
445 attachremote(char* name)
446 {
447 	int fd;
448 	char buf[128];
449 
450 	print("attach %s\n", name);
451 	rfd = open(name, ORDWR);
452 	if(rfd < 0)
453 		sysfatal("can't open remote %s", name);
454 
455 	sprint(buf, "%sctl", name);
456 	fd = open(buf, OWRITE);
457 	if(fd < 0)
458 		sysfatal("can't set baud rate on %s", buf);
459 	write(fd, "B9600", 6);
460 	close(fd);
461 	Binit(&rfb, rfd, OREAD);
462 }
463 
464 int
465 sendremote(Fcall *r)
466 {
467 	Fcall *nr;
468 
469 	if(r->type == Tread || r->type == Twrite)
470 		nr = malloc(sizeof(Fcall)+Readlen);
471 	else
472 		nr = malloc(sizeof(Fcall));
473 
474 	if(nr == nil)
475 		return -1;
476 
477 	*nr = *r;
478 	if(r->type == Tread)
479 		nr->data = (char*)(nr+1);
480 	else if(r->type == Twrite){
481 		nr->data = (char*)(nr+1);
482 		*(ulong*)nr->data = *(ulong*)r->data;
483 	}
484 	rendezvous(Rendez, (ulong)nr);
485 	return 0;
486 }
487 
488 void
489 mflush(Fcall *f)
490 {
491 	reply(f, nil);
492 }
493 
494 void
495 msession(Fcall *f)
496 {
497 	memset(f->authid, 0, sizeof(f->authid));
498 	memset(f->authdom, 0, sizeof(f->authdom));
499 	memset(f->chal, 0, sizeof(f->chal));
500 	reply(f, nil);
501 }
502 
503 void
504 mnop(Fcall *f)
505 {
506 	reply(f, nil);
507 }
508 
509 void
510 mattach(Fcall *f)
511 {
512 	Fid *nf;
513 
514 	nf = newfid(f->fid);
515 	if(nf == nil) {
516 		reply(f, "fid busy");
517 		return;
518 	}
519 	nf->qid = (Qid){Qroot, 0};
520 	f->qid = nf->qid;
521 	memset(f->rauth, 0, sizeof(f->rauth));
522 	nopen++;
523 	reply(f, nil);
524 }
525 
526 void
527 mclone(Fcall *f)
528 {
529 	Fid *of, *nf;
530 
531 	of = lookfid(f->fid);
532 	if(of == nil) {
533 		reply(f, "bad fid");
534 		return;
535 	}
536 	nf = newfid(f->newfid);
537 	if(nf == nil) {
538 		reply(f, "fid busy");
539 		return;
540 	}
541 	nf->qid = of->qid;
542 	f->qid = nf->qid;
543 	nopen++;
544 	reply(f, nil);
545 }
546 
547 void
548 mwalk(Fcall *f)
549 {
550 	Fid *nf;
551 	int i;
552 
553 	nf = lookfid(f->fid);
554 	if(nf == nil) {
555 		reply(f, "bad fid");
556 		return;
557 	}
558 	if(strcmp(f->name, ".") == 0) {
559 		f->qid = nf->qid;
560 		reply(f, nil);
561 		return;
562 	}
563 
564 	switch(nf->qid.path) {
565 	case Qroot:
566 		if (strcmp(f->name, procname) == 0) {
567 			nf->qid.path = Q1;
568 			f->qid = nf->qid;
569 			reply(f, nil);
570 			return;
571 		}
572 		break;
573 	case Q1:
574 		if(strcmp(f->name, "..") == 0) {
575 			f->qid.path = Qroot;
576 			reply(f, nil);
577 			return;
578 		}
579 		for(i = 0; i < NFILES; i++) {
580 			if(fnames[i] != nil && strcmp(f->name, fnames[i]) == 0) {
581 				nf->qid.path = i;
582 				f->qid = nf->qid;
583 				reply(f, nil);
584 				return;
585 			}
586 		}
587 		break;
588 	}
589 	reply(f, Eexist);
590 }
591 
592 void
593 statgen(Qid *q, char *buf)
594 {
595 	Dir dir;
596 
597 	strcpy(dir.uid, "none");
598 	strcpy(dir.gid, "none");
599 	dir.qid = *q;
600 	dir.length = 0;
601 	dir.atime = time(0);
602 	dir.mtime = dir.atime;
603 	memset(dir.name, 0, NAMELEN);
604 
605 	switch(q->path) {
606 	case Qroot:
607 		strcpy(dir.name, ".");
608 		dir.mode = CHDIR|0555;
609 		break;
610 	case Q1:
611 		strcpy(dir.name, procname);
612 		dir.mode = CHDIR|0555;
613 		break;
614 	case Qctl:
615 	case Qfpregs:
616 	case Qkregs:
617 	case Qmem:
618 	case Qproc:
619 	case Qregs:
620 	case Qtext:
621 	case Qstatus:
622 		strcpy(dir.name, fnames[q->path]);
623 		dir.mode = 0644;
624 		break;
625 	default:
626 		sysfatal("stat: bad qid %lux", q->path);
627 	}
628 	convD2M(&dir, buf);
629 }
630 
631 void
632 mopen(Fcall *f)
633 {
634 	Fid *of;
635 	char buf[ERRLEN];
636 
637 	of = lookfid(f->fid);
638 	if(of == nil) {
639 		reply(f, "bad fid");
640 		return;
641 	}
642 
643 	switch(of->qid.path) {
644 	case Qroot:
645 	case Q1:
646 		break;
647 	case Qctl:
648 	case Qfpregs:
649 	case Qkregs:
650 	case Qmem:
651 	case Qproc:
652 	case Qregs:
653 	case Qstatus:
654 		f->qid = of->qid;
655 		break;
656 	case Qtext:
657 		close(textfd);
658 		textfd = open(textfile, OREAD);
659 		if(textfd < 0) {
660 			sprint(buf, "text: %r");
661 			reply(f, buf);
662 			return;
663 		}
664 		break;
665 	default:
666 		reply(f, "bad qid");
667 		return;
668 	}
669 	of->open = 1;
670 	reply(f, nil);
671 }
672 
673 void
674 mread(Fcall *f)
675 {
676 	Fid *of;
677 	Qid qid;
678 	int i, n;
679 	char *p, buf[512];
680 
681 	of = lookfid(f->fid);
682 	if(of == nil) {
683 		reply(f, "bad fid");
684 		return;
685 	}
686 
687 	switch(of->qid.path) {
688 	case Qroot:
689 		qid = (Qid){Q1, 0};
690 		statgen(&qid, dirbuf);
691 		if(f->offset >= DIRLEN)
692 			f->count = 0;
693 		else if(f->offset+f->count > DIRLEN)
694 			f->count = DIRLEN-f->offset;
695 		f->data = dirbuf+f->offset;
696 		reply(f, nil);
697 		break;
698 	case Q1:
699 		if(!of->open) {
700 			reply(f, "not open");
701 			return;
702 		}
703 		p = dirbuf;
704 		for(i = 0; i < NFILES; i++) {
705 			if(fnames[i]) {
706 				qid = (Qid){i, 0};
707 				statgen(&qid, p);
708 				p += DIRLEN;
709 			}
710 		}
711 		n = p-dirbuf;
712 		if(f->offset >= n)
713 			f->count = 0;
714 		else if(f->offset+f->count > n)
715 			f->count = n-f->offset;
716 		f->data = dirbuf+f->offset;
717 		reply(f, nil);
718 		break;
719 	case Qctl:
720 	case Qfpregs:
721 	case Qkregs:
722 	case Qmem:
723 	case Qproc:
724 	case Qregs:
725 		if(!of->open) {
726 			reply(f, "not open");
727 			return;
728 		}
729 		if(sendremote(f) < 0) {
730 			snprint(buf, sizeof buf, "remote write %r");
731 			reply(f, buf);
732 			return;
733 		}
734 		break;
735 	case Qtext:
736 		n = seek(textfd, f->offset, 0);
737 		if(n < 0) {
738 			errstr(buf);
739 			reply(f, buf);
740 			break;
741 		}
742 		n = read(textfd, dbuf, f->count);
743 		if(n < 0) {
744 			errstr(buf);
745 			reply(f, buf);
746 			break;
747 		}
748 		f->count = n;
749 		f->data = dbuf;
750 		reply(f, nil);
751 		break;
752 	case Qstatus:
753 		n = sprint(buf, "%-28s%-28s%-28s", "remote", "system", "New");
754 		for(i = 0; i < 9; i++)
755 			n += sprint(buf+n, "%-12d", 0);
756 		if(f->offset+f->count > n)
757 			f->count = n - f->offset;
758 		if(f->count < 0)
759 			f->count = 0;
760 		f->data = buf+f->offset;
761 		reply(f, nil);
762 		break;
763 	default:
764 		sysfatal("unknown read");
765 	}
766 }
767 
768 /*
769  *	disentangle our file system from the namespace
770  */
771 void
772 umount(void)
773 {
774 	bind("#p", "/proc", MREPL);
775 	exits(nil);
776 }
777 
778 void
779 mwrite(Fcall *f)
780 {
781 	Fid *of;
782 
783 	of = lookfid(f->fid);
784 	if(of == nil) {
785 		reply(f, "bad fid");
786 		return;
787 	}
788 
789 	switch(of->qid.path) {
790 	case Qroot:
791 	case Q1:
792 		reply(f, "permission denied");
793 		break;
794 	case Qctl:
795 		if(strncmp(f->data, "kill", f->count-1) == 0 ||
796 		   strncmp(f->data, "exit", f->count-1) == 0) {
797 			reply(f, nil);
798 			umount();
799 		}else if(strncmp(f->data, "refresh", 7) == 0){
800 			flushcache();
801 			reply(f, nil);
802 		}else if(strncmp(f->data, "hashstats", 9) == 0){
803 			int i;
804 			lock(&pglock);
805 			for(i=0; i<NHASH; i++)
806 				if(pgtab[i])
807 					print("%lud ", pgtab[i]->len);
808 			print("\n");
809 			unlock(&pglock);
810 			reply(f, nil);
811 		}else
812 			reply(f, "permission denied");
813 		break;
814 	case Qfpregs:
815 	case Qkregs:
816 	case Qmem:
817 	case Qproc:
818 	case Qregs:
819 		if(!of->open) {
820 			reply(f, "not open");
821 			return;
822 		}
823 		if(sendremote(f) < 0) {
824 			reply(f, "remote write %r");
825 			return;
826 		}
827 		break;
828 	case Qtext:
829 		reply(f, "text write protected");
830 	default:
831 		reply(f, "bad path");
832 		break;
833 	}
834 }
835 
836 void
837 mclunk(Fcall *f)
838 {
839 	Fid *of;
840 
841 	of = lookfid(f->fid);
842 	if(of == nil) {
843 		reply(f, "bad fid");
844 		return;
845 	}
846 	delfid(f);
847 	reply(f, nil);
848 	nopen--;
849 	if(nopen <= 0) {
850 		postnote(PNGROUP, getpid(), "kill");
851 		exits(nil);
852 	}
853 }
854 
855 void
856 mstat(Fcall *f)
857 {
858 	Fid *of;
859 
860 	of = lookfid(f->fid);
861 	if(of == nil) {
862 		reply(f, "bad fid");
863 		return;
864 	}
865 
866 	statgen(&of->qid, f->stat);
867 	reply(f, nil);
868 }
869 
870 void
871 mwstat(Fcall *f)
872 {
873 	reply(f, "permission denied");
874 }
875 
876 void
877 mcreate(Fcall *f)
878 {
879 	reply(f, "permission denied");
880 }
881 
882 void
883 mremove(Fcall *f)
884 {
885 	reply(f, "permission denied");
886 }
887 
888 Fid*
889 newfid(int fid)
890 {
891 	Fid *nf, **l;
892 
893 	if(lookfid(fid))
894 		return nil;
895 
896 	nf = mallocz(sizeof(*nf), 1);
897 	assert(nf != nil);
898 
899 	nf->fid = fid;
900 	nf->open = 0;
901 	nf->qid = (Qid){0, 0};
902 
903 	l = &hash[fid%NFHASH];
904 	nf->hash = *l;
905 	*l = nf;
906 
907 	return nf;
908 }
909 
910 Fid*
911 lookfid(int fid)
912 {
913 	Fid *l;
914 
915 	for(l = hash[fid%NFHASH]; l; l = l->hash)
916 		if(l->fid == fid)
917 			return l;
918 
919 	return nil;
920 }
921 
922 int
923 delfid(Fcall *f)
924 {
925 	Fid **l, *nf;
926 
927 	l = &hash[f->fid%NFHASH];
928 	for(nf = *l; nf; nf = nf->hash) {
929 		if(nf->fid == f->fid) {
930 			*l = nf->hash;
931 			free(nf);
932 			return 1;
933 		}
934 	}
935 	return 0;
936 }
937