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