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
50 changes: 48 additions & 2 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
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};
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 +113,50 @@ pub fn chdir<P: ?Sized + NixPath>(path: &P) -> Result<()> {
Errno::result(res).map(drop)
}

#[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)
}

#[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(ptr).to_bytes().len();
buf.set_len(len);
buf.shrink_to_fit();
let s = try!(CString::new(buf).map_err(|_| Error::Sys(Errno::EILSEQ)));
let s = try!(s.into_string().map_err(|_| Error::Sys(Errno::EILSEQ)));
return Ok(PathBuf::from(&s));
} else {
let error = Errno::last();
// ERANGE means buffer was too small to store directory name
if error != Errno::ERANGE {
return Err(Error::Sys(error));
}
Copy link
Member

Choose a reason for hiding this comment

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

Trailing whitespace.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the hint. I've corrected those now. Any idea why cargo clippy did not warn about those?

}

// 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
32 changes: 30 additions & 2 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
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 tempfile::tempfile;
use tempdir::TempDir;
use libc::off_t;
use std::os::unix::prelude::*;



#[test]
fn test_fork_and_waitpid() {
let pid = fork();
Expand Down Expand Up @@ -119,6 +122,31 @@ macro_rules! execve_test_factory(
)
);

#[test]
fn test_getcwd() {
// workaround for the fact that on os x TmpDir::new returns /var/folders/... but upon
// chdir into that directory getcwd returns /private/var/folders/...
let base = if cfg!(target_os = "macos") {
"/private/tmp/"
} else {
"/tmp/"
};
Copy link
Member

Choose a reason for hiding this comment

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

This seems a little fragile. I think it would be OK to just verify that the nix getcwd results match those of libstd without hardcoding absolute base paths which may change based on the OS and system (My tmpfs may not be at /tmp).

Copy link
Author

Choose a reason for hiding this comment

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

good idea, I'll change this and add documentation for getcwd and mkdir

let mut tmp_dir = TempDir::new_in(base, "test_getcwd").expect("create temp dir").into_path();
assert!(chdir(tmp_dir.as_path()).is_ok());
assert_eq!(getcwd().unwrap(), tmp_dir);

// 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(), tmp_dir);
}

#[test]
fn test_lseek() {
const CONTENTS: &'static [u8] = b"abcdef123456";
Expand Down