Compare commits

...

7 Commits

Author SHA1 Message Date
Tim Zhang e4551b1013 shim-protos: replace ttrpc-codegen to the released crate version
The new version v0.6.0 has been released.

Signed-off-by: Tim Zhang <tim@hyper.sh>
2025-07-15 17:54:05 +00:00
jinda.ljd 0e4ff0c5da truncate backing file path exceeding 64 bytes in LoopInfo
When the backing file path exceeds 64 bytes, an 'out of range' error occurs due to the limitation of the `file_name` field in `LoopInfo`. This commit truncates the file path to ensure it does not exceed the maximum supported length, preventing the error while maintaining usability.

Signed-off-by: jinda.ljd <jinda.ljd@alibaba-inc.com>
2025-07-15 02:56:39 +00:00
zzzzzzzzzy9 afab3c8eba add some test for mount_rootfs and umount_recursive and setup_loop
Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
2025-07-07 04:51:59 +00:00
zzzzzzzzzy9 11e97809b8 support loop-dev mount in mount_rootfs
Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
2025-07-07 04:51:59 +00:00
zzzzzzzzzy9 15fbabcf8e Unmount the mount point of the container when the container is deleted.
Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
2025-07-07 04:51:59 +00:00
zzzzzzzzzy9 6aa801807e move mount to mount_linux and mount_other
Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
2025-07-07 04:51:59 +00:00
Maksym Pavlenko 220d6d6a65
Fix runc dependency
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
2025-06-30 10:09:42 -07:00
8 changed files with 516 additions and 35 deletions

View File

@ -31,7 +31,7 @@ log.workspace = true
nix = { workspace = true, features = ["socket", "uio", "term"] }
oci-spec.workspace = true
prctl.workspace = true
runc = { path = "../runc", version = "0.2.0", features = ["async"] }
runc = { path = "../runc", version = "0.3.0", features = ["async"] }
serde.workspace = true
serde_json.workspace = true
time.workspace = true

View File

@ -34,6 +34,7 @@ use containerd_shim::{
asynchronous::monitor::{monitor_subscribe, monitor_unsubscribe, Subscription},
io_error,
monitor::{ExitEvent, Subject, Topic},
mount::umount_recursive,
other, other_error,
protos::{
api::ProcessInfo,
@ -312,6 +313,7 @@ impl ProcessLifecycle<InitProcess> for RuncInitLifecycle {
);
}
}
umount_recursive(Path::new(&self.bundle).join("rootfs").to_str(), 0)?;
self.exit_signal.signal();
Ok(())
}

View File

