xref: /plan9-contrib/sys/src/cmd/cpu.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 /*
2  * cpu.c - Make a connection to a cpu server
3  *
4  *	   Invoked by listen as 'cpu -R | -N service net netdir'
5  *	    	   by users  as 'cpu [-h system] [-c cmd args ...]'
6  */
7 
8 #include <u.h>
9 #include <libc.h>
10 #include <bio.h>
11 #include <auth.h>
12 #include <fcall.h>
13 
14 void	remoteside(void);
15 void	fatal(int, char*, ...);
16 void	lclnoteproc(int);
17 void	rmtnoteproc(void);
18 void	catcher(void*, char*);
19 void	usage(void);
20 void	writestr(int, char*, char*, int);
21 int	readstr(int, char*, int);
22 char	*rexcall(int*, char*, char*);
23 int	filter(int);
24 
25 int 	notechan;
26 char	system[32];
27 int	cflag;
28 int	hflag;
29 int	fflag;
30 int	dbg;
31 
32 char	*srvname = "cpu";
33 char	*exportfs = "/bin/exportfs";
34 
35 /* authentication mechanisms */
36 static int	netkeyauth(int);
37 static int	netkeysrvauth(int, char*);
38 
39 typedef struct AuthMethod AuthMethod;
40 struct AuthMethod {
41 	char	*name;			/* name of method */
42 	int	(*cf)(int);		/* client side authentication */
43 	int	(*sf)(int, char*);	/* server side authentication */
44 } authmethod[] =
45 {
46 	{ "p9",		auth,		srvauth},
47 	{ "netkey",	netkeyauth,	netkeysrvauth},
48 	{ nil,	nil}
49 };
50 AuthMethod *am = authmethod;	/* default is p9 */
51 
52 int setam(char*);
53 
54 void
55 usage(void)
56 {
57 	fprint(2, "usage: cpu [-h system] [-a authmethod] [-c cmd args ...]\n");
58 	exits("usage");
59 }
60 int fdd;
61 
62 void
63 main(int argc, char **argv)
64 {
65 	char dat[128], buf[128], cmd[128], *p, *err;
66 	int data;
67 
68 	ARGBEGIN{
69 	case 'a':
70 		p = ARGF();
71 		if(p==0)
72 			usage();
73 		if(setam(p) < 0)
74 			fatal(0, "unknown auth method %s\n", p);
75 		break;
76 	case 'd':
77 		dbg++;
78 		break;
79 	case 'f':
80 		fflag++;
81 		break;
82 	case 'R':				/* From listen */
83 		remoteside();
84 		break;
85 	case 'h':
86 		hflag++;
87 		p = ARGF();
88 		if(p==0)
89 			usage();
90 		strcpy(system, p);
91 		break;
92 	case 'c':
93 		cflag++;
94 		cmd[0] = '!';
95 		cmd[1] = '\0';
96 		while(p = ARGF()) {
97 			strcat(cmd, " ");
98 			strcat(cmd, p);
99 		}
100 		break;
101 	}ARGEND;
102 
103 	if(argv);
104 	if(argc != 0)
105 		usage();
106 
107 	if(hflag == 0) {
108 		p = getenv("cpu");
109 		if(p == 0)
110 			fatal(0, "set $cpu");
111 		strcpy(system, p);
112 	}
113 
114 	if(err = rexcall(&data, system, srvname))
115 		fatal(1, "%s: %s", err, system);
116 
117 	/* Tell the remote side the command to execute and where our working directory is */
118 	if(cflag)
119 		writestr(data, cmd, "command", 0);
120 	if(getwd(dat, sizeof(dat)) == 0)
121 		writestr(data, "NO", "dir", 0);
122 	else
123 		writestr(data, dat, "dir", 0);
124 
125 	/* start up a process to pass along notes */
126 	lclnoteproc(data);
127 
128 	/* Wait for the other end to execute and start our file service
129 	 * of /mnt/term */
130 	if(readstr(data, buf, sizeof(buf)) < 0)
131 		fatal(1, "waiting for FS");
132 	if(strncmp("FS", buf, 2) != 0) {
133 		print("remote cpu: %s", buf);
134 		exits(buf);
135 	}
136 
137 	if(fflag)
138 		data = filter(data);
139 
140 	/* Begin serving the gnot namespace */
141 	close(0);
142 	dup(data, 0);
143 	close(data);
144 	if(dbg)
145 		execl(exportfs, exportfs, "-d", 0);
146 	else
147 		execl(exportfs, exportfs, 0);
148 	fatal(1, "starting exportfs");
149 }
150 
151 void
152 fatal(int syserr, char *fmt, ...)
153 {
154 	char buf[ERRLEN];
155 	va_list arg;
156 
157 	va_start(arg, fmt);
158 	doprint(buf, buf+sizeof(buf), fmt, arg);
159 	va_end(arg);
160 	if(syserr)
161 		fprint(2, "cpu: %s: %r\n", buf);
162 	else
163 		fprint(2, "cpu: %s\n", buf);
164 	exits(buf);
165 }
166 
167 char *negstr = "negotiating authentication method";
168 
169 /* Invoked with stdin, stdout and stderr connected to the network connection */
170 void
171 remoteside(void)
172 {
173 	char user[NAMELEN], home[128], buf[128], xdir[128], cmd[128];
174 	int i, n, fd, badchdir, gotcmd;
175 
176 	/* negotiate authentication mechanism */
177 	n = readstr(0, cmd, sizeof(cmd));
178 	if(n < 0)
179 		fatal(1, "authenticating");
180 	if(setam(cmd) < 0){
181 		writestr(1, "unsupported auth method", nil, 0);
182 		fatal(1, "bad auth method %s", cmd);
183 	} else
184 		writestr(1, "", "", 1);
185 
186 	if((*am->sf)(0, user) < 0)
187 		fatal(1, "srvauth");
188 	if(newns(user, 0) < 0)
189 		fatal(1, "newns");
190 
191 	/* Set environment values for the user */
192 	putenv("user", user);
193 	sprint(home, "/usr/%s", user);
194 	putenv("home", home);
195 
196 	/* Now collect invoking cpus current directory or possibly a command */
197 	gotcmd = 0;
198 	if(readstr(0, xdir, sizeof(xdir)) < 0)
199 		fatal(1, "dir/cmd");
200 	if(xdir[0] == '!') {
201 		strcpy(cmd, &xdir[1]);
202 		gotcmd = 1;
203 		if(readstr(0, xdir, sizeof(xdir)) < 0)
204 			fatal(1, "dir");
205 	}
206 
207 
208 	/* Establish the new process at the current working directory of the
209 	 * gnot */
210 	badchdir = 0;
211 	if(strcmp(xdir, "NO") == 0)
212 		chdir(home);
213 	else if(chdir(xdir) < 0) {
214 		badchdir = 1;
215 		chdir(home);
216 	}
217 
218 	/* Start the gnot serving its namespace */
219 	writestr(1, "FS", "FS", 0);
220 	writestr(1, "/", "exportfs dir", 0);
221 
222 	n = read(1, buf, sizeof(buf));
223 	if(n != 2 || buf[0] != 'O' || buf[1] != 'K')
224 		exits("remote tree");
225 
226 	fd = dup(1, -1);
227 
228 	/* push fcall and note proc */
229 	if(fflag)
230 		fd = filter(fd);
231 	if(amount(fd, "/mnt/term", MCREATE|MREPL, "") < 0)
232 		exits("mount failed");
233 	close(fd);
234 
235 	/* the remote noteproc uses the mount so it must follow it */
236 	rmtnoteproc();
237 
238 	for(i = 0; i < 3; i++)
239 		close(i);
240 
241 	if(open("/mnt/term/dev/cons", OREAD) != 0){
242 		exits("open stdin");
243 	}
244 	if(open("/mnt/term/dev/cons", OWRITE) != 1){
245 		exits("open stdout");
246 	}
247 	dup(1, 2);
248 
249 	if(badchdir)
250 		print("cpu: failed to chdir to '%s'\n", xdir);
251 
252 	if(gotcmd)
253 		execl("/bin/rc", "rc", "-lc", cmd, 0);
254 	else
255 		execl("/bin/rc", "rc", "-li", 0);
256 	fatal(1, "exec shell");
257 }
258 
259 char*
260 rexcall(int *fd, char *host, char *service)
261 {
262 	char *na;
263 	char dir[4*NAMELEN];
264 	char err[ERRLEN];
265 	int n;
266 
267 	na = netmkaddr(host, 0, service);
268 	if((*fd = dial(na, 0, dir, 0)) < 0)
269 		return "can't dial";
270 
271 	/* negotiate authentication mechanism */
272 	writestr(*fd, am->name, negstr, 0);
273 	n = readstr(*fd, err, ERRLEN);
274 	if(n < 0)
275 		return negstr;
276 	if(*err){
277 		werrstr(err);
278 		return negstr;
279 	}
280 
281 	/* authenticate */
282 	if((*am->cf)(*fd) < 0)
283 		return "can't authenticate";
284 	if(strstr(dir, "tcp"))
285 		fflag = 1;
286 	return 0;
287 }
288 
289 void
290 writestr(int fd, char *str, char *thing, int ignore)
291 {
292 	int l, n;
293 
294 	l = strlen(str);
295 	n = write(fd, str, l+1);
296 	if(!ignore && n < 0)
297 		fatal(1, "writing network: %s", thing);
298 }
299 
300 int
301 readstr(int fd, char *str, int len)
302 {
303 	int n;
304 
305 	while(len) {
306 		n = read(fd, str, 1);
307 		if(n < 0)
308 			return -1;
309 		if(*str == '\0')
310 			return 0;
311 		str++;
312 		len--;
313 	}
314 	return -1;
315 }
316 
317 static int
318 readln(char *buf, int n)
319 {
320 	char *p = buf;
321 
322 	n--;
323 	while(n > 0){
324 		if(read(0, p, 1) != 1)
325 			break;
326 		if(*p == '\n' || *p == '\r'){
327 			*p = 0;
328 			return p-buf;
329 		}
330 		p++;
331 	}
332 	*p = 0;
333 	return p-buf;
334 }
335 
336 static int
337 netkeyauth(int fd)
338 {
339 	char chall[NAMELEN];
340 	char resp[NAMELEN];
341 
342 	strcpy(chall, getuser());
343 	print("user[%s]: ", chall);
344 	if(readln(resp, sizeof(resp)) < 0)
345 		return -1;
346 	if(*resp != 0)
347 		strcpy(chall, resp);
348 	writestr(fd, chall, "challenge/response", 1);
349 
350 	for(;;){
351 		if(readstr(fd, chall, sizeof chall) < 0)
352 			break;
353 		if(*chall == 0)
354 			return 0;
355 		print("challenge: %s\nresponse: ", chall);
356 		if(readln(resp, sizeof(resp)) < 0)
357 			break;
358 		writestr(fd, resp, "challenge/response", 1);
359 	}
360 	return -1;
361 }
362 
363 static int
364 netkeysrvauth(int fd, char *user)
365 {
366 	char response[NAMELEN];
367 	Chalstate ch;
368 	int tries;
369 
370 	if(readstr(fd, user, NAMELEN) < 0)
371 		return -1;
372 
373 	for(tries = 0; tries < 10; tries++){
374 		if(getchal(&ch, user) < 0)
375 			return -1;
376 		writestr(fd, ch.chal, "challenge", 1);
377 		if(readstr(fd, response, sizeof response) < 0)
378 			return -1;
379 		if(chalreply(&ch, response) >= 0)
380 			break;
381 	}
382 	if(tries >= 10)
383 		return -1;
384 	writestr(fd, "", "challenge", 1);
385 	return 0;
386 }
387 
388 /* Network on fd1, mount driver on fd0 */
389 int
390 filter(int fd)
391 {
392 	int p[2];
393 
394 	if(pipe(p) < 0)
395 		fatal(1, "pipe");
396 
397 	switch(rfork(RFNOWAIT|RFPROC|RFFDG)) {
398 	case -1:
399 		fatal(1, "rfork record module");
400 	case 0:
401 		dup(fd, 1);
402 		close(fd);
403 		dup(p[0], 0);
404 		close(p[0]);
405 		close(p[1]);
406 		execl("/bin/aux/fcall", "fcall", 0);
407 		fatal(1, "exec record module");
408 	default:
409 		close(fd);
410 		close(p[0]);
411 	}
412 	return p[1];
413 }
414 
415 int
416 setam(char *name)
417 {
418 	for(am = authmethod; am->name != nil; am++)
419 		if(strcmp(am->name, name) == 0)
420 			return 0;
421 	am = authmethod;
422 	return -1;
423 }
424 
425 char *rmtnotefile = "/mnt/term/dev/cpunote";
426 
427 /*
428  *  loop reading /mnt/term/dev/note looking for notes.
429  *  The child returns to start the shell.
430  */
431 void
432 rmtnoteproc(void)
433 {
434 	int n, fd, pid, notepid;
435 	char buf[256];
436 	Waitmsg w;
437 
438 	/* new proc returns to start shell */
439 	pid = rfork(RFPROC|RFFDG|RFNOTEG|RFNAMEG|RFMEM);
440 	switch(pid){
441 	case -1:
442 		syslog(0, "cpu", "cpu -R: can't start noteproc: %r");
443 		return;
444 	case 0:
445 		return;
446 	}
447 
448 	/* new proc reads notes from other side and posts them to shell */
449 	switch(notepid = rfork(RFPROC|RFFDG|RFMEM)){
450 	case -1:
451 		syslog(0, "cpu", "cpu -R: can't start wait proc: %r");
452 		_exits(0);
453 	case 0:
454 		fd = open(rmtnotefile, OREAD);
455 		if(fd < 0){
456 			syslog(0, "cpu", "cpu -R: can't open %s", rmtnotefile);
457 			_exits(0);
458 		}
459 
460 		for(;;){
461 			n = read(fd, buf, sizeof(buf)-1);
462 			if(n <= 0){
463 				postnote(PNGROUP, pid, "hangup");
464 				_exits(0);
465 			}
466 			buf[n] = 0;
467 			postnote(PNGROUP, pid, buf);
468 		}
469 		break;
470 	}
471 
472 	/* original proc waits for shell proc to die and kills note proc */
473 	for(;;){
474 		n = wait(&w);
475 		if(n < 0 || n == pid)
476 			break;
477 	}
478 	postnote(PNPROC, notepid, "kill");
479 	_exits(0);
480 }
481 
482 enum
483 {
484 	Qdir,
485 	Qcpunote,
486 
487 	Nfid = 32,
488 };
489 
490 struct {
491 	char	*name;
492 	Qid	qid;
493 	ulong	perm;
494 } fstab[] =
495 {
496 	[Qdir]		{ ".",		{CHDIR|Qdir, 0},	CHDIR|0555	},
497 	[Qcpunote]	{ "cpunote",	{Qcpunote, 0},		0444		},
498 };
499 
500 typedef struct Note Note;
501 struct Note
502 {
503 	Note *next;
504 	char msg[ERRLEN];
505 };
506 
507 typedef struct Request Request;
508 struct Request
509 {
510 	Request *next;
511 	Fcall f;
512 };
513 
514 typedef struct Fid Fid;
515 struct Fid
516 {
517 	int	fid;
518 	int	file;
519 };
520 Fid fids[Nfid];
521 
522 struct {
523 	Lock;
524 	Note *nfirst, *nlast;
525 	Request *rfirst, *rlast;
526 } nfs;
527 
528 int
529 fsreply(int fd, Fcall *f)
530 {
531 	char buf[ERRLEN+MAXMSG];
532 	int n;
533 
534 	if(f->type == Rerror)
535 		f->count = strlen(f->data);
536 	if(dbg)
537 		fprint(2, "<-%F\n", f);
538 	n = convS2M(f, buf);
539 	if(n > 0){
540 		if(write(fd, buf, n) != n){
541 			close(fd);
542 			return -1;
543 		}
544 	}
545 	return 0;
546 }
547 
548 /* match a note read request with a note, reply to the request */
549 int
550 kick(int fd)
551 {
552 	Request *rp;
553 	Note *np;
554 	int rv;
555 
556 	for(;;){
557 		lock(&nfs);
558 		rp = nfs.rfirst;
559 		np = nfs.nfirst;
560 		if(rp == nil || np == nil){
561 			unlock(&nfs);
562 			break;
563 		}
564 		nfs.rfirst = rp->next;
565 		nfs.nfirst = np->next;
566 		unlock(&nfs);
567 
568 		rp->f.type = Rread;
569 		rp->f.count = strlen(np->msg);
570 		rp->f.data = np->msg;
571 		rv = fsreply(fd, &rp->f);
572 		free(rp);
573 		free(np);
574 		if(rv < 0)
575 			return -1;
576 	}
577 	return 0;
578 }
579 
580 void
581 flushreq(int tag)
582 {
583 	Request **l, *rp;
584 
585 	lock(&nfs);
586 	for(l = &nfs.rfirst; *l != nil; l = &(*l)->next){
587 		rp = *l;
588 		if(rp->f.tag == tag){
589 			*l = rp->next;
590 			unlock(&nfs);
591 			free(rp);
592 			return;
593 		}
594 	}
595 	unlock(&nfs);
596 }
597 
598 Fid*
599 getfid(int fid)
600 {
601 	int i, freefid;
602 
603 	freefid = -1;
604 	for(i = 0; i < Nfid; i++){
605 		if(freefid < 0 && fids[i].file < 0)
606 			freefid = i;
607 		if(fids[i].fid == fid)
608 			return &fids[i];
609 	}
610 	if(freefid >= 0){
611 		fids[freefid].fid = fid;
612 		return &fids[freefid];
613 	}
614 	return nil;
615 }
616 
617 int
618 fsstat(int fd, Fid *fid, Fcall *f)
619 {
620 	Dir d;
621 
622 	memset(&d, 0, sizeof(d));
623 	strcpy(d.name, fstab[fid->file].name);
624 	d.qid = fstab[fid->file].qid;
625 	d.mode = fstab[fid->file].perm;
626 	d.atime = d.mtime = time(0);
627 	convD2M(&d, f->stat);
628 	return fsreply(fd, f);
629 }
630 
631 int
632 fsread(int fd, Fid *fid, Fcall *f)
633 {
634 	Dir d;
635 	char buf[DIRLEN];
636 	Request *rp;
637 
638 	switch(fid->file){
639 	default:
640 		return -1;
641 	case Qdir:
642 		if(f->offset == 0 && f->count == DIRLEN){
643 			memset(&d, 0, sizeof(d));
644 			d.qid = fstab[Qcpunote].qid;
645 			d.mode = fstab[Qcpunote].perm;
646 			d.atime = d.mtime = time(0);
647 			convD2M(&d, buf);
648 			f->data = buf;
649 			f->count = 0;
650 		} else
651 			f->count = 0;
652 		return fsreply(fd, f);
653 	case Qcpunote:
654 		rp = mallocz(sizeof(*rp), 1);
655 		if(rp == nil)
656 			return -1;
657 		rp->f = *f;
658 		lock(&nfs);
659 		if(nfs.rfirst == nil)
660 			nfs.rfirst = rp;
661 		else
662 			nfs.rlast->next = rp;
663 		nfs.rlast = rp;
664 		unlock(&nfs);
665 		return kick(fd);;
666 	}
667 }
668 
669 char *Eperm = "permission denied";
670 char *Enofile = "out of files";
671 char *Enotexist = "file does not exist";
672 char *Enotdir = "not a directory";
673 
674 void
675 notefs(int fd)
676 {
677 	char buf[MAXFDATA+MAXMSG];
678 	int n;
679 	Fcall f;
680 	Fid *fid, *nfid;
681 	int doreply;
682 
683 	rfork(RFNOTEG);
684 	fmtinstall('F', fcallconv);
685 
686 	for(n = 0; n < Nfid; n++)
687 		fids[n].file = -1;
688 
689 	for(;;){
690 		n = read9p(fd, buf, sizeof(buf));
691 		if(n <= 0)
692 			break;
693 		if(convM2S(buf, &f, n) < 0)
694 			break;
695 		if(dbg)
696 			fprint(2, "->%F\n", &f);
697 		doreply = 1;
698 		fid = getfid(f.fid);
699 		if(fid == nil){
700 nofids:
701 			f.type = Rerror;
702 			f.data = Enofile;
703 			fsreply(fd, &f);
704 			continue;
705 		}
706 		switch(f.type++){
707 		default:
708 			f.type = Rerror;
709 			f.data = "unknown type";
710 			break;
711 		case Tflush:
712 			flushreq(f.oldtag);
713 			break;
714 		case Tsession:
715 			memset(f.authid, 0, sizeof(f.authid));
716 			memset(f.authdom, 0, sizeof(f.authdom));
717 			memset(f.chal, 0, sizeof(f.chal));
718 			break;
719 		case Tnop:
720 			break;
721 		case Tattach:
722 			f.qid = fstab[Qdir].qid;
723 			fid->file = Qdir;
724 			break;
725 		case Tclone:
726 			nfid = getfid(f.newfid);
727 			if(nfid == nil)
728 				goto nofids;
729 			nfid->file = fid->file;
730 			break;
731 		case Tclwalk:
732 			nfid = getfid(f.newfid);
733 			if(nfid == nil)
734 				goto nofids;
735 			nfid->file = fid->file;
736 			fid = nfid;
737 			/* fall through */
738 		case Twalk:
739 			if(fid->file != Qdir){
740 				f.type = Rerror;
741 				f.data = Enotdir;
742 			} else if(strcmp(f.name, "cpunote") == 0){
743 				fid->file = Qcpunote;
744 				f.qid = fstab[Qcpunote].qid;
745 			} else {
746 				f.type = Rerror;
747 				f.data = Enotexist;
748 			}
749 			break;
750 		case Topen:
751 			if(f.mode != OREAD){
752 				f.type = Rerror;
753 				f.data = Eperm;
754 			}
755 			break;
756 		case Tcreate:
757 			f.type = Rerror;
758 			f.data = Eperm;
759 			break;
760 		case Tread:
761 			if(fsread(fd, fid, &f) < 0)
762 				goto err;
763 			doreply = 0;
764 			break;
765 		case Twrite:
766 			f.type = Rerror;
767 			f.data = Eperm;
768 			break;
769 		case Tclunk:
770 			fid->file = -1;
771 			break;
772 		case Tremove:
773 			f.type = Rerror;
774 			f.data = Eperm;
775 			break;
776 		case Tstat:
777 			if(fsstat(fd, fid, &f) < 0)
778 				goto err;
779 			doreply = 0;
780 			break;
781 		case Twstat:
782 			f.type = Rerror;
783 			f.data = Eperm;
784 			break;
785 		}
786 		if(doreply)
787 			if(fsreply(fd, &f) < 0)
788 				break;
789 	}
790 err:
791 	close(fd);
792 }
793 
794 char 	notebuf[ERRLEN];
795 
796 void
797 catcher(void*, char *text)
798 {
799 	strncpy(notebuf, text, ERRLEN);
800 	notebuf[ERRLEN-1] = 0;
801 	noted(NCONT);
802 }
803 
804 /*
805  *  mount in /dev a note file for the remote side to read.
806  */
807 void
808 lclnoteproc(int netfd)
809 {
810 	int exportfspid, n;
811 	Waitmsg w;
812 	Note *np;
813 	int pfd[2];
814 
815 	if(pipe(pfd) < 0){
816 		fprint(2, "cpu: can't start note proc: %r\n");
817 		return;
818 	}
819 
820 	/* new proc mounts and returns to start exportfs */
821 	switch(exportfspid = rfork(RFPROC|RFNAMEG|RFFDG|RFMEM)){
822 	case -1:
823 		fprint(2, "cpu: can't start note proc: %r\n");
824 		return;
825 	case 0:
826 		close(pfd[0]);
827 		amount(pfd[1], "/dev", MBEFORE, "");
828 		close(pfd[1]);
829 		return;
830 	}
831 
832 	close(netfd);
833 	close(pfd[1]);
834 
835 	/* new proc listens for note file system rpc's */
836 	switch(rfork(RFPROC|RFNAMEG|RFMEM)){
837 	case -1:
838 		fprint(2, "cpu: can't start note proc: %r\n");
839 		_exits(0);
840 	case 0:
841 		notefs(pfd[0]);
842 		_exits(0);
843 	}
844 
845 	/* original proc waits for notes */
846 	notify(catcher);
847 	for(;;) {
848 		*notebuf = 0;
849 		n = wait(&w);
850 		if(n < 0) {
851 			if(*notebuf == 0)
852 				break;
853 			np = mallocz(sizeof(Note), 1);
854 			if(np != nil){
855 				strcpy(np->msg, notebuf);
856 				lock(&nfs);
857 				if(nfs.nfirst == nil)
858 					nfs.nfirst = np;
859 				else
860 					nfs.nlast->next = np;
861 				nfs.nlast = np;
862 				unlock(&nfs);
863 				kick(pfd[0]);
864 			}
865 			unlock(&nfs);
866 		} else if(n == exportfspid)
867 			break;
868 	}
869 
870 	exits(w.msg);
871 }
872