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;
|
run: docker exec language_support_test bash -c "cd /root/occlum/demos/embedded_mode && SGX_MODE=SIM make;
|
||||||
SGX_MODE=SIM make test"
|
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
|
- 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;
|
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;
|
occlum-go build -o web_server ./web_server.go;
|
||||||
@ -72,9 +75,6 @@ jobs:
|
|||||||
sleep ${{ env.nap_time }};
|
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" &
|
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
|
- name: Curl test
|
||||||
run: |
|
run: |
|
||||||
sleep ${{ env.nap_time }};
|
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;
|
run: docker exec $language_support_test bash -c "cd /root/occlum/demos/embedded_mode && make;
|
||||||
make test"
|
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
|
- name: Go server set up and run
|
||||||
run: docker exec $language_support_test bash -c "export GO111MODULE=on && export GOPROXY=https://goproxy.cn;
|
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;
|
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 }};
|
sleep ${{ env.nap_time }};
|
||||||
docker exec $language_support_test bash -c "cd /root/occlum/demos/golang/grpc_pingpong && ./run_ping_on_occlum.sh"
|
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.
|
# Sleeps longer to make sure the server is up.
|
||||||
- name: Curl test
|
- name: Curl test
|
||||||
run: |
|
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")
|
return_op_unsupported_error!("seek")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn position(&self) -> Result<off_t> {
|
||||||
|
return_op_unsupported_error!("position")
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Result<Metadata> {
|
fn metadata(&self) -> Result<Metadata> {
|
||||||
return_op_unsupported_error!("metadata")
|
return_op_unsupported_error!("metadata")
|
||||||
}
|
}
|
||||||
@ -83,14 +87,16 @@ pub trait File: Debug + Sync + Send + Any {
|
|||||||
return_op_unsupported_error!("set_status_flags")
|
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")
|
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")
|
return_op_unsupported_error!("set_advisory_lock")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn release_advisory_locks(&self) {}
|
||||||
|
|
||||||
fn fallocate(&self, _flags: FallocateFlags, _offset: usize, _len: usize) -> Result<()> {
|
fn fallocate(&self, _flags: FallocateFlags, _offset: usize, _len: usize) -> Result<()> {
|
||||||
return_op_unsupported_error!("fallocate")
|
return_op_unsupported_error!("fallocate")
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,6 @@ use super::*;
|
|||||||
pub fn do_close(fd: FileDesc) -> Result<()> {
|
pub fn do_close(fd: FileDesc) -> Result<()> {
|
||||||
debug!("close: fd: {}", fd);
|
debug!("close: fd: {}", fd);
|
||||||
let current = current!();
|
let current = current!();
|
||||||
let mut files = current.files().lock().unwrap();
|
current.close_file(fd)?;
|
||||||
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);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::flock::flock;
|
use super::flock::c_flock;
|
||||||
use super::*;
|
use super::*;
|
||||||
use util::mem_util::from_user;
|
use util::mem_util::from_user;
|
||||||
|
|
||||||
@ -19,9 +19,11 @@ pub enum FcntlCmd<'a> {
|
|||||||
/// Set the file status flags
|
/// Set the file status flags
|
||||||
SetFl(u32),
|
SetFl(u32),
|
||||||
/// Test a file lock
|
/// Test a file lock
|
||||||
GetLk(&'a mut flock),
|
GetLk(&'a mut c_flock),
|
||||||
/// Acquire or release a file lock
|
/// 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> {
|
impl<'a> FcntlCmd<'a> {
|
||||||
@ -35,16 +37,22 @@ impl<'a> FcntlCmd<'a> {
|
|||||||
libc::F_GETFL => FcntlCmd::GetFl(),
|
libc::F_GETFL => FcntlCmd::GetFl(),
|
||||||
libc::F_SETFL => FcntlCmd::SetFl(arg as u32),
|
libc::F_SETFL => FcntlCmd::SetFl(arg as u32),
|
||||||
libc::F_GETLK => {
|
libc::F_GETLK => {
|
||||||
let flock_mut_ptr = arg as *mut flock;
|
let lock_mut_ptr = arg as *mut c_flock;
|
||||||
from_user::check_mut_ptr(flock_mut_ptr)?;
|
from_user::check_mut_ptr(lock_mut_ptr)?;
|
||||||
let flock_mut_c = unsafe { &mut *flock_mut_ptr };
|
let lock_mut_c = unsafe { &mut *lock_mut_ptr };
|
||||||
FcntlCmd::GetLk(flock_mut_c)
|
FcntlCmd::GetLk(lock_mut_c)
|
||||||
}
|
}
|
||||||
libc::F_SETLK => {
|
libc::F_SETLK => {
|
||||||
let flock_ptr = arg as *const flock;
|
let lock_ptr = arg as *const c_flock;
|
||||||
from_user::check_ptr(flock_ptr)?;
|
from_user::check_ptr(lock_ptr)?;
|
||||||
let flock_c = unsafe { &*flock_ptr };
|
let lock_c = unsafe { &*lock_ptr };
|
||||||
FcntlCmd::SetLk(flock_c)
|
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"),
|
_ => 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)?;
|
file.set_status_flags(status_flags)?;
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
FcntlCmd::GetLk(flock_mut_c) => {
|
FcntlCmd::GetLk(lock_mut_c) => {
|
||||||
let file = file_table.get(fd)?;
|
let file = file_table.get(fd)?;
|
||||||
let mut lock = Flock::from_c(*flock_mut_c)?;
|
let lock_type = RangeLockType::from_u16(lock_mut_c.l_type)?;
|
||||||
if let FlockType::F_UNLCK = lock.l_type {
|
if RangeLockType::F_UNLCK == lock_type {
|
||||||
return_errno!(EINVAL, "invalid flock type for getlk");
|
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)?;
|
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
|
0
|
||||||
}
|
}
|
||||||
FcntlCmd::SetLk(flock_c) => {
|
FcntlCmd::SetLk(lock_c) => {
|
||||||
let file = file_table.get(fd)?;
|
let file = file_table.get(fd)?;
|
||||||
let lock = Flock::from_c(*flock_c)?;
|
let lock = RangeLockBuilder::new()
|
||||||
file.set_advisory_lock(&lock)?;
|
.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
|
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::fallocate::{do_fallocate, FallocateFlags};
|
||||||
pub use self::fcntl::{do_fcntl, FcntlCmd};
|
pub use self::fcntl::{do_fcntl, FcntlCmd};
|
||||||
pub use self::file_flags::{AccessMode, CreationFlags, StatusFlags};
|
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::fspath::{get_abs_path_by_fd, FsPath, AT_FDCWD};
|
||||||
pub use self::fsync::{do_fdatasync, do_fsync};
|
pub use self::fsync::{do_fdatasync, do_fsync};
|
||||||
pub use self::getdents::{do_getdents, do_getdents64};
|
pub use self::getdents::{do_getdents, do_getdents64};
|
||||||
@ -39,7 +38,6 @@ mod dup;
|
|||||||
mod fallocate;
|
mod fallocate;
|
||||||
mod fcntl;
|
mod fcntl;
|
||||||
mod file_flags;
|
mod file_flags;
|
||||||
mod flock;
|
|
||||||
mod fspath;
|
mod fspath;
|
||||||
mod fsync;
|
mod fsync;
|
||||||
mod getdents;
|
mod getdents;
|
||||||
|
@ -151,9 +151,31 @@ impl FileTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove file descriptors that are close-on-spawn
|
/// Remove all the file descriptors
|
||||||
pub fn close_on_spawn(&mut self) {
|
pub fn del_all(&mut self) -> Vec<FileRef> {
|
||||||
let mut deleted_fds = Vec::new();
|
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() {
|
for (fd, entry) in self.table.iter_mut().enumerate() {
|
||||||
let need_close = if let Some(entry) = entry {
|
let need_close = if let Some(entry) = entry {
|
||||||
entry.close_on_spawn
|
entry.close_on_spawn
|
||||||
@ -161,6 +183,7 @@ impl FileTable {
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
if need_close {
|
if need_close {
|
||||||
|
deleted_files.push(entry.as_ref().unwrap().file.clone());
|
||||||
*entry = None;
|
*entry = None;
|
||||||
deleted_fds.push(fd as FileDesc);
|
deleted_fds.push(fd as FileDesc);
|
||||||
self.num_fds -= 1;
|
self.num_fds -= 1;
|
||||||
@ -170,6 +193,7 @@ impl FileTable {
|
|||||||
for fd in deleted_fds {
|
for fd in deleted_fds {
|
||||||
self.broadcast_del(fd);
|
self.broadcast_del(fd);
|
||||||
}
|
}
|
||||||
|
deleted_files
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notifier(&self) -> &FileTableNotifier {
|
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)
|
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> {
|
fn metadata(&self) -> Result<Metadata> {
|
||||||
let metadata = self.inode.metadata()?;
|
let metadata = self.inode.metadata()?;
|
||||||
Ok(metadata)
|
Ok(metadata)
|
||||||
@ -182,30 +187,60 @@ impl File for INodeFile {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_advisory_lock(&self, lock: &mut Flock) -> Result<()> {
|
fn test_advisory_lock(&self, lock: &mut RangeLock) -> Result<()> {
|
||||||
// Let the advisory lock could be placed
|
let ext = match self.inode.ext() {
|
||||||
// TODO: Implement the real advisory lock
|
Some(ext) => ext,
|
||||||
lock.l_type = FlockType::F_UNLCK;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_advisory_lock(&self, lock: &Flock) -> Result<()> {
|
fn set_advisory_lock(&self, lock: &RangeLock, is_nonblocking: bool) -> Result<()> {
|
||||||
match lock.l_type {
|
if RangeLockType::F_UNLCK == lock.type_() {
|
||||||
FlockType::F_RDLCK => {
|
return Ok(self.unlock_range_lock(lock));
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
// Let the advisory lock could be acquired or released
|
|
||||||
// TODO: Implement the real advisory lock
|
self.check_advisory_lock_with_access_mode(lock)?;
|
||||||
Ok(())
|
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> {
|
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
|
||||||
@ -269,6 +304,40 @@ impl INodeFile {
|
|||||||
pub fn abs_path(&self) -> &str {
|
pub fn abs_path(&self) -> &str {
|
||||||
&self.abs_path
|
&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 {
|
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::{File, FileRef};
|
||||||
pub use self::file_ops::{
|
pub use self::file_ops::{
|
||||||
occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FallocateFlags, FileMode,
|
occlum_ocall_ioctl, AccessMode, BuiltinIoctlNum, CreationFlags, FallocateFlags, FileMode,
|
||||||
Flock, FlockType, IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType,
|
IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType, StructuredIoctlNum,
|
||||||
StructuredIoctlNum,
|
|
||||||
};
|
};
|
||||||
pub use self::file_table::{FileDesc, FileTable, FileTableEvent, FileTableNotifier};
|
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_ops::Statfs;
|
||||||
pub use self::fs_view::FsView;
|
pub use self::fs_view::FsView;
|
||||||
pub use self::host_fd::HostFd;
|
pub use self::host_fd::HostFd;
|
||||||
@ -41,6 +43,7 @@ mod events;
|
|||||||
mod file;
|
mod file;
|
||||||
mod file_ops;
|
mod file_ops;
|
||||||
mod file_table;
|
mod file_table;
|
||||||
|
mod flock;
|
||||||
mod fs_ops;
|
mod fs_ops;
|
||||||
mod fs_view;
|
mod fs_view;
|
||||||
mod host_fd;
|
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");
|
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 {
|
if num_remaining_threads == 0 {
|
||||||
|
thread.close_all_files();
|
||||||
exit_process(&thread, term_status);
|
exit_process(&thread, term_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ fn new_process_common(
|
|||||||
};
|
};
|
||||||
let vm_ref = Arc::new(vm);
|
let vm_ref = Arc::new(vm);
|
||||||
let files_ref = {
|
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))
|
Arc::new(SgxMutex::new(files))
|
||||||
};
|
};
|
||||||
let fs_ref = Arc::new(RwLock::new(current_ref.fs().read().unwrap().clone()));
|
let fs_ref = Arc::new(RwLock::new(current_ref.fs().read().unwrap().clone()));
|
||||||
@ -347,12 +347,25 @@ fn init_files(
|
|||||||
current_ref: &ThreadRef,
|
current_ref: &ThreadRef,
|
||||||
file_actions: &[FileAction],
|
file_actions: &[FileAction],
|
||||||
host_stdio_fds: Option<&HostStdioFds>,
|
host_stdio_fds: Option<&HostStdioFds>,
|
||||||
|
reuse_tid: &Option<ThreadId>,
|
||||||
) -> Result<FileTable> {
|
) -> Result<FileTable> {
|
||||||
// Usually, we just inherit the file table from the current process
|
// Usually, we just inherit the file table from the current process
|
||||||
let should_inherit_file_table = current_ref.process().pid() > 0;
|
let should_inherit_file_table = current_ref.process().pid() > 0;
|
||||||
if should_inherit_file_table {
|
if should_inherit_file_table {
|
||||||
// Fork: clone file table
|
// Fork: clone file table
|
||||||
let mut cloned_file_table = current_ref.files().lock().unwrap().clone();
|
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
|
// Perform file actions to modify the cloned file table
|
||||||
for file_action in file_actions {
|
for file_action in file_actions {
|
||||||
match file_action {
|
match file_action {
|
||||||
|
@ -124,6 +124,27 @@ impl Thread {
|
|||||||
self.files().lock().unwrap().put(new_file, close_on_spawn)
|
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 {
|
pub fn fs(&self) -> &FsViewRef {
|
||||||
&self.fs
|
&self.fs
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ FAIL_LOG = $(BUILD_DIR)/test/.fail
|
|||||||
TEST_DEPS := client data_sink naughty_child
|
TEST_DEPS := client data_sink naughty_child
|
||||||
# Tests: need to be compiled and run by test-% target
|
# 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 \
|
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 \
|
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 \
|
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 \
|
ioctl fcntl eventfd emulate_syscall access signal sysinfo prctl rename procfs wait \
|
||||||
spawn_attribute exec statfs random umask pgrp vfork
|
spawn_attribute exec statfs random umask pgrp vfork
|
||||||
# Benchmarks: need to be compiled and run by bench-% target
|
# 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) {
|
if (ret < 0) {
|
||||||
THROW_ERROR("failed to wait4 the child process");
|
THROW_ERROR("failed to wait4 the child process");
|
||||||
}
|
}
|
||||||
if (!WIFEXITED(status)) {
|
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
|
||||||
THROW_ERROR("test cases in child faild");
|
THROW_ERROR("test cases in child faild");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -47,33 +47,6 @@ static int __fcntl_setfl(int fd, int open_flags) {
|
|||||||
return 0;
|
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) {
|
static int __fcntl_dupfd(int fd, int open_flags) {
|
||||||
if (fcntl(fd, F_DUPFD, 0) < 0) {
|
if (fcntl(fd, F_DUPFD, 0) < 0) {
|
||||||
THROW_ERROR("failed to duplicate the fd");
|
THROW_ERROR("failed to duplicate the fd");
|
||||||
@ -113,10 +86,6 @@ static int test_fcntl_setfl() {
|
|||||||
return test_fcntl_framework(__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() {
|
static int test_fcntl_dupfd() {
|
||||||
return test_fcntl_framework(__fcntl_dupfd);
|
return test_fcntl_framework(__fcntl_dupfd);
|
||||||
}
|
}
|
||||||
@ -128,7 +97,6 @@ static int test_fcntl_dupfd() {
|
|||||||
static test_case_t test_cases[] = {
|
static test_case_t test_cases[] = {
|
||||||
TEST_CASE(test_fcntl_getfl),
|
TEST_CASE(test_fcntl_getfl),
|
||||||
TEST_CASE(test_fcntl_setfl),
|
TEST_CASE(test_fcntl_setfl),
|
||||||
TEST_CASE(test_getlk_and_setlk),
|
|
||||||
TEST_CASE(test_fcntl_dupfd),
|
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