Enable term for nix

This commit is contained in:
topjohnwu
2025-09-09 10:19:01 -07:00
parent ef4e230258
commit 177a456d8b
5 changed files with 82 additions and 91 deletions

View File

@@ -26,4 +26,4 @@ bytemuck = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
bit-set = { workspace = true }
argh = { workspace = true }
nix = { workspace = true, features = ["fs", "poll", "signal"] }
nix = { workspace = true, features = ["fs", "poll", "signal", "term"] }

View File

@@ -21,7 +21,7 @@ use std::fs::File;
use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::os::fd::FromRawFd;
use su::{get_pty_num, pump_tty, restore_stdin};
use su::{get_pty_num, pump_tty};
use zygisk::zygisk_should_load_module;
#[path = "../include/consts.rs"]
@@ -181,9 +181,8 @@ pub mod ffi {
fn recv_fd(socket: i32) -> i32;
fn recv_fds(socket: i32) -> Vec<i32>;
fn write_to_fd(self: &SuRequest, fd: i32);
fn pump_tty(infd: i32, outfd: i32);
fn pump_tty(ptmx: i32, pump_stdin: bool);
fn get_pty_num(fd: i32) -> i32;
fn restore_stdin() -> bool;
fn restorecon();
fn lgetfilecon(path: Utf8CStrRef, con: &mut [u8]) -> bool;
fn setfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;

View File

@@ -4,4 +4,4 @@ mod db;
mod pts;
pub use daemon::SuInfo;
pub use pts::{get_pty_num, pump_tty, restore_stdin};
pub use pts::{get_pty_num, pump_tty};

View File

@@ -1,13 +1,12 @@
use base::{FileOrStd, LibcReturn, LoggedResult, OsResult, libc, warn};
use libc::{
STDIN_FILENO, STDOUT_FILENO, TCSADRAIN, TCSAFLUSH, TIOCGWINSZ, TIOCSWINSZ, c_int, cfmakeraw,
ssize_t, tcgetattr, tcsetattr, termios, winsize,
};
use base::{FileOrStd, LibcReturn, LoggedResult, OsResult, ResultExt, libc, warn};
use libc::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ, TIOCSWINSZ, c_int, ssize_t, winsize};
use nix::{
fcntl::OFlag,
poll::{PollFd, PollFlags, PollTimeout},
sys::signal::{SigSet, Signal},
poll::{PollFd, PollFlags, PollTimeout, poll},
sys::signal::{SigSet, Signal, raise},
sys::signalfd::{SfdFlags, SignalFd},
sys::termios::{SetArg, Termios, cfmakeraw, tcgetattr, tcsetattr},
unistd::pipe2,
};
use std::fs::File;
use std::io::{Read, Write};
@@ -16,7 +15,6 @@ use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::ptr::null_mut;
use std::sync::atomic::{AtomicBool, Ordering};
static mut OLD_STDIN: Option<termios> = None;
static SHOULD_USE_SPLICE: AtomicBool = AtomicBool::new(true);
const TIOCGPTN: u32 = 0x80045430;
@@ -33,51 +31,10 @@ pub fn get_pty_num(fd: i32) -> i32 {
pty_num
}
fn set_stdin_raw() -> bool {
unsafe {
let mut termios: termios = std::mem::zeroed();
if tcgetattr(STDIN_FILENO, &mut termios) < 0 {
return false;
}
let old_c_oflag = termios.c_oflag;
OLD_STDIN = Some(termios);
cfmakeraw(&mut termios);
// don't modify output flags, since we are not setting stdout raw
termios.c_oflag = old_c_oflag;
if tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) < 0
&& tcsetattr(STDIN_FILENO, TCSADRAIN, &termios) < 0
{
warn!("Failed to set terminal attributes");
return false;
}
}
true
}
pub fn restore_stdin() -> bool {
unsafe {
if let Some(ref termios) = OLD_STDIN
&& tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0
&& tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0
{
warn!("Failed to restore terminal attributes");
return false;
}
OLD_STDIN = None;
true
}
}
fn resize_pty(outfd: i32) {
fn sync_winsize(ptmx: i32) {
let mut ws: winsize = unsafe { std::mem::zeroed() };
if unsafe { ioctl(STDIN_FILENO, TIOCGWINSZ as u32, &mut ws) } >= 0 {
unsafe { ioctl(outfd, TIOCSWINSZ as u32, &ws) };
unsafe { ioctl(ptmx, TIOCSWINSZ as u32, &ws) };
}
}
@@ -114,21 +71,55 @@ fn pump_via_splice(infd: RawFd, outfd: RawFd, pipe: &(OwnedFd, OwnedFd)) -> Logg
Ok(())
}
fn pump_tty_impl(infd: OwnedFd, raw_out: RawFd) -> LoggedResult<()> {
let mut set = SigSet::empty();
set.add(Signal::SIGWINCH);
set.thread_block()
.check_os_err("pthread_sigmask", None, None)?;
let signal_fd =
SignalFd::with_flags(&set, SfdFlags::SFD_CLOEXEC).into_os_result("signalfd", None, None)?;
fn set_stdin_raw() -> LoggedResult<Termios> {
let mut term = tcgetattr(FileOrStd::StdIn.as_file())?;
let old_term = term.clone();
let raw_in = infd.as_raw_fd();
let raw_sig = signal_fd.as_raw_fd();
let old_output_flags = old_term.output_flags;
cfmakeraw(&mut term);
// Preserve output_flags, since we are not setting stdout raw
term.output_flags = old_output_flags;
tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSAFLUSH, &term)
.or_else(|_| tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSADRAIN, &term))
.check_os_err("tcsetattr", None, None)
.log_with_msg(|w| w.write_str("Failed to set terminal attributes"))?;
Ok(old_term)
}
fn restore_stdin(term: Termios) -> LoggedResult<()> {
tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSAFLUSH, &term)
.or_else(|_| tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSADRAIN, &term))
.check_os_err("tcsetattr", None, None)
.log_with_msg(|w| w.write_str("Failed to restore terminal attributes"))
}
fn pump_tty_impl(ptmx: OwnedFd, pump_stdin: bool) -> LoggedResult<()> {
let mut signal_fd: Option<SignalFd> = None;
let raw_ptmx = ptmx.as_raw_fd();
let mut raw_sig = -1;
let mut poll_fds = Vec::with_capacity(3);
poll_fds.push(PollFd::new(infd.as_fd(), PollFlags::POLLIN));
poll_fds.push(PollFd::new(signal_fd.as_fd(), PollFlags::POLLIN));
if raw_out >= 0 {
poll_fds.push(PollFd::new(ptmx.as_fd(), PollFlags::POLLIN));
if pump_stdin {
// If stdin is tty, we need to monitor SIGWINCH
let mut set = SigSet::empty();
set.add(Signal::SIGWINCH);
set.thread_block()
.check_os_err("pthread_sigmask", None, None)?;
let sig = SignalFd::with_flags(&set, SfdFlags::SFD_CLOEXEC)
.into_os_result("signalfd", None, None)?;
raw_sig = sig.as_raw_fd();
signal_fd = Some(sig);
poll_fds.push(PollFd::new(
signal_fd.as_ref().unwrap().as_fd(),
PollFlags::POLLIN,
));
// We also need to pump stdin to ptmx
poll_fds.push(PollFd::new(
FileOrStd::StdIn.as_file().as_fd(),
PollFlags::POLLIN,
@@ -139,21 +130,21 @@ fn pump_tty_impl(infd: OwnedFd, raw_out: RawFd) -> LoggedResult<()> {
let stop_flags = PollFlags::POLLERR | PollFlags::POLLHUP | PollFlags::POLLNVAL;
// Open a pipe to bypass userspace copy with splice
let pipe_fd = nix::unistd::pipe2(OFlag::O_CLOEXEC).into_os_result("pipe2", None, None)?;
let pipe_fd = pipe2(OFlag::O_CLOEXEC).into_os_result("pipe2", None, None)?;
'poll: loop {
// Wait for event
nix::poll::poll(&mut poll_fds, PollTimeout::NONE).check_os_err("poll", None, None)?;
poll(&mut poll_fds, PollTimeout::NONE).check_os_err("poll", None, None)?;
for pfd in &poll_fds {
if pfd.all().unwrap_or(false) {
let raw_fd = pfd.as_fd().as_raw_fd();
if raw_fd == STDIN_FILENO {
pump_via_splice(STDIN_FILENO, raw_out, &pipe_fd)?;
} else if raw_fd == raw_in {
pump_via_splice(raw_in, STDOUT_FILENO, &pipe_fd)?;
pump_via_splice(STDIN_FILENO, raw_ptmx, &pipe_fd)?;
} else if raw_fd == raw_ptmx {
pump_via_splice(raw_ptmx, STDOUT_FILENO, &pipe_fd)?;
} else if raw_fd == raw_sig {
resize_pty(raw_out);
signal_fd.read_signal()?;
sync_winsize(raw_ptmx);
signal_fd.as_ref().unwrap().read_signal()?;
}
} else if pfd
.revents()
@@ -168,12 +159,19 @@ fn pump_tty_impl(infd: OwnedFd, raw_out: RawFd) -> LoggedResult<()> {
Ok(())
}
pub fn pump_tty(infd: i32, outfd: i32) {
set_stdin_raw();
pub fn pump_tty(ptmx: RawFd, pump_stdin: bool) {
let old_term = if pump_stdin {
sync_winsize(ptmx);
set_stdin_raw().ok()
} else {
None
};
let infd = unsafe { OwnedFd::from_raw_fd(infd) };
pump_tty_impl(infd, outfd).ok();
let ptmx = unsafe { OwnedFd::from_raw_fd(ptmx) };
pump_tty_impl(ptmx, pump_stdin).ok();
restore_stdin();
nix::sys::signal::raise(Signal::SIGWINCH).ok();
if let Some(term) = old_term {
restore_stdin(term).ok();
}
raise(Signal::SIGWINCH).ok();
}

View File

@@ -63,14 +63,8 @@ int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGI
}
static void sighandler(int sig) {
restore_stdin();
// Assume we'll only be called before death
// See note before sigaction() in set_stdin_raw()
//
// Now, close all standard I/O to cause the pumps
// to exit so we can continue and retrieve the exit
// code
// Close all standard I/O to cause the pumps to exit
// so we can continue and retrieve the exit code.
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
@@ -243,9 +237,9 @@ int su_client_main(int argc, char *argv[]) {
write_int(fd, 1);
int ptmx = recv_fd(fd);
setup_sighandlers(sighandler);
// if stdin is not a tty, if we pump to ptmx, our process may intercept the input to ptmx and
// If stdin is not a tty, and if we pump to ptmx, our process may intercept the input to ptmx and
// output to stdout, which cause the target process lost input.
pump_tty(ptmx, (atty & ATTY_IN) ? ptmx : -1);
pump_tty(ptmx, atty & ATTY_IN);
} else {
write_int(fd, 0);
}