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/).
|
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
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
|
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
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