407 lines
13 KiB
Rust
407 lines
13 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
use core::fmt;
|
|
|
|
use ostd::sync::{RwMutexWriteGuard, WaitQueue};
|
|
|
|
use self::range::FileRangeChange;
|
|
pub use self::{
|
|
builder::RangeLockItemBuilder,
|
|
range::{FileRange, OverlapWith, OFFSET_MAX},
|
|
};
|
|
use crate::{prelude::*, process::Pid};
|
|
|
|
mod builder;
|
|
mod range;
|
|
|
|
/// The metadata of a POSIX advisory file range lock.
|
|
#[derive(Debug, Clone)]
|
|
struct RangeLock {
|
|
/// Owner of the lock, representing the process holding the lock
|
|
owner: Pid,
|
|
/// Type of lock: can be F_RDLCK (read lock), F_WRLCK (write lock), or F_UNLCK (unlock)
|
|
type_: RangeLockType,
|
|
/// Range of the lock which specifies the portion of the file being locked
|
|
range: FileRange,
|
|
}
|
|
|
|
/// Represents a POSIX advisory file range lock in the kernel.
|
|
/// Contains metadata about the lock and the processes waiting for it.
|
|
/// The lock is associated with a specific range of the file.
|
|
pub struct RangeLockItem {
|
|
/// The lock data including its properties
|
|
lock: RangeLock,
|
|
/// Waiters that are being blocked by this lock
|
|
waitqueue: Arc<WaitQueue>,
|
|
}
|
|
|
|
impl RangeLockItem {
|
|
/// Returns the type of the lock (READ/WRITE/UNLOCK)
|
|
pub fn type_(&self) -> RangeLockType {
|
|
self.lock.type_
|
|
}
|
|
|
|
/// Sets the type of the lock to the specified type
|
|
pub fn set_type(&mut self, type_: RangeLockType) {
|
|
self.lock.type_ = type_;
|
|
}
|
|
|
|
/// Returns the owner (process ID) of the lock
|
|
pub fn owner(&self) -> Pid {
|
|
self.lock.owner
|
|
}
|
|
|
|
/// Sets the owner of the lock to the specified process ID
|
|
pub fn set_owner(&mut self, owner: Pid) {
|
|
self.lock.owner = owner;
|
|
}
|
|
|
|
/// Returns the range of the lock
|
|
pub fn range(&self) -> FileRange {
|
|
self.lock.range
|
|
}
|
|
|
|
/// Sets the range of the lock to the specified range
|
|
pub fn set_range(&mut self, range: FileRange) {
|
|
self.lock.range = range;
|
|
}
|
|
|
|
/// Checks if this lock conflicts with another lock
|
|
/// Returns true if there is a conflict, otherwise false
|
|
pub fn conflict_with(&self, other: &Self) -> bool {
|
|
// If locks are owned by the same process, they do not conflict
|
|
if self.owner() == other.owner() {
|
|
return false;
|
|
}
|
|
// If the ranges do not overlap, they do not conflict
|
|
if self.overlap_with(other).is_none() {
|
|
return false;
|
|
}
|
|
// Write locks are exclusive and conflict with any other lock
|
|
if self.type_() == RangeLockType::WriteLock || other.type_() == RangeLockType::WriteLock {
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Checks if this lock overlaps with another lock
|
|
/// Returns an Option that contains the overlap details if they overlap
|
|
pub fn overlap_with(&self, other: &Self) -> Option<OverlapWith> {
|
|
self.range().overlap_with(&other.range())
|
|
}
|
|
|
|
/// Merges the range of this lock with another lock's range
|
|
/// If the merge fails, it will trigger a panic
|
|
pub fn merge_with(&mut self, other: &Self) {
|
|
self.lock
|
|
.range
|
|
.merge(&other.range())
|
|
.expect("merge range failed");
|
|
}
|
|
|
|
/// Returns the starting position of the lock range
|
|
pub fn start(&self) -> usize {
|
|
self.range().start()
|
|
}
|
|
|
|
/// Returns the ending position of the lock range
|
|
pub fn end(&self) -> usize {
|
|
self.range().end()
|
|
}
|
|
|
|
/// Sets a new starting position for the lock range
|
|
/// If the range shrinks, it will wake all waiting processes
|
|
pub fn set_start(&mut self, new_start: usize) {
|
|
let change = self
|
|
.lock
|
|
.range
|
|
.set_start(new_start)
|
|
.expect("invalid new start");
|
|
if let FileRangeChange::Shrunk = change {
|
|
self.wake_all();
|
|
}
|
|
}
|
|
|
|
/// Sets a new ending position for the lock range
|
|
/// If the range shrinks, it will wake all waiting processes
|
|
pub fn set_end(&mut self, new_end: usize) {
|
|
let change = self.range().set_end(new_end).expect("invalid new end");
|
|
if let FileRangeChange::Shrunk = change {
|
|
self.wake_all();
|
|
}
|
|
}
|
|
|
|
/// Puts the current process in a wait state until the lock condition is satisfied
|
|
pub fn wait(&mut self) {
|
|
let cond = || None::<()>;
|
|
self.waitqueue.wait_until(cond);
|
|
}
|
|
|
|
/// Wakes all the processes waiting on this lock
|
|
/// Returns the number of processes that were woken
|
|
pub fn wake_all(&self) -> usize {
|
|
self.waitqueue.wake_all()
|
|
}
|
|
}
|
|
|
|
/// Implements the drop trait for RangeLockItem
|
|
/// Ensures that all waiting processes are woken when this item goes out of scope
|
|
impl Drop for RangeLockItem {
|
|
fn drop(&mut self) {
|
|
self.wake_all();
|
|
}
|
|
}
|
|
|
|
/// Implements the Debug trait for RangeLockItem
|
|
/// Customizes the output when the item is printed in debug mode
|
|
impl Debug for RangeLockItem {
|
|
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()
|
|
}
|
|
}
|
|
|
|
/// Implements the Clone trait for RangeLockItem
|
|
/// Allows creating a copy of the item with the same properties
|
|
impl Clone for RangeLockItem {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
lock: self.lock.clone(),
|
|
waitqueue: self.waitqueue.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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: RwMutex<VecDeque<RangeLockItem>>,
|
|
}
|
|
|
|
impl RangeLockList {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
inner: RwMutex::new(VecDeque::new()),
|
|
}
|
|
}
|
|
|
|
/// Test whether `lock` may be set.
|
|
///
|
|
/// If there is a conflict, return the conflicting lock.
|
|
/// Otherwise, return a lock with type `Unlock`.
|
|
pub fn test_lock(&self, lock: RangeLockItem) -> RangeLockItem {
|
|
debug!("test_lock with RangeLock: {:?}", lock);
|
|
let mut req_lock = lock.clone();
|
|
let list = self.inner.read();
|
|
for existing_lock in list.iter() {
|
|
if lock.conflict_with(existing_lock) {
|
|
req_lock.set_owner(existing_lock.owner());
|
|
req_lock.set_type(existing_lock.type_());
|
|
req_lock.set_range(existing_lock.range());
|
|
return req_lock;
|
|
}
|
|
}
|
|
req_lock.set_type(RangeLockType::Unlock);
|
|
req_lock
|
|
}
|
|
|
|
/// Set a lock on the file.
|
|
///
|
|
/// If the lock is non-blocking and there is a conflict, return `Err(Errno::EAGAIN)`.
|
|
/// Otherwise, block the current process until the lock can be set.
|
|
pub fn set_lock(&self, req_lock: &RangeLockItem, is_nonblocking: bool) -> Result<()> {
|
|
debug!(
|
|
"set_lock with RangeLock: {:?}, is_nonblocking: {}",
|
|
req_lock, is_nonblocking
|
|
);
|
|
loop {
|
|
let mut 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 {
|
|
Self::insert_lock_into_list(&mut list, req_lock);
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
conflict_lock.wait();
|
|
}
|
|
}
|
|
|
|
/// Insert a lock into the list.
|
|
fn insert_lock_into_list(
|
|
list: &mut RwMutexWriteGuard<VecDeque<RangeLockItem>>,
|
|
lock: &RangeLockItem,
|
|
) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Unlock the lock.
|
|
///
|
|
/// The lock will be removed from the list.
|
|
/// Adjacent locks will be merged if they have the same owner and type.
|
|
/// Overlapping locks will be split or merged if they have the same owner.
|
|
pub fn unlock(&self, lock: &RangeLockItem) {
|
|
debug!("unlock with RangeLock: {:?}", lock);
|
|
let mut list = self.inner.write();
|
|
let mut skipped = 0;
|
|
while let Some(idx) = list
|
|
.iter()
|
|
.skip(skipped)
|
|
.position(|lk| lk.owner() == lock.owner())
|
|
{
|
|
// (idx + skipped) is the original position in list
|
|
let idx = idx + skipped;
|
|
let existing_lock = &mut list[idx];
|
|
|
|
let overlap_with = match lock.overlap_with(existing_lock) {
|
|
Some(overlap) => overlap,
|
|
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()
|
|
}
|
|
}
|
|
|
|
/// Type of file range lock, aligned with Linux kernel.
|
|
/// F_RDLCK = 0, F_WRLCK = 1, F_UNLCK = 2,
|
|
#[derive(Debug, Copy, Clone, PartialEq, TryFromInt)]
|
|
#[repr(u16)]
|
|
pub enum RangeLockType {
|
|
ReadLock = 0,
|
|
WriteLock = 1,
|
|
Unlock = 2,
|
|
}
|