Add mount and umount syscall

This commit is contained in:
LI Qing 2021-08-31 15:25:51 +08:00 committed by Zongmin.Gu
parent 36918e42bf
commit 7bc2c336b6
16 changed files with 753 additions and 54 deletions

@ -277,7 +277,7 @@ The two aforementioned requirements are not only satisfied by the Occlum toolcha
To debug an app running upon Occlum, one can harness Occlum's builtin support for GDB via `occlum gdb` command. More info can be found [here](demos/gdb_support/). To debug an app running upon Occlum, one can harness Occlum's builtin support for GDB via `occlum gdb` command. More info can be found [here](demos/gdb_support/).
Meanwhile, one can use `occlum mount` command to access and manipulate the secure filesystem for debug purpose. More info can be found [here](docs/occlum_mount.md). Meanwhile, one can use `occlum mount` command to access and manipulate the secure filesystem for debug purpose. More info can be found [here](docs/mount_cmd.md).
If the cause of a problem does not seem to be the app but Occlum itself, then one can take a glimpse into the inner workings of Occlum by checking out its log. Occlum's log level can be adjusted through `OCCLUM_LOG_LEVEL` environment variable. It has six levels: `off`, `error`, `warn`, `debug`, `info`, and `trace`. The default value is `off`, i.e., showing no log messages at all. The most verbose level is `trace`. If the cause of a problem does not seem to be the app but Occlum itself, then one can take a glimpse into the inner workings of Occlum by checking out its log. Occlum's log level can be adjusted through `OCCLUM_LOG_LEVEL` environment variable. It has six levels: `off`, `error`, `warn`, `debug`, `info`, and `trace`. The default value is `off`, i.e., showing no log messages at all. The most verbose level is `trace`.

2
deps/sefs vendored

@ -1 +1 @@
Subproject commit 6eaf1d2f50327631cf62a965f30d56ce40934c76 Subproject commit cee7425b3131ff6bec76aa6a9e9f189386ba47d6

44
docs/runtime_mount.md Normal file