@ -27,6 +27,7 @@ use containerd_shim::{
event::Event,
io_error,
monitor::{Subject, Topic},
mount::umount_recursive,
protos::{events::task::TaskExit, protobuf::MessageDyn, ttrpc::context::with_duration},
util::{
convert_to_timestamp, read_options, read_pid_from_file, read_runtime, read_spec, timestamp,
@ -124,6 +125,8 @@ impl Shim for Service {
runc.delete(&self.id, Some(&DeleteOpts { force: true }))
.await
.unwrap_or_else(|e| warn!("failed to remove runc container: {}", e));
umount_recursive(bundle.join("rootfs").to_str(), 0)
.unwrap_or_else(|e| warn!("failed to umount recursive rootfs: {}", e));
let mut resp = DeleteResponse::new();
// sigkill
resp.set_exit_status(137);

View File

@ -53,9 +53,7 @@ protobuf = "3.7.2"
ttrpc = "0.8.3"
[build-dependencies]
# TODO - replace git sha with next version that incudes the changes from the sha below
# ttrpc-codegen = "0.5.0"
ttrpc-codegen = { git = "https://github.com/containerd/ttrpc-rust", rev = "4929e9079941e7ead82fae3911340f8cbe810e7e" }
ttrpc-codegen = "0.6.0"
[dev-dependencies]
ctrlc = { version = "3.0", features = ["termination"] }

View File

@ -54,6 +54,7 @@ prctl.workspace = true
signal-hook = "0.3.13"
serde.workspace = true
serde_json.workspace = true
tempfile.workspace = true
thiserror.workspace = true
time.workspace = true

View File

@ -55,7 +55,14 @@ pub mod cgroup;
pub mod event;
pub mod logger;
pub mod monitor;
pub mod mount;
#[cfg(target_os = "linux")]
pub mod mount_linux;
#[cfg(not(target_os = "linux"))]
pub mod mount_other;
#[cfg(target_os = "linux")]
pub use mount_linux as mount;
#[cfg(not(target_os = "linux"))]
pub use mount_other as mount;
mod reap;
#[cfg(not(feature = "async"))]
pub mod synchronous;

View File

@ -1,4 +1,3 @@
#![cfg(not(windows))]
/*
Copyright The containerd Authors.
@ -19,36 +18,118 @@
use std::{
collections::HashMap,
env,
fs::File,
io::BufRead,
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not},
os::fd::AsRawFd,
path::Path,
};
use lazy_static::lazy_static;
use log::error;
#[cfg(target_os = "linux")]
use nix::mount::{mount, MsFlags};
#[cfg(target_os = "linux")]
use nix::sched::{unshare, CloneFlags};
#[cfg(target_os = "linux")]
use nix::unistd::{fork, ForkResult};
use nix::{
mount::{mount, MntFlags, MsFlags},
sched::{unshare, CloneFlags},
unistd::{fork, ForkResult},
};
use crate::error::{Error, Result};
#[cfg(not(feature = "async"))]
use crate::monitor::{monitor_subscribe, wait_pid, Topic};
#[cfg(target_os = "linux")]
struct Flag {
clear: bool,
flags: MsFlags,
}
#[cfg(target_os = "linux")]
#[derive(Debug, Default)]
pub struct LoopParams {
readonly: bool,
auto_clear: bool,
direct: bool,
}
#[repr(C)]
#[derive(Debug)]
pub struct LoopInfo {
device: u64,
inode: u64,
rdevice: u64,
offset: u64,
size_limit: u64,
number: u32,
encrypt_type: u32,
encrypt_key_size: u32,
flags: u32,
file_name: [u8; 64],
crypt_name: [u8; 64],
encrypt_key: [u8; 32],
init: [u64; 2],
}
impl Default for LoopInfo {
fn default() -> Self {
LoopInfo {
device: 0,
inode: 0,
rdevice: 0,
offset: 0,
size_limit: 0,
number: 0,
encrypt_type: 0,
encrypt_key_size: 0,
flags: 0,
file_name: [0; 64],
crypt_name: [0; 64],
encrypt_key: [0; 32],
init: [0; 2],
}
}
}
const LOOP_CONTROL_PATH: &str = "/dev/loop-control";
const LOOP_DEV_FORMAT: &str = "/dev/loop";
const EBUSY_STRING: &str = "device or resource busy";
const OVERLAY_LOWERDIR_PREFIX: &str = "lowerdir=";
#[cfg(target_os = "linux")]
#[derive(Debug, Default, Clone)]
struct MountInfo {
/// id is a unique identifier of the mount (may be reused after umount).
pub id: u32,
/// parent is the ID of the parent mount (or of self for the root
/// of this mount namespace's mount tree).
pub parent: u32,
/// major and minor are the major and the minor components of the Dev
/// field of unix.Stat_t structure returned by unix.*Stat calls for
/// files on this filesystem.
pub major: u32,
pub minor: u32,
/// root is the pathname of the directory in the filesystem which forms
/// the root of this mount.
pub root: String,
/// mountpoint is the pathname of the mount point relative to the
/// process's root directory.
pub mountpoint: String,
/// options is a comma-separated list of mount options.
pub options: String,
/// optional are zero or more fields of the form "tag[:value]",
/// separated by a space. Currently, the possible optional fields are
/// "shared", "master", "propagate_from", and "unbindable". For more
/// information, see mount_namespaces(7) Linux man page.
pub optional: String,
/// fs_type is the filesystem type in the form "type[.subtype]".
pub fs_type: String,
/// source is filesystem-specific information, or "none".
pub source: String,
/// vfs_options is a comma-separated list of superblock options.
pub vfs_options: String,
}
lazy_static! {
static ref MOUNT_FLAGS: HashMap<&'static str, Flag> = {
let mut mf = HashMap::new();
let zero: MsFlags = MsFlags::from_bits(0).unwrap();
let zero: MsFlags = MsFlags::empty();
mf.insert(
"async",
Flag {
@ -267,12 +348,10 @@ fn longest_common_prefix(dirs: &[String]) -> &str {
// NOTE: the snapshot id is based on digits.
// in order to avoid to get snapshots/x, should be back to parent dir.
// however, there is assumption that the common dir is ${root}/io.containerd.v1.overlayfs/snapshots.
#[cfg(target_os = "linux")]
fn trim_flawed_dir(s: &str) -> String {
s[0..s.rfind('/').unwrap_or(0) + 1].to_owned()
}
#[cfg(target_os = "linux")]
#[derive(Default)]
struct LowerdirCompactor {
options: Vec<String>,
@ -280,7 +359,6 @@ struct LowerdirCompactor {
lowerdir_prefix: Option<String>,
}
#[cfg(target_os = "linux")]
impl LowerdirCompactor {
fn new(options: &[String]) -> Self {
Self {
@ -409,7 +487,6 @@ impl From<MountExitCode> for Result<()> {
}
#[cfg(not(feature = "async"))]
#[cfg(target_os = "linux")]
pub fn mount_rootfs(
fs_type: Option<&str>,
source: Option<&str>,
@ -428,7 +505,7 @@ pub fn mount_rootfs(
(None, options.to_vec())
};
let mut flags: MsFlags = MsFlags::from_bits(0).unwrap();
let mut flags: MsFlags = MsFlags::empty();
let mut data = Vec::new();
options.iter().for_each(|x| {
if let Some(f) = MOUNT_FLAGS.get(x.as_str()) {
@ -467,7 +544,7 @@ pub fn mount_rootfs(
}
// mount with non-propagation first, or remount with changed data
let oflags = flags.bitand(PROPAGATION_TYPES.not());
let zero: MsFlags = MsFlags::from_bits(0).unwrap();
let zero: MsFlags = MsFlags::empty();
if flags.bitand(MsFlags::MS_REMOUNT).eq(&zero) || data.is_some() {
mount(source, target.as_ref(), fs_type, oflags, data).unwrap_or_else(|err| {
error!(
@ -524,7 +601,6 @@ pub fn mount_rootfs(
}
#[cfg(feature = "async")]
#[cfg(target_os = "linux")]
pub fn mount_rootfs(
fs_type: Option<&str>,
source: Option<&str>,
@ -541,8 +617,10 @@ pub fn mount_rootfs(
(None, options.to_vec())
};
let mut flags: MsFlags = MsFlags::from_bits(0).unwrap();
let mut flags: MsFlags = MsFlags::empty();
let mut data = Vec::new();
let mut lo_setup = false;
let mut loop_params = LoopParams::default();
options.iter().for_each(|x| {
if let Some(f) = MOUNT_FLAGS.get(x.as_str()) {
if f.clear {
@ -550,6 +628,8 @@ pub fn mount_rootfs(
} else {
flags.bitor_assign(f.flags)
}
} else if x.as_str() == "loop" {
lo_setup = true;
} else {
data.push(x.as_str())
}
@ -562,17 +642,31 @@ pub fn mount_rootfs(
None
};
unshare(CloneFlags::CLONE_FS).unwrap();
if let Some(workdir) = chdir {
unshare(CloneFlags::CLONE_FS)?;
env::set_current_dir(Path::new(&workdir)).unwrap_or_else(|_| {
unsafe { libc::_exit(i32::from(MountExitCode::ChdirErr)) };
});
}
// mount with non-propagation first, or remount with changed data
let oflags = flags.bitand(PROPAGATION_TYPES.not());
let zero: MsFlags = MsFlags::from_bits(0).unwrap();
if lo_setup {
loop_params = LoopParams {
readonly: oflags.bitand(MsFlags::MS_RDONLY) == MsFlags::MS_RDONLY,
auto_clear: true,
direct: false,
};
}
let zero: MsFlags = MsFlags::empty();
if flags.bitand(MsFlags::MS_REMOUNT).eq(&zero) || data.is_some() {
mount(source, target.as_ref(), fs_type, oflags, data).map_err(mount_error!(
let mut lo_file = String::new();
let s = if lo_setup {
lo_file = setup_loop(source, loop_params)?;
Some(lo_file.as_str())
} else {
source
};
mount(s, target.as_ref(), fs_type, oflags, data).map_err(mount_error!(
e,
"Mount {:?} to {}",
source,
@ -605,18 +699,297 @@ pub fn mount_rootfs(
Ok(())
}
#[cfg(not(target_os = "linux"))]
pub fn mount_rootfs(
fs_type: Option<&str>,
source: Option<&str>,
options: &[String],
target: impl AsRef<Path>,
) -> Result<()> {
Err(Error::Unimplemented("start".to_string()))
fn setup_loop(source: Option<&str>, params: LoopParams) -> Result<String> {
let src = source.ok_or(other!("loop source is None"))?;
for _ in 0..100 {
let num = get_free_loop_dev()?;
let loop_dev = format!("{}{}", LOOP_DEV_FORMAT, num);
match setup_loop_dev(src, loop_dev.as_str(), &params) {
Ok(_) => return Ok(loop_dev),
Err(e) => {
if e.to_string().contains(EBUSY_STRING) {
continue;
} else {
return Err(e);
}
}
}
}
Err(Error::Other(
"creating new loopback device after 100 times".to_string(),
))
}
pub fn get_free_loop_dev() -> Result<i32> {
const LOOP_CTL_GET_FREE: i32 = 0x4c82;
let loop_control = File::options()
.read(true)
.write(true)
.open(LOOP_CONTROL_PATH)
.map_err(|e| Error::IoError {
context: format!("open {} error: ", LOOP_CONTROL_PATH),
err: e,
})?;
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_control.as_raw_fd() as libc::c_int,
LOOP_CTL_GET_FREE as libc::c_ulong,
) as i32;
#[cfg(target_env = "musl")]
let ret = libc::ioctl(
loop_control.as_raw_fd() as libc::c_int,
LOOP_CTL_GET_FREE as libc::c_int,
) as i32;
match nix::errno::Errno::result(ret) {
Ok(ret) => Ok(ret),
Err(e) => Err(Error::Nix(e)),
}
}
}
pub fn setup_loop_dev(backing_file: &str, loop_dev: &str, params: &LoopParams) -> Result<File> {
const LOOP_SET_FD: u32 = 0x4c00;
const LOOP_CLR_FD: u32 = 0x4c01;
const LOOP_SET_STATUS64: u32 = 0x4c04;
const LOOP_SET_DIRECT_IO: u32 = 0x4c08;
const LO_FLAGS_READ_ONLY: u32 = 0x1;
const LO_FLAGS_AUTOCLEAR: u32 = 0x4;
let mut open_options = File::options();
open_options.read(true);
if !params.readonly {
open_options.write(true);
}
// 1. open backing file
let back = open_options
.open(backing_file)
.map_err(|e| Error::IoError {
context: format!("open {} error: ", backing_file),
err: e,
})?;
let loop_dev = open_options.open(loop_dev).map_err(|e| Error::IoError {
context: format!("open {} error: ", loop_dev),
err: e,
})?;
// 2. set FD
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_FD as libc::c_ulong,
back.as_raw_fd() as libc::c_int,
);
#[cfg(target_env = "musl")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_FD as libc::c_int,
back.as_raw_fd() as libc::c_int,
);
if let Err(e) = nix::errno::Errno::result(ret) {
return Err(Error::Nix(e));
}
}
// 3. set info
let mut info = LoopInfo::default();
let backing_file_truncated = if backing_file.as_bytes().len() > info.file_name.len() {
&backing_file[0..info.file_name.len()]
} else {
backing_file
};
info.file_name[..backing_file_truncated.as_bytes().len()]
.copy_from_slice(backing_file_truncated.as_bytes());
if params.readonly {
info.flags |= LO_FLAGS_READ_ONLY;
}
if params.auto_clear {
info.flags |= LO_FLAGS_AUTOCLEAR;
}
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_STATUS64 as libc::c_ulong,
info,
);
#[cfg(target_env = "musl")]
if let Err(e) = nix::errno::Errno::result(ret) {
libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_CLR_FD as libc::c_int,
0,
);
return Err(Error::Nix(e));
}
}
// 4. Set Direct IO
if params.direct {
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_DIRECT_IO as libc::c_ulong,
1,
);
#[cfg(target_env = "musl")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_DIRECT_IO as libc::c_int,
1,
);
if let Err(e) = nix::errno::Errno::result(ret) {
#[cfg(target_env = "gnu")]
libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_CLR_FD as libc::c_ulong,
0,
);
#[cfg(target_env = "musl")]
libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_CLR_FD as libc::c_int,
0,
);
return Err(Error::Nix(e));
}
}
}
Ok(loop_dev)
}
pub fn umount_recursive(target: Option<&str>, flags: i32) -> Result<()> {
if let Some(target) = target {
let mut mounts = get_mounts(Some(prefix_filter(target.to_string())))?;
mounts.sort_by(|a, b| b.mountpoint.len().cmp(&a.mountpoint.len()));
for (index, target) in mounts.iter().enumerate() {
umount_all(Some(target.clone().mountpoint), flags)?;
}
};
Ok(())
}
fn umount_all(target: Option<String>, flags: i32) -> Result<()> {
if let Some(target) = target {
if let Err(e) = std::fs::metadata(target.clone()) {
if e.kind() == std::io::ErrorKind::NotFound {
return Ok(());
}
}
loop {
if let Err(e) = nix::mount::umount2(
&std::path::PathBuf::from(&target),
MntFlags::from_bits(flags).unwrap_or(MntFlags::empty()),
) {
if e == nix::errno::Errno::EINVAL {
return Ok(());
}
return Err(Error::from(e));
}
}
};
Ok(())
}
fn prefix_filter(prefix: String) -> impl Fn(MountInfo) -> bool {
move |m: MountInfo| {
if let Some(s) = (m.mountpoint.clone() + "/").strip_prefix(&(prefix.clone() + "/")) {
return false;
}
true
}
}
fn get_mounts<F>(f: Option<F>) -> Result<Vec<MountInfo>>
where
F: Fn(MountInfo) -> bool,
{
let mountinfo_path = "/proc/self/mountinfo";
let file = std::fs::File::open(mountinfo_path).map_err(io_error!(e, "io_error"))?;
let reader = std::io::BufReader::new(file);
let lines: Vec<String> = reader.lines().map_while(|line| line.ok()).collect();
let mount_points = lines
.into_iter()
.filter_map(|line| {
/*
See http://man7.org/linux/man-pages/man5/proc.5.html
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount)
(2) parent ID: ID of parent (or of self for the top of the mount tree)
(3) major:minor: value of st_dev for files on filesystem
(4) root: root of the mount within the filesystem
(5) mount point: mount point relative to the process's root
(6) mount options: per mount options
(7) optional fields: zero or more fields of the form "tag[:value]"
(8) separator: marks the end of the optional fields
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options
In other words, we have:
* 6 mandatory fields (1)..(6)
* 0 or more optional fields (7)
* a separator field (8)
* 3 mandatory fields (9)..(11)
*/
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 10 {
// mountpoint parse error.
return None;
}
// separator field
let mut sep_idx = parts.len() - 4;
// In Linux <= 3.9 mounting a cifs with spaces in a share
// name (like "//srv/My Docs") _may_ end up having a space
// in the last field of mountinfo (like "unc=//serv/My Docs").
// Since kernel 3.10-rc1, cifs option "unc=" is ignored,
// so spaces should not appear.
//
// Check for a separator, and work around the spaces bug
for i in (0..sep_idx).rev() {
if parts[i] == "-" {
sep_idx = i;
break;
}
if sep_idx == 5 {
// mountpoint parse error
return None;
}
}
let mut mount_info = MountInfo {
id: str::parse::<u32>(parts[0]).ok()?,
parent: str::parse::<u32>(parts[1]).ok()?,
major: 0,
minor: 0,
root: parts[3].to_string(),
mountpoint: parts[4].to_string(),
options: parts[5].to_string(),
optional: parts[6..sep_idx].join(" "),
fs_type: parts[sep_idx + 1].to_string(),
source: parts[sep_idx + 2].to_string(),
vfs_options: parts[sep_idx + 3].to_string(),
};
let major_minor = parts[2].splitn(3, ':').collect::<Vec<&str>>();
if major_minor.len() != 2 {
// mountpoint parse error.
return None;
}
mount_info.major = str::parse::<u32>(major_minor[0]).ok()?;
mount_info.minor = str::parse::<u32>(major_minor[1]).ok()?;
if let Some(f) = &f {
if f(mount_info.clone()) {
// skip this mountpoint. This mountpoint is not the container's mountpoint
return None;
}
}
Some(mount_info)
})
.collect::<Vec<MountInfo>>();
Ok(mount_points)
}
#[cfg(test)]
#[cfg(target_os = "linux")]
mod tests {
use super::*;
@ -723,4 +1096,66 @@ mod tests {
assert_eq!(options, expected_options);
}
}
#[cfg(feature = "async")]
#[test]
fn test_mount_rootfs_umount_recursive() {
let target = tempfile::tempdir().expect("create target dir error");
let lower1 = tempfile::tempdir().expect("create lower1 dir error");
let lower2 = tempfile::tempdir().expect("create lower2 dir error");
let upperdir = tempfile::tempdir().expect("create upperdir dir error");
let workdir = tempfile::tempdir().expect("create workdir dir error");
let options = vec![
"lowerdir=".to_string()
+ lower1.path().to_str().expect("lower1 path to str error")
+ ":"
+ lower2.path().to_str().expect("lower2 path to str error"),
"upperdir=".to_string()
+ upperdir
.path()
.to_str()
.expect("upperdir path to str error"),
"workdir=".to_string() + workdir.path().to_str().expect("workdir path to str error"),
];
// mount target.
let result = mount_rootfs(Some("overlay"), Some("overlay"), &options, &target);
assert!(result.is_ok());
let mut mountinfo = get_mounts(Some(prefix_filter(
target
.path()
.to_str()
.expect("target path to str error")
.to_string(),
)))
.expect("get_mounts error");
// make sure the target has been mounted.
assert_ne!(0, mountinfo.len());
// umount target.
let result = umount_recursive(target.path().to_str(), 0);
assert!(result.is_ok());
mountinfo = get_mounts(Some(prefix_filter(
target
.path()
.to_str()
.expect("target path to str error")
.to_string(),
)))
.expect("get_mounts error");
// make sure the target has been unmounted.
assert_eq!(0, mountinfo.len());
}
#[cfg(feature = "async")]
#[test]
fn test_setup_loop_dev() {
let path = tempfile::NamedTempFile::new().expect("cannot create tempfile");
let backing_file = path.path().to_str();
let params = LoopParams {
readonly: false,
auto_clear: true,
direct: false,
};
let result = setup_loop(backing_file, params);
assert!(result.is_ok());
}
}

View File

@ -0,0 +1,35 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#![allow(unused)]
use std::path::Path;
use crate::error::{Error, Result};
pub fn mount_rootfs(
fs_type: Option<&str>,
source: Option<&str>,
options: &[String],
target: impl AsRef<Path>,
) -> Result<()> {
// On on-Linux systems, we should return OK
// instead of exiting with an error.
Ok(())
}
pub fn umount_recursive(target: Option<&str>, flags: i32) -> Result<()> {
Ok(())
}