From 3a80b25ed6fa6aff98ba2d72cb28d66b8e013d4f Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Thu, 14 Jul 2022 02:17:01 +0300 Subject: [PATCH] Kernel: Support F_SETLKW in fcntl --- Kernel/FileSystem/Inode.cpp | 66 +++++++++++++++-------- Kernel/FileSystem/Inode.h | 13 ++++- Kernel/FileSystem/OpenFileDescription.cpp | 4 +- Kernel/FileSystem/OpenFileDescription.h | 2 +- Kernel/Syscalls/fcntl.cpp | 5 +- Kernel/Thread.h | 38 ++++++++++++- Kernel/ThreadBlockers.cpp | 34 ++++++++++++ 7 files changed, 133 insertions(+), 29 deletions(-) diff --git a/Kernel/FileSystem/Inode.cpp b/Kernel/FileSystem/Inode.cpp index 3721ccf777..69f495d101 100644 --- a/Kernel/FileSystem/Inode.cpp +++ b/Kernel/FileSystem/Inode.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2021, Andreas Kling * Copyright (c) 2021, sin-ack + * Copyright (c) 2022, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause */ @@ -297,50 +298,71 @@ static inline ErrorOr normalize_flock(OpenFileDescription const& descripti return {}; } -ErrorOr Inode::can_apply_flock(OpenFileDescription const&, flock const& new_lock) const +bool Inode::can_apply_flock(flock const& new_lock) const { VERIFY(new_lock.l_whence == SEEK_SET); if (new_lock.l_type == F_UNLCK) - return {}; + return true; - return m_flocks.with([&](auto& flocks) -> ErrorOr { + return m_flocks.with([&](auto& flocks) { for (auto const& lock : flocks) { if (!range_overlap(lock.start, lock.len, new_lock.l_start, new_lock.l_len)) continue; if (new_lock.l_type == F_RDLCK && lock.type == F_WRLCK) - return EAGAIN; + return false; if (new_lock.l_type == F_WRLCK) - return EAGAIN; + return false; } - return {}; + return true; }); } -ErrorOr Inode::apply_flock(Process const& process, OpenFileDescription const& description, Userspace input_lock) +ErrorOr Inode::try_apply_flock(Process const& process, OpenFileDescription const& description, flock const& lock) +{ + return m_flocks.with([&](auto& flocks) -> ErrorOr { + if (!can_apply_flock(lock)) + return false; + + if (lock.l_type == F_UNLCK) { + bool any_locks_unlocked = false; + for (size_t i = 0; i < flocks.size(); ++i) { + if (&description == flocks[i].owner && flocks[i].start == lock.l_start && flocks[i].len == lock.l_len) { + flocks.remove(i); + any_locks_unlocked |= true; + } + } + + if (any_locks_unlocked) + m_flock_blocker_set.unblock_all_blockers_whose_conditions_are_met(); + + // Judging by the Linux implementation, unlocking a non-existent lock also works. + return true; + } + + TRY(flocks.try_append(Flock { lock.l_start, lock.l_len, &description, process.pid().value(), lock.l_type })); + return true; + }); +} + +ErrorOr Inode::apply_flock(Process const& process, OpenFileDescription const& description, Userspace input_lock, ShouldBlock should_block) { auto new_lock = TRY(copy_typed_from_user(input_lock)); TRY(normalize_flock(description, new_lock)); - return m_flocks.with([&](auto& flocks) -> ErrorOr { - TRY(can_apply_flock(description, new_lock)); - - if (new_lock.l_type == F_UNLCK) { - for (size_t i = 0; i < flocks.size(); ++i) { - if (&description == flocks[i].owner && flocks[i].start == new_lock.l_start && flocks[i].len == new_lock.l_len) { - flocks.remove(i); - } - } - - // Judging by the Linux implementation, unlocking a non-existent lock also works. + while (true) { + auto success = TRY(try_apply_flock(process, description, new_lock)); + if (success) return {}; - } - TRY(flocks.try_append(Flock { new_lock.l_start, new_lock.l_len, &description, process.pid().value(), new_lock.l_type })); - return {}; - }); + if (should_block == ShouldBlock::No) + return EAGAIN; + + if (Thread::current()->block({}, *this, new_lock).was_interrupted()) + return EINTR; + } } ErrorOr Inode::get_flock(OpenFileDescription const& description, Userspace reference_lock) const diff --git a/Kernel/FileSystem/Inode.h b/Kernel/FileSystem/Inode.h index befb3a42f2..479c86167c 100644 --- a/Kernel/FileSystem/Inode.h +++ b/Kernel/FileSystem/Inode.h @@ -22,6 +22,11 @@ namespace Kernel { +enum class ShouldBlock { + No = 0, + Yes = 1 +}; + class Inode : public ListedRefCounted , public Weakable { friend class VirtualFileSystem; @@ -94,10 +99,11 @@ public: ErrorOr> fifo(); - ErrorOr can_apply_flock(OpenFileDescription const&, flock const&) const; - ErrorOr apply_flock(Process const&, OpenFileDescription const&, Userspace); + bool can_apply_flock(flock const&) const; + ErrorOr apply_flock(Process const&, OpenFileDescription const&, Userspace, ShouldBlock); ErrorOr get_flock(OpenFileDescription const&, Userspace) const; void remove_flocks_for_description(OpenFileDescription const&); + Thread::FlockBlockerSet& flock_blocker_set() { return m_flock_blocker_set; }; protected: Inode(FileSystem&, InodeIndex); @@ -112,6 +118,8 @@ protected: mutable Mutex m_inode_lock { "Inode"sv }; private: + ErrorOr try_apply_flock(Process const&, OpenFileDescription const&, flock const&); + FileSystem& m_file_system; InodeIndex m_index { 0 }; WeakPtr m_shared_vmobject; @@ -129,6 +137,7 @@ private: short type; }; + Thread::FlockBlockerSet m_flock_blocker_set; SpinlockProtected> m_flocks; public: diff --git a/Kernel/FileSystem/OpenFileDescription.cpp b/Kernel/FileSystem/OpenFileDescription.cpp index f4381c7dce..4f19e4c502 100644 --- a/Kernel/FileSystem/OpenFileDescription.cpp +++ b/Kernel/FileSystem/OpenFileDescription.cpp @@ -445,12 +445,12 @@ FileBlockerSet& OpenFileDescription::blocker_set() return m_file->blocker_set(); } -ErrorOr OpenFileDescription::apply_flock(Process const& process, Userspace lock) +ErrorOr OpenFileDescription::apply_flock(Process const& process, Userspace lock, ShouldBlock should_block) { if (!m_inode) return EBADF; - return m_inode->apply_flock(process, *this, lock); + return m_inode->apply_flock(process, *this, lock, should_block); } ErrorOr OpenFileDescription::get_flock(Userspace lock) const diff --git a/Kernel/FileSystem/OpenFileDescription.h b/Kernel/FileSystem/OpenFileDescription.h index 3d0c7b2c94..829a3a9e9f 100644 --- a/Kernel/FileSystem/OpenFileDescription.h +++ b/Kernel/FileSystem/OpenFileDescription.h @@ -124,7 +124,7 @@ public: FileBlockerSet& blocker_set(); - ErrorOr apply_flock(Process const&, Userspace); + ErrorOr apply_flock(Process const&, Userspace, ShouldBlock); ErrorOr get_flock(Userspace) const; private: diff --git a/Kernel/Syscalls/fcntl.cpp b/Kernel/Syscalls/fcntl.cpp index 8b9d9b0a95..1fb961116a 100644 --- a/Kernel/Syscalls/fcntl.cpp +++ b/Kernel/Syscalls/fcntl.cpp @@ -45,7 +45,10 @@ ErrorOr Process::sys$fcntl(int fd, int cmd, uintptr_t arg) TRY(description->get_flock(Userspace(arg))); return 0; case F_SETLK: - TRY(description->apply_flock(Process::current(), Userspace(arg))); + TRY(description->apply_flock(Process::current(), Userspace(arg), ShouldBlock::No)); + return 0; + case F_SETLKW: + TRY(description->apply_flock(Process::current(), Userspace(arg), ShouldBlock::Yes)); return 0; default: return EINVAL; diff --git a/Kernel/Thread.h b/Kernel/Thread.h index b507261a62..e337b52c3f 100644 --- a/Kernel/Thread.h +++ b/Kernel/Thread.h @@ -283,7 +283,8 @@ public: Routing, Sleep, Signal, - Wait + Wait, + Flock }; virtual ~Blocker(); virtual StringView state_string() const = 0; @@ -788,6 +789,41 @@ public: bool m_finalized { false }; }; + class FlockBlocker final : public Blocker { + public: + FlockBlocker(NonnullRefPtr, flock const&); + virtual StringView state_string() const override { return "Locking File"sv; } + virtual Type blocker_type() const override { return Type::Flock; } + virtual void will_unblock_immediately_without_blocking(UnblockImmediatelyReason) override; + virtual bool setup_blocker() override; + bool try_unblock(bool from_add_blocker); + + private: + NonnullRefPtr m_inode; + flock const& m_flock; + bool m_did_unblock { false }; + }; + + class FlockBlockerSet final : public BlockerSet { + public: + void unblock_all_blockers_whose_conditions_are_met() + { + BlockerSet::unblock_all_blockers_whose_conditions_are_met([&](auto& b, void*, bool&) { + VERIFY(b.blocker_type() == Blocker::Type::Flock); + auto& blocker = static_cast(b); + return blocker.try_unblock(false); + }); + } + + private: + bool should_add_blocker(Blocker& b, void*) override + { + VERIFY(b.blocker_type() == Blocker::Type::Flock); + auto& blocker = static_cast(b); + return !blocker.try_unblock(true); + } + }; + template ErrorOr try_join(AddBlockerHandler add_blocker) { diff --git a/Kernel/ThreadBlockers.cpp b/Kernel/ThreadBlockers.cpp index f510218576..ca3fd8368e 100644 --- a/Kernel/ThreadBlockers.cpp +++ b/Kernel/ThreadBlockers.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2022, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause */ @@ -820,4 +821,37 @@ bool Thread::WaitBlocker::unblock(Process& process, UnblockFlags flags, u8 signa return true; } +Thread::FlockBlocker::FlockBlocker(NonnullRefPtr inode, flock const& flock) + : m_inode(move(inode)) + , m_flock(flock) +{ +} + +void Thread::FlockBlocker::will_unblock_immediately_without_blocking(UnblockImmediatelyReason reason) +{ + VERIFY(reason == UnblockImmediatelyReason::UnblockConditionAlreadyMet); +} + +bool Thread::FlockBlocker::setup_blocker() +{ + return add_to_blocker_set(m_inode->flock_blocker_set()); +} + +bool Thread::FlockBlocker::try_unblock(bool from_add_blocker) +{ + if (!m_inode->can_apply_flock(m_flock)) + return false; + + { + SpinlockLocker lock(m_lock); + if (m_did_unblock) + return false; + m_did_unblock = true; + } + + if (!from_add_blocker) + unblock_from_blocker(); + return true; +} + }