Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

builder: fix rafs v5 dir size for reproducible build #642

Merged
merged 1 commit into from
Aug 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
jiangliu marked this conversation as resolved.
Show resolved Hide resolved

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about passing "RafsVersion" instead of "&RafsVersion"?

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