Add file POSIX advisory range lock
This commit is contained in:
parent
d9744bf971
commit
8f4fbba220
6
.github/workflows/demo_test.yml
vendored
6
.github/workflows/demo_test.yml
vendored
@ -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 }};
|
||||
|
8
.github/workflows/hw_mode_test.yml
vendored
8
.github/workflows/hw_mode_test.yml
vendored
@ -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
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 {
|
||||
|
58
src/libos/src/fs/flock/builder.rs
Normal file
58
src/libos/src/fs/flock/builder.rs
Normal file
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
387
src/libos/src/fs/flock/mod.rs
Normal file
387
src/libos/src/fs/flock/mod.rs
Normal file
@ -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"),
|
||||
})
|
||||
}
|
||||
}
|
175
src/libos/src/fs/flock/range.rs
Normal file
175
src/libos/src/fs/flock/range.rs
Normal file
@ -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
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
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
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user