nix-rust / nix

Rust friendly bindings to *nix APIs
MIT License
2.57k stars 650 forks source link

ForkPty Always returns Parent #2402

Open s4Dt0y opened 1 month ago

s4Dt0y commented 1 month ago

I am pretty sure that I am not understanding this correctly...I am trying to open a pty for the sh shell using the nix libary with this code:

    let fd = unsafe {
        let res = nix::pty::forkpty(None, None).unwrap();
        match res.fork_result {
            ForkResult::Parent { child } => {
                println!("NOOO!")
            }
            ForkResult::Child => {
                let shell_name =
                    CStr::from_bytes_with_nul(b"sh\0").expect("Should always have null terminator");
                nix::unistd::execvp::<CString>(shell_name, &[]).unwrap();
                return;
            }
        };
        res.master
    };

However, it always returns the parent. What am I doing wrong here? I want to get forkpty to return the child, not the parent.

Note: I took a look at the src code and man page of forkpty and, from what I can make out, the function returns the parent if it runs into an error? Does that have anything to do with it?

SteveLauC commented 1 month ago

However, it always returns the parent.

The child process will be spawned in your code and execute sh, you should be able to see it in your monitor app (search the process name sh), would you like to elaborate on it a bit what do you mean by "it always returns the parent"?

Here is a program to prove that the child process exists:

I am using Nix from git, where the return value of forkpty() differs from the one in Nix 0.28.0

use std::{thread::sleep, time::Duration};
use nix::pty::{forkpty, ForkptyResult};

fn main() {
    match unsafe {forkpty(None, None)}.unwrap() {
        ForkptyResult::Parent { child: _child, master: _master} => {
            // don't drop the master side for 1 second so that child process can
            // get its job done
            sleep(Duration::from_secs(1));
        }
        ForkptyResult::Child => {
            std::fs::write("hello_from_child", "Hi").unwrap();
        }
    };
}
$ stat hello_from_child
stat: cannot statx 'hello_from_child': No such file or directory

$ ./target/debug/rust

$ stat hello_from_child
  File: hello_from_child
  Size: 2               Blocks: 8          IO Block: 4096   regular file
Device: 0,40    Inode: 10896442    Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/   steve)   Gid: ( 1000/   steve)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2024-05-19 20:04:39.014018425 +0800
Modify: 2024-05-19 20:04:39.014018425 +0800
Change: 2024-05-19 20:04:39.014018425 +0800
 Birth: 2024-05-19 20:04:39.014018425 +0800

the function returns the parent if it runs into an error

No, the interface exposed by Nix returns Err(errno), the C raw interface would return -1 and set errno to indicate the error.