rust/src/librustuv/file.rs

752 lines
27 KiB
Rust
Raw Normal View History

// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::c_str::CString;
use std::c_str;
use std::cast::transmute;
use std::cast;
use std::libc::{c_int, c_char, c_void, c_uint};
use std::libc;
use std::rt::BlockedTask;
use std::rt::io;
use std::rt::io::{FileStat, IoError};
use std::rt::rtio;
use std::rt::local::Local;
use std::rt::sched::{Scheduler, SchedHandle};
use std::vec;
2013-11-05 13:29:45 -06:00
use super::{Loop, UvError, uv_error_to_io_error};
use uvio::HomingIO;
use uvll;
pub struct FsRequest {
req: *uvll::uv_fs_t,
priv fired: bool,
}
pub struct FileWatcher {
priv loop_: Loop,
priv fd: c_int,
priv close: rtio::CloseBehavior,
priv home: SchedHandle,
}
impl FsRequest {
pub fn open(loop_: &Loop, path: &CString, flags: int, mode: int)
-> Result<FileWatcher, UvError>
{
execute(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_open(loop_.handle,
req, path.with_ref(|p| p), flags as c_int,
mode as c_int, cb)
}).map(|req|
FileWatcher::new(*loop_, req.get_result() as c_int,
rtio::CloseSynchronously)
)
}
pub fn unlink(loop_: &Loop, path: &CString) -> Result<(), UvError> {
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_unlink(loop_.handle, req, path.with_ref(|p| p),
cb)
})
}
pub fn lstat(loop_: &Loop, path: &CString) -> Result<FileStat, UvError> {
execute(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_lstat(loop_.handle, req, path.with_ref(|p| p),
cb)
}).map(|req| req.mkstat())
}
pub fn stat(loop_: &Loop, path: &CString) -> Result<FileStat, UvError> {
execute(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_stat(loop_.handle, req, path.with_ref(|p| p),
cb)
}).map(|req| req.mkstat())
}
pub fn write(loop_: &Loop, fd: c_int, buf: &[u8], offset: i64)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_write(loop_.handle, req,
fd, vec::raw::to_ptr(buf) as *c_void,
buf.len() as c_uint, offset, cb)
})
}
pub fn read(loop_: &Loop, fd: c_int, buf: &mut [u8], offset: i64)
-> Result<int, UvError>
{
do execute(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_read(loop_.handle, req,
fd, vec::raw::to_ptr(buf) as *c_void,
buf.len() as c_uint, offset, cb)
}).map |req| {
req.get_result() as int
}
}
pub fn close(loop_: &Loop, fd: c_int, sync: bool) -> Result<(), UvError> {
if sync {
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_close(loop_.handle, req, fd, cb)
})
} else {
unsafe {
let req = uvll::malloc_req(uvll::UV_FS);
2013-11-05 13:29:45 -06:00
uvll::uv_fs_close(loop_.handle, req, fd, close_cb);
return Ok(());
}
extern fn close_cb(req: *uvll::uv_fs_t) {
unsafe {
uvll::uv_fs_req_cleanup(req);
uvll::free_req(req);
}
}
}
}
pub fn mkdir(loop_: &Loop, path: &CString, mode: c_int)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_mkdir(loop_.handle, req, path.with_ref(|p| p),
mode, cb)
})
}
pub fn rmdir(loop_: &Loop, path: &CString) -> Result<(), UvError> {
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_rmdir(loop_.handle, req, path.with_ref(|p| p),
cb)
})
}
pub fn rename(loop_: &Loop, path: &CString, to: &CString)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_rename(loop_.handle,
req,
path.with_ref(|p| p),
to.with_ref(|p| p),
cb)
})
}
pub fn chmod(loop_: &Loop, path: &CString, mode: c_int)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_chmod(loop_.handle, req, path.with_ref(|p| p),
mode, cb)
})
}
pub fn readdir(loop_: &Loop, path: &CString, flags: c_int)
-> Result<~[Path], UvError>
{
execute(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_readdir(loop_.handle,
req, path.with_ref(|p| p), flags, cb)
}).map(|req| unsafe {
let mut paths = ~[];
let path = CString::new(path.with_ref(|p| p), false);
let parent = Path::new(path);
do c_str::from_c_multistring(req.get_ptr() as *libc::c_char,
Some(req.get_result() as uint)) |rel| {
let p = rel.as_bytes();
paths.push(parent.join(p.slice_to(rel.len())));
};
paths
})
}
pub fn readlink(loop_: &Loop, path: &CString) -> Result<Path, UvError> {
do execute(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_readlink(loop_.handle, req,
path.with_ref(|p| p), cb)
}).map |req| {
Path::new(unsafe {
CString::new(req.get_ptr() as *libc::c_char, false)
})
}
}
pub fn chown(loop_: &Loop, path: &CString, uid: int, gid: int)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_chown(loop_.handle,
req, path.with_ref(|p| p),
uid as uvll::uv_uid_t,
gid as uvll::uv_gid_t,
cb)
})
}
pub fn truncate(loop_: &Loop, file: c_int, offset: i64)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_ftruncate(loop_.handle, req, file, offset, cb)
})
}
pub fn link(loop_: &Loop, src: &CString, dst: &CString)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_link(loop_.handle, req,
src.with_ref(|p| p),
dst.with_ref(|p| p),
cb)
})
}
pub fn symlink(loop_: &Loop, src: &CString, dst: &CString)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_symlink(loop_.handle, req,
src.with_ref(|p| p),
dst.with_ref(|p| p),
0, cb)
})
}
pub fn fsync(loop_: &Loop, fd: c_int) -> Result<(), UvError> {
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_fsync(loop_.handle, req, fd, cb)
})
}
pub fn datasync(loop_: &Loop, fd: c_int) -> Result<(), UvError> {
execute_nop(|req, cb| unsafe {
2013-11-05 13:29:45 -06:00
uvll::uv_fs_fdatasync(loop_.handle, req, fd, cb)
})
}
pub fn utime(loop_: &Loop, path: &CString, atime: u64, mtime: u64)
-> Result<(), UvError>
{
execute_nop(|req, cb| unsafe {
uvll::uv_fs_utime(loop_.handle, req, path.with_ref(|p| p),
atime as libc::c_double, mtime as libc::c_double,
cb)
})
}
pub fn get_result(&self) -> c_int {
unsafe { uvll::get_result_from_fs_req(self.req) }
}
pub fn get_stat(&self) -> uvll::uv_stat_t {
let stat = uvll::uv_stat_t::new();
unsafe { uvll::populate_stat(self.req, &stat); }
stat
}
pub fn get_ptr(&self) -> *libc::c_void {
unsafe { uvll::get_ptr_from_fs_req(self.req) }
}
pub fn mkstat(&self) -> FileStat {
let path = unsafe { uvll::get_path_from_fs_req(self.req) };
let path = unsafe { Path::new(CString::new(path, false)) };
let stat = self.get_stat();
fn to_msec(stat: uvll::uv_timespec_t) -> u64 {
// Be sure to cast to u64 first to prevent overflowing if the tv_sec
// field is a 32-bit integer.
(stat.tv_sec as u64) * 1000 + (stat.tv_nsec as u64) / 1000000
}
let kind = match (stat.st_mode as c_int) & libc::S_IFMT {
libc::S_IFREG => io::TypeFile,
libc::S_IFDIR => io::TypeDirectory,
libc::S_IFIFO => io::TypeNamedPipe,
libc::S_IFBLK => io::TypeBlockSpecial,
libc::S_IFLNK => io::TypeSymlink,
_ => io::TypeUnknown,
};
FileStat {
path: path,
size: stat.st_size as u64,
kind: kind,
perm: (stat.st_mode as io::FilePermission) & io::AllPermissions,
created: to_msec(stat.st_birthtim),
modified: to_msec(stat.st_mtim),
accessed: to_msec(stat.st_atim),
unstable: io::UnstableFileStat {
device: stat.st_dev as u64,
inode: stat.st_ino as u64,
rdev: stat.st_rdev as u64,
nlink: stat.st_nlink as u64,
uid: stat.st_uid as u64,
gid: stat.st_gid as u64,
blksize: stat.st_blksize as u64,
blocks: stat.st_blocks as u64,
flags: stat.st_flags as u64,
gen: stat.st_gen as u64,
}
}
}
}
impl Drop for FsRequest {
fn drop(&mut self) {
unsafe {
if self.fired {
uvll::uv_fs_req_cleanup(self.req);
}
uvll::free_req(self.req);
}
}
}
fn execute(f: &fn(*uvll::uv_fs_t, uvll::uv_fs_cb) -> c_int)
-> Result<FsRequest, UvError>
{
let mut req = FsRequest {
fired: false,
req: unsafe { uvll::malloc_req(uvll::UV_FS) }
};
return match f(req.req, fs_cb) {
0 => {
req.fired = true;
let mut slot = None;
unsafe { uvll::set_data_for_req(req.req, &slot) }
let sched: ~Scheduler = Local::take();
do sched.deschedule_running_task_and_then |_, task| {
slot = Some(task);
}
match req.get_result() {
n if n < 0 => Err(UvError(n)),
_ => Ok(req),
}
}
n => Err(UvError(n))
};
extern fn fs_cb(req: *uvll::uv_fs_t) {
let slot: &mut Option<BlockedTask> = unsafe {
cast::transmute(uvll::get_data_for_req(req))
};
let sched: ~Scheduler = Local::take();
sched.resume_blocked_task_immediately(slot.take_unwrap());
}
}
fn execute_nop(f: &fn(*uvll::uv_fs_t, uvll::uv_fs_cb) -> c_int)
-> Result<(), UvError>
{
execute(f).map(|_| {})
}
impl HomingIO for FileWatcher {
fn home<'r>(&'r mut self) -> &'r mut SchedHandle { &mut self.home }
}
impl FileWatcher {
pub fn new(loop_: Loop, fd: c_int, close: rtio::CloseBehavior) -> FileWatcher {
FileWatcher {
loop_: loop_,
fd: fd,
close: close,
home: get_handle_to_current_scheduler!()
}
}
fn base_read(&mut self, buf: &mut [u8], offset: i64) -> Result<int, IoError> {
let _m = self.fire_missiles();
let r = FsRequest::read(&self.loop_, self.fd, buf, offset);
r.map_err(uv_error_to_io_error)
}
fn base_write(&mut self, buf: &[u8], offset: i64) -> Result<(), IoError> {
let _m = self.fire_missiles();
let r = FsRequest::write(&self.loop_, self.fd, buf, offset);
r.map_err(uv_error_to_io_error)
}
fn seek_common(&mut self, pos: i64, whence: c_int) ->
Result<u64, IoError>{
#[fixed_stack_segment]; #[inline(never)];
unsafe {
match libc::lseek(self.fd, pos as libc::off_t, whence) {
-1 => {
Err(IoError {
kind: io::OtherIoError,
desc: "Failed to lseek.",
detail: None
})
},
n => Ok(n as u64)
}
}
}
}
impl Drop for FileWatcher {
fn drop(&mut self) {
let _m = self.fire_missiles();
match self.close {
rtio::DontClose => {}
rtio::CloseAsynchronously => {
FsRequest::close(&self.loop_, self.fd, false);
}
rtio::CloseSynchronously => {
FsRequest::close(&self.loop_, self.fd, true);
}
}
}
}
impl rtio::RtioFileStream for FileWatcher {
fn read(&mut self, buf: &mut [u8]) -> Result<int, IoError> {
self.base_read(buf, -1)
}
fn write(&mut self, buf: &[u8]) -> Result<(), IoError> {
self.base_write(buf, -1)
}
fn pread(&mut self, buf: &mut [u8], offset: u64) -> Result<int, IoError> {
self.base_read(buf, offset as i64)
}
fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError> {
self.base_write(buf, offset as i64)
}
fn seek(&mut self, pos: i64, whence: io::SeekStyle) -> Result<u64, IoError> {
use std::libc::{SEEK_SET, SEEK_CUR, SEEK_END};
let whence = match whence {
io::SeekSet => SEEK_SET,
io::SeekCur => SEEK_CUR,
io::SeekEnd => SEEK_END
};
self.seek_common(pos, whence)
}
fn tell(&self) -> Result<u64, IoError> {
use std::libc::SEEK_CUR;
// this is temporary
let self_ = unsafe { cast::transmute_mut(self) };
self_.seek_common(0, SEEK_CUR)
}
fn fsync(&mut self) -> Result<(), IoError> {
let _m = self.fire_missiles();
FsRequest::fsync(&self.loop_, self.fd).map_err(uv_error_to_io_error)
}
fn datasync(&mut self) -> Result<(), IoError> {
let _m = self.fire_missiles();
FsRequest::datasync(&self.loop_, self.fd).map_err(uv_error_to_io_error)
}
fn truncate(&mut self, offset: i64) -> Result<(), IoError> {
let _m = self.fire_missiles();
let r = FsRequest::truncate(&self.loop_, self.fd, offset);
r.map_err(uv_error_to_io_error)
}
}
#[cfg(test)]
mod test {
use super::*;
//use std::rt::test::*;
use std::libc::{STDOUT_FILENO, c_int};
use std::vec;
use std::str;
use std::unstable::run_in_bare_thread;
use super::super::{Loop, Buf, slice_to_uv_buf};
use std::libc::{O_CREAT, O_RDWR, O_RDONLY, S_IWUSR, S_IRUSR};
#[test]
fn file_test_full_simple() {
do run_in_bare_thread {
let mut loop_ = Loop::new();
let create_flags = O_RDWR | O_CREAT;
let read_flags = O_RDONLY;
// 0644 BZZT! WRONG! 0600! See below.
let mode = S_IWUSR |S_IRUSR;
// these aren't defined in std::libc :(
//map_mode(S_IRGRP) |
//map_mode(S_IROTH);
let path_str = "./tmp/file_full_simple.txt";
let write_val = "hello".as_bytes().to_owned();
let write_buf = slice_to_uv_buf(write_val);
let write_buf_ptr: *Buf = &write_buf;
let read_buf_len = 1028;
let read_mem = vec::from_elem(read_buf_len, 0u8);
let read_buf = slice_to_uv_buf(read_mem);
let read_buf_ptr: *Buf = &read_buf;
let open_req = FsRequest::new();
do open_req.open(&loop_, &path_str.to_c_str(), create_flags as int,
mode as int) |req, uverr| {
assert!(uverr.is_none());
let fd = req.get_result();
let buf = unsafe { *write_buf_ptr };
let write_req = FsRequest::new();
do write_req.write(&req.get_loop(), fd, buf, -1) |req, uverr| {
let close_req = FsRequest::new();
do close_req.close(&req.get_loop(), fd) |req, _| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let open_req = FsRequest::new();
do open_req.open(&loop_, &path_str.to_c_str(),
read_flags as int,0) |req, uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let fd = req.get_result();
let read_buf = unsafe { *read_buf_ptr };
let read_req = FsRequest::new();
do read_req.read(&loop_, fd, read_buf, 0) |req, uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
// we know nread >=0 because uverr is none..
let nread = req.get_result() as uint;
// nread == 0 would be EOF
if nread > 0 {
let read_str = unsafe {
let read_buf = *read_buf_ptr;
str::from_utf8(
vec::from_buf(
read_buf.base, nread))
};
assert!(read_str == ~"hello");
let close_req = FsRequest::new();
do close_req.close(&loop_, fd) |req,uverr| {
assert!(uverr.is_none());
let loop_ = &req.get_loop();
let unlink_req = FsRequest::new();
do unlink_req.unlink(loop_,
&path_str.to_c_str())
|_,uverr| {
assert!(uverr.is_none());
};
};
};
};
};
};
};
};
loop_.run();
loop_.close();
}
}
#[test]
fn file_test_full_simple_sync() {
do run_in_bare_thread {
// setup
let mut loop_ = Loop::new();
let create_flags = O_RDWR |
O_CREAT;
let read_flags = O_RDONLY;
// 0644
let mode = S_IWUSR |
S_IRUSR;
//S_IRGRP |
//S_IROTH;
let path_str = "./tmp/file_full_simple_sync.txt";
let write_val = "hello".as_bytes().to_owned();
let write_buf = slice_to_uv_buf(write_val);
// open/create
let open_req = FsRequest::new();
let result = open_req.open_sync(&loop_, &path_str.to_c_str(),
create_flags as int, mode as int);
assert!(result.is_ok());
let fd = result.unwrap();
// write
let write_req = FsRequest::new();
let result = write_req.write_sync(&loop_, fd, write_buf, -1);
assert!(result.is_ok());
// close
let close_req = FsRequest::new();
let result = close_req.close_sync(&loop_, fd);
assert!(result.is_ok());
// re-open
let open_req = FsRequest::new();
let result = open_req.open_sync(&loop_, &path_str.to_c_str(),
read_flags as int,0);
assert!(result.is_ok());
let len = 1028;
let fd = result.unwrap();
// read
let read_mem: ~[u8] = vec::from_elem(len, 0u8);
let buf = slice_to_uv_buf(read_mem);
let read_req = FsRequest::new();
let result = read_req.read_sync(&loop_, fd, buf, 0);
assert!(result.is_ok());
let nread = result.unwrap();
// nread == 0 would be EOF.. we know it's >= zero because otherwise
// the above assert would fail
if nread > 0 {
let read_str = str::from_utf8(
read_mem.slice(0, nread as uint));
assert!(read_str == ~"hello");
// close
let close_req = FsRequest::new();
let result = close_req.close_sync(&loop_, fd);
assert!(result.is_ok());
// unlink
let unlink_req = FsRequest::new();
let result = unlink_req.unlink_sync(&loop_, &path_str.to_c_str());
assert!(result.is_ok());
} else { fail!("nread was 0.. wudn't expectin' that."); }
loop_.close();
}
}
fn naive_print(loop_: &Loop, input: &str) {
let write_val = input.as_bytes();
let write_buf = slice_to_uv_buf(write_val);
let write_req = FsRequest::new();
write_req.write_sync(loop_, STDOUT_FILENO, write_buf, -1);
}
#[test]
fn file_test_write_to_stdout() {
do run_in_bare_thread {
let mut loop_ = Loop::new();
naive_print(&loop_, "zanzibar!\n");
loop_.run();
loop_.close();
};
}
#[test]
fn file_test_stat_simple() {
do run_in_bare_thread {
let mut loop_ = Loop::new();
let path = "./tmp/file_test_stat_simple.txt";
let create_flags = O_RDWR |
O_CREAT;
let mode = S_IWUSR |
S_IRUSR;
let write_val = "hello".as_bytes().to_owned();
let write_buf = slice_to_uv_buf(write_val);
let write_buf_ptr: *Buf = &write_buf;
let open_req = FsRequest::new();
do open_req.open(&loop_, &path.to_c_str(), create_flags as int,
mode as int) |req, uverr| {
assert!(uverr.is_none());
let fd = req.get_result();
let buf = unsafe { *write_buf_ptr };
let write_req = FsRequest::new();
do write_req.write(&req.get_loop(), fd, buf, 0) |req, uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let stat_req = FsRequest::new();
do stat_req.stat(&loop_, &path.to_c_str()) |req, uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let stat = req.get_stat();
let sz: uint = stat.st_size as uint;
assert!(sz > 0);
let close_req = FsRequest::new();
do close_req.close(&loop_, fd) |req, uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let unlink_req = FsRequest::new();
do unlink_req.unlink(&loop_,
&path.to_c_str()) |req,uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let stat_req = FsRequest::new();
do stat_req.stat(&loop_,
&path.to_c_str()) |_, uverr| {
// should cause an error because the
// file doesn't exist anymore
assert!(uverr.is_some());
};
};
};
};
};
};
loop_.run();
loop_.close();
}
}
#[test]
fn file_test_mk_rm_dir() {
do run_in_bare_thread {
let mut loop_ = Loop::new();
let path = "./tmp/mk_rm_dir";
let mode = S_IWUSR |
S_IRUSR;
let mkdir_req = FsRequest::new();
do mkdir_req.mkdir(&loop_, &path.to_c_str(),
mode as c_int) |req,uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let stat_req = FsRequest::new();
do stat_req.stat(&loop_, &path.to_c_str()) |req, uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let stat = req.get_stat();
naive_print(&loop_, format!("{:?}", stat));
assert!(stat.is_dir());
let rmdir_req = FsRequest::new();
do rmdir_req.rmdir(&loop_, &path.to_c_str()) |req,uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let stat_req = FsRequest::new();
do stat_req.stat(&loop_, &path.to_c_str()) |_req, uverr| {
assert!(uverr.is_some());
}
}
}
}
loop_.run();
loop_.close();
}
}
#[test]
fn file_test_mkdir_chokes_on_double_create() {
do run_in_bare_thread {
let mut loop_ = Loop::new();
let path = "./tmp/double_create_dir";
let mode = S_IWUSR |
S_IRUSR;
let mkdir_req = FsRequest::new();
do mkdir_req.mkdir(&loop_, &path.to_c_str(), mode as c_int) |req,uverr| {
assert!(uverr.is_none());
let loop_ = req.get_loop();
let mkdir_req = FsRequest::new();
do mkdir_req.mkdir(&loop_, &path.to_c_str(),
mode as c_int) |req,uverr| {
assert!(uverr.is_some());
let loop_ = req.get_loop();
let _stat = req.get_stat();
let rmdir_req = FsRequest::new();
do rmdir_req.rmdir(&loop_, &path.to_c_str()) |req,uverr| {
assert!(uverr.is_none());
let _loop = req.get_loop();
}
}
}
loop_.run();
loop_.close();
}
}
#[test]
fn file_test_rmdir_chokes_on_nonexistant_path() {
do run_in_bare_thread {
let mut loop_ = Loop::new();
let path = "./tmp/never_existed_dir";
let rmdir_req = FsRequest::new();
do rmdir_req.rmdir(&loop_, &path.to_c_str()) |_req, uverr| {
assert!(uverr.is_some());
}
loop_.run();
loop_.close();
}
}
}