use libc::{c_char, FILE};
use std::ptr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("libc error: {0}")]
LibC(#[from] errno::Errno),
#[error("memstream invalid")]
MemStreamInvalid,
}
#[derive(Debug)]
pub(crate) struct MemStream {
pub(crate) fp: *mut FILE,
buf: Box<*mut c_char>,
buf_size: Box<usize>,
}
impl MemStream {
pub(crate) fn new() -> Result<Self, Error> {
let mut buf = Box::new(ptr::null_mut::<c_char>());
let mut buf_size = Box::new(0);
let fp = unsafe { libc::open_memstream(buf.as_mut(), buf_size.as_mut()) };
if fp.is_null() {
return Err(errno::errno().into());
}
let res = unsafe { libc::fflush(fp) };
if res != 0 {
unsafe { libc::fclose(fp) };
return Err(errno::errno().into());
}
if buf.is_null() {
unsafe { libc::fclose(fp) };
return Err(Error::MemStreamInvalid);
}
Ok(Self { fp, buf, buf_size })
}
}
impl AsRef<[u8]> for MemStream {
fn as_ref(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(*self.buf as _, *self.buf_size) }
}
}
impl Drop for MemStream {
fn drop(&mut self) {
unsafe {
libc::fclose(self.fp);
libc::free(*self.buf as _);
}
self.fp = ptr::null_mut();
*self.buf = ptr::null_mut();
*self.buf_size = 0x0;
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn hello_world() {
let stream = MemStream::new().unwrap();
let text = b"Hello, world!";
unsafe {
libc::fwrite(text.as_ptr() as _, 1, text.len(), stream.fp);
libc::fflush(stream.fp);
}
assert_eq!(stream.as_ref(), b"Hello, world!");
}
#[test]
fn no_flush() {
let stream = MemStream::new().unwrap();
let text = b"Hello, world!";
unsafe {
libc::fwrite(text.as_ptr() as _, 1, text.len(), stream.fp);
}
assert_eq!(stream.as_ref(), b"");
}
#[test]
fn create() {
let ms = MemStream::new().unwrap();
assert!(!ms.fp.is_null());
assert!(!ms.buf.is_null());
}
#[test]
fn drop() {
let ms = Box::leak(Box::new(MemStream::new().unwrap()));
let pms = ms as *mut MemStream;
unsafe {
std::ptr::drop_in_place(pms);
}
assert!(ms.fp.is_null());
}
}