Skip to content

Commit

Permalink
Merge pull request #642 from imeoer/fix-v5-dir-size
Browse files Browse the repository at this point in the history
builder: fix rafs v5 dir size for reproducible build
  • Loading branch information
jiangliu authored Aug 6, 2022
2 parents 2ffacea + f6a2ecc commit c68347a
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 61 deletions.
28 changes: 28 additions & 0 deletions src/bin/nydus-image/builder/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ use crate::core::context::{
};
use crate::core::node::{Node, Overlay};
use crate::core::tree::Tree;
use nydus_utils::try_round_up_4k;

// The RAFS v5 format does not have directory entries, so for ensuring
// that the directory size is independent of the host filesystem for
// reproducible builds, we define a virtual dummy value here.
const RAFS_V5_VIRTUAL_ENTRY_SIZE: u8 = 8;

const TAR_BLOB_NAME: &str = "image.blob";
const TAR_BOOTSTRAP_NAME: &str = "image.boot";
Expand Down Expand Up @@ -71,6 +77,7 @@ impl FilesystemTreeBuilder {

let mut child = Tree::new(child);
child.children = self.load_children(ctx, bootstrap_ctx, &mut child.node, layer_idx)?;
DirectoryBuilder::set_v5_dir_size(&ctx.fs_version, &mut child);
result.push(child);
}

Expand All @@ -85,6 +92,26 @@ impl DirectoryBuilder {
Self {}
}

// Since different filesystems may calculate different directory sizes,
// for RAFS v5, nydus needs to implement the directory size calculation
// itself to achieve reproducible builds on different hosts or filesystems.
// For RAFS v6, the directory size is calculated by actual entries later.
fn set_v5_dir_size(fs_version: &RafsVersion, tree: &mut Tree) {
if !tree.node.is_dir() || !fs_version.is_v5() {
return;
}
let mut d_size = 0u64;
for child in tree.children.iter() {
d_size += child.node.inode.name_size() as u64 + RAFS_V5_VIRTUAL_ENTRY_SIZE as u64;
}
if d_size == 0 {
tree.node.inode.set_size(4096);
} else {
tree.node.inode.set_size(try_round_up_4k(d_size).unwrap());
}
tree.node.set_v5_inode_blocks();
}

/// Build node tree from a filesystem directory
fn build_tree_from_fs(
&mut self,
Expand All @@ -108,6 +135,7 @@ impl DirectoryBuilder {
{ tree_builder.load_children(ctx, bootstrap_ctx, &mut tree.node, layer_idx) },
"load_from_directory"
)?;
Self::set_v5_dir_size(&ctx.fs_version, &mut tree);

Ok(tree)
}
Expand Down
161 changes: 100 additions & 61 deletions src/bin/nydus-image/core/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl Display for Node {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{} {:?}: index {} ino {} real_ino {} i_parent {} child_index {} child_count {} i_nlink {} i_size {} i_name_size {} i_symlink_size {} has_xattr {} link {:?}",
"{} {:?}: index {} ino {} real_ino {} i_parent {} child_index {} child_count {} i_nlink {} i_size {} i_blocks {} i_name_size {} i_symlink_size {} has_xattr {} link {:?} i_mtime {} i_mtime_nsec {}",
self.file_type(),
self.target(),
self.index,
Expand All @@ -255,10 +255,13 @@ impl Display for Node {
self.inode.child_count(),
self.inode.nlink(),
self.inode.size(),
self.inode.blocks(),
self.inode.name_size(),
self.inode.symlink_size(),
self.inode.has_xattr(),
self.symlink,
self.inode.mtime(),
self.inode.mtime_nsec(),
)
}
}
Expand Down Expand Up @@ -833,6 +836,23 @@ impl Node {
Ok(())
}

// Xattr paris are located into bootstrap rather than blob, however, we should
// also reflect the size they consume like other file system. We don't
// directly use local file's metadata for `i_block` since we are purchasing
// "reproducible build" which means nydus image can be built from anywhere with
// the unique image built.
// TODO: The real size occupied within blob is compressed. Therefore, the
// sum of all chunks' size should be more accurate. But we don't know the size
// right now since compression is not acted yet. Try to make this accurate later.
pub fn set_v5_inode_blocks(&mut self) {
if let InodeWrapper::V5(_) = self.inode {
self.inode.set_blocks(div_round_up(
self.inode.size() + self.xattrs.aligned_size_v5() as u64,
512,
));
}
}

fn build_inode_stat(&mut self) -> Result<()> {
let meta = self.meta()?;

Expand All @@ -847,8 +867,28 @@ impl Node {
// (blob + bootstrap in one tar layer), which causes the layer hash to change and wastes
// registry storage space, so the mtime of the root directory is forced to be ignored here.
let ignore_mtime = self.is_root();
self.inode
.set_inode_info(&meta, &self.xattrs, self.explicit_uidgid, ignore_mtime);

self.inode.set_mode(meta.st_mode());
if self.explicit_uidgid {
self.inode.set_uid(meta.st_uid());
self.inode.set_gid(meta.st_gid());
}
if !ignore_mtime {
self.inode.set_mtime(meta.st_mtime() as u64);
self.inode.set_mtime_nsec(meta.st_mtime_nsec() as u32);
}
self.inode.set_projid(0);
self.inode.set_rdev(meta.st_rdev() as u32);
// Ignore actual nlink value and calculate from rootfs directory instead
self.inode.set_nlink(1);

// Since different filesystems may get different directory sizes/blocks,
// calculate these two values later.
if !self.is_dir() {
self.inode.set_size(meta.st_size());
// Set inode blocks for RAFS v5 dir, v6 dir's will be calculated by runtime.
self.set_v5_inode_blocks();
}

Ok(())
}
Expand Down Expand Up @@ -1291,6 +1331,13 @@ impl InodeWrapper {
}
}

