From 4acb148d0e41ebdf7e5c0da76177152d111a18e3 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Sat, 21 Dec 2024 00:28:28 +0200 Subject: [PATCH] vfs: add is_terminal() --- kernel/libk/src/vfs/file/device.rs | 16 +++-------- kernel/libk/src/vfs/file/mod.rs | 16 +++++++++++ lib/abi/src/io/device.rs | 2 ++ lib/runtime/src/io.rs | 11 ++++++- userspace/lib/libterm/src/input.rs | 6 ++-- userspace/lib/libterm/src/lib.rs | 40 +++++++++++++++++++++----- userspace/lib/libterm/src/yggdrasil.rs | 14 ++++----- userspace/red/src/main.rs | 2 +- userspace/sysutils/src/view.rs | 11 +++++-- 9 files changed, 86 insertions(+), 32 deletions(-) diff --git a/kernel/libk/src/vfs/file/device.rs b/kernel/libk/src/vfs/file/device.rs index 6edc501a..46282c00 100644 --- a/kernel/libk/src/vfs/file/device.rs +++ b/kernel/libk/src/vfs/file/device.rs @@ -93,12 +93,6 @@ impl CharFile { 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 { if !self.write { @@ -111,10 +105,8 @@ impl CharFile { self.device.0.write_nonblocking(buf) } } - // if self.write { - // self.device.0.write(buf) - // } else { - // Err(Error::ReadOnly) - // } - // } + + pub fn is_terminal(&self) -> bool { + self.device.0.is_terminal() + } } diff --git a/kernel/libk/src/vfs/file/mod.rs b/kernel/libk/src/vfs/file/mod.rs index 7a912c07..06a763a5 100644 --- a/kernel/libk/src/vfs/file/mod.rs +++ b/kernel/libk/src/vfs/file/mod.rs @@ -341,6 +341,14 @@ impl File { /// Performs a device-specific request pub fn device_request(&self, req: &mut DeviceRequest) -> Result<(), Error> { + match req { + DeviceRequest::IsTerminal(value) => { + *value = self.is_terminal(); + return Ok(()); + } + _ => (), + } + match self { Self::Char(f) => f.device.device_request(req), Self::Block(f) => f.device.device_request(req), @@ -375,6 +383,14 @@ impl File { _ => 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 { diff --git a/lib/abi/src/io/device.rs b/lib/abi/src/io/device.rs index 1f01e85f..9b88578d 100644 --- a/lib/abi/src/io/device.rs +++ b/lib/abi/src/io/device.rs @@ -29,6 +29,8 @@ pub enum DeviceRequest { GetTerminalSize(MaybeUninit), /// Sets a foreground process group ID for the terminal SetTerminalGroup(ProcessGroupId), + /// Returns `true` if a file/device is a terminal + IsTerminal(bool), /// "Acquires" ownership of the device, preventing others from accessing it AcquireDevice, diff --git a/lib/runtime/src/io.rs b/lib/runtime/src/io.rs index fc034378..0f56f303 100644 --- a/lib/runtime/src/io.rs +++ b/lib/runtime/src/io.rs @@ -28,7 +28,7 @@ pub use abi::io::{ 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; pub fn current_directory T>(mapper: F) -> Result { @@ -50,3 +50,12 @@ pub fn set_current_directory(path: &str) -> Result<(), Error> { unsafe { crate::sys::set_process_option(&mut option) }?; 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, + } +} diff --git a/userspace/lib/libterm/src/input.rs b/userspace/lib/libterm/src/input.rs index b9073132..a9459593 100644 --- a/userspace/lib/libterm/src/input.rs +++ b/userspace/lib/libterm/src/input.rs @@ -1,8 +1,10 @@ use std::{ fmt, - io::{self, Read, Stdin}, + io::{self, Read}, }; +use crate::TermInput; + #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum TermKey { Char(char), @@ -23,7 +25,7 @@ pub trait ReadChar { fn read_char(&mut self) -> Result; } -impl ReadChar for Stdin { +impl ReadChar for TermInput { fn read_char(&mut self) -> Result { let mut buf = [0; 4]; self.read_exact(&mut buf[..1]) diff --git a/userspace/lib/libterm/src/lib.rs b/userspace/lib/libterm/src/lib.rs index 684bde9a..64ac1819 100644 --- a/userspace/lib/libterm/src/lib.rs +++ b/userspace/lib/libterm/src/lib.rs @@ -1,9 +1,7 @@ #![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))] use std::{ - fmt, - io::{self, stdin, stdout, Stdin, Stdout, Write}, - os::fd::{AsRawFd, RawFd}, + fmt, fs::File, io::{self, stdin, stdout, IsTerminal, Read, Stdin, Stdout, Write}, os::fd::{AsRawFd, RawFd} }; pub use self::{input::ReadChar, sys::RawMode}; @@ -43,8 +41,31 @@ pub trait RawTerminal { 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 { + 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 { - stdin: Stdin, + stdin: TermInput, stdout: Stdout, raw: RawMode, } @@ -134,9 +155,8 @@ impl RawTerminal for Stdout { } impl Term { - pub fn is_tty() -> bool { - // TODO - true + pub fn stdin_is_tty() -> bool { + stdin().is_terminal() } pub fn input_fd(&self) -> RawFd { @@ -145,6 +165,12 @@ impl Term { pub fn open() -> Result { 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(); // Set stdin to raw mode diff --git a/userspace/lib/libterm/src/yggdrasil.rs b/userspace/lib/libterm/src/yggdrasil.rs index d5411f41..b958e4c5 100644 --- a/userspace/lib/libterm/src/yggdrasil.rs +++ b/userspace/lib/libterm/src/yggdrasil.rs @@ -1,10 +1,10 @@ use std::{ - io::{self, Stdin, Stdout}, + io::{self, Stdout}, mem::MaybeUninit, - os::yggdrasil::io::{ + os::{fd::AsRawFd, yggdrasil::io::{ device::{DeviceRequest, FdDeviceRequest}, terminal::{update_terminal_options, TerminalOptions}, - }, + }}, }; pub struct RawMode(TerminalOptions); @@ -13,15 +13,15 @@ impl RawMode { /// # Safety /// /// May leave the terminal in broken state, unsafe. - pub unsafe fn enter(stdin: &Stdin) -> io::Result { - update_terminal_options(stdin, |_| TerminalOptions::raw_input()).map(RawMode) + pub unsafe fn enter(stdin: &F) -> io::Result { + update_terminal_options(stdin.as_raw_fd(), |_| TerminalOptions::raw_input()).map(RawMode) } /// # Safety /// /// May leave the terminal in broken state, unsafe. - pub unsafe fn leave(&self, stdin: &Stdin) { - update_terminal_options(stdin, |_| self.0).ok(); + pub unsafe fn leave(&self, stdin: &F) { + update_terminal_options(stdin.as_raw_fd(), |_| self.0).ok(); } } diff --git a/userspace/red/src/main.rs b/userspace/red/src/main.rs index 7badeb29..6677238e 100644 --- a/userspace/red/src/main.rs +++ b/userspace/red/src/main.rs @@ -347,7 +347,7 @@ fn main() { return; } - if !Term::is_tty() { + if !Term::stdin_is_tty() { eprintln!("Not a tty"); return; } diff --git a/userspace/sysutils/src/view.rs b/userspace/sysutils/src/view.rs index 73867ab9..3e82fa90 100644 --- a/userspace/sysutils/src/view.rs +++ b/userspace/sysutils/src/view.rs @@ -1,6 +1,7 @@ use std::{ fmt::{self, Write}, io, + process::ExitCode, }; use clap::Parser; @@ -160,9 +161,15 @@ struct Args { filename: String, } -fn main() -> Result<(), Error> { +fn main() -> ExitCode { // TODO check if running in a terminal let args = Args::parse(); 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 + } + } }