xref: /plan9/sys/src/cmd/con/con.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
1 #include <u.h>
2 #include <libc.h>
3 
4 #include "rustream.h"
5 #include "ruttyio.h"
6 #include "rusignal.h"
7 #include "rufilio.h"
8 
9 int debug;		/* true if debugging */
10 int ctl = -1;		/* control fd (for break's) */
11 int raw;		/* true if raw is on */
12 int consctl = -1;	/* control fd for cons */
13 int ttypid;		/* pid's if the 2 processes (used to kill them) */
14 int msgfd = -1;		/* mesgld file descriptor (for signals to be written to) */
15 int outfd = 1;		/* local output file descriptor */
16 int cooked;		/* non-zero forces cooked mode */
17 int returns;		/* non-zero forces carriage returns not to be filtered out */
18 int	strip;		/* strip off parity bits */
19 char firsterr[2*ERRLEN];
20 char transerr[2*ERRLEN];
21 int limited;
22 char *remuser;
23 int verbose;
24 int baud;
25 int notkbd;
26 int nltocr;		/* translate kbd nl to cr  and vice versa */
27 
28 typedef struct Msg Msg;
29 #define MAXMSG (2*8192)
30 
31 int	dodial(char*, char*, char*);
32 void	fromkbd(int);
33 void	fromnet(int);
34 long	iread(int, void*, int);
35 long	iwrite(int, void*, int);
36 int	menu(int);
37 void	msgfromkbd(int);
38 void	msgfromnet(int);
39 void	msgnotifyf(void*, char*);
40 int	msgwrite(int, void*, int);
41 void	notifyf(void*, char*);
42 void	pass(int, int, int);
43 void	rawoff(void);
44 void	rawon(void);
45 int	readupto(int, char*, int);
46 int	sendctl(int, int);
47 int	sendctl1(int, int, int);
48 void	stdcon(int);
49 char*	system(int, char*);
50 void	dosystem(int, char*);
51 int	wasintr(void);
52 void	punt(char*);
53 char*	syserr(void);
54 void	seterr(char*);
55 
56 /* protocols */
57 void	device(char*, char*);
58 void	rlogin(char*, char*);
59 void	simple(char*, char*);
60 
61 void
62 usage(void)
63 {
64 	punt("usage: con [-drCvsn] [-l [user]] [-c cmd] net!host[!service]");
65 }
66 
67 void
68 main(int argc, char *argv[])
69 {
70 	char *dest;
71 	char *cmd = 0;
72 
73 	returns = 1;
74 	ARGBEGIN{
75 	case 'b':
76 		baud = atoi(ARGF());
77 		break;
78 	case 'd':
79 		debug = 1;
80 		break;
81 	case 'l':
82 		limited = 1;
83 		if(argv[1][0] != '-')
84 			remuser = ARGF();
85 		break;
86 	case 'n':
87 		notkbd = 1;
88 		break;
89 	case 'r':
90 		returns = 0;
91 		break;
92 	case 'R':
93 		nltocr = 1;
94 		break;
95 	case 'C':
96 		cooked = 1;
97 		break;
98 	case 'c':
99 		cmd = ARGF();
100 		break;
101 	case 'v':
102 		verbose = 1;
103 		break;
104 	case 's':
105 		strip = 1;
106 		break;
107 	default:
108 		usage();
109 	}ARGEND
110 
111 	if(argc != 1){
112 		if(remuser == 0)
113 			usage();
114 		dest = remuser;
115 		remuser = 0;
116 	} else
117 		dest = argv[0];
118 	if(*dest == '/' && strchr(dest, '!') == 0)
119 		device(dest, cmd);
120 	else if(limited){
121 		simple(dest, cmd);	/* doesn't return if dialout succeeds */
122 		rlogin(dest, cmd);	/* doesn't return if dialout succeeds */
123 	} else {
124 		rlogin(dest, cmd);	/* doesn't return if dialout succeeds */
125 		simple(dest, cmd);	/* doesn't return if dialout succeeds */
126 	}
127 	punt(firsterr);
128 }
129 
130 /*
131  *  just dial and use as a byte stream with remote echo
132  */
133 void
134 simple(char *dest, char *cmd)
135 {
136 	int net;
137 
138 	net = dodial(dest, 0, 0);
139 	if(net < 0)
140 		return;
141 
142 	if(cmd)
143 		dosystem(net, cmd);
144 
145 	if(!cooked)
146 		rawon();
147 	stdcon(net);
148 	exits(0);
149 }
150 
151 /*
152  *  dial, do UCB authentication, use as a byte stream with local echo
153  *
154  *  return if dial failed
155  */
156 void
157 rlogin(char *dest, char *cmd)
158 {
159 	int net;
160 	char buf[2*NAMELEN];
161 	char *p;
162 	char *localuser;
163 
164 	/* only useful on TCP */
165 	if(strchr(dest, '!')
166 	&& (strncmp(dest, "tcp!", 4)!=0 && strncmp(dest, "net!", 4)!=0))
167 		return;
168 
169 	net = dodial(dest, "tcp", "login");
170 	if(net < 0)
171 		return;
172 
173 	/*
174 	 *  do UCB rlogin authentication
175 	 */
176 	localuser = getuser();
177 	if(remuser == 0){
178 		if(limited)
179 			remuser = ":";
180 		else
181 			remuser = localuser;
182 	}
183 	p = getenv("TERM");
184 	if(p == 0)
185 		p = "p9";
186 	if(write(net, "", 1)<0
187 	|| write(net, localuser, strlen(localuser)+1)<0
188 	|| write(net, remuser, strlen(remuser)+1)<0
189 	|| write(net, p, strlen(p)+1)<0){
190 		close(net);
191 		punt("BSD authentication failed");
192 	}
193 	if(read(net, buf, 1) != 1)
194 		punt("BSD authentication failed1");
195 	if(buf[0] != 0){
196 		fprint(2, "con: remote error: ");
197 		while(read(net, buf, 1) == 1){
198 			write(2, buf, 1);
199 			if(buf[0] == '\n')
200 				break;
201 		}
202 		exits("read");
203 	}
204 
205 	if(cmd)
206 		dosystem(net, cmd);
207 
208 	if(!cooked)
209 		rawon();
210 	nltocr = 1;
211 	stdcon(net);
212 	exits(0);
213 }
214 
215 /*
216  *  just open a device and use it as a connection
217  */
218 void
219 device(char *dest, char *cmd)
220 {
221 	int net;
222 	char cname[3*NAMELEN];
223 
224 	net = open(dest, ORDWR);
225 	if(net < 0) {
226 		fprint(2, "con: cannot open %s: %r\n", dest);
227 		exits("open");
228 	}
229 	sprint(cname, "%sctl", dest);
230 	ctl = open(cname, ORDWR);
231 	if(ctl >= 0 && baud > 0)
232 		fprint(ctl, "b%d", baud);
233 
234 	if(cmd)
235 		dosystem(net, cmd);
236 
237 	if(!cooked)
238 		rawon();
239 	stdcon(net);
240 	exits(0);
241 }
242 
243 /*
244  *  ignore interrupts
245  */
246 void
247 notifyf(void *a, char *msg)
248 {
249 	USED(a);
250 
251 	if(strstr(msg, "closed pipe")
252 	|| strcmp(msg, "interrupt") == 0
253 	|| strcmp(msg, "hangup") == 0)
254 		noted(NCONT);
255 	noted(NDFLT);
256 }
257 
258 /*
259  *  turn keyboard raw mode on
260  */
261 void
262 rawon(void)
263 {
264 	if(debug)
265 		fprint(2, "rawon\n");
266 	if(raw)
267 		return;
268 	if(consctl < 0)
269 		consctl = open("/dev/consctl", OWRITE);
270 	if(consctl < 0){
271 		fprint(2, "can't open consctl\n");
272 		return;
273 	}
274 	write(consctl, "rawon", 5);
275 	raw = 1;
276 }
277 
278 /*
279  *  turn keyboard raw mode off
280  */
281 void
282 rawoff(void)
283 {
284 	if(debug)
285 		fprint(2, "rawoff\n");
286 	if(raw == 0)
287 		return;
288 	if(consctl < 0)
289 		consctl = open("/dev/consctl", OWRITE);
290 	if(consctl < 0){
291 		fprint(2, "can't open consctl\n");
292 		return;
293 	}
294 	write(consctl, "rawoff", 6);
295 	raw = 0;
296 }
297 
298 /*
299  *  control menu
300  */
301 #define STDHELP	"\t(b)reak, (q)uit, (i)nterrupt, toggle printing (r)eturns, (.)continue, (!cmd)\n"
302 
303 int
304 menu(int net)
305 {
306 	char buf[MAXMSG];
307 	long n;
308 	int done;
309 	int wasraw = raw;
310 
311 	if(wasraw)
312 		rawoff();
313 
314 	fprint(2, ">>> ");
315 	for(done = 0; !done; ){
316 		n = read(0, buf, sizeof(buf)-1);
317 		if(n <= 0)
318 			return -1;
319 		buf[n] = 0;
320 		switch(buf[0]){
321 		case '!':
322 			print(buf);
323 			system(net, buf+1);
324 			print("!\n");
325 			done = 1;
326 			break;
327 		case '.':
328 			done = 1;
329 			break;
330 		case 'q':
331 			return -1;
332 			break;
333 		case 'i':
334 			buf[0] = 0x1c;
335 			if(msgfd <= 0)
336 				write(net, buf, 1);
337 			else
338 				sendctl1(msgfd, M_SIGNAL, SIGQUIT);
339 			done = 1;
340 			break;
341 		case 'b':
342 			if(msgfd >= 0)
343 				sendctl(msgfd, M_BREAK);
344 			else if(ctl >= 0)
345 				write(ctl, "k", 1);
346 			done = 1;
347 			break;
348 		case 'r':
349 			returns = 1-returns;
350 			done = 1;
351 			break;
352 		default:
353 			fprint(2, STDHELP);
354 			break;
355 		}
356 		if(!done)
357 			fprint(2, ">>> ");
358 	}
359 
360 	if(wasraw)
361 		rawon();
362 	else
363 		rawoff();
364 	return 0;
365 }
366 
367 /*
368  *  the real work.  two processes pass bytes back and forth between the
369  *  terminal and the network.
370  */
371 void
372 stdcon(int net)
373 {
374 	int netpid;
375 
376 	ttypid = getpid();
377 	switch(netpid = rfork(RFMEM|RFPROC)){
378 	case -1:
379 		perror("con");
380 		exits("fork");
381 	case 0:
382 		notify(notifyf);
383 		fromnet(net);
384 		postnote(PNPROC, ttypid, "kill");
385 		exits(0);
386 	default:
387 		notify(notifyf);
388 		fromkbd(net);
389 		if(notkbd)
390 			for(;;)sleep(0);
391 		postnote(PNPROC, netpid, "kill");
392 		exits(0);
393 	}
394 }
395 
396 /*
397  *  Read the keyboard and write it to the network.  '^\' gets us into
398  *  the menu.
399  */
400 void
401 fromkbd(int net)
402 {
403 	long n;
404 	char buf[MAXMSG];
405 	char *p, *ep;
406 	int eofs;
407 
408 	eofs = 0;
409 	for(;;){
410 		n = read(0, buf, sizeof(buf));
411 		if(n < 0){
412 			if(wasintr()){
413 				if(cooked){
414 					buf[0] = 0x7f;
415 					n = 1;
416 				} else
417 					continue;
418 			} else
419 				return;
420 		}
421 		if(n == 0){
422 			if(++eofs > 32)
423 				return;
424 		} else
425 			eofs = 0;
426 		if(n && memchr(buf, 0x1c, n)){
427 			if(menu(net) < 0)
428 				return;
429 		}else{
430 			if(cooked && n==0){
431 				buf[0] = 0x4;
432 				n = 1;
433 			}
434 			if(nltocr){
435 				ep = buf+n;
436 				for(p = buf; p < ep; p++)
437 					switch(*p){
438 					case '\r':
439 						*p = '\n';
440 						break;
441 					case '\n':
442 						*p = '\r';
443 						break;
444 					}
445 			}
446 			if(iwrite(net, buf, n) != n)
447 				return;
448 		}
449 	}
450 }
451 
452 /*
453  *  Read from the network and write to the screen.
454  *  Filter out spurious carriage returns.
455  */
456 void
457 fromnet(int net)
458 {
459 	long n;
460 	char buf[MAXMSG];
461 	char *cp, *ep;
462 
463 	for(;;){
464 		n = iread(net, buf, sizeof(buf));
465 		if(n < 0)
466 			return;
467 		if(n == 0)
468 			continue;
469 
470 		if (strip)
471 			for (cp=buf; cp<buf+n; cp++)
472 				*cp &= 0177;
473 
474 		if(!returns){
475 			/* convert cr's to null's */
476 			cp = buf;
477 			ep = buf + n;
478 			while(cp < ep && (cp = memchr(cp, '\r', ep-cp))){
479 				memmove(cp, cp+1, ep-cp-1);
480 				ep--;
481 				n--;
482 			}
483 		}
484 
485 		if(n > 0 && iwrite(outfd, buf, n) != n){
486 			if(outfd == 1)
487 				return;
488 			outfd = 1;
489 			if(iwrite(1, buf, n) != n)
490 				return;
491 		}
492 	}
493 }
494 
495 /*
496  *  dial and return a data connection
497  */
498 int
499 dodial(char *dest, char *net, char *service)
500 {
501 	char name[3*NAMELEN];
502 	char devdir[3*NAMELEN];
503 	int data;
504 
505 	devdir[0] = 0;
506 	strcpy(name, netmkaddr(dest, net, service));
507 	data = dial(name, 0, devdir, &ctl);
508 	if(data < 0){
509 		seterr(name);
510 		return -1;
511 	}
512 	fprint(2, "connected to %s on %s\n", name, devdir);
513 	return data;
514 }
515 
516 void
517 dosystem(int fd, char *cmd)
518 {
519 	char *p;
520 
521 	p = system(fd, cmd);
522 	if(*p){
523 		print("con: %s terminated with %s\n", cmd, p);
524 		exits(p);
525 	}
526 }
527 
528 /*
529  *  run a command with the network connection as standard IO
530  */
531 char *
532 system(int fd, char *cmd)
533 {
534 	int pid;
535 	int p;
536 	static Waitmsg msg;
537 	int pfd[2];
538 	int n;
539 	char buf[4096];
540 
541 	if(pipe(pfd) < 0){
542 		perror("pipe");
543 		return "pipe failed";
544 	}
545 	outfd = pfd[1];
546 
547 	close(consctl);
548 	consctl = -1;
549 	switch(pid = fork()){
550 	case -1:
551 		perror("con");
552 		return "fork failed";
553 	case 0:
554 		close(pfd[1]);
555 		dup(pfd[0], 0);
556 		dup(pfd[0], 1);
557 		close(ctl);
558 		close(fd);
559 		close(pfd[0]);
560 		if(*cmd)
561 			execl("/bin/rc", "rc", "-c", cmd, 0);
562 		else
563 			execl("/bin/rc", "rc", 0);
564 		perror("con");
565 		exits("exec");
566 		break;
567 	default:
568 		close(pfd[0]);
569 		while((n = read(pfd[1], buf, sizeof(buf))) > 0){
570 			if(msgfd >= 0){
571 				if(msgwrite(fd, buf, n) != n)
572 					break;
573 			} else {
574 				if(write(fd, buf, n) != n)
575 					break;
576 			}
577 		}
578 		p = wait(&msg);
579 		outfd = 1;
580 		close(pfd[1]);
581 		if(p < 0 || p != pid)
582 			return "lost child";
583 		break;
584 	}
585 	return msg.msg;
586 }
587 
588 int
589 wasintr(void)
590 {
591 	return strcmp(syserr(), "interrupted") == 0;
592 }
593 
594 void
595 punt(char *msg)
596 {
597 	if(*msg == 0)
598 		msg = transerr;
599 	fprint(2, "con: %s\n", msg);
600 	exits(msg);
601 }
602 
603 char*
604 syserr(void)
605 {
606 	static char err[ERRLEN];
607 	errstr(err);
608 	return err;
609 }
610 
611 void
612 seterr(char *addr)
613 {
614 	char *se = syserr();
615 
616 	if(verbose)
617 		fprint(2, "'%s' calling %s\n", se, addr);
618 	if(firsterr[0] && (strstr(se, "translate") ||
619 	 strstr(se, "file does not exist") ||
620 	 strstr(se, "unknown address") ||
621 	 strstr(se, "directory entry not found")))
622 		return;
623 	strcpy(firsterr, se);
624 }
625 
626 
627 long
628 iread(int f, void *a, int n)
629 {
630 	long m;
631 
632 	for(;;){
633 		m = read(f, a, n);
634 		if(m >= 0 || !wasintr())
635 			break;
636 	}
637 	return m;
638 }
639 
640 long
641 iwrite(int f, void *a, int n)
642 {
643 	long m;
644 
645 	m = write(f, a, n);
646 	if(m < 0 && wasintr())
647 		return n;
648 	return m;
649 }
650 
651 /*
652  *  The rest is to support the V10 mesgld protocol.
653  */
654 
655 /*
656  *  network orderings
657  */
658 #define get2byte(p) ((p)[0] + ((p)[1]<<8))
659 #define get4byte(p) ((p)[0] + ((p)[1]<<8) + ((p)[2]<<16) + ((p)[3]<<24))
660 #define put2byte(p, i) ((p)[0]=(i), (p)[1]=(i)>>8)
661 #define put4byte(p, i) ((p)[0]=(i), (p)[1]=(i)>>8, (p)[2]=(i)>>16, (p)[3]=(i)>>24)
662 
663 /*
664  *  tty parameters
665  */
666 int sgflags = ECHO;
667 
668 /*
669  *  a mesgld message
670  */
671 struct Msg {
672 	struct mesg h;
673 	char b[MAXMSG];
674 };
675 
676 /*
677  *  convert certain interrupts into mesgld messages
678  */
679 void
680 msgnotifyf(void *a, char *msg)
681 {
682 	USED(a);
683 
684 	if(strstr(msg, "closed pipe"))
685 		noted(NCONT);
686 	if(strcmp(msg, "interrupt") == 0){
687 		sendctl1(msgfd, M_SIGNAL, SIGINT);
688 		noted(NCONT);
689 	}
690 	if(strcmp(msg, "hangup") == 0){
691 		sendctl(msgfd, M_HANGUP);
692 		noted(NCONT);
693 	}
694 	noted(NDFLT);
695 }
696 
697 /*
698  *  send an empty mesgld message
699  */
700 int
701 sendctl(int net, int type)
702 {
703 	Msg m;
704 
705 	m.h.type = type;
706 	m.h.magic = MSGMAGIC;
707 	put2byte(m.h.size, 0);
708 	if(iwrite(net, &m, sizeof(struct mesg)) != sizeof(struct mesg))
709 		return -1;
710 	return 0;
711 }
712 
713 /*
714  *  send a one byte mesgld message
715  */
716 int
717 sendctl1(int net, int type, int parm)
718 {
719 	Msg m;
720 
721 	m.h.type = type;
722 	m.h.magic = MSGMAGIC;
723 	m.b[0] = parm;
724 	put2byte(m.h.size, 1);
725 	if(iwrite(net, &m, sizeof(struct mesg)+1) != sizeof(struct mesg)+1)
726 		return -1;
727 	return 0;
728 }
729 
730 /*
731  *  read n bytes.  return -1 if it fails, 0 otherwise.
732  */
733 int
734 readupto(int from, char *a, int len)
735 {
736 	int n;
737 
738 	while(len > 0){
739 		n = iread(from, a, len);
740 		if(n < 0)
741 			return -1;
742 		a += n;
743 		len -= n;
744 	}
745 	return 0;
746 }
747 
748 /*
749  *  Decode a mesgld message from the network
750  */
751 void
752 msgfromnet(int net)
753 {
754 	ulong com;
755 	struct stioctl *io;
756 	struct sgttyb *sg;
757 	struct ttydevb *td;
758 	struct tchars *tc;
759 	int len;
760 	Msg m;
761 
762 	for(;;){
763 		/* get a complete mesgld message */
764 		if(readupto(net, (char*)&m.h, sizeof(struct mesg)) < 0)
765 			break;
766 		if(m.h.magic != MSGMAGIC){
767 			fprint(2, "con: bad message magic 0x%ux\n", m.h.magic);
768 			break;
769 		}
770 		len = get2byte(m.h.size);
771 		if(len > sizeof(m.b)){
772 			len = sizeof(m.b);
773 			fprint(2, "con: mesgld message too long\n");
774 		}
775 		if(len && readupto(net, m.b, len) < 0)
776 			break;
777 
778 		/* decode */
779 		switch(m.h.type){
780 		case M_HANGUP:
781 			if(debug)
782 				fprint(2, "M_HANGUP\n");
783 			return;
784 		case M_DATA:
785 			if(debug)
786 				fprint(2, "M_DATA %d bytes\n", len);
787 			if(iwrite(outfd, m.b, len) != len){
788 				if(outfd == 1)
789 					return;
790 				outfd = 1;
791 				if(iwrite(outfd, m.b, len) != len)
792 					return;
793 			}
794 			continue;
795 		case M_IOCTL:
796 			break;
797 		default:
798 			/* ignore */
799 			if(debug)
800 				fprint(2, "con: unknown message\n");
801 			continue;
802 		}
803 
804 		/*
805 		 *  answer an ioctl
806 		 */
807 		io = (struct stioctl *)m.b;
808 		com = get4byte(io->com);
809 		if(debug)
810 			fprint(2, "M_IOCTL %lud\n", com);
811 		switch(com){
812 		case FIOLOOKLD:
813 			put4byte(io->data, tty_ld);
814 			len = 0;
815 			break;
816 		case TIOCGETP:
817 			sg = (struct sgttyb *)io->data;
818 			sg->sg_ispeed = sg->sg_ospeed = B9600;
819 			sg->sg_erase = 0010;	/* back space */
820 			sg->sg_kill = 0025;	/* CNTL U */
821 			put2byte(sg->sg_flags, sgflags);
822 			len = sizeof(struct sgttyb);
823 			break;
824 		case TIOCSETN:
825 		case TIOCSETP:
826 			sg = (struct sgttyb *)io->data;
827 			sgflags = get2byte(sg->sg_flags);
828 			if((sgflags&(RAW|CBREAK)) || !(sgflags&ECHO))
829 				rawon();
830 			else
831 				rawoff();
832 			len = 0;
833 			break;
834 		case TIOCGETC:
835 			tc = (struct tchars *)io->data;
836 			tc->t_intrc = 0177;
837 			tc->t_quitc = 0034;
838 			tc->t_startc = 0;
839 			tc->t_stopc = 0;
840 			tc->t_eofc = 0004;
841 			tc->t_brkc = 0;
842 			len = sizeof(struct tchars);
843 			break;
844 		case TIOCSETC:
845 			len = 0;
846 			break;
847 		case TIOCGDEV:
848 			td = (struct ttydevb *)io->data;
849 			td->ispeed = td->ospeed = B9600;
850 			put2byte(td->flags, 0);
851 			len = sizeof(struct ttydevb);
852 			break;
853 		case TIOCSDEV:
854 			len = 0;
855 			break;
856 		default:
857 			/*
858 			 *  unimplemented
859 			 */
860 			m.b[len] = 0;
861 			if(sendctl(net, M_IOCNAK) < 0)
862 				return;
863 			continue;
864 		}
865 
866 		/*
867 		 *  acknowledge
868 		 */
869 		m.h.type = M_IOCACK;
870 		m.h.magic = MSGMAGIC;
871 		len += 4;
872 		put2byte(m.h.size, len);
873 		len += sizeof(struct mesg);
874 		if(iwrite(net, &m, len) != len)
875 			return;
876 	}
877 }
878 
879 /*
880  *  Read the keyboard, convert to mesgld messages, and write it to the network.
881  *  '^\' gets us into the menu.
882  */
883 void
884 msgfromkbd(int net)
885 {
886 	long n;
887 	char buf[MAXMSG];
888 
889 	for(;;){
890 		n = iread(0, buf, sizeof(buf));
891 		if(n < 0)
892 			return;
893 		if(n && memchr(buf, 0034, n)){
894 			if(menu(net) < 0)
895 				return;
896 		} else {
897 			if(msgwrite(net, buf, n) != n)
898 				return;
899 		}
900 	}
901 }
902 
903 int
904 msgwrite(int fd, void *buf, int len)
905 {
906 	Msg m;
907 	int n;
908 
909 	n = len;
910 	memmove(m.b, buf, n);
911 	put2byte(m.h.size, n);
912 	m.h.magic = MSGMAGIC;
913 	m.h.type = M_DATA;
914 	n += sizeof(struct mesg);
915 	if(iwrite(fd, &m, n) != n)
916 		return -1;
917 
918 	put2byte(m.h.size, 0);
919 	m.h.magic = MSGMAGIC;
920 	m.h.type = M_DELIM;
921 	n = sizeof(struct mesg);
922 	if(iwrite(fd, &m, n) != n)
923 		return -1;
924 
925 	return len;
926 }
927 
928