use std::io;
#[cfg(unix)]
pub struct CloneOutcome {
pub pid: i32,
pub got_ctty: bool,
}
#[cfg(unix)]
pub fn clone_shell(tty_path: &str) -> io::Result<CloneOutcome> {
use std::ffi::CString;
use std::os::unix::io::RawFd;
let tty_c = CString::new(tty_path)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid tty path"))?;
let ttyfd: RawFd = unsafe { libc::open(tty_c.as_ptr(), libc::O_RDWR | libc::O_NOCTTY) };
if ttyfd < 0 {
return Err(io::Error::last_os_error());
}
let pid = unsafe { libc::fork() };
match pid {
-1 => {
unsafe { libc::close(ttyfd) };
Err(io::Error::last_os_error())
}
0 => {
let mut got_ctty = false;
unsafe {
if libc::setsid() == -1 {
eprintln!(
"clone: failed to create new session: {}",
io::Error::last_os_error()
);
}
libc::dup2(ttyfd, 0);
libc::dup2(ttyfd, 1);
libc::dup2(ttyfd, 2);
if ttyfd > 2 {
libc::close(ttyfd);
}
let cttyfd = libc::open(tty_c.as_ptr(), libc::O_RDWR);
if cttyfd >= 0 {
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
libc::ioctl(cttyfd, libc::TIOCSCTTY as libc::c_ulong, 0);
}
libc::close(cttyfd);
}
let dev_tty = b"/dev/tty\0";
let verify = libc::open(dev_tty.as_ptr() as *const libc::c_char, libc::O_RDWR);
if verify >= 0 {
got_ctty = true;
libc::close(verify);
}
}
Ok(CloneOutcome { pid: 0, got_ctty })
}
child_pid => {
unsafe { libc::close(ttyfd) };
Ok(CloneOutcome {
pid: child_pid,
got_ctty: true,
})
}
}
}
#[cfg(not(unix))]
pub struct CloneOutcome {
pub pid: i32,
pub got_ctty: bool,
}
#[cfg(not(unix))]
pub fn clone_shell(_tty_path: &str) -> io::Result<CloneOutcome> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"clone not supported",
))
}
pub fn builtin_clone(args: &[&str]) -> (i32, String, Option<u32>) {
if args.is_empty() {
return (1, "clone: terminal required\n".to_string(), None);
}
match clone_shell(args[0]) {
Ok(o) => (0, String::new(), Some(o.pid as u32)),
Err(e) => (1, format!("clone: {}: {}\n", args[0], e), None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builtin_clone_no_args() {
let (status, _, _) = builtin_clone(&[]);
assert_eq!(status, 1);
}
#[test]
fn test_builtin_clone_invalid_tty() {
let (status, output, _) = builtin_clone(&["/nonexistent/tty"]);
assert_eq!(status, 1);
assert!(output.contains("clone"));
}
}