Factor out termios and the line discipline into their own crates

This commit is contained in:
pjht 2024-09-23 14:46:04 -05:00
parent 84786479c2
commit 142c1d2aab
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
4 changed files with 62 additions and 369 deletions

23
Cargo.lock generated
View File

@ -68,6 +68,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "embedded-io" name = "embedded-io"
version = "0.4.0" version = "0.4.0"
@ -113,12 +119,28 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.158" version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "line_discipline"
version = "0.1.0"
dependencies = [
"itertools",
]
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@ -193,6 +215,7 @@ dependencies = [
"dev_driver_rpc", "dev_driver_rpc",
"devfs_rpc", "devfs_rpc",
"file_rpc", "file_rpc",
"line_discipline",
"parking_lot", "parking_lot",
"slab", "slab",
"syslog_rpc", "syslog_rpc",

View File

@ -7,6 +7,7 @@ edition = "2021"
dev_driver_rpc = { version = "0.1.0", path = "../dev_driver_rpc" } dev_driver_rpc = { version = "0.1.0", path = "../dev_driver_rpc" }
devfs_rpc = { version = "0.1.0", path = "../devfs/devfs_rpc" } devfs_rpc = { version = "0.1.0", path = "../devfs/devfs_rpc" }
file_rpc = { version = "0.1.0", path = "../file_rpc" } file_rpc = { version = "0.1.0", path = "../file_rpc" }
line_discipline = { version = "0.1.0", path = "../line_discipline" }
parking_lot = "0.12.3" parking_lot = "0.12.3"
slab = "0.4.9" slab = "0.4.9"
syslog_rpc = { version = "0.1.0", path = "../syslog/syslog_rpc" } syslog_rpc = { version = "0.1.0", path = "../syslog/syslog_rpc" }

View File

@ -1,23 +1,20 @@
mod termios;
use std::{ use std::{
borrow::Cow, collections::HashMap, io::empty, os::mikros::{ipc, syscalls}, path::{Path, PathBuf}, sync::{ borrow::Cow,
atomic::{AtomicBool, AtomicUsize, Ordering}, collections::HashMap,
Arc, os::mikros::{ipc, syscalls},
} path::{Path, PathBuf},
sync::Arc,
}; };
use file_rpc::PollEvents; use file_rpc::PollEvents;
use line_discipline::LineDiscipline;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use slab::Slab; use slab::Slab;
use termios::Termios;
struct Pty { struct Pty {
input_buffer: Mutex<Vec<u8>>, input_buffer: Mutex<Vec<u8>>,
output_buffer: Mutex<Vec<u8>>, output_buffer: Mutex<Vec<u8>>,
termios: Termios, line_discipline: Mutex<LineDiscipline>,
output_paused: AtomicBool,
column: AtomicUsize,
} }
impl Pty { impl Pty {
@ -25,169 +22,48 @@ impl Pty {
Pty { Pty {
input_buffer: Mutex::new(Vec::new()), input_buffer: Mutex::new(Vec::new()),
output_buffer: Mutex::new(Vec::new()), output_buffer: Mutex::new(Vec::new()),
termios: Termios::default(), line_discipline: Mutex::new(LineDiscipline::new()),
output_paused: AtomicBool::new(false),
column: AtomicUsize::new(0),
} }
} }
fn read_master(&self, len: usize) -> Result<Vec<u8>, ()> { fn read_master(&self, len: usize) -> Vec<u8> {
if self.output_paused.load(Ordering::Relaxed) { if self.line_discipline.lock().output_paused() {
return Ok(vec![]); return Vec::new();
} }
let mut output_buffer = self.output_buffer.lock(); let mut output_buffer = self.output_buffer.lock();
output_buffer.extend_from_slice(&self.line_discipline.lock().get_terminal_output());
let buf_len = output_buffer.len(); let buf_len = output_buffer.len();
let rem_buf = output_buffer.split_off(usize::min(buf_len, len)); let rem_buf = output_buffer.split_off(usize::min(buf_len, len));
let buf = std::mem::replace(&mut *output_buffer, rem_buf); std::mem::replace(&mut *output_buffer, rem_buf)
Ok(buf)
} }
fn write_master(&self, data: &[u8]) -> Result<(), ()> { fn write_master(&self, data: &[u8]) {
if !self.termios.cflags.cread { self.line_discipline.lock().process_terminal_input(data);
return Ok(());
}
for &(mut ch) in data {
if self.termios.iflags.istrip {
ch &= 0x7F;
}
if ch == b'\r' && self.termios.iflags.igncr {
continue;
} else if ch == b'\r' && self.termios.iflags.icrnl {
ch = b'\n';
} else if ch == b'\n' && self.termios.iflags.inlcr {
ch = b'\r';
}
if ch as char == self.termios.verase && self.termios.lflags.icanon {
let buf_last = self.input_buffer.lock().last().copied();
if let Some(erased_ch) = buf_last {
let erased_ch = erased_ch as char;
if erased_ch != '\n' {
self.input_buffer.lock().pop();
self.erase_char(erased_ch);
}
}
} else if ch as char == self.termios.vkill && self.termios.lflags.icanon {
let buf_last = self.input_buffer.lock().last().copied();
if let Some(erased_ch) = buf_last {
let erased_ch = erased_ch as char;
if erased_ch != '\n' {
self.input_buffer.lock().pop();
self.erase_char(erased_ch);
}
}
} else if ch as char == self.termios.vstop && self.termios.iflags.ixon {
self.output_paused.store(true, Ordering::Relaxed);
} else if (ch as char == self.termios.vstart && self.termios.iflags.ixon)
|| (self.termios.iflags.ixany && self.output_paused.load(Ordering::Relaxed))
{
self.output_paused.store(false, Ordering::Relaxed);
} else {
self.input_buffer.lock().push(ch);
if self.termios.lflags.echo || (ch == b'\n' && self.termios.lflags.echonl) {
let mut output_buffer = self.output_buffer.lock();
if Self::is_nonprinting_control(ch as char) {
output_buffer.push(b'^');
output_buffer.push(ch + b'@');
self.column.fetch_add(2, Ordering::Relaxed);
} else if ch == b'\t' {
let curr_col = self.column.load(Ordering::Relaxed);
let num_cols = 8 - (curr_col % 8);
for _ in 0..num_cols {
output_buffer.push(b' ');
}
self.column.fetch_add(num_cols, Ordering::Relaxed);
} else {
output_buffer.push(ch);
self.column.fetch_add(1, Ordering::Relaxed);
}
}
}
}
Ok(())
} }
fn read_slave(&self, len: usize) -> Result<Vec<u8>, ()> { fn read_slave(&self, len: usize) -> Vec<u8> {
while !self.input_buffer.lock().contains(&b'\n') { while !self.input_buffer.lock().contains(&b'\n') {
ipc::process_messages(); ipc::process_messages();
let mut input_buffer = self.input_buffer.lock();
input_buffer.extend_from_slice(&self.line_discipline.lock().get_program_input());
} }
let mut input_buffer = self.input_buffer.lock(); let mut input_buffer = self.input_buffer.lock();
input_buffer.extend_from_slice(&self.line_discipline.lock().get_program_input());
let Some(line_end) = input_buffer.iter().position(|&x| x == b'\n') else { let Some(line_end) = input_buffer.iter().position(|&x| x == b'\n') else {
return Ok(vec![]); return Vec::new();
}; };
let rem_buf = input_buffer.split_off(usize::min(len, line_end + 1)); let rem_buf = input_buffer.split_off(usize::min(len, line_end + 1));
let buf = std::mem::replace(&mut *input_buffer, rem_buf); std::mem::replace(&mut *input_buffer, rem_buf)
Ok(buf)
} }
fn write_slave(&self, data: &[u8]) -> Result<(), ()> { fn write_slave(&self, data: &[u8]) {
let mut output_buffer = self.output_buffer.lock(); self.line_discipline.lock().process_program_output(data);
for &byte in data {
if self.termios.oflags.opost {
if byte == b'\n' && self.termios.oflags.onlcr {
if self.column.load(Ordering::Relaxed) != 0 {
output_buffer.push(b'\r');
}
output_buffer.push(b'\n');
self.column.store(0, Ordering::Relaxed);
} else if byte == b'\r' && self.termios.oflags.ocrnl {
output_buffer.push(b'\n');
if self.termios.oflags.onlret {
self.column.store(0, Ordering::Relaxed);
}
} else {
output_buffer.push(byte);
}
} else if (byte == b'\n' && self.termios.oflags.onlret) || byte == b'\r' {
self.column.store(0, Ordering::Relaxed);
}
}
Ok(())
}
fn is_nonprinting_control(ch: char) -> bool {
ch.is_ascii_control() && ch != '\r' && ch != '\n' && ch != '\t'
}
fn erase_char(&self, ch: char) {
if !self.termios.lflags.echoe {
return;
}
let mut output_buffer = self.output_buffer.lock();
if Self::is_nonprinting_control(ch) {
output_buffer.push(8);
output_buffer.push(8);
self.column.fetch_sub(2, Ordering::Relaxed);
} else if ch == '\t' {
let curr_col = self.column.load(Ordering::Relaxed);
let mut before_tab_cols = 0;
let mut after_tab = false;
let input_buffer = self.input_buffer.lock();
for &byte in input_buffer.iter().rev() {
if Self::is_nonprinting_control(byte as char) {
before_tab_cols += 2;
} else if byte == b'\t' {
after_tab = true;
break;
} else {
before_tab_cols += 1;
}
}
let num_cols = if after_tab {
8 - before_tab_cols
} else {
curr_col - before_tab_cols
};
for _ in 0..num_cols {
output_buffer.push(8);
}
self.column.fetch_sub(num_cols, Ordering::Relaxed);
} else {
output_buffer.push(8);
self.column.fetch_sub(1, Ordering::Relaxed);
}
} }
fn curr_slave_poll(&self) -> PollEvents { fn curr_slave_poll(&self) -> PollEvents {
self.input_buffer
.lock()
.extend_from_slice(&self.line_discipline.lock().get_program_input());
let mut events = PollEvents::POLLOUT; let mut events = PollEvents::POLLOUT;
if self.input_buffer.lock().contains(&b'\n') { if self.input_buffer.lock().contains(&b'\n') {
events |= PollEvents::POLLIN; events |= PollEvents::POLLIN;
@ -196,6 +72,9 @@ impl Pty {
} }
fn curr_master_poll(&self) -> PollEvents { fn curr_master_poll(&self) -> PollEvents {
self.output_buffer
.lock()
.extend_from_slice(&self.line_discipline.lock().get_terminal_output());
let mut events = PollEvents::POLLOUT; let mut events = PollEvents::POLLOUT;
if !self.output_buffer.lock().is_empty() { if !self.output_buffer.lock().is_empty() {
events |= PollEvents::POLLIN; events |= PollEvents::POLLIN;
@ -239,11 +118,11 @@ impl file_rpc::Server for Serv {
if fd as usize >= usize::MAX / 2 { if fd as usize >= usize::MAX / 2 {
let ptys = self.ptys.read(); let ptys = self.ptys.read();
let pty = &ptys[fd as usize - usize::MAX / 2]; let pty = &ptys[fd as usize - usize::MAX / 2];
pty.read_slave(len).map(|x| x.into()) Ok(pty.read_slave(len).into())
} else { } else {
let ptys = self.ptys.read(); let ptys = self.ptys.read();
let pty = &ptys[fd as usize]; let pty = &ptys[fd as usize];
pty.read_master(len).map(|x| x.into()) Ok(pty.read_master(len).into())
} }
} }
@ -251,11 +130,13 @@ impl file_rpc::Server for Serv {
if fd as usize >= usize::MAX / 2 { if fd as usize >= usize::MAX / 2 {
let ptys = self.ptys.read(); let ptys = self.ptys.read();
let pty = &ptys[fd as usize - usize::MAX / 2]; let pty = &ptys[fd as usize - usize::MAX / 2];
pty.write_slave(data) pty.write_slave(data);
Ok(())
} else { } else {
let ptys = self.ptys.read(); let ptys = self.ptys.read();
let pty = &ptys[fd as usize]; let pty = &ptys[fd as usize];
pty.write_master(data) pty.write_master(data);
Ok(())
} }
} }

View File

@ -1,212 +0,0 @@
#[allow(unused)]
pub struct Iflags {
pub brkint: bool,
pub icrnl: bool,
pub ignbrk: bool,
pub igncr: bool,
pub ignpar: bool,
pub inlcr: bool,
pub inpck: bool,
pub istrip: bool,
pub ixany: bool,
pub ixoff: bool,
pub ixon: bool,
pub parmark: bool,
}
impl Default for Iflags {
fn default() -> Self {
Self {
brkint: false,
icrnl: true,
ignbrk: false,
igncr: false,
ignpar: true,
inlcr: false,
inpck: true,
istrip: false,
ixany: false,
ixoff: false,
ixon: true,
parmark: false,
}
}
}
#[allow(unused)]
pub struct Oflags {
pub opost: bool,
pub onlcr: bool,
pub ocrnl: bool,
pub onocr: bool,
pub onlret: bool,
pub ofdel: bool,
pub nldly: NlDelay,
pub crdly: CrDelay,
pub tabdly: TabDelay,
pub bsdly: BsDelay,
pub vtdly: VtDelay,
pub ffdly: FfDelay,
}
impl Default for Oflags {
fn default() -> Self {
Self {
opost: true,
onlcr: true,
ocrnl: false,
onocr: false,
onlret: false,
ofdel: false,
nldly: NlDelay::Nl0,
crdly: CrDelay::Cr0,
tabdly: TabDelay::Tab0,
bsdly: BsDelay::Bs0,
vtdly: VtDelay::Vt0,
ffdly: FfDelay::Ff0,
}
}
}
#[allow(unused)]
pub enum NlDelay {
Nl0,
Nl1,
}
#[allow(unused)]
pub enum CrDelay {
Cr0,
Cr1,
Cr2,
Cr3,
}
#[allow(unused)]
pub enum TabDelay {
Tab0,
Tab1,
Tab2,
Tab3,
}
#[allow(unused)]
pub enum BsDelay {
Bs0,
Bs1,
}
#[allow(unused)]
pub enum VtDelay {
Vt0,
Vt1,
}
#[allow(unused)]
pub enum FfDelay {
Ff0,
Ff1,
}
#[allow(unused)]
pub struct Cflags {
pub csize: CharSize,
pub cstopb: bool,
pub cread: bool,
pub parenb: bool,
pub parodd: bool,
pub hupcl: bool,
pub clocal: bool,
}
impl Default for Cflags {
fn default() -> Self {
Self {
csize: CharSize::Cs8,
cstopb: false,
cread: true,
parenb: false,
parodd: false,
hupcl: true,
clocal: false,
}
}
}
#[allow(unused)]
pub enum CharSize {
Cs5,
Cs6,
Cs7,
Cs8,
}
#[allow(unused)]
pub struct Lflags {
pub echo: bool,
pub echoe: bool,
pub echok: bool,
pub echonl: bool,
pub icanon: bool,
pub iexten: bool,
pub isig: bool,
pub noflsh: bool,
pub tostop: bool,
}
impl Default for Lflags {
fn default() -> Self {
Self {
echo: true,
echoe: true,
echok: true,
echonl: false,
icanon: true,
iexten: true,
isig: true,
noflsh: false,
tostop: false,
}
}
}
#[allow(unused)]
pub struct Termios {
pub iflags: Iflags,
pub oflags: Oflags,
pub cflags: Cflags,
pub lflags: Lflags,
pub veof: char,
pub veol: char,
pub verase: char,
pub vintr: char,
pub vkill: char,
pub vmin: u32,
pub vquit: char,
pub vstart: char,
pub vstop: char,
pub vsusp: char,
pub vtime: u32,
}
impl Default for Termios {
fn default() -> Self {
Self {
iflags: Iflags::default(),
oflags: Oflags::default(),
cflags: Cflags::default(),
lflags: Lflags::default(),
veof: '\u{4}', //ctrl-D (EOT)
veol: '\n',
verase: '\u{8}', //ctrl-H (BS)
vintr: '\u{3}', //ctrl-C (ETX)
vkill: '\u{15}', //ctrl-U (NAK)
vmin: 0,
vquit: '\u{1C}', //ctrl-\
vstart: '\u{11}', // ctrl-S (XONN)
vstop: '\u{13}', // ctrl-Q (XOFF)
vsusp: '\u{1A}', // ctrl-Z (SUB)
vtime: 0,
}
}
}