From 6517f36dd4743e51087aed5cf53e8c462f0e37b8 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Thu, 3 Mar 2022 02:26:49 +0000 Subject: [PATCH 1/8] nydus-image: persist v6 prefetch table to extended super block Place a prefetch table which has nids for files and directories that needs to be prefetched. The prefetch table is very close to blobs table on v6 rafs extended super block. Signed-off-by: Changwei Ge --- rafs/src/metadata/layout/v6.rs | 91 ++++++++++++++++++++++++++- src/bin/nydus-image/core/bootstrap.rs | 38 ++++++++++- src/bin/nydus-image/core/prefetch.rs | 35 +++++++++++ 3 files changed, 160 insertions(+), 4 deletions(-) diff --git a/rafs/src/metadata/layout/v6.rs b/rafs/src/metadata/layout/v6.rs index 19df1947a53..2c2f30a2745 100644 --- a/rafs/src/metadata/layout/v6.rs +++ b/rafs/src/metadata/layout/v6.rs @@ -300,8 +300,11 @@ pub struct RafsV6SuperBlockExt { s_blob_table_size: u32, /// chunk size s_chunk_size: u32, + s_prefetch_table_offset: u64, + s_prefetch_table_size: u32, + s_padding: u32, /// Reserved - s_reserved: [u8; 232], + s_reserved: [u8; 216], } impl_bootstrap_converter!(RafsV6SuperBlockExt); @@ -393,6 +396,18 @@ impl RafsV6SuperBlockExt { u64 ); impl_pub_getter_setter!(blob_table_size, set_blob_table_size, s_blob_table_size, u32); + impl_pub_getter_setter!( + prefetch_table_size, + set_prefetch_table_size, + s_prefetch_table_size, + u32 + ); + impl_pub_getter_setter!( + prefetch_table_offset, + set_prefetch_table_offset, + s_prefetch_table_offset, + u64 + ); } impl RafsStore for RafsV6SuperBlockExt { @@ -412,7 +427,10 @@ impl Default for RafsV6SuperBlockExt { s_blob_table_offset: u64::to_le(0), s_blob_table_size: u32::to_le(0), s_chunk_size: u32::to_le(0), - s_reserved: [0u8; 232], + s_prefetch_table_offset: u64::to_le(0), + s_prefetch_table_size: u32::to_le(0), + s_padding: u32::to_le(0), + s_reserved: [0u8; 216], } } } @@ -1640,6 +1658,75 @@ impl RafsXAttrs { } } +#[derive(Clone, Default, Debug)] +pub struct RafsV6PrefetchTable { + /// List of inode numbers for prefetch. + /// Note: It's not inode index of inodes table being stored here. + pub inodes: Vec, +} + +impl RafsV6PrefetchTable { + /// Create a new instance of `RafsV6PrefetchTable`. + pub fn new() -> RafsV6PrefetchTable { + RafsV6PrefetchTable { inodes: vec![] } + } + + /// Get content size of the inode prefetch table. + pub fn size(&self) -> usize { + self.len() * size_of::() + } + + /// Get number of entries in the prefetch table. + pub fn len(&self) -> usize { + self.inodes.len() + } + + /// Check whether the inode prefetch table is empty. + pub fn is_empty(&self) -> bool { + self.inodes.is_empty() + } + + /// Add an inode into the inode prefetch table. + pub fn add_entry(&mut self, ino: u32) { + self.inodes.push(ino); + } + + /// Store the inode prefetch table to a writer. + pub fn store(&mut self, w: &mut dyn RafsIoWrite) -> Result { + // Sort prefetch table by inode index, hopefully, it can save time when mounting rafs + // Because file data is dumped in the order of inode index. + self.inodes.sort_unstable(); + + let (_, data, _) = unsafe { self.inodes.align_to::() }; + w.write_all(data.as_ref())?; + + // OK. Let's see if we have to align... :-( + // let cur_len = self.inodes.len() * size_of::(); + + Ok(data.len()) + } + + /// Load a inode prefetch table from a reader. + /// + /// Note: Generally, prefetch happens after loading bootstrap, so with methods operating + /// files with changing their offset won't bring errors. But we still use `pread` now so as + /// to make this method more stable and robust. Even dup(2) can't give us a separated file struct. + pub fn load_prefetch_table_from( + &mut self, + r: &mut RafsIoReader, + offset: u64, + entries: usize, + ) -> Result { + self.inodes = vec![0u32; entries]; + + let (_, data, _) = unsafe { self.inodes.align_to_mut::() }; + r.seek_to_offset(offset)?; + r.read_exact(data)?; + + Ok(data.len()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/bin/nydus-image/core/bootstrap.rs b/src/bin/nydus-image/core/bootstrap.rs index c9afd7da832..2204ab6ac3f 100644 --- a/src/bin/nydus-image/core/bootstrap.rs +++ b/src/bin/nydus-image/core/bootstrap.rs @@ -543,9 +543,22 @@ impl Bootstrap { let blob_table_entries = blob_table.entries.len(); + let (prefetch_table_offset, prefetch_table_size) = if ctx.prefetch.len() > 0 { + // Prefetch table is very close to blob devices table + let offset = blob_table_offset + blob_table_size; + let size = ctx.prefetch.len() * size_of::() as u32; + trace!("prefetch table locates at offset {} size {}", offset, size); + (offset, size) + } else { + (0, 0) + }; + let orig_meta_addr = bootstrap_ctx.nodes[0].offset; let meta_addr = if blob_table_size > 0 { - align_offset(blob_table_offset + blob_table_size, EROFS_BLOCK_SIZE as u64) + align_offset( + blob_table_offset + blob_table_size + prefetch_table_size as u64, + EROFS_BLOCK_SIZE as u64, + ) } else { orig_meta_addr }; @@ -613,7 +626,28 @@ impl Bootstrap { Result<()> )?; - // Erofs does not have inode table, so we lose the chance to decide if this + // `Node` offset might be updated during above inodes dumping. So `get_prefetch_table` after it. + let prefetch_table = ctx + .prefetch + .get_rafsv6_prefetch_table(&bootstrap_ctx.nodes, meta_addr); + + if let Some(mut pt) = prefetch_table { + // Device slots are very close to extended super block. + ext_sb.set_prefetch_table_offset(prefetch_table_offset); + ext_sb.set_prefetch_table_size(prefetch_table_size); + bootstrap_writer + .seek_to_offset(prefetch_table_offset as u64) + .context("failed seek to prefetch table offset")?; + + pt.store(&mut bootstrap_writer).unwrap(); + + bootstrap_writer.file.seek(SeekFrom::Start(ext_sb_offset))?; + ext_sb + .store(&mut bootstrap_writer) + .context("failed to revise extended SB")?; + } + + // EROFS does not have inode table, so we lose the chance to decide if this // image has xattr. So we have to rewrite extended super block. if ctx.has_xattr { ext_sb.set_has_xattr(); diff --git a/src/bin/nydus-image/core/prefetch.rs b/src/bin/nydus-image/core/prefetch.rs index 922f2d0075d..f5e9bbdf374 100644 --- a/src/bin/nydus-image/core/prefetch.rs +++ b/src/bin/nydus-image/core/prefetch.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use anyhow::{Context, Error, Result}; use rafs::metadata::layout::v5::RafsV5PrefetchTable; +use rafs::metadata::layout::v6::{calculate_nid, RafsV6PrefetchTable}; use crate::node::Node; @@ -171,6 +172,40 @@ impl Prefetch { } } + pub fn len(&self) -> u32 { + if self.policy == PrefetchPolicy::Fs { + self.readahead_patterns.values().len() as u32 + } else { + 0 + } + } + + pub fn get_rafsv6_prefetch_table( + &mut self, + nodes: &[Node], + meta_addr: u64, + ) -> Option { + if self.policy == PrefetchPolicy::Fs { + let mut prefetch_table = RafsV6PrefetchTable::new(); + for i in self.readahead_patterns.values().filter_map(|v| *v) { + trace!( + "v6 prefetch table: map node index {} to offset {} nid {} path {:?} name {:?}", + i, + nodes[i as usize].offset, + calculate_nid(nodes[i as usize].offset, meta_addr), + nodes[i as usize].path(), + nodes[i as usize].name() + ); + // 32bit nid can represent 128GB bootstrap, it is large enough, no need + // to worry about casting here + prefetch_table.add_entry(calculate_nid(nodes[i as usize].offset, meta_addr) as u32); + } + Some(prefetch_table) + } else { + None + } + } + pub fn disable(&mut self) { self.disabled = true; } From f5ad040a8dd67dd71a830221fc35af1cbf76a992 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Thu, 3 Mar 2022 02:30:24 +0000 Subject: [PATCH 2/8] rafs/v6: implement is_hardlink() for v6 format Signed-off-by: Changwei Ge --- rafs/src/metadata/direct_v6.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rafs/src/metadata/direct_v6.rs b/rafs/src/metadata/direct_v6.rs index 1b24fe43815..05166257459 100644 --- a/rafs/src/metadata/direct_v6.rs +++ b/rafs/src/metadata/direct_v6.rs @@ -878,7 +878,8 @@ impl RafsInode for OndiskInodeWrapper { /// Check whether the inode is a hardlink. fn is_hardlink(&self) -> bool { - todo!() + let inode = self.disk_inode(); + inode.nlink() > 1 && self.is_reg() } /// Get inode number of the parent directory. From ef416f3fb42302a24643efa23c946cfa9ceeacff Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Thu, 3 Mar 2022 02:34:33 +0000 Subject: [PATCH 3/8] rafs/v6: perform prefetch based on v6 super block Load v6 prefetch table from extended super block and prefetch files' contents per as the table. Signed-off-by: Changwei Ge --- rafs/src/fs.rs | 6 ++-- rafs/src/metadata/md_v6.rs | 58 +++++++++++++++++++++++++++++++++++++- rafs/src/metadata/mod.rs | 9 +++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/rafs/src/fs.rs b/rafs/src/fs.rs index 648fee4d80e..b8f0605ccc6 100644 --- a/rafs/src/fs.rs +++ b/rafs/src/fs.rs @@ -464,9 +464,7 @@ impl Rafs { let prefetch_all = self.prefetch_all; let _ = std::thread::spawn(move || { - if sb.meta.is_v5() { - Self::do_prefetch_v5(reader, prefetch_files, prefetch_all, sb, device); - } + Self::do_prefetch(reader, prefetch_files, prefetch_all, sb, device); }); } @@ -479,7 +477,7 @@ impl Rafs { self.sb.superblock.root_ino() } - fn do_prefetch_v5( + fn do_prefetch( mut reader: RafsIoReader, prefetch_files: Option>, prefetch_all: bool, diff --git a/rafs/src/metadata/md_v6.rs b/rafs/src/metadata/md_v6.rs index ccab3b502bd..9a99cec466f 100644 --- a/rafs/src/metadata/md_v6.rs +++ b/rafs/src/metadata/md_v6.rs @@ -2,14 +2,19 @@ // // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashSet; use std::io::Result; +use std::mem::size_of; use std::sync::Arc; use super::direct_v6::DirectSuperBlockV6; -use super::layout::v6::{RafsV6SuperBlock, RafsV6SuperBlockExt}; +use super::layout::v6::{RafsV6PrefetchTable, RafsV6SuperBlock, RafsV6SuperBlockExt}; use super::layout::RAFS_SUPER_VERSION_V6; +use super::*; use super::{RafsMode, RafsSuper, RafsSuperBlock, RafsSuperFlags}; + use crate::RafsIoReader; +use crate::{RafsError, RafsResult}; impl RafsSuper { pub(crate) fn try_load_v6(&mut self, r: &mut RafsIoReader) -> Result { @@ -39,6 +44,15 @@ impl RafsSuper { self.meta.meta_blkaddr = sb.s_meta_blkaddr; self.meta.root_nid = sb.s_root_nid; + self.meta.prefetch_table_entries = ext_sb.prefetch_table_size() / size_of::() as u32; + self.meta.prefetch_table_offset = ext_sb.prefetch_table_offset(); + + trace!( + "prefetch table offset {} entries {} ", + self.meta.prefetch_table_offset, + self.meta.prefetch_table_entries + ); + match self.mode { RafsMode::Direct => { let mut sb_v6 = DirectSuperBlockV6::new(&self.meta, self.validate_digest); @@ -49,6 +63,48 @@ impl RafsSuper { RafsMode::Cached => Err(enosys!("Rafs v6 does not support cached mode")), } } + + pub(crate) fn prefetch_data_v6(&self, r: &mut RafsIoReader, fetcher: F) -> RafsResult + where + F: Fn(&mut BlobIoVec), + { + let hint_entries = self.meta.prefetch_table_entries as usize; + + if hint_entries == 0 { + return Ok(0); + } + + let mut prefetch_table = RafsV6PrefetchTable::new(); + let mut hardlinks: HashSet = HashSet::new(); + let mut head_desc = BlobIoVec::new(); + + // Try to prefetch according to the list of files specified by the + // builder's `--prefetch-policy fs` option. + prefetch_table + .load_prefetch_table_from(r, self.meta.prefetch_table_offset, hint_entries) + .map_err(|e| { + RafsError::Prefetch(format!( + "Failed in loading hint prefetch table at offset {}. {:?}", + self.meta.prefetch_table_offset, e + )) + })?; + + trace!("prefetch table contents {:?}", prefetch_table); + + for ino in prefetch_table.inodes { + // Inode number 0 is invalid, it was added because prefetch table has to be aligned. + if ino == 0 { + break; + } + debug!("hint prefetch inode {}", ino); + self.prefetch_data(ino as u64, &mut head_desc, &mut hardlinks, &fetcher) + .map_err(|e| RafsError::Prefetch(e.to_string()))?; + } + // The left chunks whose size is smaller than 4MB will be fetched here. + fetcher(&mut head_desc); + + Ok(hint_entries) + } } #[cfg(test)] diff --git a/rafs/src/metadata/mod.rs b/rafs/src/metadata/mod.rs index 86d46cb4ae8..ef9a954e866 100644 --- a/rafs/src/metadata/mod.rs +++ b/rafs/src/metadata/mod.rs @@ -637,6 +637,8 @@ impl RafsSuper { Ok(()) } else if self.meta.is_v5() { self.prefetch_data_v5(r, fetcher).map(|_| ()) + } else if self.meta.is_v6() { + self.prefetch_data_v6(r, fetcher).map(|_| ()) } else { Err(RafsError::Prefetch( "Unknown filesystem version, prefetch disabled".to_string(), @@ -711,7 +713,12 @@ impl RafsSuper { for i in descendants.iter() { Self::prefetch_inode(i, head_desc, hardlinks, try_prefetch)?; } - } else if !inode.is_empty_size() { + } else if !inode.is_empty_size() && inode.is_reg() { + // An empty regular file will also be packed into nydus image, + // then it has a size of zero. + // Moreover, for rafs v5, symlink has size of zero but non-zero size + // for symlink size. For rafs v6, symlink size is also represented by i_size. + // So we have to restrain the condition here. Self::prefetch_inode(&inode, head_desc, hardlinks, try_prefetch)?; } From e7b443eda6127bbb47abea6adde72ac8178defe2 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Thu, 3 Mar 2022 03:08:00 +0000 Subject: [PATCH 4/8] nydus-image: rename seek_to_offset to seek_offset Because seek is transitive. Signed-off-by: Changwei Ge --- rafs/src/lib.rs | 2 +- rafs/src/metadata/layout/v6.rs | 4 ++-- src/bin/nydus-image/core/bootstrap.rs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rafs/src/lib.rs b/rafs/src/lib.rs index abe89b9d28b..7b8cbd44593 100644 --- a/rafs/src/lib.rs +++ b/rafs/src/lib.rs @@ -134,7 +134,7 @@ pub trait RafsIoWrite: Write + Seek + 'static { } /// Seek the writer to the `offset`. - fn seek_to_offset(&mut self, offset: u64) -> Result { + fn seek_offset(&mut self, offset: u64) -> Result { self.seek(SeekFrom::Start(offset)).map_err(|e| { error!("Seeking to offset {} from start fails, {}", offset, e); e diff --git a/rafs/src/metadata/layout/v6.rs b/rafs/src/metadata/layout/v6.rs index 2c2f30a2745..d04f94698ab 100644 --- a/rafs/src/metadata/layout/v6.rs +++ b/rafs/src/metadata/layout/v6.rs @@ -412,9 +412,9 @@ impl RafsV6SuperBlockExt { impl RafsStore for RafsV6SuperBlockExt { fn store(&self, w: &mut dyn RafsIoWrite) -> Result { - w.seek_to_offset((EROFS_SUPER_OFFSET + EROFS_SUPER_BLOCK_SIZE) as u64)?; + w.seek_offset((EROFS_SUPER_OFFSET + EROFS_SUPER_BLOCK_SIZE) as u64)?; w.write_all(self.as_ref())?; - w.seek_to_offset(EROFS_BLOCK_SIZE as u64)?; + w.seek_offset(EROFS_BLOCK_SIZE as u64)?; Ok(EROFS_BLOCK_SIZE as usize - (EROFS_SUPER_OFFSET + EROFS_SUPER_BLOCK_SIZE) as usize) } diff --git a/src/bin/nydus-image/core/bootstrap.rs b/src/bin/nydus-image/core/bootstrap.rs index 2204ab6ac3f..74d9ae65263 100644 --- a/src/bin/nydus-image/core/bootstrap.rs +++ b/src/bin/nydus-image/core/bootstrap.rs @@ -597,8 +597,8 @@ impl Bootstrap { // dump devtslot bootstrap_writer - .seek_to_offset(EROFS_DEVTABLE_OFFSET as u64) - .context("failed to seek to devtslot")?; + .seek_offset(EROFS_DEVTABLE_OFFSET as u64) + .context("failed to seek devtslot")?; for slot in devtable.iter() { slot.store(&mut bootstrap_writer) .context("failed to store device slot")?; @@ -606,7 +606,7 @@ impl Bootstrap { // Dump blob table bootstrap_writer - .seek_to_offset(blob_table_offset as u64) + .seek_offset(blob_table_offset as u64) .context("failed seek for extended blob table offset")?; blob_table .store(&mut bootstrap_writer) @@ -636,8 +636,8 @@ impl Bootstrap { ext_sb.set_prefetch_table_offset(prefetch_table_offset); ext_sb.set_prefetch_table_size(prefetch_table_size); bootstrap_writer - .seek_to_offset(prefetch_table_offset as u64) - .context("failed seek to prefetch table offset")?; + .seek_offset(prefetch_table_offset as u64) + .context("failed seek prefetch table offset")?; pt.store(&mut bootstrap_writer).unwrap(); From 63f4bfc6b97f09784d3ab3a8f752bd9405012b85 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Thu, 3 Mar 2022 03:16:42 +0000 Subject: [PATCH 5/8] rafs/v6: implement collect_descendants_inodes() for rafs v6 Rafs prefetch needs the functionality. Signed-off-by: Changwei Ge --- rafs/src/metadata/direct_v6.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/rafs/src/metadata/direct_v6.rs b/rafs/src/metadata/direct_v6.rs index 05166257459..63d1acdc489 100644 --- a/rafs/src/metadata/direct_v6.rs +++ b/rafs/src/metadata/direct_v6.rs @@ -917,7 +917,37 @@ impl RafsInode for OndiskInodeWrapper { &self, descendants: &mut Vec>, ) -> Result { - todo!() + if !self.is_dir() { + return Err(enotdir!()); + } + + let mut child_dirs: Vec> = Vec::new(); + + // EROFS packs dot and dotdot, so skip them two. + self.walk_children_inodes(2, &mut |inode: Option>, + name: OsString, + ino, + offset| { + // Safe to unwrap since it must have child inode. + if let Some(child_inode) = inode { + if child_inode.is_dir() { + trace!("Got dir {:?}", child_inode.name()); + child_dirs.push(child_inode); + } else if !child_inode.is_empty_size() && child_inode.is_reg() { + descendants.push(child_inode); + } + Ok(PostWalkAction::Continue) + } else { + Ok(PostWalkAction::Continue) + } + }) + .unwrap(); + + for d in child_dirs { + d.collect_descendants_inodes(descendants)?; + } + + Ok(0) } fn alloc_bio_vecs(&self, offset: u64, size: usize, user_io: bool) -> Result> { From 1cad5da827fa67a53c2bb67065115d35bd045605 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Thu, 3 Mar 2022 10:02:48 +0000 Subject: [PATCH 6/8] rafs/storage: map to blob addressable chunk infos V6 format does not have chunks' particualer addresses in bootstrap but only their indexes by which to locate each chunk from blob. For prefetch, we aslo have to map the chunks info so that bios can be sorted when handling prefetch requests. Signed-off-by: Changwei Ge --- storage/src/cache/filecache/cache_entry.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/storage/src/cache/filecache/cache_entry.rs b/storage/src/cache/filecache/cache_entry.rs index 3f833a29b1a..f04e4aecb69 100644 --- a/storage/src/cache/filecache/cache_entry.rs +++ b/storage/src/cache/filecache/cache_entry.rs @@ -221,6 +221,15 @@ impl BlobCache for FileCacheEntry { bios: &[BlobIoDesc], ) -> StorageResult { let mut bios = bios.to_vec(); + bios.iter_mut().for_each(|b| { + if let Some(ref chunks_meta) = self.meta { + // TODO: the first blob backend io triggers chunks array download. + if let BlobIoChunk::Address(_blob_index, chunk_index) = b.chunkinfo { + let cki = BlobMetaChunk::new(chunk_index as usize, &chunks_meta.state); + b.chunkinfo = BlobIoChunk::Base(Arc::new(cki)); + } + } + }); bios.sort_by_key(|entry| entry.chunkinfo.compress_offset()); self.metrics.prefetch_unmerged_chunks.add(bios.len() as u64); From 11115bfc1588f4da47dda025afea8d206f862f90 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Fri, 4 Mar 2022 02:52:17 +0000 Subject: [PATCH 7/8] rafs: use real root ino when perform dynamic prefetch Signed-off-by: Changwei Ge --- rafs/src/metadata/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rafs/src/metadata/mod.rs b/rafs/src/metadata/mod.rs index ef9a954e866..305f2291632 100644 --- a/rafs/src/metadata/mod.rs +++ b/rafs/src/metadata/mod.rs @@ -553,15 +553,16 @@ impl RafsSuper { /// Convert a file path to an inode number. pub fn ino_from_path(&self, f: &Path) -> Result { + let root_ino = self.superblock.root_ino(); if f == Path::new("/") { - return Ok(ROOT_ID); + return Ok(root_ino); } if !f.starts_with("/") { return Err(einval!()); } - let mut parent = self.get_inode(ROOT_ID, self.validate_digest)?; + let mut parent = self.get_inode(root_ino, self.validate_digest)?; let entries = f .components() From 8bd4600952e7a3b1e96984c312140f8271473269 Mon Sep 17 00:00:00 2001 From: Changwei Ge Date: Mon, 7 Mar 2022 02:04:30 +0000 Subject: [PATCH 8/8] nydus-image: only write extended sb once Right now, when prefetch enabled and xattrs packed, extended sb will be revised and rewritten more than onece. It is not necessary. Only write extended block when all the settings are done. Signed-off-by: Changwei Ge --- src/bin/nydus-image/core/bootstrap.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/bin/nydus-image/core/bootstrap.rs b/src/bin/nydus-image/core/bootstrap.rs index 74d9ae65263..9e742b27788 100644 --- a/src/bin/nydus-image/core/bootstrap.rs +++ b/src/bin/nydus-image/core/bootstrap.rs @@ -591,10 +591,6 @@ impl Bootstrap { ext_sb.set_blob_table_offset(blob_table_offset); ext_sb.set_blob_table_size(blob_table_size as u32); - ext_sb - .store(&mut bootstrap_writer) - .context("failed to store extended SB")?; - // dump devtslot bootstrap_writer .seek_offset(EROFS_DEVTABLE_OFFSET as u64) @@ -640,23 +636,19 @@ impl Bootstrap { .context("failed seek prefetch table offset")?; pt.store(&mut bootstrap_writer).unwrap(); - - bootstrap_writer.file.seek(SeekFrom::Start(ext_sb_offset))?; - ext_sb - .store(&mut bootstrap_writer) - .context("failed to revise extended SB")?; } // EROFS does not have inode table, so we lose the chance to decide if this // image has xattr. So we have to rewrite extended super block. if ctx.has_xattr { ext_sb.set_has_xattr(); - bootstrap_writer.file.seek(SeekFrom::Start(ext_sb_offset))?; - ext_sb - .store(&mut bootstrap_writer) - .context("failed to revise extended SB")?; } + bootstrap_writer.file.seek(SeekFrom::Start(ext_sb_offset))?; + ext_sb + .store(&mut bootstrap_writer) + .context("failed to write extended super block")?; + // Flush remaining data in BufWriter to file bootstrap_writer .flush()