Add file POSIX advisory range lock

This commit is contained in:
LI Qing 2021-06-18 16:13:28 +08:00 committed by Tate, Hongliang Tian
parent d9744bf971
commit 8f4fbba220
22 changed files with 1055 additions and 187 deletions

@ -56,6 +56,9 @@ jobs:
run: docker exec language_support_test bash -c "cd /root/occlum/demos/embedded_mode && SGX_MODE=SIM make;
SGX_MODE=SIM make test"
- name: Run Golang sqlite test
run: docker exec language_support_test bash -c "cd /root/occlum/demos/golang/go_sqlite/ && SGX_MODE=SIM ./run_go_sqlite_demo.sh"
- name: Go Server set up and run
run: docker exec language_support_test bash -c "cd /root/occlum/demos/golang/web_server && occlum-go mod init web_server && occlum-go get -u -v github.com/gin-gonic/gin;
occlum-go build -o web_server ./web_server.go;
@ -72,9 +75,6 @@ jobs:
sleep ${{ env.nap_time }};
docker exec language_support_test bash -c "cd /root/occlum/demos/golang/grpc_pingpong && SGX_MODE=SIM ./run_ping_on_occlum.sh" &
- name: Run Golang sqlite test
run: docker exec language_support_test bash -c "cd /root/occlum/demos/golang/go_sqlite/ && SGX_MODE=SIM ./run_go_sqlite_demo.sh"
- name: Curl test
run: |
sleep ${{ env.nap_time }};

@ -239,6 +239,10 @@ jobs:
run: docker exec $language_support_test bash -c "cd /root/occlum/demos/embedded_mode && make;
make test"
- name: Run Golang sqlite test
run: docker exec $language_support_test bash -c "export GO111MODULE=on && export GOPROXY=https://goproxy.cn;
cd /root/occlum/demos/golang/go_sqlite/ && ./run_go_sqlite_demo.sh"
- name: Go server set up and run
run: docker exec $language_support_test bash -c "export GO111MODULE=on && export GOPROXY=https://goproxy.cn;
cd /root/occlum/demos/golang/web_server && occlum-go mod init web_server && occlum-go get -u -v github.com/gin-gonic/gin;
@ -257,10 +261,6 @@ jobs:
sleep ${{ env.nap_time }};
docker exec $language_support_test bash -c "cd /root/occlum/demos/golang/grpc_pingpong && ./run_ping_on_occlum.sh"
- name: Run Golang sqlite test
run: docker exec $language_support_test bash -c "export GO111MODULE=on && export GOPROXY=https://goproxy.cn;
cd /root/occlum/demos/golang/go_sqlite/ && ./run_go_sqlite_demo.sh"
# Sleeps longer to make sure the server is up.
- name: Curl test
run: |

2
deps/sefs vendored

@ -1 +1 @@
Subproject commit 0a17ee1ec824181c1cf2db97a05f612e96879645
Subproject commit 232823888c83c2ad3a29dee219d26252bcda37b1

@ -43,6 +43,10 @@ pub trait File: Debug + Sync + Send + Any {
return_op_unsupported_error!("seek")
}
fn position(&self) -> Result<off_t> {
return_op_unsupported_error!("position")
}
fn metadata(&self) -> Result<Metadata> {
return_op_unsupported_error!("metadata")
}
@ -83,14 +87,16 @@ pub trait File: Debug + Sync + Send + Any {
return_op_unsupported_error!("set_status_flags")
}
fn test_advisory_lock(&self, lock: &mut Flock) -> Result<()> {
fn test_advisory_lock(&self, lock: &mut RangeLock) -> Result<()> {
return_op_unsupported_error!("test_advisory_lock")
}
fn set_advisory_lock(&self, lock: &Flock) -> Result<()> {
fn set_advisory_lock(&self, lock: &RangeLock, is_nonblocking: bool) -> Result<()> {
return_op_unsupported_error!("set_advisory_lock")
}
fn release_advisory_locks(&self) {}
fn fallocate(&self, _flags: FallocateFlags, _offset: usize, _len: usize) -> Result<()> {
return_op_unsupported_error!("fallocate")
}

@ -3,13 +3,6 @@ use super::*;
pub fn do_close(fd: FileDesc) -> Result<()> {
debug!("close: fd: {}", fd);
let current = current!();
let mut files = current.files().lock().unwrap();
let file = files.del(fd)?;
// Deadlock note: EpollFile's drop method needs to access file table. So
// if the drop method is invoked inside the del method, then there will be
// a deadlock.
// TODO: make FileTable a struct of internal mutability to avoid deadlock.
drop(files);
drop(file);
current.close_file(fd)?;
Ok(())
}

@ -1,4 +1,4 @@
use super::flock::flock;
use super::flock::c_flock;
use super::*;
use util::mem_util::from_user;
@ -19,9 +19,11 @@ pub enum FcntlCmd<'a> {
/// Set the file status flags
SetFl(u32),
/// Test a file lock
GetLk(&'a mut flock),
GetLk(&'a mut c_flock),
/// Acquire or release a file lock
SetLk(&'a flock),
SetLk(&'a c_flock),
/// The blocking version of SetLK
SetLkWait(&'a c_flock),
}
impl<'a> FcntlCmd<'a> {
@ -35,16 +37,22 @@ impl<'a> FcntlCmd<'a> {
libc::F_GETFL => FcntlCmd::GetFl(),
libc::F_SETFL => FcntlCmd::SetFl(arg as u32),
libc::F_GETLK => {
let flock_mut_ptr = arg as *mut flock;
from_user::check_mut_ptr(flock_mut_ptr)?;
let flock_mut_c = unsafe { &mut *flock_mut_ptr };
FcntlCmd::GetLk(flock_mut_c)
let lock_mut_ptr = arg as *mut c_flock;
from_user::check_mut_ptr(lock_mut_ptr)?;
let lock_mut_c = unsafe { &mut *lock_mut_ptr };
FcntlCmd::GetLk(lock_mut_c)
}
libc::F_SETLK => {
let flock_ptr = arg as *const flock;
from_user::check_ptr(flock_ptr)?;
let flock_c = unsafe { &*flock_ptr };
FcntlCmd::SetLk(flock_c)
let lock_ptr = arg as *const c_flock;
from_user::check_ptr(lock_ptr)?;
let lock_c = unsafe { &*lock_ptr };
FcntlCmd::SetLk(lock_c)
}
libc::F_SETLKW => {
let lock_ptr = arg as *const c_flock;
from_user::check_ptr(lock_ptr)?;
let lock_c = unsafe { &*lock_ptr };
FcntlCmd::SetLkWait(lock_c)
}
_ => return_errno!(EINVAL, "unsupported command"),
})
@ -92,20 +100,39 @@ pub fn do_fcntl(fd: FileDesc, cmd: &mut FcntlCmd) -> Result<isize> {
file.set_status_flags(status_flags)?;
0
}
FcntlCmd::GetLk(flock_mut_c) => {
FcntlCmd::GetLk(lock_mut_c) => {
let file = file_table.get(fd)?;
let mut lock = Flock::from_c(*flock_mut_c)?;
if let FlockType::F_UNLCK = lock.l_type {
let lock_type = RangeLockType::from_u16(lock_mut_c.l_type)?;
if RangeLockType::F_UNLCK == lock_type {
return_errno!(EINVAL, "invalid flock type for getlk");
}
let mut lock = RangeLockBuilder::new()
.type_(lock_type)
.range(FileRange::from_c_flock_and_file(&lock_mut_c, &file)?)
.build()?;
file.test_advisory_lock(&mut lock)?;
(*flock_mut_c).copy_from_safe(&lock);
trace!("getlk returns: {:?}", lock);
(*lock_mut_c).copy_from_range_lock(&lock);
0
}
FcntlCmd::SetLk(flock_c) => {
FcntlCmd::SetLk(lock_c) => {
let file = file_table.get(fd)?;
let lock = Flock::from_c(*flock_c)?;
file.set_advisory_lock(&lock)?;
let lock = RangeLockBuilder::new()
.type_(RangeLockType::from_u16(lock_c.l_type)?)
.range(FileRange::from_c_flock_and_file(&lock_c, &file)?)
.build()?;
let is_nonblocking = true;
file.set_advisory_lock(&lock, is_nonblocking)?;
0
}
FcntlCmd::SetLkWait(lock_c) => {
let file = file_table.get(fd)?;
let lock = RangeLockBuilder::new()
.type_(RangeLockType::from_u16(lock_c.l_type)?)
.range(FileRange::from_c_flock_and_file(&lock_c, &file)?)
.build()?;
let is_nonblocking = false;
file.set_advisory_lock(&lock, is_nonblocking)?;
0
}
};

@ -1,88 +0,0 @@
/// File POSIX advisory lock
use super::*;
use process::pid_t;
/// C struct for a lock
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct flock {
pub l_type: u16,
pub l_whence: u16,
pub l_start: off_t,
pub l_len: off_t,
pub l_pid: pid_t,
}
impl flock {
pub fn copy_from_safe(&mut self, lock: &Flock) {
self.l_type = lock.l_type as u16;
self.l_whence = lock.l_whence as u16;
self.l_start = lock.l_start;
self.l_len = lock.l_len;
self.l_pid = lock.l_pid;
}
}
/// Type safe representation of flock
#[derive(Debug, Copy, Clone)]
pub struct Flock {
pub l_type: FlockType,
pub l_whence: FlockWhence,
pub l_start: off_t,
pub l_len: off_t,
pub l_pid: pid_t,
}
impl Flock {
pub fn from_c(flock_c: &flock) -> Result<Self> {
let l_type = FlockType::from_u16(flock_c.l_type)?;
let l_whence = FlockWhence::from_u16(flock_c.l_whence)?;
Ok(Self {
l_type: l_type,
l_whence: l_whence,
l_start: flock_c.l_start,
l_len: flock_c.l_len,
l_pid: flock_c.l_pid,
})
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
#[repr(u16)]
pub enum FlockType {
F_RDLCK = 0,
F_WRLCK = 1,
F_UNLCK = 2,
}
impl FlockType {
pub fn from_u16(_type: u16) -> Result<Self> {
Ok(match _type {
0 => FlockType::F_RDLCK,
1 => FlockType::F_WRLCK,
2 => FlockType::F_UNLCK,
_ => return_errno!(EINVAL, "invalid flock type"),
})
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
#[repr(u16)]
pub enum FlockWhence {
SEEK_SET = 0,
SEEK_CUR = 1,
SEEK_END = 2,
}
impl FlockWhence {
pub fn from_u16(whence: u16) -> Result<Self> {
Ok(match whence {
0 => FlockWhence::SEEK_SET,
1 => FlockWhence::SEEK_CUR,
2 => FlockWhence::SEEK_END,
_ => return_errno!(EINVAL, "Invalid whence"),
})
}
}

@ -9,7 +9,6 @@ pub use self::dup::{do_dup, do_dup2, do_dup3};
pub use self::fallocate::{do_fallocate, FallocateFlags};
pub use self::fcntl::{do_fcntl, FcntlCmd};
pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags};
pub use self::flock::{Flock, FlockType};
pub use self::fspath::{get_abs_path_by_fd, FsPath, AT_FDCWD};
pub use self::fsync::{do_fdatasync, do_fsync};
pub use self::getdents::{do_getdents, do_getdents64};
@ -39,7 +38,6 @@ mod dup;
mod fallocate;
mod fcntl;
mod file_flags;
mod flock;
mod fspath;
mod fsync;
mod getdents;

@ -151,9 +151,31 @@ impl FileTable {
}
}
/// Remove file descriptors that are close-on-spawn
pub fn close_on_spawn(&mut self) {
/// Remove all the file descriptors
pub fn del_all(&mut self) -> Vec<FileRef> {
let mut deleted_fds = Vec::new();
let mut deleted_files = Vec::new();
for (fd, entry) in self
.table
.iter_mut()
.filter(|entry| entry.is_some())
.enumerate()
{
deleted_files.push(entry.as_ref().unwrap().file.clone());
*entry = None;
deleted_fds.push(fd as FileDesc);
}
self.num_fds = 0;
for fd in deleted_fds {
self.broadcast_del(fd);
}
deleted_files
}
/// Remove file descriptors that are close-on-spawn
pub fn close_on_spawn(&mut self) -> Vec<FileRef> {
let mut deleted_fds = Vec::new();
let mut deleted_files = Vec::new();
for (fd, entry) in self.table.iter_mut().enumerate() {
let need_close = if let Some(entry) = entry {
entry.close_on_spawn
@ -161,6 +183,7 @@ impl FileTable {
false
};
if need_close {
deleted_files.push(entry.as_ref().unwrap().file.clone());
*entry = None;
deleted_fds.push(fd as FileDesc);
self.num_fds -= 1;
@ -170,6 +193,7 @@ impl FileTable {
for fd in deleted_fds {
self.broadcast_del(fd);
}
deleted_files
}
pub fn notifier(&self) -> &FileTableNotifier {

@ -0,0 +1,58 @@
use super::*;
pub struct RangeLockBuilder {
// Mandatory field
type_: Option<RangeLockType>,
range: Option<FileRange>,
// Optional fields
owner: Option<pid_t>,
waiters: Option<WaiterQueue>,
}
impl RangeLockBuilder {
pub fn new() -> Self {
Self {
owner: None,
type_: None,
range: None,
waiters: None,
}
}
pub fn owner(mut self, owner: pid_t) -> Self {
self.owner = Some(owner);
self
}
pub fn type_(mut self, type_: RangeLockType) -> Self {
self.type_ = Some(type_);
self
}
pub fn range(mut self, range: FileRange) -> Self {
self.range = Some(range);
self
}
pub fn waiters(mut self, waiters: WaiterQueue) -> Self {
self.waiters = Some(waiters);
self
}
pub fn build(self) -> Result<RangeLock> {
let owner = self.owner.unwrap_or_else(|| current!().process().pid());
let type_ = self
.type_
.ok_or_else(|| errno!(EINVAL, "type_ is mandatory"))?;
let range = self
.range
.ok_or_else(|| errno!(EINVAL, "range is mandatory"))?;
let waiters = self.waiters;
Ok(RangeLock {
owner,
type_,
range,
waiters,
})
}
}

@ -0,0 +1,387 @@
/// File POSIX advisory range locks
use super::*;
use crate::events::{Waiter, WaiterQueue};
use crate::util::sync::rw_lock::RwLockWriteGuard;
use process::pid_t;
use rcore_fs::vfs::AnyExt;
pub use self::builder::RangeLockBuilder;
pub use self::range::{FileRange, OverlapWith, OFFSET_MAX};
use self::range::{FileRangeChange, RangeLockWhence};
mod builder;
mod range;
/// C struct for a file range lock in Libc
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct c_flock {
/// Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK
pub l_type: u16,
/// Where `l_start' is relative to
pub l_whence: u16,
/// Offset where the lock begins
pub l_start: off_t,
/// Size of the locked area, 0 means until EOF
pub l_len: off_t,
/// Process holding the lock
pub l_pid: pid_t,
}
impl c_flock {
pub fn copy_from_range_lock(&mut self, lock: &RangeLock) {
self.l_type = lock.type_ as u16;
if RangeLockType::F_UNLCK != lock.type_ {
self.l_whence = RangeLockWhence::SEEK_SET as u16;
self.l_start = lock.start() as off_t;
self.l_len = if lock.end() == OFFSET_MAX {
0
} else {
lock.range.len() as off_t
};
self.l_pid = lock.owner;
}
}
}
/// Kernel representation of file range lock
pub struct RangeLock {
/// Owner of lock, process holding the lock
owner: pid_t,
/// Type of lock, F_RDLCK, F_WRLCK, or F_UNLCK
type_: RangeLockType,
/// Range of lock
range: FileRange,
/// Optional waiters that are blocking by the lock
waiters: Option<WaiterQueue>,
}
impl RangeLock {
pub fn type_(&self) -> RangeLockType {
self.type_
}
pub fn set_type(&mut self, type_: RangeLockType) {
self.type_ = type_;
}
pub fn owner(&self) -> pid_t {
self.owner
}
pub fn conflict_with(&self, other: &Self) -> bool {
// locks owned by the same process do not conflict
if self.owner == other.owner {
return false;
}
// locks do not conflict if not overlap
if self.overlap_with(other).is_none() {
return false;
}
// write lock is exclusive
if self.type_ == RangeLockType::F_WRLCK || other.type_ == RangeLockType::F_WRLCK {
return true;
}
false
}
pub fn overlap_with(&self, other: &Self) -> Option<OverlapWith> {
self.range.overlap_with(&other.range)
}
pub fn merge_with(&mut self, other: &Self) {
self.range.merge(&other.range).expect("merge range failed");
}
pub fn start(&self) -> usize {
self.range.start()
}
pub fn end(&self) -> usize {
self.range.end()
}
pub fn set_start(&mut self, new_start: usize) {
let change = self.range.set_start(new_start).expect("invalid new start");
if let FileRangeChange::Shrinked = change {
self.dequeue_and_wake_all_waiters();
}
}
pub fn set_end(&mut self, new_end: usize) {
let change = self.range.set_end(new_end).expect("invalid new end");
if let FileRangeChange::Shrinked = change {
self.dequeue_and_wake_all_waiters();
}
}
pub fn enqueue_waiter(&mut self, waiter: &Waiter) {
if self.waiters.is_none() {
self.waiters = Some(WaiterQueue::new());
}
self.waiters.as_ref().unwrap().reset_and_enqueue(waiter)
}
pub fn dequeue_and_wake_all_waiters(&mut self) -> usize {
if self.waiters.is_some() {
return self.waiters.as_ref().unwrap().dequeue_and_wake_all();
}
0
}
}
impl Drop for RangeLock {
fn drop(&mut self) {
self.dequeue_and_wake_all_waiters();
}
}
impl Debug for RangeLock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RangeLock")
.field("owner", &self.owner)
.field("type_", &self.type_)
.field("range", &self.range)
.finish()
}
}
impl Clone for RangeLock {
fn clone(&self) -> Self {
Self {
owner: self.owner.clone(),
type_: self.type_.clone(),
range: self.range.clone(),
waiters: None,
}
}
}
/// List of File POSIX advisory range locks.
///
/// Rule of ordering:
/// Locks are sorted by owner process, then by the starting offset.
///
/// Rule of mergeing:
/// Adjacent and overlapping locks with same owner and type will be merged.
///
/// Rule of updating:
/// New locks with different type will replace or split the overlapping locks
/// if they have same owner.
///
pub struct RangeLockList {
inner: RwLock<VecDeque<RangeLock>>,
}
impl RangeLockList {
pub fn new() -> Self {
Self {
inner: RwLock::new(VecDeque::new()),
}
}
pub fn test_lock(&self, lock: &mut RangeLock) {
debug!("test_lock with RangeLock: {:?}", lock);
let list = self.inner.read().unwrap();
for existing_lock in list.iter() {
if lock.conflict_with(existing_lock) {
// Return the information about the conflict lock
lock.owner = existing_lock.owner;
lock.type_ = existing_lock.type_;
lock.range = existing_lock.range;
return;
}
}
// The lock could be placed at this time
lock.type_ = RangeLockType::F_UNLCK;
}
pub fn set_lock(&self, lock: &RangeLock, is_nonblocking: bool) -> Result<()> {
debug!(
"set_lock with RangeLock: {:?}, is_nonblocking: {}",
lock, is_nonblocking
);
loop {
let mut list = self.inner.write().unwrap();
if let Some(mut conflict_lock) = list.iter_mut().find(|l| l.conflict_with(lock)) {
if is_nonblocking {
return_errno!(EAGAIN, "lock conflict, try again later");
}
// Start to wait
let waiter = Waiter::new();
// TODO: Add deadlock detection, and returns EDEADLK
warn!("Do not support deadlock detection, maybe wait infinitely");
conflict_lock.enqueue_waiter(&waiter);
// Ensure that we drop any locks before wait
drop(list);
waiter.wait(None)?;
// Wake up, let's try to set lock again
continue;
}
// No conflict here, let's insert the lock
return Ok(Self::insert_lock_into_list(&mut list, lock));
}
}
fn insert_lock_into_list(list: &mut RwLockWriteGuard<VecDeque<RangeLock>>, lock: &RangeLock) {
let first_same_owner_idx = match list.iter().position(|lk| lk.owner() == lock.owner()) {
Some(idx) => idx,
None => {
// Can't find existing locks with same owner.
list.push_front(lock.clone());
return;
}
};
// Insert the lock at the start position with same owner, may breaking
// the rules of RangeLockList.
// We will handle the inserted lock with next one to adjust the list to
// obey the rules.
list.insert(first_same_owner_idx, lock.clone());
let mut pre_idx = first_same_owner_idx;
let mut next_idx = pre_idx + 1;
loop {
if next_idx >= list.len() {
break;
}
let pre_lock = list[pre_idx].clone();
let next_lock = list[next_idx].clone();
if next_lock.owner() != pre_lock.owner() {
break;
}
if next_lock.type_() == pre_lock.type_() {
// Same type
if pre_lock.end() < next_lock.start() {
break;
} else if next_lock.end() < pre_lock.start() {
list.swap(pre_idx, next_idx);
pre_idx += 1;
next_idx += 1;
} else {
// Merge adjacent or overlapping locks
list[next_idx].merge_with(&pre_lock);
list.remove(pre_idx);
}
} else {
// Different type
if pre_lock.end() <= next_lock.start() {
break;
} else if next_lock.end() <= pre_lock.start() {
list.swap(pre_idx, next_idx);
pre_idx += 1;
next_idx += 1;
} else {
// Split overlapping locks
let overlap_with = pre_lock.overlap_with(&next_lock).unwrap();
match overlap_with {
OverlapWith::ToLeft => {
list[next_idx].set_start(pre_lock.end());
break;
}
OverlapWith::InMiddle => {
let right_lk = {
let mut r_lk = next_lock.clone();
r_lk.set_start(pre_lock.end());
r_lk
};
list[next_idx].set_end(pre_lock.start());
list.swap(pre_idx, next_idx);
list.insert(next_idx + 1, right_lk);
break;
}
OverlapWith::ToRight => {
list[next_idx].set_end(pre_lock.start());
list.swap(pre_idx, next_idx);
pre_idx += 1;
next_idx += 1;
}
OverlapWith::Includes => {
// New lock can replace the old one
list.remove(next_idx);
}
}
}
}
}
}
pub fn unlock(&self, lock: &RangeLock) {
debug!("unlock with RangeLock: {:?}", lock);
let mut list = self.inner.write().unwrap();
let mut skipped = 0;
loop {
let idx = match list
.iter()
.skip(skipped)
.position(|lk| lk.owner() == lock.owner())
{
Some(idx) => idx,
None => break,
};
let existing_lock = &mut list[idx];
let overlap_with = match lock.overlap_with(existing_lock) {
Some(overlap_with) => overlap_with,
None => {
skipped = idx + 1;
continue;
}
};
match overlap_with {
OverlapWith::ToLeft => {
existing_lock.set_start(lock.end());
break;
}
OverlapWith::InMiddle => {
// Split the lock
let right_lk = {
let mut r_lk = existing_lock.clone();
r_lk.set_start(lock.end());
r_lk
};
existing_lock.set_end(lock.start());
list.insert(idx + 1, right_lk);
break;
}
OverlapWith::ToRight => {
existing_lock.set_end(lock.start());
skipped = idx + 1;
}
OverlapWith::Includes => {
// The lock can be deleted from the list
list.remove(idx);
skipped = idx;
}
}
}
}
}
impl Default for RangeLockList {
fn default() -> Self {
Self::new()
}
}
impl AnyExt for RangeLockList {}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u16)]
pub enum RangeLockType {
F_RDLCK = 0,
F_WRLCK = 1,
F_UNLCK = 2,
}
impl RangeLockType {
pub fn from_u16(_type: u16) -> Result<Self> {
Ok(match _type {
0 => RangeLockType::F_RDLCK,
1 => RangeLockType::F_WRLCK,
2 => RangeLockType::F_UNLCK,
_ => return_errno!(EINVAL, "invalid flock type"),
})
}
}

@ -0,0 +1,175 @@
use super::*;
pub const OFFSET_MAX: usize = off_t::MAX as usize;
#[derive(Debug, Copy, Clone)]
pub struct FileRange {
start: usize,
end: usize,
}
impl FileRange {
/// Create the range through C flock and opened file reference
pub fn from_c_flock_and_file(lock: &c_flock, file: &FileRef) -> Result<Self> {
let start = {
let whence = RangeLockWhence::from_u16(lock.l_whence)?;
match whence {
RangeLockWhence::SEEK_SET => lock.l_start,
RangeLockWhence::SEEK_CUR => file
.position()?
.checked_add(lock.l_start)
.ok_or_else(|| errno!(EOVERFLOW, "start overflow"))?,
RangeLockWhence::SEEK_END => (file.metadata()?.size as off_t)
.checked_add(lock.l_start)
.ok_or_else(|| errno!(EOVERFLOW, "start overflow"))?,
}
};
if start < 0 {
return_errno!(EINVAL, "invalid start");
}
let (start, end) = if lock.l_len > 0 {
let end = start
.checked_add(lock.l_len)
.ok_or_else(|| errno!(EOVERFLOW, "end overflow"))?;
(start as usize, end as usize)
} else if lock.l_len == 0 {
(start as usize, OFFSET_MAX)
} else {
// len < 0, must recalculate the start
let end = start;
let new_start = start + lock.l_len;
if new_start < 0 {
return_errno!(EINVAL, "invalid len");
}
(new_start as usize, end as usize)
};
Ok(Self { start, end })
}
pub fn new(start: usize, end: usize) -> Result<Self> {
if start >= end {
return_errno!(EINVAL, "invalid parameters");
}
Ok(Self { start, end })
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn set_start(&mut self, new_start: usize) -> Result<FileRangeChange> {
if new_start >= self.end {
return_errno!(EINVAL, "invalid new start");
}
let old_start = self.start;
self.start = new_start;
let change = if new_start > old_start {
FileRangeChange::Shrinked
} else if new_start < old_start {
FileRangeChange::Expanded
} else {
FileRangeChange::Same
};
Ok(change)
}
pub fn set_end(&mut self, new_end: usize) -> Result<FileRangeChange> {
if new_end <= self.start {
return_errno!(EINVAL, "invalid new end");
}
let old_end = self.end;
self.end = new_end;
let change = if new_end < old_end {
FileRangeChange::Shrinked
} else if new_end > old_end {
FileRangeChange::Expanded
} else {
FileRangeChange::Same
};
Ok(change)
}
pub fn overlap_with(&self, other: &Self) -> Option<OverlapWith> {
if self.start >= other.end || self.end <= other.start {
return None;
}
let overlap = if self.start <= other.start && self.end < other.end {
OverlapWith::ToLeft
} else if self.start > other.start && self.end < other.end {
OverlapWith::InMiddle
} else if self.start > other.start && self.end >= other.end {
OverlapWith::ToRight
} else {
OverlapWith::Includes
};
Some(overlap)
}
pub fn merge(&mut self, other: &Self) -> Result<FileRangeChange> {
if self.end < other.start || other.end < self.start {
return_errno!(EINVAL, "can not merge separated ranges");
}
let mut change = FileRangeChange::Same;
if other.start < self.start {
self.start = other.start;
change = FileRangeChange::Expanded;
}
if other.end > self.end {
self.end = other.end;
change = FileRangeChange::Expanded;
}
Ok(change)
}
}
#[derive(Debug)]
pub enum FileRangeChange {
Same,
Expanded,
Shrinked,
}
/// The position of a range (say A) relative another overlapping range (say B).
#[derive(Debug)]
pub enum OverlapWith {
/// The position where range A is to the left of B (A.start <= B.start && A.end < B.end).
ToLeft,
/// The position where range A is to the right of B (A.start > B.start && A.end >= B.end).
ToRight,
/// The position where range A is in the middle of B (A.start > B.start && A.end < B.end).
InMiddle,
/// The position where range A includes B (A.start <= B.start && A.end >= B.end).
Includes,
}
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
#[repr(u16)]
pub enum RangeLockWhence {
SEEK_SET = 0,
SEEK_CUR = 1,
SEEK_END = 2,
}
impl RangeLockWhence {
pub fn from_u16(whence: u16) -> Result<Self> {
Ok(match whence {
0 => RangeLockWhence::SEEK_SET,
1 => RangeLockWhence::SEEK_CUR,
2 => RangeLockWhence::SEEK_END,
_ => return_errno!(EINVAL, "Invalid whence"),
})
}
}

@ -112,6 +112,11 @@ impl File for INodeFile {
Ok(*offset as i64)
}
fn position(&self) -> Result<off_t> {
let offset = self.offset.lock().unwrap();
Ok(*offset as off_t)
}
fn metadata(&self) -> Result<Metadata> {
let metadata = self.inode.metadata()?;
Ok(metadata)
@ -182,30 +187,60 @@ impl File for INodeFile {
Ok(())
}
fn test_advisory_lock(&self, lock: &mut Flock) -> Result<()> {
// Let the advisory lock could be placed
// TODO: Implement the real advisory lock
lock.l_type = FlockType::F_UNLCK;
fn test_advisory_lock(&self, lock: &mut RangeLock) -> Result<()> {
let ext = match self.inode.ext() {
Some(ext) => ext,
None => {
warn!("Inode extension is not supportted, the lock could be placed");
lock.set_type(RangeLockType::F_UNLCK);
return Ok(());
}
};
match ext.get::<RangeLockList>() {
None => {
// The advisory lock could be placed if there is no lock list
lock.set_type(RangeLockType::F_UNLCK);
}
Some(range_lock_list) => {
range_lock_list.test_lock(lock);
}
}
Ok(())
}
fn set_advisory_lock(&self, lock: &Flock) -> Result<()> {
match lock.l_type {
FlockType::F_RDLCK => {
if !self.access_mode.readable() {
return_errno!(EACCES, "File not readable");
}
}
FlockType::F_WRLCK => {
if !self.access_mode.writable() {
return_errno!(EACCES, "File not writable");
}
}
_ => (),
fn set_advisory_lock(&self, lock: &RangeLock, is_nonblocking: bool) -> Result<()> {
if RangeLockType::F_UNLCK == lock.type_() {
return Ok(self.unlock_range_lock(lock));
}
// Let the advisory lock could be acquired or released
// TODO: Implement the real advisory lock
Ok(())
self.check_advisory_lock_with_access_mode(lock)?;
let ext = match self.inode.ext() {
Some(ext) => ext,
None => {
warn!(
"Inode extension is not supported, let the lock could be acquired or released"
);
// TODO: Implement inode extension for FS
return Ok(());
}
};
let range_lock_list = match ext.get::<RangeLockList>() {
Some(list) => list,
None => ext.get_or_put_default::<RangeLockList>(),
};
range_lock_list.set_lock(lock, is_nonblocking)
}
fn release_advisory_locks(&self) {
let range_lock = RangeLockBuilder::new()
.type_(RangeLockType::F_UNLCK)
.range(FileRange::new(0, OFFSET_MAX).unwrap())
.build()
.unwrap();
self.unlock_range_lock(&range_lock)
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
@ -269,6 +304,40 @@ impl INodeFile {
pub fn abs_path(&self) -> &str {
&self.abs_path
}
fn check_advisory_lock_with_access_mode(&self, lock: &RangeLock) -> Result<()> {
match lock.type_() {
RangeLockType::F_RDLCK => {
if !self.access_mode.readable() {
return_errno!(EBADF, "File not readable");
}
}
RangeLockType::F_WRLCK => {
if !self.access_mode.writable() {
return_errno!(EBADF, "File not writable");
}
}
_ => (),
}
Ok(())
}
fn unlock_range_lock(&self, lock: &RangeLock) {
let ext = match self.inode.ext() {
Some(ext) => ext,
None => {
return;
}
};
let range_lock_list = match ext.get::<RangeLockList>() {
Some(list) => list,
None => {
return;
}
};
range_lock_list.unlock(lock)
}
}
impl Debug for INodeFile {

@ -20,10 +20,12 @@ pub use self::events::{AtomicIoEvents, IoEvents, IoNotifier};
pub use self::file::{File, FileRef};
pub use self::file_ops::{
occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FallocateFlags, FileMode,
Flock, FlockType, IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType,
StructuredIoctlNum,
IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType, StructuredIoctlNum,
};
pub use self::file_table::{FileDesc, FileTable, FileTableEvent, FileTableNotifier};
pub use self::flock::{
FileRange, RangeLock, RangeLockBuilder, RangeLockList, RangeLockType, OFFSET_MAX,
};
pub use self::fs_ops::Statfs;
pub use self::fs_view::FsView;
pub use self::host_fd::HostFd;
@ -41,6 +43,7 @@ mod events;
mod file;
mod file_ops;
mod file_table;
mod flock;
mod fs_ops;
mod fs_view;
mod host_fd;

@ -64,8 +64,9 @@ fn exit_thread(term_status: TermStatus) {
table::del_thread(thread.tid()).expect("tid must be in the table");
}
// If this thread is the last thread, then exit the process
// If this thread is the last thread, close all files then exit the process
if num_remaining_threads == 0 {
thread.close_all_files();
exit_process(&thread, term_status);
}

@ -246,7 +246,7 @@ fn new_process_common(
};
let vm_ref = Arc::new(vm);
let files_ref = {
let files = init_files(current_ref, file_actions, host_stdio_fds)?;
let files = init_files(current_ref, file_actions, host_stdio_fds, &reuse_tid)?;
Arc::new(SgxMutex::new(files))
};
let fs_ref = Arc::new(RwLock::new(current_ref.fs().read().unwrap().clone()));
@ -347,12 +347,25 @@ fn init_files(
current_ref: &ThreadRef,
file_actions: &[FileAction],
host_stdio_fds: Option<&HostStdioFds>,
reuse_tid: &Option<ThreadId>,
) -> Result<FileTable> {
// Usually, we just inherit the file table from the current process
let should_inherit_file_table = current_ref.process().pid() > 0;
if should_inherit_file_table {
// Fork: clone file table
let mut cloned_file_table = current_ref.files().lock().unwrap().clone();
// By default, file descriptors remain open across an execve().
// File descriptors that are marked close-on-exec are closed, which will cause
// the release of advisory locks owned by current process.
if reuse_tid.is_some() {
let closed_files = cloned_file_table.close_on_spawn();
for file in closed_files {
file.release_advisory_locks();
}
return Ok(cloned_file_table);
}
// Perform file actions to modify the cloned file table
for file_action in file_actions {
match file_action {

@ -124,6 +124,27 @@ impl Thread {
self.files().lock().unwrap().put(new_file, close_on_spawn)
}
/// Close a file from the file table. It will release the POSIX advisory locks owned
/// by current process.
pub fn close_file(&self, fd: FileDesc) -> Result<()> {
// Deadlock note: EpollFile's drop method needs to access file table. So
// if the drop method is invoked inside the del method, then there will be
// a deadlock.
let file = self.files().lock().unwrap().del(fd)?;
file.release_advisory_locks();
Ok(())
}
/// Close all files in the file table. It will release the POSIX advisory locks owned
/// by current process.
pub fn close_all_files(&self) {
// Deadlock note: Same with the issue in close_file method
let files = self.files().lock().unwrap().del_all();
for file in files {
file.release_advisory_locks();
}
}
pub fn fs(&self) -> &FsViewRef {
&self.fs
}

@ -16,8 +16,8 @@ FAIL_LOG = $(BUILD_DIR)/test/.fail
TEST_DEPS := client data_sink naughty_child
# Tests: need to be compiled and run by test-% target
TESTS ?= env empty hello_world malloc mmap file fs_perms getpid spawn sched pipe time timerfd \
truncate readdir mkdir open stat link symlink chmod chown tls pthread system_info resolv_conf rlimit \
server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group \
truncate readdir mkdir open stat link symlink chmod chown tls pthread system_info rlimit \
server server_epoll unix_socket cout hostfs cpuid rdtsc device sleep exit_group flock \
ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename procfs wait \
spawn_attribute exec statfs random umask pgrp vfork
# Benchmarks: need to be compiled and run by bench-% target

2
test/env/main.c vendored

@ -144,7 +144,7 @@ static int test_env_set_child_env_and_argv() {
if (ret < 0) {
THROW_ERROR("failed to wait4 the child process");
}
if (!WIFEXITED(status)) {
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
THROW_ERROR("test cases in child faild");
}
return 0;

@ -47,33 +47,6 @@ static int __fcntl_setfl(int fd, int open_flags) {
return 0;
}
static int __fcntl_getlk_and_setlk(int fd, int open_flags) {
int ret;
struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, 0 };
// getlk
ret = fcntl(fd, F_GETLK, &fl);
if (ret < 0) {
THROW_ERROR("failed to call getlk");
}
if (fl.l_type != F_UNLCK) {
THROW_ERROR("failed to get correct fl type");
}
// setlk
if ((open_flags & O_WRONLY) || (open_flags & O_RDWR)) {
fl.l_type = F_WRLCK;
} else {
fl.l_type = F_RDLCK;
}
ret = fcntl(fd, F_SETLK, &fl);
if (ret < 0) {
THROW_ERROR("failed to call setlk");
}
return 0;
}
static int __fcntl_dupfd(int fd, int open_flags) {
if (fcntl(fd, F_DUPFD, 0) < 0) {
THROW_ERROR("failed to duplicate the fd");
@ -113,10 +86,6 @@ static int test_fcntl_setfl() {
return test_fcntl_framework(__fcntl_setfl);
}
static int test_getlk_and_setlk() {
return test_fcntl_framework(__fcntl_getlk_and_setlk);
}
static int test_fcntl_dupfd() {
return test_fcntl_framework(__fcntl_dupfd);
}
@ -128,7 +97,6 @@ static int test_fcntl_dupfd() {
static test_case_t test_cases[] = {
TEST_CASE(test_fcntl_getfl),
TEST_CASE(test_fcntl_setfl),
TEST_CASE(test_getlk_and_setlk),
TEST_CASE(test_fcntl_dupfd),
};

5
test/flock/Makefile Normal file

@ -0,0 +1,5 @@
include ../test_common.mk
EXTRA_C_FLAGS :=
EXTRA_LINK_FLAGS :=
BIN_ARGS :=

208
test/flock/main.c Normal file

@ -0,0 +1,208 @@
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <spawn.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include "test.h"
// ============================================================================
// Helper structs & variables & functions
// ============================================================================
const char **g_argv;
int g_argc;
const char *g_file_path = "/root/test_flock_file.txt";
int g_fd;
off_t g_file_len = 128;
// Expected child arguments
const int child_argc = 2;
const char *child_argv[3] = {
"flock",
"child",
NULL
};
static int open_or_create_file() {
int flags = O_RDWR | O_CREAT;
int mode = 00666;
int fd = open(g_file_path, flags, mode);
if (fd < 0) {
THROW_ERROR("failed to open or create file");
}
if (ftruncate(fd, g_file_len) < 0) {
THROW_ERROR("failed to expand the file len");
}
return fd;
}
static int remove_file() {
if (unlink(g_file_path) < 0) {
THROW_ERROR("failed to unlink the created file");
}
return 0;
}
// ============================================================================
// Test cases for file POSIX advisory lock
// ============================================================================
static int test_getlk() {
struct flock fl = { F_RDLCK, SEEK_SET, 0, 0, 0 };
if (fcntl(g_fd, F_GETLK, &fl) < 0) {
THROW_ERROR("failed to call getlk");
}
if (fl.l_type != F_UNLCK) {
THROW_ERROR("failed to get correct fl type");
}
return 0;
}
static int test_setlk() {
struct flock fl = { F_RDLCK, SEEK_SET, 0, g_file_len / 2, 0 };
if (fcntl(g_fd, F_SETLK, &fl) < 0) {
THROW_ERROR("failed to call setlk");
}
fl.l_len = g_file_len;
if (fcntl(g_fd, F_SETLK, &fl) < 0) {
THROW_ERROR("failed to expand the lock");
}
fl.l_type = F_WRLCK;
fl.l_len = g_file_len / 2;
if (fcntl(g_fd, F_SETLK, &fl) < 0) {
THROW_ERROR("failed change the lock type of existing lock");
}
return 0;
}
static int test_spawn_child_and_unlock() {
int status, child_pid;
int ret = posix_spawn(&child_pid,
"/bin/flock", NULL, NULL,
(char *const *)child_argv,
NULL);
if (ret < 0) {
THROW_ERROR("spawn process error");
}
printf("Spawn a child process with pid=%d\n", child_pid);
// Sleep 3s for the child to run setlkw test and wait, is 3s enough?
sleep(3);
// Unlock the flock will cause child process to finish running
struct flock fl = { F_UNLCK, SEEK_SET, 0, 0, 0 };
if (fcntl(g_fd, F_SETLK, &fl) < 0) {
THROW_ERROR("failed to unlock the lock");
}
// Wait for child exit
ret = wait4(child_pid, &status, 0, NULL);
if (ret < 0) {
THROW_ERROR("failed to wait4 the child process");
}
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
THROW_ERROR("test cases in child faild");
}
// The lock will be unlocked on child exit, so we can lock again
struct flock fl2 = { F_WRLCK, SEEK_SET, 0, g_file_len / 4, 0 };
ret = fcntl(g_fd, F_SETLKW, &fl2);
if (ret < 0 && errno != EINTR) {
THROW_ERROR("failed to check the result of setlkw");
}
return 0;
}
// ============================================================================
// Child Test cases
// ============================================================================
static int test_child_getlk() {
struct flock fl = { F_RDLCK, SEEK_SET, 0, g_file_len / 4, 0 };
if (fcntl(g_fd, F_GETLK, &fl) < 0) {
THROW_ERROR("failed to call getlk");
}
if (fl.l_type != F_WRLCK) {
THROW_ERROR("failed to get correct fl type");
}
if (fl.l_pid == 0) {
THROW_ERROR("failed to get correct fl pid");
}
if (fl.l_len != g_file_len / 2) {
THROW_ERROR("failed to get correct fl len");
}
return 0;
}
static int test_child_setlk() {
struct flock fl = { F_RDLCK, SEEK_SET, 0, g_file_len / 4, 0 };
int res = fcntl(g_fd, F_SETLK, &fl);
if (!(res < 0 && errno == EAGAIN)) {
THROW_ERROR("failed to check the result of setlk with conflict lock");
}
return 0;
}
static int test_child_setlkw() {
struct flock fl = { F_RDLCK, SEEK_SET, 0, g_file_len / 4, 0 };
int res = fcntl(g_fd, F_SETLKW, &fl);
if (res < 0 && errno != EINTR) {
THROW_ERROR("failed to check the result of setlkw with conflict lock");
}
return 0;
}
// ============================================================================
// Test suite main
// ============================================================================
static test_case_t test_cases[] = {
TEST_CASE(test_getlk),
TEST_CASE(test_setlk),
TEST_CASE(test_spawn_child_and_unlock),
};
static test_case_t child_test_cases[] = {
TEST_CASE(test_child_getlk),
TEST_CASE(test_child_setlk),
TEST_CASE(test_child_setlkw),
};
int main(int argc, const char *argv[]) {
// Save argument for test cases
g_argc = argc;
g_argv = argv;
g_fd = open_or_create_file();
if (g_fd < 0) {
THROW_ERROR("failed to open/create file");
}
// Test argc
if (argc == 2) {
if (test_suite_run(child_test_cases, ARRAY_SIZE(child_test_cases)) < 0) {
THROW_ERROR("failed run child test");
}
// Donot close file intentionally to unlock the lock on exit
// close(g_fd);
} else {
if (test_suite_run(test_cases, ARRAY_SIZE(test_cases)) < 0) {
THROW_ERROR("failed run test");
}
close(g_fd);
if (remove_file() < 0) {
THROW_ERROR("failed to remove file after test");
}
}
return 0;
}