#include <sys/cdefs.h>
#define PTS_COMPAT
#define PTS_EXTERNAL
#define PTS_LINUX
#include <sys/param.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/filio.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/racct.h>
#include <sys/resourcevar.h>
#include <sys/serial.h>
#include <sys/stat.h>
#include <sys/stdarg.h>
#include <sys/syscall.h>
#include <sys/syscallsubr.h>
#include <sys/sysctl.h>
#include <sys/sysproto.h>
#include <sys/systm.h>
#include <sys/tty.h>
#include <sys/ttycom.h>
#include <sys/uio.h>
#include <sys/user.h>
static struct unrhdr *pts_pool;
static MALLOC_DEFINE(M_PTS, "pts", "pseudo tty device");
struct pts_softc {
int pts_unit;
unsigned int pts_flags;
#define PTS_PKT 0x1
#define PTS_FINISHED 0x2
char pts_pkt;
struct cv pts_inwait;
struct selinfo pts_inpoll;
struct cv pts_outwait;
struct selinfo pts_outpoll;
#ifdef PTS_EXTERNAL
struct cdev *pts_cdev;
#endif
struct ucred *pts_cred;
};
static int
ptsdev_read(struct file *fp, struct uio *uio, struct ucred *active_cred,
int flags, struct thread *td)
{
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
int error = 0;
char pkt;
if (uio->uio_resid == 0)
return (0);
tty_lock(tp);
for (;;) {
if (psc->pts_flags & PTS_PKT && psc->pts_pkt) {
pkt = psc->pts_pkt;
psc->pts_pkt = 0;
tty_unlock(tp);
error = ureadc(pkt, uio);
return (error);
}
if (ttydisc_getc_poll(tp)) {
if (psc->pts_flags & PTS_PKT) {
tty_unlock(tp);
error = ureadc(TIOCPKT_DATA, uio);
if (error)
return (error);
tty_lock(tp);
}
error = ttydisc_getc_uio(tp, uio);
break;
}
if (psc->pts_flags & PTS_FINISHED)
break;
if (fp->f_flag & O_NONBLOCK) {
error = EWOULDBLOCK;
break;
}
error = cv_wait_sig(&psc->pts_outwait, tp->t_mtx);
if (error != 0)
break;
}
tty_unlock(tp);
return (error);
}
static int
ptsdev_write(struct file *fp, struct uio *uio, struct ucred *active_cred,
int flags, struct thread *td)
{
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
char ib[256], *ibstart;
size_t iblen, rintlen;
int error = 0;
if (uio->uio_resid == 0)
return (0);
for (;;) {
ibstart = ib;
iblen = MIN(uio->uio_resid, sizeof ib);
error = uiomove(ib, iblen, uio);
tty_lock(tp);
if (error != 0) {
iblen = 0;
goto done;
}
MPASS(iblen > 0);
do {
rintlen = ttydisc_rint_simple(tp, ibstart, iblen);
ibstart += rintlen;
iblen -= rintlen;
if (iblen == 0) {
break;
}
if (psc->pts_flags & PTS_FINISHED) {
error = EIO;
goto done;
}
if (fp->f_flag & O_NONBLOCK) {
error = EWOULDBLOCK;
goto done;
}
ttydisc_rint_done(tp);
error = cv_wait_sig(&psc->pts_inwait, tp->t_mtx);
if (error != 0)
goto done;
} while (iblen > 0);
if (uio->uio_resid == 0)
break;
tty_unlock(tp);
}
done: ttydisc_rint_done(tp);
tty_unlock(tp);
uio->uio_resid += iblen;
return (error);
}
static int
ptsdev_ioctl(struct file *fp, u_long cmd, void *data,
struct ucred *active_cred, struct thread *td)
{
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
int error = 0, sig;
switch (cmd) {
case FIODTYPE:
*(int *)data = D_TTY;
return (0);
case FIONBIO:
return (0);
case FIONREAD:
tty_lock(tp);
*(int *)data = ttydisc_getc_poll(tp);
tty_unlock(tp);
return (0);
case FIODGNAME:
#ifdef COMPAT_FREEBSD32
case FIODGNAME_32:
#endif
{
struct fiodgname_arg *fgn;
const char *p;
int i;
fgn = data;
p = tty_devname(tp);
i = strlen(p) + 1;
if (i > fgn->len)
return (EINVAL);
return (copyout(p, fiodgname_buf_get_ptr(fgn, cmd), i));
}
#ifdef PTS_LINUX
case TIOCGETA:
tty_lock(tp);
*(struct termios*)data = tp->t_termios;
tty_unlock(tp);
return (0);
#endif
case TIOCSETAF:
case TIOCSETAW:
cmd = TIOCSETA;
break;
#if defined(PTS_COMPAT) || defined(PTS_LINUX)
case TIOCGPTN:
if (psc->pts_unit < 0)
return (ENOTTY);
*(unsigned int *)data = psc->pts_unit;
return (0);
#endif
case TIOCGPGRP:
tty_lock(tp);
if (tp->t_pgrp != NULL)
*(int *)data = tp->t_pgrp->pg_id;
else
*(int *)data = NO_PID;
tty_unlock(tp);
return (0);
case TIOCGSID:
tty_lock(tp);
if (tp->t_session == NULL)
error = ENOTTY;
else
*(int *)data = tp->t_session->s_sid;
tty_unlock(tp);
return (error);
case TIOCPTMASTER:
return (0);
case TIOCSIG:
sig = *(int *)data;
if (sig < 1 || sig >= NSIG)
return (EINVAL);
tty_lock(tp);
tty_signal_pgrp(tp, sig);
tty_unlock(tp);
return (0);
case TIOCPKT:
tty_lock(tp);
if (*(int *)data)
psc->pts_flags |= PTS_PKT;
else
psc->pts_flags &= ~PTS_PKT;
tty_unlock(tp);
return (0);
}
tty_lock(tp);
error = tty_ioctl(tp, cmd, data, fp->f_flag, td);
tty_unlock(tp);
if (error == ENOIOCTL)
error = ENOTTY;
return (error);
}
static int
ptsdev_poll(struct file *fp, int events, struct ucred *active_cred,
struct thread *td)
{
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
int revents = 0;
tty_lock(tp);
if (psc->pts_flags & PTS_FINISHED) {
tty_unlock(tp);
return ((events & (POLLIN|POLLRDNORM)) | POLLHUP);
}
if (events & (POLLIN|POLLRDNORM)) {
if (ttydisc_getc_poll(tp) ||
(psc->pts_flags & PTS_PKT && psc->pts_pkt))
revents |= events & (POLLIN|POLLRDNORM);
}
if (events & (POLLOUT|POLLWRNORM)) {
if (ttydisc_rint_poll(tp))
revents |= events & (POLLOUT|POLLWRNORM);
}
if (revents == 0) {
if (events & (POLLIN|POLLRDNORM))
selrecord(td, &psc->pts_outpoll);
if (events & (POLLOUT|POLLWRNORM))
selrecord(td, &psc->pts_inpoll);
}
tty_unlock(tp);
return (revents);
}
static void
pts_kqops_read_detach(struct knote *kn)
{
struct file *fp = kn->kn_fp;
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
knlist_remove(&psc->pts_outpoll.si_note, kn, 0);
}
static int
pts_kqops_read_event(struct knote *kn, long hint)
{
struct file *fp = kn->kn_fp;
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
if (psc->pts_flags & PTS_FINISHED) {
kn->kn_flags |= EV_EOF;
return (1);
} else {
kn->kn_data = ttydisc_getc_poll(tp);
return (kn->kn_data > 0);
}
}
static void
pts_kqops_write_detach(struct knote *kn)
{
struct file *fp = kn->kn_fp;
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
knlist_remove(&psc->pts_inpoll.si_note, kn, 0);
}
static int
pts_kqops_write_event(struct knote *kn, long hint)
{
struct file *fp = kn->kn_fp;
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
if (psc->pts_flags & PTS_FINISHED) {
kn->kn_flags |= EV_EOF;
return (1);
} else {
kn->kn_data = ttydisc_rint_poll(tp);
return (kn->kn_data > 0);
}
}
static const struct filterops pts_kqops_read = {
.f_isfd = 1,
.f_detach = pts_kqops_read_detach,
.f_event = pts_kqops_read_event,
.f_copy = knote_triv_copy,
};
static const struct filterops pts_kqops_write = {
.f_isfd = 1,
.f_detach = pts_kqops_write_detach,
.f_event = pts_kqops_write_event,
.f_copy = knote_triv_copy,
};
static int
ptsdev_kqfilter(struct file *fp, struct knote *kn)
{
struct tty *tp = fp->f_data;
struct pts_softc *psc = tty_softc(tp);
int error = 0;
tty_lock(tp);
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &pts_kqops_read;
knlist_add(&psc->pts_outpoll.si_note, kn, 1);
break;
case EVFILT_WRITE:
kn->kn_fop = &pts_kqops_write;
knlist_add(&psc->pts_inpoll.si_note, kn, 1);
break;
default:
error = EINVAL;
break;
}
tty_unlock(tp);
return (error);
}
static int
ptsdev_stat(struct file *fp, struct stat *sb, struct ucred *active_cred)
{
struct tty *tp = fp->f_data;
#ifdef PTS_EXTERNAL
struct pts_softc *psc = tty_softc(tp);
#endif
struct cdev *dev = tp->t_dev;
bzero(sb, sizeof *sb);
#ifdef PTS_EXTERNAL
if (psc->pts_cdev != NULL)
sb->st_ino = sb->st_rdev = dev2udev(psc->pts_cdev);
else
#endif
sb->st_ino = sb->st_rdev = tty_udev(tp);
sb->st_atim = dev->si_atime;
sb->st_ctim = dev->si_ctime;
sb->st_mtim = dev->si_mtime;
sb->st_uid = dev->si_uid;
sb->st_gid = dev->si_gid;
sb->st_mode = dev->si_mode | S_IFCHR;
return (0);
}
static int
ptsdev_close(struct file *fp, struct thread *td)
{
struct tty *tp = fp->f_data;
tty_lock(tp);
tty_rel_gone(tp);
if (fp->f_vnode != NULL)
return (vnops.fo_close(fp, td));
return (0);
}
static int
ptsdev_fill_kinfo(struct file *fp, struct kinfo_file *kif, struct filedesc *fdp)
{
struct tty *tp;
kif->kf_type = KF_TYPE_PTS;
tp = fp->f_data;
kif->kf_un.kf_pts.kf_pts_dev = tty_udev(tp);
kif->kf_un.kf_pts.kf_pts_dev_freebsd11 =
kif->kf_un.kf_pts.kf_pts_dev;
strlcpy(kif->kf_path, tty_devname(tp), sizeof(kif->kf_path));
return (0);
}
static const struct fileops ptsdev_ops = {
.fo_read = ptsdev_read,
.fo_write = ptsdev_write,
.fo_truncate = invfo_truncate,
.fo_ioctl = ptsdev_ioctl,
.fo_poll = ptsdev_poll,
.fo_kqfilter = ptsdev_kqfilter,
.fo_stat = ptsdev_stat,
.fo_close = ptsdev_close,
.fo_chmod = invfo_chmod,
.fo_chown = invfo_chown,
.fo_sendfile = invfo_sendfile,
.fo_fill_kinfo = ptsdev_fill_kinfo,
.fo_cmp = file_kcmp_generic,
.fo_flags = DFLAG_PASSABLE,
};
static void
ptsdrv_outwakeup(struct tty *tp)
{
struct pts_softc *psc = tty_softc(tp);
cv_broadcast(&psc->pts_outwait);
selwakeup(&psc->pts_outpoll);
KNOTE_LOCKED(&psc->pts_outpoll.si_note, 0);
}
static void
ptsdrv_inwakeup(struct tty *tp)
{
struct pts_softc *psc = tty_softc(tp);
cv_broadcast(&psc->pts_inwait);
selwakeup(&psc->pts_inpoll);
KNOTE_LOCKED(&psc->pts_inpoll.si_note, 0);
}
static int
ptsdrv_open(struct tty *tp)
{
struct pts_softc *psc = tty_softc(tp);
psc->pts_flags &= ~PTS_FINISHED;
return (0);
}
static void
ptsdrv_close(struct tty *tp)
{
struct pts_softc *psc = tty_softc(tp);
psc->pts_flags |= PTS_FINISHED;
ptsdrv_outwakeup(tp);
ptsdrv_inwakeup(tp);
}
static void
ptsdrv_pktnotify(struct tty *tp, char event)
{
struct pts_softc *psc = tty_softc(tp);
switch (event) {
case TIOCPKT_STOP:
psc->pts_pkt &= ~TIOCPKT_START;
break;
case TIOCPKT_START:
psc->pts_pkt &= ~TIOCPKT_STOP;
break;
case TIOCPKT_NOSTOP:
psc->pts_pkt &= ~TIOCPKT_DOSTOP;
break;
case TIOCPKT_DOSTOP:
psc->pts_pkt &= ~TIOCPKT_NOSTOP;
break;
}
psc->pts_pkt |= event;
ptsdrv_outwakeup(tp);
}
static void
ptsdrv_free(void *softc)
{
struct pts_softc *psc = softc;
if (psc->pts_unit >= 0)
free_unr(pts_pool, psc->pts_unit);
chgptscnt(psc->pts_cred->cr_ruidinfo, -1, 0);
racct_sub_cred(psc->pts_cred, RACCT_NPTS, 1);
crfree(psc->pts_cred);
seldrain(&psc->pts_inpoll);
seldrain(&psc->pts_outpoll);
knlist_destroy(&psc->pts_inpoll.si_note);
knlist_destroy(&psc->pts_outpoll.si_note);
#ifdef PTS_EXTERNAL
if (psc->pts_cdev != NULL)
destroy_dev_sched(psc->pts_cdev);
#endif
free(psc, M_PTS);
}
static struct ttydevsw pts_class = {
.tsw_flags = TF_NOPREFIX,
.tsw_outwakeup = ptsdrv_outwakeup,
.tsw_inwakeup = ptsdrv_inwakeup,
.tsw_open = ptsdrv_open,
.tsw_close = ptsdrv_close,
.tsw_pktnotify = ptsdrv_pktnotify,
.tsw_free = ptsdrv_free,
};
#ifndef PTS_EXTERNAL
static
#endif
int
pts_alloc(int fflags, struct thread *td, struct file *fp)
{
int unit, ok, error;
struct tty *tp;
struct pts_softc *psc;
struct proc *p = td->td_proc;
struct ucred *cred = td->td_ucred;
PROC_LOCK(p);
error = racct_add(p, RACCT_NPTS, 1);
if (error != 0) {
PROC_UNLOCK(p);
return (EAGAIN);
}
ok = chgptscnt(cred->cr_ruidinfo, 1, lim_cur(td, RLIMIT_NPTS));
if (!ok) {
racct_sub(p, RACCT_NPTS, 1);
PROC_UNLOCK(p);
return (EAGAIN);
}
PROC_UNLOCK(p);
unit = alloc_unr(pts_pool);
if (unit < 0) {
racct_sub(p, RACCT_NPTS, 1);
chgptscnt(cred->cr_ruidinfo, -1, 0);
return (EAGAIN);
}
psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO);
cv_init(&psc->pts_inwait, "ptsin");
cv_init(&psc->pts_outwait, "ptsout");
psc->pts_unit = unit;
psc->pts_cred = crhold(cred);
tp = tty_alloc(&pts_class, psc);
knlist_init_mtx(&psc->pts_inpoll.si_note, tp->t_mtx);
knlist_init_mtx(&psc->pts_outpoll.si_note, tp->t_mtx);
tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit);
finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops);
return (0);
}
#ifdef PTS_EXTERNAL
int
pts_alloc_external(int fflags, struct thread *td, struct file *fp,
struct cdev *dev, const char *name)
{
int ok, error;
struct tty *tp;
struct pts_softc *psc;
struct proc *p = td->td_proc;
struct ucred *cred = td->td_ucred;
PROC_LOCK(p);
error = racct_add(p, RACCT_NPTS, 1);
if (error != 0) {
PROC_UNLOCK(p);
return (EAGAIN);
}
ok = chgptscnt(cred->cr_ruidinfo, 1, lim_cur(td, RLIMIT_NPTS));
if (!ok) {
racct_sub(p, RACCT_NPTS, 1);
PROC_UNLOCK(p);
return (EAGAIN);
}
PROC_UNLOCK(p);
psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO);
cv_init(&psc->pts_inwait, "ptsin");
cv_init(&psc->pts_outwait, "ptsout");
psc->pts_unit = -1;
psc->pts_cdev = dev;
psc->pts_cred = crhold(cred);
tp = tty_alloc(&pts_class, psc);
knlist_init_mtx(&psc->pts_inpoll.si_note, tp->t_mtx);
knlist_init_mtx(&psc->pts_outpoll.si_note, tp->t_mtx);
tty_makedev(tp, td->td_ucred, "%s", name);
finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops);
return (0);
}
#endif
int
sys_posix_openpt(struct thread *td, struct posix_openpt_args *uap)
{
int error, fd;
struct file *fp;
if (uap->flags & ~(O_RDWR|O_NOCTTY|O_CLOEXEC))
return (EINVAL);
error = falloc(td, &fp, &fd, uap->flags);
if (error)
return (error);
error = pts_alloc(FFLAGS(uap->flags & O_ACCMODE), td, fp);
if (error != 0) {
fdclose(td, fp, fd);
fdrop(fp, td);
return (error);
}
td->td_retval[0] = fd;
fdrop(fp, td);
return (0);
}
static void
pts_init(void *unused)
{
pts_pool = new_unrhdr(0, INT_MAX, NULL);
}
SYSINIT(pts, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, pts_init, NULL);