Use one lock for LineDiscipline

This commit is contained in:
Ruihan Li
2025-05-03 22:58:25 +08:00
committed by Tate, Hongliang Tian
parent 45258d0475
commit 5a9a63e1a7
3 changed files with 234 additions and 300 deletions

View File

@ -27,6 +27,7 @@ use crate::{
};
const BUFFER_CAPACITY: usize = 4096;
const IO_CAPACITY: usize = 4096;
/// Pseudo terminal master.
/// Internally, it has two buffers.
@ -37,7 +38,6 @@ pub struct PtyMaster {
index: u32,
slave: Arc<PtySlave>,
input: SpinLock<RingBuffer<u8>>,
/// The state of input buffer
pollee: Pollee,
}
@ -45,8 +45,9 @@ impl PtyMaster {
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
Arc::new_cyclic(move |master| {
let slave = Arc::new_cyclic(move |weak_self| PtySlave {
ldisc: LineDiscipline::new(),
ldisc: SpinLock::new(LineDiscipline::new()),
job_control: JobControl::new(),
pollee: Pollee::new(),
master: master.clone(),
weak_self: weak_self.clone(),
});
@ -73,51 +74,57 @@ impl PtyMaster {
&self.slave
}
pub(super) fn slave_push_char(&self, ch: u8) {
let mut input = self.input.disable_irq().lock();
input.push_overwrite(ch);
fn slave_push(&self, chs: &[u8]) {
let mut input = self.input.lock();
for ch in chs {
// TODO: This is termios-specific behavior and should be part of the TTY implementation
// instead of the TTY driver implementation. See the ONLCR flag for more details.
if *ch == b'\n' {
input.push_overwrite(b'\r');
input.push_overwrite(b'\n');
continue;
}
input.push_overwrite(*ch);
}
self.pollee.notify(IoEvents::IN);
}
pub(super) fn slave_poll(
&self,
mask: IoEvents,
mut poller: Option<&mut PollHandle>,
) -> IoEvents {
let mut poll_status = IoEvents::empty();
fn slave_echo(&self) -> impl FnMut(&str) + '_ {
let mut input = self.input.lock();
let mut has_notified = false;
let poll_in_mask = mask & IoEvents::IN;
if !poll_in_mask.is_empty() {
let poll_in_status = self.slave.ldisc.poll(poll_in_mask, poller.as_deref_mut());
poll_status |= poll_in_status;
move |content| {
for byte in content.as_bytes() {
input.push_overwrite(*byte);
}
if !has_notified {
self.pollee.notify(IoEvents::IN);
has_notified = true;
}
}
let poll_out_mask = mask & IoEvents::OUT;
if !poll_out_mask.is_empty() {
let poll_out_status = self
.pollee
.poll_with(poll_out_mask, poller, || self.check_io_events());
poll_status |= poll_out_status;
}
poll_status
}
fn try_read(&self, writer: &mut VmWriter) -> Result<usize> {
let mut input = self.input.disable_irq().lock();
fn try_read(&self, buf: &mut [u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut input = self.input.lock();
if input.is_empty() {
return_errno_with_message!(Errno::EAGAIN, "the buffer is empty");
}
let read_len = input.read_fallible(writer)?;
let read_len = input.len().min(buf.len());
input.pop_slice(&mut buf[..read_len]).unwrap();
self.pollee.invalidate();
Ok(read_len)
}
fn check_io_events(&self) -> IoEvents {
let input = self.input.disable_irq().lock();
let input = self.input.lock();
if !input.is_empty() {
IoEvents::IN | IoEvents::OUT
@ -128,60 +135,27 @@ impl PtyMaster {
}
impl Pollable for PtyMaster {
fn poll(&self, mask: IoEvents, mut poller: Option<&mut PollHandle>) -> IoEvents {
let mut poll_status = IoEvents::empty();
let poll_in_mask = mask & IoEvents::IN;
if !poll_in_mask.is_empty() {
let poll_in_status = self
.pollee
.poll_with(poll_in_mask, poller.as_deref_mut(), || {
self.check_io_events()
});
poll_status |= poll_in_status;
}
let poll_out_mask = mask & IoEvents::OUT;
if !poll_out_mask.is_empty() {
let poll_out_status = self.slave.ldisc.poll(poll_out_mask, poller);
poll_status |= poll_out_status;
}
poll_status
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.pollee
.poll_with(mask, poller, || self.check_io_events())
}
}
impl FileIo for PtyMaster {
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
if !writer.has_avail() {
return Ok(0);
}
// TODO: Add support for non-blocking mode and timeout
let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)];
let read_len = self.wait_events(IoEvents::IN, None, || self.try_read(&mut buf))?;
// TODO: deal with nonblocking and timeout
self.wait_events(IoEvents::IN, None, || self.try_read(writer))
writer.write_fallible(&mut buf[..read_len].into())?;
Ok(read_len)
}
fn write(&self, reader: &mut VmReader) -> Result<usize> {
let buf = reader.collect()?;
let write_len = buf.len();
let mut input = self.input.lock();
for character in buf {
self.slave.ldisc.push_char(
character,
|signum| {
if let Some(foreground) = self.slave.job_control.foreground() {
broadcast_signal_async(Arc::downgrade(&foreground), signum);
}
},
|content| {
for byte in content.as_bytes() {
input.push_overwrite(*byte);
}
},
);
}
let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
self.pollee.notify(IoEvents::IN);
self.slave.master_push(&buf[..write_len]);
Ok(write_len)
}
@ -248,8 +222,9 @@ impl Drop for PtyMaster {
}
pub struct PtySlave {
ldisc: LineDiscipline,
ldisc: SpinLock<LineDiscipline>,
job_control: JobControl,
pollee: Pollee,
master: Weak<PtyMaster>,
weak_self: Weak<Self>,
}
@ -262,6 +237,34 @@ impl PtySlave {
fn master(&self) -> Arc<PtyMaster> {
self.master.upgrade().unwrap()
}
fn master_push(&self, chs: &[u8]) {
let mut ldisc = self.ldisc.lock();
let master = self.master();
let mut echo = master.slave_echo();
for ch in chs {
ldisc.push_char(
*ch,
|signum| {
if let Some(foreground) = self.job_control.foreground() {
broadcast_signal_async(Arc::downgrade(&foreground), signum);
}
},
&mut echo,
);
}
self.pollee.notify(IoEvents::IN);
}
fn check_io_events(&self) -> IoEvents {
if self.ldisc.lock().buffer_len() != 0 {
IoEvents::IN | IoEvents::OUT
} else {
IoEvents::OUT
}
}
}
impl Device for PtySlave {
@ -282,59 +285,57 @@ impl Terminal for PtySlave {
impl Pollable for PtySlave {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.master().slave_poll(mask, poller)
self.pollee
.poll_with(mask, poller, || self.check_io_events())
}
}
impl FileIo for PtySlave {
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
let mut buf = vec![0u8; writer.avail()];
self.job_control.wait_until_in_foreground()?;
let read_len = self.ldisc.read(&mut buf)?;
writer.write_fallible(&mut buf.as_slice().into())?;
// TODO: Add support for non-blocking mode and timeout
let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)];
let read_len =
self.wait_events(IoEvents::IN, None, || self.ldisc.lock().try_read(&mut buf))?;
self.pollee.invalidate();
writer.write_fallible(&mut buf[..read_len].into())?;
Ok(read_len)
}
fn write(&self, reader: &mut VmReader) -> Result<usize> {
let buf = reader.collect()?;
let write_len = buf.len();
let master = self.master();
for ch in buf {
// do we need to add '\r' here?
if ch == b'\n' {
master.slave_push_char(b'\r');
master.slave_push_char(b'\n');
} else {
master.slave_push_char(ch);
}
}
let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
self.master().slave_push(&buf[..write_len]);
Ok(write_len)
}
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
match cmd {
IoctlCmd::TCGETS => {
let termios = self.ldisc.termios();
let termios = *self.ldisc.lock().termios();
current_userspace!().write_val(arg, &termios)?;
}
IoctlCmd::TCSETS => {
let termios = current_userspace!().read_val(arg)?;
self.ldisc.set_termios(termios);
self.ldisc.lock().set_termios(termios);
}
IoctlCmd::TIOCGPTN => {
let idx = self.index();
current_userspace!().write_val(arg, &idx)?;
}
IoctlCmd::TIOCGWINSZ => {
let winsize = self.ldisc.window_size();
let winsize = self.ldisc.lock().window_size();
current_userspace!().write_val(arg, &winsize)?;
}
IoctlCmd::TIOCSWINSZ => {
let winsize = current_userspace!().read_val(arg)?;
self.ldisc.set_window_size(winsize);
self.ldisc.lock().set_window_size(winsize);
}
IoctlCmd::FIONREAD => {
let buffer_len = self.ldisc.buffer_len() as i32;
let buffer_len = self.ldisc.lock().buffer_len() as i32;
current_userspace!().write_val(arg, &buffer_len)?;
}
_ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)

View File

@ -2,16 +2,12 @@
use alloc::format;
use ostd::{sync::LocalIrqDisabled, trap::disable_local};
use super::termio::{KernelTermios, WinSize, CC_C_CHAR};
use crate::{
events::IoEvents,
prelude::*,
process::signal::{
constants::{SIGINT, SIGQUIT},
sig_num::SigNum,
PollHandle, Pollable, Pollee,
},
util::ring_buffer::RingBuffer,
};
@ -21,22 +17,15 @@ use crate::{
const BUFFER_CAPACITY: usize = 4096;
// Lock ordering to prevent deadlock (circular dependencies):
// 1. `termios`
// 2. `current_line`
// 3. `read_buffer`
// 4. `work_item_para`
pub struct LineDiscipline {
/// Current line
current_line: SpinLock<CurrentLine, LocalIrqDisabled>,
current_line: CurrentLine,
/// The read buffer
read_buffer: SpinLock<RingBuffer<u8>, LocalIrqDisabled>,
read_buffer: RingBuffer<u8>,
/// Termios
termios: SpinLock<KernelTermios, LocalIrqDisabled>,
termios: KernelTermios,
/// Windows size
winsize: SpinLock<WinSize, LocalIrqDisabled>,
/// Pollee
pollee: Pollee,
winsize: WinSize,
}
pub struct CurrentLine {
@ -78,112 +67,85 @@ impl CurrentLine {
}
}
impl Pollable for LineDiscipline {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.pollee
.poll_with(mask, poller, || self.check_io_events())
}
}
impl LineDiscipline {
/// Creates a new line discipline
/// Creates a new line discipline.
pub fn new() -> Self {
Self {
current_line: SpinLock::new(CurrentLine::default()),
read_buffer: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)),
termios: SpinLock::new(KernelTermios::default()),
winsize: SpinLock::new(WinSize::default()),
pollee: Pollee::new(),
current_line: CurrentLine::default(),
read_buffer: RingBuffer::new(BUFFER_CAPACITY),
termios: KernelTermios::default(),
winsize: WinSize::default(),
}
}
/// Pushes a char to the line discipline
/// Pushes a character to the line discipline.
pub fn push_char<F1: FnMut(SigNum), F2: FnMut(&str)>(
&self,
&mut self,
ch: u8,
mut signal_callback: F1,
echo_callback: F2,
) {
let termios = self.termios.lock();
let ch = if termios.contains_icrnl() && ch == b'\r' {
let ch = if self.termios.contains_icrnl() && ch == b'\r' {
b'\n'
} else {
ch
};
if let Some(signum) = char_to_signal(ch, &termios) {
if let Some(signum) = char_to_signal(ch, &self.termios) {
signal_callback(signum);
// CBREAK mode may require the character to be outputted, so just go ahead.
// CBREAK mode may require the character to be echoed, so just go ahead.
}
// Typically, a tty in raw mode does not echo. But the tty can also be in a CBREAK mode,
// Typically, a TTY in raw mode does not echo. But the TTY can also be in a CBREAK mode,
// with ICANON closed and ECHO opened.
if termios.contain_echo() {
self.output_char(ch, &termios, echo_callback);
if self.termios.contain_echo() {
self.output_char(ch, echo_callback);
}
// Raw mode
if !termios.is_canonical_mode() {
self.read_buffer.lock().push_overwrite(ch);
self.pollee.notify(IoEvents::IN);
if !self.termios.is_canonical_mode() {
self.read_buffer.push_overwrite(ch);
return;
}
// Canonical mode
if ch == *termios.get_special_char(CC_C_CHAR::VKILL) {
if ch == *self.termios.get_special_char(CC_C_CHAR::VKILL) {
// Erase current line
self.current_line.lock().drain();
self.current_line.drain();
}
if ch == *termios.get_special_char(CC_C_CHAR::VERASE) {
if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) {
// Type backspace
let mut current_line = self.current_line.lock();
if !current_line.is_empty() {
current_line.backspace();
}
self.current_line.backspace();
}
if is_line_terminator(ch, &termios) {
// If a new line is met, all bytes in current_line will be moved to read_buffer
let mut current_line = self.current_line.lock();
current_line.push_char(ch);
let current_line_chars = current_line.drain();
for char in current_line_chars {
self.read_buffer.lock().push_overwrite(char);
self.pollee.notify(IoEvents::IN);
if is_line_terminator(ch, &self.termios) {
// A new line is met. Move all bytes in `current_line` to `read_buffer`.
self.current_line.push_char(ch);
for line_ch in self.current_line.drain() {
self.read_buffer.push_overwrite(line_ch);
}
}
if is_printable_char(ch) {
// Printable character
self.current_line.lock().push_char(ch);
}
}
fn check_io_events(&self) -> IoEvents {
let buffer = self.read_buffer.lock();
if !buffer.is_empty() {
IoEvents::IN
} else {
IoEvents::empty()
self.current_line.push_char(ch);
}
}
// TODO: respect output flags
fn output_char<F: FnMut(&str)>(&self, ch: u8, termios: &KernelTermios, mut echo_callback: F) {
fn output_char<F: FnMut(&str)>(&self, ch: u8, mut echo_callback: F) {
match ch {
b'\n' => echo_callback("\n"),
b'\r' => echo_callback("\r\n"),
ch if ch == *termios.get_special_char(CC_C_CHAR::VERASE) => {
// write a space to overwrite current character
ch if ch == *self.termios.get_special_char(CC_C_CHAR::VERASE) => {
// Write a space to overwrite the current character
let backspace: &str = core::str::from_utf8(b"\x08 \x08").unwrap();
echo_callback(backspace);
}
ch if is_printable_char(ch) => print!("{}", char::from(ch)),
ch if is_ctrl_char(ch) && termios.contains_echo_ctl() => {
ch if is_ctrl_char(ch) && self.termios.contains_echo_ctl() => {
let ctrl_char = format!("^{}", ctrl_char_to_printable(ch));
echo_callback(&ctrl_char);
}
@ -191,140 +153,86 @@ impl LineDiscipline {
}
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
self.wait_events(IoEvents::IN, None, || self.try_read(buf))
}
/// Reads all bytes buffered to `dst`.
///
/// This method returns the actual read length.
fn try_read(&self, dst: &mut [u8]) -> Result<usize> {
let (vmin, vtime) = {
let termios = self.termios.lock();
let vmin = *termios.get_special_char(CC_C_CHAR::VMIN);
let vtime = *termios.get_special_char(CC_C_CHAR::VTIME);
(vmin, vtime)
};
let read_len = {
if vmin == 0 && vtime == 0 {
// poll read
self.poll_read(dst)
} else if vmin > 0 && vtime == 0 {
// block read
self.block_read(dst, vmin)?
} else if vmin == 0 && vtime > 0 {
todo!()
} else if vmin > 0 && vtime > 0 {
todo!()
} else {
unreachable!()
}
};
self.pollee.invalidate();
Ok(read_len)
}
/// Reads bytes from `self` to `dst`, returning the actual bytes read.
///
/// If no bytes are available, this method returns 0 immediately.
fn poll_read(&self, dst: &mut [u8]) -> usize {
let termios = self.termios.lock();
let mut buffer = self.read_buffer.lock();
let len = buffer.len();
let max_read_len = len.min(dst.len());
if max_read_len == 0 {
return 0;
}
let mut read_len = 0;
for dst_i in dst.iter_mut().take(max_read_len) {
if let Some(next_char) = buffer.pop() {
if termios.is_canonical_mode() {
// canonical mode, read until meet new line
if is_line_terminator(next_char, &termios) {
// The eof should not be read
if !is_eof(next_char, &termios) {
*dst_i = next_char;
read_len += 1;
}
break;
} else {
*dst_i = next_char;
read_len += 1;
}
} else {
// raw mode
// FIXME: avoid additional bound check
*dst_i = next_char;
read_len += 1;
}
} else {
break;
}
}
read_len
}
/// Reads bytes from `self` into `dst`,
/// returning the actual number of bytes read.
///
/// # Errors
///
/// If the available bytes are fewer than `min(dst.len(), vmin)`,
/// If no bytes are available or the available bytes are fewer than `min(dst.len(), vmin)`,
/// this method returns [`Errno::EAGAIN`].
pub fn block_read(&self, dst: &mut [u8], vmin: u8) -> Result<usize> {
let _guard = disable_local();
let buffer_len = self.read_buffer.lock().len();
if buffer_len >= dst.len() {
return Ok(self.poll_read(dst));
pub fn try_read(&mut self, dst: &mut [u8]) -> Result<usize> {
let vmin = *self.termios.get_special_char(CC_C_CHAR::VMIN);
let vtime = *self.termios.get_special_char(CC_C_CHAR::VTIME);
if vtime != 0 {
warn!("non-zero VTIME is not supported");
}
if buffer_len < vmin as usize {
return_errno!(Errno::EAGAIN);
// If `vmin` is zero or `dst` is empty, the following condition will always be false. This
// is correct, as the expected behavior is to never block or return `EAGAIN`.
if self.buffer_len() < dst.len().min(vmin as _) {
return_errno_with_message!(
Errno::EAGAIN,
"the characters in the buffer are not enough"
);
}
Ok(self.poll_read(&mut dst[..buffer_len]))
for (i, dst_i) in dst.iter_mut().enumerate() {
let Some(ch) = self.read_buffer.pop() else {
// No more characters
return Ok(i);
};
if self.termios.is_canonical_mode() && is_eof(ch, &self.termios) {
// This allows the userspace program to see `Ok(0)`
return Ok(i);
}
*dst_i = ch;
if self.termios.is_canonical_mode() && is_line_terminator(ch, &self.termios) {
// Read until line terminators in canonical mode
return Ok(i + 1);
}
}
Ok(dst.len())
}
/// Returns whether there is buffered data
pub fn is_empty(&self) -> bool {
self.read_buffer.lock().len() == 0
pub fn termios(&self) -> &KernelTermios {
&self.termios
}
pub fn termios(&self) -> KernelTermios {
*self.termios.lock()
pub fn set_termios(&mut self, termios: KernelTermios) {
self.termios = termios;
}
pub fn set_termios(&self, termios: KernelTermios) {
*self.termios.lock() = termios;
}
pub fn drain_input(&self) {
self.current_line.lock().drain();
self.read_buffer.lock().clear();
self.pollee.invalidate();
pub fn drain_input(&mut self) {
self.current_line.drain();
self.read_buffer.clear();
}
pub fn buffer_len(&self) -> usize {
self.read_buffer.lock().len()
self.read_buffer.len()
}
pub fn window_size(&self) -> WinSize {
*self.winsize.lock()
self.winsize
}
pub fn set_window_size(&self, winsize: WinSize) {
*self.winsize.lock() = winsize;
pub fn set_window_size(&mut self, winsize: WinSize) {
self.winsize = winsize;
}
}
fn is_line_terminator(item: u8, termios: &KernelTermios) -> bool {
if item == b'\n'
|| item == *termios.get_special_char(CC_C_CHAR::VEOF)
|| item == *termios.get_special_char(CC_C_CHAR::VEOL)
fn is_line_terminator(ch: u8, termios: &KernelTermios) -> bool {
if ch == b'\n'
|| ch == *termios.get_special_char(CC_C_CHAR::VEOF)
|| ch == *termios.get_special_char(CC_C_CHAR::VEOL)
{
return true;
}
if termios.contains_iexten() && item == *termios.get_special_char(CC_C_CHAR::VEOL2) {
if termios.contains_iexten() && ch == *termios.get_special_char(CC_C_CHAR::VEOL2) {
return true;
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
use ostd::early_print;
use ostd::{early_print, sync::LocalIrqDisabled};
use spin::Once;
use self::{driver::TtyDriver, line_discipline::LineDiscipline};
@ -15,7 +15,7 @@ use crate::{
prelude::*,
process::{
broadcast_signal_async,
signal::{PollHandle, Pollable},
signal::{PollHandle, Pollable, Pollee},
JobControl, Terminal,
},
};
@ -36,13 +36,16 @@ pub(super) fn init() {
driver::init();
}
const IO_CAPACITY: usize = 4096;
pub struct Tty {
/// tty_name
#[expect(unused)]
name: CString,
/// line discipline
ldisc: LineDiscipline,
ldisc: SpinLock<LineDiscipline, LocalIrqDisabled>,
job_control: JobControl,
pollee: Pollee,
/// driver
driver: SpinLock<Weak<TtyDriver>>,
weak_self: Weak<Self>,
@ -52,8 +55,9 @@ impl Tty {
pub fn new(name: CString) -> Arc<Self> {
Arc::new_cyclic(move |weak_ref| Tty {
name,
ldisc: LineDiscipline::new(),
ldisc: SpinLock::new(LineDiscipline::new()),
job_control: JobControl::new(),
pollee: Pollee::new(),
driver: SpinLock::new(Weak::new()),
weak_self: weak_ref.clone(),
})
@ -66,7 +70,7 @@ impl Tty {
pub fn push_char(&self, ch: u8) {
// FIXME: Use `early_print` to avoid calling virtio-console.
// This is only a workaround
self.ldisc.push_char(
self.ldisc.lock().push_char(
ch,
|signum| {
if let Some(foreground) = self.job_control.foreground() {
@ -74,69 +78,90 @@ impl Tty {
}
},
|content| early_print!("{}", content),
)
);
self.pollee.notify(IoEvents::IN);
}
fn check_io_events(&self) -> IoEvents {
if self.ldisc.lock().buffer_len() != 0 {
IoEvents::IN | IoEvents::OUT
} else {
IoEvents::OUT
}
}
}
impl Pollable for Tty {
fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents {
self.ldisc.poll(mask, poller)
self.pollee
.poll_with(mask, poller, || self.check_io_events())
}
}
impl FileIo for Tty {
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
let mut buf = vec![0; writer.avail()];
self.job_control.wait_until_in_foreground()?;
let read_len = self.ldisc.read(buf.as_mut_slice())?;
writer.write_fallible(&mut buf.as_slice().into())?;
// TODO: Add support for non-blocking mode and timeout
let mut buf = vec![0u8; writer.avail().min(IO_CAPACITY)];
let read_len =
self.wait_events(IoEvents::IN, None, || self.ldisc.lock().try_read(&mut buf))?;
self.pollee.invalidate();
// TODO: Confirm what we should do if `write_fallible` fails in the middle.
writer.write_fallible(&mut buf[..read_len].into())?;
Ok(read_len)
}
fn write(&self, reader: &mut VmReader) -> Result<usize> {
let buf = reader.collect()?;
if let Ok(content) = alloc::str::from_utf8(&buf) {
let mut buf = vec![0u8; reader.remain().min(IO_CAPACITY)];
let write_len = reader.read_fallible(&mut buf.as_mut_slice().into())?;
if let Ok(content) = alloc::str::from_utf8(&buf[..write_len]) {
print!("{content}");
} else {
println!("Not utf-8 content: {:?}", buf);
}
Ok(buf.len())
Ok(write_len)
}
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
match cmd {
IoctlCmd::TCGETS => {
// Get terminal attributes
let termios = self.ldisc.termios();
trace!("get termios = {:?}", termios);
let termios = *self.ldisc.lock().termios();
current_userspace!().write_val(arg, &termios)?;
}
IoctlCmd::TCSETS => {
// Set terminal attributes
let termios = current_userspace!().read_val(arg)?;
debug!("set termios = {:?}", termios);
self.ldisc.set_termios(termios);
self.ldisc.lock().set_termios(termios);
}
IoctlCmd::TCSETSW => {
let termios = current_userspace!().read_val(arg)?;
debug!("set termios = {:?}", termios);
self.ldisc.set_termios(termios);
// TODO: drain output buffer
self.ldisc.lock().set_termios(termios);
// TODO: Drain the output buffer
}
IoctlCmd::TCSETSF => {
let termios = current_userspace!().read_val(arg)?;
debug!("set termios = {:?}", termios);
self.ldisc.set_termios(termios);
self.ldisc.drain_input();
// TODO: drain output buffer
let mut ldisc = self.ldisc.lock();
ldisc.set_termios(termios);
ldisc.drain_input();
// TODO: Drain the output buffer
self.pollee.invalidate();
}
IoctlCmd::TIOCGWINSZ => {
let winsize = self.ldisc.window_size();
let winsize = self.ldisc.lock().window_size();
current_userspace!().write_val(arg, &winsize)?;
}
IoctlCmd::TIOCSWINSZ => {
let winsize = current_userspace!().read_val(arg)?;
self.ldisc.set_window_size(winsize);
self.ldisc.lock().set_window_size(winsize);
}
_ => (self.weak_self.upgrade().unwrap() as Arc<dyn Terminal>)
.job_ioctl(cmd, arg, false)?,