Skip to content

Commit

Permalink
Auto merge of #416 - philippkeller:master, r=posborne
Browse files Browse the repository at this point in the history
add unistd::getcwd and unistd::mkdir

As a (late) followup of [this withdrawn PR](rust-lang/libc#326) I have added getcwd (wrapper around `libc::getcwd`) and mkdir (wrapper around `libc::mkdir`) and added testing.

A few notes:

 - I'm new to rust so I would appreciate some pair of eyes testing the code, plus I'm open for revision of code or general remarks about my coding style
 - I have run the tests both on OSX as on Linux (Ubuntu)
 - I've run `clippy` to see if my code is well formatted, however clippy issues many warnings about the project. I think I didn't add any more warnings
 - the methods in unistd are not documented so I also left out the documentation of `getcwd` and `mkdir`, although I think it'd probably be good to add some documentation, especially some example code how to use the methods
 - the base idea of `getcwd` is [taken from std](https://github.com/rust-lang/rust/blob/1.9.0/src/libstd/sys/unix/os.rs#L95-L119), should I mention that somewhere?
  • Loading branch information
homu committed Sep 7, 2016
2 parents 45e3170 + 7dd12c6 commit 3612b35
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 37 deletions.
101 changes: 99 additions & 2 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
use {Errno, Error, Result, NixPath};
use fcntl::{fcntl, OFlag, O_NONBLOCK, O_CLOEXEC, FD_CLOEXEC};
use fcntl::FcntlArg::{F_SETFD, F_SETFL};
use libc::{self, c_char, c_void, c_int, c_uint, size_t, pid_t, off_t, uid_t, gid_t};
use libc::{self, c_char, c_void, c_int, c_uint, size_t, pid_t, off_t, uid_t, gid_t, mode_t};
use std::mem;
use std::ffi::CString;
use std::ffi::{CString, CStr, OsString};
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use std::os::unix::io::RawFd;
use void::Void;
use sys::stat::Mode;

#[cfg(any(target_os = "linux", target_os = "android"))]
pub use self::linux::*;
Expand Down Expand Up @@ -111,6 +114,100 @@ pub fn chdir<P: ?Sized + NixPath>(path: &P) -> Result<()> {
Errno::result(res).map(drop)
}

/// Creates new directory `path` with access rights `mode`.
///
/// # Errors
///
/// There are several situations where mkdir might fail:
///
/// - current user has insufficient rights in the parent directory
/// - the path already exists
/// - the path name is too long (longer than `PATH_MAX`, usually 4096 on linux, 1024 on OS X)
///
/// For a full list consult
/// [man mkdir(2)](http://man7.org/linux/man-pages/man2/mkdir.2.html#ERRORS)
///
/// # Example
///
/// ```rust
/// extern crate tempdir;
/// extern crate nix;
///
/// use nix::unistd;
/// use nix::sys::stat;
/// use tempdir::TempDir;
///
/// fn main() {
/// let mut tmp_dir = TempDir::new("test_mkdir").unwrap().into_path();
/// tmp_dir.push("new_dir");
///
/// // create new directory and give read, write and execute rights to the owner
/// match unistd::mkdir(&tmp_dir, stat::S_IRWXU) {
/// Ok(_) => println!("created {:?}", tmp_dir),
/// Err(err) => println!("Error creating directory: {}", err),
/// }
/// }
/// ```
#[inline]
pub fn mkdir<P: ?Sized + NixPath>(path: &P, mode: Mode) -> Result<()> {
let res = try!(path.with_nix_path(|cstr| {
unsafe { libc::mkdir(cstr.as_ptr(), mode.bits() as mode_t) }
}));

Errno::result(res).map(drop)
}

/// Returns the current directory as a PathBuf
///
/// Err is returned if the current user doesn't have the permission to read or search a component
/// of the current path.
///
/// # Example
///
/// ```rust
/// extern crate nix;
///
/// use nix::unistd;
///
/// fn main() {
/// // assume that we are allowed to get current directory
/// let dir = unistd::getcwd().unwrap();
/// println!("The current directory is {:?}", dir);
/// }
/// ```
#[inline]
pub fn getcwd() -> Result<PathBuf> {
let mut buf = Vec::with_capacity(512);
loop {
unsafe {
let ptr = buf.as_mut_ptr() as *mut libc::c_char;

// The buffer must be large enough to store the absolute pathname plus
// a terminating null byte, or else null is returned.
// To safely handle this we start with a reasonable size (512 bytes)
// and double the buffer size upon every error
if !libc::getcwd(ptr, buf.capacity()).is_null() {
let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
buf.set_len(len);
buf.shrink_to_fit();
return Ok(PathBuf::from(OsString::from_vec(buf)));
} else {
let error = Errno::last();
// ERANGE means buffer was too small to store directory name
if error != Errno::ERANGE {
return Err(Error::Sys(error));
}
}

// Trigger the internal buffer resizing logic of `Vec` by requiring
// more space than the current capacity.
let cap = buf.capacity();
buf.set_len(cap);
buf.reserve(1);
}
}
}

#[inline]
pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<uid_t>, group: Option<gid_t>) -> Result<()> {
let res = try!(path.with_nix_path(|cstr| {
Expand Down
91 changes: 56 additions & 35 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,57 @@
extern crate tempdir;

use nix::unistd::*;
use nix::unistd::ForkResult::*;
use nix::sys::wait::*;
use nix::sys::stat;
use std::iter;
use std::ffi::CString;

use std::io::{Write, Read};
use std::os::unix::prelude::*;
use std::env::current_dir;
use tempfile::tempfile;
use tempdir::TempDir;
use libc::off_t;
use std::os::unix::prelude::*;



#[test]
fn test_fork_and_waitpid() {
let pid = fork();
match pid {
Ok(Child) => {} // ignore child here
Ok(Parent { child }) => {
// assert that child was created and pid > 0
assert!(child > 0);
let wait_status = waitpid(child, None);
match wait_status {
// assert that waitpid returned correct status and the pid is the one of the child
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),

// panic, must never happen
Ok(_) => panic!("Child still alive, should never happen"),

// panic, waitpid should never fail
Err(_) => panic!("Error: waitpid Failed")
}

},
// panic, fork should never fail unless there is a serious problem with the OS
Err(_) => panic!("Error: Fork Failed")
Ok(Child) => {} // ignore child here
Ok(Parent { child }) => {
// assert that child was created and pid > 0
assert!(child > 0);
let wait_status = waitpid(child, None);
match wait_status {
// assert that waitpid returned correct status and the pid is the one of the child
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),

// panic, must never happen
Ok(_) => panic!("Child still alive, should never happen"),

// panic, waitpid should never fail
Err(_) => panic!("Error: waitpid Failed")
}

},
// panic, fork should never fail unless there is a serious problem with the OS
Err(_) => panic!("Error: Fork Failed")
}
}

#[test]
fn test_wait() {
let pid = fork();
match pid {
Ok(Child) => {} // ignore child here
Ok(Parent { child }) => {
let wait_status = wait();

// just assert that (any) one child returns with WaitStatus::Exited
assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
},
// panic, fork should never fail unless there is a serious problem with the OS
Err(_) => panic!("Error: Fork Failed")
Ok(Child) => {} // ignore child here
Ok(Parent { child }) => {
let wait_status = wait();

// just assert that (any) one child returns with WaitStatus::Exited
assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
},
// panic, fork should never fail unless there is a serious problem with the OS
Err(_) => panic!("Error: Fork Failed")
}
}

Expand Down Expand Up @@ -119,6 +122,24 @@ macro_rules! execve_test_factory(
)
);

#[test]
fn test_getcwd() {
let mut tmp_dir = TempDir::new("test_getcwd").unwrap().into_path();
assert!(chdir(tmp_dir.as_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)
for _ in 0..5 {
let newdir = iter::repeat("a").take(100).collect::<String>();
tmp_dir.push(newdir);
assert!(mkdir(tmp_dir.as_path(), stat::S_IRWXU).is_ok());
}
assert!(chdir(tmp_dir.as_path()).is_ok());
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
}

#[test]
fn test_lseek() {
const CONTENTS: &'static [u8] = b"abcdef123456";
Expand All @@ -129,10 +150,10 @@ fn test_lseek() {
lseek(tmp.as_raw_fd(), offset, Whence::SeekSet).unwrap();

let mut buf = String::new();
tmp.read_to_string(&mut buf).unwrap();
assert_eq!(b"f123456", buf.as_bytes());
tmp.read_to_string(&mut buf).unwrap();
assert_eq!(b"f123456", buf.as_bytes());

close(tmp.as_raw_fd()).unwrap();
close(tmp.as_raw_fd()).unwrap();
}

#[cfg(any(target_os = "linux", target_os = "android"))]
Expand Down

0 comments on commit 3612b35

Please sign in to comment.