diff --git a/src/backup/catalog_shell.rs b/src/backup/catalog_shell.rs index 9a7e5248..90aaeb4d 100644 --- a/src/backup/catalog_shell.rs +++ b/src/backup/catalog_shell.rs @@ -21,7 +21,7 @@ pub struct Shell { /// List of paths selected for a restore selected: HashSet>, /// Decoder instance for the current pxar archive - decoder: Decoder, fn(&Path) -> Result<(), Error>>, + decoder: Decoder>, /// Root directory for the give archive as stored in the catalog root: Vec, } @@ -60,7 +60,7 @@ impl Shell { pub fn new( mut catalog: CatalogReader, archive_name: &str, - decoder: Decoder, fn(&Path) -> Result<(), Error>> + decoder: Decoder> ) -> Result { let catalog_root = catalog.root()?; // The root for the given archive as stored in the catalog diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 4f362fa3..779645f8 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1032,7 +1032,8 @@ async fn restore_do(param: Value) -> Result { if let Some(target) = target { let feature_flags = pxar::flags::DEFAULT; - let mut decoder = pxar::SequentialDecoder::new(&mut reader, feature_flags, |path| { + let mut decoder = pxar::SequentialDecoder::new(&mut reader, feature_flags); + decoder.set_callback(move |path| { if verbose { eprintln!("{:?}", path); } @@ -1706,11 +1707,8 @@ async fn mount_do(param: Value, pipe: Option) -> Result { let most_used = index.find_most_used_chunks(8); let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, most_used); let reader = BufferedDynamicReader::new(index, chunk_reader); - let decoder = - pxar::Decoder::, fn(&Path) -> Result<(), Error>>::new( - Box::new(reader), - |_| Ok(()), - )?; + //let decoder = pxar::Decoder::new(Box::::new(reader))?; + let decoder = pxar::Decoder::new(reader)?; let options = OsStr::new("ro,default_permissions"); let mut session = pxar::fuse::Session::from_decoder(decoder, &options, pipe.is_none()) .map_err(|err| format_err!("pxar mount failed: {}", err))?; @@ -1822,14 +1820,11 @@ async fn catalog_shell(param: Value) -> Result { let most_used = index.find_most_used_chunks(8); let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config.clone(), most_used); let reader = BufferedDynamicReader::new(index, chunk_reader); - let decoder = - pxar::Decoder::, fn(&Path) -> Result<(), Error>>::new( - reader, - |path| { - println!("{:?}", path); - Ok(()) - } - )?; + let mut decoder = pxar::Decoder::new(reader)?; + decoder.set_callback(|path| { + println!("{:?}", path); + Ok(()) + }); let tmpfile = client.download(CATALOG_NAME, tmpfile).await?; let index = DynamicIndexReader::new(tmpfile) diff --git a/src/bin/pxar.rs b/src/bin/pxar.rs index 49eb1c98..0f8d1c0a 100644 --- a/src/bin/pxar.rs +++ b/src/bin/pxar.rs @@ -26,7 +26,7 @@ fn dump_archive_from_reader( feature_flags: u64, verbose: bool, ) -> Result<(), Error> { - let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |_| Ok(())); + let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags); let stdout = std::io::stdout(); let mut out = stdout.lock(); @@ -70,7 +70,8 @@ fn extract_archive_from_reader( verbose: bool, pattern: Option> ) -> Result<(), Error> { - let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |path| { + let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags); + decoder.set_callback(move |path| { if verbose { println!("{:?}", path); } diff --git a/src/client/pxar_decode_writer.rs b/src/client/pxar_decode_writer.rs index efb1c708..73b88137 100644 --- a/src/client/pxar_decode_writer.rs +++ b/src/client/pxar_decode_writer.rs @@ -31,7 +31,8 @@ impl PxarDecodeWriter { let child = thread::spawn(move|| { let mut reader = unsafe { std::fs::File::from_raw_fd(rx) }; - let mut decoder = pxar::SequentialDecoder::new(&mut reader, pxar::flags::DEFAULT, |path| { + let mut decoder = pxar::SequentialDecoder::new(&mut reader, pxar::flags::DEFAULT); + decoder.set_callback(move |path| { if verbose { println!("{:?}", path); } diff --git a/src/pxar/decoder.rs b/src/pxar/decoder.rs index 60a19ba6..598fab0a 100644 --- a/src/pxar/decoder.rs +++ b/src/pxar/decoder.rs @@ -27,8 +27,8 @@ pub struct DirectoryEntry { } // This one needs Read+Seek -pub struct Decoder Result<(), Error>> { - inner: SequentialDecoder, +pub struct Decoder { + inner: SequentialDecoder, root_start: u64, root_end: u64, } @@ -36,15 +36,16 @@ pub struct Decoder Result<(), Error>> { const HEADER_SIZE: u64 = std::mem::size_of::() as u64; const GOODBYE_ITEM_SIZE: u64 = std::mem::size_of::() as u64; -impl Result<(), Error>> Decoder { - pub fn new(mut reader: R, callback: F) -> Result { +impl Decoder { + pub fn new(mut reader: R) -> Result { let root_end = reader.seek(SeekFrom::End(0))?; + let inner = SequentialDecoder::new(reader, super::flags::DEFAULT); + + Ok(Self { inner, root_start: 0, root_end }) + } - Ok(Self { - inner: SequentialDecoder::new(reader, super::flags::DEFAULT, callback), - root_start: 0, - root_end, - }) + pub fn set_callback Result<(), Error> + 'static>(&mut self, callback: F ) { + self.inner.set_callback(callback); } pub fn root(&mut self) -> Result { diff --git a/src/pxar/fuse.rs b/src/pxar/fuse.rs index dc597c18..a790cf41 100644 --- a/src/pxar/fuse.rs +++ b/src/pxar/fuse.rs @@ -41,13 +41,6 @@ lazy_static! { static ref CHILD_PARENT: Mutex> = Mutex::new(HashMap::new()); } -/// Callback function for `super::decoder::Decoder`. -/// -/// At the moment, this is only needed to satisfy the `SequentialDecoder`. -fn decoder_callback(_path: &Path) -> Result<(), Error> { - Ok(()) -} - type Inode = u64; type Offset = u64; /// FFI types for easier readability @@ -88,14 +81,10 @@ 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>>, +struct Context { + decoder: Decoder, goodbye_cache: Option<(Inode, Vec<(PxarGoodbyeItem, Offset, Offset)>)>, attr_cache: Option<(Inode, PxarAttributes)>, ino_offset: Offset, @@ -103,9 +92,10 @@ struct Context { /// `Session` stores a pointer to the session context and is used to mount the /// archive to the given mountpoint. -pub struct Session { +pub struct Session { ptr: MutPtr, verbose: bool, + _phantom: std::marker::PhantomData, } /// `Operations` defines the callback function table of supported operations. @@ -161,7 +151,27 @@ struct Operations { copy_file_range: Option, } -impl Session { +impl Session> { + /// Create a new low level fuse session. + /// + /// `Session` is created using the provided mount options and sets the + /// default signal handlers. + /// Options have to be provided as comma separated OsStr, e.g. + /// ("ro,default_permissions"). + pub fn new(archive_path: &Path, options: &OsStr, verbose: bool) -> Result { + let file = File::open(archive_path)?; + let args = Self::setup_args(options, verbose)?; + let oprs = Self::setup_callbacks(); + + // By storing the decoder as userdata of the session, each request may + // access it. + let reader = BufReader::new(file); + let decoder = Decoder::new(reader)?; + Self::setup_session(decoder, args, oprs, verbose) + } +} + +impl Session { fn setup_args(options: &OsStr, verbose: bool) -> Result, Error> { // First argument should be the executable name let mut arguments = vec![ @@ -181,18 +191,18 @@ impl Session { let mut oprs = Operations::default(); oprs.init = Some(init); oprs.destroy = Some(destroy); - oprs.lookup = Some(lookup); - oprs.getattr = Some(getattr); - oprs.readlink = Some(readlink); - oprs.open = Some(open); - oprs.read = Some(read); - oprs.opendir = Some(opendir); - oprs.readdir = Some(readdir); + oprs.lookup = Some(Self::lookup); + oprs.getattr = Some(Self::getattr); + oprs.readlink = Some(Self::readlink); + oprs.open = Some(Self::open); + oprs.read = Some(Self::read); + oprs.opendir = Some(Self::opendir); + oprs.readdir = Some(Self::readdir); oprs } fn setup_session( - decoder: Decoder, fn(&Path) -> Result<(), Error>>, + decoder: Decoder, args: Vec, oprs: Operations, verbose: bool, @@ -234,27 +244,10 @@ impl Session { Ok(Self { ptr: session_ptr, verbose, + _phantom: std::marker::PhantomData, }) } - /// Create a new low level fuse session. - /// - /// `Session` is created using the provided mount options and sets the - /// default signal handlers. - /// Options have to be provided as comma separated OsStr, e.g. - /// ("ro,default_permissions"). - pub fn new(archive_path: &Path, options: &OsStr, verbose: bool) -> Result { - let file = File::open(archive_path)?; - let args = Self::setup_args(options, verbose)?; - let oprs = Self::setup_callbacks(); - - // By storing the decoder as userdata of the session, each request may - // access it. - let reader: Box = Box::new(BufReader::new(file)); - let decoder = Decoder::new(reader, decoder_callback as fn(&Path) -> Result<(), Error>)?; - Self::setup_session(decoder, args, oprs, verbose) - } - /// Create a new low level fuse session using the given `Decoder`. /// /// `Session` is created using the provided mount options and sets the @@ -262,7 +255,7 @@ impl Session { /// Options have to be provided as comma separated OsStr, e.g. /// ("ro,default_permissions"). pub fn from_decoder( - decoder: Decoder, fn(&Path) -> Result<(), Error>>, + decoder: Decoder, options: &OsStr, verbose: bool, ) -> Result { @@ -316,9 +309,230 @@ impl Session { Ok(()) } + + /// Creates a context providing an exclusive mutable reference to the `Context`. + /// + /// 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 Context) -> Result<(), i32>, + { + let boxed_ctx = unsafe { + let ptr = fuse_req_userdata(req) as *mut Mutex>; + Box::from_raw(ptr) + }; + let result = boxed_ctx + .lock() + .map(|mut ctx| { + ctx.ino_offset = match inode { + FUSE_ROOT_ID => ctx.decoder.root_end_offset() - GOODBYE_ITEM_SIZE, + _ => inode, + }; + code(&mut ctx) + }) + .unwrap_or(Err(libc::EIO)); + + if let Err(err) = result { + unsafe { + let _res = fuse_reply_err(req, err); + } + } + + // Release ownership of boxed context, do not drop it. + let _ = Box::into_raw(boxed_ctx); + } + + /// 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()); + + Self::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, + generation: 1, + attr: stat(child_inode, &entry, payload_size)?, + attr_timeout: std::f64::MAX, + entry_timeout: std::f64::MAX, + }; + + // Update the parent for this child entry. Used to get parent offset if + // only child offset is known. + CHILD_PARENT + .lock() + .map_err(|_| libc::EIO)? + .insert(child_offset, ctx.ino_offset); + let _res = unsafe { fuse_reply_entry(req, Some(&e)) }; + + Ok(()) + }); + } + + extern "C" fn getattr(req: Request, inode: u64, _fileinfo: MutPtr) { + Self::run_in_context(req, inode, |ctx| { + let (_, entry, attr, payload_size) = ctx + .decoder + .attributes(ctx.ino_offset) + .map_err(|_| libc::EIO)?; + ctx.attr_cache = Some((ctx.ino_offset, attr)); + let attr = stat(inode, &entry, payload_size)?; + let _res = unsafe { + // Since fs is read-only, the timeout can be max. + let timeout = std::f64::MAX; + fuse_reply_attr(req, Some(&attr), timeout) + }; + + Ok(()) + }); + } + + extern "C" fn readlink(req: Request, inode: u64) { + Self::run_in_context(req, inode, |ctx| { + let (target, _) = ctx + .decoder + .read_link(ctx.ino_offset) + .map_err(|_| libc::EIO)?; + let link = CString::new(target.into_os_string().into_vec()).map_err(|_| libc::EIO)?; + let _ret = unsafe { fuse_reply_readlink(req, link.as_ptr()) }; + + Ok(()) + }); + } + + extern "C" fn open(req: Request, inode: u64, fileinfo: MutPtr) { + Self::run_in_context(req, inode, |ctx| { + ctx.decoder.open(ctx.ino_offset).map_err(|_| libc::ENOENT)?; + let _ret = unsafe { fuse_reply_open(req, fileinfo) }; + + Ok(()) + }); + } + + extern "C" fn read(req: Request, inode: u64, size: size_t, offset: c_int, _fileinfo: MutPtr) { + Self::run_in_context(req, inode, |ctx| { + let mut data = ctx + .decoder + .read(ctx.ino_offset, size, offset as u64) + .map_err(|_| libc::EIO)?; + + let _res = unsafe { + let len = data.len(); + let dptr = data.as_mut_ptr() as *mut c_char; + fuse_reply_buf(req, dptr, len) + }; + + Ok(()) + }); + } + + /// Open the directory referenced by the given inode for reading. + /// + /// This simply checks if the inode references a valid directory, no internal + /// state identifies the directory as opened. + extern "C" fn opendir(req: Request, inode: u64, fileinfo: MutPtr) { + Self::run_in_context(req, inode, |ctx| { + let (_, entry, _, _) = ctx + .decoder + .attributes(ctx.ino_offset) + .map_err(|_| libc::EIO)?; + if (entry.mode as u32 & libc::S_IFMT) != libc::S_IFDIR { + return Err(libc::ENOENT); + } + let _ret = unsafe { fuse_reply_open(req, fileinfo) }; + + Ok(()) + }); + } + + /// Read and return the entries of the directory referenced by i-node. + /// + /// Replies to the request with the entries fitting into a buffer of length + /// `size`, as requested by the caller. + /// `offset` identifies the start index of entries to return. This is used on + /// repeated calls, occurring if not all entries fitted into the buffer. + /// The goodbye table of the directory is cached in order to speedup repeated + /// calls occurring when not all entries fitted in the reply buffer. + extern "C" fn readdir(req: Request, inode: u64, size: size_t, offset: c_int, _fileinfo: MutPtr) { + let offset = offset as usize; + + Self::run_in_context(req, inode, |mut ctx| { + update_goodbye_cache(&mut ctx)?; + let gb_table = &ctx.goodbye_cache.as_ref().unwrap().1; + let n_entries = gb_table.len(); + let mut buf = ReplyBuf::new(req, size, offset); + + if offset < n_entries { + for e in gb_table[offset..gb_table.len()].iter() { + let (filename, entry, _, payload_size) = + ctx.decoder.attributes(e.1).map_err(|_| libc::EIO)?; + let name = CString::new(filename.as_bytes()).map_err(|_| libc::EIO)?; + let item_offset = find_offset(&entry, e.1, e.2); + let item_inode = calculate_inode(item_offset, ctx.decoder.root_end_offset()); + let attr = stat(item_inode, &entry, payload_size).map_err(|_| libc::EIO)?; + match buf.add_entry(&name, &attr) { + Ok(ReplyBufState::Okay) => {} + Ok(ReplyBufState::Overfull) => return buf.reply_filled(), + Err(_) => return Err(libc::EIO), + } + } + } + + // Add current directory entry "." + if offset <= n_entries { + 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(); + match buf.add_entry(&name, &attr) { + Ok(ReplyBufState::Okay) => {} + Ok(ReplyBufState::Overfull) => return buf.reply_filled(), + Err(_) => return Err(libc::EIO), + } + } + + // Add parent directory entry ".." + if offset <= n_entries + 1 { + let parent_off = if inode == FUSE_ROOT_ID { + ctx.decoder.root_end_offset() - GOODBYE_ITEM_SIZE + } else { + let guard = CHILD_PARENT.lock().map_err(|_| libc::EIO)?; + *guard.get(&ctx.ino_offset).ok_or_else(|| libc::EIO)? + }; + 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) { + Ok(ReplyBufState::Okay) => {} + Ok(ReplyBufState::Overfull) => return buf.reply_filled(), + Err(_) => return Err(libc::EIO), + } + } + + buf.reply_filled() + }); + } } -impl Drop for Session { +impl Drop for Session { fn drop(&mut self) { unsafe { fuse_session_unmount(self.ptr); @@ -328,41 +542,6 @@ impl Drop for Session { } } -/// Creates a context providing an exclusive mutable reference to the `Context`. -/// -/// 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 Context) -> Result<(), i32>, -{ - let boxed_ctx = unsafe { - let ptr = fuse_req_userdata(req) as *mut Mutex; - Box::from_raw(ptr) - }; - let result = boxed_ctx - .lock() - .map(|mut ctx| { - ctx.ino_offset = match inode { - FUSE_ROOT_ID => ctx.decoder.root_end_offset() - GOODBYE_ITEM_SIZE, - _ => inode, - }; - code(&mut ctx) - }) - .unwrap_or(Err(libc::EIO)); - - if let Err(err) = result { - unsafe { - let _res = fuse_reply_err(req, err); - } - } - - // 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 /// @@ -414,7 +593,7 @@ struct EntryParam { /// 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> { +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 @@ -444,8 +623,8 @@ fn update_goodbye_cache(mut ctx: &mut Context) -> Result<(), i32> { /// 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 find_goodbye_entry( - mut ctx: &mut Context, +fn find_goodbye_entry( + mut ctx: &mut Context, filename: &CStr, hash: u64, ) -> Result<(u64, PxarEntry, PxarAttributes, u64), i32> { @@ -477,41 +656,6 @@ fn find_goodbye_entry( Err(libc::ENOENT) } -/// 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 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, - generation: 1, - attr: stat(child_inode, &entry, payload_size)?, - attr_timeout: std::f64::MAX, - entry_timeout: std::f64::MAX, - }; - - // Update the parent for this child entry. Used to get parent offset if - // only child offset is known. - CHILD_PARENT - .lock() - .map_err(|_| libc::EIO)? - .insert(child_offset, ctx.ino_offset); - let _res = unsafe { fuse_reply_entry(req, Some(&e)) }; - - Ok(()) - }); -} /// Create a `libc::stat` with the provided i-node, entry and payload size fn stat(inode: u64, entry: &PxarEntry, payload_size: u64) -> Result { @@ -540,82 +684,6 @@ fn stat(inode: u64, entry: &PxarEntry, payload_size: u64) -> Result {} - Ok(ReplyBufState::Overfull) => return buf.reply_filled(), - Err(_) => return Err(libc::EIO), - } - } - } - - // Add current directory entry "." - if offset <= n_entries { - 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(); - match buf.add_entry(&name, &attr) { - Ok(ReplyBufState::Okay) => {} - Ok(ReplyBufState::Overfull) => return buf.reply_filled(), - Err(_) => return Err(libc::EIO), - } - } - - // Add parent directory entry ".." - if offset <= n_entries + 1 { - let parent_off = if inode == FUSE_ROOT_ID { - ctx.decoder.root_end_offset() - GOODBYE_ITEM_SIZE - } else { - let guard = CHILD_PARENT.lock().map_err(|_| libc::EIO)?; - *guard.get(&ctx.ino_offset).ok_or_else(|| libc::EIO)? - }; - 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) { - Ok(ReplyBufState::Okay) => {} - Ok(ReplyBufState::Overfull) => return buf.reply_filled(), - Err(_) => return Err(libc::EIO), - } - } - - buf.reply_filled() - }); -} diff --git a/src/pxar/sequential_decoder.rs b/src/pxar/sequential_decoder.rs index e3826188..98644af7 100644 --- a/src/pxar/sequential_decoder.rs +++ b/src/pxar/sequential_decoder.rs @@ -30,18 +30,22 @@ use crate::tools::fs; use crate::tools::xattr; // This one need Read, but works without Seek -pub struct SequentialDecoder Result<(), Error>> { +pub struct SequentialDecoder { reader: R, feature_flags: u64, allow_existing_dirs: bool, skip_buffer: Vec, - callback: F, + callback: Option Result<(), Error>>>, } const HEADER_SIZE: u64 = std::mem::size_of::() as u64; -impl Result<(), Error>> SequentialDecoder { - pub fn new(reader: R, feature_flags: u64, callback: F) -> Self { +impl SequentialDecoder { + + pub fn new( + reader: R, + feature_flags: u64, + ) -> Self { let skip_buffer = vec::undefined(64 * 1024); Self { @@ -49,10 +53,14 @@ impl Result<(), Error>> SequentialDecoder { feature_flags, allow_existing_dirs: false, skip_buffer, - callback, + callback: None, } } + pub fn set_callback Result<(), Error> + 'static>(&mut self, callback: F ) { + self.callback = Some(Box::new(callback)); + } + pub fn set_allow_existing_dirs(&mut self, allow: bool) { self.allow_existing_dirs = allow; } @@ -793,7 +801,9 @@ impl Result<(), Error>> SequentialDecoder { let (target, _offset) = self.read_hardlink(head.size)?; let target_path = base_path.join(&target); if dirs.last_dir_fd().is_some() { - (self.callback)(&full_path)?; + if let Some(ref callback) = self.callback { + (callback)(&full_path)?; + } hardlink(&target_path, &full_path)?; } return Ok(()); @@ -826,7 +836,9 @@ impl Result<(), Error>> SequentialDecoder { }; if fd.is_some() { - (self.callback)(&full_path)?; + if let Some(ref callback) = self.callback { + (callback)(&full_path)?; + } } match ifmt {