Ver código fonte

Kernel: Support F_SETLKW in fcntl

Idan Horowitz 3 anos atrás
pai
commit
3a80b25ed6

+ 39 - 17
Kernel/FileSystem/Inode.cpp

@@ -1,6 +1,7 @@
 /*
  * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
+ * Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -297,52 +298,73 @@ static inline ErrorOr<void> normalize_flock(OpenFileDescription const& descripti
     return {};
 }
 
-ErrorOr<void> 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<void> {
+    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<void> Inode::apply_flock(Process const& process, OpenFileDescription const& description, Userspace<flock const*> input_lock)
+ErrorOr<bool> Inode::try_apply_flock(Process const& process, OpenFileDescription const& description, flock const& lock)
 {
-    auto new_lock = TRY(copy_typed_from_user(input_lock));
-    TRY(normalize_flock(description, new_lock));
+    return m_flocks.with([&](auto& flocks) -> ErrorOr<bool> {
+        if (!can_apply_flock(lock))
+            return false;
 
-    return m_flocks.with([&](auto& flocks) -> ErrorOr<void> {
-        TRY(can_apply_flock(description, new_lock));
-
-        if (new_lock.l_type == F_UNLCK) {
+        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 == new_lock.l_start && flocks[i].len == new_lock.l_len) {
+                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 {};
+            return true;
         }
 
-        TRY(flocks.try_append(Flock { new_lock.l_start, new_lock.l_len, &description, process.pid().value(), new_lock.l_type }));
-        return {};
+        TRY(flocks.try_append(Flock { lock.l_start, lock.l_len, &description, process.pid().value(), lock.l_type }));
+        return true;
     });
 }
 
+ErrorOr<void> Inode::apply_flock(Process const& process, OpenFileDescription const& description, Userspace<flock const*> input_lock, ShouldBlock should_block)
+{
+    auto new_lock = TRY(copy_typed_from_user(input_lock));
+    TRY(normalize_flock(description, new_lock));
+
+    while (true) {
+        auto success = TRY(try_apply_flock(process, description, new_lock));
+        if (success)
+            return {};
+
+        if (should_block == ShouldBlock::No)
+            return EAGAIN;
+
+        if (Thread::current()->block<Thread::FlockBlocker>({}, *this, new_lock).was_interrupted())
+            return EINTR;
+    }
+}
+
 ErrorOr<void> Inode::get_flock(OpenFileDescription const& description, Userspace<flock*> reference_lock) const
 {
     flock lookup = {};

+ 11 - 2
Kernel/FileSystem/Inode.h

@@ -22,6 +22,11 @@
 
 namespace Kernel {
 
+enum class ShouldBlock {
+    No = 0,
+    Yes = 1
+};
+
 class Inode : public ListedRefCounted<Inode, LockType::Spinlock>
     , public Weakable<Inode> {
     friend class VirtualFileSystem;
@@ -94,10 +99,11 @@ public:
 
     ErrorOr<NonnullRefPtr<FIFO>> fifo();
 
-    ErrorOr<void> can_apply_flock(OpenFileDescription const&, flock const&) const;
-    ErrorOr<void> apply_flock(Process const&, OpenFileDescription const&, Userspace<flock const*>);
+    bool can_apply_flock(flock const&) const;
+    ErrorOr<void> apply_flock(Process const&, OpenFileDescription const&, Userspace<flock const*>, ShouldBlock);
     ErrorOr<void> get_flock(OpenFileDescription const&, Userspace<flock*>) 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<bool> try_apply_flock(Process const&, OpenFileDescription const&, flock const&);
+
     FileSystem& m_file_system;
     InodeIndex m_index { 0 };
     WeakPtr<Memory::SharedInodeVMObject> m_shared_vmobject;
@@ -129,6 +137,7 @@ private:
         short type;
     };
 
+    Thread::FlockBlockerSet m_flock_blocker_set;
     SpinlockProtected<Vector<Flock>> m_flocks;
 
 public:

+ 2 - 2
Kernel/FileSystem/OpenFileDescription.cpp

@@ -445,12 +445,12 @@ FileBlockerSet& OpenFileDescription::blocker_set()
     return m_file->blocker_set();
 }
 
-ErrorOr<void> OpenFileDescription::apply_flock(Process const& process, Userspace<flock const*> lock)
+ErrorOr<void> OpenFileDescription::apply_flock(Process const& process, Userspace<flock const*> 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<void> OpenFileDescription::get_flock(Userspace<flock*> lock) const

+ 1 - 1
Kernel/FileSystem/OpenFileDescription.h

@@ -124,7 +124,7 @@ public:
 
     FileBlockerSet& blocker_set();
 
-    ErrorOr<void> apply_flock(Process const&, Userspace<flock const*>);
+    ErrorOr<void> apply_flock(Process const&, Userspace<flock const*>, ShouldBlock);
     ErrorOr<void> get_flock(Userspace<flock*>) const;
 
 private:

+ 4 - 1
Kernel/Syscalls/fcntl.cpp

@@ -45,7 +45,10 @@ ErrorOr<FlatPtr> Process::sys$fcntl(int fd, int cmd, uintptr_t arg)
         TRY(description->get_flock(Userspace<flock*>(arg)));
         return 0;
     case F_SETLK:
-        TRY(description->apply_flock(Process::current(), Userspace<flock const*>(arg)));
+        TRY(description->apply_flock(Process::current(), Userspace<flock const*>(arg), ShouldBlock::No));
+        return 0;
+    case F_SETLKW:
+        TRY(description->apply_flock(Process::current(), Userspace<flock const*>(arg), ShouldBlock::Yes));
         return 0;
     default:
         return EINVAL;

+ 37 - 1
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<Inode>, 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<Inode> 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<Thread::FlockBlocker&>(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<Thread::FlockBlocker&>(b);
+            return !blocker.try_unblock(true);
+        }
+    };
+
     template<typename AddBlockerHandler>
     ErrorOr<void> try_join(AddBlockerHandler add_blocker)
     {

+ 34 - 0
Kernel/ThreadBlockers.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2020, the SerenityOS developers.
+ * Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
  *
  * 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> 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;
+}
+
 }