yggdrasil/kernel/libk/src/vfs/terminal.rs

326 lines
9.0 KiB
Rust

use core::{
mem::MaybeUninit,
task::{Context, Poll},
};
use alloc::boxed::Box;
use async_trait::async_trait;
use libk_util::{
ring::{LossyRingQueue, RingBuffer},
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
};
use yggdrasil_abi::{
error::Error,
io::{DeviceRequest, TerminalInputOptions, TerminalLineOptions, TerminalOptions, TerminalSize},
process::{ProcessGroupId, Signal},
};
use crate::task::process::Process;
use super::{CharDevice, FileReadiness};
pub struct Terminal<O: TerminalOutput> {
input: TerminalInput,
output: O,
config: IrqSafeRwLock<TerminalOptions>,
}
// Keyboard, PTY slave, terminal, etc. -> program
pub struct TerminalInput {
buffer: IrqSafeSpinlock<InputBuffer>,
ready_ring: LossyRingQueue<u8>,
signal_pgroup: IrqSafeRwLock<Option<ProcessGroupId>>,
}
// Program -> screen, PTY master, terminal, etc.
pub trait TerminalOutput: Sync {
fn write(&self, byte: u8) -> Result<(), Error>;
fn notify_readers(&self) {}
fn size(&self) -> TerminalSize {
TerminalSize {
rows: 80,
columns: 24,
}
}
fn set_size(&self, size: TerminalSize) -> Result<(), Error> {
let _ = size;
Err(Error::InvalidOperation)
}
fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
let mut written = 0;
for &byte in bytes {
self.write(byte)?;
written += 1;
}
Ok(written)
}
}
struct InputBuffer {
pending: Box<[MaybeUninit<u8>]>,
position: usize,
}
impl<O: TerminalOutput> Terminal<O> {
pub fn from_parts(config: TerminalOptions, input: TerminalInput, output: O) -> Self {
Self {
input,
output,
config: IrqSafeRwLock::new(config),
}
}
pub fn input(&self) -> &TerminalInput {
&self.input
}
pub fn output(&self) -> &O {
&self.output
}
pub fn config(&self) -> TerminalOptions {
*self.config.read()
}
#[inline]
pub async fn read_from_input(&self, buffer: &mut [u8]) -> Result<usize, Error> {
let eof = {
let config = self.config.read();
config.is_canonical().then_some(config.chars.eof)
};
self.input.read(buffer, eof).await
}
#[inline]
pub fn read_from_input_nonblocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
let eof = {
let config = self.config.read();
config.is_canonical().then_some(config.chars.eof)
};
self.input.read_nonblocking(buffer, eof)
}
pub fn putc_to_output(&self, byte: u8) -> Result<(), Error> {
self.output.write(byte)
}
pub fn write_to_output(&self, buffer: &[u8]) -> Result<usize, Error> {
// TODO handle options
self.output.write_multiple(buffer)
}
pub fn write_to_input(&self, mut byte: u8) {
let config = self.config.read();
let mut buffer = self.input.buffer.lock();
if byte == b'\r' && config.input.contains(TerminalInputOptions::CR_TO_NL) {
byte = b'\n';
}
if config.is_canonical() {
// Canonical line processing
// Echo back
if byte == config.chars.erase {
let echo =
buffer.erase_pending() && config.line.contains(TerminalLineOptions::ECHO_ERASE);
if echo {
self.output.write_multiple(b"\x1B[D \x1B[D").ok();
}
return;
}
if byte == b'\n' {
// TODO NL_TO_CRNL
if config.is_echo_newline() {
self.output.write(byte).ok();
}
} else if byte.is_ascii_control() {
if config.line.contains(TerminalLineOptions::ECHO) {
self.output.write_multiple(&[b'^', byte + 0x40]).ok();
}
} else if config.line.contains(TerminalLineOptions::ECHO) {
self.output.write(byte).ok();
}
if byte == config.chars.interrupt {
if config.line.contains(TerminalLineOptions::SIGNAL) {
self.output.notify_readers();
if let Some(group_id) = *self.input.signal_pgroup.read() {
Process::signal_group(group_id, Signal::Interrupted);
self.input.ready_ring.notify_all();
return;
}
} else {
buffer.write_pending(byte);
}
} else {
buffer.write_pending(byte);
}
if byte == b'\n' || byte == config.chars.eof {
let data = buffer.flush();
self.input.ready_ring.write_multiple(data);
}
} else {
// Raw line processing
self.input.ready_ring.write(byte);
}
}
pub fn handle_device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
match req {
DeviceRequest::SetTerminalGroup(group) => {
self.input.set_signal_pgroup(*group);
Ok(())
}
DeviceRequest::GetTerminalOptions(options) => {
options.write(*self.config.read());
Ok(())
}
DeviceRequest::SetTerminalOptions(options) => {
self.input.flush();
*self.config.write() = *options;
Ok(())
}
DeviceRequest::SetTerminalSize(size) => {
// TODO SIGWINCH?
self.output.set_size(*size)
}
DeviceRequest::GetTerminalSize(size) => {
size.write(self.output.size());
Ok(())
}
_ => Err(Error::InvalidOperation),
}
}
}
impl TerminalInput {
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
Ok(Self {
buffer: IrqSafeSpinlock::new(InputBuffer::with_capacity(capacity)),
ready_ring: LossyRingQueue::try_with_capacity(capacity)?,
signal_pgroup: IrqSafeRwLock::new(None),
})
}
pub async fn read(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
let mut lock = self.ready_ring.read_lock().await;
Ok(read_all(&mut lock, buffer, eof))
}
pub fn read_nonblocking(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
let mut lock = self.ready_ring.try_read_lock().ok_or(Error::WouldBlock)?;
Ok(read_all(&mut lock, buffer, eof))
}
pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<()> {
self.ready_ring.poll_readable(cx)
}
pub fn flush(&self) {
let mut lock = self.buffer.lock();
let data = lock.flush();
self.ready_ring.write_multiple(data);
}
pub fn set_signal_pgroup(&self, value: ProcessGroupId) {
self.signal_pgroup.write().replace(value);
}
}
impl InputBuffer {
pub fn with_capacity(capacity: usize) -> Self {
Self {
pending: Box::new_uninit_slice(capacity),
position: 0,
}
}
pub fn write_pending(&mut self, byte: u8) {
if self.position == self.pending.len() {
todo!();
}
self.pending[self.position].write(byte);
self.position += 1;
}
pub fn erase_pending(&mut self) -> bool {
if self.position != 0 {
self.position -= 1;
true
} else {
false
}
}
pub fn flush(&mut self) -> &[u8] {
let data = unsafe { MaybeUninit::slice_assume_init_ref(&self.pending[..self.position]) };
self.position = 0;
data
}
}
#[async_trait]
impl<O: TerminalOutput> CharDevice for Terminal<O> {
async fn write(&'static self, buf: &[u8]) -> Result<usize, Error> {
self.write_to_output(buf)
}
fn write_nonblocking(&'static self, buf: &[u8]) -> Result<usize, Error> {
self.write_to_output(buf)
}
async fn read(&'static self, buf: &mut [u8]) -> Result<usize, Error> {
self.read_from_input(buf).await
}
fn read_nonblocking(&'static self, buf: &mut [u8]) -> Result<usize, Error> {
self.read_from_input_nonblocking(buf)
}
fn is_readable(&self) -> bool {
true
}
fn is_writable(&self) -> bool {
true
}
fn is_terminal(&self) -> bool {
true
}
fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
self.handle_device_request(req)
}
}
impl<O: TerminalOutput> FileReadiness for Terminal<O> {
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.input().poll_read(cx).map(Ok)
}
}
pub fn read_all(source: &mut RingBuffer<u8>, target: &mut [u8], eof: Option<u8>) -> usize {
let mut pos = 0;
while pos < target.len()
&& let Some(ch) = source.try_read()
{
if eof.map(|eof| eof == ch).unwrap_or(false) {
break;
}
target[pos] = ch;
pos += 1;
}
pos
}