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