@ -0,0 +1,44 @@
# Mount and Unmount Filesystems at Runtime
## Background
Users can specify the mount configuration in the `Occlum.json` file, then the filesystems are mounted during the libOS startup phase. While this design provides a safe and simple way to access files, it is not as convenient as traditional Host OS. Apps are not allowd to mount and unmount filesystems at runtime.
## How to mount filesystems at runtime?
Apps running inside Occlum can mount some specific filesystems via the [mount()](https://man7.org/linux/man-pages/man2/mount.2.html) system call. This makes it flexible to mount and access files at runtime.
Currently, we only support to create a new mount with the trusted UnionFS consisting of SEFSs or the untrusted HostFS. The mountpoint is not allowd to be the root directory("/").
### 1. Mount trusted UnionFS consisting of SEFSs
Example code:
```
mount("unionfs", "<target_dir>", "unionfs", 0/* mountflags is ignored */,
"lowerdir=<lower>,upperdir=<upper>,key=<128-bit-key>")
```
Mount options:
- The `lowerdir=<lower>` is a mandatory field, which describes the directory path of the RO SEFS on Host OS.
- The `upperdir=<upper>` is a mandatory field, which describes the directory path of the RW SEFS on Host OS.
- The `key=<128-bit-key>` is an optional field, which describes the 128bit key used to encrypt or decrypt the FS. Here is an example of the key: `key=c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61`. If this field is not provided, it will use the automatic key derived from the enclave sealing key.
### 2. Mount untrusted HostFS
Example code:
```
mount("hostfs", “<target_dir>”, "hostfs", 0/* mountflags is ignored */,
"dir=<host_dir>")
```
Mount options:
- The `dir=<host_dir>` is a mandatory field, which describes the directory path on Host OS.
## How to unmount filesystems at runtime?
Apps running inside Occlum can unmount some specific filesystems via the [umount()/umount2()](https://man7.org/linux/man-pages/man2/umount.2.html) system calls. Note that root directory("/") is not allowd to unmount.
Example code:
```
umount("<target_dir>")
```

@ -57,7 +57,7 @@ fn conf_get_hardcoded_file_mac() -> sgx_aes_gcm_128bit_tag_t {
mac mac
} }
fn parse_mac(mac_str: &str) -> Result<sgx_aes_gcm_128bit_tag_t> { pub fn parse_mac(mac_str: &str) -> Result<sgx_aes_gcm_128bit_tag_t> {
let bytes_str_vec = { let bytes_str_vec = {
let bytes_str_vec: Vec<&str> = mac_str.split("-").collect(); let bytes_str_vec: Vec<&str> = mac_str.split("-").collect();
if bytes_str_vec.len() != 16 { if bytes_str_vec.len() != 16 {
@ -73,6 +73,22 @@ fn parse_mac(mac_str: &str) -> Result<sgx_aes_gcm_128bit_tag_t> {
Ok(mac) Ok(mac)
} }
pub fn parse_key(key_str: &str) -> Result<sgx_key_128bit_t> {
let bytes_str_vec = {
let bytes_str_vec: Vec<&str> = key_str.split("-").collect();
if bytes_str_vec.len() != 16 {
return_errno!(EINVAL, "The length or format of KEY string is invalid");
}
bytes_str_vec
};
let mut key: sgx_key_128bit_t = Default::default();
for (byte_i, byte_str) in bytes_str_vec.iter().enumerate() {
key[byte_i] = u8::from_str_radix(byte_str, 16).map_err(|e| errno!(e))?;
}
Ok(key)
}
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub resource_limits: ConfigResourceLimits, pub resource_limits: ConfigResourceLimits,
@ -119,7 +135,26 @@ pub enum ConfigMountFsType {
TYPE_PROCFS, TYPE_PROCFS,
} }
#[derive(Debug)] impl ConfigMountFsType {
pub fn from_input(input: &str) -> Result<ConfigMountFsType> {
const ALL_FS_TYPES: [&str; 6] = ["sefs", "hostfs", "ramfs", "unionfs", "devfs", "procfs"];
let type_ = match input {
"sefs" => ConfigMountFsType::TYPE_SEFS,
"hostfs" => ConfigMountFsType::TYPE_HOSTFS,
"ramfs" => ConfigMountFsType::TYPE_RAMFS,
"unionfs" => ConfigMountFsType::TYPE_UNIONFS,
"devfs" => ConfigMountFsType::TYPE_DEVFS,
"procfs" => ConfigMountFsType::TYPE_PROCFS,
_ => {
return_errno!(EINVAL, "Unsupported file system type");
}
};
Ok(type_)
}
}
#[derive(Default, Debug)]
pub struct ConfigMountOptions { pub struct ConfigMountOptions {
pub mac: Option<sgx_aes_gcm_128bit_tag_t>, pub mac: Option<sgx_aes_gcm_128bit_tag_t>,
pub layers: Option<Vec<ConfigMount>>, pub layers: Option<Vec<ConfigMount>>,
@ -190,19 +225,7 @@ impl ConfigEnv {
impl ConfigMount { impl ConfigMount {
fn from_input(input: &InputConfigMount) -> Result<ConfigMount> { fn from_input(input: &InputConfigMount) -> Result<ConfigMount> {
const ALL_FS_TYPES: [&str; 6] = ["sefs", "hostfs", "ramfs", "unionfs", "devfs", "procfs"]; let type_ = ConfigMountFsType::from_input(input.type_.as_str())?;
let type_ = match input.type_.as_str() {
"sefs" => ConfigMountFsType::TYPE_SEFS,
"hostfs" => ConfigMountFsType::TYPE_HOSTFS,
"ramfs" => ConfigMountFsType::TYPE_RAMFS,
"unionfs" => ConfigMountFsType::TYPE_UNIONFS,
"devfs" => ConfigMountFsType::TYPE_DEVFS,
"procfs" => ConfigMountFsType::TYPE_PROCFS,
_ => {
return_errno!(EINVAL, "Unsupported file system type");
}
};
let target = { let target = {
let target = PathBuf::from(&input.target); let target = PathBuf::from(&input.target);
if !target.starts_with("/") { if !target.starts_with("/") {

@ -98,6 +98,7 @@ impl ToErrno for rcore_fs::vfs::FsError {
FsError::NameTooLong => ENAMETOOLONG, FsError::NameTooLong => ENAMETOOLONG,
FsError::FileTooBig => EFBIG, FsError::FileTooBig => EFBIG,
FsError::OpNotSupported => EOPNOTSUPP, FsError::OpNotSupported => EOPNOTSUPP,
FsError::NotMountPoint => EINVAL,
} }
} }
} }

@ -40,7 +40,12 @@ pub fn init_devfs() -> Result<Arc<MountFS>> {
let mountable_devfs = MountFS::new(devfs); let mountable_devfs = MountFS::new(devfs);
// Mount the ramfs at '/shm' // Mount the ramfs at '/shm'
let ramfs = RamFS::new(); let ramfs = RamFS::new();
mount_fs_at(ramfs, &mountable_devfs.root_inode(), &Path::new("/shm"))?; mount_fs_at(
ramfs,
&mountable_devfs.root_inode(),
&Path::new("/shm"),
true,
)?;
// TODO: Add stdio(stdin, stdout, stderr) into DevFS // TODO: Add stdio(stdin, stdout, stderr) into DevFS
Ok(mountable_devfs) Ok(mountable_devfs)
} }

@ -2,7 +2,9 @@ use super::*;
pub use self::chdir::do_chdir; pub use self::chdir::do_chdir;
pub use self::getcwd::do_getcwd; pub use self::getcwd::do_getcwd;
pub use self::mount::do_mount_rootfs; pub use self::mount::{
do_mount, do_mount_rootfs, do_umount, MountFlags, MountOptions, UmountFlags,
};
pub use self::statfs::{do_fstatfs, do_statfs, Statfs}; pub use self::statfs::{do_fstatfs, do_statfs, Statfs};
pub use self::sync::do_sync; pub use self::sync::do_sync;

@ -1,7 +1,12 @@
use std::path::PathBuf;
use std::sync::Once; use std::sync::Once;
use config::{parse_key, parse_mac, ConfigMount, ConfigMountFsType, ConfigMountOptions};
use rcore_fs_mountfs::MNode;
use util::mem_util::from_user;
use util::resolv_conf_util::write_resolv_conf; use util::resolv_conf_util::write_resolv_conf;
use super::rootfs::{mount_nonroot_fs_according_to, open_root_fs_according_to}; use super::rootfs::{mount_nonroot_fs_according_to, open_root_fs_according_to, umount_nonroot_fs};
use super::*; use super::*;
lazy_static! { lazy_static! {
@ -21,7 +26,7 @@ pub fn do_mount_rootfs(
let rootfs = open_root_fs_according_to(&user_config.mount, user_key)?; let rootfs = open_root_fs_according_to(&user_config.mount, user_key)?;
rootfs.root_inode() rootfs.root_inode()
}; };
mount_nonroot_fs_according_to(&new_root_inode, &user_config.mount, user_key)?; mount_nonroot_fs_according_to(&new_root_inode, &user_config.mount, user_key, true)?;
MOUNT_ONCE.call_once(|| { MOUNT_ONCE.call_once(|| {
let mut root_inode = ROOT_INODE.write().unwrap(); let mut root_inode = ROOT_INODE.write().unwrap();
root_inode.fs().sync().expect("failed to sync old rootfs"); root_inode.fs().sync().expect("failed to sync old rootfs");
@ -34,3 +39,296 @@ pub fn do_mount_rootfs(
Ok(()) Ok(())
} }
pub fn do_mount(
source: &str,
target: &str,
flags: MountFlags,
options: MountOptions,
) -> Result<()> {
debug!(
"mount: source: {}, target: {}, flags: {:?}, options: {:?}",
source, target, flags, options
);
let target = if target == "/" {
return_errno!(EPERM, "can not mount on root");
} else if target.len() > 0 && target.as_bytes()[0] == b'/' {
PathBuf::from(target)
} else {
let thread = current!();
let fs = thread.fs().read().unwrap();
PathBuf::from(fs.convert_to_abs_path(target))
};
if flags.contains(MountFlags::MS_REMOUNT)
|| flags.contains(MountFlags::MS_BIND)
|| flags.contains(MountFlags::MS_SHARED)
|| flags.contains(MountFlags::MS_PRIVATE)
|| flags.contains(MountFlags::MS_SLAVE)
|| flags.contains(MountFlags::MS_UNBINDABLE)
|| flags.contains(MountFlags::MS_MOVE)
{
return_errno!(EINVAL, "Only support to create a new mount");
}
let (mount_configs, user_key) = match options {
MountOptions::UnionFS(unionfs_options) => {
let mc = {
let image_mc = ConfigMount {
type_: ConfigMountFsType::TYPE_SEFS,
target: target.clone(),
source: Some(unionfs_options.lower_dir.clone()),
options: Default::default(),
};
let container_mc = ConfigMount {
type_: ConfigMountFsType::TYPE_SEFS,
target: target.clone(),
source: Some(unionfs_options.upper_dir.clone()),
options: Default::default(),
};
ConfigMount {
type_: ConfigMountFsType::TYPE_UNIONFS,
target,
source: None,
options: ConfigMountOptions {
layers: Some(vec![image_mc, container_mc]),
..Default::default()
},
}
};
(vec![mc], unionfs_options.key)
}
MountOptions::SEFS(sefs_options) => {
let mc = ConfigMount {
type_: ConfigMountFsType::TYPE_SEFS,
target,
source: Some(sefs_options.dir.clone()),
options: ConfigMountOptions {
mac: sefs_options.mac,
..Default::default()
},
};
(vec![mc], sefs_options.key)
}
MountOptions::HostFS(dir) => {
let mc = ConfigMount {
type_: ConfigMountFsType::TYPE_HOSTFS,
target,
source: Some(dir.clone()),
options: Default::default(),
};
(vec![mc], None)
}
MountOptions::RamFS => {
let mc = ConfigMount {
type_: ConfigMountFsType::TYPE_RAMFS,
target,
source: None,
options: Default::default(),
};
(vec![mc], None)
}
};
let mut root_inode = ROOT_INODE.write().unwrap();
// Should we sync the fs before mount?
root_inode.fs().sync()?;
let follow_symlink = !flags.contains(MountFlags::MS_NOSYMFOLLOW);
mount_nonroot_fs_according_to(&*root_inode, &mount_configs, &user_key, follow_symlink)?;
Ok(())
}
pub fn do_umount(target: &str, flags: UmountFlags) -> Result<()> {
debug!("umount: target: {}, flags: {:?}", target, flags);
let target = if target == "/" {
return_errno!(EPERM, "cannot umount rootfs");
} else if target.len() > 0 && target.as_bytes()[0] == b'/' {
target.to_owned()
} else {
let thread = current!();
let fs = thread.fs().read().unwrap();
fs.convert_to_abs_path(target)
};
let mut root_inode = ROOT_INODE.write().unwrap();
// Should we sync the fs before umount?
root_inode.fs().sync()?;
let follow_symlink = !flags.contains(UmountFlags::UMOUNT_NOFOLLOW);
umount_nonroot_fs(&*root_inode, &target, follow_symlink)?;
Ok(())
}
bitflags! {
pub struct MountFlags: u32 {
const MS_RDONLY = 1;
const MS_NOSUID = 2;
const MS_NODEV = 4;
const MS_NOEXEC = 8;
const MS_SYNCHRONOUS = 16;
const MS_REMOUNT = 32;
const MS_MANDLOCK = 64;
const MS_DIRSYNC = 128;
const MS_NOSYMFOLLOW = 256;
const MS_NOATIME = 1024;
const MS_NODIRATIME = 2048;
const MS_BIND = 4096;
const MS_MOVE = 8192;
const MS_REC = 16384;
const MS_SILENT = 32768;
const MS_POSIXACL = 1 << 16;
const MS_UNBINDABLE = 1 << 17;
const MS_PRIVATE = 1 << 18;
const MS_SLAVE = 1 << 19;
const MS_SHARED = 1 << 20;
const MS_RELATIME = 1 << 21;
const MS_KERNMOUNT = 1 << 22;
const MS_I_VERSION = 1 << 23;
const MS_STRICTATIME = 1 << 24;
const MS_LAZYTIME = 1 << 25;
const MS_SUBMOUNT = 1 << 26;
const MS_NOREMOTELOCK = 1 << 27;
const MS_NOSEC = 1 << 28;
const MS_BORN = 1 << 29;
const MS_ACTIVE = 1 << 30;
const MS_NOUSER = 1 << 31;
}
}
#[derive(Debug)]
pub enum MountOptions {
UnionFS(UnionFSMountOptions),
SEFS(SEFSMountOptions),
HostFS(PathBuf),
RamFS,
}
impl MountOptions {
pub fn from_fs_type_and_options(type_: &ConfigMountFsType, options: *const i8) -> Result<Self> {
Ok(match type_ {
ConfigMountFsType::TYPE_SEFS => {
let sefs_mount_options = {
let options = from_user::clone_cstring_safely(options)?
.to_string_lossy()
.into_owned();
SEFSMountOptions::from_input(options.as_str())?
};
Self::SEFS(sefs_mount_options)
}
ConfigMountFsType::TYPE_UNIONFS => {
let unionfs_mount_options = {
let options = from_user::clone_cstring_safely(options)?
.to_string_lossy()
.into_owned();
UnionFSMountOptions::from_input(options.as_str())?
};
Self::UnionFS(unionfs_mount_options)
}
ConfigMountFsType::TYPE_HOSTFS => {
let options = from_user::clone_cstring_safely(options)?
.to_string_lossy()
.into_owned();
let dir = {
let options: Vec<&str> = options.split(",").collect();
let dir = options
.iter()
.find_map(|s| s.strip_prefix("dir="))
.ok_or_else(|| errno!(EINVAL, "no dir options"))?;
PathBuf::from(dir)
};
Self::HostFS(dir)
}
ConfigMountFsType::TYPE_RAMFS => Self::RamFS,
_ => {
return_errno!(EINVAL, "unsupported fs type");
}
})
}
}
#[derive(Debug)]
pub struct UnionFSMountOptions {
lower_dir: PathBuf,
upper_dir: PathBuf,
key: Option<sgx_key_128bit_t>,
}
impl UnionFSMountOptions {
pub fn from_input(input: &str) -> Result<Self> {
let options: Vec<&str> = input.split(",").collect();
let lower_dir = options
.iter()
.find_map(|s| s.strip_prefix("lowerdir="))
.ok_or_else(|| errno!(EINVAL, "no lowerdir options"))?;
let upper_dir = options
.iter()
.find_map(|s| s.strip_prefix("upperdir="))
.ok_or_else(|| errno!(EINVAL, "no upperdir options"))?;
let key = match options.iter().find_map(|s| s.strip_prefix("key=")) {
Some(key_str) => Some(parse_key(key_str)?),
None => None,
};
Ok(Self {
lower_dir: PathBuf::from(lower_dir),
upper_dir: PathBuf::from(upper_dir),
key,
})
}
}
#[derive(Debug)]
pub struct SEFSMountOptions {
dir: PathBuf,
key: Option<sgx_key_128bit_t>,
mac: Option<sgx_aes_gcm_128bit_tag_t>,
}
impl SEFSMountOptions {
pub fn from_input(input: &str) -> Result<Self> {
let options: Vec<&str> = input.split(",").collect();
let dir = options
.iter()
.find_map(|s| s.strip_prefix("dir="))
.ok_or_else(|| errno!(EINVAL, "no dir options"))?;
let key = match options.iter().find_map(|s| s.strip_prefix("key=")) {
Some(key_str) => Some(parse_key(key_str)?),
None => None,
};
let mac = match options.iter().find_map(|s| s.strip_prefix("mac=")) {
Some(mac_str) => Some(parse_mac(mac_str)?),
None => None,
};
Ok(Self {
dir: PathBuf::from(dir),
key,
mac,
})
}
}
bitflags! {
pub struct UmountFlags: u32 {
const MNT_FORCE = 1;
const MNT_DETACH = 2;
const MNT_EXPIRE = 4;
const UMOUNT_NOFOLLOW = 8;
}
}
impl UmountFlags {
pub fn from_u32(raw: u32) -> Result<Self> {
let flags = Self::from_bits(raw).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
if flags.contains(Self::MNT_EXPIRE)
&& (flags.contains(Self::MNT_FORCE) || flags.contains(Self::MNT_DETACH))
{
return_errno!(EINVAL, "MNT_EXPIRE with either MNT_DETACH or MNT_FORCE");
}
Ok(flags)
}
}

@ -161,9 +161,6 @@ impl FsView {
/// Lookup INode from the cwd of the process, dereference symlink /// Lookup INode from the cwd of the process, dereference symlink
pub fn lookup_inode(&self, path: &str) -> Result<Arc<dyn INode>> { pub fn lookup_inode(&self, path: &str) -> Result<Arc<dyn INode>> {
// Linux uses 40 as the upper limit for resolving symbolic links,
// so Occlum use it as a reasonable value
const MAX_SYMLINKS: usize = 40;
debug!("lookup_inode: cwd: {:?}, path: {:?}", self.cwd(), path); debug!("lookup_inode: cwd: {:?}, path: {:?}", self.cwd(), path);
if path.len() > 0 && path.as_bytes()[0] == b'/' { if path.len() > 0 && path.as_bytes()[0] == b'/' {
// absolute path // absolute path

@ -67,3 +67,7 @@ fn split_path(path: &str) -> (&str, &str) {
} }
(dir_path, file_name) (dir_path, file_name)
} }
// Linux uses 40 as the upper limit for resolving symbolic links,
// so Occlum use it as a reasonable value
pub const MAX_SYMLINKS: usize = 40;

@ -22,7 +22,7 @@ lazy_static! {
let rootfs = open_root_fs_according_to(mount_config, &None)?; let rootfs = open_root_fs_according_to(mount_config, &None)?;
rootfs.root_inode() rootfs.root_inode()
}; };
mount_nonroot_fs_according_to(&root_inode, mount_config, &None)?; mount_nonroot_fs_according_to(&root_inode, mount_config, &None, true)?;
Ok(root_inode) Ok(root_inode)
} }
@ -74,10 +74,28 @@ pub fn open_root_fs_according_to(
Ok(root_mountable_unionfs) Ok(root_mountable_unionfs)
} }
pub fn umount_nonroot_fs(
root: &Arc<dyn INode>,
abs_path: &str,
follow_symlink: bool,
) -> Result<()> {
let mount_dir = if follow_symlink {
root.lookup_follow(abs_path, MAX_SYMLINKS)?
} else {
let (dir_path, file_name) = split_path(abs_path);
root.lookup_follow(dir_path, MAX_SYMLINKS)?
.lookup(file_name)?
};
mount_dir.downcast_ref::<MNode>().unwrap().umount()?;
Ok(())
}
pub fn mount_nonroot_fs_according_to( pub fn mount_nonroot_fs_according_to(
root: &Arc<dyn INode>, root: &Arc<dyn INode>,
mount_configs: &Vec<ConfigMount>, mount_configs: &Vec<ConfigMount>,
user_key: &Option<sgx_key_128bit_t>, user_key: &Option<sgx_key_128bit_t>,
follow_symlink: bool,
) -> Result<()> { ) -> Result<()> {
for mc in mount_configs { for mc in mount_configs {
if mc.target == Path::new("/") { if mc.target == Path::new("/") {
@ -92,7 +110,7 @@ pub fn mount_nonroot_fs_according_to(
match mc.type_ { match mc.type_ {
TYPE_SEFS => { TYPE_SEFS => {
let sefs = open_or_create_sefs_according_to(&mc, user_key)?; let sefs = open_or_create_sefs_according_to(&mc, user_key)?;
mount_fs_at(sefs, root, &mc.target)?; mount_fs_at(sefs, root, &mc.target, follow_symlink)?;
} }
TYPE_HOSTFS => { TYPE_HOSTFS => {
if mc.source.is_none() { if mc.source.is_none() {
@ -101,22 +119,44 @@ pub fn mount_nonroot_fs_according_to(
let source_path = mc.source.as_ref().unwrap(); let source_path = mc.source.as_ref().unwrap();
let hostfs = HostFS::new(source_path); let hostfs = HostFS::new(source_path);
mount_fs_at(hostfs, root, &mc.target)?; mount_fs_at(hostfs, root, &mc.target, follow_symlink)?;
} }
TYPE_RAMFS => { TYPE_RAMFS => {
let ramfs = RamFS::new(); let ramfs = RamFS::new();
mount_fs_at(ramfs, root, &mc.target)?; mount_fs_at(ramfs, root, &mc.target, follow_symlink)?;
} }
TYPE_DEVFS => { TYPE_DEVFS => {
let devfs = dev_fs::init_devfs()?; let devfs = dev_fs::init_devfs()?;
mount_fs_at(devfs, root, &mc.target)?; mount_fs_at(devfs, root, &mc.target, follow_symlink)?;
} }
TYPE_PROCFS => { TYPE_PROCFS => {
let procfs = ProcFS::new(); let procfs = ProcFS::new();
mount_fs_at(procfs, root, &mc.target)?; mount_fs_at(procfs, root, &mc.target, follow_symlink)?;
} }
TYPE_UNIONFS => { TYPE_UNIONFS => {
return_errno!(EINVAL, "Cannot mount UnionFS at non-root path"); let layer_mcs = mc
.options
.layers
.as_ref()
.ok_or_else(|| errno!(EINVAL, "Invalid layers for unionfs"))?;
let image_fs_mc = layer_mcs
.get(0)
.ok_or_else(|| errno!(EINVAL, "Invalid image layer"))?;
let container_fs_mc = layer_mcs
.get(1)
.ok_or_else(|| errno!(EINVAL, "Invalid container layer"))?;
let unionfs = match (&image_fs_mc.type_, &container_fs_mc.type_) {
(TYPE_SEFS, TYPE_SEFS) => {
let image_sefs = open_or_create_sefs_according_to(image_fs_mc, user_key)?;
let container_sefs =
open_or_create_sefs_according_to(container_fs_mc, user_key)?;
UnionFS::new(vec![container_sefs, image_sefs])?
}
(_, _) => {
return_errno!(EINVAL, "Unsupported fs type inside unionfs");
}
};
mount_fs_at(unionfs, root, &mc.target, follow_symlink)?;
} }
} }
} }
@ -126,22 +166,21 @@ pub fn mount_nonroot_fs_according_to(
pub fn mount_fs_at( pub fn mount_fs_at(
fs: Arc<dyn FileSystem>, fs: Arc<dyn FileSystem>,
parent_inode: &Arc<dyn INode>, parent_inode: &Arc<dyn INode>,
abs_path: &Path, path: &Path,
follow_symlink: bool,
) -> Result<()> { ) -> Result<()> {
let mut mount_dir = parent_inode.find(".")?; let path = path
// The first component of abs_path is the RootDir, skip it. .to_str()
for dirname in abs_path.iter().skip(1) { .ok_or_else(|| errno!(EINVAL, "invalid path"))?;
mount_dir = match mount_dir.find(dirname.to_str().unwrap()) { let mount_dir = if follow_symlink {
Ok(existing_dir) => { parent_inode.lookup_follow(path, MAX_SYMLINKS)?
if existing_dir.metadata()?.type_ != FileType::Dir { } else {
return_errno!(EIO, "not a directory"); let (dir_path, file_name) = split_path(path);
} parent_inode
existing_dir .lookup_follow(dir_path, MAX_SYMLINKS)?
} .lookup(file_name)?
Err(_) => return_errno!(ENOENT, "Mount point does not exist"), };
}; mount_dir.downcast_ref::<MNode>().unwrap().mount(fs)?;
}
mount_dir.downcast_ref::<MNode>().unwrap().mount(fs);
Ok(()) Ok(())
} }

@ -5,9 +5,11 @@ use super::file_ops::{
FsPath, LinkFlags, StatFlags, UnlinkFlags, AT_FDCWD, FsPath, LinkFlags, StatFlags, UnlinkFlags, AT_FDCWD,
}; };
use super::fs_ops; use super::fs_ops;
use super::fs_ops::{MountFlags, MountOptions, UmountFlags};
use super::time::{clockid_t, itimerspec_t, ClockID}; use super::time::{clockid_t, itimerspec_t, ClockID};
use super::timer_file::{TimerCreationFlags, TimerSetFlags}; use super::timer_file::{TimerCreationFlags, TimerSetFlags};
use super::*; use super::*;
use config::ConfigMountFsType;
use util::mem_util::from_user; use util::mem_util::from_user;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
@ -653,3 +655,41 @@ pub fn do_statfs(path: *const i8, statfs_buf: *mut Statfs) -> Result<isize> {
} }
Ok(0) Ok(0)
} }
pub fn do_mount(
source: *const i8,
target: *const i8,
fs_type: *const i8,
flags: u32,
options: *const i8,
) -> Result<isize> {
let source = from_user::clone_cstring_safely(source)?
.to_string_lossy()
.into_owned();
let target = from_user::clone_cstring_safely(target)?
.to_string_lossy()
.into_owned();
let flags = MountFlags::from_bits(flags).ok_or_else(|| errno!(EINVAL, "invalid flags"))?;
let mount_options = {
let fs_type = {
let fs_type = from_user::clone_cstring_safely(fs_type)?
.to_string_lossy()
.into_owned();
ConfigMountFsType::from_input(fs_type.as_str())?
};
MountOptions::from_fs_type_and_options(&fs_type, options)?
};
fs_ops::do_mount(&source, &target, flags, mount_options)?;
Ok(0)
}
pub fn do_umount(target: *const i8, flags: u32) -> Result<isize> {
let target = from_user::clone_cstring_safely(target)?
.to_string_lossy()
.into_owned();
let flags = UmountFlags::from_u32(flags)?;
fs_ops::do_umount(&target, flags)?;
Ok(0)
}

@ -26,12 +26,12 @@ use crate::fs::{
do_eventfd, do_eventfd2, do_faccessat, do_fallocate, do_fchdir, do_fchmod, do_fchmodat, do_eventfd, do_eventfd2, do_faccessat, do_fallocate, do_fchdir, do_fchmod, do_fchmodat,
do_fchown, do_fchownat, do_fcntl, do_fdatasync, do_fstat, do_fstatat, do_fstatfs, do_fsync, do_fchown, do_fchownat, do_fcntl, do_fdatasync, do_fstat, do_fstatat, do_fstatfs, do_fsync,
do_ftruncate, do_getcwd, do_getdents, do_getdents64, do_ioctl, do_lchown, do_link, do_linkat, do_ftruncate, do_getcwd, do_getdents, do_getdents64, do_ioctl, do_lchown, do_link, do_linkat,
do_lseek, do_lstat, do_mkdir, do_mkdirat, do_mount_rootfs, do_open, do_openat, do_pipe, do_lseek, do_lstat, do_mkdir, do_mkdirat, do_mount, do_mount_rootfs, do_open, do_openat,
do_pipe2, do_pread, do_pwrite, do_read, do_readlink, do_readlinkat, do_readv, do_rename, do_pipe, do_pipe2, do_pread, do_pwrite, do_read, do_readlink, do_readlinkat, do_readv,
do_renameat, do_rmdir, do_sendfile, do_stat, do_statfs, do_symlink, do_symlinkat, do_sync, do_rename, do_renameat, do_rmdir, do_sendfile, do_stat, do_statfs, do_symlink, do_symlinkat,
do_timerfd_create, do_timerfd_gettime, do_timerfd_settime, do_truncate, do_umask, do_unlink, do_sync, do_timerfd_create, do_timerfd_gettime, do_timerfd_settime, do_truncate, do_umask,
do_unlinkat, do_write, do_writev, iovec_t, AsTimer, File, FileDesc, FileRef, HostStdioFds, do_umount, do_unlink, do_unlinkat, do_write, do_writev, iovec_t, AsTimer, File, FileDesc,
Stat, Statfs, FileRef, HostStdioFds, Stat, Statfs,
}; };
use crate::interrupt::{do_handle_interrupt, sgx_interrupt_info_t}; use crate::interrupt::{do_handle_interrupt, sgx_interrupt_info_t};
use crate::misc::{resource_t, rlimit_t, sysinfo_t, utsname_t, RandFlags}; use crate::misc::{resource_t, rlimit_t, sysinfo_t, utsname_t, RandFlags};
@ -253,8 +253,8 @@ macro_rules! process_syscall_table_with_callback {
(Sync = 162) => do_sync(), (Sync = 162) => do_sync(),
(Acct = 163) => handle_unsupported(), (Acct = 163) => handle_unsupported(),
(Settimeofday = 164) => handle_unsupported(), (Settimeofday = 164) => handle_unsupported(),
(Mount = 165) => handle_unsupported(), (Mount = 165) => do_mount(source: *const i8, target: *const i8, fs_type: *const i8, flags: u32, options: *const i8),
(Umount2 = 166) => handle_unsupported(), (Umount2 = 166) => do_umount(target: *const i8, flags: u32),
(Swapon = 167) => handle_unsupported(), (Swapon = 167) => handle_unsupported(),
(Swapoff = 168) => handle_unsupported(), (Swapoff = 168) => handle_unsupported(),
(Reboot = 169) => handle_unsupported(), (Reboot = 169) => handle_unsupported(),

12
test/mount/Makefile Normal file

@ -0,0 +1,12 @@
DEPS_FILE := mnt_test
include ../test_common.mk
EXTRA_C_FLAGS :=
EXTRA_LINK_FLAGS :=
BIN_ARGS :=
mnt_test:
@mkdir -p $(BUILD_DIR)/test/$@/mnt_sefs
@mkdir -p $(BUILD_DIR)/test/$@/mnt_unionfs/upper
@mkdir -p $(BUILD_DIR)/test/$@/mnt_unionfs/lower
@mkdir -p $(BUILD_DIR)/test/$@/mnt_hostfs

234
test/mount/main.c Normal file

@ -0,0 +1,234 @@
#include <sys/stat.h>
#include <sys/mount.h>
#include <errno.h>
#include <fcntl.h>
#include "test_fs.h"
// ============================================================================
// Helper function
// ============================================================================
static int remove_file(const char *file_path) {
int ret;
ret = unlink(file_path);
if (ret < 0) {
THROW_ERROR("failed to unlink the created file");
}
return 0;
}
static int write_read_file(const char *file_path) {
char *write_str = "Hello World\n";
int fd;
fd = open(file_path, O_RDWR | O_CREAT | O_TRUNC, 00666);
if (fd < 0) {
THROW_ERROR("failed to open a file to write");
}
if (write(fd, write_str, strlen(write_str)) <= 0) {
THROW_ERROR("failed to write");
}
close(fd);
if (fs_check_file_content(file_path, write_str) < 0) {
THROW_ERROR("failed to check file content");
}
return 0;
}
static int create_dir(const char *dir) {
struct stat stat_buf;
mode_t mode = 00775;
if (stat(dir, &stat_buf) == 0) {
if (!S_ISDIR(stat_buf.st_mode)) {
if (remove_file(dir) < 0) {
THROW_ERROR("failed to remove: %s", dir);
}
if (mkdir(dir, mode) < 0) {
THROW_ERROR("failed to mkdir: %s", dir);
}
}
} else {
if (mkdir(dir, mode) < 0) {
THROW_ERROR("failed to mkdir: %s", dir);
}
}
return 0;
}
static int check_file_no_exists(const char *file_path) {
struct stat stat_buf;
int ret = stat(file_path, &stat_buf);
if (!(ret < 0 && errno == ENOENT)) {
THROW_ERROR("stat on \"%s\" should return ENOENT", file_path);
}
return 0;
}
// ============================================================================
// Test cases for mount
// ============================================================================
static int __test_mount_sefs(const char *mnt_dir) {
if (create_dir(mnt_dir) < 0) {
THROW_ERROR("failed to create sefs mnt dir");
}
if (mount("sefs", mnt_dir, "sefs", 0, "dir=./mnt_test/mnt_sefs") < 0) {
THROW_ERROR("failed to mount sefs");
}
return 0;
}
static int __test_mount_unionfs(const char *mnt_dir) {
if (create_dir(mnt_dir) < 0) {
THROW_ERROR("failed to create unionfs mnt dir");
}
if (mount("unionfs", mnt_dir, "unionfs", 0,
"lowerdir=./mnt_test/mnt_unionfs/lower,upperdir=./mnt_test/mnt_unionfs/upper") < 0) {
THROW_ERROR("failed to mount unionfs");
}
return 0;
}
static int __test_mount_hostfs(const char *mnt_dir) {
if (create_dir(mnt_dir) < 0) {
THROW_ERROR("failed to create hostfs mnt dir");
}
if (mount("hostfs", mnt_dir, "hostfs", 0, "dir=./mnt_test/mnt_hostfs") < 0) {
THROW_ERROR("failed to mount hostfs");
}
return 0;
}
static int __test_mount_ramfs(const char *mnt_dir) {
if (create_dir(mnt_dir) < 0) {
THROW_ERROR("failed to create ramfs mnt dir");
}
if (mount("ramfs", mnt_dir, "ramfs", 0, NULL) < 0) {
THROW_ERROR("failed to mount ramfs");
}
return 0;
}
typedef int(*test_mount_func_t)(const char *);
static int test_mount_framework(test_mount_func_t fn, const char *dir, bool mount) {
if (fn(dir) < 0) {
return -1;
}
char file_path[PATH_MAX] = { 0 };
snprintf(file_path, sizeof(file_path), "%s/test_write_read.txt", dir);
if (mount) {
if (write_read_file(file_path) < 0) {
THROW_ERROR("failed to RW files on mounted fs");
}
} else {
if (check_file_no_exists(file_path) < 0) {
THROW_ERROR("failed to check file exists after umount");
}
}
return 0;
}
static int test_mount_sefs() {
const char *mnt_dir = "/mnt_sefs";
return test_mount_framework(__test_mount_sefs, mnt_dir, true);
}
static int test_mount_unionfs() {
const char *mnt_dir = "/mnt_unionfs";
return test_mount_framework(__test_mount_unionfs, mnt_dir, true);
}
static int test_mount_hostfs() {
const char *mnt_dir = "/mnt_hostfs";
return test_mount_framework(__test_mount_hostfs, mnt_dir, true);
}
static int test_mount_ramfs() {
const char *mnt_dir = "/mnt_ramfs";
return test_mount_framework(__test_mount_ramfs, mnt_dir, true);
}
// ============================================================================
// Test cases for umount
// ============================================================================
static int __test_umount_fs(const char *target) {
int flags = MNT_EXPIRE | MNT_DETACH;
int ret = umount2(target, flags);
if (!(ret < 0 && errno == EINVAL)) {
THROW_ERROR("failed to check invalid flags");
}
char subdir[PATH_MAX] = { 0 };
snprintf(subdir, sizeof(subdir), "%s/subdir", target);
if (create_dir(subdir) < 0) {
THROW_ERROR("failed to create dir: %s", subdir);
}
ret = umount(subdir);
if (!(ret < 0 && errno == EINVAL)) {
THROW_ERROR("failed to check umount non-mountpoint");
}
if (umount(target) < 0) {
THROW_ERROR("failed to umount fs on: %s", target);
}
return 0;
}
static int test_umount_sefs() {
const char *target = "/mnt_sefs";
return test_mount_framework(__test_umount_fs, target, false);
}
static int test_umount_unionfs() {
const char *target = "/mnt_unionfs";
return test_mount_framework(__test_umount_fs, target, false);
}
static int test_umount_hostfs() {
const char *target = "/mnt_hostfs";
return test_mount_framework(__test_umount_fs, target, false);
}
static int test_umount_ramfs() {
const char *target = "/mnt_ramfs";
return test_mount_framework(__test_umount_fs, target, false);
}
// ============================================================================
// Test suite main
// ============================================================================
static test_case_t test_cases[] = {
// TODO: enable it if SEFS is thread-safe
//TEST_CASE(test_mount_sefs),
//TEST_CASE(test_umount_sefs),
TEST_CASE(test_mount_unionfs),
TEST_CASE(test_umount_unionfs),
TEST_CASE(test_mount_hostfs),
TEST_CASE(test_umount_hostfs),
TEST_CASE(test_mount_ramfs),
TEST_CASE(test_umount_ramfs),
};
int main(int argc, const char *argv[]) {
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}