Skip to content

Commit 15fbabc

Browse files
zzzzzzzzzy9mxpv
authored andcommitted
Unmount the mount point of the container when the container is deleted.
Signed-off-by: zzzzzzzzzy9 <[email protected]>
1 parent 6aa8018 commit 15fbabc

File tree

4 files changed

+175
-1
lines changed

4 files changed

+175
-1
lines changed

crates/runc-shim/src/runc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use containerd_shim::{
3434
asynchronous::monitor::{monitor_subscribe, monitor_unsubscribe, Subscription},
3535
io_error,
3636
monitor::{ExitEvent, Subject, Topic},
37+
mount::umount_recursive,
3738
other, other_error,
3839
protos::{
3940
api::ProcessInfo,
@@ -312,6 +313,7 @@ impl ProcessLifecycle<InitProcess> for RuncInitLifecycle {
312313
);
313314
}
314315
}
316+
umount_recursive(Path::new(&self.bundle).join("rootfs").to_str(), 0)?;
315317
self.exit_signal.signal();
316318
Ok(())
317319
}

crates/runc-shim/src/service.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use containerd_shim::{
2727
event::Event,
2828
io_error,
2929
monitor::{Subject, Topic},
30+
mount::umount_recursive,
3031
protos::{events::task::TaskExit, protobuf::MessageDyn, ttrpc::context::with_duration},
3132
util::{
3233
convert_to_timestamp, read_options, read_pid_from_file, read_runtime, read_spec, timestamp,
@@ -124,6 +125,8 @@ impl Shim for Service {
124125
runc.delete(&self.id, Some(&DeleteOpts { force: true }))
125126
.await
126127
.unwrap_or_else(|e| warn!("failed to remove runc container: {}", e));
128+
umount_recursive(bundle.join("rootfs").to_str(), 0)
129+
.unwrap_or_else(|e| warn!("failed to umount recursive rootfs: {}", e));
127130
let mut resp = DeleteResponse::new();
128131
// sigkill
129132
resp.set_exit_status(137);

crates/shim/src/mount_linux.rs

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818
use std::{
1919
collections::HashMap,
2020
env,
21+
io::BufRead,
2122
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not},
2223
path::Path,
2324
};
2425

2526
use lazy_static::lazy_static;
2627
use log::error;
2728
use nix::{
28-
mount::{mount, MsFlags},
29+
mount::{mount, MntFlags, MsFlags},
2930
sched::{unshare, CloneFlags},
3031
unistd::{fork, ForkResult},
3132
};
@@ -41,6 +42,39 @@ struct Flag {
4142

4243
const OVERLAY_LOWERDIR_PREFIX: &str = "lowerdir=";
4344

45+
#[derive(Debug, Default, Clone)]
46+
struct MountInfo {
47+
/// id is a unique identifier of the mount (may be reused after umount).
48+
pub id: u32,
49+
/// parent is the ID of the parent mount (or of self for the root
50+
/// of this mount namespace's mount tree).
51+
pub parent: u32,
52+
/// major and minor are the major and the minor components of the Dev
53+
/// field of unix.Stat_t structure returned by unix.*Stat calls for
54+
/// files on this filesystem.
55+
pub major: u32,
56+
pub minor: u32,
57+
/// root is the pathname of the directory in the filesystem which forms
58+
/// the root of this mount.
59+
pub root: String,
60+
/// mountpoint is the pathname of the mount point relative to the
61+
/// process's root directory.
62+
pub mountpoint: String,
63+
/// options is a comma-separated list of mount options.
64+
pub options: String,
65+
/// optional are zero or more fields of the form "tag[:value]",
66+
/// separated by a space. Currently, the possible optional fields are
67+
/// "shared", "master", "propagate_from", and "unbindable". For more
68+
/// information, see mount_namespaces(7) Linux man page.
69+
pub optional: String,
70+
/// fs_type is the filesystem type in the form "type[.subtype]".
71+
pub fs_type: String,
72+
/// source is filesystem-specific information, or "none".
73+
pub source: String,
74+
/// vfs_options is a comma-separated list of superblock options.
75+
pub vfs_options: String,
76+
}
77+
4478
lazy_static! {
4579
static ref MOUNT_FLAGS: HashMap<&'static str, Flag> = {
4680
let mut mf = HashMap::new();
@@ -596,6 +630,137 @@ pub fn mount_rootfs(
596630
Ok(())
597631
}
598632

633+
pub fn umount_recursive(target: Option<&str>, flags: i32) -> Result<()> {
634+
if let Some(target) = target {
635+
let mut mounts = get_mounts(Some(prefix_filter(target.to_string())))?;
636+
mounts.sort_by(|a, b| b.mountpoint.len().cmp(&a.mountpoint.len()));
637+
for (index, target) in mounts.iter().enumerate() {
638+
umount_all(Some(target.clone().mountpoint), flags)?;
639+
}
640+
};
641+
Ok(())
642+
}
643+
644+
fn umount_all(target: Option<String>, flags: i32) -> Result<()> {
645+
if let Some(target) = target {
646+
if let Err(e) = std::fs::metadata(target.clone()) {
647+
if e.kind() == std::io::ErrorKind::NotFound {
648+
return Ok(());
649+
}
650+
}
651+
loop {
652+
if let Err(e) = nix::mount::umount2(
653+
&std::path::PathBuf::from(&target),
654+
MntFlags::from_bits(flags).unwrap_or(MntFlags::empty()),
655+
) {
656+
if e == nix::errno::Errno::EINVAL {
657+
return Ok(());
658+
}
659+
return Err(Error::from(e));
660+
}
661+
}
662+
};
663+
Ok(())
664+
}
665+
666+
fn prefix_filter(prefix: String) -> impl Fn(MountInfo) -> bool {
667+
move |m: MountInfo| {
668+
if let Some(s) = (m.mountpoint.clone() + "/").strip_prefix(&(prefix.clone() + "/")) {
669+
return false;
670+
}
671+
true
672+
}
673+
}
674+
675+
fn get_mounts<F>(f: Option<F>) -> Result<Vec<MountInfo>>
676+
where
677+
F: Fn(MountInfo) -> bool,
678+
{
679+
let mountinfo_path = "/proc/self/mountinfo";
680+
let file = std::fs::File::open(mountinfo_path).map_err(io_error!(e, "io_error"))?;
681+
let reader = std::io::BufReader::new(file);
682+
let lines: Vec<String> = reader.lines().map_while(|line| line.ok()).collect();
683+
let mount_points = lines
684+
.into_iter()
685+
.filter_map(|line| {
686+
/*
687+
See http://man7.org/linux/man-pages/man5/proc.5.html
688+
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
689+
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
690+
(1) mount ID: unique identifier of the mount (may be reused after umount)
691+
(2) parent ID: ID of parent (or of self for the top of the mount tree)
692+
(3) major:minor: value of st_dev for files on filesystem
693+
(4) root: root of the mount within the filesystem
694+
(5) mount point: mount point relative to the process's root
695+
(6) mount options: per mount options
696+
(7) optional fields: zero or more fields of the form "tag[:value]"
697+
(8) separator: marks the end of the optional fields
698+
(9) filesystem type: name of filesystem of the form "type[.subtype]"
699+
(10) mount source: filesystem specific information or "none"
700+
(11) super options: per super block options
701+
In other words, we have:
702+
* 6 mandatory fields (1)..(6)
703+
* 0 or more optional fields (7)
704+
* a separator field (8)
705+
* 3 mandatory fields (9)..(11)
706+
*/
707+
let parts: Vec<&str> = line.split_whitespace().collect();
708+
if parts.len() < 10 {
709+
// mountpoint parse error.
710+
return None;
711+
}
712+
// separator field
713+
let mut sep_idx = parts.len() - 4;
714+
// In Linux <= 3.9 mounting a cifs with spaces in a share
715+
// name (like "//srv/My Docs") _may_ end up having a space
716+
// in the last field of mountinfo (like "unc=//serv/My Docs").
717+
// Since kernel 3.10-rc1, cifs option "unc=" is ignored,
718+
// so spaces should not appear.
719+
//
720+
// Check for a separator, and work around the spaces bug
721+
for i in (0..sep_idx).rev() {
722+
if parts[i] == "-" {
723+
sep_idx = i;
724+
break;
725+
}
726+
if sep_idx == 5 {
727+
// mountpoint parse error
728+
return None;
729+
}
730+
}
731+
732+
let mut mount_info = MountInfo {
733+
id: str::parse::<u32>(parts[0]).ok()?,
734+
parent: str::parse::<u32>(parts[1]).ok()?,
735+
major: 0,
736+
minor: 0,
737+
root: parts[3].to_string(),
738+
mountpoint: parts[4].to_string(),
739+
options: parts[5].to_string(),
740+
optional: parts[6..sep_idx].join(" "),
741+
fs_type: parts[sep_idx + 1].to_string(),
742+
source: parts[sep_idx + 2].to_string(),
743+
vfs_options: parts[sep_idx + 3].to_string(),
744+
};
745+
let major_minor = parts[2].splitn(3, ':').collect::<Vec<&str>>();
746+
if major_minor.len() != 2 {
747+
// mountpoint parse error.
748+
return None;
749+
}
750+
mount_info.major = str::parse::<u32>(major_minor[0]).ok()?;
751+
mount_info.minor = str::parse::<u32>(major_minor[1]).ok()?;
752+
if let Some(f) = &f {
753+
if f(mount_info.clone()) {
754+
// skip this mountpoint. This mountpoint is not the container's mountpoint
755+
return None;
756+
}
757+
}
758+
Some(mount_info)
759+
})
760+
.collect::<Vec<MountInfo>>();
761+
Ok(mount_points)
762+
}
763+
599764
#[cfg(test)]
600765
mod tests {
601766
use super::*;

crates/shim/src/mount_other.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ pub fn mount_rootfs(
2929
// instead of exiting with an error.
3030
Ok(())
3131
}
32+
33+
pub fn umount_recursive(target: Option<&str>, flags: i32) -> Result<()> {
34+
Ok(())
35+
}

0 commit comments

Comments
 (0)