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