2 * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
5 * Portions of this software were developed under sponsorship from Snow
6 * B.V., the Netherlands.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
35 /* Add compatibility bits for FreeBSD. */
38 /* Add /dev/ptyXX compat bits. */
41 /* Add bits to make Linux binaries work. */
44 #include <sys/param.h>
46 #include <sys/condvar.h>
48 #include <sys/fcntl.h>
50 #include <sys/filedesc.h>
51 #include <sys/filio.h>
52 #include <sys/kernel.h>
53 #include <sys/malloc.h>
56 #include <sys/resourcevar.h>
57 #include <sys/serial.h>
59 #include <sys/syscall.h>
60 #include <sys/syscallsubr.h>
61 #include <sys/sysent.h>
62 #include <sys/sysproto.h>
63 #include <sys/systm.h>
65 #include <sys/ttycom.h>
67 #include <machine/stdarg.h>
69 static struct unrhdr
*pts_pool
;
70 #define MAXPTSDEVS 999
72 static MALLOC_DEFINE(M_PTS
, "pts", "pseudo tty device");
78 * (t) locked by tty_lock()
79 * (c) const until freeing
82 int pts_unit
; /* (c) Device unit number. */
83 unsigned int pts_flags
; /* (t) Device flags. */
84 #define PTS_PKT 0x1 /* Packet mode. */
85 char pts_pkt
; /* (t) Unread packet mode data. */
87 struct cv pts_inwait
; /* (t) Blocking write() on master. */
88 struct selinfo pts_inpoll
; /* (t) Select queue for write(). */
89 struct cv pts_outwait
; /* (t) Blocking read() on master. */
90 struct selinfo pts_outpoll
; /* (t) Select queue for read(). */
93 struct cdev
*pts_cdev
; /* (c) Master device node. */
94 #endif /* PTS_EXTERNAL */
96 struct uidinfo
*pts_uidinfo
; /* (c) Resource limit. */
100 * Controller-side file operations.
104 ptsdev_read(struct file
*fp
, struct uio
*uio
, struct ucred
*active_cred
,
105 int flags
, struct thread
*td
)
107 struct tty
*tp
= fp
->f_data
;
108 struct pts_softc
*psc
= tty_softc(tp
);
112 if (uio
->uio_resid
== 0)
119 * Implement packet mode. When packet mode is turned on,
120 * the first byte contains a bitmask of events that
121 * occured (start, stop, flush, window size, etc).
123 if (psc
->pts_flags
& PTS_PKT
&& psc
->pts_pkt
) {
128 error
= ureadc(pkt
, uio
);
133 * Transmit regular data.
135 * XXX: We shouldn't use ttydisc_getc_poll()! Even
136 * though in this implementation, there is likely going
137 * to be data, we should just call ttydisc_getc_uio()
138 * and use its return value to sleep.
140 if (ttydisc_getc_poll(tp
)) {
141 if (psc
->pts_flags
& PTS_PKT
) {
143 * XXX: Small race. Fortunately PTY
144 * consumers aren't multithreaded.
148 error
= ureadc(TIOCPKT_DATA
, uio
);
154 error
= ttydisc_getc_uio(tp
, uio
);
158 /* Maybe the device isn't used anyway. */
159 if (tty_opened(tp
) == 0)
162 /* Wait for more data. */
163 if (fp
->f_flag
& O_NONBLOCK
) {
167 error
= cv_wait_sig(&psc
->pts_outwait
, tp
->t_mtx
);
178 ptsdev_write(struct file
*fp
, struct uio
*uio
, struct ucred
*active_cred
,
179 int flags
, struct thread
*td
)
181 struct tty
*tp
= fp
->f_data
;
182 struct pts_softc
*psc
= tty_softc(tp
);
183 char ib
[256], *ibstart
;
184 size_t iblen
, rintlen
;
187 if (uio
->uio_resid
== 0)
192 iblen
= MIN(uio
->uio_resid
, sizeof ib
);
193 error
= uiomove(ib
, iblen
, uio
);
200 * When possible, avoid the slow path. rint_bypass()
201 * copies all input to the input queue at once.
205 if (ttydisc_can_bypass(tp
)) {
206 /* Store data at once. */
207 rintlen
= ttydisc_rint_bypass(tp
,
213 /* All data written. */
217 error
= ttydisc_rint(tp
, *ibstart
, 0);
219 /* Character stored successfully. */
226 /* Maybe the device isn't used anyway. */
227 if (tty_opened(tp
) == 0) {
232 /* Wait for more data. */
233 if (fp
->f_flag
& O_NONBLOCK
) {
238 /* Wake up users on the slave side. */
239 ttydisc_rint_done(tp
);
240 error
= cv_wait_sig(&psc
->pts_inwait
, tp
->t_mtx
);
245 if (uio
->uio_resid
== 0)
250 done
: ttydisc_rint_done(tp
);
256 ptsdev_ioctl(struct file
*fp
, u_long cmd
, void *data
,
257 struct ucred
*active_cred
, struct thread
*td
)
259 struct tty
*tp
= fp
->f_data
;
260 struct pts_softc
*psc
= tty_softc(tp
);
265 /* This device supports non-blocking operation. */
268 struct fiodgname_arg
*fgn
;
272 /* Reverse device name lookups, for ptsname() and ttyname(). */
275 if (psc
->pts_cdev
!= NULL
)
276 p
= devtoname(psc
->pts_cdev
);
278 #endif /* PTS_EXTERNAL */
283 return copyout(p
, fgn
->buf
, i
);
287 * We need to implement TIOCGPGRP and TIOCGSID here again. When
288 * called on the pseudo-terminal master, it should not check if
289 * the terminal is the foreground terminal of the calling
292 * TIOCGETA is also implemented here. Various Linux PTY routines
293 * often call isatty(), which is implemented by tcgetattr().
297 /* Obtain terminal flags through tcgetattr(). */
299 bcopy(&tp
->t_termios
, data
, sizeof(struct termios
));
302 #endif /* PTS_LINUX */
306 * We must make sure we turn tcsetattr() calls of TCSAFLUSH and
307 * TCSADRAIN into something different. If an application would
308 * call TCSAFLUSH or TCSADRAIN on the master descriptor, it may
309 * deadlock waiting for all data to be read.
313 #if defined(PTS_COMPAT) || defined(PTS_LINUX)
316 * Get the device unit number.
318 if (psc
->pts_unit
< 0)
320 *(unsigned int *)data
= psc
->pts_unit
;
322 #endif /* PTS_COMPAT || PTS_LINUX */
324 /* Get the foreground process group ID. */
326 if (tp
->t_pgrp
!= NULL
)
327 *(int *)data
= tp
->t_pgrp
->pg_id
;
329 *(int *)data
= NO_PID
;
333 /* Get the session leader process ID. */
335 if (tp
->t_session
== NULL
)
338 *(int *)data
= tp
->t_session
->s_sid
;
342 /* Yes, we are a pseudo-terminal master. */
345 /* Signal the foreground process group. */
347 if (sig
< 1 || sig
>= NSIG
)
351 tty_signal_pgrp(tp
, sig
);
355 /* Enable/disable packet mode. */
358 psc
->pts_flags
|= PTS_PKT
;
360 psc
->pts_flags
&= ~PTS_PKT
;
365 /* Just redirect this ioctl to the slave device. */
367 error
= tty_ioctl(tp
, cmd
, data
, td
);
374 ptsdev_poll(struct file
*fp
, int events
, struct ucred
*active_cred
,
377 struct tty
*tp
= fp
->f_data
;
378 struct pts_softc
*psc
= tty_softc(tp
);
383 if (tty_opened(tp
) == 0) {
384 /* Slave device is not opened. */
387 (POLLHUP
|POLLIN
|POLLRDNORM
|POLLOUT
|POLLWRNORM
));
390 if (events
& (POLLIN
|POLLRDNORM
)) {
391 /* See if we can getc something. */
392 if (ttydisc_getc_poll(tp
) ||
393 (psc
->pts_flags
& PTS_PKT
&& psc
->pts_pkt
))
394 revents
|= events
& (POLLIN
|POLLRDNORM
);
396 if (events
& (POLLOUT
|POLLWRNORM
)) {
397 /* See if we can rint something. */
398 if (ttydisc_rint_poll(tp
))
399 revents
|= events
& (POLLOUT
|POLLWRNORM
);
403 * No need to check for POLLHUP here. This device cannot be used
404 * as a callout device, which means we always have a carrier,
405 * because the master is.
410 * This code might look misleading, but the naming of
411 * poll events on this side is the opposite of the slave
414 if (events
& (POLLIN
|POLLRDNORM
))
415 selrecord(td
, &psc
->pts_outpoll
);
416 if (events
& (POLLOUT
|POLLWRNORM
))
417 selrecord(td
, &psc
->pts_inpoll
);
426 ptsdev_stat(struct file
*fp
, struct stat
*sb
, struct ucred
*active_cred
,
429 struct tty
*tp
= fp
->f_data
;
431 struct pts_softc
*psc
= tty_softc(tp
);
432 #endif /* PTS_EXTERNAL */
435 * According to POSIX, we must implement an fstat(). This also
436 * makes this implementation compatible with Linux binaries,
437 * because Linux calls fstat() on the pseudo-terminal master to
440 * XXX: POSIX also mentions we must fill in st_dev, st_atime,
441 * st_ctime and st_mtime, but how?
444 bzero(sb
, sizeof *sb
);
446 if (psc
->pts_cdev
!= NULL
)
447 sb
->st_ino
= sb
->st_rdev
= dev2udev(psc
->pts_cdev
);
449 #endif /* PTS_EXTERNAL */
450 sb
->st_ino
= sb
->st_rdev
= tty_udev(tp
);
451 sb
->st_mode
= S_IFCHR
;
452 sb
->st_uid
= tp
->t_dev
->si_cred
->cr_ruid
;
453 sb
->st_gid
= GID_TTY
;
459 ptsdev_close(struct file
*fp
, struct thread
*td
)
461 struct tty
*tp
= fp
->f_data
;
463 /* Deallocate TTY device. */
470 static struct fileops ptsdev_ops
= {
471 .fo_read
= ptsdev_read
,
472 .fo_write
= ptsdev_write
,
473 .fo_ioctl
= ptsdev_ioctl
,
474 .fo_poll
= ptsdev_poll
,
475 .fo_stat
= ptsdev_stat
,
476 .fo_close
= ptsdev_close
,
477 .fo_flags
= DFLAG_PASSABLE
,
485 ptsdrv_outwakeup(struct tty
*tp
)
487 struct pts_softc
*psc
= tty_softc(tp
);
489 cv_broadcast(&psc
->pts_outwait
);
490 selwakeup(&psc
->pts_outpoll
);
494 ptsdrv_inwakeup(struct tty
*tp
)
496 struct pts_softc
*psc
= tty_softc(tp
);
498 cv_broadcast(&psc
->pts_inwait
);
499 selwakeup(&psc
->pts_inpoll
);
503 ptsdrv_close(struct tty
*tp
)
506 /* Wake up any blocked readers/writers. */
507 ptsdrv_outwakeup(tp
);
512 ptsdrv_pktnotify(struct tty
*tp
, char event
)
514 struct pts_softc
*psc
= tty_softc(tp
);
517 * Clear conflicting flags.
522 psc
->pts_pkt
&= ~TIOCPKT_START
;
525 psc
->pts_pkt
&= ~TIOCPKT_STOP
;
528 psc
->pts_pkt
&= ~TIOCPKT_DOSTOP
;
531 psc
->pts_pkt
&= ~TIOCPKT_NOSTOP
;
535 psc
->pts_pkt
|= event
;
536 ptsdrv_outwakeup(tp
);
540 ptsdrv_free(void *softc
)
542 struct pts_softc
*psc
= softc
;
544 /* Make device number available again. */
545 if (psc
->pts_unit
>= 0)
546 free_unr(pts_pool
, psc
->pts_unit
);
548 chgptscnt(psc
->pts_uidinfo
, -1, 0);
549 uifree(psc
->pts_uidinfo
);
552 /* Destroy master device as well. */
553 if (psc
->pts_cdev
!= NULL
)
554 destroy_dev_sched(psc
->pts_cdev
);
555 #endif /* PTS_EXTERNAL */
560 static struct ttydevsw pts_class
= {
561 .tsw_flags
= TF_NOPREFIX
,
562 .tsw_outwakeup
= ptsdrv_outwakeup
,
563 .tsw_inwakeup
= ptsdrv_inwakeup
,
564 .tsw_close
= ptsdrv_close
,
565 .tsw_pktnotify
= ptsdrv_pktnotify
,
566 .tsw_free
= ptsdrv_free
,
570 pts_alloc(int fflags
, struct thread
*td
, struct file
*fp
)
574 struct pts_softc
*psc
;
575 struct proc
*p
= td
->td_proc
;
576 struct uidinfo
*uid
= td
->td_ucred
->cr_ruidinfo
;
578 /* Resource limiting. */
580 ok
= chgptscnt(uid
, 1, lim_cur(p
, RLIMIT_NPTS
));
585 /* Try to allocate a new pts unit number. */
586 unit
= alloc_unr(pts_pool
);
588 chgptscnt(uid
, -1, 0);
592 /* Allocate TTY and softc. */
593 psc
= malloc(sizeof(struct pts_softc
), M_PTS
, M_WAITOK
|M_ZERO
);
594 cv_init(&psc
->pts_inwait
, "pts inwait");
595 cv_init(&psc
->pts_outwait
, "pts outwait");
597 psc
->pts_unit
= unit
;
598 psc
->pts_uidinfo
= uid
;
601 tp
= tty_alloc(&pts_class
, psc
, NULL
);
603 /* Expose the slave device as well. */
604 tty_makedev(tp
, td
->td_ucred
, "pts/%u", psc
->pts_unit
);
606 finit(fp
, fflags
, DTYPE_PTS
, tp
, &ptsdev_ops
);
613 pts_alloc_external(int fflags
, struct thread
*td
, struct file
*fp
,
614 struct cdev
*dev
, const char *name
)
618 struct pts_softc
*psc
;
619 struct proc
*p
= td
->td_proc
;
620 struct uidinfo
*uid
= td
->td_ucred
->cr_ruidinfo
;
622 /* Resource limiting. */
624 ok
= chgptscnt(uid
, 1, lim_cur(p
, RLIMIT_NPTS
));
629 /* Allocate TTY and softc. */
630 psc
= malloc(sizeof(struct pts_softc
), M_PTS
, M_WAITOK
|M_ZERO
);
631 cv_init(&psc
->pts_inwait
, "pts inwait");
632 cv_init(&psc
->pts_outwait
, "pts outwait");
636 psc
->pts_uidinfo
= uid
;
639 tp
= tty_alloc(&pts_class
, psc
, NULL
);
641 /* Expose the slave device as well. */
642 tty_makedev(tp
, td
->td_ucred
, "%s", name
);
644 finit(fp
, fflags
, DTYPE_PTS
, tp
, &ptsdev_ops
);
648 #endif /* PTS_EXTERNAL */
651 posix_openpt(struct thread
*td
, struct posix_openpt_args
*uap
)
657 * POSIX states it's unspecified when other flags are passed. We
660 if (uap
->flags
& ~(O_RDWR
|O_NOCTTY
))
663 error
= falloc(td
, &fp
, &fd
);
667 /* Allocate the actual pseudo-TTY. */
668 error
= pts_alloc(FFLAGS(uap
->flags
& O_ACCMODE
), td
, fp
);
670 fdclose(td
->td_proc
->p_fd
, fp
, fd
, td
);
674 /* Pass it back to userspace. */
675 td
->td_retval
[0] = fd
;
681 #if defined(PTS_COMPAT) || defined(PTS_LINUX)
683 ptmx_fdopen(struct cdev
*dev
, int fflags
, struct thread
*td
, struct file
*fp
)
687 error
= pts_alloc(fflags
& (FREAD
|FWRITE
), td
, fp
);
694 static struct cdevsw ptmx_cdevsw
= {
695 .d_version
= D_VERSION
,
696 .d_fdopen
= ptmx_fdopen
,
699 #endif /* PTS_COMPAT || PTS_LINUX */
702 pts_init(void *unused
)
705 pts_pool
= new_unrhdr(0, MAXPTSDEVS
, NULL
);
706 #if defined(PTS_COMPAT) || defined(PTS_LINUX)
707 make_dev(&ptmx_cdevsw
, 0, UID_ROOT
, GID_WHEEL
, 0666, "ptmx");
708 #endif /* PTS_COMPAT || PTS_LINUX */
711 SYSINIT(pts
, SI_SUB_DRIVERS
, SI_ORDER_MIDDLE
, pts_init
, NULL
);