├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── examples ├── main.rs └── new.rs ├── rustfmt.toml ├── src ├── descriptor │ ├── err.rs │ └── mod.rs ├── fork │ ├── err.rs │ ├── mod.rs │ └── pty │ │ ├── master │ │ ├── err.rs │ │ └── mod.rs │ │ ├── mod.rs │ │ └── slave │ │ ├── err.rs │ │ └── mod.rs ├── lib.rs └── prelude.rs └── tests ├── it_can_read_write.rs ├── it_fork_with_new_pty.rs └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | Cargo.lock 13 | 14 | # Swap 15 | *.rs# 16 | *.rs.swp 17 | *.swp 18 | *.swo 19 | 20 | # Temp files 21 | .*~ 22 | 23 | # Backup files 24 | *.bak 25 | *.bk 26 | *.orig 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | rust: 4 | - nightly 5 | - stable 6 | - beta 7 | 8 | branches: 9 | except: 10 | - gh-pages 11 | 12 | # Only while clippy is failing 13 | matrix: 14 | allow_failures: 15 | - rust: nightly 16 | - os: osx 17 | env: 18 | global: 19 | - PATH="$PATH:$TRAVIS_BUILD_DIR/target/debug" 20 | - RUST_BACKTRACE="1" 21 | cache: 22 | directories: 23 | - $TRAVIS_BUILD_DIR/target 24 | - $HOME/.cargo 25 | 26 | os: 27 | - linux 28 | - osx 29 | 30 | script: 31 | - cargo build --verbose 32 | - cargo test 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.2.0 2 | * Improve the Error Handling. 3 | * Improve the POO representation. 4 | * Remove the **posix_openpt**(3) call. 5 | 6 | ### 0.1.6 7 | 8 | * [#5](https://github.com/hibariya/pty-rs/pull/5) Improve error representation 9 | 10 | ### 0.1.5 11 | 12 | * API Change: [#3 Remove unnecessary `Copy` trait](https://github.com/hibariya/pty-rs/pull/3) 13 | * Mark `Child#pty` as private, add public `Child#pty()`. 14 | * Remove `Copy` trait from `Child` and `ChildPTY`. 15 | * Remove `ChildPTY#fd()`, impl `AsRawFd` for `ChildPTY`. 16 | * Bug fix: [#4 Continue wait if it's still alive](https://github.com/hibariya/pty-rs/pull/4) 17 | 18 | ### 0.1.4 19 | 20 | * API Change: [#2](https://github.com/hibariya/pty-rs/pull/2) Make `pty::fork()` return a single value. 21 | 22 | ### 0.1.3 23 | 24 | * Support stable rust 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pty" 3 | version = "0.2.2" 4 | authors = ["Hika Hibariya "] 5 | repository = "https://github.com/hibariya/pty-rs" 6 | homepage = "https://github.com/hibariya/pty-rs" 7 | license = "MIT" 8 | description = "Fork with new pseudo-terminal (PTY)" 9 | keywords = ["pty", "tty", "pseudo", "terminal", "fork"] 10 | 11 | [features] 12 | default = [] 13 | lints = ["clippy", "nightly"] 14 | nightly = [] # for building with nightly and unstable features 15 | unstable = [] # for building with unstable features on stable Rust 16 | debug = [] # for building with debug messages 17 | travis = ["lints", "nightly"] # for building with travis-cargo 18 | 19 | [dependencies.errno] 20 | version = "0.1" 21 | 22 | [dependencies.libc] 23 | version = ">= 0.2.18" 24 | 25 | [dependencies.clippy] 26 | version = "0.0" 27 | optional = true 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hika Hibariya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PTY 2 | [![Crate][crate-badge]][crate] [![docs-badge][]][docs] [![license-badge][]][license] [![travis-badge][]][travis] 3 | 4 | [crate-badge]: https://img.shields.io/badge/crates.io-v0.2.0-orange.svg?style=flat-square 5 | [crate]: https://crates.io/crates/pty 6 | 7 | [docs-badge]: https://img.shields.io/badge/API-docs-blue.svg?style=flat-square 8 | [docs]: http://note.hibariya.org/pty-rs/pty/index.html 9 | 10 | [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square 11 | [license]: https://github.com/hibariya/pty-rs/blob/master/LICENSE.txt 12 | 13 | [travis-badge]: https://travis-ci.org/hibariya/pty-rs.svg?branch=master&style=flat-square 14 | [travis]: https://travis-ci.org/hibariya/pty-rs 15 | 16 | The `pty` crate provides `pty::fork()`. That makes a parent process fork with new pseudo-terminal (PTY). 17 | 18 | This crate depends on followings: 19 | 20 | * `libc` library 21 | * POSIX environment 22 | 23 | ## Usage 24 | 25 | Add this to your `Cargo.toml`: 26 | 27 | ```toml 28 | [dependencies] 29 | pty = "0.2" 30 | ``` 31 | 32 | and this to your crate root: 33 | 34 | ```rust 35 | extern crate pty; 36 | ``` 37 | 38 | ### pty::fork() 39 | 40 | This function returns `pty::Child`. It represents the child process and its PTY. 41 | 42 | For example, the following code spawns `tty(1)` command by `pty::fork()` and outputs the result of the command. 43 | 44 | ```rust 45 | extern crate pty; 46 | extern crate libc; 47 | 48 | use std::ffi::CString; 49 | use std::io::Read; 50 | use std::ptr; 51 | 52 | use pty::fork::*; 53 | 54 | fn main() { 55 | let fork = Fork::from_ptmx().unwrap(); 56 | 57 | if let Some(mut master) = fork.is_parent().ok() { 58 | // Read output via PTY master 59 | let mut output = String::new(); 60 | 61 | match master.read_to_string(&mut output) { 62 | Ok(_nread) => println!("child tty is: {}", output.trim()), 63 | Err(e) => panic!("read error: {}", e), 64 | } 65 | } 66 | else { 67 | // Child process just exec `tty` 68 | Command::new("tty").status().expect("could not execute tty"); 69 | } 70 | } 71 | ``` 72 | 73 | When run this, we get new PTY in the child process. 74 | 75 | ``` 76 | $ tty 77 | /dev/pts/5 78 | $ cargo run 79 | Running `target/debug/example` 80 | child tty is: /dev/pts/8 81 | ``` 82 | 83 | ## Documentation 84 | 85 | API documentation for latest version: http://hibariya.github.io/pty-rs/pty/index.html 86 | 87 | ## Contributing 88 | 89 | 1. Fork it ( https://github.com/hibariya/pty-rs/fork ) 90 | 2. Create your feature branch (`git checkout -b my-new-feature`) 91 | 3. Commit your changes (`git commit -am 'Add some feature'`) 92 | 4. Push to the branch (`git push origin my-new-feature`) 93 | 5. Create a new Pull Request 94 | 95 | ## License 96 | 97 | Copyright (c) 2015 Hika Hibariya 98 | 99 | Distributed under the [MIT License](LICENSE.txt). 100 | -------------------------------------------------------------------------------- /examples/main.rs: -------------------------------------------------------------------------------- 1 | extern crate pty; 2 | extern crate libc; 3 | extern crate errno; 4 | 5 | use pty::fork::*; 6 | use std::io::Read; 7 | use std::process::{Command}; 8 | 9 | fn main() { 10 | let fork = Fork::from_ptmx().unwrap(); 11 | 12 | if let Some(mut master) = fork.is_parent().ok() { 13 | // Read output via PTY master 14 | let mut output = String::new(); 15 | 16 | match master.read_to_string(&mut output) { 17 | Ok(_nread) => println!("child tty is: {}", output.trim()), 18 | Err(e) => panic!("read error: {}", e), 19 | } 20 | } else { 21 | // Child process just exec `tty` 22 | Command::new("tty").status().expect("could not execute tty"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/new.rs: -------------------------------------------------------------------------------- 1 | extern crate pty; 2 | extern crate libc; 3 | 4 | use self::pty::prelude::*; 5 | 6 | use std::io::prelude::*; 7 | use std::process::{Command, Stdio}; 8 | 9 | fn main() { 10 | let fork = Fork::from_ptmx().unwrap(); 11 | 12 | if let Some(mut master) = fork.is_parent().ok() { 13 | let mut string = String::new(); 14 | 15 | master.read_to_string(&mut string).unwrap_or_else(|e| panic!(e)); 16 | 17 | let output = Command::new("tty") 18 | .stdin(Stdio::inherit()) 19 | .output() 20 | .unwrap() 21 | .stdout; 22 | let output_str = String::from_utf8_lossy(&output); 23 | 24 | let parent_tty = output_str.trim(); 25 | let child_tty = string.trim(); 26 | 27 | println!("child_tty(\"{}\")[{}] != \"{}\" => {}", child_tty, child_tty.len(), "", child_tty != ""); 28 | assert!(child_tty != ""); 29 | assert!(child_tty != parent_tty); 30 | 31 | let mut parent_tty_dir: Vec<&str> = parent_tty.split("/").collect(); 32 | let mut child_tty_dir: Vec<&str> = child_tty.split("/").collect(); 33 | 34 | parent_tty_dir.pop(); 35 | child_tty_dir.pop(); 36 | 37 | assert_eq!(parent_tty_dir, child_tty_dir); 38 | } else { 39 | let _ = Command::new("tty").status(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tabs_spaces=4 2 | hard_tabs=false 3 | -------------------------------------------------------------------------------- /src/descriptor/err.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | /// The alias `Result` learns `DescriptorError` possibility. 5 | 6 | pub type Result = ::std::result::Result; 7 | 8 | /// The enum `DescriptorError` defines the possible errors 9 | /// from constructor Descriptor. 10 | #[derive(Clone, Copy, Debug)] 11 | pub enum DescriptorError { 12 | /// Can't open. 13 | OpenFail, 14 | /// Can't closed. 15 | CloseFail, 16 | } 17 | 18 | impl fmt::Display for DescriptorError { 19 | /// The function `fmt` formats the value using the given formatter. 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | write!(f, "{}", ::errno::errno()) 22 | } 23 | } 24 | 25 | impl Error for DescriptorError { 26 | /// The function `description` returns a short description of the error. 27 | fn description(&self) -> &str { 28 | match *self { 29 | DescriptorError::OpenFail => "can't open the fd", 30 | DescriptorError::CloseFail => "can't close the fd", 31 | } 32 | } 33 | 34 | /// The function `cause` returns the lower-level cause of this error, if any. 35 | 36 | fn cause(&self) -> Option<&Error> { 37 | None 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/descriptor/mod.rs: -------------------------------------------------------------------------------- 1 | mod err; 2 | 3 | use ::libc; 4 | 5 | pub use self::err::DescriptorError; 6 | use std::os::unix::io::{AsRawFd, RawFd}; 7 | 8 | pub trait Descriptor: AsRawFd { 9 | /// The constructor function `open` opens the path 10 | /// and returns the fd. 11 | fn open(path: *const libc::c_char, 12 | flag: libc::c_int, 13 | mode: Option) 14 | -> Result { 15 | unsafe { 16 | match libc::open(path, flag, mode.unwrap_or_default()) { 17 | -1 => Err(DescriptorError::OpenFail), 18 | fd => Ok(fd), 19 | } 20 | } 21 | } 22 | 23 | /// The function `close` leaves the fd. 24 | fn close(&self) -> Result<(), DescriptorError> { 25 | unsafe { 26 | match libc::close(self.as_raw_fd()) { 27 | -1 => Err(DescriptorError::CloseFail), 28 | _ => Ok(()), 29 | } 30 | } 31 | } 32 | 33 | /// The destructor function `drop` call the method `close` 34 | /// and panic if a error is occurred. 35 | fn drop(&self) { 36 | if self.close().is_err() { 37 | unimplemented!(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/fork/err.rs: -------------------------------------------------------------------------------- 1 | use ::descriptor::DescriptorError; 2 | use std::error::Error; 3 | use std::fmt; 4 | 5 | use super::pty::{MasterError, SlaveError}; 6 | 7 | /// The alias `Result` learns `ForkError` possibility. 8 | 9 | pub type Result = ::std::result::Result; 10 | 11 | /// The enum `ForkError` defines the possible errors from constructor Fork. 12 | #[derive(Clone, Copy, Debug)] 13 | pub enum ForkError { 14 | /// Can't creates the child. 15 | Failure, 16 | /// Can't set the id group. 17 | SetsidFail, 18 | /// Can't suspending the calling process. 19 | WaitpidFail, 20 | /// Is child and not parent. 21 | IsChild, 22 | /// Is parent and not child. 23 | IsParent, 24 | /// The Master occured a error. 25 | BadMaster(MasterError), 26 | /// The Slave occured a error. 27 | BadSlave(SlaveError), 28 | /// The Master's Descriptor occured a error. 29 | BadDescriptorMaster(DescriptorError), 30 | /// The Slave's Descriptor occured a error. 31 | BadDescriptorSlave(DescriptorError), 32 | } 33 | 34 | impl fmt::Display for ForkError { 35 | /// The function `fmt` formats the value using the given formatter. 36 | 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | write!(f, "{}", ::errno::errno()) 39 | } 40 | } 41 | 42 | impl Error for ForkError { 43 | /// The function `description` returns a short description of the error. 44 | 45 | fn description(&self) -> &str { 46 | match *self { 47 | ForkError::Failure => { 48 | "On failure, -1 is returned in the parent,no child process is created, and errno \ 49 | isset appropriately." 50 | } 51 | ForkError::SetsidFail => { 52 | "fails if the calling process is alreadya process group leader." 53 | } 54 | ForkError::WaitpidFail => "Can't suspending the calling process.", 55 | ForkError::IsChild => "is child and not parent", 56 | ForkError::IsParent => "is parent and not child", 57 | ForkError::BadMaster(_) => "the master as occured an error", 58 | ForkError::BadSlave(_) => "the slave as occured an error", 59 | ForkError::BadDescriptorMaster(_) => "the master's descriptor as occured an error", 60 | ForkError::BadDescriptorSlave(_) => "the slave's descriptor as occured an error", 61 | 62 | } 63 | } 64 | 65 | /// The function `cause` returns the lower-level cause of this error, if any. 66 | 67 | fn cause(&self) -> Option<&Error> { 68 | match *self { 69 | ForkError::BadMaster(ref err) => Some(err), 70 | ForkError::BadSlave(ref err) => Some(err), 71 | ForkError::BadDescriptorMaster(ref err) => Some(err), 72 | ForkError::BadDescriptorSlave(ref err) => Some(err), 73 | _ => None, 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/fork/mod.rs: -------------------------------------------------------------------------------- 1 | mod pty; 2 | mod err; 3 | 4 | use ::descriptor::Descriptor; 5 | 6 | use ::libc; 7 | pub use self::err::{ForkError, Result}; 8 | pub use self::pty::{Master, MasterError}; 9 | pub use self::pty::{Slave, SlaveError}; 10 | use std::ffi::CString; 11 | 12 | #[derive(Debug)] 13 | pub enum Fork { 14 | // Parent child's pid and master's pty. 15 | Parent(libc::pid_t, Master), 16 | // Child pid 0. 17 | Child(Slave), 18 | } 19 | 20 | impl Fork { 21 | /// The constructor function `new` forks the program 22 | /// and returns the current pid. 23 | pub fn new(path: &'static str) -> Result { 24 | match Master::new(CString::new(path).ok().unwrap_or_default().as_ptr()) { 25 | Err(cause) => Err(ForkError::BadMaster(cause)), 26 | Ok(master) => unsafe { 27 | if let Some(cause) = master.grantpt().err().or(master.unlockpt().err()) { 28 | Err(ForkError::BadMaster(cause)) 29 | } else { 30 | match libc::fork() { 31 | -1 => Err(ForkError::Failure), 32 | 0 => { 33 | match master.ptsname() { 34 | Err(cause) => Err(ForkError::BadMaster(cause)), 35 | Ok(name) => Fork::from_pts(name), 36 | } 37 | } 38 | pid => Ok(Fork::Parent(pid, master)), 39 | } 40 | } 41 | }, 42 | } 43 | } 44 | 45 | /// The constructor function `from_pts` is a private 46 | /// extention from the constructor function `new` who 47 | /// prepares and returns the child. 48 | fn from_pts(ptsname: *const ::libc::c_char) -> Result { 49 | unsafe { 50 | if libc::setsid() == -1 { 51 | Err(ForkError::SetsidFail) 52 | } else { 53 | match Slave::new(ptsname) { 54 | Err(cause) => Err(ForkError::BadSlave(cause)), 55 | Ok(slave) => { 56 | if let Some(cause) = slave.dup2(libc::STDIN_FILENO) 57 | .err() 58 | .or(slave.dup2(libc::STDOUT_FILENO) 59 | .err() 60 | .or(slave.dup2(libc::STDERR_FILENO).err())) { 61 | Err(ForkError::BadSlave(cause)) 62 | } else { 63 | Ok(Fork::Child(slave)) 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | /// The constructor function `from_ptmx` forks the program 72 | /// and returns the current pid for a default PTMX's path. 73 | pub fn from_ptmx() -> Result { 74 | Fork::new(::DEFAULT_PTMX) 75 | } 76 | 77 | /// Waits until it's terminated. 78 | pub fn wait(&self) -> Result { 79 | match *self { 80 | Fork::Child(_) => Err(ForkError::IsChild), 81 | Fork::Parent(pid, _) => { 82 | loop { 83 | unsafe { 84 | match libc::waitpid(pid, &mut 0, 0) { 85 | 0 => continue, 86 | -1 => return Err(ForkError::WaitpidFail), 87 | _ => return Ok(pid), 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | /// The function `is_parent` returns the pid or parent 96 | /// or none. 97 | pub fn is_parent(&self) -> Result { 98 | match *self { 99 | Fork::Child(_) => Err(ForkError::IsChild), 100 | Fork::Parent(_, ref master) => Ok(master.clone()), 101 | } 102 | } 103 | 104 | /// The function `is_child` returns the pid or child 105 | /// or none. 106 | pub fn is_child(&self) -> Result<&Slave> { 107 | match *self { 108 | Fork::Parent(_, _) => Err(ForkError::IsParent), 109 | Fork::Child(ref slave) => Ok(slave), 110 | } 111 | } 112 | } 113 | 114 | impl Drop for Fork { 115 | fn drop(&mut self) { 116 | match *self { 117 | Fork::Parent(_, ref master) => Descriptor::drop(master), 118 | _ => {} 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/fork/pty/master/err.rs: -------------------------------------------------------------------------------- 1 | use ::descriptor::DescriptorError; 2 | use std::error::Error; 3 | use std::fmt; 4 | 5 | /// The alias `Result` learns `MasterError` possibility. 6 | 7 | pub type Result = ::std::result::Result; 8 | 9 | /// The enum `MasterError` defines the possible errors from constructor Master. 10 | #[derive(Clone, Copy, Debug)] 11 | pub enum MasterError { 12 | BadDescriptor(DescriptorError), 13 | GrantptError, 14 | UnlockptError, 15 | PtsnameError, 16 | } 17 | 18 | impl fmt::Display for MasterError { 19 | /// The function `fmt` formats the value using the given formatter. 20 | 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | write!(f, "{}", ::errno::errno()) 23 | } 24 | } 25 | 26 | impl Error for MasterError { 27 | /// The function `description` returns a short description of the error. 28 | 29 | fn description(&self) -> &str { 30 | match *self { 31 | MasterError::BadDescriptor(_) => "the descriptor as occured an error", 32 | MasterError::GrantptError => "the `grantpt` has a error, errnois set appropriately.", 33 | MasterError::UnlockptError => "the `grantpt` has a error, errnois set appropriately.", 34 | MasterError::PtsnameError => "the `ptsname` has a error", 35 | 36 | } 37 | } 38 | 39 | /// The function `cause` returns the lower-level cause of this error, if any. 40 | 41 | fn cause(&self) -> Option<&Error> { 42 | match *self { 43 | MasterError::BadDescriptor(ref err) => Some(err), 44 | _ => None, 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/fork/pty/master/mod.rs: -------------------------------------------------------------------------------- 1 | mod err; 2 | 3 | use libc; 4 | 5 | use ::descriptor::Descriptor; 6 | 7 | pub use self::err::{MasterError, Result}; 8 | use std::io; 9 | use std::os::unix::io::{AsRawFd, RawFd}; 10 | 11 | #[derive(Debug, Copy, Clone)] 12 | pub struct Master { 13 | pty: RawFd, 14 | } 15 | 16 | impl Master { 17 | pub fn new(path: *const ::libc::c_char) -> Result { 18 | match Self::open(path, libc::O_RDWR, None) { 19 | Err(cause) => Err(MasterError::BadDescriptor(cause)), 20 | Ok(fd) => Ok(Master { pty: fd }), 21 | } 22 | } 23 | 24 | /// Change UID and GID of slave pty associated with master pty whose 25 | /// fd is provided, to the real UID and real GID of the calling thread. 26 | pub fn grantpt(&self) -> Result { 27 | unsafe { 28 | match libc::grantpt(self.as_raw_fd()) { 29 | -1 => Err(MasterError::GrantptError), 30 | c => Ok(c), 31 | } 32 | } 33 | } 34 | 35 | /// Unlock the slave pty associated with the master to which fd refers. 36 | pub fn unlockpt(&self) -> Result { 37 | unsafe { 38 | match libc::unlockpt(self.as_raw_fd()) { 39 | -1 => Err(MasterError::UnlockptError), 40 | c => Ok(c), 41 | } 42 | } 43 | } 44 | 45 | /// Returns a pointer to a static buffer, which will be overwritten on 46 | /// subsequent calls. 47 | pub fn ptsname(&self) -> Result<*const libc::c_char> { 48 | unsafe { 49 | match libc::ptsname(self.as_raw_fd()) { 50 | c if c.is_null() => Err(MasterError::PtsnameError), 51 | c => Ok(c), 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl Descriptor for Master {} 58 | 59 | impl AsRawFd for Master { 60 | /// The accessor function `as_raw_fd` returns the fd. 61 | fn as_raw_fd(&self) -> RawFd { 62 | self.pty 63 | } 64 | } 65 | 66 | impl io::Read for Master { 67 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 68 | unsafe { 69 | match libc::read(self.as_raw_fd(), 70 | buf.as_mut_ptr() as *mut libc::c_void, 71 | buf.len()) { 72 | -1 => Ok(0), 73 | len => Ok(len as usize), 74 | } 75 | } 76 | } 77 | } 78 | 79 | impl io::Write for Master { 80 | fn write(&mut self, buf: &[u8]) -> io::Result { 81 | unsafe { 82 | match libc::write(self.as_raw_fd(), 83 | buf.as_ptr() as *const libc::c_void, 84 | buf.len()) { 85 | -1 => Err(io::Error::last_os_error()), 86 | ret => Ok(ret as usize), 87 | } 88 | } 89 | } 90 | 91 | fn flush(&mut self) -> io::Result<()> { 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/fork/pty/mod.rs: -------------------------------------------------------------------------------- 1 | mod master; 2 | mod slave; 3 | 4 | pub use self::master::{Master, MasterError}; 5 | pub use self::slave::{Slave, SlaveError}; 6 | -------------------------------------------------------------------------------- /src/fork/pty/slave/err.rs: -------------------------------------------------------------------------------- 1 | use ::descriptor::DescriptorError; 2 | use std::error::Error; 3 | use std::fmt; 4 | 5 | /// The alias `Result` learns `SlaveError` possibility. 6 | 7 | pub type Result = ::std::result::Result; 8 | 9 | /// The enum `SlaveError` defines the possible errors from constructor Slave. 10 | #[derive(Clone, Copy, Debug)] 11 | pub enum SlaveError { 12 | BadDescriptor(DescriptorError), 13 | Dup2Error, 14 | } 15 | 16 | impl fmt::Display for SlaveError { 17 | /// The function `fmt` formats the value using the given formatter. 18 | 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | write!(f, "{}", ::errno::errno()) 21 | } 22 | } 23 | 24 | impl Error for SlaveError { 25 | /// The function `description` returns a short description of the error. 26 | 27 | fn description(&self) -> &str { 28 | match *self { 29 | SlaveError::BadDescriptor(_) => "the descriptor as occured an error", 30 | SlaveError::Dup2Error => "the `dup2` has a error, errno isset appropriately.", 31 | } 32 | } 33 | 34 | /// The function `cause` returns the lower-level cause of this error, if any. 35 | 36 | fn cause(&self) -> Option<&Error> { 37 | match *self { 38 | SlaveError::BadDescriptor(ref err) => Some(err), 39 | _ => None, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/fork/pty/slave/mod.rs: -------------------------------------------------------------------------------- 1 | mod err; 2 | 3 | use ::descriptor::Descriptor; 4 | use ::libc; 5 | 6 | pub use self::err::{SlaveError, Result}; 7 | use std::os::unix::io::{AsRawFd, RawFd}; 8 | 9 | #[derive(Debug)] 10 | pub struct Slave { 11 | pty: RawFd, 12 | } 13 | 14 | impl Slave { 15 | /// The constructor function `new` returns the Slave interface. 16 | pub fn new(path: *const ::libc::c_char) -> Result { 17 | match Self::open(path, libc::O_RDWR, None) { 18 | Err(cause) => Err(SlaveError::BadDescriptor(cause)), 19 | Ok(fd) => Ok(Slave { pty: fd }), 20 | } 21 | } 22 | 23 | pub fn dup2(&self, std: libc::c_int) -> Result { 24 | unsafe { 25 | match libc::dup2(self.as_raw_fd(), std) { 26 | -1 => Err(SlaveError::Dup2Error), 27 | d => Ok(d), 28 | } 29 | } 30 | } 31 | } 32 | 33 | impl Descriptor for Slave {} 34 | 35 | impl AsRawFd for Slave { 36 | /// The accessor function `as_raw_fd` returns the fd. 37 | fn as_raw_fd(&self) -> RawFd { 38 | self.pty 39 | } 40 | } 41 | 42 | impl Drop for Slave { 43 | fn drop(&mut self) { 44 | Descriptor::drop(self); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # PTY 2 | //! 3 | //! [![Crate][crate-badge]][crate] [![docs-badge][]][docs] [![license-badge][]][license] [![travis-badge][]][travis] 4 | //! 5 | //! [crate-badge]: https://img.shields.io/badge/crates.io-v0.2.0-orange.svg?style=flat-square 6 | //! [crate]: https://crates.io/crates/pty 7 | //! 8 | //! [docs-badge]: https://img.shields.io/badge/API-docs-blue.svg?style=flat-square 9 | //! [docs]: http://note.hibariya.org/pty-rs/pty/index.html 10 | //! 11 | //! [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square 12 | //! [license]: https://github.com/hibariya/pty-rs/blob/master/LICENSE.txt 13 | //! 14 | //! [travis-badge]: https://travis-ci.org/hibariya/pty-rs.svg?branch=master&style=flat-square 15 | //! [travis]: https://travis-ci.org/hibariya/pty-rs 16 | //! 17 | //! The `pty` crate provides `pty::fork()`. That makes a parent process fork with new pseudo-terminal (PTY). 18 | //! 19 | //! This crate depends on followings: 20 | //! 21 | //! * `libc` library 22 | //! * POSIX environment 23 | //! 24 | //! ## Usage 25 | //! 26 | //! Add this to your `Cargo.toml`: 27 | //! 28 | //! ```toml 29 | //! [dependencies] 30 | //! pty = "0.2" 31 | //! ``` 32 | //! 33 | //! and this to your crate root: 34 | //! 35 | //! ```rust 36 | //! extern crate pty; 37 | //! ``` 38 | //! 39 | //! ### pty::fork() 40 | //! 41 | //! This function returns `pty::Child`. It represents the child process and its PTY. 42 | //! 43 | //! For example, the following code spawns `tty(1)` command by `pty::fork()` and outputs the result of the command. 44 | //! 45 | //! ```rust 46 | //! extern crate pty; 47 | //! extern crate libc; 48 | //! 49 | //! use std::ffi::CString; 50 | //! use std::io::Read; 51 | //! use std::process::{Command}; 52 | //! 53 | //! use pty::fork::*; 54 | //! 55 | //! fn main() { 56 | //! let fork = Fork::from_ptmx().unwrap(); 57 | //! 58 | //! if let Some(mut master) = fork.is_parent().ok() { 59 | //! // Read output via PTY master 60 | //! let mut output = String::new(); 61 | //! 62 | //! match master.read_to_string(&mut output) { 63 | //! Ok(_nread) => println!("child tty is: {}", output.trim()), 64 | //! Err(e) => panic!("read error: {}", e), 65 | //! } 66 | //! } 67 | //! else { 68 | //! // Child process just exec `tty` 69 | //! Command::new("tty").status().expect("could not execute tty"); 70 | //! } 71 | //! } 72 | //! ``` 73 | 74 | #![crate_type = "lib"] 75 | 76 | #![cfg_attr(feature = "nightly", feature(plugin))] 77 | #![cfg_attr(feature = "lints", plugin(clippy))] 78 | #![cfg_attr(feature = "lints", deny(warnings))] 79 | #![deny( 80 | missing_debug_implementations, 81 | missing_copy_implementations, 82 | trivial_casts, 83 | trivial_numeric_casts, 84 | unstable_features, 85 | unused_import_braces, 86 | unused_features, 87 | unused_qualifications, 88 | )] 89 | 90 | extern crate libc; 91 | extern crate errno; 92 | 93 | mod descriptor; 94 | pub mod fork; 95 | pub mod prelude; 96 | 97 | const DEFAULT_PTMX: &'static str = "/dev/ptmx"; 98 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use super::fork::{Fork, ForkError}; 2 | pub use super::fork::{Master, MasterError}; 3 | pub use super::fork::{Slave, SlaveError}; 4 | -------------------------------------------------------------------------------- /tests/it_can_read_write.rs: -------------------------------------------------------------------------------- 1 | extern crate pty; 2 | extern crate libc; 3 | 4 | use self::pty::prelude::*; 5 | 6 | use std::io::prelude::*; 7 | use std::string::String; 8 | use std::process::Command; 9 | 10 | fn read_line(master:&mut Master) -> String { 11 | let mut buf = [0]; 12 | let mut res = String::new(); 13 | while buf[0] as char != '\n' { 14 | master.read(&mut buf).expect("cannot read 1 byte"); 15 | res.push(buf[0] as char) 16 | } 17 | res 18 | } 19 | 20 | #[test] 21 | fn it_can_read_write() { 22 | let fork = Fork::from_ptmx().unwrap(); 23 | 24 | if let Some(mut master) = fork.is_parent().ok() { 25 | let _ = master.write("echo readme!\n".to_string().as_bytes()); 26 | 27 | read_line(&mut master); // this is the "echo readme!" we just sent 28 | read_line(&mut master); // this is the shell and "echo readme!" again 29 | assert_eq!(read_line(&mut master).trim(), "readme!"); 30 | let _ = master.write("exit\n".to_string().as_bytes()); 31 | } else { 32 | let _ = Command::new("bash").status(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/it_fork_with_new_pty.rs: -------------------------------------------------------------------------------- 1 | extern crate pty; 2 | extern crate libc; 3 | extern crate errno; 4 | 5 | use self::pty::prelude::*; 6 | 7 | use std::io::prelude::*; 8 | use std::process::{Command, Stdio}; 9 | use std::string::String; 10 | 11 | #[test] 12 | fn it_fork_with_new_pty() { 13 | let fork = Fork::from_ptmx().unwrap(); 14 | 15 | if let Some(mut master) = fork.is_parent().ok() { 16 | let mut string = String::new(); 17 | 18 | master.read_to_string(&mut string).unwrap_or_else(|e| panic!(e)); 19 | 20 | let output = Command::new("tty") 21 | .stdin(Stdio::inherit()) 22 | .output() 23 | .unwrap() 24 | .stdout; 25 | let output_str = String::from_utf8_lossy(&output); 26 | 27 | let parent_tty = output_str.trim(); 28 | let child_tty = string.trim(); 29 | 30 | assert!(child_tty != ""); 31 | assert!(child_tty != parent_tty); 32 | 33 | // only compare if parent is tty 34 | // travis runs the tests without tty 35 | if parent_tty != "not a tty" { 36 | let mut parent_tty_dir: Vec<&str> = parent_tty.split("/").collect(); 37 | let mut child_tty_dir: Vec<&str> = child_tty.split("/").collect(); 38 | 39 | parent_tty_dir.pop(); 40 | child_tty_dir.pop(); 41 | 42 | assert_eq!(parent_tty_dir, child_tty_dir); 43 | } 44 | } else { 45 | Command::new("tty").status().expect("could not execute tty"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | mod it_fork_with_new_pty; 2 | mod it_can_read_write; 3 | --------------------------------------------------------------------------------