use crate::{pool::Pool, status::Status};
use apr_sys;
use std::io::{Read, Write};
use std::path::Path;
pub use apr_sys::apr_file_t;
pub struct OpenFlags(i32);
impl OpenFlags {
pub const READ: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_READ as i32);
pub const WRITE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_WRITE as i32);
pub const CREATE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_CREATE as i32);
pub const APPEND: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_APPEND as i32);
pub const TRUNCATE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_TRUNCATE as i32);
pub const BINARY: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_BINARY as i32);
pub const EXCL: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_EXCL as i32);
pub const BUFFERED: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_BUFFERED as i32);
pub const DELONCLOSE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_DELONCLOSE as i32);
pub const XTHREAD: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_XTHREAD as i32);
pub const SHARELOCK: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_SHARELOCK as i32);
pub const NOCLEANUP: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_NOCLEANUP as i32);
pub const SENDFILE_ENABLED: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_SENDFILE_ENABLED as i32);
pub const LARGEFILE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_LARGEFILE as i32);
pub fn combine(flags: &[OpenFlags]) -> Self {
let combined = flags.iter().fold(0, |acc, flag| acc | flag.0);
OpenFlags(combined)
}
}
pub type FilePerms = apr_sys::apr_fileperms_t;
#[repr(transparent)]
pub struct File {
raw: *mut apr_sys::apr_file_t,
_no_send: std::marker::PhantomData<*mut ()>,
}
impl File {
pub fn open<P: AsRef<Path>>(
path: P,
flags: OpenFlags,
perms: FilePerms,
pool: &Pool<'_>,
) -> Result<Self, Status> {
let path_str = path.as_ref().to_string_lossy();
let path_cstr = alloc::ffi::CString::new(path_str.as_ref())
.map_err(|_| Status::from(apr_sys::APR_EINVAL as i32))?;
let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
let status = unsafe {
apr_sys::apr_file_open(
&mut file_ptr,
path_cstr.as_ptr(),
flags.0,
perms,
pool.as_mut_ptr(),
)
};
if status == apr_sys::APR_SUCCESS as i32 {
Ok(File {
raw: file_ptr,
_no_send: std::marker::PhantomData,
})
} else {
Err(Status::from(status))
}
}
pub fn temp_file(template: &str, flags: OpenFlags, pool: &Pool<'_>) -> Result<Self, Status> {
let template_cstr = alloc::ffi::CString::new(template)
.map_err(|_| Status::from(apr_sys::APR_EINVAL as i32))?;
let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
let status = unsafe {
apr_sys::apr_file_mktemp(
&mut file_ptr,
template_cstr.as_ptr() as *mut core::ffi::c_char,
flags.0,
pool.as_mut_ptr(),
)
};
if status == apr_sys::APR_SUCCESS as i32 {
Ok(File {
raw: file_ptr,
_no_send: std::marker::PhantomData,
})
} else {
Err(Status::from(status))
}
}
pub fn stdin(pool: &Pool<'_>) -> Result<Self, Status> {
let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
let status = unsafe { apr_sys::apr_file_open_stdin(&mut file_ptr, pool.as_mut_ptr()) };
if status == apr_sys::APR_SUCCESS as i32 {
Ok(File {
raw: file_ptr,
_no_send: std::marker::PhantomData,
})
} else {
Err(Status::from(status))
}
}
pub fn stdout(pool: &Pool<'_>) -> Result<Self, Status> {
let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
let status = unsafe { apr_sys::apr_file_open_stdout(&mut file_ptr, pool.as_mut_ptr()) };
if status == apr_sys::APR_SUCCESS as i32 {
Ok(File {
raw: file_ptr,
_no_send: std::marker::PhantomData,
})
} else {
Err(Status::from(status))
}
}
pub fn stderr(pool: &Pool<'_>) -> Result<Self, Status> {
let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
let status = unsafe { apr_sys::apr_file_open_stderr(&mut file_ptr, pool.as_mut_ptr()) };
if status == apr_sys::APR_SUCCESS as i32 {
Ok(File {
raw: file_ptr,
_no_send: std::marker::PhantomData,
})
} else {
Err(Status::from(status))
}
}
pub fn as_ptr(&self) -> *const apr_sys::apr_file_t {
self.raw
}
pub fn as_mut_ptr(&self) -> *mut apr_sys::apr_file_t {
self.raw
}
pub fn flush(&mut self) -> Result<(), Status> {
let status = unsafe { apr_sys::apr_file_flush(self.raw) };
if status == apr_sys::APR_SUCCESS as i32 {
Ok(())
} else {
Err(Status::from(status))
}
}
pub fn close(mut self) -> Result<(), Status> {
let status = unsafe { apr_sys::apr_file_close(self.raw) };
self.raw = std::ptr::null_mut();
if status == apr_sys::APR_SUCCESS as i32 {
Ok(())
} else {
Err(Status::from(status))
}
}
}
impl Drop for File {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe {
apr_sys::apr_file_close(self.raw);
}
}
}
}
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut bytes_read: apr_sys::apr_size_t = buf.len() as apr_sys::apr_size_t;
let status = unsafe {
apr_sys::apr_file_read(
self.raw,
buf.as_mut_ptr() as *mut core::ffi::c_void,
&mut bytes_read,
)
};
match status as u32 {
s if s == apr_sys::APR_SUCCESS => Ok(bytes_read as usize),
s if s == apr_sys::APR_EOF => Ok(0),
_ => Err(std::io::Error::other(Status::from(status))),
}
}
}
impl Write for File {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut bytes_written: apr_sys::apr_size_t = buf.len() as apr_sys::apr_size_t;
let status = unsafe {
apr_sys::apr_file_write(
self.raw,
buf.as_ptr() as *const core::ffi::c_void,
&mut bytes_written,
)
};
if status == apr_sys::APR_SUCCESS as i32 {
Ok(bytes_written as usize)
} else {
Err(std::io::Error::other(Status::from(status)))
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.flush().map_err(std::io::Error::other)
}
}
pub struct FileBuilder<'a> {
flags: OpenFlags,
perms: FilePerms,
pool: Option<&'a Pool<'a>>,
}
impl<'a> FileBuilder<'a> {
pub fn new() -> Self {
FileBuilder {
flags: OpenFlags(0),
perms: 0o644,
pool: None,
}
}
pub fn read(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::READ.0);
self
}
pub fn write(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::WRITE.0);
self
}
pub fn create(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::CREATE.0);
self
}
pub fn append(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::APPEND.0);
self
}
pub fn truncate(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::TRUNCATE.0);
self
}
pub fn binary(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::BINARY.0);
self
}
pub fn exclusive(mut self) -> Self {
self.flags = OpenFlags(self.flags.0 | OpenFlags::EXCL.0);
self
}
pub fn permissions(mut self, perms: FilePerms) -> Self {
self.perms = perms;
self
}
pub fn pool(mut self, pool: &'a Pool) -> Self {
self.pool = Some(pool);
self
}
pub fn open<P: AsRef<Path>>(self, path: P) -> Result<File, Status> {
let pool = self
.pool
.ok_or_else(|| Status::from(apr_sys::APR_EINVAL as i32))?;
File::open(path, self.flags, self.perms, pool)
}
}
impl<'a> Default for FileBuilder<'a> {
fn default() -> Self {
Self::new()
}
}
impl File {
pub fn builder() -> FileBuilder<'static> {
FileBuilder::new()
}
}
pub mod io {
use super::*;
use crate::error::{ErrorContext, Result};
use crate::pool;
pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
let path_ref = path.as_ref();
pool::with_tmp_pool(|pool| {
let mut file = File::open(path_ref, OpenFlags::READ, 0, pool)
.with_context(|| format!("Failed to open file for reading: {:?}", path_ref))?;
let mut s = String::new();
file.read_to_string(&mut s)
.with_context(|| format!("Failed to read file contents: {:?}", path_ref))?;
Ok(s)
})
}
pub fn write<P: AsRef<Path>>(path: P, contents: &str) -> Result<()> {
let path_ref = path.as_ref();
pool::with_tmp_pool(|pool| {
let mut file = File::open(
path_ref,
OpenFlags::combine(&[OpenFlags::WRITE, OpenFlags::CREATE, OpenFlags::TRUNCATE]),
0o644,
pool,
)
.with_context(|| format!("Failed to open file for writing: {:?}", path_ref))?;
file.write_all(contents.as_bytes())
.with_context(|| format!("Failed to write to file: {:?}", path_ref))?;
file.flush()
.with_context(|| format!("Failed to flush file: {:?}", path_ref))?;
Ok(())
})
}
pub fn copy<P1: AsRef<Path>, P2: AsRef<Path>>(from: P1, to: P2) -> Result<()> {
let from_ref = from.as_ref();
let to_ref = to.as_ref();
pool::with_tmp_pool(|pool| {
let mut src = File::open(from_ref, OpenFlags::READ, 0, pool)
.with_context(|| format!("Failed to open source file: {:?}", from_ref))?;
let mut dst = File::open(
to_ref,
OpenFlags::combine(&[OpenFlags::WRITE, OpenFlags::CREATE, OpenFlags::TRUNCATE]),
0o644,
pool,
)
.with_context(|| format!("Failed to open destination file: {:?}", to_ref))?;
std::io::copy(&mut src, &mut dst)
.with_context(|| format!("Failed to copy from {:?} to {:?}", from_ref, to_ref))?;
Ok(())
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
#[test]
fn test_file_open_write_read() {
let pool = Pool::new();
let temp_path = format!("./target/apr_test_file_{}", std::process::id());
{
let mut file = File::open(
&temp_path,
OpenFlags::combine(&[OpenFlags::WRITE, OpenFlags::CREATE, OpenFlags::TRUNCATE]),
apr_sys::APR_FPROT_OS_DEFAULT as i32,
&pool,
)
.expect("Failed to open file for writing");
file.write_all(b"Hello, APR!")
.expect("Failed to write to file");
file.flush().expect("Failed to flush file");
}
{
let mut file = File::open(&temp_path, OpenFlags::READ, 0, &pool)
.expect("Failed to open file for reading");
let mut buffer = String::new();
file.read_to_string(&mut buffer)
.expect("Failed to read from file");
assert_eq!(buffer, "Hello, APR!");
}
let _ = std::fs::remove_file(temp_path);
}
#[test]
fn test_temp_file() {
let pool = Pool::new();
let template = std::env::temp_dir().join("apr_temp_XXXXXX");
let template = template.to_str().unwrap().replace('\\', "/");
let mut temp_file =
File::temp_file(&template, OpenFlags(0), &pool).expect("Failed to create temp file");
temp_file
.write_all(b"Temporary data")
.expect("Failed to write to temp file");
}
#[test]
fn test_file_builder() {
let pool = Pool::new();
let result = File::builder()
.read()
.write()
.create()
.truncate()
.permissions(0o600)
.pool(&pool)
.open("./target/test_builder_file");
match result {
Ok(_) => {
let _ = std::fs::remove_file("./target/test_builder_file");
}
Err(_) => {
}
}
}
#[test]
fn test_open_flags_combine() {
let flags = OpenFlags::combine(&[OpenFlags::READ, OpenFlags::WRITE, OpenFlags::CREATE]);
let expected =
(apr_sys::APR_FOPEN_READ | apr_sys::APR_FOPEN_WRITE | apr_sys::APR_FOPEN_CREATE) as i32;
assert_eq!(flags.0, expected);
}
}