diff options
Diffstat (limited to 'src/tty/unix.rs')
-rw-r--r-- | src/tty/unix.rs | 405 |
1 files changed, 0 insertions, 405 deletions
diff --git a/src/tty/unix.rs b/src/tty/unix.rs deleted file mode 100644 index 0e3dc2fd..00000000 --- a/src/tty/unix.rs +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! tty related functionality -//! - -use crate::cli::Options; -use crate::config::{Config, Shell}; -use crate::display::OnResize; -use crate::term::SizeInfo; -use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; -use mio; - -use libc::{self, c_int, pid_t, winsize, TIOCSCTTY}; -use nix::pty::openpty; -use signal_hook::{self as sighook, iterator::Signals}; - -use mio::unix::EventedFd; -use std::ffi::CStr; -use std::fs::File; -use std::io; -use std::os::unix::{ - io::{AsRawFd, FromRawFd, RawFd}, - process::CommandExt, -}; -use std::process::{Child, Command, Stdio}; -use std::ptr; -use std::sync::atomic::{AtomicUsize, Ordering}; - -/// Process ID of child process -/// -/// Necessary to put this in static storage for `sigchld` to have access -static PID: AtomicUsize = AtomicUsize::new(0); - -pub fn child_pid() -> pid_t { - PID.load(Ordering::Relaxed) as pid_t -} - -/// Get the current value of errno -fn errno() -> c_int { - ::errno::errno().0 -} - -/// Get raw fds for master/slave ends of a new pty -fn make_pty(size: winsize) -> (RawFd, RawFd) { - let mut win_size = size; - win_size.ws_xpixel = 0; - win_size.ws_ypixel = 0; - - let ends = openpty(Some(&win_size), None).expect("openpty failed"); - - (ends.master, ends.slave) -} - -/// Really only needed on BSD, but should be fine elsewhere -fn set_controlling_terminal(fd: c_int) { - let res = unsafe { - // TIOSCTTY changes based on platform and the `ioctl` call is different - // based on architecture (32/64). So a generic cast is used to make sure - // there are no issues. To allow such a generic cast the clippy warning - // is disabled. - #[allow(clippy::cast_lossless)] - libc::ioctl(fd, TIOCSCTTY as _, 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(buf: &mut [i8; 1024]) -> Passwd<'_> { - // Create zeroed passwd struct - let mut entry: libc::passwd = unsafe { ::std::mem::uninitialized() }; - - 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() as *mut _, 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 - Passwd { - name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() }, - passwd: unsafe { CStr::from_ptr(entry.pw_passwd).to_str().unwrap() }, - uid: entry.pw_uid, - gid: entry.pw_gid, - gecos: unsafe { CStr::from_ptr(entry.pw_gecos).to_str().unwrap() }, - dir: unsafe { CStr::from_ptr(entry.pw_dir).to_str().unwrap() }, - shell: unsafe { CStr::from_ptr(entry.pw_shell).to_str().unwrap() }, - } -} - -pub struct Pty { - child: Child, - pub fd: File, - token: mio::Token, - signals: Signals, - signals_token: mio::Token, -} - -impl Pty { - /// Resize the pty - /// - /// Tells the kernel that the window size changed with the new pixel - /// dimensions and line/column counts. - pub fn resize<T: ToWinsize>(&self, size: &T) { - let win = size.to_winsize(); - - let res = unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) }; - - if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", errno()); - } - } -} - -/// Create a new tty and return a handle to interact with it. -pub fn new<T: ToWinsize>( - config: &Config, - options: &Options, - size: &T, - window_id: Option<usize>, -) -> Pty { - let win_size = size.to_winsize(); - let mut buf = [0; 1024]; - let pw = get_pw_entry(&mut buf); - - let (master, slave) = make_pty(win_size); - - let default_shell = if cfg!(target_os = "macos") { - let shell_name = pw.shell.rsplit('/').next().unwrap(); - let argv = vec![String::from("-c"), format!("exec -a -{} {}", shell_name, pw.shell)]; - - Shell::new_with_args("/bin/bash", argv) - } else { - Shell::new(pw.shell) - }; - let shell = config.shell().unwrap_or(&default_shell); - - let initial_command = options.command().unwrap_or(shell); - - let mut builder = Command::new(initial_command.program()); - for arg in initial_command.args() { - builder.arg(arg); - } - - // Setup child stdin/stdout/stderr as slave fd of pty - // Ownership of fd is transferred to the Stdio structs and will be closed by them at the end of - // this scope. (It is not an issue that the fd is closed three times since File::drop ignores - // error on libc::close.) - builder.stdin(unsafe { Stdio::from_raw_fd(slave) }); - builder.stderr(unsafe { Stdio::from_raw_fd(slave) }); - builder.stdout(unsafe { Stdio::from_raw_fd(slave) }); - - // Setup shell environment - builder.env("LOGNAME", pw.name); - builder.env("USER", pw.name); - builder.env("SHELL", pw.shell); - builder.env("HOME", pw.dir); - - if let Some(window_id) = window_id { - builder.env("WINDOWID", format!("{}", window_id)); - } - - builder.before_exec(move || { - // Create a new process group - unsafe { - let err = libc::setsid(); - if err == -1 { - die!("Failed to set session id: {}", errno()); - } - } - - set_controlling_terminal(slave); - - // No longer need slave/master fds - unsafe { - libc::close(slave); - libc::close(master); - } - - 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); - } - Ok(()) - }); - - // Handle set working directory option - if let Some(ref dir) = options.working_dir { - builder.current_dir(dir.as_path()); - } - - // Prepare signal handling before spawning child - let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling"); - - match builder.spawn() { - Ok(child) => { - // Remember child PID so other modules can use it - PID.store(child.id() as usize, Ordering::Relaxed); - - unsafe { - // Maybe this should be done outside of this function so nonblocking - // isn't forced upon consumers. Although maybe it should be? - set_nonblocking(master); - } - - let pty = Pty { - child, - fd: unsafe { File::from_raw_fd(master) }, - token: mio::Token::from(0), - signals, - signals_token: mio::Token::from(0), - }; - pty.resize(size); - pty - }, - Err(err) => { - die!("Failed to spawn command: {}", err); - }, - } -} - -impl EventedReadWrite for Pty { - type Reader = File; - type Writer = File; - - #[inline] - fn register( - &mut self, - poll: &mio::Poll, - token: &mut dyn Iterator<Item = mio::Token>, - interest: mio::Ready, - poll_opts: mio::PollOpt, - ) -> io::Result<()> { - self.token = token.next().unwrap(); - poll.register(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?; - - self.signals_token = token.next().unwrap(); - poll.register( - &self.signals, - self.signals_token, - mio::Ready::readable(), - mio::PollOpt::level(), - ) - } - - #[inline] - fn reregister( - &mut self, - poll: &mio::Poll, - interest: mio::Ready, - poll_opts: mio::PollOpt, - ) -> io::Result<()> { - poll.reregister(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?; - - poll.reregister( - &self.signals, - self.signals_token, - mio::Ready::readable(), - mio::PollOpt::level(), - ) - } - - #[inline] - fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { - poll.deregister(&EventedFd(&self.fd.as_raw_fd()))?; - poll.deregister(&self.signals) - } - - #[inline] - fn reader(&mut self) -> &mut File { - &mut self.fd - } - - #[inline] - fn read_token(&self) -> mio::Token { - self.token - } - - #[inline] - fn writer(&mut self) -> &mut File { - &mut self.fd - } - - #[inline] - fn write_token(&self) -> mio::Token { - self.token - } -} - -impl EventedPty for Pty { - #[inline] - fn next_child_event(&mut self) -> Option<ChildEvent> { - self.signals.pending().next().and_then(|signal| { - if signal != sighook::SIGCHLD { - return None; - } - - match self.child.try_wait() { - Err(e) => { - error!("Error checking child process termination: {}", e); - None - }, - Ok(None) => None, - Ok(_) => Some(ChildEvent::Exited), - } - }) - } - - #[inline] - fn child_event_token(&self) -> mio::Token { - self.signals_token - } -} - -pub fn process_should_exit() -> bool { - false -} - -/// Types that can produce a `libc::winsize` -pub trait ToWinsize { - /// Get a `libc::winsize` - fn to_winsize(&self) -> winsize; -} - -impl<'a> ToWinsize for &'a SizeInfo { - fn to_winsize(&self) -> winsize { - winsize { - ws_row: self.lines().0 as libc::c_ushort, - ws_col: self.cols().0 as libc::c_ushort, - ws_xpixel: self.width as libc::c_ushort, - ws_ypixel: self.height as libc::c_ushort, - } - } -} - -impl OnResize for i32 { - fn on_resize(&mut self, size: &SizeInfo) { - let win = size.to_winsize(); - - let res = unsafe { libc::ioctl(*self, libc::TIOCSWINSZ, &win as *const _) }; - - if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", errno()); - } - } -} - -unsafe fn set_nonblocking(fd: c_int) { - use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; - - let res = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - assert_eq!(res, 0); -} - -#[test] -fn test_get_pw_entry() { - let mut buf: [i8; 1024] = [0; 1024]; - let _pw = get_pw_entry(&mut buf); -} |