Browse Source

Add popen/pclose

Jeremy Soller 6 years ago
parent
commit
52493a1ec3

+ 3 - 1
src/header/stdio/default.rs

@@ -21,7 +21,9 @@ impl GlobalFile {
             read_pos: 0,
             read_size: 0,
             unget: None,
-            writer
+            writer,
+
+            pid: None,
         }))
     }
     pub fn get(&self) -> *mut FILE {

+ 3 - 1
src/header/stdio/helpers.rs

@@ -75,6 +75,8 @@ pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> Option<*mut FILE> {
         read_pos: 0,
         read_size: 0,
         unget: None,
-        writer
+        writer,
+
+        pid: None,
     })))
 }

+ 124 - 12
src/header/stdio/mod.rs

@@ -12,8 +12,7 @@ use va_list::VaList as va_list;
 use c_str::CStr;
 use fs::File;
 use header::errno::{self, STR_ERROR};
-use header::fcntl;
-use header::stdlib::mkstemp;
+use header::{fcntl, stdlib, unistd};
 use header::string::strlen;
 use io::{self, BufRead, LineWriter, Read, Write};
 use mutex::Mutex;
@@ -67,7 +66,10 @@ pub struct FILE {
     read_pos: usize,
     read_size: usize,
     unget: Option<u8>,
-    pub(crate) /* stdio_ext */ writer: LineWriter<File>
+    pub(crate) /* stdio_ext */ writer: LineWriter<File>,
+
+    // Optional pid for use with popen/pclose
+    pid: Option<c_int>,
 }
 
 impl Read for FILE {
@@ -247,8 +249,9 @@ pub extern "C" fn ferror(stream: *mut FILE) -> c_int {
 /// Ensure the file is unlocked before calling this function, as it will attempt to lock the file
 /// itself.
 #[no_mangle]
-pub unsafe extern "C" fn fflush(stream: *mut FILE) -> c_int {
-    unsafe { &mut *stream }.lock().flush().is_err() as c_int
+pub extern "C" fn fflush(stream: *mut FILE) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
+    stream.flush().is_err() as c_int
 }
 
 /// Get a single char from a stream
@@ -605,9 +608,27 @@ pub extern "C" fn getw(stream: *mut FILE) -> c_int {
     }
 }
 
-// #[no_mangle]
-pub extern "C" fn pclose(_stream: *mut FILE) -> c_int {
-    unimplemented!();
+#[no_mangle]
+pub extern "C" fn pclose(stream: *mut FILE) -> c_int {
+    let pid = {
+        let mut stream = unsafe { &mut *stream }.lock();
+
+        if let Some(pid) = stream.pid.take() {
+            pid
+        } else {
+            unsafe { errno = errno::ECHILD };
+            return -1;
+        }
+    };
+
+    fclose(stream);
+
+    let mut wstatus = 0;
+    if Sys::waitpid(pid, &mut wstatus, 0) < 0 {
+        return -1;
+    }
+
+    wstatus
 }
 
 #[no_mangle]
@@ -625,9 +646,100 @@ pub unsafe extern "C" fn perror(s: *const c_char) {
     }
 }
 
-// #[no_mangle]
-pub extern "C" fn popen(_command: *const c_char, _mode: *const c_char) -> *mut FILE {
-    unimplemented!();
+#[no_mangle]
+pub unsafe extern "C" fn popen(command: *const c_char, mode: *const c_char) -> *mut FILE {
+    //TODO: share code with system
+
+    let mode = CStr::from_ptr(mode);
+
+    let mut cloexec = false;
+    let mut write_opt = None;
+    for b in mode.to_bytes().iter() {
+        match b {
+            b'e' => cloexec = true,
+            b'r' if write_opt.is_none() => write_opt = Some(false),
+            b'w' if write_opt.is_none() => write_opt = Some(true),
+            _ => {
+                errno = errno::EINVAL;
+                return ptr::null_mut();
+            }
+        }
+    }
+
+    let write = match write_opt {
+        Some(some) => some,
+        None => {
+            errno = errno::EINVAL;
+            return ptr::null_mut();
+        }
+    };
+
+    let mut pipes = [-1, -1];
+    if unistd::pipe(pipes.as_mut_ptr()) != 0 {
+        return ptr::null_mut();
+    }
+
+    let child_pid = unistd::fork();
+    if child_pid == 0 {
+        let command_nonnull = if command.is_null() {
+            "exit 0\0".as_ptr()
+        } else {
+            command as *const u8
+        };
+
+        let shell = "/bin/sh\0".as_ptr();
+
+        let args = [
+            "sh\0".as_ptr(),
+            "-c\0".as_ptr(),
+            command_nonnull,
+            ptr::null(),
+        ];
+
+        // Setup up stdin or stdout
+        //TODO: dup errors are ignored, should they be?
+        {
+            if write {
+                unistd::dup2(0, pipes[0]);
+            } else {
+                unistd::dup2(1, pipes[1]);
+            }
+
+            unistd::close(pipes[0]);
+            unistd::close(pipes[1]);
+        }
+
+        unistd::execv(shell as *const c_char, args.as_ptr() as *const *mut c_char);
+
+        stdlib::exit(127);
+
+        unreachable!();
+    } else if child_pid > 0 {
+        let (fd, fd_mode) = if write {
+            unistd::close(pipes[0]);
+            (pipes[1], if cloexec {
+                c_str!("we")
+            } else {
+                c_str!("w")
+            })
+        } else {
+            unistd::close(pipes[1]);
+            (pipes[0], if cloexec {
+                c_str!("re")
+            } else {
+                c_str!("r")
+            })
+        };
+
+        if let Some(f) = helpers::_fdopen(fd, fd_mode.as_ptr()) {
+            (*f).pid = Some(child_pid);
+            f
+        } else {
+            ptr::null_mut()
+        }
+    } else {
+        ptr::null_mut()
+    }
 }
 
 /// Put a character `c` into `stream`
@@ -748,7 +860,7 @@ pub extern "C" fn tempnam(_dir: *const c_char, _pfx: *const c_char) -> *mut c_ch
 pub extern "C" fn tmpfile() -> *mut FILE {
     let mut file_name = *b"/tmp/tmpfileXXXXXX";
     let file_name = file_name.as_mut_ptr() as *mut c_char;
-    let fd = mkstemp(file_name);
+    let fd = stdlib::mkstemp(file_name);
 
     if fd < 0 {
         return ptr::null_mut();

+ 1 - 0
tests/Makefile

@@ -29,6 +29,7 @@ EXPECT_BINS=\
 	stdio/fwrite \
 	stdio/getc_unget \
 	stdio/mutex \
+	stdio/popen \
 	stdio/printf \
 	stdio/rename \
 	stdio/scanf \

+ 0 - 0
tests/expected/stdio/popen.stderr


+ 9 - 0
tests/expected/stdio/popen.stdout

@@ -0,0 +1,9 @@
+1-never-gonna-give-you-up
+2-never-gonna-let-you-down
+3-never-gonna-run-around
+4-and-desert-you
+5-never-gonna-make-you-cry
+6-never-gonna-say-goodbye
+7-never-gonna-tell-a-lie
+8-and-hurt-you
+status 0

+ 29 - 0
tests/stdio/popen.c

@@ -0,0 +1,29 @@
+#include <stdio.h>
+
+int main(int argc, char **argv) {
+    FILE *fp;
+    int status;
+    char path[256];
+
+
+    fp = popen("ls -1 example_dir", "r");
+    if (fp == NULL) {
+        perror("popen");
+        return -1;
+    }
+
+    while (fgets(path, 256, fp) != NULL) {
+        printf("%s", path);
+    }
+
+
+    status = pclose(fp);
+    if (status == -1) {
+        perror("pclose");
+        return -1;
+    } else {
+        printf("status %x\n", status);
+    }
+
+    return 0;
+}