Skip to content

Commit

Permalink
feat: Use rustix instead of libc
Browse files Browse the repository at this point in the history
Closes crossterm-rs#847

rustix is a wrapper around either raw Linux system calls or libc, based
on the current platform. The main advantage is that it can make programs
much more efficient, since system calls can be inlined directly into
the functions that call them. I've seen rustix reduce instruction counts
in my programs when I've made the switch in another programs.

In addition, it reduces the amount of unsafe code.

Signed-off-by: John Nunley <dev@notgull.net>
  • Loading branch information
notgull committed May 14, 2024
1 parent fce58c8 commit 1726226
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 106 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ keywords = ["event", "color", "cli", "input", "terminal"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2021"
rust-version = "1.58.0"
rust-version = "1.63.0"
categories = ["command-line-interface", "command-line-utilities"]

[lib]
Expand Down Expand Up @@ -71,13 +71,17 @@ crossterm_winapi = { version = "0.9.1", optional = true }
# UNIX dependencies
#
[target.'cfg(unix)'.dependencies]
libc = "0.2"
signal-hook = { version = "0.3.17", optional = true }
filedescriptor = { version = "0.8", optional = true }
mio = { version = "0.8", features = ["os-poll"], optional = true }
<<<<<<< HEAD
signal-hook-mio = { version = "0.2.3", features = [
"support-v0_8",
], optional = true }
=======
signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"], optional = true }
rustix = { version = "0.38.0", default-features = false, features = ["std", "stdio", "termios"] }
>>>>>>> 4ca0c8e (feat: Use rustix instead of libc)

#
# Dev dependencies (examples, ...)
Expand Down
76 changes: 25 additions & 51 deletions src/terminal/sys/file_descriptor.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,42 @@
use std::{
fs, io,
os::unix::{
io::{IntoRawFd, RawFd},
prelude::AsRawFd,
},
};
use std::{fs, io};

use libc::size_t;
use rustix::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};

