// 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::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; 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 { execute(|req, cb| unsafe { 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 { uvll::uv_fs_unlink(loop_.handle, req, path.with_ref(|p| p), cb) }) } pub fn lstat(loop_: &Loop, path: &CString) -> Result { execute(|req, cb| unsafe { 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 { execute(|req, cb| unsafe { 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 { 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 { do execute(|req, cb| unsafe { 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 { uvll::uv_fs_close(loop_.handle, req, fd, cb) }) } else { unsafe { let req = uvll::malloc_req(uvll::UV_FS); 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 { 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 { 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 { 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 { 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 { 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 { do execute(|req, cb| unsafe { 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 { 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 { 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 { 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 { 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 { uvll::uv_fs_fsync(loop_.handle, req, fd, cb) }) } pub fn datasync(loop_: &Loop, fd: c_int) -> Result<(), UvError> { execute_nop(|req, cb| unsafe { uvll::uv_fs_fdatasync(loop_.handle, req, fd, 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 { 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 = 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 { 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{ #[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 { 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 { 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 { 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 { 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(); } } }