sys_bsd.c revision 1.25 1 /* $NetBSD: sys_bsd.c,v 1.25 2003/07/14 15:56:29 itojun Exp $ */
2
3 /*
4 * Copyright (c) 1988, 1990, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 #ifndef lint
38 #if 0
39 from: static char sccsid[] = "@(#)sys_bsd.c 8.4 (Berkeley) 5/30/95";
40 #else
41 __RCSID("$NetBSD: sys_bsd.c,v 1.25 2003/07/14 15:56:29 itojun Exp $");
42 #endif
43 #endif /* not lint */
44
45 /*
46 * The following routines try to encapsulate what is system dependent
47 * (at least between 4.x and dos) which is used in telnet.c.
48 */
49
50
51 #include <fcntl.h>
52 #include <sys/types.h>
53 #include <sys/time.h>
54 #include <sys/socket.h>
55 #include <signal.h>
56 #include <stdlib.h>
57 #include <unistd.h>
58 #include <errno.h>
59 #include <poll.h>
60 #include <arpa/telnet.h>
61
62 #include "ring.h"
63
64 #include "fdset.h"
65
66 #include "defines.h"
67 #include "externs.h"
68 #include "types.h"
69
70 #define SIG_FUNC_RET void
71
72 SIG_FUNC_RET susp(int);
73 SIG_FUNC_RET ayt(int);
74
75 SIG_FUNC_RET intr(int);
76 SIG_FUNC_RET intr2(int);
77 SIG_FUNC_RET sendwin(int);
78 SIG_FUNC_RET deadpeer(int);
79
80
81 int
82 tout, /* Output file descriptor */
83 tin, /* Input file descriptor */
84 net;
85
86 struct termios old_tc = { 0 };
87 extern struct termios new_tc;
88
89 # ifndef TCSANOW
90 # ifdef TCSETS
91 # define TCSANOW TCSETS
92 # define TCSADRAIN TCSETSW
93 # define tcgetattr(f, t) ioctl(f, TCGETS, (char *)t)
94 # else
95 # ifdef TCSETA
96 # define TCSANOW TCSETA
97 # define TCSADRAIN TCSETAW
98 # define tcgetattr(f, t) ioctl(f, TCGETA, (char *)t)
99 # else
100 # define TCSANOW TIOCSETA
101 # define TCSADRAIN TIOCSETAW
102 # define tcgetattr(f, t) ioctl(f, TIOCGETA, (char *)t)
103 # endif
104 # endif
105 # define tcsetattr(f, a, t) ioctl(f, a, (char *)t)
106 # define cfgetospeed(ptr) ((ptr)->c_cflag&CBAUD)
107 # ifdef CIBAUD
108 # define cfgetispeed(ptr) (((ptr)->c_cflag&CIBAUD) >> IBSHIFT)
109 # else
110 # define cfgetispeed(ptr) cfgetospeed(ptr)
111 # endif
112 # endif /* TCSANOW */
113
114
115 void
116 init_sys(void)
117 {
118 tout = fileno(stdout);
119 tin = fileno(stdin);
120
121 errno = 0;
122 }
123
124
125 int
126 TerminalWrite(char *buf, int n)
127 {
128 return write(tout, buf, n);
129 }
130
131 int
132 TerminalRead(unsigned char *buf, int n)
133 {
134 return read(tin, buf, n);
135 }
136
137 /*
138 *
139 */
140
141 int
142 TerminalAutoFlush(void)
143 {
144 #ifdef LNOFLSH
145 int flush;
146
147 ioctl(0, TIOCLGET, (char *)&flush);
148 return !(flush&LNOFLSH); /* if LNOFLSH, no autoflush */
149 #else /* LNOFLSH */
150 return 1;
151 #endif /* LNOFLSH */
152 }
153
154 #ifdef KLUDGELINEMODE
155 extern int kludgelinemode;
156 #endif
157 /*
158 * TerminalSpecialChars()
159 *
160 * Look at an input character to see if it is a special character
161 * and decide what to do.
162 *
163 * Output:
164 *
165 * 0 Don't add this character.
166 * 1 Do add this character
167 */
168
169 int
170 TerminalSpecialChars(int c)
171 {
172 if (c == termIntChar) {
173 intp();
174 return 0;
175 } else if (c == termQuitChar) {
176 #ifdef KLUDGELINEMODE
177 if (kludgelinemode)
178 sendbrk();
179 else
180 #endif
181 sendabort();
182 return 0;
183 } else if (c == termEofChar) {
184 if (my_want_state_is_will(TELOPT_LINEMODE)) {
185 sendeof();
186 return 0;
187 }
188 return 1;
189 } else if (c == termSuspChar) {
190 sendsusp();
191 return(0);
192 } else if (c == termFlushChar) {
193 xmitAO(); /* Transmit Abort Output */
194 return 0;
195 } else if (!MODE_LOCAL_CHARS(globalmode)) {
196 if (c == termKillChar) {
197 xmitEL();
198 return 0;
199 } else if (c == termEraseChar) {
200 xmitEC(); /* Transmit Erase Character */
201 return 0;
202 }
203 }
204 return 1;
205 }
206
207
208 /*
209 * Flush output to the terminal
210 */
211
212 void
213 TerminalFlushOutput(void)
214 {
215 (void) ioctl(fileno(stdout), TIOCFLUSH, (char *) 0);
216 }
217
218 void
219 TerminalSaveState(void)
220 {
221 tcgetattr(0, &old_tc);
222
223 new_tc = old_tc;
224 }
225
226 cc_t *
227 tcval(int func)
228 {
229 switch(func) {
230 case SLC_IP: return(&termIntChar);
231 case SLC_ABORT: return(&termQuitChar);
232 case SLC_EOF: return(&termEofChar);
233 case SLC_EC: return(&termEraseChar);
234 case SLC_EL: return(&termKillChar);
235 case SLC_XON: return(&termStartChar);
236 case SLC_XOFF: return(&termStopChar);
237 case SLC_FORW1: return(&termForw1Char);
238 case SLC_FORW2: return(&termForw2Char);
239 # ifdef VDISCARD
240 case SLC_AO: return(&termFlushChar);
241 # endif
242 # ifdef VSUSP
243 case SLC_SUSP: return(&termSuspChar);
244 # endif
245 # ifdef VWERASE
246 case SLC_EW: return(&termWerasChar);
247 # endif
248 # ifdef VREPRINT
249 case SLC_RP: return(&termRprntChar);
250 # endif
251 # ifdef VLNEXT
252 case SLC_LNEXT: return(&termLiteralNextChar);
253 # endif
254 # ifdef VSTATUS
255 case SLC_AYT: return(&termAytChar);
256 # endif
257
258 case SLC_SYNCH:
259 case SLC_BRK:
260 case SLC_EOR:
261 default:
262 return((cc_t *)0);
263 }
264 }
265
266 void
267 TerminalDefaultChars(void)
268 {
269 memmove(new_tc.c_cc, old_tc.c_cc, sizeof(old_tc.c_cc));
270 # ifndef VDISCARD
271 termFlushChar = CONTROL('O');
272 # endif
273 # ifndef VWERASE
274 termWerasChar = CONTROL('W');
275 # endif
276 # ifndef VREPRINT
277 termRprntChar = CONTROL('R');
278 # endif
279 # ifndef VLNEXT
280 termLiteralNextChar = CONTROL('V');
281 # endif
282 # ifndef VSTART
283 termStartChar = CONTROL('Q');
284 # endif
285 # ifndef VSTOP
286 termStopChar = CONTROL('S');
287 # endif
288 # ifndef VSTATUS
289 termAytChar = CONTROL('T');
290 # endif
291 }
292
293 #ifdef notdef
294 void
295 TerminalRestoreState(void)
296 {
297 }
298 #endif
299
300 /*
301 * TerminalNewMode - set up terminal to a specific mode.
302 * MODE_ECHO: do local terminal echo
303 * MODE_FLOW: do local flow control
304 * MODE_TRAPSIG: do local mapping to TELNET IAC sequences
305 * MODE_EDIT: do local line editing
306 *
307 * Command mode:
308 * MODE_ECHO|MODE_EDIT|MODE_FLOW|MODE_TRAPSIG
309 * local echo
310 * local editing
311 * local xon/xoff
312 * local signal mapping
313 *
314 * Linemode:
315 * local/no editing
316 * Both Linemode and Single Character mode:
317 * local/remote echo
318 * local/no xon/xoff
319 * local/no signal mapping
320 */
321
322
323 void
324 TerminalNewMode(int f)
325 {
326 static int prevmode = 0;
327 struct termios tmp_tc;
328 int onoff;
329 int old;
330 cc_t esc;
331
332 globalmode = f&~MODE_FORCE;
333 if (prevmode == f)
334 return;
335
336 /*
337 * Write any outstanding data before switching modes
338 * ttyflush() returns 0 only when there is no more data
339 * left to write out, it returns -1 if it couldn't do
340 * anything at all, otherwise it returns 1 + the number
341 * of characters left to write.
342 #ifndef USE_TERMIO
343 * We would really like to ask the kernel to wait for the output
344 * to drain, like we can do with the TCSADRAIN, but we don't have
345 * that option. The only ioctl that waits for the output to
346 * drain, TIOCSETP, also flushes the input queue, which is NOT
347 * what we want (TIOCSETP is like TCSADFLUSH).
348 #endif
349 */
350 old = ttyflush(SYNCHing|flushout);
351 if (old < 0 || old > 1) {
352 tcgetattr(tin, &tmp_tc);
353 do {
354 /*
355 * Wait for data to drain, then flush again.
356 */
357 tcsetattr(tin, TCSADRAIN, &tmp_tc);
358 old = ttyflush(SYNCHing|flushout);
359 if (old == -2)
360 return;
361 } while (old < 0 || old > 1);
362 }
363
364 old = prevmode;
365 prevmode = f&~MODE_FORCE;
366 tmp_tc = new_tc;
367
368 if (f&MODE_ECHO) {
369 tmp_tc.c_lflag |= ECHO;
370 tmp_tc.c_oflag |= ONLCR;
371 if (crlf)
372 tmp_tc.c_iflag |= ICRNL;
373 } else {
374 tmp_tc.c_lflag &= ~ECHO;
375 tmp_tc.c_oflag &= ~ONLCR;
376 # ifdef notdef
377 if (crlf)
378 tmp_tc.c_iflag &= ~ICRNL;
379 # endif
380 }
381
382 if ((f&MODE_FLOW) == 0) {
383 tmp_tc.c_iflag &= ~(IXOFF|IXON); /* Leave the IXANY bit alone */
384 } else {
385 if (restartany < 0) {
386 tmp_tc.c_iflag |= IXOFF|IXON; /* Leave the IXANY bit alone */
387 } else if (restartany > 0) {
388 tmp_tc.c_iflag |= IXOFF|IXON|IXANY;
389 } else {
390 tmp_tc.c_iflag |= IXOFF|IXON;
391 tmp_tc.c_iflag &= ~IXANY;
392 }
393 }
394
395 if ((f&MODE_TRAPSIG) == 0) {
396 tmp_tc.c_lflag &= ~ISIG;
397 localchars = 0;
398 } else {
399 tmp_tc.c_lflag |= ISIG;
400 localchars = 1;
401 }
402
403 if (f&MODE_EDIT) {
404 tmp_tc.c_lflag |= ICANON;
405 } else {
406 tmp_tc.c_lflag &= ~ICANON;
407 tmp_tc.c_iflag &= ~ICRNL;
408 tmp_tc.c_cc[VMIN] = 1;
409 tmp_tc.c_cc[VTIME] = 0;
410 }
411
412 if ((f&(MODE_EDIT|MODE_TRAPSIG)) == 0) {
413 tmp_tc.c_lflag &= ~IEXTEN;
414 }
415
416 if (f&MODE_SOFT_TAB) {
417 # ifdef OXTABS
418 tmp_tc.c_oflag |= OXTABS;
419 # endif
420 # ifdef TABDLY
421 tmp_tc.c_oflag &= ~TABDLY;
422 tmp_tc.c_oflag |= TAB3;
423 # endif
424 } else {
425 # ifdef OXTABS
426 tmp_tc.c_oflag &= ~OXTABS;
427 # endif
428 # ifdef TABDLY
429 tmp_tc.c_oflag &= ~TABDLY;
430 # endif
431 }
432
433 if (f&MODE_LIT_ECHO) {
434 # ifdef ECHOCTL
435 tmp_tc.c_lflag &= ~ECHOCTL;
436 # endif
437 } else {
438 # ifdef ECHOCTL
439 tmp_tc.c_lflag |= ECHOCTL;
440 # endif
441 }
442
443 if (f == -1) {
444 onoff = 0;
445 } else {
446 if (f & MODE_INBIN)
447 tmp_tc.c_iflag &= ~ISTRIP;
448 else
449 tmp_tc.c_iflag |= ISTRIP;
450 if (f & MODE_OUTBIN) {
451 tmp_tc.c_cflag &= ~(CSIZE|PARENB);
452 tmp_tc.c_cflag |= CS8;
453 tmp_tc.c_oflag &= ~OPOST;
454 } else {
455 tmp_tc.c_cflag &= ~(CSIZE|PARENB);
456 tmp_tc.c_cflag |= old_tc.c_cflag & (CSIZE|PARENB);
457 tmp_tc.c_oflag |= OPOST;
458 }
459 onoff = 1;
460 }
461
462 if (f != -1) {
463 (void) signal(SIGTSTP, susp);
464 (void) signal(SIGINFO, ayt);
465 #if defined(USE_TERMIO) && defined(NOKERNINFO)
466 tmp_tc.c_lflag |= NOKERNINFO;
467 #endif
468 /*
469 * We don't want to process ^Y here. It's just another
470 * character that we'll pass on to the back end. It has
471 * to process it because it will be processed when the
472 * user attempts to read it, not when we send it.
473 */
474 # ifdef VDSUSP
475 tmp_tc.c_cc[VDSUSP] = (cc_t)(_POSIX_VDISABLE);
476 # endif
477 /*
478 * If the VEOL character is already set, then use VEOL2,
479 * otherwise use VEOL.
480 */
481 esc = (rlogin != _POSIX_VDISABLE) ? rlogin : escape;
482 if ((tmp_tc.c_cc[VEOL] != esc)
483 # ifdef VEOL2
484 && (tmp_tc.c_cc[VEOL2] != esc)
485 # endif
486 ) {
487 if (tmp_tc.c_cc[VEOL] == (cc_t)(_POSIX_VDISABLE))
488 tmp_tc.c_cc[VEOL] = esc;
489 # ifdef VEOL2
490 else if (tmp_tc.c_cc[VEOL2] == (cc_t)(_POSIX_VDISABLE))
491 tmp_tc.c_cc[VEOL2] = esc;
492 # endif
493 }
494 } else {
495 (void) signal(SIGINFO, (void (*)(int)) ayt_status);
496 (void) signal(SIGTSTP, SIG_DFL);
497 (void) sigsetmask(sigblock(0) & ~(1<<(SIGTSTP-1)));
498 tmp_tc = old_tc;
499 }
500 if (tcsetattr(tin, TCSADRAIN, &tmp_tc) < 0)
501 tcsetattr(tin, TCSANOW, &tmp_tc);
502
503 ioctl(tin, FIONBIO, (char *)&onoff);
504 ioctl(tout, FIONBIO, (char *)&onoff);
505 #if defined(TN3270)
506 if (noasynchtty == 0) {
507 ioctl(tin, FIOASYNC, (char *)&onoff);
508 }
509 #endif /* defined(TN3270) */
510
511 }
512
513 void
514 TerminalSpeeds(long *ispeed, long *ospeed)
515 {
516 long in, out;
517
518 out = cfgetospeed(&old_tc);
519 in = cfgetispeed(&old_tc);
520 if (in == 0)
521 in = out;
522
523 *ispeed = in;
524 *ospeed = out;
525 }
526
527 int
528 TerminalWindowSize(long *rows, long *cols)
529 {
530 struct winsize ws;
531
532 if (ioctl(fileno(stdin), TIOCGWINSZ, (char *)&ws) >= 0) {
533 *rows = ws.ws_row;
534 *cols = ws.ws_col;
535 return 1;
536 }
537 return 0;
538 }
539
540 int
541 NetClose(int fd)
542 {
543 return close(fd);
544 }
545
546
547 void
548 NetNonblockingIO(int fd, int onoff)
549 {
550 ioctl(fd, FIONBIO, (char *)&onoff);
551 }
552
553 #ifdef TN3270
554 void
555 NetSigIO(int fd, int onoff)
556 {
557 ioctl(fd, FIOASYNC, (char *)&onoff); /* hear about input */
558 }
559
560 void
561 NetSetPgrp(int fd)
562 {
563 int myPid;
564
565 myPid = getpid();
566 fcntl(fd, F_SETOWN, myPid);
567 }
568 #endif /*defined(TN3270)*/
569
570 /*
572 * Various signal handling routines.
573 */
574
575 /* ARGSUSED */
576 SIG_FUNC_RET
577 deadpeer(int sig)
578 {
579 setcommandmode();
580 longjmp(peerdied, -1);
581 }
582
583 /* ARGSUSED */
584 SIG_FUNC_RET
585 intr(int sig)
586 {
587 if (localchars) {
588 intp();
589 return;
590 }
591 setcommandmode();
592 longjmp(toplevel, -1);
593 }
594
595 /* ARGSUSED */
596 SIG_FUNC_RET
597 intr2(int sig)
598 {
599 if (localchars) {
600 #ifdef KLUDGELINEMODE
601 if (kludgelinemode)
602 sendbrk();
603 else
604 #endif
605 sendabort();
606 return;
607 }
608 }
609
610 /* ARGSUSED */
611 SIG_FUNC_RET
612 susp(int sig)
613 {
614 if ((rlogin != _POSIX_VDISABLE) && rlogin_susp())
615 return;
616 if (localchars)
617 sendsusp();
618 }
619
620 /* ARGSUSED */
621 SIG_FUNC_RET
622 sendwin(int sig)
623 {
624 if (connected) {
625 sendnaws();
626 }
627 }
628
629 /* ARGSUSED */
630 SIG_FUNC_RET
631 ayt(int sig)
632 {
633 if (connected)
634 sendayt();
635 else
636 ayt_status();
637 }
638
639
640 void
642 sys_telnet_init(void)
643 {
644 (void) signal(SIGINT, intr);
645 (void) signal(SIGQUIT, intr2);
646 (void) signal(SIGPIPE, deadpeer);
647 (void) signal(SIGWINCH, sendwin);
648 (void) signal(SIGTSTP, susp);
649 (void) signal(SIGINFO, ayt);
650
651 setconnmode(0);
652
653 NetNonblockingIO(net, 1);
654
655 #ifdef TN3270
656 if (noasynchnet == 0) { /* DBX can't handle! */
657 NetSigIO(net, 1);
658 NetSetPgrp(net);
659 }
660 #endif /* defined(TN3270) */
661
662 if (SetSockOpt(net, SOL_SOCKET, SO_OOBINLINE, 1) == -1) {
663 perror("SetSockOpt");
664 }
665 }
666
667 /*
668 * Process rings -
669 *
670 * This routine tries to fill up/empty our various rings.
671 *
672 * The parameter specifies whether this is a poll operation,
673 * or a block-until-something-happens operation.
674 *
675 * The return value is 1 if something happened, 0 if not, < 0 if an
676 * error occured.
677 */
678
679 int
680 process_rings(int netin, int netout, int netex, int ttyin, int ttyout,
681 int dopoll) /* If 0, then block until something to do */
682 {
683 struct pollfd set[3];
684 int c;
685 /* One wants to be a bit careful about setting returnValue
686 * to one, since a one implies we did some useful work,
687 * and therefore probably won't be called to block next
688 * time (TN3270 mode only).
689 */
690 int returnValue = 0;
691
692 set[0].fd = net;
693 set[0].events = (netout ? POLLOUT : 0) | (netin ? POLLIN : 0) |
694 (netex ? POLLPRI : 0);
695 set[1].fd = tout;
696 set[1].events = ttyout ? POLLOUT : 0;
697 set[2].fd = tin;
698 set[2].events = ttyin ? POLLIN : 0;
699
700 if ((c = poll(set, 3, dopoll ? 0 : INFTIM)) < 0) {
701 if (c == -1) {
702 /*
703 * we can get EINTR if we are in line mode,
704 * and the user does an escape (TSTP), or
705 * some other signal generator.
706 */
707 if (errno == EINTR) {
708 return 0;
709 }
710 #ifdef TN3270
711 /*
712 * we can get EBADF if we were in transparent
713 * mode, and the transcom process died.
714 */
715 if (errno == EBADF)
716 return 0;
717 #endif /* defined(TN3270) */
718 /* I don't like this, does it ever happen? */
719 printf("sleep(5) from telnet, after poll\r\n");
720 sleep(5);
721 }
722 return 0;
723 }
724
725 /*
726 * Any urgent data?
727 */
728 if (set[0].revents & POLLPRI) {
729 SYNCHing = 1;
730 (void) ttyflush(1); /* flush already enqueued data */
731 }
732
733 /*
734 * Something to read from the network...
735 */
736 if (set[0].revents & POLLIN) {
737 int canread;
738
739 canread = ring_empty_consecutive(&netiring);
740 c = recv(net, (char *)netiring.supply, canread, 0);
741 if (c < 0 && errno == EWOULDBLOCK) {
742 c = 0;
743 } else if (c <= 0) {
744 return -1;
745 }
746 if (netdata) {
747 Dump('<', netiring.supply, c);
748 }
749 if (c)
750 ring_supplied(&netiring, c);
751 returnValue = 1;
752 }
753
754 /*
755 * Something to read from the tty...
756 */
757 if (set[2].revents & POLLIN) {
758 c = TerminalRead(ttyiring.supply, ring_empty_consecutive(&ttyiring));
759 if (c < 0 && errno == EIO)
760 c = 0;
761 if (c < 0 && errno == EWOULDBLOCK) {
762 c = 0;
763 } else {
764 if (c < 0) {
765 return -1;
766 }
767 if (c == 0) {
768 /* must be an EOF... */
769 if (MODE_LOCAL_CHARS(globalmode) && isatty(tin)) {
770 *ttyiring.supply = termEofChar;
771 c = 1;
772 } else {
773 clienteof = 1;
774 shutdown(net, 1);
775 return 0;
776 }
777 }
778 if (termdata) {
779 Dump('<', ttyiring.supply, c);
780 }
781 ring_supplied(&ttyiring, c);
782 }
783 returnValue = 1; /* did something useful */
784 }
785
786 if (set[0].revents & POLLOUT) {
787 returnValue |= netflush();
788 }
789 if (set[1].revents & POLLOUT) {
790 returnValue |= (ttyflush(SYNCHing|flushout) > 0);
791 }
792
793 return returnValue;
794 }
795