xref: /minix/minix/drivers/tty/pty/pty.c (revision 433d6423)
1 /*	pty.c - pseudo terminal driver			Author: Kees J. Bot
2  *								30 Dec 1995
3  * PTYs can be seen as a bidirectional pipe with TTY
4  * input and output processing.  For example a simple rlogin session:
5  *
6  *	keyboard -> rlogin -> in.rld -> /dev/ptypX -> /dev/ttypX -> shell
7  *	shell -> /dev/ttypX -> /dev/ptypX -> in.rld -> rlogin -> screen
8  *
9  * This file takes care of copying data between the tty/pty device pairs and
10  * the open/read/write/close calls on the pty devices.  The TTY task takes
11  * care of the input and output processing (interrupt, backspace, raw I/O,
12  * etc.) using the pty_slave_read() and pty_slave_write() functions as the
13  * "keyboard" and "screen" functions of the ttypX devices.
14  * Be careful when reading this code, the terms "reading" and "writing" are
15  * used both for the tty (slave) and the pty (master) end of the pseudo tty.
16  * Writes to one end are to be read at the other end and vice-versa.
17  */
18 
19 #include <minix/drivers.h>
20 #include <termios.h>
21 #include <assert.h>
22 #include <sys/termios.h>
23 #include <signal.h>
24 #include "tty.h"
25 
26 /* PTY bookkeeping structure, one per pty/tty pair. */
27 typedef struct pty {
28   tty_t		*tty;		/* associated TTY structure */
29   char		state;		/* flags: busy, closed, ... */
30 
31   /* Read call on master (/dev/ptypX). */
32   endpoint_t	rdcaller;	/* process making the call, or NONE if none */
33   cdev_id_t	rdid;		/* ID of suspended read request */
34   cp_grant_id_t	rdgrant;	/* grant for reader's address space */
35   size_t	rdleft;		/* # bytes yet to be read */
36   size_t	rdcum;		/* # bytes written so far */
37 
38   /* Write call to master (/dev/ptypX). */
39   endpoint_t	wrcaller;	/* process making the call, or NONE if none*/
40   cdev_id_t	wrid;		/* ID of suspended write request */
41   cp_grant_id_t	wrgrant;	/* grant for writer's address space */
42   size_t	wrleft;		/* # bytes yet to be written */
43   size_t	wrcum;		/* # bytes written so far */
44 
45   /* Output buffer. */
46   int		ocount;		/* # characters in the buffer */
47   char		*ohead, *otail;	/* head and tail of the circular buffer */
48   char		obuf[2048];	/* buffer for bytes going to the pty reader */
49 
50   /* select() data. */
51   unsigned int	select_ops;	/* Which operations do we want to know about? */
52   endpoint_t	select_proc;	/* Who wants to know about it? */
53 } pty_t;
54 
55 #define TTY_ACTIVE	0x01	/* tty is open/active */
56 #define PTY_ACTIVE	0x02	/* pty is open/active */
57 #define TTY_CLOSED	0x04	/* tty side has closed down */
58 #define PTY_CLOSED	0x08	/* pty side has closed down */
59 
60 static pty_t pty_table[NR_PTYS];	/* PTY bookkeeping */
61 
62 static void pty_start(pty_t *pp);
63 static void pty_finish(pty_t *pp);
64 
65 static int pty_master_open(devminor_t minor, int access,
66 	endpoint_t user_endpt);
67 static int pty_master_close(devminor_t minor);
68 static ssize_t pty_master_read(devminor_t minor, u64_t position,
69 	endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags,
70 	cdev_id_t id);
71 static ssize_t pty_master_write(devminor_t minor, u64_t position,
72 	endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags,
73 	cdev_id_t id);
74 static int pty_master_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id);
75 static int pty_master_select(devminor_t minor, unsigned int ops,
76 	endpoint_t endpt);
77 
78 static struct chardriver pty_master_tab = {
79   .cdr_open	= pty_master_open,
80   .cdr_close	= pty_master_close,
81   .cdr_read	= pty_master_read,
82   .cdr_write	= pty_master_write,
83   .cdr_cancel	= pty_master_cancel,
84   .cdr_select	= pty_master_select
85 };
86 
87 /*===========================================================================*
88  *				pty_master_open				     *
89  *===========================================================================*/
90 static int pty_master_open(devminor_t minor, int UNUSED(access),
91 	endpoint_t UNUSED(user_endpt))
92 {
93   tty_t *tp;
94   pty_t *pp;
95 
96   assert(minor >= PTYPX_MINOR && minor < PTYPX_MINOR + NR_PTYS);
97 
98   if ((tp = line2tty(minor)) == NULL)
99 	return ENXIO;
100   pp = tp->tty_priv;
101 
102   if (pp->state & PTY_ACTIVE)
103 	return EIO;
104 
105   pp->state |= PTY_ACTIVE;
106   pp->rdcum = 0;
107   pp->wrcum = 0;
108 
109   return OK;
110 }
111 
112 /*===========================================================================*
113  *				pty_master_close			     *
114  *===========================================================================*/
115 static int pty_master_close(devminor_t minor)
116 {
117   tty_t *tp;
118   pty_t *pp;
119 
120   if ((tp = line2tty(minor)) == NULL)
121 	return ENXIO;
122   pp = tp->tty_priv;
123 
124   if ((pp->state & (TTY_ACTIVE | TTY_CLOSED)) != TTY_ACTIVE) {
125 	pp->state = 0;
126   } else {
127 	pp->state |= PTY_CLOSED;
128 	sigchar(tp, SIGHUP, 1);
129   }
130 
131   return OK;
132 }
133 
134 /*===========================================================================*
135  *				pty_master_read				     *
136  *===========================================================================*/
137 static ssize_t pty_master_read(devminor_t minor, u64_t UNUSED(position),
138 	endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags,
139 	cdev_id_t id)
140 {
141   tty_t *tp;
142   pty_t *pp;
143   ssize_t r;
144 
145   if ((tp = line2tty(minor)) == NULL)
146 	return ENXIO;
147   pp = tp->tty_priv;
148 
149   /* Check, store information on the reader, do I/O. */
150   if (pp->state & TTY_CLOSED)
151 	return 0; /* EOF */
152 
153   if (pp->rdcaller != NONE || pp->rdleft != 0 || pp->rdcum != 0)
154 	return EIO;
155 
156   if (size <= 0)
157 	return EINVAL;
158 
159   pp->rdcaller = endpt;
160   pp->rdid = id;
161   pp->rdgrant = grant;
162   pp->rdleft = size;
163   pty_start(pp);
164 
165   handle_events(tp);
166 
167   if (pp->rdleft == 0) {
168 	pp->rdcaller = NONE;
169 	return EDONTREPLY;		/* already done */
170   }
171 
172   if (flags & CDEV_NONBLOCK) {
173 	r = pp->rdcum > 0 ? pp->rdcum : EAGAIN;
174 	pp->rdleft = pp->rdcum = 0;
175 	pp->rdcaller = NONE;
176 	return r;
177   }
178 
179   return EDONTREPLY;			/* do suspend */
180 }
181 
182 /*===========================================================================*
183  *				pty_master_write			     *
184  *===========================================================================*/
185 static ssize_t pty_master_write(devminor_t minor, u64_t UNUSED(position),
186 	endpoint_t endpt, cp_grant_id_t grant, size_t size, int flags,
187 	cdev_id_t id)
188 {
189   tty_t *tp;
190   pty_t *pp;
191   ssize_t r;
192 
193   if ((tp = line2tty(minor)) == NULL)
194 	return ENXIO;
195   pp = tp->tty_priv;
196 
197   /* Check, store information on the writer, do I/O. */
198   if (pp->state & TTY_CLOSED)
199 	return EIO;
200 
201   if (pp->wrcaller != NONE || pp->wrleft != 0 || pp->wrcum != 0)
202 	return EIO;
203 
204   if (size <= 0)
205 	return EINVAL;
206 
207   pp->wrcaller = endpt;
208   pp->wrid = id;
209   pp->wrgrant = grant;
210   pp->wrleft = size;
211 
212   handle_events(tp);
213 
214   if (pp->wrleft == 0) {
215 	pp->wrcaller = NONE;
216 	return EDONTREPLY;		/* already done */
217   }
218 
219   if (flags & CDEV_NONBLOCK) {
220 	r = pp->wrcum > 0 ? pp->wrcum : EAGAIN;
221 	pp->wrleft = pp->wrcum = 0;
222 	pp->wrcaller = NONE;
223 	return r;
224   }
225 
226   return EDONTREPLY;			/* do suspend */
227 }
228 
229 /*===========================================================================*
230  *				pty_master_cancel			     *
231  *===========================================================================*/
232 static int pty_master_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id)
233 {
234   tty_t *tp;
235   pty_t *pp;
236   int r;
237 
238   if ((tp = line2tty(minor)) == NULL)
239 	return ENXIO;
240   pp = tp->tty_priv;
241 
242   if (pp->rdcaller == endpt && pp->rdid == id) {
243 	/* Cancel a read from a PTY. */
244 	r = pp->rdcum > 0 ? pp->rdcum : EINTR;
245 	pp->rdleft = pp->rdcum = 0;
246 	pp->rdcaller = NONE;
247 	return r;
248   }
249 
250   if (pp->wrcaller == endpt && pp->wrid == id) {
251 	/* Cancel a write to a PTY. */
252 	r = pp->wrcum > 0 ? pp->wrcum : EINTR;
253 	pp->wrleft = pp->wrcum = 0;
254 	pp->wrcaller = NONE;
255 	return r;
256   }
257 
258   /* Request not found. */
259   return EDONTREPLY;
260 }
261 
262 /*===========================================================================*
263  *				select_try_pty				     *
264  *===========================================================================*/
265 static int select_try_pty(tty_t *tp, int ops)
266 {
267   pty_t *pp = tp->tty_priv;
268   int r = 0;
269 
270   if (ops & CDEV_OP_WR)  {
271 	/* Write won't block on error. */
272 	if (pp->state & TTY_CLOSED) r |= CDEV_OP_WR;
273 	else if (pp->wrleft != 0 || pp->wrcum != 0) r |= CDEV_OP_WR;
274 	else if (tp->tty_incount < buflen(tp->tty_inbuf)) r |= CDEV_OP_WR;
275   }
276 
277   if (ops & CDEV_OP_RD) {
278 	/* Read won't block on error. */
279 	if (pp->state & TTY_CLOSED) r |= CDEV_OP_RD;
280 	else if (pp->rdleft != 0 || pp->rdcum != 0) r |= CDEV_OP_RD;
281 	else if (pp->ocount > 0) r |= CDEV_OP_RD;	/* Actual data. */
282   }
283 
284   return r;
285 }
286 
287 /*===========================================================================*
288  *				select_retry_pty			     *
289  *===========================================================================*/
290 void select_retry_pty(tty_t *tp)
291 {
292   pty_t *pp = tp->tty_priv;
293   devminor_t minor;
294   int r;
295 
296   /* See if the pty side of a pty is ready to return a select. */
297   if (pp->select_ops && (r = select_try_pty(tp, pp->select_ops))) {
298 	minor = PTYPX_MINOR + (int) (pp - pty_table);
299 	chardriver_reply_select(pp->select_proc, minor, r);
300 	pp->select_ops &= ~r;
301   }
302 }
303 
304 /*===========================================================================*
305  *				pty_master_select			     *
306  *===========================================================================*/
307 static int pty_master_select(devminor_t minor, unsigned int ops,
308 	endpoint_t endpt)
309 {
310   tty_t *tp;
311   pty_t *pp;
312   int ready_ops, watch;
313 
314   if ((tp = line2tty(minor)) == NULL)
315 	return ENXIO;
316   pp = tp->tty_priv;
317 
318   watch = (ops & CDEV_NOTIFY);
319   ops &= (CDEV_OP_RD | CDEV_OP_WR | CDEV_OP_ERR);
320 
321   ready_ops = select_try_pty(tp, ops);
322 
323   ops &= ~ready_ops;
324   if (ops && watch) {
325 	pp->select_ops |= ops;
326 	pp->select_proc = endpt;
327   }
328 
329   return ready_ops;
330 }
331 
332 /*===========================================================================*
333  *				do_pty					     *
334  *===========================================================================*/
335 void do_pty(message *m_ptr, int ipc_status)
336 {
337 /* Process a request for a PTY master (/dev/ptypX) device. */
338 
339   chardriver_process(&pty_master_tab, m_ptr, ipc_status);
340 }
341 
342 /*===========================================================================*
343  *				pty_slave_write				     *
344  *===========================================================================*/
345 static int pty_slave_write(tty_t *tp, int try)
346 {
347 /* (*dev_write)() routine for PTYs.  Transfer bytes from the writer on
348  * /dev/ttypX to the output buffer.
349  */
350   pty_t *pp = tp->tty_priv;
351   int count, ocount, s;
352 
353   /* PTY closed down? */
354   if (pp->state & PTY_CLOSED) {
355   	if (try) return 1;
356 	if (tp->tty_outleft > 0) {
357 		chardriver_reply_task(tp->tty_outcaller, tp->tty_outid, EIO);
358 		tp->tty_outleft = tp->tty_outcum = 0;
359 		tp->tty_outcaller = NONE;
360 	}
361 	return 0;
362   }
363 
364   /* While there is something to do. */
365   for (;;) {
366 	ocount = buflen(pp->obuf) - pp->ocount;
367 	if (try) return (ocount > 0);
368 	count = bufend(pp->obuf) - pp->ohead;
369 	if (count > ocount) count = ocount;
370 	if (count > tp->tty_outleft) count = tp->tty_outleft;
371 	if (count == 0 || tp->tty_inhibited)
372 		break;
373 
374 	/* Copy from user space to the PTY output buffer. */
375 	if (tp->tty_outcaller == KERNEL) {
376 		/* We're trying to print on kernel's behalf */
377 		memcpy(pp->ohead, (void *) tp->tty_outgrant + tp->tty_outcum,
378 			count);
379 	} else {
380 		if ((s = sys_safecopyfrom(tp->tty_outcaller, tp->tty_outgrant,
381 				tp->tty_outcum, (vir_bytes) pp->ohead,
382 				count)) != OK) {
383 			break;
384 		}
385 	}
386 
387 	/* Perform output processing on the output buffer. */
388 	out_process(tp, pp->obuf, pp->ohead, bufend(pp->obuf), &count, &ocount);
389 	if (count == 0) break;
390 
391 	/* Assume echoing messed up by output. */
392 	tp->tty_reprint = TRUE;
393 
394 	/* Bookkeeping. */
395 	pp->ocount += ocount;
396 	if ((pp->ohead += ocount) >= bufend(pp->obuf))
397 		pp->ohead -= buflen(pp->obuf);
398 	pty_start(pp);
399 
400 	tp->tty_outcum += count;
401 	if ((tp->tty_outleft -= count) == 0) {
402 		/* Output is finished, reply to the writer. */
403 		chardriver_reply_task(tp->tty_outcaller, tp->tty_outid,
404 			tp->tty_outcum);
405 		tp->tty_outcum = 0;
406 		tp->tty_outcaller = NONE;
407 	}
408   }
409   pty_finish(pp);
410   return 1;
411 }
412 
413 /*===========================================================================*
414  *				pty_slave_echo				     *
415  *===========================================================================*/
416 static void pty_slave_echo(tty_t *tp, int c)
417 {
418 /* Echo one character.  (Like pty_write, but only one character, optionally.) */
419 
420   pty_t *pp = tp->tty_priv;
421   int count, ocount;
422 
423   ocount = buflen(pp->obuf) - pp->ocount;
424   if (ocount == 0) return;		/* output buffer full */
425   count = 1;
426   *pp->ohead = c;			/* add one character */
427 
428   out_process(tp, pp->obuf, pp->ohead, bufend(pp->obuf), &count, &ocount);
429   if (count == 0) return;
430 
431   pp->ocount += ocount;
432   if ((pp->ohead += ocount) >= bufend(pp->obuf)) pp->ohead -= buflen(pp->obuf);
433   pty_start(pp);
434 }
435 
436 /*===========================================================================*
437  *				pty_start				     *
438  *===========================================================================*/
439 static void pty_start(pty_t *pp)
440 {
441 /* Transfer bytes written to the output buffer to the PTY reader. */
442   int count;
443 
444   /* While there are things to do. */
445   for (;;) {
446   	int s;
447 	count = bufend(pp->obuf) - pp->otail;
448 	if (count > pp->ocount) count = pp->ocount;
449 	if (count > pp->rdleft) count = pp->rdleft;
450 	if (count == 0) break;
451 
452 	/* Copy from the output buffer to the readers address space. */
453 	if((s = sys_safecopyto(pp->rdcaller, pp->rdgrant, pp->rdcum,
454 		(vir_bytes) pp->otail, count)) != OK) {
455 		break;
456  	}
457 
458 	/* Bookkeeping. */
459 	pp->ocount -= count;
460 	if ((pp->otail += count) == bufend(pp->obuf)) pp->otail = pp->obuf;
461 	pp->rdcum += count;
462 	pp->rdleft -= count;
463   }
464 }
465 
466 /*===========================================================================*
467  *				pty_finish				     *
468  *===========================================================================*/
469 static void pty_finish(pty_t *pp)
470 {
471 /* Finish the read request of a PTY reader if there is at least one byte
472  * transferred.
473  */
474 
475   if (pp->rdcum > 0) {
476 	chardriver_reply_task(pp->rdcaller, pp->rdid, pp->rdcum);
477 	pp->rdleft = pp->rdcum = 0;
478 	pp->rdcaller = NONE;
479   }
480 }
481 
482 /*===========================================================================*
483  *				pty_slave_read				     *
484  *===========================================================================*/
485 static int pty_slave_read(tty_t *tp, int try)
486 {
487 /* Offer bytes from the PTY writer for input on the TTY.  (Do it one byte at
488  * a time, 99% of the writes will be for one byte, so no sense in being smart.)
489  */
490   pty_t *pp = tp->tty_priv;
491   char c;
492 
493   if (pp->state & PTY_CLOSED) {
494 	if (try) return 1;
495 	if (tp->tty_inleft > 0) {
496 		chardriver_reply_task(tp->tty_incaller, tp->tty_inid,
497 			tp->tty_incum);
498 		tp->tty_inleft = tp->tty_incum = 0;
499 		tp->tty_incaller = NONE;
500 	}
501 	return 1;
502   }
503 
504   if (try) {
505   	if (pp->wrleft > 0)
506   		return 1;
507   	return 0;
508   }
509 
510   while (pp->wrleft > 0) {
511   	int s;
512 
513 	/* Transfer one character to 'c'. */
514 	if ((s = sys_safecopyfrom(pp->wrcaller, pp->wrgrant, pp->wrcum,
515 		(vir_bytes) &c, 1)) != OK) {
516 		printf("pty: safecopy failed (error %d)\n", s);
517 		break;
518 	}
519 
520 	/* Input processing. */
521 	if (in_process(tp, &c, 1) == 0) break;
522 
523 	/* PTY writer bookkeeping. */
524 	pp->wrcum++;
525 	if (--pp->wrleft == 0) {
526 		chardriver_reply_task(pp->wrcaller, pp->wrid, pp->wrcum);
527 		pp->wrcum = 0;
528 		pp->wrcaller = NONE;
529 	}
530   }
531 
532   return 0;
533 }
534 
535 /*===========================================================================*
536  *				pty_slave_open				     *
537  *===========================================================================*/
538 static int pty_slave_open(tty_t *tp, int UNUSED(try))
539 {
540 /* The tty side has been opened. */
541   pty_t *pp = tp->tty_priv;
542 
543   assert(tp->tty_minor >= TTYPX_MINOR && tp->tty_minor < TTYPX_MINOR + NR_PTYS);
544 
545   /* TTY_ACTIVE may already be set, which would indicate that the slave is
546    * reopened after being fully closed while the master is still open. In that
547    * case TTY_CLOSED will also be set, so clear that one.
548    */
549   pp->state |= TTY_ACTIVE;
550   pp->state &= ~TTY_CLOSED;
551 
552   return 0;
553 }
554 
555 /*===========================================================================*
556  *				pty_slave_close				     *
557  *===========================================================================*/
558 static int pty_slave_close(tty_t *tp, int UNUSED(try))
559 {
560 /* The tty side has closed, so shut down the pty side. */
561   pty_t *pp = tp->tty_priv;
562 
563   if (!(pp->state & PTY_ACTIVE)) return 0;
564 
565   if (pp->rdleft > 0) {
566 	chardriver_reply_task(pp->rdcaller, pp->rdid, pp->rdcum);
567 	pp->rdleft = pp->rdcum = 0;
568 	pp->rdcaller = NONE;
569   }
570 
571   if (pp->wrleft > 0) {
572 	chardriver_reply_task(pp->wrcaller, pp->wrid, pp->wrcum);
573 	pp->wrleft = pp->wrcum = 0;
574 	pp->wrcaller = NONE;
575   }
576 
577   if (pp->state & PTY_CLOSED) pp->state = 0;
578   else pp->state |= TTY_CLOSED;
579 
580   return 0;
581 }
582 
583 /*===========================================================================*
584  *				pty_slave_icancel			     *
585  *===========================================================================*/
586 static int pty_slave_icancel(tty_t *tp, int UNUSED(try))
587 {
588 /* Discard waiting input. */
589   pty_t *pp = tp->tty_priv;
590 
591   if (pp->wrleft > 0) {
592 	chardriver_reply_task(pp->wrcaller, pp->wrid, pp->wrcum + pp->wrleft);
593 	pp->wrcum = pp->wrleft = 0;
594 	pp->wrcaller = NONE;
595   }
596 
597   return 0;
598 }
599 
600 /*===========================================================================*
601  *				pty_slave_ocancel			     *
602  *===========================================================================*/
603 static int pty_slave_ocancel(tty_t *tp, int UNUSED(try))
604 {
605 /* Drain the output buffer. */
606   pty_t *pp = tp->tty_priv;
607 
608   pp->ocount = 0;
609   pp->otail = pp->ohead;
610 
611   return 0;
612 }
613 
614 /*===========================================================================*
615  *				pty_init				     *
616  *===========================================================================*/
617 void pty_init(tty_t *tp)
618 {
619   pty_t *pp;
620   int line;
621 
622   /* Associate PTY and TTY structures. */
623   line = tp - tty_table;
624   pp = tp->tty_priv = &pty_table[line];
625   pp->tty = tp;
626   pp->select_ops = 0;
627   pp->rdcaller = NONE;
628   pp->wrcaller = NONE;
629 
630   /* Set up output queue. */
631   pp->ohead = pp->otail = pp->obuf;
632 
633   /* Fill in TTY function hooks. */
634   tp->tty_devread = pty_slave_read;
635   tp->tty_devwrite = pty_slave_write;
636   tp->tty_echo = pty_slave_echo;
637   tp->tty_icancel = pty_slave_icancel;
638   tp->tty_ocancel = pty_slave_ocancel;
639   tp->tty_open = pty_slave_open;
640   tp->tty_close = pty_slave_close;
641   tp->tty_select_ops = 0;
642 }
643