rt-thread-official/components/lwp/terminal/freebsd/tty_compat.c

714 lines
20 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* (tty_compat.c)
* The compatible layer which interacts with process management core (lwp)
*
* Change Logs:
* Date Author Notes
* 2023-11-13 Shell init ver.
*/
#define DBG_TAG "lwp.tty"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include "../tty_config.h"
#include "../tty_internal.h"
#include "../terminal.h"
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 1994-1995 Søren Schmidt
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* is the tty and session leader already binding ? */
static rt_bool_t _is_already_binding(lwp_tty_t tp, rt_lwp_t p)
{
rt_bool_t rc;
rt_processgroup_t pgrp = p->pgrp;
/* lwp is already locked */
RT_ASSERT(pgrp);
/* Note: pgrp->session is constant after process group is created */
if (tp->t_session && tp->t_session == pgrp->session)
{
rc = RT_TRUE;
}
else
{
rc = RT_FALSE;
}
return rc;
}
static rt_bool_t _is_tty_or_sess_busy(lwp_tty_t tp, rt_lwp_t p)
{
rt_bool_t rc;
rt_session_t sess = p->pgrp->session;
SESS_LOCK(sess);
if (sess->ctty)
{
rc = RT_TRUE;
}
else if (tp->t_session)
{
/**
* TODO: allow TTY stolen if the sess leader is killed while resource
* had not been collected
*/
if (tp->t_session->leader == RT_NULL)
rc = RT_FALSE;
else
rc = RT_TRUE;
}
else
{
rc = RT_FALSE;
}
SESS_UNLOCK(sess);
return rc;
}
int lwp_tty_bg_stop(struct lwp_tty *tp, struct rt_condvar *cv)
{
int error;
int revokecnt = tp->t_revokecnt;
rt_lwp_t self_lwp;
rt_thread_t header_thr;
rt_thread_t cur_thr = rt_thread_self();
int jobctl_stopped;
self_lwp = cur_thr->lwp;
RT_ASSERT(self_lwp);
jobctl_stopped = self_lwp->jobctl_stopped;
tty_lock_assert(tp, MA_OWNED | MA_NOTRECURSED);
MPASS(!tty_gone(tp));
LWP_LOCK(self_lwp);
header_thr = rt_list_entry(self_lwp->t_grp.prev, struct rt_thread, sibling);
if (!jobctl_stopped && header_thr == cur_thr &&
cur_thr->sibling.prev == &self_lwp->t_grp)
{
/* update lwp status */
jobctl_stopped = self_lwp->jobctl_stopped = RT_TRUE;
}
LWP_UNLOCK(self_lwp);
error = cv_wait(cv, tp->t_mtx);
if (jobctl_stopped)
{
self_lwp->jobctl_stopped = RT_FALSE;
}
/* Bail out when the device slipped away. */
if (tty_gone(tp))
return -ENXIO;
/* Restart the system call when we may have been revoked. */
if (tp->t_revokecnt != revokecnt)
return -ERESTART;
return error;
}
/* process management */
int lwp_tty_set_ctrl_proc(lwp_tty_t tp, rt_thread_t td)
{
int rc = -1;
struct rt_lwp *p = td->lwp;
tty_unlock(tp);
LWP_LOCK(p);
tty_lock(tp);
if (is_sess_leader(p))
{
if (_is_already_binding(tp, p))
{
rc = 0;
}
else if (_is_tty_or_sess_busy(tp, p))
{
rc = -EPERM;
}
else
{
/**
* Binding controlling process
* note: p->pgrp is protected by lwp lock;
* pgrp->session is always constant.
*/
tp->t_session = p->pgrp->session;
tp->t_session->ctty = tp;
tp->t_sessioncnt++;
/* Assign foreground process group */
tp->t_pgrp = p->pgrp;
p->term_ctrlterm = RT_TRUE;
LOG_D("%s(sid=%d)", __func__, tp->t_session->sid);
rc = 0;
}
}
else
{
rc = -EPERM;
}
LWP_UNLOCK(p);
return rc;
}
int lwp_tty_assign_foreground(lwp_tty_t tp, rt_thread_t td, int pgid)
{
struct rt_processgroup *pg;
rt_lwp_t cur_lwp = td->lwp;
tty_unlock(tp);
pg = lwp_pgrp_find_and_inc_ref(pgid);
if (pg == NULL || cur_lwp == NULL)
{
tty_lock(tp);
return -EPERM;
}
else
{
PGRP_LOCK(pg);
if (pg->sid != cur_lwp->sid)
{
PGRP_UNLOCK(pg);
lwp_pgrp_dec_ref(pg);
LOG_D("%s: NoPerm current process (pid=%d, pgid=%d, sid=%d), "
"tagget group (pgid=%d, sid=%d)", __func__,
cur_lwp->pid, cur_lwp->pgid, cur_lwp->sid, pgid, pg->sid);
tty_lock(tp);
return -EPERM;
}
}
tty_lock(tp);
/**
* Determine if this TTY is the controlling TTY after
* relocking the TTY.
*/
if (!tty_is_ctty(tp, td->lwp))
{
PGRP_UNLOCK(pg);
LOG_D("%s: NoCTTY current process (pid=%d, pgid=%d, sid=%d), "
"tagget group (pgid=%d, sid=%d)", __func__,
cur_lwp->pid, cur_lwp->pgid, cur_lwp->sid, pgid, pg->sid);
return -ENOTTY;
}
tp->t_pgrp = pg;
PGRP_UNLOCK(pg);
lwp_pgrp_dec_ref(pg);
/* Wake up the background process groups. */
cv_broadcast(&tp->t_bgwait);
LOG_D("%s: Foreground group %p (pgid=%d)", __func__, tp->t_pgrp,
tp->t_pgrp ? tp->t_pgrp->pgid : -1);
return 0;
}
/**
* Signalling processes.
*/
void lwp_tty_signal_sessleader(struct lwp_tty *tp, int sig)
{
struct rt_lwp *p;
struct rt_session *s;
tty_assert_locked(tp);
MPASS(sig >= 1 && sig < _LWP_NSIG);
/* Make signals start output again. */
tp->t_flags &= ~TF_STOPPED;
tp->t_termios.c_lflag &= ~FLUSHO;
/**
* Load s.leader exactly once to avoid race where s.leader is
* set to NULL by a concurrent invocation of killjobc() by the
* session leader. Note that we are not holding t_session's
* lock for the read.
*/
if ((s = tp->t_session) != NULL &&
(p = (void *)rt_atomic_load((rt_atomic_t *)&s->leader)) != NULL)
{
lwp_signal_kill(p, sig, SI_KERNEL, 0);
}
}
void lwp_tty_signal_pgrp(struct lwp_tty *tp, int sig)
{
tty_assert_locked(tp);
MPASS(sig >= 1 && sig < _LWP_NSIG);
/* Make signals start output again. */
tp->t_flags &= ~TF_STOPPED;
tp->t_termios.c_lflag &= ~FLUSHO;
#ifdef USING_BSD_SIGINFO
if (sig == SIGINFO && !(tp->t_termios.c_lflag & NOKERNINFO))
tty_info(tp);
#endif /* USING_BSD_SIGINFO */
if (tp->t_pgrp != NULL)
{
PGRP_LOCK(tp->t_pgrp);
lwp_pgrp_signal_kill(tp->t_pgrp, sig, SI_KERNEL, 0);
PGRP_UNLOCK(tp->t_pgrp);
}
}
/* bsd_ttydev_methods.d_ioctl */
rt_inline size_t _copy_to_user(void *to, void *from, size_t n)
{
return lwp_put_to_user(to, from, n) == n ? 0 : -EFAULT;
}
rt_inline size_t _copy_from_user(void *to, void *from, size_t n)
{
return lwp_get_from_user(to, from, n) == n ? 0 : -EFAULT;
}
static void termios_to_termio(struct termios *tios, struct termio *tio)
{
memset(tio, 0, sizeof(*tio));
tio->c_iflag = tios->c_iflag;
tio->c_oflag = tios->c_oflag;
tio->c_cflag = tios->c_cflag;
tio->c_lflag = tios->c_lflag;
tio->c_line = tios->c_line;
memcpy(tio->c_cc, tios->c_cc, NCC);
}
static void termio_to_termios(struct termio *tio, struct termios *tios)
{
int i;
tios->c_iflag = tio->c_iflag;
tios->c_oflag = tio->c_oflag;
tios->c_cflag = tio->c_cflag;
tios->c_lflag = tio->c_lflag;
for (i = NCC; i < NCCS; i++)
tios->c_cc[i] = _POSIX_VDISABLE;
memcpy(tios->c_cc, tio->c_cc, NCC);
}
#define IOCTL(cmd, data, fflags, td) \
bsd_ttydev_methods.d_ioctl(tp, cmd, data, fflags, td)
int lwp_tty_ioctl_adapter(lwp_tty_t tp, int cmd, int oflags, void *args, rt_thread_t td)
{
long fflags = FFLAGS(oflags);
struct termios tios;
struct termio tio;
int error;
LOG_D("%s(cmd=0x%x, args=%p)", __func__, cmd, args);
switch (cmd & 0xffff)
{
case TCGETS:
error = IOCTL(TIOCGETA, (rt_caddr_t)&tios, fflags, td);
if (error)
break;
cfsetospeed(&tios, tios.__c_ispeed);
error = _copy_to_user(args, &tios, sizeof(tios));
break;
case TCSETS:
error = _copy_from_user(&tios, args, sizeof(tios));
if (error)
break;
tios.__c_ispeed = tios.__c_ospeed = cfgetospeed(&tios);
error = (IOCTL(TIOCSETA, (rt_caddr_t)&tios, fflags, td));
break;
case TCSETSW:
error = _copy_from_user(&tios, args, sizeof(tios));
if (error)
break;
error = (IOCTL(TIOCSETAW, (rt_caddr_t)&tios, fflags, td));
break;
case TCSETSF:
error = _copy_from_user(&tios, args, sizeof(tios));
if (error)
break;
error = (IOCTL(TIOCSETAF, (rt_caddr_t)&tios, fflags, td));
break;
case TCGETA:
error = IOCTL(TIOCGETA, (rt_caddr_t)&tios, fflags, td);
if (error)
break;
termios_to_termio(&tios, &tio);
error = _copy_to_user((void *)args, &tio, sizeof(tio));
break;
case TCSETA:
error = _copy_from_user(&tio, (void *)args, sizeof(tio));
if (error)
break;
termio_to_termios(&tio, &tios);
error = (IOCTL(TIOCSETA, (rt_caddr_t)&tios, fflags, td));
break;
case TCSETAW:
error = _copy_from_user(&tio, (void *)args, sizeof(tio));
if (error)
break;
termio_to_termios(&tio, &tios);
error = (IOCTL(TIOCSETAW, (rt_caddr_t)&tios, fflags, td));
break;
case TCSETAF:
error = _copy_from_user(&tio, (void *)args, sizeof(tio));
if (error)
break;
termio_to_termios(&tio, &tios);
error = (IOCTL(TIOCSETAF, (rt_caddr_t)&tios, fflags, td));
break;
case TCSBRK:
if (args != 0)
{
/**
* Linux manual: SVr4, UnixWare, Solaris, and Linux treat
* tcsendbreak(fd,arg) with nonzero arg like tcdrain(fd).
*/
error = IOCTL(TIOCDRAIN, (rt_caddr_t)&tios, fflags, td);
}
else
{
/**
* Linux manual: If the terminal is using asynchronous serial
* data transmission, and arg is zero, then send a break (a
* stream of zero bits) for between 0.25 and 0.5 seconds.
*/
LOG_D("%s: ioctl TCSBRK arg 0 not implemented", __func__);
error = -ENOSYS;
}
break;
#ifdef USING_BSD_IOCTL_EXT
/* Software flow control */
case TCXONC: {
switch (args->arg)
{
case TCOOFF:
args->cmd = TIOCSTOP;
break;
case TCOON:
args->cmd = TIOCSTART;
break;
case TCIOFF:
case TCION: {
int c;
struct write_args wr;
error = IOCTL(TIOCGETA, (rt_caddr_t)&tios, fflags,
td);
if (error)
break;
fdrop(fp, td);
c = (args->arg == TCIOFF) ? VSTOP : VSTART;
c = tios.c_cc[c];
if (c != _POSIX_VDISABLE)
{
wr.fd = args->fd;
wr.buf = &c;
wr.nbyte = sizeof(c);
return (sys_write(td, &wr));
}
else
return 0;
}
default:
fdrop(fp, td);
return -EINVAL;
}
args->arg = 0;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
}
#endif /* USING_BSD_IOCTL_EXT */
case TCFLSH: {
int val;
error = 0;
switch ((rt_base_t)args)
{
case TCIFLUSH:
val = FREAD;
break;
case TCOFLUSH:
val = FWRITE;
break;
case TCIOFLUSH:
val = FREAD | FWRITE;
break;
default:
error = -EINVAL;
break;
}
if (!error)
error = (IOCTL(TIOCFLUSH, (rt_caddr_t)&val, fflags, td));
break;
}
#ifdef USING_BSD_IOCTL_EXT
case TIOCEXCL:
args->cmd = TIOCEXCL;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCNXCL:
args->cmd = TIOCNXCL;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
#endif /* USING_BSD_IOCTL_EXT */
/* Controlling terminal */
case TIOCSCTTY:
case TIOCNOTTY:
/* Process group and session ID */
case TIOCGPGRP:
case TIOCSPGRP:
case TIOCGSID:
/* TIOCOUTQ */
/* TIOCSTI */
case TIOCGWINSZ:
case TIOCSWINSZ:
error = IOCTL(cmd, (rt_caddr_t)args, fflags, td);
break;
#ifdef USING_BSD_IOCTL_EXT
case TIOCMGET:
args->cmd = TIOCMGET;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCMBIS:
args->cmd = TIOCMBIS;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCMBIC:
args->cmd = TIOCMBIC;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCMSET:
args->cmd = TIOCMSET;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
#endif /* USING_BSD_IOCTL_EXT */
/* TIOCGSOFTCAR */
/* TIOCSSOFTCAR */
case FIONREAD: /* TIOCINQ */
error = (IOCTL(FIONREAD, args, fflags, td));
break;
#ifdef USING_BSD_IOCTL_EXT
/* TIOCLINUX */
case TIOCCONS:
args->cmd = TIOCCONS;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCGSERIAL: {
struct linux_serial_struct lss;
bzero(&lss, sizeof(lss));
lss.type = PORT_16550A;
lss.flags = 0;
lss.close_delay = 0;
error = copyout(&lss, (void *)args->arg, sizeof(lss));
break;
}
case TIOCSSERIAL: {
struct linux_serial_struct lss;
error = copyin((void *)args->arg, &lss, sizeof(lss));
if (error)
break;
/* XXX - It really helps to have an implementation that
* does nothing. NOT!
*/
error = 0;
break;
}
case TIOCPKT:
args->cmd = TIOCPKT;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case FIONBIO:
args->cmd = FIONBIO;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCSETD: {
int line;
switch (args->arg)
{
case N_TTY:
line = TTYDISC;
break;
case N_SLIP:
line = SLIPDISC;
break;
case N_PPP:
line = PPPDISC;
break;
default:
fdrop(fp, td);
return -EINVAL;
}
error = (ioctl_emit(TIOCSETD, (rt_caddr_t)&line, fflags, td));
break;
}
case TIOCGETD: {
int linux_line;
int bsd_line = TTYDISC;
error =
ioctl_emit(TIOCGETD, (rt_caddr_t)&bsd_line, fflags, td);
if (error)
break;
switch (bsd_line)
{
case TTYDISC:
linux_line = N_TTY;
break;
case SLIPDISC:
linux_line = N_SLIP;
break;
case PPPDISC:
linux_line = N_PPP;
break;
default:
fdrop(fp, td);
return -EINVAL;
}
error = (copyout(&linux_line, (void *)args->arg, sizeof(int)));
break;
}
/* TCSBRKP */
/* TIOCTTYGSTRUCT */
case FIONCLEX:
args->cmd = FIONCLEX;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case FIOCLEX:
args->cmd = FIOCLEX;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case FIOASYNC:
args->cmd = FIOASYNC;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
/* TIOCSERCONFIG */
/* TIOCSERGWILD */
/* TIOCSERSWILD */
/* TIOCGLCKTRMIOS */
/* TIOCSLCKTRMIOS */
case TIOCSBRK:
args->cmd = TIOCSBRK;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCCBRK:
args->cmd = TIOCCBRK;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
case TIOCGPTN: {
int nb;
error = ioctl_emit(TIOCGPTN, (rt_caddr_t)&nb, fflags, td);
if (!error)
error = copyout(&nb, (void *)args->arg, sizeof(int));
break;
}
case TIOCGPTPEER:
linux_msg(td, "unsupported ioctl TIOCGPTPEER");
error = -ENOIOCTL;
break;
case TIOCSPTLCK:
/*
* Our unlockpt() does nothing. Check that fd refers
* to a pseudo-terminal master device.
*/
args->cmd = TIOCPTMASTER;
error = (sys_ioctl(td, (struct ioctl_args *)args));
break;
#endif /* USING_BSD_IOCTL_EXT */
/**
* those are for current implementation of devfs, and we dont want to
* log them
*/
case F_DUPFD:
case F_DUPFD_CLOEXEC:
case F_GETFD:
case F_SETFD:
case F_GETFL:
case F_SETFL:
/* fall back to fs */
error = -ENOIOCTL;
break;
default:
LOG_I("%s: unhandle commands 0x%x", __func__, cmd);
error = -ENOSYS;
break;
}
return (error);
}