use std::ffi::{CStr, CString};
use std::fs::File;
use std::io;
use std::mem;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use haiku_sys::*;
use support::Flattenable;
use libc::{c_int, off_t, size_t, DIR};
pub struct AttributeDescriptor {
pub name: String,
pub size: i64,
pub raw_attribute_type: type_code,
}
enum FileDescriptor {
Owned(File),
Borrowed(c_int)
}
pub struct AttributeIterator {
dir: *mut DIR,
file: FileDescriptor,
}
impl Drop for AttributeIterator {
fn drop(&mut self) {
let _ = unsafe { fs_close_attr_dir(self.dir) };
}
}
impl Iterator for AttributeIterator {
type Item = io::Result<AttributeDescriptor>;
fn next(&mut self) -> Option<io::Result<AttributeDescriptor>> {
let ent = unsafe { fs_read_attr_dir(self.dir) };
if ent as u32 == 0 {
None
} else {
let fd = match self.file {
FileDescriptor::Owned(ref f) => f.as_raw_fd(),
FileDescriptor::Borrowed(ref f) => *f
};
let attr_name = unsafe {(*ent).d_name.as_ptr()};
let name_str = unsafe { CStr::from_ptr(attr_name) };
let str_buf: String = name_str.to_string_lossy().into_owned();
let mut attr_info_data = unsafe { mem::zeroed() };
let stat_result = unsafe {fs_stat_attr(fd, attr_name, &mut attr_info_data)};
if stat_result as i32 == -1 {
return Some(Err(io::Error::last_os_error()));
}
Some(Ok(AttributeDescriptor{name: str_buf, size: attr_info_data.size, raw_attribute_type: attr_info_data.attr_type}))
}
}
}
pub trait AttributeExt {
fn iter_attributes(&self) -> io::Result<AttributeIterator>;
fn find_attribute(&self, name: &str) -> io::Result<AttributeDescriptor>;
fn read_attribute_raw(&self, name: &str, raw_type: type_code, pos: off_t, size: i64) -> io::Result<Vec<u8>>;
fn write_attribute_raw(&self, name: &str, raw_type: type_code, pos: off_t, buffer: &[u8]) -> io::Result<()>;
fn remove_attribute(&self, name: &str) -> io::Result<()>;
fn read_attribute<T: Flattenable<T>>(&self, attribute: &AttributeDescriptor) -> io::Result<T> {
let value = self.read_attribute_raw(&attribute.name, attribute.raw_attribute_type, 0, 0);
if value.is_err() {
return Err(value.unwrap_err());
}
if T::type_code() != attribute.raw_attribute_type {
return Err(io::Error::new(io::ErrorKind::InvalidData, "type mismatch"));
}
let contents = T::unflatten(&value.unwrap());
match contents {
Ok(c) => Ok(c),
Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "error unflattening data"))
}
}
fn write_attribute<T: Flattenable<T>>(&self, name: &str, value: &T) -> io::Result<()> {
let data = value.flatten();
self.write_attribute_raw(name, T::type_code(), 0, &data)?;
Ok(())
}
}
impl AttributeExt for File {
fn iter_attributes(&self) -> io::Result<AttributeIterator> {
let fd = self.as_raw_fd();
let d = unsafe { fs_fopen_attr_dir(fd) };
if (d as u32) == 0 {
return Err(io::Error::last_os_error());
} else {
Ok(AttributeIterator{dir: d, file: FileDescriptor::Borrowed(fd)})
}
}
fn find_attribute(&self, name: &str) -> io::Result<AttributeDescriptor> {
let fd = self.as_raw_fd();
let mut attr_info_data = unsafe { mem::zeroed() };
let attr_name = CString::new(name).unwrap();
let stat_result = unsafe {fs_stat_attr(fd, attr_name.as_ptr(), &mut attr_info_data)};
if stat_result as i32 == -1 {
return Err(io::Error::last_os_error());
}
Ok(AttributeDescriptor{name: name.to_string(), size: attr_info_data.size, raw_attribute_type: attr_info_data.attr_type})
}
fn read_attribute_raw(&self, name: &str, _raw_type: u32, pos: off_t, size: i64) -> io::Result<Vec<u8>>{
let fd = self.as_raw_fd();
let descriptor = self.find_attribute(name)?;
if descriptor.size < pos {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "the position is higher than the size of the attribute"));
}
let attr_name = CString::new(descriptor.name).unwrap();
let len = if size > 0 {
size
} else {
descriptor.size - pos
};
let mut dst = Vec::with_capacity(descriptor.size as usize);
let read_size = unsafe { fs_read_attr(fd, attr_name.as_ptr(), descriptor.raw_attribute_type,
pos, dst.as_mut_ptr(), len as size_t) };
if read_size == -1 {
return Err(io::Error::last_os_error());
}
unsafe { dst.set_len(read_size as usize) };
Ok(dst)
}
fn write_attribute_raw(&self, name: &str, raw_type: u32, pos: off_t, buffer: &[u8]) -> io::Result<()> {
let fd = self.as_raw_fd();
let attr_name = CString::new(name).unwrap();
let write_size = unsafe { fs_write_attr(fd, attr_name.as_ptr(), raw_type, pos, buffer.as_ptr(), buffer.len() as size_t) };
if write_size < 0 || write_size as usize != buffer.len() {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn remove_attribute(&self, name: &str) -> io::Result<()> {
let fd = self.as_raw_fd();
let attr_name = CString::new(name).unwrap();
let result = unsafe { fs_remove_attr(fd, attr_name.as_ptr()) };
if result == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
impl AttributeExt for Path {
fn iter_attributes(&self) -> io::Result<AttributeIterator> {
let file = File::open(self)?;
let d = unsafe { fs_fopen_attr_dir(file.as_raw_fd()) };
if (d as u32) == 0 {
return Err(io::Error::last_os_error());
} else {
Ok(AttributeIterator{dir: d, file: FileDescriptor::Owned(file)})
}
}
fn find_attribute(&self, name: &str) -> io::Result<AttributeDescriptor> {
let file = File::open(self)?;
file.find_attribute(name)
}
fn read_attribute_raw(&self, name: &str, raw_type: u32, pos: off_t, size: i64) -> io::Result<Vec<u8>> {
let file = File::open(self)?;
file.read_attribute_raw(name, raw_type, pos, size)
}
fn write_attribute_raw(&self, name: &str, raw_type: u32, pos: off_t, buffer: &[u8]) -> io::Result<()> {
use std::fs::OpenOptions;
let file = OpenOptions::new().write(true).open(self)?;
file.write_attribute_raw(name, raw_type, pos, buffer)
}
fn remove_attribute(&self, name: &str) -> io::Result<()> {
use std::fs::OpenOptions;
let file = OpenOptions::new().write(true).open(self)?;
file.remove_attribute(name)
}
}
#[cfg(test)]
mod test {
extern crate tempfile;
use haiku_sys::B_STRING_TYPE;
use std::ffi::CStr;
use std::fs::File;
use std::path::Path;
use storage::attributes::AttributeExt;
#[test]
fn test_attribute_ext() {
let path = Path::new("/boot/system/apps/StyledEdit");
let file = File::open(&path).unwrap();
let mut attribute_iterator = file.iter_attributes().unwrap();
let attribute_descriptor = attribute_iterator.find(|attribute| attribute.as_ref().unwrap().name == "SYS:NAME").unwrap();
let attribute_data_raw = file.read_attribute_raw("SYS:NAME", 0, 0, 0).unwrap();
let attribute_data_cstring = CStr::from_bytes_with_nul(attribute_data_raw.as_slice()).unwrap();
let attribute_data = attribute_data_cstring.to_str().unwrap();
let attribute_data_higher_api = file.read_attribute::<String>(&attribute_descriptor.unwrap()).unwrap();
assert_eq!(attribute_data, attribute_data_higher_api);
let temporary_file = tempfile::NamedTempFile::new().unwrap();
let file = temporary_file.as_file();
let string_data = String::from("attribute test data");
let int_data: u8 = 15;
file.write_attribute("test_string", &string_data).unwrap();
file.write_attribute("test_u8", &int_data).unwrap();
let string_read = file.read_attribute_raw("test_string", B_STRING_TYPE, 3, 1).unwrap();
assert_eq!(string_read[0], 'r' as u8);
let int_attribute = file.find_attribute("test_u8").unwrap();
let int_read = file.read_attribute::<u8>(&int_attribute).unwrap();
assert_eq!(int_read, int_data);
file.remove_attribute("test_u8").unwrap();
assert!(file.find_attribute("test_u8").is_err());
let path = temporary_file.path();
let string_read = path.read_attribute_raw("test_string", B_STRING_TYPE, 3, 1).unwrap();
assert_eq!(string_read[0], 'r' as u8);
path.write_attribute("test_u8", &int_data).unwrap();
let int_read = path.read_attribute::<u8>(&int_attribute).unwrap();
assert_eq!(int_read, int_data);
path.remove_attribute("test_u8").unwrap();
assert!(path.find_attribute("test_u8").is_err());
}
}