Add flock and sys_flock

This commit is contained in:
Fabing Li 2024-08-12 11:51:41 +08:00 committed by Tate, Hongliang Tian
parent cbd8879243
commit ce2af1eb05
7 changed files with 289 additions and 1 deletions

View File

@ -93,7 +93,7 @@ provided by Linux on x86-64 architecture.
| 70 | msgrcv | ❌ |
| 71 | msgctl | ❌ |
| 72 | fcntl | ✅ |
| 73 | flock | |
| 73 | flock | |
| 74 | fsync | ✅ |
| 75 | fdatasync | ✅ |
| 76 | truncate | ✅ |

View File

@ -0,0 +1,193 @@
// SPDX-License-Identifier: MPL-2.0
use alloc::fmt;
use core::ptr;
use ostd::sync::WaitQueue;
use crate::{
fs::{file_handle::FileLike, inode_handle::InodeHandle},
prelude::*,
};
/// Represents a file lock (FLOCK) with an owner and type.
#[derive(Debug, Clone)]
struct Flock {
/// Owner of the lock, which is an opened file descriptor.
owner: Weak<dyn FileLike>,
/// Type of the lock, either shared or exclusive.
type_: FlockType,
}
/// Represents a Flock item that can be held in a list of file locks.
/// Each FlockItem contains a lock and a wait queue for threads that are blocked by the lock.
pub struct FlockItem {
lock: Flock,
/// A wait queue for any threads that are blocked by this lock.
waitqueue: Arc<WaitQueue>,
}
impl FlockItem {
/// Creates a new FlockItem with the specified owner and lock type.
pub fn new(owner: &Arc<dyn FileLike>, type_: FlockType) -> Self {
Self {
lock: Flock {
owner: Arc::downgrade(owner),
type_,
},
waitqueue: Arc::new(WaitQueue::new()),
}
}
/// Returns the owner of the lock if it exists.
pub fn owner(&self) -> Option<Arc<dyn FileLike>> {
Weak::upgrade(&self.lock.owner)
}
/// Checks if this lock has the same owner as another lock.
pub fn same_owner_with(&self, other: &Self) -> bool {
self.lock.owner.ptr_eq(&other.lock.owner)
}
/// Returns true if this lock conflicts with another lock.
/// Two locks conflict if they have different owners and at least one of them is an exclusive lock.
pub fn conflict_with(&self, other: &Self) -> bool {
if self.same_owner_with(other) {
return false;
}
if self.lock.type_ == FlockType::ExclusiveLock
|| other.lock.type_ == FlockType::ExclusiveLock
{
return true;
}
false
}
/// Waits until the lock can be acquired.
pub fn wait(&self) {
let cond = || None::<()>;
self.waitqueue.wait_until(cond);
}
/// Wakes all threads that are waiting for this lock.
pub fn wake_all(&self) {
self.waitqueue.wake_all();
}
}
impl Clone for FlockItem {
fn clone(&self) -> Self {
Self {
lock: self.lock.clone(),
waitqueue: self.waitqueue.clone(),
}
}
}
/// When a FlockItem is dropped, it wakes all threads that are waiting for the lock.
impl Drop for FlockItem {
fn drop(&mut self) {
self.waitqueue.wake_all();
}
}
impl Debug for FlockItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Flock")
.field("owner", &self.lock.owner.as_ptr())
.field("type_", &self.lock.type_)
.finish()
}
}
/// Represents a list of non-POSIX file advisory locks (FLOCK).
/// The list is used to manage file locks and resolve conflicts between them.
pub struct FlockList {
inner: RwMutex<VecDeque<FlockItem>>,
}
impl FlockList {
/// Creates a new FlockList.
pub fn new() -> Self {
Self {
inner: RwMutex::new(VecDeque::new()),
}
}
/// Attempts to set a lock on the file.
/// If no conflicting locks exist, the lock is set and the function returns Ok(()).
/// If is_nonblocking is true and a conflicting lock exists, the function returns EAGAIN.
/// Otherwise, the function waits until the lock can be acquired.
pub fn set_lock(&self, mut req_lock: FlockItem, is_nonblocking: bool) -> Result<()> {
debug!(
"set_lock with Flock: {:?}, is_nonblocking: {}",
req_lock, is_nonblocking
);
loop {
let conflict_lock;
{
let mut list = self.inner.write();
if let Some(existing_lock) = list.iter().find(|l| req_lock.conflict_with(l)) {
if is_nonblocking {
return_errno_with_message!(Errno::EAGAIN, "the file is locked");
}
conflict_lock = existing_lock.clone();
} else {
match list.iter().position(|l| req_lock.same_owner_with(l)) {
Some(idx) => {
core::mem::swap(&mut req_lock, &mut list[idx]);
}
None => {
list.push_front(req_lock);
}
}
return Ok(());
}
}
conflict_lock.wait();
}
}
/// Unlocks the specified owner, waking any waiting threads.
/// If the owner is no longer valid, the lock is removed from the list.
/// If the owner is valid, the lock is removed from the list and all threads waiting for the lock are woken.
/// The function does nothing if the owner is not found in the list.
/// The function is called when the file is closed or the lock is released.
pub fn unlock<R>(&self, req_owner: &InodeHandle<R>) {
debug!(
"unlock with owner: {:?}",
req_owner as *const InodeHandle<R>
);
let mut list = self.inner.write();
list.retain(|lock| {
if let Some(owner) = lock.owner() {
if ptr::eq(
Arc::as_ptr(&owner) as *const InodeHandle<R>,
req_owner as *const InodeHandle<R>,
) {
lock.wake_all(); // Wake all threads waiting for this lock.
false // Remove lock from the list.
} else {
true // Keep lock in the list.
}
} else {
false // Remove lock if the owner is no longer valid.
}
});
}
}
impl Default for FlockList {
fn default() -> Self {
Self::new()
}
}
/// Represents the type of a Flock - either shared or exclusive.
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u16)]
pub enum FlockType {
/// Represents a shared lock.
SharedLock = 0,
/// Represents an exclusive lock.
ExclusiveLock = 1,
}

View File

@ -25,6 +25,7 @@ use crate::syscall::{
exit_group::sys_exit_group,
fallocate::sys_fallocate,
fcntl::sys_fcntl,
flock::sys_flock,
fork::sys_fork,
fsync::{sys_fdatasync, sys_fsync},
futex::sys_futex,
@ -190,6 +191,7 @@ impl_syscall_nums_and_dispatch_fn! {
SYS_KILL = 62 => sys_kill(args[..2]);
SYS_UNAME = 63 => sys_uname(args[..1]);
SYS_FCNTL = 72 => sys_fcntl(args[..3]);
SYS_FLOCK = 73 => sys_flock(args[..2]);
SYS_FSYNC = 74 => sys_fsync(args[..1]);
SYS_FDATASYNC = 75 => sys_fdatasync(args[..1]);
SYS_TRUNCATE = 76 => sys_truncate(args[..2]);

View File

@ -0,0 +1,82 @@
// SPDX-License-Identifier: MPL-2.0
use super::SyscallReturn;
use crate::{
fs::{
file_table::FileDesc,
inode_handle::InodeHandle,
utils::{FlockItem, FlockType},
},
prelude::*,
};
pub fn sys_flock(fd: FileDesc, ops: i32, ctx: &Context) -> Result<SyscallReturn> {
debug!("flock: fd: {}, ops: {:?}", fd, ops);
let file = {
let current = ctx.process;
let file_table = current.file_table().lock();
file_table.get_file(fd)?.clone()
};
let inode_file = file
.downcast_ref::<InodeHandle>()
.ok_or(Error::with_message(Errno::EBADF, "not inode"))?;
let ops: FlockOps = FlockOps::from_i32(ops)?;
if ops.contains(FlockOps::LOCK_UN) {
inode_file.unlock_flock();
} else {
let is_nonblocking = ops.contains(FlockOps::LOCK_NB);
let flock = {
let type_ = FlockType::from(ops);
FlockItem::new(&file, type_)
};
inode_file.set_flock(flock, is_nonblocking)?;
}
Ok(SyscallReturn::Return(0))
}
impl From<FlockOps> for FlockType {
fn from(ops: FlockOps) -> Self {
if ops.contains(FlockOps::LOCK_EX) {
Self::ExclusiveLock
} else if ops.contains(FlockOps::LOCK_SH) {
Self::SharedLock
} else {
panic!("invalid flockops");
}
}
}
bitflags! {
struct FlockOps: i32 {
/// Shared lock
const LOCK_SH = 1;
/// Exclusive lock
const LOCK_EX = 2;
// Or'd with one of the above to prevent blocking
const LOCK_NB = 4;
// Remove lock
const LOCK_UN = 8;
}
}
impl FlockOps {
fn from_i32(bits: i32) -> Result<Self> {
if let Some(ops) = Self::from_bits(bits) {
if ops.contains(Self::LOCK_SH) {
if ops.contains(Self::LOCK_EX) || ops.contains(Self::LOCK_UN) {
return_errno_with_message!(Errno::EINVAL, "invalid operation");
}
} else if ops.contains(Self::LOCK_EX) {
if ops.contains(Self::LOCK_UN) {
return_errno_with_message!(Errno::EINVAL, "invalid operation");
}
} else if !ops.contains(Self::LOCK_UN) {
return_errno_with_message!(Errno::EINVAL, "invalid operation");
}
Ok(ops)
} else {
return_errno_with_message!(Errno::EINVAL, "invalid operation");
}
}
}

View File

@ -33,6 +33,7 @@ mod exit;
mod exit_group;
mod fallocate;
mod fcntl;
mod flock;
mod fork;
mod fsync;
mod futex;

View File

@ -16,6 +16,7 @@ TESTS ?= \
epoll_test \
eventfd_test \
fcntl_test \
flock_test \
fsync_test \
getdents_test \
link_test \

View File

@ -0,0 +1,9 @@
FlockTest.TestSharedLockFailExclusiveHolderBlocking_NoRandomSave
FlockTest.TestExclusiveLockFailExclusiveHolderBlocking_NoRandomSave
FlockTest.BlockingLockFirstSharedSecondExclusive_NoRandomSave
FlockTest.BlockingLockFirstExclusiveSecondShared_NoRandomSave
FlockTest.BlockingLockFirstExclusiveSecondExclusive_NoRandomSave
FlockTestNoFixture.FlockSymlink
FlockTestNoFixture.FlockProc
FlockTestNoFixture.FlockPipe
FlockTestNoFixture.FlockSocket