#[macro_use]
extern crate log;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate nydus_error;
#[macro_use]
extern crate nydus_storage as storage;
use std::any::Any;
use std::borrow::Cow;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::fs::File;
use std::io::{BufWriter, Error, Read, Result, Seek, SeekFrom, Write};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::metadata::{RafsInodeExt, RafsSuper};
pub mod fs;
pub mod metadata;
#[cfg(test)]
pub mod mock;
#[derive(Debug)]
pub enum RafsError {
Unsupported,
Uninitialized,
AlreadyMounted,
ReadMetadata(Error, String),
LoadConfig(Error),
ParseConfig(serde_json::Error),
SwapBackend(Error),
FillSuperblock(Error),
CreateDevice(Error),
Prefetch(String),
Configure(String),
Incompatible(u16),
IllegalMetaStruct(MetaType, String),
InvalidImageData,
}
impl std::error::Error for RafsError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl Display for RafsError {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{:?}", self)?;
Ok(())
}
}
#[derive(Debug)]
pub enum MetaType {
Regular,
Dir,
Symlink,
}
pub type RafsResult<T> = std::result::Result<T, RafsError>;
pub type RafsIoReader = Box<dyn RafsIoRead>;
pub trait RafsIoRead: Read + AsRawFd + Seek + Send {}
impl RafsIoRead for File {}
pub type RafsIoWriter = Box<dyn RafsIoWrite>;
pub trait RafsIoWrite: Write + Seek + 'static {
fn as_any(&self) -> &dyn Any;
fn validate_alignment(&mut self, size: usize, alignment: usize) -> Result<usize> {
if alignment != 0 {
let cur = self.seek(SeekFrom::Current(0))?;
if (size & (alignment - 1) != 0) || (cur & (alignment as u64 - 1) != 0) {
return Err(einval!("unaligned data"));
}
}
Ok(size)
}
fn write_padding(&mut self, size: usize) -> Result<()> {
if size > WRITE_PADDING_DATA.len() {
return Err(einval!("invalid padding size"));
}
self.write_all(&WRITE_PADDING_DATA[0..size])
}
fn seek_to_end(&mut self) -> Result<u64> {
self.seek(SeekFrom::End(0)).map_err(|e| {
error!("Seeking to end fails, {}", e);
e
})
}
fn seek_offset(&mut self, offset: u64) -> Result<u64> {
self.seek(SeekFrom::Start(offset)).map_err(|e| {
error!("Seeking to offset {} from start fails, {}", offset, e);
e
})
}
fn seek_current(&mut self, offset: i64) -> Result<u64> {
self.seek(SeekFrom::Current(offset))
}
fn finalize(&mut self, _name: Option<String>) -> anyhow::Result<()> {
Ok(())
}
fn as_bytes(&mut self) -> std::io::Result<Cow<[u8]>> {
unimplemented!()
}
}
impl RafsIoWrite for File {
fn as_any(&self) -> &dyn Any {
self
}
}
impl RafsIoWrite for BufWriter<File> {
fn as_any(&self) -> &dyn Any {
self
}
}
const WRITE_PADDING_DATA: [u8; 64] = [0u8; 64];
impl dyn RafsIoRead {
pub fn seek_to_next_aligned(&mut self, last_read_len: usize, alignment: usize) -> Result<u64> {
let suffix = last_read_len & (alignment - 1);
let offset = if suffix == 0 { 0 } else { alignment - suffix };
self.seek(SeekFrom::Current(offset as i64)).map_err(|e| {
error!("Seeking to offset {} from current fails, {}", offset, e);
e
})
}
pub fn seek_plus_offset(&mut self, plus_offset: i64) -> Result<u64> {
self.seek(SeekFrom::Current(plus_offset)).map_err(|e| {
error!(
"Seeking to offset {} from current fails, {}",
plus_offset, e
);
e
})
}
pub fn seek_to_offset(&mut self, offset: u64) -> Result<u64> {
self.seek(SeekFrom::Start(offset)).map_err(|e| {
error!("Seeking to offset {} from start fails, {}", offset, e);
e
})
}
pub fn seek_to_end(&mut self, offset: i64) -> Result<u64> {
self.seek(SeekFrom::End(offset)).map_err(|e| {
error!("Seeking to end fails, {}", e);
e
})
}
pub fn from_file(path: impl AsRef<Path>) -> RafsResult<RafsIoReader> {
let f = File::open(&path).map_err(|e| {
RafsError::ReadMetadata(e, path.as_ref().to_string_lossy().into_owned())
})?;
Ok(Box::new(f))
}
}
pub struct RafsIterator<'a> {
_rs: &'a RafsSuper,
cursor_stack: Vec<(Arc<dyn RafsInodeExt>, PathBuf)>,
}
impl<'a> RafsIterator<'a> {
pub fn new(rs: &'a RafsSuper) -> Self {
let cursor_stack = match rs.get_extended_inode(rs.superblock.root_ino(), false) {
Ok(node) => {
let path = PathBuf::from("/");
vec![(node, path)]
}
Err(e) => {
error!(
"failed to get root inode from the bootstrap {}, damaged or malicious file?",
e
);
vec![]
}
};
RafsIterator {
_rs: rs,
cursor_stack,
}
}
}
impl<'a> Iterator for RafsIterator<'a> {
type Item = (Arc<dyn RafsInodeExt>, PathBuf);
fn next(&mut self) -> Option<Self::Item> {
let (node, path) = self.cursor_stack.pop()?;
if node.is_dir() {
let children = 0..node.get_child_count();
for idx in children.rev() {
if let Ok(child) = node.get_child_by_index(idx) {
let child_path = path.join(child.name());
self.cursor_stack.push((child, child_path));
} else {
error!(
"failed to get child inode from the bootstrap, damaged or malicious file?"
);
}
}
}
Some((node, path))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::RafsMode;
use std::fs::OpenOptions;
use vmm_sys_util::tempfile::TempFile;
#[test]
fn test_rafs_io_writer() {
let mut file = TempFile::new().unwrap().into_file();
assert!(file.validate_alignment(2, 8).is_err());
assert!(file.validate_alignment(7, 8).is_err());
assert!(file.validate_alignment(9, 8).is_err());
assert!(file.validate_alignment(8, 8).is_ok());
file.write_all(&[0x0u8; 7]).unwrap();
assert!(file.validate_alignment(8, 8).is_err());
{
let obj: &mut dyn RafsIoWrite = &mut file;
obj.write_padding(1).unwrap();
}
assert!(file.validate_alignment(8, 8).is_ok());
file.write_all(&[0x0u8; 1]).unwrap();
assert!(file.validate_alignment(8, 8).is_err());
let obj: &mut dyn RafsIoRead = &mut file;
assert_eq!(obj.seek_to_offset(0).unwrap(), 0);
assert_eq!(obj.seek_plus_offset(7).unwrap(), 7);
assert_eq!(obj.seek_to_next_aligned(7, 8).unwrap(), 8);
assert_eq!(obj.seek_plus_offset(7).unwrap(), 15);
}
#[test]
fn test_rafs_iterator() {
let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
let path = PathBuf::from(root_dir).join("../tests/texture/bootstrap/rafs-v5.boot");
let bootstrap = OpenOptions::new()
.read(true)
.write(false)
.open(&path)
.unwrap();
let mut rs = RafsSuper {
mode: RafsMode::Direct,
validate_digest: false,
..Default::default()
};
rs.load(&mut (Box::new(bootstrap) as RafsIoReader)).unwrap();
let iter = RafsIterator::new(&rs);
let mut last = false;
for (idx, (_node, path)) in iter.enumerate() {
assert!(!last);
if idx == 1 {
assert_eq!(path, PathBuf::from("/bin"));
} else if idx == 2 {
assert_eq!(path, PathBuf::from("/boot"));
} else if idx == 3 {
assert_eq!(path, PathBuf::from("/dev"));
} else if idx == 10 {
assert_eq!(path, PathBuf::from("/etc/DIR_COLORS.256color"));
} else if idx == 11 {
assert_eq!(path, PathBuf::from("/etc/DIR_COLORS.lightbgcolor"));
} else if path == PathBuf::from("/var/yp") {
last = true;
}
}
assert!(last);
}
}