pub fn set_mode(&mut self, mode: u32) {
match self {
InodeWrapper::V5(i) => i.i_mode = mode,
InodeWrapper::V6(i) => i.i_mode = mode,
}
}

pub fn is_dir(&self) -> bool {
match self {
InodeWrapper::V5(i) => i.is_dir(),
Expand Down Expand Up @@ -1426,34 +1473,83 @@ impl InodeWrapper {
}
}

pub fn set_uid(&mut self, uid: u32) {
match self {
InodeWrapper::V5(i) => i.i_uid = uid,
InodeWrapper::V6(i) => i.i_uid = uid,
}
}

pub fn gid(&self) -> u32 {
match self {
InodeWrapper::V5(i) => i.i_gid,
InodeWrapper::V6(i) => i.i_gid,
}
}

pub fn set_gid(&mut self, gid: u32) {
match self {
InodeWrapper::V5(i) => i.i_gid = gid,
InodeWrapper::V6(i) => i.i_gid = gid,
}
}

pub fn mtime(&self) -> u64 {
match self {
InodeWrapper::V5(i) => i.i_mtime,
InodeWrapper::V6(i) => i.i_mtime,
}
}

pub fn set_mtime(&mut self, mtime: u64) {
match self {
InodeWrapper::V5(i) => i.i_mtime = mtime,
InodeWrapper::V6(i) => i.i_mtime = mtime,
}
}

pub fn mtime_nsec(&self) -> u32 {
match self {
InodeWrapper::V5(i) => i.i_mtime_nsec,
InodeWrapper::V6(i) => i.i_mtime_nsec,
}
}

pub fn set_mtime_nsec(&mut self, mtime_nsec: u32) {
match self {
InodeWrapper::V5(i) => i.i_mtime_nsec = mtime_nsec,
InodeWrapper::V6(i) => i.i_mtime_nsec = mtime_nsec,
}
}

pub fn blocks(&self) -> u64 {
match self {
InodeWrapper::V5(i) => i.i_blocks,
InodeWrapper::V6(i) => i.i_blocks,
}
}

pub fn set_blocks(&mut self, blocks: u64) {
match self {
InodeWrapper::V5(i) => i.i_blocks = blocks,
InodeWrapper::V6(i) => i.i_blocks = blocks,
}
}

pub fn set_rdev(&mut self, rdev: u32) {
match self {
InodeWrapper::V5(i) => i.i_rdev = rdev,
InodeWrapper::V6(i) => i.i_rdev = rdev,
}
}

pub fn set_projid(&mut self, projid: u32) {
match self {
InodeWrapper::V5(i) => i.i_projid = projid,
InodeWrapper::V6(i) => i.i_projid = projid,
}
}

pub fn nlink(&self) -> u32 {
match self {
InodeWrapper::V5(i) => i.i_nlink,
Expand Down Expand Up @@ -1489,7 +1585,7 @@ impl InodeWrapper {
}
}

fn set_name_size(&mut self, size: usize) {
pub fn set_name_size(&mut self, size: usize) {
debug_assert!(size < u16::MAX as usize);
match self {
InodeWrapper::V5(i) => i.i_name_size = size as u16,
Expand Down Expand Up @@ -1546,63 +1642,6 @@ impl InodeWrapper {
}
}

fn set_inode_info<T: MetadataExt>(
&mut self,
meta: &T,
xattrs: &RafsXAttrs,
explicit_uidgid: bool,
ignore_mtime: bool,
) {
match self {
InodeWrapper::V5(i) => {
i.i_mode = meta.st_mode();
if explicit_uidgid {
i.i_uid = meta.st_uid();
i.i_gid = meta.st_gid();
}
if !ignore_mtime {
i.i_mtime = meta.st_mtime() as u64;
i.i_mtime_nsec = meta.st_mtime_nsec() as u32;
}
i.i_projid = 0;
i.i_size = meta.st_size();
i.i_rdev = meta.st_rdev() as u32;
// Ignore actual nlink value and calculate from rootfs directory instead
i.i_nlink = 1;

// Xattr paris are located into bootstrap rather than blob, however, we should
// also reflect the size they consume like other file system. We don't
// directly use local file's metadata for `i_block` since we are purchasing
// "reproducible build" which means nydus image can be built from anywhere with
// the unique image built.
i.i_blocks = div_round_up(i.i_size + xattrs.aligned_size_v5() as u64, 512);
}
InodeWrapper::V6(i) => {
i.i_mode = meta.st_mode();
if explicit_uidgid {
i.i_uid = meta.st_uid();
i.i_gid = meta.st_gid();
}
if !ignore_mtime {
i.i_mtime = meta.st_mtime() as u64;
i.i_mtime_nsec = meta.st_mtime_nsec() as u32;
}
i.i_projid = 0;
i.i_size = meta.st_size();
i.i_rdev = meta.st_rdev() as u32;
// Ignore actual nlink value and calculate from rootfs directory instead
i.i_nlink = 1;

// Xattr paris are located into bootstrap rather than blob, however, we should
// also reflect the size they consume like other file system. We don't
// directly use local file's metadata for `i_block` since we are purchasing
// "reproducible build" which means nydus image can be built from anywhere with
// the unique image built.
i.i_blocks = div_round_up(i.i_size + xattrs.aligned_size_v5() as u64, 512);
}
}
}

fn create_chunk(&self) -> ChunkWrapper {
match self {
InodeWrapper::V5(_) => ChunkWrapper::V5(RafsV5ChunkInfo::new()),
Expand Down

0 comments on commit c68347a

Please sign in to comment.