tty_pty.c revision 1.28 1 /* $NetBSD: tty_pty.c,v 1.28 1995/04/19 18:58:14 mycroft Exp $ */
2
3 /*
4 * Copyright (c) 1982, 1986, 1989, 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 * @(#)tty_pty.c 8.2 (Berkeley) 9/23/93
36 */
37
38 /*
39 * Pseudo-teletype Driver
40 * (Actually two drivers, requiring two entries in 'cdevsw')
41 */
42 #include "pty.h" /* XXX */
43
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/ioctl.h>
47 #include <sys/proc.h>
48 #include <sys/tty.h>
49 #include <sys/conf.h>
50 #include <sys/file.h>
51 #include <sys/uio.h>
52 #include <sys/kernel.h>
53 #include <sys/vnode.h>
54
55 #if NPTY == 1
56 #undef NPTY
57 #define NPTY 32 /* crude XXX */
58 #endif
59
60 #define BUFSIZ 100 /* Chunk size iomoved to/from user */
61
62 /*
63 * pts == /dev/tty[pqrs]?
64 * ptc == /dev/pty[pqrs]?
65 */
66 struct pt_softc {
67 struct tty *pt_tty;
68 int pt_flags;
69 struct selinfo pt_selr, pt_selw;
70 u_char pt_send;
71 u_char pt_ucntl;
72 } pt_softc[NPTY]; /* XXX */
73 int npty = NPTY; /* for pstat -t */
74
75 #define PF_PKT 0x08 /* packet mode */
76 #define PF_STOPPED 0x10 /* user told stopped */
77 #define PF_REMOTE 0x20 /* remote and flow controlled input */
78 #define PF_NOSTOP 0x40
79 #define PF_UCNTL 0x80 /* user control mode */
80
81 void ptsstop __P((struct tty *, int));
82
83 /*
84 * Establish n (or default if n is 1) ptys in the system.
85 */
86 void
87 ptyattach(n)
88 int n;
89 {
90 #ifdef notyet
91 #define DEFAULT_NPTY 32
92
93 /* maybe should allow 0 => none? */
94 if (n <= 1)
95 n = DEFAULT_NPTY;
96 pt_softc = malloc(n * sizeof(struct pt_softc), M_DEVBUF, M_WAITOK);
97 npty = n;
98 #endif
99 }
100
101 /*ARGSUSED*/
102 ptsopen(dev, flag, devtype, p)
103 dev_t dev;
104 int flag, devtype;
105 struct proc *p;
106 {
107 struct pt_softc *pti;
108 register struct tty *tp;
109 int error;
110
111 if (minor(dev) >= npty)
112 return (ENXIO);
113 pti = &pt_softc[minor(dev)];
114 if (!pti->pt_tty)
115 tp = pti->pt_tty = ttymalloc();
116 else
117 tp = pti->pt_tty;
118 if ((tp->t_state & TS_ISOPEN) == 0) {
119 tp->t_state |= TS_WOPEN;
120 ttychars(tp); /* Set up default chars */
121 tp->t_iflag = TTYDEF_IFLAG;
122 tp->t_oflag = TTYDEF_OFLAG;
123 tp->t_lflag = TTYDEF_LFLAG;
124 tp->t_cflag = TTYDEF_CFLAG;
125 tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
126 ttsetwater(tp); /* would be done in xxparam() */
127 } else if (tp->t_state&TS_XCLUDE && p->p_ucred->cr_uid != 0)
128 return (EBUSY);
129 if (tp->t_oproc) /* Ctrlr still around. */
130 tp->t_state |= TS_CARR_ON;
131 while ((tp->t_state & TS_CARR_ON) == 0) {
132 tp->t_state |= TS_WOPEN;
133 if (flag&FNONBLOCK)
134 break;
135 if (error = ttysleep(tp, (caddr_t)&tp->t_rawq, TTIPRI | PCATCH,
136 ttopen, 0))
137 return (error);
138 }
139 error = (*linesw[tp->t_line].l_open)(dev, tp);
140 ptcwakeup(tp, FREAD|FWRITE);
141 return (error);
142 }
143
144 ptsclose(dev, flag, mode, p)
145 dev_t dev;
146 int flag, mode;
147 struct proc *p;
148 {
149 register struct pt_softc *pti = &pt_softc[minor(dev)];
150 register struct tty *tp = pti->pt_tty;
151 int err;
152
153 err = (*linesw[tp->t_line].l_close)(tp, flag);
154 err |= ttyclose(tp);
155 ptcwakeup(tp, FREAD|FWRITE);
156 return (err);
157 }
158
159 ptsread(dev, uio, flag)
160 dev_t dev;
161 struct uio *uio;
162 int flag;
163 {
164 struct proc *p = curproc;
165 register struct pt_softc *pti = &pt_softc[minor(dev)];
166 register struct tty *tp = pti->pt_tty;
167 int error = 0;
168
169 again:
170 if (pti->pt_flags & PF_REMOTE) {
171 while (isbackground(p, tp)) {
172 if ((p->p_sigignore & sigmask(SIGTTIN)) ||
173 (p->p_sigmask & sigmask(SIGTTIN)) ||
174 p->p_pgrp->pg_jobc == 0 ||
175 p->p_flag & P_PPWAIT)
176 return (EIO);
177 pgsignal(p->p_pgrp, SIGTTIN, 1);
178 if (error = ttysleep(tp, (caddr_t)&lbolt,
179 TTIPRI | PCATCH, ttybg, 0))
180 return (error);
181 }
182 if (tp->t_canq.c_cc == 0) {
183 if (flag & IO_NDELAY)
184 return (EWOULDBLOCK);
185 if (error = ttysleep(tp, (caddr_t)&tp->t_canq,
186 TTIPRI | PCATCH, ttyin, 0))
187 return (error);
188 goto again;
189 }
190 while (tp->t_canq.c_cc > 1 && uio->uio_resid > 0)
191 if (ureadc(getc(&tp->t_canq), uio) < 0) {
192 error = EFAULT;
193 break;
194 }
195 if (tp->t_canq.c_cc == 1)
196 (void) getc(&tp->t_canq);
197 if (tp->t_canq.c_cc)
198 return (error);
199 } else
200 if (tp->t_oproc)
201 error = (*linesw[tp->t_line].l_read)(tp, uio, flag);
202 ptcwakeup(tp, FWRITE);
203 return (error);
204 }
205
206 /*
207 * Write to pseudo-tty.
208 * Wakeups of controlling tty will happen
209 * indirectly, when tty driver calls ptsstart.
210 */
211 ptswrite(dev, uio, flag)
212 dev_t dev;
213 struct uio *uio;
214 int flag;
215 {
216 register struct pt_softc *pti = &pt_softc[minor(dev)];
217 register struct tty *tp = pti->pt_tty;
218
219 if (tp->t_oproc == 0)
220 return (EIO);
221 return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
222 }
223
224 /*
225 * Start output on pseudo-tty.
226 * Wake up process selecting or sleeping for input from controlling tty.
227 */
228 void
229 ptsstart(tp)
230 struct tty *tp;
231 {
232 register struct pt_softc *pti = &pt_softc[minor(tp->t_dev)];
233
234 if (tp->t_state & TS_TTSTOP)
235 return;
236 if (pti->pt_flags & PF_STOPPED) {
237 pti->pt_flags &= ~PF_STOPPED;
238 pti->pt_send = TIOCPKT_START;
239 }
240 ptcwakeup(tp, FREAD);
241 }
242
243 ptcwakeup(tp, flag)
244 struct tty *tp;
245 int flag;
246 {
247 struct pt_softc *pti = &pt_softc[minor(tp->t_dev)];
248
249 if (flag & FREAD) {
250 selwakeup(&pti->pt_selr);
251 wakeup((caddr_t)&tp->t_outq.c_cf);
252 }
253 if (flag & FWRITE) {
254 selwakeup(&pti->pt_selw);
255 wakeup((caddr_t)&tp->t_rawq.c_cf);
256 }
257 }
258
259 int ptcopen __P((dev_t, int, int, struct proc *));
260
261 /*ARGSUSED*/
262 int
263 ptcopen(dev, flag, devtype, p)
264 dev_t dev;
265 int flag, devtype;
266 struct proc *p;
267 {
268 struct pt_softc *pti;
269 register struct tty *tp;
270
271 if (minor(dev) >= npty)
272 return (ENXIO);
273 pti = &pt_softc[minor(dev)];
274 if (!pti->pt_tty)
275 tp = pti->pt_tty = ttymalloc();
276 else
277 tp = pti->pt_tty;
278 if (tp->t_oproc)
279 return (EIO);
280 tp->t_oproc = ptsstart;
281 (void)(*linesw[tp->t_line].l_modem)(tp, 1);
282 tp->t_lflag &= ~EXTPROC;
283 pti->pt_flags = 0;
284 pti->pt_send = 0;
285 pti->pt_ucntl = 0;
286 return (0);
287 }
288
289 int
290 ptcclose(dev)
291 dev_t dev;
292 {
293 register struct pt_softc *pti = &pt_softc[minor(dev)];
294 register struct tty *tp = pti->pt_tty;
295
296 (void)(*linesw[tp->t_line].l_modem)(tp, 0);
297 tp->t_state &= ~TS_CARR_ON;
298 tp->t_oproc = 0; /* mark closed */
299 return (0);
300 }
301
302 ptcread(dev, uio, flag)
303 dev_t dev;
304 struct uio *uio;
305 int flag;
306 {
307 register struct pt_softc *pti = &pt_softc[minor(dev)];
308 register struct tty *tp = pti->pt_tty;
309 char buf[BUFSIZ];
310 int error = 0, cc;
311
312 /*
313 * We want to block until the slave
314 * is open, and there's something to read;
315 * but if we lost the slave or we're NBIO,
316 * then return the appropriate error instead.
317 */
318 for (;;) {
319 if (tp->t_state&TS_ISOPEN) {
320 if (pti->pt_flags&PF_PKT && pti->pt_send) {
321 error = ureadc((int)pti->pt_send, uio);
322 if (error)
323 return (error);
324 if (pti->pt_send & TIOCPKT_IOCTL) {
325 cc = min(uio->uio_resid,
326 sizeof(tp->t_termios));
327 uiomove(&tp->t_termios, cc, uio);
328 }
329 pti->pt_send = 0;
330 return (0);
331 }
332 if (pti->pt_flags&PF_UCNTL && pti->pt_ucntl) {
333 error = ureadc((int)pti->pt_ucntl, uio);
334 if (error)
335 return (error);
336 pti->pt_ucntl = 0;
337 return (0);
338 }
339 if (tp->t_outq.c_cc && (tp->t_state&TS_TTSTOP) == 0)
340 break;
341 }
342 if ((tp->t_state&TS_CARR_ON) == 0)
343 return (0); /* EOF */
344 if (flag & IO_NDELAY)
345 return (EWOULDBLOCK);
346 if (error = tsleep((caddr_t)&tp->t_outq.c_cf, TTIPRI | PCATCH,
347 ttyin, 0))
348 return (error);
349 }
350 if (pti->pt_flags & (PF_PKT|PF_UCNTL))
351 error = ureadc(0, uio);
352 while (uio->uio_resid > 0 && error == 0) {
353 cc = q_to_b(&tp->t_outq, buf, min(uio->uio_resid, BUFSIZ));
354 if (cc <= 0)
355 break;
356 error = uiomove(buf, cc, uio);
357 }
358 if (tp->t_outq.c_cc <= tp->t_lowat) {
359 if (tp->t_state&TS_ASLEEP) {
360 tp->t_state &= ~TS_ASLEEP;
361 wakeup((caddr_t)&tp->t_outq);
362 }
363 selwakeup(&tp->t_wsel);
364 }
365 return (error);
366 }
367
368 void
369 ptsstop(tp, flush)
370 register struct tty *tp;
371 int flush;
372 {
373 struct pt_softc *pti = &pt_softc[minor(tp->t_dev)];
374 int flag;
375
376 /* note: FLUSHREAD and FLUSHWRITE already ok */
377 if (flush == 0) {
378 flush = TIOCPKT_STOP;
379 pti->pt_flags |= PF_STOPPED;
380 } else
381 pti->pt_flags &= ~PF_STOPPED;
382 pti->pt_send |= flush;
383 /* change of perspective */
384 flag = 0;
385 if (flush & FREAD)
386 flag |= FWRITE;
387 if (flush & FWRITE)
388 flag |= FREAD;
389 ptcwakeup(tp, flag);
390 }
391
392 ptcselect(dev, rw, p)
393 dev_t dev;
394 int rw;
395 struct proc *p;
396 {
397 register struct pt_softc *pti = &pt_softc[minor(dev)];
398 register struct tty *tp = pti->pt_tty;
399 int s;
400
401 if ((tp->t_state&TS_CARR_ON) == 0)
402 return (1);
403 switch (rw) {
404
405 case FREAD:
406 /*
407 * Need to block timeouts (ttrstart).
408 */
409 s = spltty();
410 if ((tp->t_state&TS_ISOPEN) &&
411 tp->t_outq.c_cc && (tp->t_state&TS_TTSTOP) == 0) {
412 splx(s);
413 return (1);
414 }
415 splx(s);
416 /* FALLTHROUGH */
417
418 case 0: /* exceptional */
419 if ((tp->t_state&TS_ISOPEN) &&
420 (pti->pt_flags&PF_PKT && pti->pt_send ||
421 pti->pt_flags&PF_UCNTL && pti->pt_ucntl))
422 return (1);
423 selrecord(p, &pti->pt_selr);
424 break;
425
426
427 case FWRITE:
428 if (tp->t_state&TS_ISOPEN) {
429 if (pti->pt_flags & PF_REMOTE) {
430 if (tp->t_canq.c_cc == 0)
431 return (1);
432 } else {
433 if (tp->t_rawq.c_cc + tp->t_canq.c_cc < TTYHOG-2)
434 return (1);
435 if (tp->t_canq.c_cc == 0 && (tp->t_iflag&ICANON))
436 return (1);
437 }
438 }
439 selrecord(p, &pti->pt_selw);
440 break;
441
442 }
443 return (0);
444 }
445
446 ptcwrite(dev, uio, flag)
447 dev_t dev;
448 register struct uio *uio;
449 int flag;
450 {
451 register struct pt_softc *pti = &pt_softc[minor(dev)];
452 register struct tty *tp = pti->pt_tty;
453 register u_char *cp;
454 register int cc = 0;
455 u_char locbuf[BUFSIZ];
456 int cnt = 0;
457 int error = 0;
458
459 again:
460 if ((tp->t_state&TS_ISOPEN) == 0)
461 goto block;
462 if (pti->pt_flags & PF_REMOTE) {
463 if (tp->t_canq.c_cc)
464 goto block;
465 while (uio->uio_resid > 0 && tp->t_canq.c_cc < TTYHOG - 1) {
466 if (cc == 0) {
467 cc = min(uio->uio_resid, BUFSIZ);
468 cc = min(cc, TTYHOG - 1 - tp->t_canq.c_cc);
469 cp = locbuf;
470 error = uiomove((caddr_t)cp, cc, uio);
471 if (error)
472 return (error);
473 /* check again for safety */
474 if ((tp->t_state&TS_ISOPEN) == 0)
475 return (EIO);
476 }
477 if (cc)
478 (void) b_to_q((char *)cp, cc, &tp->t_canq);
479 cc = 0;
480 }
481 (void) putc(0, &tp->t_canq);
482 ttwakeup(tp);
483 wakeup((caddr_t)&tp->t_canq);
484 return (0);
485 }
486 while (uio->uio_resid > 0) {
487 if (cc == 0) {
488 cc = min(uio->uio_resid, BUFSIZ);
489 cp = locbuf;
490 error = uiomove((caddr_t)cp, cc, uio);
491 if (error)
492 return (error);
493 /* check again for safety */
494 if ((tp->t_state&TS_ISOPEN) == 0)
495 return (EIO);
496 }
497 while (cc > 0) {
498 if ((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= TTYHOG - 2 &&
499 (tp->t_canq.c_cc > 0 || !(tp->t_iflag&ICANON))) {
500 wakeup((caddr_t)&tp->t_rawq);
501 goto block;
502 }
503 (*linesw[tp->t_line].l_rint)(*cp++, tp);
504 cnt++;
505 cc--;
506 }
507 cc = 0;
508 }
509 return (0);
510 block:
511 /*
512 * Come here to wait for slave to open, for space
513 * in outq, or space in rawq.
514 */
515 if ((tp->t_state&TS_CARR_ON) == 0)
516 return (EIO);
517 if (flag & IO_NDELAY) {
518 /* adjust for data copied in but not written */
519 uio->uio_resid += cc;
520 if (cnt == 0)
521 return (EWOULDBLOCK);
522 return (0);
523 }
524 if (error = tsleep((caddr_t)&tp->t_rawq.c_cf, TTOPRI | PCATCH,
525 ttyout, 0)) {
526 /* adjust for data copied in but not written */
527 uio->uio_resid += cc;
528 return (error);
529 }
530 goto again;
531 }
532
533 /*ARGSUSED*/
534 ptyioctl(dev, cmd, data, flag, p)
535 dev_t dev;
536 u_long cmd;
537 caddr_t data;
538 int flag;
539 struct proc *p;
540 {
541 register struct pt_softc *pti = &pt_softc[minor(dev)];
542 register struct tty *tp = pti->pt_tty;
543 register u_char *cc = tp->t_cc;
544 int stop, error;
545
546 /*
547 * IF CONTROLLER STTY THEN MUST FLUSH TO PREVENT A HANG.
548 * ttywflush(tp) will hang if there are characters in the outq.
549 */
550 if (cmd == TIOCEXT) {
551 /*
552 * When the EXTPROC bit is being toggled, we need
553 * to send an TIOCPKT_IOCTL if the packet driver
554 * is turned on.
555 */
556 if (*(int *)data) {
557 if (pti->pt_flags & PF_PKT) {
558 pti->pt_send |= TIOCPKT_IOCTL;
559 ptcwakeup(tp, FREAD);
560 }
561 tp->t_lflag |= EXTPROC;
562 } else {
563 if ((tp->t_state & EXTPROC) &&
564 (pti->pt_flags & PF_PKT)) {
565 pti->pt_send |= TIOCPKT_IOCTL;
566 ptcwakeup(tp, FREAD);
567 }
568 tp->t_lflag &= ~EXTPROC;
569 }
570 return(0);
571 } else
572 if (cdevsw[major(dev)].d_open == ptcopen)
573 switch (cmd) {
574
575 case TIOCGPGRP:
576 /*
577 * We aviod calling ttioctl on the controller since,
578 * in that case, tp must be the controlling terminal.
579 */
580 *(int *)data = tp->t_pgrp ? tp->t_pgrp->pg_id : 0;
581 return (0);
582
583 case TIOCPKT:
584 if (*(int *)data) {
585 if (pti->pt_flags & PF_UCNTL)
586 return (EINVAL);
587 pti->pt_flags |= PF_PKT;
588 } else
589 pti->pt_flags &= ~PF_PKT;
590 return (0);
591
592 case TIOCUCNTL:
593 if (*(int *)data) {
594 if (pti->pt_flags & PF_PKT)
595 return (EINVAL);
596 pti->pt_flags |= PF_UCNTL;
597 } else
598 pti->pt_flags &= ~PF_UCNTL;
599 return (0);
600
601 case TIOCREMOTE:
602 if (*(int *)data)
603 pti->pt_flags |= PF_REMOTE;
604 else
605 pti->pt_flags &= ~PF_REMOTE;
606 ttyflush(tp, FREAD|FWRITE);
607 return (0);
608
609 #ifdef COMPAT_43
610 case TIOCSETP:
611 case TIOCSETN:
612 #endif
613 case TIOCSETD:
614 case TIOCSETA:
615 case TIOCSETAW:
616 case TIOCSETAF:
617 ndflush(&tp->t_outq, tp->t_outq.c_cc);
618 break;
619
620 case TIOCSIG:
621 if (*(unsigned int *)data >= NSIG)
622 return(EINVAL);
623 if ((tp->t_lflag&NOFLSH) == 0)
624 ttyflush(tp, FREAD|FWRITE);
625 pgsignal(tp->t_pgrp, *(unsigned int *)data, 1);
626 if ((*(unsigned int *)data == SIGINFO) &&
627 ((tp->t_lflag&NOKERNINFO) == 0))
628 ttyinfo(tp);
629 return(0);
630 }
631 error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
632 if (error < 0)
633 error = ttioctl(tp, cmd, data, flag, p);
634 if (error < 0) {
635 if (pti->pt_flags & PF_UCNTL &&
636 (cmd & ~0xff) == UIOCCMD(0)) {
637 if (cmd & 0xff) {
638 pti->pt_ucntl = (u_char)cmd;
639 ptcwakeup(tp, FREAD);
640 }
641 return (0);
642 }
643 error = ENOTTY;
644 }
645 /*
646 * If external processing and packet mode send ioctl packet.
647 */
648 if ((tp->t_lflag&EXTPROC) && (pti->pt_flags & PF_PKT)) {
649 switch(cmd) {
650 case TIOCSETA:
651 case TIOCSETAW:
652 case TIOCSETAF:
653 #ifdef COMPAT_43
654 case TIOCSETP:
655 case TIOCSETN:
656 #endif
657 #if defined(COMPAT_43) || defined(COMPAT_SUNOS)
658 case TIOCSETC:
659 case TIOCSLTC:
660 case TIOCLBIS:
661 case TIOCLBIC:
662 case TIOCLSET:
663 #endif
664 pti->pt_send |= TIOCPKT_IOCTL;
665 ptcwakeup(tp, FREAD);
666 default:
667 break;
668 }
669 }
670 stop = (tp->t_iflag & IXON) && CCEQ(cc[VSTOP], CTRL('s'))
671 && CCEQ(cc[VSTART], CTRL('q'));
672 if (pti->pt_flags & PF_NOSTOP) {
673 if (stop) {
674 pti->pt_send &= ~TIOCPKT_NOSTOP;
675 pti->pt_send |= TIOCPKT_DOSTOP;
676 pti->pt_flags &= ~PF_NOSTOP;
677 ptcwakeup(tp, FREAD);
678 }
679 } else {
680 if (!stop) {
681 pti->pt_send &= ~TIOCPKT_DOSTOP;
682 pti->pt_send |= TIOCPKT_NOSTOP;
683 pti->pt_flags |= PF_NOSTOP;
684 ptcwakeup(tp, FREAD);
685 }
686 }
687 return (error);
688 }
689