libc_tools/
popen.rs

1use libc::{
2    __errno_location, _exit, c_int, c_void, execl, fclose, fdopen, pipe, pipe2, read, socketpair,
3    AF_UNIX, FILE, O_NONBLOCK, SOCK_STREAM, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO,
4};
5use std::ffi::{CString, NulError};
6
7use crate::{
8    create_pipe, create_pipe2, dup::DupError, wait::Wait, Close, Dup, Fork, ForkPid,
9    SocketPairError,
10};
11
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub struct Popen {
14    pub arg: String,
15    pub stdin: *mut FILE,
16    pub stdout: *mut FILE,
17    pub stderr: *mut FILE,
18    pub pid: Option<c_int>,
19}
20
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub enum PopenError {
23    PipeCreateFailed,
24    ExecArgFailed(c_int),
25    ForkFailed,
26    PipeRedirectFailed(c_int),
27    Dup2Errno(DupError),
28    FdOpenErrno(c_int),
29    CloseError(Close),
30    SocketPairError(SocketPairError),
31    CreateRedirectError(c_int),
32    CStringParesError(NulError),
33}
34
35impl PopenError {
36    fn to_string(&self) -> String {
37        match self {
38            Self::PipeCreateFailed => "create pipe failed!".to_string(),
39            Self::ExecArgFailed(code) => std::format!("exec arg failed! exit code: {}", code),
40            Self::ForkFailed => "fork failed!".to_string(),
41            Self::PipeRedirectFailed(v) => {
42                std::format!("redirect std(in|out|err) to pipe failed! code: {}", v)
43            }
44            PopenError::Dup2Errno(v) => std::format!("dup2 old fd to new fd failed! {}", v),
45            Self::FdOpenErrno(v) => {
46                std::format!("open file descriptor as file* stream failed! errno: {}", v)
47            }
48            Self::CloseError(v) => {
49                std::format!("{}", v)
50            }
51            Self::SocketPairError(v) => {
52                std::format!("socket pair {}", v)
53            }
54            Self::CreateRedirectError(v) => {
55                std::format!(
56                    "create socket pair and stderr pipe both failed! errno: {}",
57                    v
58                )
59            }
60            Self::CStringParesError(n) => {
61                std::format!("parse {:<.20} failed!", n.to_string())
62            }
63        }
64    }
65}
66
67// unsafe fn create_pipe() -> Result<[[libc::c_int; 2]; 3], PopenError> {
68//     let mut pipes = [[-1; 2]; 3];
69//     match (
70//         pipe(pipes[0].as_mut_ptr()),
71//         pipe(pipes[1].as_mut_ptr()),
72//         pipe(pipes[2].as_mut_ptr()),
73//     ) {
74//         (-1, _, _) | (_, -1, _) | (_, _, -1) => Err(PopenError::PipeCreateFailed),
75//         _ => Ok(pipes),
76//     }
77// }
78
79fn socket_pipe() -> Result<[[c_int; 2]; 2], PopenError> {
80    let mut sv = [0 as c_int; 2];
81    let mut fd = [0 as c_int; 2];
82    match unsafe {
83        (
84            socketpair(AF_UNIX, SOCK_STREAM, 0, sv.as_mut_ptr()),
85            pipe2(fd.as_mut_ptr(), O_NONBLOCK),
86        )
87    } {
88        (-1, 0) => Err(PopenError::SocketPairError(SocketPairError::SocketErrno(
89            unsafe { *__errno_location() },
90        ))),
91        (0, -1) => Err(PopenError::PipeCreateFailed),
92        (-1, -1) => Err(PopenError::CreateRedirectError(unsafe {
93            *__errno_location()
94        })),
95        (0, 0) => Ok([sv, fd]),
96        _ => panic!("this should reached!"),
97    }
98}
99
100//BUG:opened fd do not closed!
101#[deprecated(note = "do not use!")]
102impl Popen {
103    pub fn arg(arg: &str) -> Box<Popen> {
104        Box::new(Popen {
105            arg: String::from(arg),
106            stdin: 0 as *mut FILE,
107            stdout: 0 as *mut FILE,
108            stderr: 0 as *mut FILE,
109            pid: None,
110        })
111    }
112    pub fn exec(mut self: Box<Popen>) -> Result<Box<Popen>, PopenError> {
113        // let [sv, fd] = socket_pipe()?;
114        let [stdout, stdin] = create_pipe!(2).ok_or(PopenError::PipeCreateFailed)?;
115        let [stderr] = create_pipe2!(1, [O_NONBLOCK]).ok_or(PopenError::PipeCreateFailed)?;
116        match Fork::fork() {
117            ForkPid::Parent((_, children)) => {
118                println!("{}", self.as_ref() as *const Popen as *const i32 as i32);
119                self.pid = Some(children);
120                Close::close(&[stdin[0], stdout[1], stderr[1]])
121                    .or_else(|x| Err(PopenError::CloseError(x)))?;
122                let r = CString::new("r").or_else(|x| Err(PopenError::CStringParesError(x)))?;
123                let w = CString::new("w").or_else(|x| Err(PopenError::CStringParesError(x)))?;
124                self.stdin = unsafe { fdopen(stdin[1], w.as_ptr()) };
125                self.stdout = unsafe { fdopen(stdout[0], r.as_ptr()) };
126                self.stderr = unsafe { fdopen(stderr[0], r.as_ptr()) };
127                Ok(self)
128            }
129            // socket provide
130            ForkPid::Children(_) => {
131                println!("{}", self.as_ref() as *const Popen as *const i32 as i32);
132                Dup::dup2s(
133                    &[stdout[1], stderr[1], STDIN_FILENO],
134                    &[STDOUT_FILENO, STDERR_FILENO, stdin[0]],
135                )
136                .unwrap();
137                Close::close(&[
138                    stdout[0], stdout[1], stdin[0], stdin[1], stderr[0], stderr[1],
139                ])
140                .unwrap();
141                let path = CString::new("/bin/sh").unwrap();
142                let sh = CString::new("sh").unwrap();
143                let exec = CString::new("-c").unwrap();
144                let zsh = CString::new("zsh").unwrap();
145                let arg = CString::new(self.arg.clone()).unwrap();
146                unsafe {
147                    _exit(execl(
148                        path.as_ptr(),
149                        sh.as_ptr(),
150                        exec.as_ptr(),
151                        zsh.as_ptr(),
152                        exec.as_ptr(),
153                        arg.as_ptr(),
154                        0,
155                    ))
156                };
157            }
158            ForkPid::None => Err(PopenError::ForkFailed),
159        }
160    }
161}
162
163impl Drop for Popen {
164    fn drop(&mut self) {
165        let null = std::ptr::null_mut::<FILE>();
166        for i in &[self.stdin, self.stdout, self.stderr][..] {
167            if *i != null {
168                unsafe { fclose(*i) };
169            }
170        }
171        if let Some(pid) = self.pid {
172            // eprintln!("pid: {}", pid);
173            while {
174                match Wait::children_with(pid, 0) {
175                    Err(Wait::WaitFailure(libc::EINTR)) => true,
176                    _ => false,
177                }
178            } {}
179        } else {
180            eprintln!("pid is missed!");
181        }
182    }
183}
184
185#[cfg(test)]
186mod popen {
187    use libc::{
188        __errno_location, fclose, fgets, perror, socketpair, strlen, FILE, PF_UNIX, SOCK_CLOEXEC,
189        SOCK_DGRAM,
190    };
191
192    use crate::{popen::popen, Close, Popen, Wait};
193
194    #[test]
195    // #[ignore = "absolutely correct"]
196    fn test_libc_popen() {
197        unsafe {
198            // the common libc only support the 'r' and 'w', but the apple Libc support '+' (with socket)
199            let stream = libc::popen(
200                "echo hello\0".as_ptr() as *mut i8,
201                "r\0".as_ptr() as *mut i8,
202            );
203            assert!(stream != std::ptr::null_mut::<libc::FILE>());
204            let mut buf: [libc::c_char; 4096] = [0; 4096];
205            while {
206                let ptr = libc::fgets(buf.as_mut_ptr(), 4096, stream);
207                ptr != std::ptr::null_mut::<libc::c_char>() && *ptr != libc::EOF as i8
208            } {
209                let len = libc::strlen(buf.as_ptr());
210                assert!(len != 0);
211                let str = "hello\0";
212                for i in 0..len - 1 {
213                    let x = &str[i..i + 1];
214                    assert!(x.as_bytes()[0] == buf[i] as u8);
215                }
216                assert!(libc::strcmp(buf.as_ptr(), "hello\0".as_ptr() as *const i8) > 0);
217            }
218            assert!(libc::fclose(stream) != -1);
219        }
220    }
221    #[test]
222    fn test_popen_date() {
223        Popen::arg("date").exec().unwrap();
224    }
225
226    #[test]
227    fn socketpair_redirect() {
228        unsafe {
229            let mut fd: [i32; 4096] = [0; 4096];
230            socketpair(PF_UNIX, SOCK_DGRAM, 0, fd.as_mut_ptr());
231            Close::close(&[fd[0]]).unwrap();
232        }
233    }
234
235    #[test]
236    fn test_out_err() {
237        unsafe {
238            let popen = Popen::arg("time").exec().unwrap();
239            let mut buf = [0 as u8; 4096];
240            let mut p;
241            while {
242                p = fgets(buf.as_mut_ptr() as *mut i8, 4096, popen.stdout);
243                p != std::ptr::null_mut::<i8>() && *p != '\0' as i8
244            } {
245                assert!(strlen(p) != 0);
246            }
247        }
248    }
249
250    #[test]
251    fn test_write_out() {
252        let o = Popen::arg(
253            r#"
254while true;
255do
256    cat /proc/stat;
257    sleep 1;
258done;
259"#,
260        )
261        .exec()
262        .unwrap();
263        let mut buf: [i8; 4096] = [0 as i8; 4096];
264        let mut p;
265        while unsafe {
266            p = fgets(buf.as_mut_ptr(), 4096, o.stderr);
267            p != std::ptr::null_mut::<i8>()
268        } {
269            assert!(unsafe { strlen(p) } != 0);
270        }
271    }
272
273    #[test]
274    fn tty_shell() {}
275}