// 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::ptr::null; use std::c_str; use std::c_str::CString; use std::libc::c_void; use std::cast::transmute; use std::libc; use std::libc::{c_int}; use super::{Request, NativeHandle, Loop, FsCallback, Buf, status_to_maybe_uv_error, UvError}; use uvll; use uvll::*; pub struct FsRequest(*uvll::uv_fs_t); impl Request for FsRequest {} pub struct RequestData { priv complete_cb: Option } impl FsRequest { pub fn new() -> FsRequest { let fs_req = unsafe { malloc_req(UV_FS) }; assert!(fs_req.is_not_null()); let fs_req: FsRequest = NativeHandle::from_native_handle(fs_req); fs_req } pub fn open(self, loop_: &Loop, path: &CString, flags: int, mode: int, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = path.with_ref(|p| unsafe { uvll::fs_open(loop_.native_handle(), self.native_handle(), p, flags, mode, complete_cb_ptr) }); assert_eq!(ret, 0); } pub fn open_sync(self, loop_: &Loop, path: &CString, flags: int, mode: int) -> Result { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(None) }; let result = path.with_ref(|p| unsafe { uvll::fs_open(loop_.native_handle(), self.native_handle(), p, flags, mode, complete_cb_ptr) }); self.sync_cleanup(result) } pub fn unlink(self, loop_: &Loop, path: &CString, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = path.with_ref(|p| unsafe { uvll::fs_unlink(loop_.native_handle(), self.native_handle(), p, complete_cb_ptr) }); assert_eq!(ret, 0); } pub fn unlink_sync(self, loop_: &Loop, path: &CString) -> Result { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(None) }; let result = path.with_ref(|p| unsafe { uvll::fs_unlink(loop_.native_handle(), self.native_handle(), p, complete_cb_ptr) }); self.sync_cleanup(result) } pub fn stat(self, loop_: &Loop, path: &CString, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = path.with_ref(|p| unsafe { uvll::fs_stat(loop_.native_handle(), self.native_handle(), p, complete_cb_ptr) }); assert_eq!(ret, 0); } pub fn write(self, loop_: &Loop, fd: c_int, buf: Buf, offset: i64, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let base_ptr = buf.base as *c_void; let len = buf.len as uint; let ret = unsafe { uvll::fs_write(loop_.native_handle(), self.native_handle(), fd, base_ptr, len, offset, complete_cb_ptr) }; assert_eq!(ret, 0); } pub fn write_sync(self, loop_: &Loop, fd: c_int, buf: Buf, offset: i64) -> Result { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(None) }; let base_ptr = buf.base as *c_void; let len = buf.len as uint; let result = unsafe { uvll::fs_write(loop_.native_handle(), self.native_handle(), fd, base_ptr, len, offset, complete_cb_ptr) }; self.sync_cleanup(result) } pub fn read(self, loop_: &Loop, fd: c_int, buf: Buf, offset: i64, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let buf_ptr = buf.base as *c_void; let len = buf.len as uint; let ret = unsafe { uvll::fs_read(loop_.native_handle(), self.native_handle(), fd, buf_ptr, len, offset, complete_cb_ptr) }; assert_eq!(ret, 0); } pub fn read_sync(self, loop_: &Loop, fd: c_int, buf: Buf, offset: i64) -> Result { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(None) }; let buf_ptr = buf.base as *c_void; let len = buf.len as uint; let result = unsafe { uvll::fs_read(loop_.native_handle(), self.native_handle(), fd, buf_ptr, len, offset, complete_cb_ptr) }; self.sync_cleanup(result) } pub fn close(self, loop_: &Loop, fd: c_int, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = unsafe { uvll::fs_close(loop_.native_handle(), self.native_handle(), fd, complete_cb_ptr) }; assert_eq!(ret, 0); } pub fn close_sync(self, loop_: &Loop, fd: c_int) -> Result { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(None) }; let result = unsafe { uvll::fs_close(loop_.native_handle(), self.native_handle(), fd, complete_cb_ptr) }; self.sync_cleanup(result) } pub fn mkdir(self, loop_: &Loop, path: &CString, mode: int, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = path.with_ref(|p| unsafe { uvll::fs_mkdir(loop_.native_handle(), self.native_handle(), p, mode, complete_cb_ptr) }); assert_eq!(ret, 0); } pub fn rmdir(self, loop_: &Loop, path: &CString, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = path.with_ref(|p| unsafe { uvll::fs_rmdir(loop_.native_handle(), self.native_handle(), p, complete_cb_ptr) }); assert_eq!(ret, 0); } pub fn readdir(self, loop_: &Loop, path: &CString, flags: c_int, cb: FsCallback) { let complete_cb_ptr = { let mut me = self; me.req_boilerplate(Some(cb)) }; let ret = path.with_ref(|p| unsafe { uvll::fs_readdir(loop_.native_handle(), self.native_handle(), p, flags, complete_cb_ptr) }); assert_eq!(ret, 0); } // accessors/utility funcs fn sync_cleanup(self, result: c_int) -> Result { self.cleanup_and_delete(); match status_to_maybe_uv_error(result as i32) { Some(err) => Err(err), None => Ok(result) } } fn req_boilerplate(&mut self, cb: Option) -> *u8 { let result = match cb { Some(_) => { compl_cb as *u8 }, None => 0 as *u8 }; self.install_req_data(cb); result } pub fn install_req_data(&mut self, cb: Option) { let fs_req = (self.native_handle()) as *uvll::uv_write_t; let data = ~RequestData { complete_cb: cb }; unsafe { let data = transmute::<~RequestData, *c_void>(data); uvll::set_data_for_req(fs_req, data); } } fn get_req_data<'r>(&'r mut self) -> &'r mut RequestData { unsafe { let data = uvll::get_data_for_req((self.native_handle())); let data = transmute::<&*c_void, &mut ~RequestData>(&data); &mut **data } } pub fn get_result(&mut self) -> c_int { unsafe { uvll::get_result_from_fs_req(self.native_handle()) } } pub fn get_loop(&self) -> Loop { unsafe { Loop{handle:uvll::get_loop_from_fs_req(self.native_handle())} } } pub fn get_stat(&self) -> uv_stat_t { let stat = uv_stat_t::new(); unsafe { uvll::populate_stat(self.native_handle(), &stat); } stat } pub fn get_ptr(&self) -> *libc::c_void { unsafe { uvll::get_ptr_from_fs_req(self.native_handle()) } } pub fn each_path(&mut self, f: &fn(&CString)) { let ptr = self.get_ptr(); match self.get_result() { n if (n <= 0) => {} n => { let n_len = n as uint; // we pass in the len that uv tells us is there // for the entries and we don't continue past that.. // it appears that sometimes the multistring isn't // correctly delimited and we stray into garbage memory? // in any case, passing Some(n_len) fixes it and ensures // good results unsafe { c_str::from_c_multistring(ptr as *libc::c_char, Some(n_len), f); } } } } fn cleanup_and_delete(self) { unsafe { let data = uvll::get_data_for_req(self.native_handle()); let _data = transmute::<*c_void, ~RequestData>(data); uvll::set_data_for_req(self.native_handle(), null::<()>()); uvll::fs_req_cleanup(self.native_handle()); free_req(self.native_handle() as *c_void) } } } impl NativeHandle<*uvll::uv_fs_t> for FsRequest { fn from_native_handle(handle: *uvll:: uv_fs_t) -> FsRequest { FsRequest(handle) } fn native_handle(&self) -> *uvll::uv_fs_t { match self { &FsRequest(ptr) => ptr } } } fn sync_cleanup(result: int) -> Result { match status_to_maybe_uv_error(result as i32) { Some(err) => Err(err), None => Ok(result) } } extern fn compl_cb(req: *uv_fs_t) { let mut req: FsRequest = NativeHandle::from_native_handle(req); // pull the user cb out of the req data let cb = { let data = req.get_req_data(); assert!(data.complete_cb.is_some()); // option dance, option dance. oooooh yeah. data.complete_cb.take_unwrap() }; // in uv_fs_open calls, the result will be the fd in the // case of success, otherwise it's -1 indicating an error let result = req.get_result(); let status = status_to_maybe_uv_error(result); // we have a req and status, call the user cb.. // only giving the user a ref to the FsRequest, as we // have to clean it up, afterwards (and they aren't really // reusable, anyways cb(&mut req, status); // clean up the req (and its data!) after calling the user cb req.cleanup_and_delete(); } #[cfg(test)] mod test { use super::*; //use std::rt::test::*; use std::libc::{STDOUT_FILENO}; 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 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 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 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(); } } }