Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add unistd::getcwd and unistd::mkdir #416

Merged
merged 9 commits into from
Sep 7, 2016
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous example included extern crate declarations and this one doesn't. I think they are not required in general for $crate (nix here).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is required. The doc says:

If the example does not contain extern crate, then extern crate ; is inserted

Since I need to do a extern crate tempdir I also need to do an extern crate nix here.

I think you want to hint that all examples should have the same style, that's why I now changed the doc of getcwd to have the same syntax as mkdir (including extern crate nix and an explicit main function)

///
/// 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