/// A file descriptor wrapper.
///
/// It allows to retrieve raw file descriptor, write to the file descriptor and
/// mainly it closes the file descriptor once dropped.
#[derive(Debug)]
pub struct FileDesc {
fd: RawFd,
close_on_drop: bool,
pub enum FileDesc {
Owned(OwnedFd),
Static(BorrowedFd<'static>),
}

impl FileDesc {
/// Constructs a new `FileDesc` with the given `RawFd`.
///
/// # Arguments
///
/// * `fd` - raw file descriptor
/// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped
pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc {
FileDesc { fd, close_on_drop }
}

pub fn read(&self, buffer: &mut [u8]) -> io::Result<usize> {
let result = unsafe {
libc::read(
self.fd,
buffer.as_mut_ptr() as *mut libc::c_void,
buffer.len() as size_t,
)
let fd = match self {
Self::Owned(fd) => fd.as_fd(),
Self::Static(fd) => fd.as_fd(),
};

if result < 0 {
Err(io::Error::last_os_error())
} else {
Ok(result as usize)
}
let result = rustix::io::read(fd, buffer)?;
Ok(result)
}

/// Returns the underlying file descriptor.
pub fn raw_fd(&self) -> RawFd {
self.fd
match self {
Self::Owned(fd) => fd.as_raw_fd(),
Self::Static(fd) => fd.as_raw_fd(),
}
}
}

impl Drop for FileDesc {
fn drop(&mut self) {
if self.close_on_drop {
// Note that errors are ignored when closing a file descriptor. The
// reason for this is that if an error occurs we don't actually know if
// the file descriptor was closed or not, and if we retried (for
// something like EINTR), we might close another valid file descriptor
// opened after we closed ours.
let _ = unsafe { libc::close(self.fd) };
impl AsFd for FileDesc {
fn as_fd(&self) -> BorrowedFd<'_> {
match self {
Self::Owned(fd) => fd.as_fd(),
Self::Static(fd) => fd.as_fd(),
}
}
}
Expand All @@ -72,18 +49,15 @@ impl AsRawFd for FileDesc {

/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
pub fn tty_fd() -> io::Result<FileDesc> {
let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
(libc::STDIN_FILENO, false)
if rustix::termios::isatty(rustix::stdio::stdin()) {
Ok(FileDesc::Static(rustix::stdio::stdin()))
} else {
(
Ok(FileDesc::Owned(
fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?
.into_raw_fd(),
true,
)
};

Ok(FileDesc::new(fd, close_on_drop))
.into(),
))
}
}
75 changes: 23 additions & 52 deletions src/terminal/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ use crate::terminal::{
sys::file_descriptor::{tty_fd, FileDesc},
WindowSize,
};
use libc::{
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
TIOCGWINSZ,
};
use parking_lot::Mutex;
use rustix::{
fd::AsFd,
termios::{Termios, Winsize},
};
use std::fs::File;

use std::os::unix::io::{IntoRawFd, RawFd};

use std::{io, mem, process};
use std::{io, process};

// Some(Termios) -> we're in the raw mode and this is the previous mode
// None -> we're not in the raw mode
Expand All @@ -23,8 +21,8 @@ pub(crate) fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some()
}

impl From<winsize> for WindowSize {
fn from(size: winsize) -> WindowSize {
impl From<Winsize> for WindowSize {
fn from(size: Winsize) -> WindowSize {
WindowSize {
columns: size.ws_col,
rows: size.ws_row,
Expand All @@ -36,27 +34,16 @@ impl From<winsize> for WindowSize {

#[allow(clippy::useless_conversion)]
pub(crate) fn window_size() -> io::Result<WindowSize> {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let mut size = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};

let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true)));
let file = File::open("/dev/tty").map(|file| FileDesc::Owned(file.into()));
let fd = if let Ok(file) = &file {
file.raw_fd()
file.as_fd()
} else {
// Fallback to libc::STDOUT_FILENO if /dev/tty is missing
STDOUT_FILENO
rustix::stdio::stdout()
};

if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
return Ok(size.into());
}

Err(std::io::Error::last_os_error().into())
let size = rustix::termios::tcgetwinsize(fd)?;
Ok(size.into())
}

#[allow(clippy::useless_conversion)]
Expand All @@ -76,12 +63,11 @@ pub(crate) fn enable_raw_mode() -> io::Result<()> {
}

let tty = tty_fd()?;
let fd = tty.raw_fd();
let mut ios = get_terminal_attr(fd)?;
let original_mode_ios = ios;
let mut ios = get_terminal_attr(&tty)?;
let original_mode_ios = ios.clone();

raw_terminal_attr(&mut ios);
set_terminal_attr(fd, &ios)?;
ios.make_raw();
set_terminal_attr(&tty, &ios)?;

// Keep it last - set the original mode only if we were able to switch to the raw mode
*original_mode = Some(original_mode_ios);
Expand All @@ -99,7 +85,7 @@ pub(crate) fn disable_raw_mode() -> io::Result<()> {

if let Some(original_mode_ios) = original_mode.as_ref() {
let tty = tty_fd()?;
set_terminal_attr(tty.raw_fd(), original_mode_ios)?;
set_terminal_attr(&tty, original_mode_ios)?;
// Keep it last - remove the original mode only if we were able to switch back
*original_mode = None;
}
Expand Down Expand Up @@ -214,27 +200,12 @@ fn tput_size() -> Option<(u16, u16)> {
}
}

// Transform the given mode into an raw mode (non-canonical) mode.
fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) }
fn get_terminal_attr(fd: impl AsFd) -> io::Result<Termios> {
let result = rustix::termios::tcgetattr(fd)?;
Ok(result)
}

fn get_terminal_attr(fd: RawFd) -> io::Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(fd, &mut termios))?;
Ok(termios)
}
}

fn set_terminal_attr(fd: RawFd, termios: &Termios) -> io::Result<()> {
wrap_with_result(unsafe { tcsetattr(fd, TCSANOW, termios) })
}

fn wrap_with_result(result: i32) -> io::Result<()> {
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
fn set_terminal_attr(fd: impl AsFd, termios: &Termios) -> io::Result<()> {
rustix::termios::tcsetattr(fd, rustix::termios::OptionalActions::Now, termios)?;
Ok(())
}
2 changes: 1 addition & 1 deletion src/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub trait IsTty {
impl<S: AsRawFd> IsTty for S {
fn is_tty(&self) -> bool {
let fd = self.as_raw_fd();
unsafe { libc::isatty(fd) == 1 }
rustix::termios::isatty(unsafe { std::os::unix::io::BorrowedFd::borrow_raw(fd) })
}
}

Expand Down

0 comments on commit 1726226

Please sign in to comment.