Skip to content

Instantly share code, notes, and snippets.

@GuillaumeGomez
Created February 21, 2017 22:47
Show Gist options
  • Save GuillaumeGomez/455cdda98a055c21581170402efa2662 to your computer and use it in GitHub Desktop.
Save GuillaumeGomez/455cdda98a055c21581170402efa2662 to your computer and use it in GitHub Desktop.
#![deny(missing_docs)]
#![deny(warnings)]
//! A crate to provide help to perform async I/O on files using `tokio`.
extern crate libc;
use std::fs::{File, OpenOptions, create_dir_all, remove_file};
use std::io;
use std::path::{Path, PathBuf};
use std::os::unix::io::AsRawFd;
use libc::c_int;
#[cfg(not(target_os = "macos"))]
fn sendfile(in_fd: c_int, out_fd: c_int, size: u64) -> bool {
let mut remaining = size as usize;
while remaining > 0 {
let wrote = unsafe {
libc::sendfile(out_fd, in_fd, ::std::ptr::null_mut(), remaining)
};
if wrote == -1 {
unsafe { libc::perror(b"sendfile\0".as_ptr() as *const i8) };
return false;
}
remaining -= wrote as usize;
}
true
}
#[cfg(target_os = "macos")]
fn sendfile(in_fd: c_int, out_fd: c_int, mut size: u64) -> bool {
let mut buf = [0u8; 512*1024];
while size > 0 {
unsafe {
let ret = libc::read(in_fd, buf.as_mut_ptr() as *mut libc::c_void,
if size > buf.len() as u64 { buf.len() } else { size as usize });
if ret == -1 {
libc::perror(b"read\0".as_ptr() as *const i8);
return false;
}
size -= ret as u64;
let mut pos = 0;
while pos < ret {
let res = libc::write(out_fd, buf.as_ptr().offset(pos) as *const libc::c_void,
ret as usize - pos as usize);
if res == -1 {
libc::perror(b"write\0".as_ptr() as *const i8);
return false;
}
pos += res;
}
}
}
true
}
fn send_data<F: AsRawFd, G: AsRawFd>(in_fd: &F, out_fd: &G, size: u64) -> bool {
sendfile(in_fd.as_raw_fd(), out_fd.as_raw_fd(), size)
}
/// This is mostly a wrapper around the `fs::File` struct to perform conversions
/// to tokio types.
#[derive(Clone)]
pub struct ReaderWriter {
folder: PathBuf,
}
impl ReaderWriter {
/// Open a folder or create it if it doesn't exist. It doesn't truncate.
pub fn new<P: AsRef<Path>>(path: &P) -> io::Result<ReaderWriter> {
if path.as_ref().is_dir() || create_dir_all(path).is_ok() {
Ok(ReaderWriter {
folder: path.as_ref().to_path_buf(),
})
} else {
Err(io::Error::new(io::ErrorKind::Other, "Path isn't a directory"))
}
}
/// Write `size` bytes from `socket` into the corresponding `file_id`.
pub fn write_to_file<P: AsRef<Path>, F: AsRawFd>(&self, file_id: &P, socket: &F,
size: u64) -> Option<bool> {
if let Ok(file) = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(self.folder.join(file_id)) {
Some(self.write(socket, &file, size))
} else {
None
}
}
/// Write `size` bytes from `in_fd` into `out_fd`.
pub fn write<F: AsRawFd, G: AsRawFd>(&self, in_fd: &F, out_fd: &G, size: u64) -> bool {
send_data(in_fd, out_fd, size)
}
/// Returns a `File` with read/write access and creates it if it doesn't exist.
pub fn get_file<P: AsRef<Path>>(&self, file_id: &P, truncate: bool) -> Option<File> {
OpenOptions::new()
.write(true)
.create(true)
.read(true)
.truncate(truncate)
.open(self.folder.join(file_id))
.ok()
}
/// Remove the corresponding file.
pub fn remove<P: AsRef<Path>>(&self, file_id: &P) -> io::Result<()> {
remove_file(self.folder.join(file_id))
}
/// Checks if the corresponding file exists
pub fn exists<P: AsRef<Path>>(&self, file_id: &P) -> bool {
let path = self.folder.join(file_id);
path.exists() && path.is_file()
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use self::tempdir::TempDir;
use ::ReaderWriter;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
fn write_in_file<P: AsRef<Path>>(p: &P, data: &str) {
let mut tmp_file = File::create(p).expect("create temp file");
write!(tmp_file, "{}", data).expect("write temp file");
}
#[test]
fn basic() {
let test_name = "basic";
let tmp_dir = TempDir::new(&test_name).expect(&format!("{}: create temp dir", &test_name));
let path = tmp_dir.path();
let rw = ReaderWriter::new(&path).expect(&format!("{}: create ReaderWriter", &test_name));
let x = "asjdfkjasdbfkasdbfkasbdkjfbasdbf";
write_in_file(&tmp_dir.path().join("in"), x);
let tmp_file = File::open(tmp_dir.path().join("in")).expect("Open failed");
assert_eq!(rw.write_to_file(&"out", &tmp_file, x.len() as u64), Some(true));
let file_path = tmp_dir.path().join("out");
let mut f = File::open(file_path).expect("file open");
let mut buffer = String::new();
f.read_to_string(&mut buffer).expect("read_to_string");
assert_eq!(&buffer, x);
}
#[test]
fn basic2() {
let test_name = "basic2";
let tmp_dir = TempDir::new(&test_name).expect(&format!("{}: create temp dir", &test_name));
let path = tmp_dir.path();
let rw = ReaderWriter::new(&path).expect(&format!("{}: create ReaderWriter", &test_name));
let x = "asjdfkjasdbfkasdbfkasbdkjfbasdbf";
write_in_file(&tmp_dir.path().join("in"), x);
let tmp_file = File::open(tmp_dir.path().join("in")).expect("Open failed");
let file_path = tmp_dir.path().join("out");
{
let out_file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file_path)
.expect("OpenOptions failed");
assert_eq!(rw.write(&tmp_file, &out_file, x.len() as u64), true);
}
let mut f = File::open(file_path).expect("file open");
let mut buffer = String::new();
f.read_to_string(&mut buffer).expect("read_to_string");
assert_eq!(&buffer, x);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment