diff --git a/README.md b/README.md index fae8311b..1a42d78b 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/deps/sefs b/deps/sefs index 6eaf1d2f..cee7425b 160000 --- a/deps/sefs +++ b/deps/sefs @@ -1 +1 @@ -Subproject commit 6eaf1d2f50327631cf62a965f30d56ce40934c76 +Subproject commit cee7425b3131ff6bec76aa6a9e9f189386ba47d6 diff --git a/docs/occlum_mount.md b/docs/mount_cmd.md similarity index 100% rename from docs/occlum_mount.md rename to docs/mount_cmd.md diff --git a/docs/runtime_mount.md b/docs/runtime_mount.md new file mode 100644 index 00000000..a2d6e8a1 --- /dev/null +++ b/docs/runtime_mount.md @@ -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", "", "unionfs", 0/* mountflags is ignored */, + "lowerdir=,upperdir=,key=<128-bit-key>") +``` + +Mount options: + +- The `lowerdir=` is a mandatory field, which describes the directory path of the RO SEFS on Host OS. +- The `upperdir=` 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", “”, "hostfs", 0/* mountflags is ignored */, + "dir=") +``` + +Mount options: + +- The `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("") +``` \ No newline at end of file diff --git a/src/libos/src/config.rs b/src/libos/src/config.rs index 6b766d6e..d2f61e92 100644 --- a/src/libos/src/config.rs +++ b/src/libos/src/config.rs @@ -57,7 +57,7 @@ fn conf_get_hardcoded_file_mac() -> sgx_aes_gcm_128bit_tag_t { mac } -fn parse_mac(mac_str: &str) -> Result { +pub fn parse_mac(mac_str: &str) -> Result { 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 { Ok(mac) } +pub fn parse_key(key_str: &str) -> Result { + 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 { + 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, pub layers: Option>, @@ -190,19 +225,7 @@ impl ConfigEnv { impl ConfigMount { fn from_input(input: &InputConfigMount) -> Result { - 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("/") { diff --git a/src/libos/src/error/to_errno.rs b/src/libos/src/error/to_errno.rs index d901db2c..4a7d2ca7 100644 --- a/src/libos/src/error/to_errno.rs +++ b/src/libos/src/error/to_errno.rs @@ -98,6 +98,7 @@ impl ToErrno for rcore_fs::vfs::FsError { FsError::NameTooLong => ENAMETOOLONG, FsError::FileTooBig => EFBIG, FsError::OpNotSupported => EOPNOTSUPP, + FsError::NotMountPoint => EINVAL, } } } diff --git a/src/libos/src/fs/dev_fs/mod.rs b/src/libos/src/fs/dev_fs/mod.rs index e09b7e03..4cddd081 100644 --- a/src/libos/src/fs/dev_fs/mod.rs +++ b/src/libos/src/fs/dev_fs/mod.rs @@ -40,7 +40,12 @@ pub fn init_devfs() -> Result> { 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) } diff --git a/src/libos/src/fs/fs_ops/mod.rs b/src/libos/src/fs/fs_ops/mod.rs index ed8a09b4..abf6a46c 100644 --- a/src/libos/src/fs/fs_ops/mod.rs +++ b/src/libos/src/fs/fs_ops/mod.rs @@ -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; diff --git a/src/libos/src/fs/fs_ops/mount.rs b/src/libos/src/fs/fs_ops/mount.rs index 841cb20f..00cc03cc 100644 --- a/src/libos/src/fs/fs_ops/mount.rs +++ b/src/libos/src/fs/fs_ops/mount.rs @@ -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 { + 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, +} + +impl UnionFSMountOptions { + pub fn from_input(input: &str) -> Result { + 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, + mac: Option, +} + +impl SEFSMountOptions { + pub fn from_input(input: &str) -> Result { + 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 { + 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) + } +} diff --git a/src/libos/src/fs/fs_view.rs b/src/libos/src/fs/fs_view.rs index ed95c9b5..539ffc13 100644 --- a/src/libos/src/fs/fs_view.rs +++ b/src/libos/src/fs/fs_view.rs @@ -161,9 +161,6 @@ impl FsView { /// Lookup INode from the cwd of the process, dereference symlink pub fn lookup_inode(&self, path: &str) -> Result> { - // 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 diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index ff38e4f3..8fdf9c78 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -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; diff --git a/src/libos/src/fs/rootfs.rs b/src/libos/src/fs/rootfs.rs index f2dd140f..b4443e7f 100644 --- a/src/libos/src/fs/rootfs.rs +++ b/src/libos/src/fs/rootfs.rs @@ -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, + 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::().unwrap().umount()?; + Ok(()) +} + pub fn mount_nonroot_fs_according_to( root: &Arc, mount_configs: &Vec, user_key: &Option, + 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, parent_inode: &Arc, - 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"), - }; - } - mount_dir.downcast_ref::().unwrap().mount(fs); + 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::().unwrap().mount(fs)?; Ok(()) } diff --git a/src/libos/src/fs/syscalls.rs b/src/libos/src/fs/syscalls.rs index cd26ec0b..4e7e5441 100644 --- a/src/libos/src/fs/syscalls.rs +++ b/src/libos/src/fs/syscalls.rs @@ -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 { } Ok(0) } + +pub fn do_mount( + source: *const i8, + target: *const i8, + fs_type: *const i8, + flags: u32, + options: *const i8, +) -> Result { + 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 { + 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) +} diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index d71246de..dd3bb20d 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -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(), diff --git a/test/mount/Makefile b/test/mount/Makefile new file mode 100644 index 00000000..5ba2dd2d --- /dev/null +++ b/test/mount/Makefile @@ -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 \ No newline at end of file diff --git a/test/mount/main.c b/test/mount/main.c new file mode 100644 index 00000000..67455e72 --- /dev/null +++ b/test/mount/main.c @@ -0,0 +1,234 @@ +#include +#include +#include +#include +#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)); +}