vfs: add is_terminal()

This commit is contained in:
Mark Poliakov 2024-12-21 00:28:28 +02:00
parent 8dbbc07ff6
commit 4acb148d0e
9 changed files with 86 additions and 32 deletions

View File

@ -93,12 +93,6 @@ impl CharFile {
self.device.0.read_nonblocking(buf) self.device.0.read_nonblocking(buf)
} }
} }
// if self.read {
// self.device.0.read(buf)
// } else {
// Err(Error::InvalidOperation)
// }
// }
pub fn write(&self, buf: &[u8]) -> Result<usize, Error> { pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
if !self.write { if !self.write {
@ -111,10 +105,8 @@ impl CharFile {
self.device.0.write_nonblocking(buf) self.device.0.write_nonblocking(buf)
} }
} }
// if self.write {
// self.device.0.write(buf) pub fn is_terminal(&self) -> bool {
// } else { self.device.0.is_terminal()
// Err(Error::ReadOnly) }
// }
// }
} }

View File

@ -341,6 +341,14 @@ impl File {
/// Performs a device-specific request /// Performs a device-specific request
pub fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> { pub fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> {
match req {
DeviceRequest::IsTerminal(value) => {
*value = self.is_terminal();
return Ok(());
}
_ => (),
}
match self { match self {
Self::Char(f) => f.device.device_request(req), Self::Char(f) => f.device.device_request(req),
Self::Block(f) => f.device.device_request(req), Self::Block(f) => f.device.device_request(req),
@ -375,6 +383,14 @@ impl File {
_ => Err(Error::InvalidOperation), _ => Err(Error::InvalidOperation),
} }
} }
pub fn is_terminal(&self) -> bool {
match self {
Self::Char(dev) => dev.is_terminal(),
Self::PtySlave(_) | Self::PtyMaster(_) => true,
_ => false,
}
}
} }
impl PageProvider for File { impl PageProvider for File {

View File

@ -29,6 +29,8 @@ pub enum DeviceRequest {
GetTerminalSize(MaybeUninit<terminal::TerminalSize>), GetTerminalSize(MaybeUninit<terminal::TerminalSize>),
/// Sets a foreground process group ID for the terminal /// Sets a foreground process group ID for the terminal
SetTerminalGroup(ProcessGroupId), SetTerminalGroup(ProcessGroupId),
/// Returns `true` if a file/device is a terminal
IsTerminal(bool),
/// "Acquires" ownership of the device, preventing others from accessing it /// "Acquires" ownership of the device, preventing others from accessing it
AcquireDevice, AcquireDevice,

View File

@ -28,7 +28,7 @@ pub use abi::io::{
FileType, OpenOptions, PipeOptions, RawFd, SeekFrom, TimerOptions, FileType, OpenOptions, PipeOptions, RawFd, SeekFrom, TimerOptions,
}; };
use abi::{error::Error, process::ProcessOption, util::FixedString}; use abi::{error::Error, io::DeviceRequest, process::ProcessOption, util::FixedString};
use alloc::string::String; use alloc::string::String;
pub fn current_directory<T, F: FnOnce(&str) -> T>(mapper: F) -> Result<T, Error> { pub fn current_directory<T, F: FnOnce(&str) -> T>(mapper: F) -> Result<T, Error> {
@ -50,3 +50,12 @@ pub fn set_current_directory(path: &str) -> Result<(), Error> {
unsafe { crate::sys::set_process_option(&mut option) }?; unsafe { crate::sys::set_process_option(&mut option) }?;
Ok(()) Ok(())
} }
pub fn is_terminal(f: RawFd) -> bool {
let mut option = DeviceRequest::IsTerminal(false);
let res = unsafe { crate::sys::device_request(f, &mut option) };
match (res, option) {
(Ok(()), DeviceRequest::IsTerminal(true)) => true,
_ => false,
}
}

View File

@ -1,8 +1,10 @@
use std::{ use std::{
fmt, fmt,
io::{self, Read, Stdin}, io::{self, Read},
}; };
use crate::TermInput;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum TermKey { pub enum TermKey {
Char(char), Char(char),
@ -23,7 +25,7 @@ pub trait ReadChar {
fn read_char(&mut self) -> Result<char, InputError>; fn read_char(&mut self) -> Result<char, InputError>;
} }
impl ReadChar for Stdin { impl ReadChar for TermInput {
fn read_char(&mut self) -> Result<char, InputError> { fn read_char(&mut self) -> Result<char, InputError> {
let mut buf = [0; 4]; let mut buf = [0; 4];
self.read_exact(&mut buf[..1]) self.read_exact(&mut buf[..1])

View File

@ -1,9 +1,7 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))] #![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
use std::{ use std::{
fmt, fmt, fs::File, io::{self, stdin, stdout, IsTerminal, Read, Stdin, Stdout, Write}, os::fd::{AsRawFd, RawFd}
io::{self, stdin, stdout, Stdin, Stdout, Write},
os::fd::{AsRawFd, RawFd},
}; };
pub use self::{input::ReadChar, sys::RawMode}; pub use self::{input::ReadChar, sys::RawMode};
@ -43,8 +41,31 @@ pub trait RawTerminal {
fn raw_size(&self) -> io::Result<(usize, usize)>; fn raw_size(&self) -> io::Result<(usize, usize)>;
} }
enum TermInput {
Stdin(Stdin),
File(File)
}
impl Read for TermInput {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Stdin(stdin) => stdin.read(buf),
Self::File(file) => file.read(buf)
}
}
}
impl AsRawFd for TermInput {
fn as_raw_fd(&self) -> RawFd {
match self {
Self::Stdin(stdin) => stdin.as_raw_fd(),
Self::File(file) => file.as_raw_fd(),
}
}
}
pub struct Term { pub struct Term {
stdin: Stdin, stdin: TermInput,
stdout: Stdout, stdout: Stdout,
raw: RawMode, raw: RawMode,
} }
@ -134,9 +155,8 @@ impl RawTerminal for Stdout {
} }
impl Term { impl Term {
pub fn is_tty() -> bool { pub fn stdin_is_tty() -> bool {
// TODO stdin().is_terminal()
true
} }
pub fn input_fd(&self) -> RawFd { pub fn input_fd(&self) -> RawFd {
@ -145,6 +165,12 @@ impl Term {
pub fn open() -> Result<Self, Error> { pub fn open() -> Result<Self, Error> {
let stdin = stdin(); let stdin = stdin();
let stdin = if stdin.is_terminal() {
TermInput::Stdin(stdin)
} else {
let file = File::open("/dev/tty")?;
TermInput::File(file)
};
let mut stdout = stdout(); let mut stdout = stdout();
// Set stdin to raw mode // Set stdin to raw mode

View File

@ -1,10 +1,10 @@
use std::{ use std::{
io::{self, Stdin, Stdout}, io::{self, Stdout},
mem::MaybeUninit, mem::MaybeUninit,
os::yggdrasil::io::{ os::{fd::AsRawFd, yggdrasil::io::{
device::{DeviceRequest, FdDeviceRequest}, device::{DeviceRequest, FdDeviceRequest},
terminal::{update_terminal_options, TerminalOptions}, terminal::{update_terminal_options, TerminalOptions},
}, }},
}; };
pub struct RawMode(TerminalOptions); pub struct RawMode(TerminalOptions);
@ -13,15 +13,15 @@ impl RawMode {
/// # Safety /// # Safety
/// ///
/// May leave the terminal in broken state, unsafe. /// May leave the terminal in broken state, unsafe.
pub unsafe fn enter(stdin: &Stdin) -> io::Result<Self> { pub unsafe fn enter<F: AsRawFd>(stdin: &F) -> io::Result<Self> {
update_terminal_options(stdin, |_| TerminalOptions::raw_input()).map(RawMode) update_terminal_options(stdin.as_raw_fd(), |_| TerminalOptions::raw_input()).map(RawMode)
} }
/// # Safety /// # Safety
/// ///
/// May leave the terminal in broken state, unsafe. /// May leave the terminal in broken state, unsafe.
pub unsafe fn leave(&self, stdin: &Stdin) { pub unsafe fn leave<F: AsRawFd>(&self, stdin: &F) {
update_terminal_options(stdin, |_| self.0).ok(); update_terminal_options(stdin.as_raw_fd(), |_| self.0).ok();
} }
} }

View File

@ -347,7 +347,7 @@ fn main() {
return; return;
} }
if !Term::is_tty() { if !Term::stdin_is_tty() {
eprintln!("Not a tty"); eprintln!("Not a tty");
return; return;
} }

View File

@ -1,6 +1,7 @@
use std::{ use std::{
fmt::{self, Write}, fmt::{self, Write},
io, io,
process::ExitCode,
}; };
use clap::Parser; use clap::Parser;
@ -160,9 +161,15 @@ struct Args {
filename: String, filename: String,
} }
fn main() -> Result<(), Error> { fn main() -> ExitCode {
// TODO check if running in a terminal // TODO check if running in a terminal
let args = Args::parse(); let args = Args::parse();
let view = View::open(&args.filename, !args.no_bar).unwrap(); let view = View::open(&args.filename, !args.no_bar).unwrap();
view.run() match view.run() {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
yggdrasil_rt::debug_trace!("view: {error}");
ExitCode::FAILURE
}
}
} }