Add mount and umount syscall
This commit is contained in:
parent
36918e42bf
commit
7bc2c336b6
@ -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/).
|
||||
|
||||
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`.
|
||||
|
||||
|
2
deps/sefs
vendored
2
deps/sefs
vendored
@ -1 +1 @@
|
||||
Subproject commit 6eaf1d2f50327631cf62a965f30d56ce40934c76
|
||||
Subproject commit cee7425b3131ff6bec76aa6a9e9f189386ba47d6
|
44
docs/runtime_mount.md
Normal file
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
|
||||
}
|
||||
|
||||
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: Vec<&str> = mac_str.split("-").collect();
|
||||
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)
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct Config {
|
||||
pub resource_limits: ConfigResourceLimits,
|
||||
@ -119,7 +135,26 @@ pub enum ConfigMountFsType {
|
||||
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 mac: Option<sgx_aes_gcm_128bit_tag_t>,
|
||||
pub layers: Option<Vec<ConfigMount>>,
|
||||
@ -190,19 +225,7 @@ impl ConfigEnv {
|
||||
|
||||
impl ConfigMount {
|
||||
fn from_input(input: &InputConfigMount) -> Result<ConfigMount> {
|
||||
const ALL_FS_TYPES: [&str; 6] = ["sefs", "hostfs", "ramfs", "unionfs", "devfs", "procfs"];
|
||||
|
||||
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 type_ = ConfigMountFsType::from_input(input.type_.as_str())?;
|
||||
let target = {
|
||||
let target = PathBuf::from(&input.target);
|
||||
if !target.starts_with("/") {
|
||||
|
@ -98,6 +98,7 @@ impl ToErrno for rcore_fs::vfs::FsError {
|
||||
FsError::NameTooLong => ENAMETOOLONG,
|
||||
FsError::FileTooBig => EFBIG,
|
||||
FsError::OpNotSupported => EOPNOTSUPP,
|
||||
FsError::NotMountPoint => EINVAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,12 @@ pub fn init_devfs() -> Result<Arc<MountFS>> {
|
||||
let mountable_devfs = MountFS::new(devfs);
|
||||
// Mount the ramfs at '/shm'
|
||||
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
|
||||
Ok(mountable_devfs)
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ use super::*;
|
||||
|
||||
pub use self::chdir::do_chdir;
|
||||
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::sync::do_sync;
|
||||
|
||||
|
@ -1,7 +1,12 @@
|
||||
use std::path::PathBuf;
|
||||
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 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::*;
|
||||
|
||||
lazy_static! {
|
||||
@ -21,7 +26,7 @@ pub fn do_mount_rootfs(
|
||||
let rootfs = open_root_fs_according_to(&user_config.mount, user_key)?;
|
||||
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(|| {
|
||||
let mut root_inode = ROOT_INODE.write().unwrap();
|
||||
root_inode.fs().sync().expect("failed to sync old rootfs");
|
||||
@ -34,3 +39,296 @@ pub fn do_mount_rootfs(
|
||||
|
||||
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
|
||||
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);
|
||||
if path.len() > 0 && path.as_bytes()[0] == b'/' {
|
||||
// absolute path
|
||||
|
@ -67,3 +67,7 @@ fn split_path(path: &str) -> (&str, &str) {
|
||||
}
|
||||
(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)?;
|
||||
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)
|
||||
}
|
||||
|
||||
@ -74,10 +74,28 @@ pub fn open_root_fs_according_to(
|
||||
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(
|
||||
root: &Arc<dyn INode>,
|
||||
mount_configs: &Vec<ConfigMount>,
|
||||
user_key: &Option<sgx_key_128bit_t>,
|
||||
follow_symlink: bool,
|
||||
) -> Result<()> {
|
||||
for mc in mount_configs {
|
||||
if mc.target == Path::new("/") {
|
||||
@ -92,7 +110,7 @@ pub fn mount_nonroot_fs_according_to(
|
||||
match mc.type_ {
|
||||
TYPE_SEFS => {
|
||||
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 => {
|
||||
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 hostfs = HostFS::new(source_path);
|
||||
mount_fs_at(hostfs, root, &mc.target)?;
|
||||
mount_fs_at(hostfs, root, &mc.target, follow_symlink)?;
|
||||
}
|
||||
TYPE_RAMFS => {
|
||||
let ramfs = RamFS::new();
|
||||
mount_fs_at(ramfs, root, &mc.target)?;
|
||||
mount_fs_at(ramfs, root, &mc.target, follow_symlink)?;
|
||||
}
|
||||
TYPE_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 => {
|
||||
let procfs = ProcFS::new();
|
||||
mount_fs_at(procfs, root, &mc.target)?;
|
||||
mount_fs_at(procfs, root, &mc.target, follow_symlink)?;
|
||||
}
|
||||
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(
|
||||
fs: Arc<dyn FileSystem>,
|
||||
parent_inode: &Arc<dyn INode>,
|
||||
abs_path: &Path,
|
||||
path: &Path,
|
||||
follow_symlink: bool,
|
||||
) -> Result<()> {
|
||||
let mut mount_dir = parent_inode.find(".")?;
|
||||
// The first component of abs_path is the RootDir, skip it.
|
||||
for dirname in abs_path.iter().skip(1) {
|
||||
mount_dir = match mount_dir.find(dirname.to_str().unwrap()) {
|
||||
Ok(existing_dir) => {
|
||||
if existing_dir.metadata()?.type_ != FileType::Dir {
|
||||
return_errno!(EIO, "not a directory");
|
||||
}
|
||||
existing_dir
|
||||
}
|
||||
Err(_) => return_errno!(ENOENT, "Mount point does not exist"),
|
||||
let path = path
|
||||
.to_str()
|
||||
.ok_or_else(|| errno!(EINVAL, "invalid path"))?;
|
||||
let mount_dir = if follow_symlink {
|
||||
parent_inode.lookup_follow(path, MAX_SYMLINKS)?
|
||||
} else {
|
||||
let (dir_path, file_name) = split_path(path);
|
||||
parent_inode
|
||||
.lookup_follow(dir_path, MAX_SYMLINKS)?
|
||||
.lookup(file_name)?
|
||||
};
|
||||
}
|
||||
mount_dir.downcast_ref::<MNode>().unwrap().mount(fs);
|
||||
mount_dir.downcast_ref::<MNode>().unwrap().mount(fs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,11 @@ use super::file_ops::{
|
||||
FsPath, LinkFlags, StatFlags, UnlinkFlags, AT_FDCWD,
|
||||
};
|
||||
use super::fs_ops;
|
||||
use super::fs_ops::{MountFlags, MountOptions, UmountFlags};
|
||||
use super::time::{clockid_t, itimerspec_t, ClockID};
|
||||
use super::timer_file::{TimerCreationFlags, TimerSetFlags};
|
||||
use super::*;
|
||||
use config::ConfigMountFsType;
|
||||
use util::mem_util::from_user;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
@ -653,3 +655,41 @@ pub fn do_statfs(path: *const i8, statfs_buf: *mut Statfs) -> Result<isize> {
|
||||
}
|
||||
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_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_lseek, do_lstat, do_mkdir, do_mkdirat, do_mount_rootfs, do_open, do_openat, do_pipe,
|
||||
do_pipe2, do_pread, do_pwrite, do_read, do_readlink, do_readlinkat, do_readv, do_rename,
|
||||
do_renameat, do_rmdir, do_sendfile, do_stat, do_statfs, do_symlink, do_symlinkat, do_sync,
|
||||
do_timerfd_create, do_timerfd_gettime, do_timerfd_settime, do_truncate, do_umask, do_unlink,
|
||||
do_unlinkat, do_write, do_writev, iovec_t, AsTimer, File, FileDesc, FileRef, HostStdioFds,
|
||||
Stat, Statfs,
|
||||
do_lseek, do_lstat, do_mkdir, do_mkdirat, do_mount, do_mount_rootfs, do_open, do_openat,
|
||||
do_pipe, do_pipe2, do_pread, do_pwrite, do_read, do_readlink, do_readlinkat, do_readv,
|
||||
do_rename, do_renameat, do_rmdir, do_sendfile, do_stat, do_statfs, do_symlink, do_symlinkat,
|
||||
do_sync, do_timerfd_create, do_timerfd_gettime, do_timerfd_settime, do_truncate, do_umask,
|
||||
do_umount, do_unlink, do_unlinkat, do_write, do_writev, iovec_t, AsTimer, File, FileDesc,
|
||||
FileRef, HostStdioFds, Stat, Statfs,
|
||||
};
|
||||
use crate::interrupt::{do_handle_interrupt, sgx_interrupt_info_t};
|
||||
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(),
|
||||
(Acct = 163) => handle_unsupported(),
|
||||
(Settimeofday = 164) => handle_unsupported(),
|
||||
(Mount = 165) => handle_unsupported(),
|
||||
(Umount2 = 166) => handle_unsupported(),
|
||||
(Mount = 165) => do_mount(source: *const i8, target: *const i8, fs_type: *const i8, flags: u32, options: *const i8),
|
||||
(Umount2 = 166) => do_umount(target: *const i8, flags: u32),
|
||||
(Swapon = 167) => handle_unsupported(),
|
||||
(Swapoff = 168) => handle_unsupported(),
|
||||
(Reboot = 169) => handle_unsupported(),
|
||||
|
12
test/mount/Makefile
Normal file
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
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));
|
||||
}
|
Loading…
Reference in New Issue
Block a user