use crate::c_includes;
pub use c_includes::UDF_BLOCK_SIZE;
use std::ffi::CString;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::marker::PhantomData;
use std::os::raw::{c_int, c_void};
use std::path::Path;
fn str_to_mutf8(input: &str) -> CString {
let mut result = Vec::with_capacity(input.len() + 1);
for &byte in input.as_bytes() {
if byte == 0 {
result.push(0xC0);
result.push(0x80);
} else {
result.push(byte);
}
}
return CString::new(result).unwrap();
}
#[repr(C)]
struct InputAdapter<R: Read + Seek> {
funcs: c_includes::udfread_block_input,
reader: R,
}
impl<R: Read + Seek> InputAdapter<R> {
fn new(reader: R) -> *mut c_includes::udfread_block_input {
let funcs = c_includes::udfread_block_input {
size: Some(Self::c_size_in_blocks),
read: Some(Self::c_read_blocks),
close: Some(Self::c_free_reader),
};
Box::into_raw(Box::<Self>::new(Self { funcs, reader }))
as *mut c_includes::udfread_block_input
}
unsafe extern "C" fn c_size_in_blocks(ptr: *mut c_includes::udfread_block_input) -> u32 {
(*(ptr as *mut Self)).size_in_blocks()
}
fn size_in_blocks(&mut self) -> u32 {
self.reader
.seek(SeekFrom::End(0))
.map(|val| val / u64::from(UDF_BLOCK_SIZE))
.unwrap_or_else(|err| {
eprintln!("Error getting file size: {}", err);
0
})
.try_into()
.unwrap_or(0)
}
unsafe extern "C" fn c_read_blocks(
ptr: *mut c_includes::udfread_block_input,
block_offset: u32,
buf: *mut c_void,
num_blocks: u32,
_flags: c_int,
) -> c_int {
(*(ptr as *mut Self)).read_blocks(block_offset, buf as *mut u8, num_blocks)
}
fn read_blocks(&mut self, block_offset: u32, buf: *mut u8, num_blocks: u32) -> c_int {
let result: std::io::Result<usize> = (|| {
let byte_offset = u64::from(block_offset) * u64::from(UDF_BLOCK_SIZE);
let num_bytes = num_blocks as usize * UDF_BLOCK_SIZE as usize;
unsafe { std::ptr::write_bytes(buf, 0, num_bytes) };
let mut buffer = unsafe { std::slice::from_raw_parts_mut(buf, num_bytes) };
let mut total_bytes_read = 0usize;
self.reader.seek(SeekFrom::Start(byte_offset))?;
loop {
let bytes_read = self.reader.read(buffer)?;
if bytes_read == 0 {
break;
} else if bytes_read < buffer.len() {
total_bytes_read += bytes_read;
buffer = &mut buffer[bytes_read..];
} else {
total_bytes_read += buffer.len();
break;
}
}
Ok(total_bytes_read)
})();
match result {
Ok(read) => (read / UDF_BLOCK_SIZE as usize) as c_int,
Err(err) => {
eprintln!("Error reading file: {}", err);
-1
}
}
}
unsafe extern "C" fn c_free_reader(ptr: *mut c_includes::udfread_block_input) -> c_int {
Box::from_raw(ptr as *mut Self);
0
}
}
pub struct UdfRead<R: Read + Seek = File> {
udfread: *mut c_includes::udfread,
phantom: PhantomData<InputAdapter<R>>,
}
impl UdfRead<File> {
pub fn open_image_file(path: impl AsRef<Path>) -> Option<Self> {
let file = match File::open(path) {
Ok(val) => Some(val),
Err(err) => {
eprintln!("Error opening file: {}", err);
None
}
}?;
Self::open_image_reader(file)
}
}
impl<R: Read + Seek> UdfRead<R> {
pub fn open_image_reader(reader: R) -> Option<UdfRead<R>> {
let input_adapter = InputAdapter::new(reader);
let udfread = unsafe { c_includes::udfread_init() };
if udfread.is_null() {
return None;
}
let open_result = unsafe { c_includes::udfread_open_input(udfread, input_adapter) };
if open_result < 0 {
eprintln!("udfread_open_input failed");
unsafe { c_includes::udfread_close(udfread) };
return None;
}
Some(UdfRead {
udfread,
phantom: PhantomData,
})
}
pub fn open_file(&self, path: &str) -> Option<UdfFile> {
let mutf8_path = str_to_mutf8(path);
let udffile = unsafe { c_includes::udfread_file_open(self.udfread, mutf8_path.as_ptr()) };
if udffile.is_null() {
return None;
}
Some(UdfFile {
udffile,
phantom: PhantomData,
})
}
}
impl<R: Read + Seek> Drop for UdfRead<R> {
fn drop(&mut self) {
unsafe { c_includes::udfread_close(self.udfread) };
}
}
pub struct UdfFile<'a> {
udffile: *mut c_includes::UDFFILE,
phantom: PhantomData<&'a UdfRead>,
}
impl<'a> UdfFile<'a> {
pub fn size(&self) -> Option<u64> {
let result = unsafe { c_includes::udfread_file_size(self.udffile) };
if result < 0 {
None
} else {
Some(result as u64)
}
}
pub fn block_lba(&self, block: u32) -> Option<u32> {
let result = unsafe { c_includes::udfread_file_lba(self.udffile, block) };
if result == 0 {
None
} else {
Some(result)
}
}
}