diff --git a/CHANGELOG.md b/CHANGELOG.md index 8179de2613..cea9680625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). and nix::Error::UnsupportedOperation}` ([#614](https://github.com/nix-rust/nix/pull/614)) - Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527)) +- Readded execvpe support, conditional on platform. libc now provides this for Haiku, Linux, and OpenBSD. + ([#683](https://github.com/nix-rust/nix/pull/683)) ### Changed - Changed `ioctl!(write ...)` to take argument by value instead as pointer. diff --git a/Cargo.toml b/Cargo.toml index e768f4a190..56e23cc798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,11 @@ exclude = [ [features] eventfd = [] -execvpe = [] preadv_pwritev = [] signalfd = [] [dependencies] -libc = "0.2.25" +libc = { git = "https://github.com/rust-lang/libc" } bitflags = "0.9" cfg-if = "0.1.0" void = "1.0.2" diff --git a/src/pty.rs b/src/pty.rs index 26c8b500cd..fd8ad98e44 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -14,8 +14,7 @@ use {Errno, Result, Error, fcntl}; /// Representation of a master/slave pty pair /// -/// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user -/// must manually close the file descriptors. +/// This is returned by `openpty` pub struct OpenptyResult { pub master: RawFd, pub slave: RawFd, diff --git a/src/unistd.rs b/src/unistd.rs index 196232b6c6..2036896b28 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1596,7 +1596,7 @@ mod linux { use {Errno, Result, NixPath}; use super::{Uid, Gid}; - #[cfg(feature = "execvpe")] + #[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))] use std::ffi::CString; pub fn pivot_root( @@ -1644,8 +1644,14 @@ mod linux { Errno::result(res).map(drop) } + /// Replaces the current process image with a new one. + /// ([see exec(3)](http://man7.org/linux/man-pages/man3/exec.3.html)) + /// + /// # Errors + /// An error is returned if the executable file does not exist, cannot be accessed, or + /// could not be executed otherwise. #[inline] - #[cfg(feature = "execvpe")] + #[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))] pub fn execvpe(filename: &CString, args: &[CString], env: &[CString]) -> Result<()> { use std::ptr; use libc::c_char; @@ -1656,10 +1662,10 @@ mod linux { let mut env_p: Vec<*const c_char> = env.iter().map(|s| s.as_ptr()).collect(); env_p.push(ptr::null()); - unsafe { - super::ffi::execvpe(filename.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) + let res = unsafe { + libc::execvpe(filename.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }; - Err(Error::Sys(Errno::last())) + Errno::result(res).map(drop) } } diff --git a/test/sys/test_aio.rs b/test/sys/test_aio.rs index 7e2bef63fe..7945aaf840 100644 --- a/test/sys/test_aio.rs +++ b/test/sys/test_aio.rs @@ -8,6 +8,7 @@ use std::io::{Write, Read, Seek, SeekFrom}; use std::ops::Deref; use std::os::unix::io::AsRawFd; use std::rc::Rc; +use std::sync::Mutex; use std::sync::atomic::{AtomicBool, Ordering}; use std::{thread, time}; use tempfile::tempfile; @@ -232,6 +233,8 @@ fn test_write() { lazy_static! { pub static ref SIGNALED: AtomicBool = AtomicBool::new(false); + // protects access to SIGUSR2 handlers, not just SIGNALED + pub static ref SIGUSR2_MTX: Mutex<()> = Mutex::new(()); } extern fn sigfunc(_: c_int) { @@ -243,8 +246,7 @@ extern fn sigfunc(_: c_int) { #[test] #[cfg_attr(any(all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips"), ignore)] fn test_write_sigev_signal() { - #[allow(unused_variables)] - let m = ::SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test"); + let _ = SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test"); let sa = SigAction::new(SigHandler::Handler(sigfunc), SA_RESETHAND, SigSet::empty()); @@ -374,8 +376,7 @@ fn test_lio_listio_nowait() { #[cfg(not(any(target_os = "ios", target_os = "macos")))] #[cfg_attr(any(target_arch = "mips", target_env = "musl"), ignore)] fn test_lio_listio_signal() { - #[allow(unused_variables)] - let m = ::SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test"); + let _ = SIGUSR2_MTX.lock().expect("Mutex got poisoned by another test"); const INITIAL: &'static [u8] = b"abcdef123456"; const WBUF: &'static [u8] = b"CDEF"; let rbuf = Rc::new(vec![0; 4].into_boxed_slice()); diff --git a/test/sys/test_termios.rs b/test/sys/test_termios.rs index c65720520d..308cf62c2b 100644 --- a/test/sys/test_termios.rs +++ b/test/sys/test_termios.rs @@ -1,7 +1,6 @@ use std::os::unix::prelude::*; -use tempfile::tempfile; -use nix::{Error, fcntl}; +use nix::{Error, fcntl, unistd}; use nix::errno::Errno; use nix::pty::openpty; use nix::sys::termios::{self, ECHO, OPOST, OCRNL, Termios, tcgetattr}; @@ -15,27 +14,22 @@ fn write_all(f: RawFd, buf: &[u8]) { } } -// Test tcgetattr on a terminal #[test] -fn test_tcgetattr_pty() { - let pty = openpty(None, None).unwrap(); - assert!(termios::tcgetattr(pty.master).is_ok()); - close(pty.master).unwrap(); - close(pty.slave).unwrap(); -} -// Test tcgetattr on something that isn't a terminal -#[test] -fn test_tcgetattr_enotty() { - let file = tempfile().unwrap(); - assert_eq!(termios::tcgetattr(file.as_raw_fd()).err(), - Some(Error::Sys(Errno::ENOTTY))); -} - -// Test tcgetattr on an invalid file descriptor -#[test] -fn test_tcgetattr_ebadf() { - assert_eq!(termios::tcgetattr(-1).err(), - Some(Error::Sys(Errno::EBADF))); +fn test_tcgetattr() { + for fd in 0..5 { + let termios = termios::tcgetattr(fd); + match unistd::isatty(fd) { + // If `fd` is a TTY, tcgetattr must succeed. + Ok(true) => assert!(termios.is_ok()), + // If it's an invalid file descriptor, tcgetattr should also return + // the same error + Err(Error::Sys(Errno::EBADF)) => { + assert_eq!(termios.err(), Some(Error::Sys(Errno::EBADF))); + }, + // Otherwise it should return any error + _ => assert!(termios.is_err()) + } + } } // Test modifying output flags @@ -47,8 +41,6 @@ fn test_output_flags() { assert!(pty.master > 0); assert!(pty.slave > 0); let termios = tcgetattr(pty.master).unwrap(); - close(pty.master).unwrap(); - close(pty.slave).unwrap(); termios }; @@ -72,8 +64,6 @@ fn test_output_flags() { let mut buf = [0u8; 10]; ::read_exact(pty.slave, &mut buf); let transformed_string = "foofoofoo\n"; - close(pty.master).unwrap(); - close(pty.slave).unwrap(); assert_eq!(&buf, transformed_string.as_bytes()); } @@ -86,8 +76,6 @@ fn test_local_flags() { assert!(pty.master > 0); assert!(pty.slave > 0); let termios = tcgetattr(pty.master).unwrap(); - close(pty.master).unwrap(); - close(pty.slave).unwrap(); termios }; @@ -114,8 +102,6 @@ fn test_local_flags() { // Try to read from the master, which should not have anything as echoing was disabled. let mut buf = [0u8; 10]; let read = read(pty.master, &mut buf).unwrap_err(); - close(pty.master).unwrap(); - close(pty.slave).unwrap(); assert_eq!(read, Error::Sys(Errno::EAGAIN)); } diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index 2e28d9e78f..d8ac94e487 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -6,9 +6,6 @@ use libc::exit; #[test] fn test_wait_signal() { - #[allow(unused_variables)] - let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); - match fork() { Ok(Child) => pause().unwrap_or(()), Ok(Parent { child }) => { @@ -22,9 +19,6 @@ fn test_wait_signal() { #[test] fn test_wait_exit() { - #[allow(unused_variables)] - let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); - match fork() { Ok(Child) => unsafe { exit(12); }, Ok(Parent { child }) => { diff --git a/test/test.rs b/test/test.rs index 4c81aa2b73..2cf3036054 100644 --- a/test/test.rs +++ b/test/test.rs @@ -25,7 +25,6 @@ mod test_unistd; use nixtest::assert_size_of; use std::os::unix::io::RawFd; -use std::sync::Mutex; use nix::unistd::read; /// Helper function analogous to std::io::Read::read_exact, but for `RawFD`s @@ -38,17 +37,6 @@ fn read_exact(f: RawFd, buf: &mut [u8]) { } } -lazy_static! { - /// Any test that changes the process's current working directory must grab - /// this mutex - pub static ref CWD_MTX: Mutex<()> = Mutex::new(()); - /// Any test that creates child processes must grab this mutex, regardless - /// of what it does with those children. - pub static ref FORK_MTX: Mutex<()> = Mutex::new(()); - /// Any test that registers a SIGUSR2 handler must grab this mutex - pub static ref SIGUSR2_MTX: Mutex<()> = Mutex::new(()); -} - #[test] pub fn test_size_of_long() { // This test is mostly here to ensure that 32bit CI is correctly diff --git a/test/test_mq.rs b/test/test_mq.rs index 60f5bd2e48..82a1a388f7 100644 --- a/test/test_mq.rs +++ b/test/test_mq.rs @@ -16,27 +16,43 @@ use nix::Error::Sys; #[test] fn test_mq_send_and_receive() { + const MSG_SIZE: c_long = 32; let attr = MqAttr::new(0, 10, MSG_SIZE, 0); - let mq_name= &CString::new(b"/a_nix_test_queue".as_ref()).unwrap(); - - let mqd0 = mq_open(mq_name, O_CREAT | O_WRONLY, - S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, - Some(&attr)).unwrap(); - let msg_to_send = "msg_1"; - mq_send(mqd0, msg_to_send.as_bytes(), 1).unwrap(); - - let mqd1 = mq_open(mq_name, O_CREAT | O_RDONLY, - S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, - Some(&attr)).unwrap(); - let mut buf = [0u8; 32]; - let mut prio = 0u32; - let len = mq_receive(mqd1, &mut buf, &mut prio).unwrap(); - assert!(prio == 1); - - mq_close(mqd1).unwrap(); - mq_close(mqd0).unwrap(); - assert_eq!(msg_to_send, str::from_utf8(&buf[0..len]).unwrap()); + let mq_name_in_parent = &CString::new(b"/a_nix_test_queue".as_ref()).unwrap(); + let mqd_in_parent = mq_open(mq_name_in_parent, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, Some(&attr)).unwrap(); + let msg_to_send = "msg_1".as_bytes(); + + mq_send(mqd_in_parent, msg_to_send, 1).unwrap(); + + let (reader, writer) = pipe().unwrap(); + + let pid = fork(); + match pid { + Ok(Child) => { + let mq_name_in_child = &CString::new(b"/a_nix_test_queue".as_ref()).unwrap(); + let mqd_in_child = mq_open(mq_name_in_child, O_CREAT | O_RDONLY, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, Some(&attr)).unwrap(); + let mut buf = [0u8; 32]; + let mut prio = 0u32; + mq_receive(mqd_in_child, &mut buf, &mut prio).unwrap(); + assert!(prio == 1); + write(writer, &buf).unwrap(); // pipe result to parent process. Otherwise cargo does not report test failures correctly + mq_close(mqd_in_child).unwrap(); + } + Ok(Parent { child }) => { + mq_close(mqd_in_parent).unwrap(); + + // Wait for the child to exit. + waitpid(child, None).unwrap(); + // Read 1024 bytes. + let mut read_buf = [0u8; 32]; + read(reader, &mut read_buf).unwrap(); + let message_str = str::from_utf8(&read_buf).unwrap(); + assert_eq!(&message_str[.. message_str.char_indices().nth(5).unwrap().0], "msg_1"); + }, + // panic, fork should never fail unless there is a serious problem with the OS + Err(_) => panic!("Error: Fork Failed") + } } diff --git a/test/test_unistd.rs b/test/test_unistd.rs index e6e7b5f211..e48fc4719c 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -9,18 +9,16 @@ use std::ffi::CString; use std::fs::File; use std::io::Write; use std::os::unix::prelude::*; +use std::env::current_dir; use tempfile::tempfile; use tempdir::TempDir; use libc::off_t; -use std::process::exit; #[test] fn test_fork_and_waitpid() { - #[allow(unused_variables)] - let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); let pid = fork(); match pid { - Ok(Child) => exit(0), + Ok(Child) => {} // ignore child here Ok(Parent { child }) => { // assert that child was created and pid > 0 let child_raw: ::libc::pid_t = child.into(); @@ -31,10 +29,10 @@ fn test_fork_and_waitpid() { Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child), // panic, must never happen - s @ Ok(_) => panic!("Child exited {:?}, should never happen", s), + Ok(_) => panic!("Child still alive, should never happen"), // panic, waitpid should never fail - Err(s) => panic!("Error: waitpid returned Err({:?}", s) + Err(_) => panic!("Error: waitpid Failed") } }, @@ -45,12 +43,9 @@ fn test_fork_and_waitpid() { #[test] fn test_wait() { - // Grab FORK_MTX so wait doesn't reap a different test's child process - #[allow(unused_variables)] - let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); let pid = fork(); match pid { - Ok(Child) => exit(0), + Ok(Child) => {} // ignore child here Ok(Parent { child }) => { let wait_status = wait(); @@ -105,8 +100,6 @@ macro_rules! execve_test_factory( ($test_name:ident, $syscall:ident, $unix_sh:expr, $android_sh:expr) => ( #[test] fn $test_name() { - #[allow(unused_variables)] - let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); // The `exec`d process will write to `writer`, and we'll read that // data from `reader`. let (reader, writer) = pipe().unwrap(); @@ -152,43 +145,40 @@ macro_rules! execve_test_factory( #[test] fn test_fchdir() { - // fchdir changes the process's cwd - #[allow(unused_variables)] - let m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test"); - let tmpdir = TempDir::new("test_fchdir").unwrap(); let tmpdir_path = tmpdir.path().canonicalize().unwrap(); let tmpdir_fd = File::open(&tmpdir_path).unwrap().into_raw_fd(); + let olddir_path = getcwd().unwrap(); + let olddir_fd = File::open(&olddir_path).unwrap().into_raw_fd(); assert!(fchdir(tmpdir_fd).is_ok()); assert_eq!(getcwd().unwrap(), tmpdir_path); + assert!(fchdir(olddir_fd).is_ok()); + assert_eq!(getcwd().unwrap(), olddir_path); + + assert!(close(olddir_fd).is_ok()); assert!(close(tmpdir_fd).is_ok()); } #[test] fn test_getcwd() { - // chdir changes the process's cwd - #[allow(unused_variables)] - let m = ::CWD_MTX.lock().expect("Mutex got poisoned by another test"); - - let tmpdir = TempDir::new("test_getcwd").unwrap(); - let tmpdir_path = tmpdir.path().canonicalize().unwrap(); - assert!(chdir(&tmpdir_path).is_ok()); - assert_eq!(getcwd().unwrap(), tmpdir_path); - - // make path 500 chars longer so that buffer doubling in getcwd - // kicks in. Note: One path cannot be longer than 255 bytes - // (NAME_MAX) whole path cannot be longer than PATH_MAX (usually - // 4096 on linux, 1024 on macos) - let mut inner_tmp_dir = tmpdir_path.to_path_buf(); + let tmp_dir = TempDir::new("test_getcwd").unwrap(); + assert!(chdir(tmp_dir.path()).is_ok()); + assert_eq!(getcwd().unwrap(), current_dir().unwrap()); + + // make path 500 chars longer so that buffer doubling in getcwd kicks in. + // Note: One path cannot be longer than 255 bytes (NAME_MAX) + // whole path cannot be longer than PATH_MAX (usually 4096 on linux, 1024 on macos) + let mut inner_tmp_dir = tmp_dir.path().to_path_buf(); for _ in 0..5 { let newdir = iter::repeat("a").take(100).collect::(); + //inner_tmp_dir = inner_tmp_dir.join(newdir).path(); inner_tmp_dir.push(newdir); assert!(mkdir(inner_tmp_dir.as_path(), stat::S_IRWXU).is_ok()); } assert!(chdir(inner_tmp_dir.as_path()).is_ok()); - assert_eq!(getcwd().unwrap(), inner_tmp_dir.as_path()); + assert_eq!(getcwd().unwrap(), current_dir().unwrap()); } #[test] @@ -227,8 +217,7 @@ fn test_lseek64() { execve_test_factory!(test_execve, execve, b"/bin/sh", b"/system/bin/sh"); -#[cfg(any(target_os = "linux", target_os = "android"))] -#[cfg(feature = "execvpe")] +#[cfg(any(target_os = "haiku", target_os = "linux", target_os = "openbsd"))] execve_test_factory!(test_execvpe, execvpe, b"sh", b"sh"); #[test]