Skip to content
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
2 changes: 1 addition & 1 deletion hakoniwa/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ impl Command {

if self.container.namespaces.contains(&Namespace::Mount) {
log::debug!(
"Mount: root: {}",
"Mount: <root>: {}",
self.runtime_rootdir_abspath.to_string_lossy(),
);
for mount in self.container.get_mounts() {
Expand Down
40 changes: 33 additions & 7 deletions hakoniwa/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,39 @@ impl Container {

/// Bind mount the `host_path` on `container_path` with read-only access in new MOUNT namespace.
pub fn bindmount_ro(&mut self, host_path: &str, container_path: &str) -> &mut Self {
let flags =
MountOptions::BIND | MountOptions::REC | MountOptions::NOSUID | MountOptions::RDONLY;
self.mount(host_path, container_path, "", flags)
let flags = MountOptions::BIND | MountOptions::REC | MountOptions::NOSUID;
let flags = flags | MountOptions::RDONLY;
self.mount(host_path, container_path, "", flags, None)
}

/// Bind mount the `host_path` on `container_path` with read-write access in new MOUNT namespace.
pub fn bindmount_rw(&mut self, host_path: &str, container_path: &str) -> &mut Self {
let flags = MountOptions::BIND | MountOptions::REC | MountOptions::NOSUID;
self.mount(host_path, container_path, "", flags)
self.mount(host_path, container_path, "", flags, None)
}

/// Mount new overlay on `container_path` with read-only access in new MOUNT namespace.
pub fn overlaymount_ro(&mut self, lowerdir_host_path: &str, container_path: &str) -> &mut Self {
let flags = MountOptions::NOSUID;
let flags = flags | MountOptions::RDONLY;
let data = format!("lowerdir={}", lowerdir_host_path);
self.mount("overlay", container_path, "overlay", flags, Some(data))
}

/// Mount new overlay on `container_path` with read-write access in new MOUNT namespace.
pub fn overlaymount_rw(
&mut self,
lowerdir_host_path: &str,
upperdir_host_path: &str,
workdir_host_path: &str,
container_path: &str,
) -> &mut Self {
let flags = MountOptions::NOSUID;
let data = format!(
"lowerdir={},upperdir={},workdir={}",
lowerdir_host_path, upperdir_host_path, workdir_host_path,
);
self.mount("overlay", container_path, "overlay", flags, Some(data))
}

/// Mount new devfs on `container_path` in new MOUNT namespace.
Expand All @@ -233,19 +257,19 @@ impl Container {
/// of device files in `container_path`, such as `/dev/null`.
pub fn devfsmount(&mut self, container_path: &str) -> &mut Self {
let flags = MountOptions::empty();
self.mount("devfs", container_path, "devfs", flags)
self.mount("devfs", container_path, "devfs", flags, None)
}

/// Mount new tmpfs on `container_path` in new MOUNT namespace.
pub fn tmpfsmount(&mut self, container_path: &str) -> &mut Self {
let flags = MountOptions::NOSUID | MountOptions::NODEV;
self.mount("tmpfs", container_path, "tmpfs", flags)
self.mount("tmpfs", container_path, "tmpfs", flags, None)
}

/// Mount new procfs on `container_path` in new MOUNT namespace.
pub fn procfsmount(&mut self, container_path: &str) -> &mut Self {
let flags = MountOptions::NOSUID | MountOptions::NODEV | MountOptions::NOEXEC;
self.mount("proc", container_path, "proc", flags)
self.mount("proc", container_path, "proc", flags, None)
}

/// Mount.
Expand All @@ -256,6 +280,7 @@ impl Container {
container_path: &str,
fstype: &str,
options: MountOptions,
data: Option<String>,
) -> &mut Self {
let source = host_path.to_string();
let target = container_path.to_string();
Expand All @@ -267,6 +292,7 @@ impl Container {
target,
fstype,
options,
data,
},
);
self
Expand Down
45 changes: 19 additions & 26 deletions hakoniwa/src/runc/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,39 +313,32 @@ pub(crate) fn mount<P1: AsRef<Path> + Debug, P2: AsRef<Path> + Debug>(
map_err!(mount::mount(Some(source), target, NULL, flags, NULL))
}

pub(crate) fn mount_filesystem<P1: AsRef<Path> + Debug, P2: AsRef<Path> + Debug>(
pub(crate) fn mount_with_data<P1: AsRef<Path> + Debug, P2: AsRef<Path> + Debug>(
fstype: &str,
source: P1,
target: P2,
flags: MsFlags,
data: Option<String>,
) -> Result<()> {
let source = source.as_ref();
let target = target.as_ref();
map_err!(mount::mount(
Some(source),
target,
Some(fstype),
flags,
NULL
))
}

pub(crate) fn mount_filesystem_with_data<P1: AsRef<Path> + Debug, P2: AsRef<Path> + Debug>(
fstype: &str,
source: P1,
target: P2,
flags: MsFlags,
data: &str,
) -> Result<()> {
let source = source.as_ref();
let target = target.as_ref();
map_err!(mount::mount(
Some(source),
target,
Some(fstype),
flags,
Some(data)
))
if let Some(d) = data {
map_err!(mount::mount(
Some(source),
target,
Some(fstype),
flags,
Some(d.as_str())
))
} else {
map_err!(mount::mount(
Some(source),
target,
Some(fstype),
flags,
NULL
))
}
}

pub(crate) fn mount_make_private<P: AsRef<Path> + Debug>(target: P) -> Result<()> {
Expand Down
36 changes: 19 additions & 17 deletions hakoniwa/src/runc/unshare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,32 +116,33 @@ fn initialize_rootfs(command: &Command, container: &Container) -> Result<()> {
continue;
}

// Mount tmpfs.
if mount.fstype == "tmpfs" {
sys::mkdir_p(target_relpath)?;
sys::mount_filesystem(
&mount.fstype,
&mount.source,
target_relpath,
mount.options.to_ms_flags(),
)?;
continue;
}

// Mount devfs.
if mount.fstype == "devfs" {
sys::mkdir_p(target_relpath)?;
sys::mount_filesystem_with_data(
sys::mount_with_data(
"tmpfs",
"tmpfs",
target_relpath,
MsFlags::MS_NOSUID,
"mode=755",
Some("mode=755".to_string()),
)?;
initialize_devfs(target_relpath)?;
continue;
}

// Mount overlay, tmpfs.
if mount.fstype == "overlay" || mount.fstype == "tmpfs" {
sys::mkdir_p(target_relpath)?;
sys::mount_with_data(
&mount.fstype,
&mount.source,
target_relpath,
mount.options.to_ms_flags(),
mount.data.clone(),
)?;
continue;
}

// Bind Mounts.
let source_abspath = &mount.source;
source_abspath
Expand Down Expand Up @@ -194,12 +195,12 @@ fn initialize_devfs(target_relpath: &str) -> Result<()> {
let pts_target_relpath = format!("{target_relpath}/pts");
let pts_flags = MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC;
sys::mkdir_p(&pts_target_relpath)?;
sys::mount_filesystem_with_data(
sys::mount_with_data(
"devpts",
"devpts",
pts_target_relpath,
pts_flags,
"newinstance,ptmxmode=0666,mode=620",
Some("newinstance,ptmxmode=0666,mode=620".to_string()),
)?;

let ptmx_original = "pts/ptmx".to_string();
Expand Down Expand Up @@ -303,11 +304,12 @@ fn unprivileged_mount_flags(path: &str, mut flags: MsFlags) -> Result<MsFlags> {
fn mount2(command: &Command, container: &Container) -> Result<()> {
let mount = container.get_mount_newproc();
if let Some(mount) = mount {
sys::mount_filesystem(
sys::mount_with_data(
&mount.fstype,
&mount.source,
&mount.target,
mount.options.to_ms_flags(),
mount.data.clone(),
)?;

let oldproc = format!("/{0}", command.runtime_mount_oldproc);
Expand Down
18 changes: 12 additions & 6 deletions hakoniwa/src/unshare/newns/mount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,27 @@ pub(crate) struct Mount {
pub(crate) target: String,
pub(crate) fstype: String,
pub(crate) options: MountOptions,
pub(crate) data: Option<String>,
}

impl std::fmt::Display for Mount {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.fstype.as_ref() {
"proc" => return write!(f, " proc: {}", self.target),
"devfs" => return write!(f, " devfs: {}", self.target),
"tmpfs" => return write!(f, " tmpfs: {}", self.target),
"proc" => return write!(f, " proc: {}", self.target),
"overlay" => return write!(f, "overlay: {}", self.target),
_ => {}
};
}

if self.options & MountOptions::RDONLY == MountOptions::RDONLY {
write!(f, "bind_ro: {} -> {}", self.source, self.target)
} else {
write!(f, "bind_rw: {} -> {}", self.source, self.target)
if self.options & MountOptions::BIND == MountOptions::BIND {
if self.options & MountOptions::RDONLY == MountOptions::RDONLY {
return write!(f, "bind_ro: {} -> {}", self.source, self.target);
} else {
return write!(f, "bind_rw: {} -> {}", self.source, self.target);
}
}

write!(f, " : {}", self.target)
}
}
86 changes: 83 additions & 3 deletions hakoniwa/tests/container_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ mod container_test {
.rootfs("/")
.unwrap()
.bindmount_ro(&current_dir().to_string_lossy(), "/blah")
.bindmount_ro("/etc/os-release", "/blah/README.md")
.bindmount_ro("/etc/os-release", "/blah/README.md") // the original README.md will not be truncated
.command("/bin/cat")
.arg("/blah/README.md")
.output()
Expand All @@ -294,8 +294,8 @@ mod container_test {
let output = Container::new()
.rootfs("/")
.unwrap()
.bindmount_ro(&dir1.to_string_lossy(), "/mydir")
.bindmount_ro(&dir2.to_string_lossy(), "/mydir")
.bindmount_ro(&dir1.to_string_lossy(), "/mydir") // will be replaced later
.bindmount_ro(&dir2.to_string_lossy(), "/mydir") // the last one takes effect
.command("/bin/cat")
.arg("/mydir/os-release")
.output()
Expand Down Expand Up @@ -452,6 +452,86 @@ mod container_test {
assert!(output.status.success());
}

#[test]
fn test_overlaymount_ro() {
let tmpdir = tempfile::tempdir().unwrap();
let tmpdir_lower = tmpdir.path().join("lower");
fs::create_dir_all(&tmpdir_lower).unwrap();
fs::write(tmpdir_lower.join("hostname"), "lower layer").unwrap();

let lowerdir = format!("{}:/etc", tmpdir_lower.to_string_lossy());
let output = Container::new()
.rootfs("/")
.unwrap()
.overlaymount_ro(&lowerdir, "/etc")
.command("/bin/findmnt")
.args(["-T", "/etc"])
.output()
.unwrap();
assert!(output.status.success());
assert_contains!(String::from_utf8_lossy(&output.stdout), " ro,nosuid");

let output = Container::new()
.rootfs("/")
.unwrap()
.overlaymount_ro(&lowerdir, "/etc")
.command("/bin/cat")
.args(["/etc/hostname"])
.output()
.unwrap();
assert!(output.status.success());
assert_eq!(String::from_utf8_lossy(&output.stdout), "lower layer");
}

#[test]
fn test_overlaymount_rw() {
let tmpdir = tempfile::tempdir().unwrap();
let tmpdir_upper = tmpdir.path().join("upper");
let tmpdir_work = tmpdir.path().join("work");
fs::create_dir_all(&tmpdir_upper).unwrap();
fs::create_dir_all(&tmpdir_work).unwrap();
fs::write(tmpdir_upper.join("hostname"), "upper layer").unwrap();

let lowerdir = "/etc";
let upperdir = tmpdir_upper.to_string_lossy().to_string();
let workdir = tmpdir_work.to_string_lossy().to_string();
let output = Container::new()
.rootfs("/")
.unwrap()
.overlaymount_rw(&lowerdir, &upperdir, &workdir, "/etc")
.command("/bin/findmnt")
.args(["-T", "/etc"])
.output()
.unwrap();
assert!(output.status.success());
assert_contains!(String::from_utf8_lossy(&output.stdout), " rw,nosuid");

let output = Container::new()
.rootfs("/")
.unwrap()
.overlaymount_rw(&lowerdir, &upperdir, &workdir, "/etc")
.command("/bin/cat")
.args(["/etc/hostname"])
.output()
.unwrap();
assert!(output.status.success());
assert_eq!(String::from_utf8_lossy(&output.stdout), "upper layer");

let output = Container::new()
.rootfs("/")
.unwrap()
.overlaymount_rw(&lowerdir, &upperdir, &workdir, "/etc")
.command("/bin/sh")
.args(["-c", "echo 'myhost' > /etc/hostname"])
.output()
.unwrap();
assert!(output.status.success());
assert_eq!(
&fs::read_to_string(tmpdir_upper.join("hostname")).unwrap(),
"myhost\n"
);
}

#[test]
fn test_devfsmount_mount_options() {
let output = Container::new()
Expand Down