Skip to content

Commit 3612b35

Browse files
committed
Auto merge of #416 - philippkeller:master, r=posborne
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?
2 parents 45e3170 + 7dd12c6 commit 3612b35

File tree

2 files changed

+155
-37
lines changed

2 files changed

+155
-37
lines changed

src/unistd.rs

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
use {Errno, Error, Result, NixPath};
44
use fcntl::{fcntl, OFlag, O_NONBLOCK, O_CLOEXEC, FD_CLOEXEC};
55
use fcntl::FcntlArg::{F_SETFD, F_SETFL};
6-
use libc::{self, c_char, c_void, c_int, c_uint, size_t, pid_t, off_t, uid_t, gid_t};
6+
use libc::{self, c_char, c_void, c_int, c_uint, size_t, pid_t, off_t, uid_t, gid_t, mode_t};
77
use std::mem;
8-
use std::ffi::CString;
8+
use std::ffi::{CString, CStr, OsString};
9+
use std::os::unix::ffi::OsStringExt;
10+
use std::path::PathBuf;
911
use std::os::unix::io::RawFd;
1012
use void::Void;
13+
use sys::stat::Mode;
1114

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

117+
/// Creates new directory `path` with access rights `mode`.
118+
///
119+
/// # Errors
120+
///
121+
/// There are several situations where mkdir might fail:
122+
///
123+
/// - current user has insufficient rights in the parent directory
124+
/// - the path already exists
125+
/// - the path name is too long (longer than `PATH_MAX`, usually 4096 on linux, 1024 on OS X)
126+
///
127+
/// For a full list consult
128+
/// [man mkdir(2)](http://man7.org/linux/man-pages/man2/mkdir.2.html#ERRORS)
129+
///
130+
/// # Example
131+
///
132+
/// ```rust
133+
/// extern crate tempdir;
134+
/// extern crate nix;
135+
///
136+
/// use nix::unistd;
137+
/// use nix::sys::stat;
138+
/// use tempdir::TempDir;
139+
///
140+
/// fn main() {
141+
/// let mut tmp_dir = TempDir::new("test_mkdir").unwrap().into_path();
142+
/// tmp_dir.push("new_dir");
143+
///
144+
/// // create new directory and give read, write and execute rights to the owner
145+
/// match unistd::mkdir(&tmp_dir, stat::S_IRWXU) {
146+
/// Ok(_) => println!("created {:?}", tmp_dir),
147+
/// Err(err) => println!("Error creating directory: {}", err),
148+
/// }
149+
/// }
150+
/// ```
151+
#[inline]
152+
pub fn mkdir<P: ?Sized + NixPath>(path: &P, mode: Mode) -> Result<()> {
153+
let res = try!(path.with_nix_path(|cstr| {
154+
unsafe { libc::mkdir(cstr.as_ptr(), mode.bits() as mode_t) }
155+
}));
156+
157+
Errno::result(res).map(drop)
158+
}
159+
160+
/// Returns the current directory as a PathBuf
161+
///
162+
/// Err is returned if the current user doesn't have the permission to read or search a component
163+
/// of the current path.
164+
///
165+
/// # Example
166+
///
167+
/// ```rust
168+
/// extern crate nix;
169+
///
170+
/// use nix::unistd;
171+
///
172+
/// fn main() {
173+
/// // assume that we are allowed to get current directory
174+
/// let dir = unistd::getcwd().unwrap();
175+
/// println!("The current directory is {:?}", dir);
176+
/// }
177+
/// ```
178+
#[inline]
179+
pub fn getcwd() -> Result<PathBuf> {
180+
let mut buf = Vec::with_capacity(512);
181+
loop {
182+
unsafe {
183+
let ptr = buf.as_mut_ptr() as *mut libc::c_char;
184+
185+
// The buffer must be large enough to store the absolute pathname plus
186+
// a terminating null byte, or else null is returned.
187+
// To safely handle this we start with a reasonable size (512 bytes)
188+
// and double the buffer size upon every error
189+
if !libc::getcwd(ptr, buf.capacity()).is_null() {
190+
let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
191+
buf.set_len(len);
192+
buf.shrink_to_fit();
193+
return Ok(PathBuf::from(OsString::from_vec(buf)));
194+
} else {
195+
let error = Errno::last();
196+
// ERANGE means buffer was too small to store directory name
197+
if error != Errno::ERANGE {
198+
return Err(Error::Sys(error));
199+
}
200+
}
201+
202+
// Trigger the internal buffer resizing logic of `Vec` by requiring
203+
// more space than the current capacity.
204+
let cap = buf.capacity();
205+
buf.set_len(cap);
206+
buf.reserve(1);
207+
}
208+
}
209+
}
210+
114211
#[inline]
115212
pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<uid_t>, group: Option<gid_t>) -> Result<()> {
116213
let res = try!(path.with_nix_path(|cstr| {

test/test_unistd.rs

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,57 @@
1+
extern crate tempdir;
2+
13
use nix::unistd::*;
24
use nix::unistd::ForkResult::*;
35
use nix::sys::wait::*;
6+
use nix::sys::stat;
7+
use std::iter;
48
use std::ffi::CString;
5-
69
use std::io::{Write, Read};
10+
use std::os::unix::prelude::*;
11+
use std::env::current_dir;
712
use tempfile::tempfile;
13+
use tempdir::TempDir;
814
use libc::off_t;
9-
use std::os::unix::prelude::*;
10-
11-
1215

1316
#[test]
1417
fn test_fork_and_waitpid() {
1518
let pid = fork();
1619
match pid {
17-
Ok(Child) => {} // ignore child here
18-
Ok(Parent { child }) => {
19-
// assert that child was created and pid > 0
20-
assert!(child > 0);
21-
let wait_status = waitpid(child, None);
22-
match wait_status {
23-
// assert that waitpid returned correct status and the pid is the one of the child
24-
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),
25-
26-
// panic, must never happen
27-
Ok(_) => panic!("Child still alive, should never happen"),
28-
29-
// panic, waitpid should never fail
30-
Err(_) => panic!("Error: waitpid Failed")
31-
}
32-
33-
},
34-
// panic, fork should never fail unless there is a serious problem with the OS
35-
Err(_) => panic!("Error: Fork Failed")
20+
Ok(Child) => {} // ignore child here
21+
Ok(Parent { child }) => {
22+
// assert that child was created and pid > 0
23+
assert!(child > 0);
24+
let wait_status = waitpid(child, None);
25+
match wait_status {
26+
// assert that waitpid returned correct status and the pid is the one of the child
27+
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),
28+
29+
// panic, must never happen
30+
Ok(_) => panic!("Child still alive, should never happen"),
31+
32+
// panic, waitpid should never fail
33+
Err(_) => panic!("Error: waitpid Failed")
34+
}
35+
36+
},
37+
// panic, fork should never fail unless there is a serious problem with the OS
38+
Err(_) => panic!("Error: Fork Failed")
3639
}
3740
}
3841

3942
#[test]
4043
fn test_wait() {
4144
let pid = fork();
4245
match pid {
43-
Ok(Child) => {} // ignore child here
44-
Ok(Parent { child }) => {
45-
let wait_status = wait();
46-
47-
// just assert that (any) one child returns with WaitStatus::Exited
48-
assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
49-
},
50-
// panic, fork should never fail unless there is a serious problem with the OS
51-
Err(_) => panic!("Error: Fork Failed")
46+
Ok(Child) => {} // ignore child here
47+
Ok(Parent { child }) => {
48+
let wait_status = wait();
49+
50+
// just assert that (any) one child returns with WaitStatus::Exited
51+
assert_eq!(wait_status, Ok(WaitStatus::Exited(child, 0)));
52+
},
53+
// panic, fork should never fail unless there is a serious problem with the OS
54+
Err(_) => panic!("Error: Fork Failed")
5255
}
5356
}
5457

@@ -119,6 +122,24 @@ macro_rules! execve_test_factory(
119122
)
120123
);
121124

125+
#[test]
126+
fn test_getcwd() {
127+
let mut tmp_dir = TempDir::new("test_getcwd").unwrap().into_path();
128+
assert!(chdir(tmp_dir.as_path()).is_ok());
129+
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
130+
131+
// make path 500 chars longer so that buffer doubling in getcwd kicks in.
132+
// Note: One path cannot be longer than 255 bytes (NAME_MAX)
133+
// whole path cannot be longer than PATH_MAX (usually 4096 on linux, 1024 on macos)
134+
for _ in 0..5 {
135+
let newdir = iter::repeat("a").take(100).collect::<String>();
136+
tmp_dir.push(newdir);
137+
assert!(mkdir(tmp_dir.as_path(), stat::S_IRWXU).is_ok());
138+
}
139+
assert!(chdir(tmp_dir.as_path()).is_ok());
140+
assert_eq!(getcwd().unwrap(), current_dir().unwrap());
141+
}
142+
122143
#[test]
123144
fn test_lseek() {
124145
const CONTENTS: &'static [u8] = b"abcdef123456";
@@ -129,10 +150,10 @@ fn test_lseek() {
129150
lseek(tmp.as_raw_fd(), offset, Whence::SeekSet).unwrap();
130151

131152
let mut buf = String::new();
132-
tmp.read_to_string(&mut buf).unwrap();
133-
assert_eq!(b"f123456", buf.as_bytes());
153+
tmp.read_to_string(&mut buf).unwrap();
154+
assert_eq!(b"f123456", buf.as_bytes());
134155

135-
close(tmp.as_raw_fd()).unwrap();
156+
close(tmp.as_raw_fd()).unwrap();
136157
}
137158

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

0 commit comments

Comments
 (0)