diff --git a/src/pxar/fuse.rs b/src/pxar/fuse.rs index 2bc2d93a..4834b790 100644 --- a/src/pxar/fuse.rs +++ b/src/pxar/fuse.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::ffi::{CStr, CString, OsStr}; use std::fs::File; -use std::io::BufReader; +use std::io::{BufReader, Read, Seek}; use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::path::Path; use std::sync::Mutex; @@ -17,7 +17,7 @@ use libc; use libc::{c_char, c_int, c_void, size_t}; use super::decoder::Decoder; -use super::format_definition::{PxarEntry, PxarGoodbyeItem}; +use super::format_definition::{PxarAttributes, PxarEntry, PxarGoodbyeItem}; /// Node ID of the root i-node /// @@ -48,6 +48,8 @@ fn decoder_callback(_path: &Path) -> Result<(), Error> { Ok(()) } +type Inode = u64; +type Offset = u64; /// FFI types for easier readability type Request = *mut c_void; type MutPtr = *mut c_void; @@ -86,6 +88,19 @@ struct FuseArgs { allocated: c_int, } +/// Trait to create ReadSeek Decoder trait objects. +pub trait ReadSeek: Read + Seek {} +impl ReadSeek for T {} + +/// `Context` for callback functions providing the decoder, caches and the +/// offset within the archive for the i-node given by the caller. +struct Context { + decoder: Decoder, fn(&Path) -> Result<(), Error>>, + goodbye_cache: Option<(Inode, Vec<(PxarGoodbyeItem, Offset, Offset)>)>, + attr_cache: Option<(Inode, PxarAttributes)>, + ino_offset: Offset, +} + /// `Session` stores a pointer to the session context and is used to mount the /// archive to the given mountpoint. pub struct Session { @@ -186,19 +201,25 @@ impl Session { // By storing the decoder as userdata of the session, each request may // access it. - let reader = BufReader::new(file); + let reader: Box = Box::new(BufReader::new(file)); let decoder = Decoder::new(reader, decoder_callback as fn(&Path) -> Result<(), Error>)?; - let session_decoder = Box::new(Mutex::new(decoder)); + let ctx = Context { + decoder, + goodbye_cache: None, + attr_cache: None, + ino_offset: 0, + }; + let session_ctx = Box::new(Mutex::new(ctx)); let session_ptr = unsafe { fuse_session_new( Some(&args), Some(&oprs), std::mem::size_of::(), - // Ownership of session_decoder is passed to the session here. + // Ownership of session_ctx is passed to the session here. // It has to be reclaimed before dropping the session to free - // the decoder and close the underlying file. This is done inside + // the `Context` and close the underlying file. This is done inside // the destroy callback function. - Box::into_raw(session_decoder) as ConstPtr, + Box::into_raw(session_ctx) as ConstPtr, ) }; @@ -273,33 +294,29 @@ impl Drop for Session { } } -/// Creates a context providing an exclusive mutable reference to the decoder. +/// Creates a context providing an exclusive mutable reference to the `Context`. /// -/// Each callback function needing access to the decoder can easily get an +/// Each callback function needing access to the `Context` can easily get an /// exclusive handle by running the code inside this context. /// Responses with error code can easily be generated by returning with the /// error code. /// The error code will be used to reply to libfuse. fn run_in_context(req: Request, inode: u64, code: F) where - F: FnOnce( - &mut Decoder, fn(&Path) -> Result<(), Error>>, - u64, - ) -> Result<(), i32>, + F: FnOnce(&mut Context) -> Result<(), i32>, { - let ptr = unsafe { - fuse_req_userdata(req) - as *mut Mutex, fn(&Path) -> Result<(), Error>>> + let boxed_ctx = unsafe { + let ptr = fuse_req_userdata(req) as *mut Mutex; + Box::from_raw(ptr) }; - let boxed_decoder = unsafe { Box::from_raw(ptr) }; - let result = boxed_decoder + let result = boxed_ctx .lock() - .map(|mut decoder| { - let ino_offset = match inode { - FUSE_ROOT_ID => decoder.root_end_offset() - GOODBYE_ITEM_SIZE, + .map(|mut ctx| { + ctx.ino_offset = match inode { + FUSE_ROOT_ID => ctx.decoder.root_end_offset() - GOODBYE_ITEM_SIZE, _ => inode, }; - code(&mut decoder, ino_offset) + code(&mut ctx) }) .unwrap_or(Err(libc::EIO)); @@ -309,8 +326,8 @@ where } } - // Release ownership of boxed decoder, do not drop it. - let _ = Box::into_raw(boxed_decoder); + // Release ownership of boxed context, do not drop it. + let _ = Box::into_raw(boxed_ctx); } /// Return the correct offset for the item based on its `PxarEntry` mode @@ -345,12 +362,10 @@ extern "C" fn init(_decoder: MutPtr) { // Notting to do here for now } -/// Cleanup the userdata created while creating the session, which is the decoder -extern "C" fn destroy(decoder: MutPtr) { - // Get ownership of the decoder and drop it when Box goes out of scope. - unsafe { - Box::from_raw(decoder); - } +/// Cleanup the userdata created while creating the session, which is the `Context` +extern "C" fn destroy(ctx: MutPtr) { + // Get ownership of the `Context` and drop it when Box goes out of scope. + unsafe { Box::from_raw(ctx) }; } /// FUSE entry for fuse_reply_entry in lookup callback @@ -363,8 +378,28 @@ struct EntryParam { entry_timeout: f64, } +/// Update the goodbye table to the one corresponding to the i-node offset, both +/// given in the `Context`. +fn update_goodbye_cache(mut ctx: &mut Context) -> Result<(), i32> { + if let Some((off, _)) = &ctx.goodbye_cache { + if *off == ctx.ino_offset { + // Cache contains already the correct goodbye table + return Ok(()); + } + } + // Inode did not match or cache is empty, need to update the cache + let gbt = ctx.decoder + .goodbye_table(None, ctx.ino_offset + GOODBYE_ITEM_SIZE) + .map_err(|_| libc::EIO)?; + ctx.goodbye_cache = Some((ctx.ino_offset, gbt)); + + Ok(()) +} + /// Lookup the goodbye item identified by `filename` and its corresponding `hash` /// +/// Updates the goodbye table cache to contain the table for the directory given +/// by the i-node in the provided `Context`. /// Search the first matching `hash` in the goodbye table, allowing for a fast /// comparison with the items. /// As there could be a hash collision, the found items filename is then compared @@ -375,57 +410,60 @@ struct EntryParam { /// The matching items archive offset, entry and payload size are returned. /// If there is no entry with matching `filename` and `hash` a `libc::ENOENT` is /// returned. -fn search_in_goodbye( - mut decoder: &mut Decoder, fn(&Path) -> Result<(), Error>>, - goodbye_table: &[(PxarGoodbyeItem, u64, u64)], +fn find_goodbye_entry( + mut ctx: &mut Context, filename: &CStr, hash: u64, -) -> Result<(u64, PxarEntry, u64), i32> { - // Search for the first position with matching hash. - let position = goodbye_table - .iter() - .position(|(e, _, _)| e.hash == hash) - .ok_or(libc::ENOENT)?; +) -> Result<(u64, PxarEntry, PxarAttributes, u64), i32> { + update_goodbye_cache(&mut ctx)?; + if let Some((_, gbt)) = &ctx.goodbye_cache { + let mut iterator = gbt.iter(); + let mut position = 0; + loop { + // Search for the next position where the hash matches the one in the + // goodbye table. + position += iterator + .position(|(e, _, _)| e.hash == hash) + .ok_or(libc::ENOENT)?; - let (_item, start, end) = &goodbye_table[position]; - // At this point it is not clear if the item is a directory or not, this - // has to be decided based on the entry mode. - // `Decoder`s attributes function accepts both, offsets pointing to - // the start of an item (PXAR_FILENAME) or the GOODBYE_TAIL_MARKER in case - // of directories, so the use of start offset is fine for both cases. - let (entry_name, entry, _, payload_size) = decoder - .attributes(*start) - .map_err(|_| libc::EIO)?; + let (_item, start, end) = &gbt[position]; + // At this point it is not clear if the item is a directory or not, this + // has to be decided based on the entry mode. + // `Decoder`s attributes function accepts both, offsets pointing to + // the start of an item (PXAR_FILENAME) or the GOODBYE_TAIL_MARKER in case + // of directories, so the use of start offset is fine for both cases. + let (entry_name, entry, attr, payload_size) = + ctx.decoder.attributes(*start).map_err(|_| libc::EIO)?; - // Possible hash collision, need to check if the found entry is indeed - // the filename to lookup. - if entry_name.as_bytes() != filename.to_bytes() { - // Hash collision, continue with the search for the next matching item. - search_in_goodbye(&mut decoder, &goodbye_table[position + 1..goodbye_table.len()], filename, hash) - } else { - // No hash collision, get the correct offset based on the entry mode. - let child_offset = find_offset(&entry, *start, *end); - Ok((child_offset, entry, payload_size)) + // Possible hash collision, need to check if the found entry is indeed + // the filename to lookup. + if entry_name.as_bytes() == filename.to_bytes() { + let child_offset = find_offset(&entry, *start, *end); + return Ok((child_offset, entry, attr, payload_size)); + } + // Have to shift the position by one as next in iterator will be 0 + position += 1; + } } + + Err(libc::ENOENT) } -/// Lookup `name` in the directory referenced by `parent` inode. +/// Lookup `name` in the directory referenced by `parent` i-node. /// /// Inserts also the child and parent file offset in the hashmap to quickly /// obtain the parent offset based on the child offset. +/// Caches goodbye table of parent and attributes of child, if found. extern "C" fn lookup(req: Request, parent: u64, name: StrPtr) { let filename = unsafe { CStr::from_ptr(name) }; let hash = super::format_definition::compute_goodbye_hash(filename.to_bytes()); - run_in_context(req, parent, |mut decoder, ino_offset| { - let goodbye_table = decoder - .goodbye_table(None, ino_offset + GOODBYE_ITEM_SIZE) - .map_err(|_| libc::EIO)?; - - // Get the child item based on its hash and filename - let (child_offset, entry, payload_size) = - search_in_goodbye(&mut decoder, &goodbye_table[..], &filename, hash)?; - let child_inode = calculate_inode(child_offset, decoder.root_end_offset()); + run_in_context(req, parent, |mut ctx| { + // find_ goodbye_entry() will also update the goodbye cache + let (child_offset, entry, attr, payload_size) = + find_goodbye_entry(&mut ctx, &filename, hash)?; + ctx.attr_cache = Some((child_offset, attr)); + let child_inode = calculate_inode(child_offset, ctx.decoder.root_end_offset()); let e = EntryParam { inode: child_inode, @@ -440,7 +478,7 @@ extern "C" fn lookup(req: Request, parent: u64, name: StrPtr) { CHILD_PARENT .lock() .map_err(|_| libc::EIO)? - .insert(child_offset, ino_offset); + .insert(child_offset, ctx.ino_offset); let _res = unsafe { fuse_reply_entry(req, Some(&e)) }; Ok(()) @@ -470,8 +508,12 @@ fn stat(inode: u64, entry: &PxarEntry, payload_size: u64) -> Result {} @@ -643,7 +694,10 @@ extern "C" fn readdir(req: Request, inode: u64, size: size_t, offset: c_int, _fi // Add current directory entry "." if offset <= n_entries { - let (_, entry, _, payload_size) = decoder.attributes(ino_offset).map_err(|_| libc::EIO)?; + let (_, entry, _, payload_size) = ctx + .decoder + .attributes(ctx.ino_offset) + .map_err(|_| libc::EIO)?; // No need to calculate i-node for current dir, since it is given as parameter let attr = stat(inode, &entry, payload_size).map_err(|_| libc::EIO)?; let name = CString::new(".").unwrap(); @@ -657,13 +711,14 @@ extern "C" fn readdir(req: Request, inode: u64, size: size_t, offset: c_int, _fi // Add parent directory entry ".." if offset <= n_entries + 1 { let parent_off = if inode == FUSE_ROOT_ID { - decoder.root_end_offset() - GOODBYE_ITEM_SIZE + ctx.decoder.root_end_offset() - GOODBYE_ITEM_SIZE } else { let guard = CHILD_PARENT.lock().map_err(|_| libc::EIO)?; - *guard.get(&ino_offset).ok_or_else(|| libc::EIO)? + *guard.get(&ctx.ino_offset).ok_or_else(|| libc::EIO)? }; - let (_, entry, _, payload_size) = decoder.attributes(parent_off).map_err(|_| libc::EIO)?; - let item_inode = calculate_inode(parent_off, decoder.root_end_offset()); + let (_, entry, _, payload_size) = + ctx.decoder.attributes(parent_off).map_err(|_| libc::EIO)?; + let item_inode = calculate_inode(parent_off, ctx.decoder.root_end_offset()); let attr = stat(item_inode, &entry, payload_size).map_err(|_| libc::EIO)?; let name = CString::new("..").unwrap(); match buf.add_entry(&name, &attr) {