diff --git a/src/main.rs b/src/main.rs index 3b1d7335..e95908d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,6 @@ +//! Alacritty - The GPU Enhanced Terminal +#![feature(question_mark)] + extern crate fontconfig; extern crate freetype; extern crate libc; @@ -7,11 +10,14 @@ extern crate euclid; use std::collections::HashMap; +use std::io::{BufReader, Read, BufRead}; + mod list_fonts; mod text; mod renderer; mod grid; mod meter; +mod tty; use renderer::{Glyph, QuadRenderer}; use text::FontDesc; @@ -76,6 +82,14 @@ fn main() { let num_cols = grid::num_cells_axis(cell_width, sep_x, width); let num_rows = grid::num_cells_axis(cell_height, sep_y, height); + let mut cmd = tty::new(num_rows as u8, num_cols as u8); + + ::std::thread::spawn(move || { + for byte in cmd.bytes() { + println!("{:?}", byte); + } + }); + println!("num_cols, num_rows = {}, {}", num_cols, num_rows); let mut grid = Grid::new(num_rows as usize, num_cols as usize); @@ -130,7 +144,7 @@ fn main() { for event in window.poll_events() { match event { glutin::Event::Closed => break 'main_loop, - _ => println!("event: {:?}", event) + _ => () } } diff --git a/src/tty.rs b/src/tty.rs new file mode 100644 index 00000000..7fd27b69 --- /dev/null +++ b/src/tty.rs @@ -0,0 +1,246 @@ +//! tty related functionality +//! +use std::env; +use std::ffi::CStr; +use std::fs::File; +use std::mem; +use std::os::unix::io::FromRawFd; +use std::ptr; + +use libc::{self, winsize, c_int, c_char}; + +macro_rules! die { + ($($arg:tt)*) => { + println!($($arg)*); + ::std::process::exit(1); + } +} + +pub enum Error { + /// TODO + Unknown, +} + +impl Error { + /// Build an Error from the current value of errno. + fn from_errno() -> Error { + let err = errno(); + match err { + _ => Error::Unknown + } + } +} + +pub type Result = ::std::result::Result; + +/// Get the current value of errno +fn errno() -> c_int { + unsafe { + ptr::read(libc::__errno_location() as *const _) + } +} + +enum Relation { + Child, + Parent +} + +fn fork() -> Relation { + let res = unsafe { + libc::fork() + }; + + if res < 0 { + die!("fork failed"); + } + + if res == 0 { + Relation::Child + } else { + Relation::Parent + } +} + +/// Get raw fds for master/slave ends of a new pty +fn openpty(rows: u8, cols: u8) -> (c_int, c_int) { + let mut master: c_int = 0; + let mut slave: c_int = 0; + + let win = winsize { + ws_row: rows as libc::c_ushort, + ws_col: cols as libc::c_ushort, + ws_xpixel: 0, + ws_ypixel: 0, + }; + + let res = unsafe { + libc::openpty(&mut master, &mut slave, ptr::null_mut(), ptr::null(), &win) + }; + + if res < 0 { + die!("openpty failed"); + } + + (master, slave) +} + +/// Really only needed on BSD, but should be fine elsewhere +fn set_controlling_terminal(fd: c_int) { + let res = unsafe { + libc::ioctl(fd, libc::TIOCSCTTY, 0) + }; + + if res < 0 { + die!("ioctl TIOCSCTTY failed: {}", errno()); + } +} + +#[derive(Debug)] +struct Passwd<'a> { + name: &'a str, + passwd: &'a str, + uid: libc::uid_t, + gid: libc::gid_t, + gecos: &'a str, + dir: &'a str, + shell: &'a str, +} + +/// Return a Passwd struct with pointers into the provided buf +/// +/// # Unsafety +/// +/// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen. +fn get_pw_entry<'a>(buf: &'a mut [i8; 1024]) -> Passwd<'a> { + // Create zeroed passwd struct + let mut entry = libc::passwd { + pw_name: ptr::null_mut(), + pw_passwd: ptr::null_mut(), + pw_uid: 0, + pw_gid: 0, + pw_gecos: ptr::null_mut(), + pw_dir: ptr::null_mut(), + pw_shell: ptr::null_mut(), + }; + + let mut res: *mut libc::passwd = ptr::null_mut(); + + // Try and read the pw file. + let uid = unsafe { libc::getuid() }; + let status = unsafe { + libc::getpwuid_r(uid, &mut entry, buf.as_mut_ptr(), buf.len(), &mut res) + }; + + if status < 0 { + die!("getpwuid_r failed"); + } + + if res.is_null() { + die!("pw not found"); + } + + + // sanity check + assert_eq!(entry.pw_uid, uid); + + // Build a borrowed Passwd struct + // + // Transmute is used here to conveniently cast from the raw CStr to a &str with the appropriate + // lifetime. + Passwd { + name: unsafe { mem::transmute(CStr::from_ptr(entry.pw_name).to_str().unwrap()) }, + passwd: unsafe { mem::transmute(CStr::from_ptr(entry.pw_passwd).to_str().unwrap()) }, + uid: entry.pw_uid, + gid: entry.pw_gid, + gecos: unsafe { mem::transmute(CStr::from_ptr(entry.pw_gecos).to_str().unwrap()) }, + dir: unsafe { mem::transmute(CStr::from_ptr(entry.pw_dir).to_str().unwrap()) }, + shell: unsafe { mem::transmute(CStr::from_ptr(entry.pw_shell).to_str().unwrap()) }, + } +} + +/// Exec a shell +fn execsh() -> ! { + let mut buf = [0; 1024]; + let pw = get_pw_entry(&mut buf); + + // setup environment + env::set_var("LOGNAME", pw.name); + env::set_var("USER", pw.name); + env::set_var("SHELL", pw.shell); + env::set_var("HOME", pw.dir); + env::set_var("TERM", "xterm-256color"); // sigh + + unsafe { + libc::signal(libc::SIGCHLD, libc::SIG_DFL); + libc::signal(libc::SIGHUP, libc::SIG_DFL); + libc::signal(libc::SIGINT, libc::SIG_DFL); + libc::signal(libc::SIGQUIT, libc::SIG_DFL); + libc::signal(libc::SIGTERM, libc::SIG_DFL); + libc::signal(libc::SIGALRM, libc::SIG_DFL); + } + + // pw.shell is null terminated + let shell = unsafe { CStr::from_ptr(pw.shell.as_ptr() as *const _) }; + + let argv = [shell.as_ptr(), ptr::null()]; + + let res = unsafe { + libc::execvp(shell.as_ptr(), argv.as_ptr()) + }; + + if res < 0 { + die!("execvp failed: {}", errno()); + } + + ::std::process::exit(1); +} + +/// Create a new tty and return a handle to interact with it. +pub fn new(rows: u8, cols: u8) -> File { + let (master, slave) = openpty(rows, cols); + + match fork() { + Relation::Child => { + unsafe { + // Create a new process group + libc::setsid(); + + // Duplicate pty slave to be child stdin, stdoud, and stderr + libc::dup2(slave, 0); + libc::dup2(slave, 1); + libc::dup2(slave, 2); + } + + set_controlling_terminal(slave); + + // No longer need slave/master fds + unsafe { + libc::close(slave); + libc::close(master); + } + + // Exec a shell! + execsh(); + }, + Relation::Parent => { + // Parent doesn't need slave fd + unsafe { + libc::close(slave); + } + + // XXX should this really return a file? + // How should this be done? Could build a File::from_raw_fd, or maybe implement a custom + // type that can be used in a mio event loop? For now, just do the file option. + unsafe { + File::from_raw_fd(master) + } + } + } +} + +#[test] +fn test_get_pw_entry() { + let mut buf: [i8; 1024] = [0; 1024]; + let pw = get_pw_entry(&mut buf); + println!("{:?}", pw); +}