Skip to content
Merged
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
41 changes: 11 additions & 30 deletions crates/lib/src/container_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,12 @@ fn export_filesystem_walk<W: Write>(
root_dir: &cap_std_ext::cap_std::fs::Dir,
sepolicy: Option<&ostree::SePolicy>,
) -> Result<()> {
use cap_std_ext::cap_primitives::fs::MetadataExt;
use std::path::Path;

// Get the device number of the root directory - we should never see a different device
// since we use noxdev() which prevents crossing mount points
let root_meta = root_dir.dir_metadata()?;
let expected_dev = root_meta.dev();

// Track hardlinks: maps inode -> first path seen
// We only track inode since all files must be on the same device
let mut hardlinks: HashMap<u64, std::path::PathBuf> = HashMap::new();
// Track hardlinks: maps (dev, inode) -> first path seen.
// We key on (dev, ino) because overlay filesystems may present
// different device numbers for directories vs regular files.
let mut hardlinks: HashMap<(u64, u64), std::path::PathBuf> = HashMap::new();

// The target mount shouldn't have submounts, but just in case we use noxdev
let walk_config = WalkConfiguration::default()
Expand Down Expand Up @@ -192,7 +187,6 @@ fn export_filesystem_walk<W: Write>(
path,
relative_path,
sepolicy,
expected_dev,
&mut hardlinks,
)
.map_err(std::io::Error::other)?;
Expand Down Expand Up @@ -250,34 +244,21 @@ fn add_file_to_tar_from_walk<W: Write>(
absolute_path: &std::path::Path,
relative_path: &std::path::Path,
sepolicy: Option<&ostree::SePolicy>,
expected_dev: u64,
hardlinks: &mut HashMap<u64, std::path::PathBuf>,
hardlinks: &mut HashMap<(u64, u64), std::path::PathBuf>,
) -> Result<()> {
use cap_std_ext::cap_primitives::fs::{MetadataExt, PermissionsExt};
use std::path::Path;

let filename_path = Path::new(filename);
let metadata = dir.metadata(filename_path)?;

// Skip files on different devices (e.g., bind mounts in containers like /etc/hosts).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this mean that now we would start including the bind-mounted /etc/hosts when we export?

Copy link

Choose a reason for hiding this comment

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

I believe it does.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As documented, this expects the image as a separate mount point:

podman run --rm \
    --mount=type=image,source=quay.io/example/example,target=/run/target \
    quay.io/example/example \
    bootc container export --kernel-in-boot /run/target > example.tar

And in that case, there won't be any container runtime injected content like /etc/hosts.

// The noxdev() option prevents descending into directories on different devices,
// but individual files can still be bind-mounted from other filesystems.
let dev = metadata.dev();
if dev != expected_dev {
tracing::debug!(
"Skipping file on different device: {} (expected dev {}, found {})",
relative_path.display(),
expected_dev,
dev
);
return Ok(());
}

// Check for hardlinks: if nlink > 1, this file may have other links
// Check for hardlinks: if nlink > 1, this file may have other links.
// We key on (dev, ino) because overlay filesystems may present
// different device numbers for directories vs regular files.
let nlink = metadata.nlink();
if nlink > 1 {
let ino = metadata.ino();
if let Some(first_path) = hardlinks.get(&ino) {
let key = (metadata.dev(), metadata.ino());
if let Some(first_path) = hardlinks.get(&key) {
// This is a hardlink to a file we've already written
let mut header = tar_header_from_meta(tar::EntryType::Link, 0, &metadata);

Expand All @@ -293,7 +274,7 @@ fn add_file_to_tar_from_walk<W: Write>(
return Ok(());
} else {
// First time seeing this inode, record it
hardlinks.insert(ino, relative_path.to_path_buf());
hardlinks.insert(key, relative_path.to_path_buf());
}
}

Expand Down