use boxlite_shared::errors::{BoxliteError, BoxliteResult};
use std::path::Path;
#[cfg(target_os = "linux")]
use std::path::PathBuf;
#[cfg(target_os = "linux")]
use std::process::Command;
#[cfg(target_os = "linux")]
#[allow(dead_code)]
pub fn mount_overlayfs_from_layers(
lower_dirs: &[PathBuf],
upper_dir: &Path,
work_dir: &Path,
target_dir: &Path,
) -> BoxliteResult<()> {
if lower_dirs.is_empty() {
return Err(BoxliteError::Storage(
"Cannot mount overlayfs with no lower directories".into(),
));
}
let lowerdir = lower_dirs
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect::<Vec<_>>()
.join(":");
let mount_options = format!(
"lowerdir={},upperdir={},workdir={}",
lowerdir,
upper_dir.display(),
work_dir.display()
);
tracing::debug!("Mounting overlayfs with options: {}", mount_options);
let output = Command::new("mount")
.args([
"-t",
"overlay",
"overlay",
"-o",
&mount_options,
target_dir
.to_str()
.ok_or_else(|| BoxliteError::Storage("Invalid target path".into()))?,
])
.output()
.map_err(|e| BoxliteError::Storage(format!("Failed to execute mount command: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(BoxliteError::Storage(format!(
"Failed to mount overlayfs: {}",
stderr
)));
}
tracing::info!("Overlayfs mounted at {}", target_dir.display());
Ok(())
}
#[cfg(target_os = "linux")]
#[allow(dead_code)]
pub fn unmount_overlayfs(mount_point: &Path) -> BoxliteResult<()> {
let output = Command::new("umount")
.arg(mount_point)
.output()
.map_err(|e| {
BoxliteError::Storage(format!(
"Failed to execute umount for {}: {}",
mount_point.display(),
e
))
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(BoxliteError::Storage(format!(
"Failed to unmount overlayfs at {}: {}",
mount_point.display(),
stderr
)));
}
tracing::debug!("Unmounted overlayfs at {}", mount_point.display());
Ok(())
}
#[cfg(not(target_os = "linux"))]
#[allow(dead_code)]
pub fn unmount_overlayfs(_mount_point: &Path) -> BoxliteResult<()> {
Ok(()) }
pub fn fix_rootfs_permissions(rootfs: &Path) -> BoxliteResult<()> {
use crate::images::{OverrideFileType, OverrideStat};
use std::fs;
use std::os::unix::fs::PermissionsExt;
tracing::info!(
"Setting per-file xattr for rootfs permissions on {}",
rootfs.display()
);
fn set_xattr_recursive(path: &Path, depth: usize) -> BoxliteResult<usize> {
let metadata = match fs::symlink_metadata(path) {
Ok(m) => m,
Err(e) => {
tracing::debug!("Skipping {}: {}", path.display(), e);
return Ok(0);
}
};
let mut count = 0;
if metadata.file_type().is_symlink() {
return Ok(0);
}
let mode = metadata.permissions().mode() & 0o7777;
let recorded = OverrideStat::read_xattr(path).map_err(|e| {
BoxliteError::Storage(format!(
"Failed to read ownership xattr on {}: {}",
path.display(),
e
))
})?;
let (uid, gid) = recorded.as_ref().map_or((0, 0), |s| (s.uid, s.gid));
let default_type = if metadata.is_dir() {
OverrideFileType::Dir
} else {
OverrideFileType::File
};
let file_type = recorded.map_or(default_type, |s| s.file_type);
let xattr_value = OverrideStat::new(uid, gid, mode, file_type).format();
let needs_write = (mode & 0o200) == 0;
if needs_write {
let mut temp_perms = metadata.permissions();
temp_perms.set_mode(mode | 0o200); let _ = fs::set_permissions(path, temp_perms);
}
match xattr::set(
path,
"user.containers.override_stat",
xattr_value.as_bytes(),
) {
Ok(_) => {
count += 1;
if depth < 2 {
tracing::debug!(
"Set xattr on {}: {} (mode: {:o})",
path.display(),
xattr_value,
mode
);
}
}
Err(e) => {
tracing::trace!("Failed to set xattr on {}: {}", path.display(), e);
}
}
if needs_write {
let mut orig_perms = metadata.permissions();
orig_perms.set_mode(mode);
let _ = fs::set_permissions(path, orig_perms);
}
if metadata.is_dir()
&& let Ok(entries) = fs::read_dir(path)
{
for entry in entries.filter_map(|e| e.ok()) {
count += set_xattr_recursive(&entry.path(), depth + 1)?;
}
}
Ok(count)
}
let count = set_xattr_recursive(rootfs, 0)?;
tracing::info!("✅ Per-file xattr set for {} files in rootfs", count);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn fix_rootfs_permissions_fails_on_malformed_override_stat() {
const CONTAINERS_OVERRIDE_XATTR: &str = "user.containers.override_stat";
let temp = TempDir::new().unwrap();
let dir = temp.path();
let f = dir.join("file");
fs::write(&f, b"x").unwrap();
xattr::set(&f, CONTAINERS_OVERRIDE_XATTR, b"not-a-valid-record")
.expect("seed malformed xattr");
let result = fix_rootfs_permissions(dir);
assert!(
result.is_err(),
"a malformed override_stat xattr is the only copy of a file's \
declared ownership; fix_rootfs_permissions must fail, not \
silently overwrite it with a fresh 0:0 record"
);
}
}