diff options
author | spacemaison <tuchsen@protonmail.com> | 2021-09-10 08:35:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-10 17:35:06 +0200 |
commit | 4f94f95c90a0b5c2cb3992533cec40cc55f05983 (patch) | |
tree | 006824300f43717d08862eeb8ebb08c151dd62c4 /zellij-server/src/os_input_output.rs | |
parent | 26a970a7d8a0f3703124ab3bb57cff4d296a2e33 (diff) |
feat(cwd-pane): Keeping the cwd when opening new panes (#691)
* feat(cwd-pane): Add a new trait to get the cwd of a given pid
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
* feat(cwd-pane): Allow for setting the cwd when spawning a new terminal
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
* feat(cwd-pane): Add an active_pane field to the Pty struct
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
* feat(cwd-pane): Update Pty with Tab's active pane id
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
* feat(cwd-pane): Refactor spawn_terminal to use cwd by default
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
* feat(cwd-pane): Fix tests and lints
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
* feat(cwd-pane): Fix formatting
* feat(cwd-pane): Refactor child pid fetching to handle errors better
Instead of panicking when transfering the process id of the forked child
command we just return an empty process id.
* feat(cwd-pane): Add non Linux/MacOS targets for the get_cwd method.
This will allow Zellij to compile on non Linux/MacOS targets without
having an inherited cwd.
* feat(cwd-pane): Refactor spawn_terminal method to use ChildId struct.
The spawn_terminal methods been refactored to use the ChildId struct in
order to clarify what the Pid's returned by it are. The documentation
for the ChildId struct was improved as well.
* feat(cwd-pane): Fix tests/lints
Co-authored-by: Jesse Tuchsen <not@disclosing>
Co-authored-by: Quentin Rasmont <qrasmont@gmail.com>
Diffstat (limited to 'zellij-server/src/os_input_output.rs')
-rw-r--r-- | zellij-server/src/os_input_output.rs | 177 |
1 files changed, 128 insertions, 49 deletions
diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 05980805f..e24b24b41 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -1,4 +1,8 @@ +#[cfg(target_os = "macos")] +use darwin_libproc; + use std::env; +use std::fs; use std::os::unix::io::RawFd; use std::os::unix::process::CommandExt; use std::path::PathBuf; @@ -10,7 +14,7 @@ use zellij_utils::{async_std, interprocess, libc, nix, signal_hook, zellij_tile} use async_std::fs::File as AsyncFile; use async_std::os::unix::io::FromRawFd; use interprocess::local_socket::LocalSocketStream; -use nix::pty::{forkpty, Winsize}; +use nix::pty::{forkpty, ForkptyResult, Winsize}; use nix::sys::signal::{kill, Signal}; use nix::sys::termios; use nix::sys::wait::waitpid; @@ -29,6 +33,7 @@ use zellij_utils::{ use async_std::io::ReadExt; pub use async_trait::async_trait; +use byteorder::{BigEndian, ByteOrder}; pub use nix::unistd::Pid; @@ -92,44 +97,94 @@ fn handle_command_exit(mut child: Child) { } } +fn handle_fork_pty( + fork_pty_res: ForkptyResult, + cmd: RunCommand, + parent_fd: RawFd, + child_fd: RawFd, +) -> (RawFd, ChildId) { + let pid_primary = fork_pty_res.master; + let (pid_secondary, pid_shell) = match fork_pty_res.fork_result { + ForkResult::Parent { child } => { + let pid_shell = read_from_pipe(parent_fd, child_fd); + (child, pid_shell) + } + ForkResult::Child => { + let child = unsafe { + let command = &mut Command::new(cmd.command); + if let Some(current_dir) = cmd.cwd { + command.current_dir(current_dir); + } + command + .args(&cmd.args) + .pre_exec(|| -> std::io::Result<()> { + // this is the "unsafe" part, for more details please see: + // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety + unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) + .expect("failed to create a new process group"); + Ok(()) + }) + .spawn() + .expect("failed to spawn") + }; + unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) + .expect("faled to set child's forceground process group"); + write_to_pipe(child.id(), parent_fd, child_fd); + handle_command_exit(child); + ::std::process::exit(0); + } + }; + + ( + pid_primary, + ChildId { + primary: pid_secondary, + shell: pid_shell.map(|pid| Pid::from_raw(pid as i32)), + }, + ) +} + /// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) /// `orig_termios`. /// -fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, Pid) { - let (pid_primary, pid_secondary): (RawFd, Pid) = { - match forkpty(None, Some(&orig_termios)) { - Ok(fork_pty_res) => { - let pid_primary = fork_pty_res.master; - let pid_secondary = match fork_pty_res.fork_result { - ForkResult::Parent { child } => child, - ForkResult::Child => { - let child = unsafe { - Command::new(cmd.command) - .args(&cmd.args) - .pre_exec(|| -> std::io::Result<()> { - // this is the "unsafe" part, for more details please see: - // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety - unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) - .expect("failed to create a new process group"); - Ok(()) - }) - .spawn() - .expect("failed to spawn") - }; - unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) - .expect("faled to set child's forceground process group"); - handle_command_exit(child); - ::std::process::exit(0); - } - }; - (pid_primary, pid_secondary) - } - Err(e) => { - panic!("failed to fork {:?}", e); - } +fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, ChildId) { + // Create a pipe to allow the child the communicate the shell's pid to it's + // parent. + let (parent_fd, child_fd) = unistd::pipe().expect("failed to create pipe"); + match forkpty(None, Some(&orig_termios)) { + Ok(fork_pty_res) => handle_fork_pty(fork_pty_res, cmd, parent_fd, child_fd), + Err(e) => { + panic!("failed to fork {:?}", e); } - }; - (pid_primary, pid_secondary) + } +} + +/// Write to a pipe given both file descriptors +fn write_to_pipe(data: u32, parent_fd: RawFd, child_fd: RawFd) { + let mut buff = [0; 4]; + BigEndian::write_u32(&mut buff, data); + if unistd::close(parent_fd).is_err() { + return; + } + if unistd::write(child_fd, &buff).is_err() { + return; + } + unistd::close(child_fd).unwrap_or_default(); +} + +/// Read from a pipe given both file descriptors +fn read_from_pipe(parent_fd: RawFd, child_fd: RawFd) -> Option<u32> { + let mut buffer = [0; 4]; + if unistd::close(child_fd).is_err() { + return None; + } + if unistd::read(parent_fd, &mut buffer).is_err() { + return None; + } + if unistd::close(parent_fd).is_err() { + return None; + } + Some(u32::from_be_bytes(buffer)) } /// If a [`TerminalAction::OpenFile(file)`] is given, the text editor specified by environment variable `EDITOR` @@ -145,11 +200,11 @@ fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, P /// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not /// set. pub fn spawn_terminal( - terminal_action: Option<TerminalAction>, + terminal_action: TerminalAction, orig_termios: termios::Termios, -) -> (RawFd, Pid) { +) -> (RawFd, ChildId) { let cmd = match terminal_action { - Some(TerminalAction::OpenFile(file_to_open)) => { + TerminalAction::OpenFile(file_to_open) => { if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); } @@ -160,15 +215,13 @@ pub fn spawn_terminal( .into_os_string() .into_string() .expect("Not valid Utf8 Encoding")]; - RunCommand { command, args } - } - Some(TerminalAction::RunCommand(command)) => command, - None => { - let command = - PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")); - let args = vec![]; - RunCommand { command, args } + RunCommand { + command, + args, + cwd: None, + } } + TerminalAction::RunCommand(command) => command, }; handle_terminal(cmd, orig_termios) @@ -214,8 +267,10 @@ impl AsyncReader for RawFdAsyncReader { pub trait ServerOsApi: Send + Sync { /// Sets the size of the terminal associated to file descriptor `fd`. fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16); - /// Spawn a new terminal, with a terminal action. - fn spawn_terminal(&self, terminal_action: Option<TerminalAction>) -> (RawFd, Pid); + /// Spawn a new terminal, with a terminal action. The returned tuple contains the master file + /// descriptor of the forked psuedo terminal and a [ChildId] struct containing process id's for + /// the forked child process. + fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId); /// Read bytes from the standard output of the virtual terminal referred to by `fd`. fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>; /// Creates an `AsyncReader` that can be used to read from `fd` in an async context @@ -247,6 +302,8 @@ pub trait ServerOsApi: Send + Sync { /// Update the receiver socket for the client fn update_receiver(&mut self, stream: LocalSocketStream); fn load_palette(&self) -> Palette; + /// Returns the current working directory for a given pid + fn get_cwd(&self, pid: Pid) -> Option<PathBuf>; } impl ServerOsApi for ServerOsInputOutput { @@ -255,7 +312,7 @@ impl ServerOsApi for ServerOsInputOutput { set_terminal_size_using_fd(fd, cols, rows); } } - fn spawn_terminal(&self, terminal_action: Option<TerminalAction>) -> (RawFd, Pid) { + fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId) { let orig_termios = self.orig_termios.lock().unwrap(); spawn_terminal(terminal_action, orig_termios.clone()) } @@ -336,6 +393,18 @@ impl ServerOsApi for ServerOsInputOutput { fn load_palette(&self) -> Palette { default_palette() } + #[cfg(target_os = "macos")] + fn get_cwd(&self, pid: Pid) -> Option<PathBuf> { + darwin_libproc::pid_cwd(pid.as_raw()).ok() + } + #[cfg(target_os = "linux")] + fn get_cwd(&self, pid: Pid) -> Option<PathBuf> { + fs::read_link(format!("/proc/{}/cwd", pid)).ok() + } + #[cfg(all(not(target_os = "linux"), not(target_os = "macos")))] + fn get_cwd(&self, _pid: Pid) -> Option<PathBuf> { + None + } } impl Clone for Box<dyn ServerOsApi> { @@ -353,3 +422,13 @@ pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> { send_instructions_to_client: Arc::new(Mutex::new(None)), }) } + +/// Process id's for forked terminals +#[derive(Debug)] +pub struct ChildId { + /// Primary process id of a forked terminal + pub primary: Pid, + /// Process id of the command running inside the forked terminal, usually a shell. The primary + /// field is it's parent process id. + pub shell: Option<Pid>, +} |