From 3fa28166428c41ca4a608d7c5c7090c69e55353e Mon Sep 17 00:00:00 2001 From: Peter Elliott Date: Sun, 18 Jul 2021 23:29:56 -0600 Subject: [PATCH] Kernel+LibC: Implement fcntl(2) advisory locks Advisory locks don't actually prevent other processes from writing to the file, but they do prevent other processes looking to acquire and advisory lock on the file. This implementation currently only adds non-blocking locks, which are all I need for now. --- Kernel/FileSystem/File.cpp | 2 +- Kernel/FileSystem/FileDescription.cpp | 18 ++++ Kernel/FileSystem/FileDescription.h | 3 + Kernel/FileSystem/Inode.cpp | 124 ++++++++++++++++++++++++++ Kernel/FileSystem/Inode.h | 15 ++++ Kernel/Syscalls/fcntl.cpp | 4 + Kernel/UnixTypes.h | 15 ++++ Userland/Libraries/LibC/fcntl.h | 12 +-- 8 files changed, 186 insertions(+), 7 deletions(-) diff --git a/Kernel/FileSystem/File.cpp b/Kernel/FileSystem/File.cpp index 2ca225a0df..cc59666497 100644 --- a/Kernel/FileSystem/File.cpp +++ b/Kernel/FileSystem/File.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Kernel { @@ -53,5 +54,4 @@ void File::detach(FileDescription&) { m_attach_count--; } - } diff --git a/Kernel/FileSystem/FileDescription.cpp b/Kernel/FileSystem/FileDescription.cpp index 179d9c57d8..6432cb26aa 100644 --- a/Kernel/FileSystem/FileDescription.cpp +++ b/Kernel/FileSystem/FileDescription.cpp @@ -74,6 +74,9 @@ FileDescription::~FileDescription() (void)m_file->close(); if (m_inode) m_inode->detach(*this); + + if (m_inode) + m_inode->remove_flocks_for_description(*this); } KResult FileDescription::attach() @@ -446,4 +449,19 @@ FileBlockCondition& FileDescription::block_condition() return m_file->block_condition(); } +KResult FileDescription::apply_flock(Process const& process, Userspace lock) +{ + if (!m_inode) + return EBADF; + + return m_inode->apply_flock(process, *this, lock); +} + +KResult FileDescription::get_flock(Userspace lock) const +{ + if (!m_inode) + return EBADF; + + return m_inode->get_flock(*this, lock); +} } diff --git a/Kernel/FileSystem/FileDescription.h b/Kernel/FileSystem/FileDescription.h index ae8f9d45b7..c22e8a9d22 100644 --- a/Kernel/FileSystem/FileDescription.h +++ b/Kernel/FileSystem/FileDescription.h @@ -127,6 +127,9 @@ public: FileBlockCondition& block_condition(); + KResult apply_flock(Process const&, Userspace); + KResult get_flock(Userspace) const; + private: friend class VirtualFileSystem; explicit FileDescription(File&); diff --git a/Kernel/FileSystem/Inode.cpp b/Kernel/FileSystem/Inode.cpp index 9663656bd4..d8e31de11c 100644 --- a/Kernel/FileSystem/Inode.cpp +++ b/Kernel/FileSystem/Inode.cpp @@ -11,11 +11,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include namespace Kernel { @@ -272,4 +274,126 @@ RefPtr Inode::shared_vmobject() const return m_shared_vmobject.strong_ref(); } +template +static inline bool range_overlap(T start1, T len1, T start2, T len2) +{ + return ((start1 < start2 + len2) || len2 == 0) && ((start2 < start1 + len1) || len1 == 0); +} + +static inline KResult normalize_flock(FileDescription const& description, flock& lock) +{ + off_t start; + switch (lock.l_whence) { + case SEEK_SET: + start = lock.l_start; + break; + case SEEK_CUR: + start = description.offset() + lock.l_start; + break; + case SEEK_END: + // FIXME: Implement SEEK_END and negative lengths. + return ENOTSUP; + default: + return EINVAL; + } + lock = { lock.l_type, SEEK_SET, start, lock.l_len, 0 }; + return KSuccess; +} + +KResult Inode::can_apply_flock(FileDescription const& description, flock const& new_lock) const +{ + VERIFY(new_lock.l_whence == SEEK_SET); + + MutexLocker locker(m_inode_lock, Mutex::Mode::Shared); + + if (new_lock.l_type == F_UNLCK) { + for (auto& lock : m_flocks) { + if (&description == lock.owner && lock.start == new_lock.l_start && lock.len == new_lock.l_len) + return KSuccess; + } + return EINVAL; + } + + for (auto& lock : m_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; + + if (new_lock.l_type == F_WRLCK) + return EAGAIN; + } + return KSuccess; +} + +KResult Inode::apply_flock(Process const& process, FileDescription const& description, Userspace input_lock) +{ + flock new_lock; + if (!copy_from_user(&new_lock, input_lock)) + return EFAULT; + + auto rc = normalize_flock(description, new_lock); + if (rc.is_error()) + return rc; + + MutexLocker locker(m_inode_lock); + + rc = can_apply_flock(description, new_lock); + if (rc.is_error()) + return rc; + + if (new_lock.l_type == F_UNLCK) { + for (size_t i = 0; i < m_flocks.size(); ++i) { + if (&description == m_flocks[i].owner && m_flocks[i].start == new_lock.l_start && m_flocks[i].len == new_lock.l_len) { + m_flocks.remove(i); + return KSuccess; + } + } + return EINVAL; + } + + m_flocks.append(Flock { new_lock.l_type, new_lock.l_start, new_lock.l_len, &description, process.pid().value() }); + return KSuccess; +} + +KResult Inode::get_flock(FileDescription const& description, Userspace reference_lock) const +{ + flock lookup; + if (!copy_from_user(&lookup, reference_lock)) + return EFAULT; + + auto rc = normalize_flock(description, lookup); + if (rc.is_error()) + return rc; + + MutexLocker locker(m_inode_lock, Mutex::Mode::Shared); + + for (auto& lock : m_flocks) { + if (!range_overlap(lock.start, lock.len, lookup.l_start, lookup.l_len)) + continue; + + if ((lookup.l_type == F_RDLCK && lock.type == F_WRLCK) || lookup.l_type == F_WRLCK) { + lookup = { lock.type, SEEK_SET, lock.start, lock.len, lock.pid }; + if (!copy_to_user(reference_lock, &lookup)) + return EFAULT; + return KSuccess; + } + } + + lookup.l_type = F_UNLCK; + if (!copy_to_user(reference_lock, &lookup)) + return EFAULT; + return KSuccess; +} + +void Inode::remove_flocks_for_description(FileDescription const& description) +{ + MutexLocker locker(m_inode_lock); + + for (size_t i = 0; i < m_flocks.size(); ++i) { + if (&description == m_flocks[i].owner) + m_flocks.remove(i--); + } +} } diff --git a/Kernel/FileSystem/Inode.h b/Kernel/FileSystem/Inode.h index 519e4a9db0..c40e530df4 100644 --- a/Kernel/FileSystem/Inode.h +++ b/Kernel/FileSystem/Inode.h @@ -97,6 +97,11 @@ public: NonnullRefPtr fifo(); + KResult can_apply_flock(FileDescription const&, flock const&) const; + KResult apply_flock(Process const&, FileDescription const&, Userspace); + KResult get_flock(FileDescription const&, Userspace) const; + void remove_flocks_for_description(FileDescription const&); + protected: Inode(FileSystem&, InodeIndex); void set_metadata_dirty(bool); @@ -119,6 +124,16 @@ private: RefPtr m_fifo; IntrusiveListNode m_inode_list_node; + struct Flock { + short type; + off_t start; + off_t len; + FileDescription const* owner; + pid_t pid; + }; + + Vector m_flocks; + public: using List = IntrusiveList, &Inode::m_inode_list_node>; }; diff --git a/Kernel/Syscalls/fcntl.cpp b/Kernel/Syscalls/fcntl.cpp index cff72f2d6b..19d978def0 100644 --- a/Kernel/Syscalls/fcntl.cpp +++ b/Kernel/Syscalls/fcntl.cpp @@ -43,6 +43,10 @@ KResultOr Process::sys$fcntl(int fd, int cmd, u32 arg) break; case F_ISTTY: return description->is_tty(); + case F_GETLK: + return description->get_flock(Userspace(arg)); + case F_SETLK: + return description->apply_flock(*Process::current(), Userspace(arg)); default: return EINVAL; } diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index f9429eb529..4ca23090fe 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -108,6 +108,9 @@ enum { #define F_GETFL 3 #define F_SETFL 4 #define F_ISTTY 5 +#define F_GETLK 6 +#define F_SETLK 7 +#define F_SETLKW 8 #define FD_CLOEXEC 1 @@ -746,3 +749,15 @@ struct statvfs { unsigned long f_flag; unsigned long f_namemax; }; + +#define F_RDLCK ((short)0) +#define F_WRLCK ((short)1) +#define F_UNLCK ((short)2) + +struct flock { + short l_type; + short l_whence; + off_t l_start; + off_t l_len; + pid_t l_pid; +}; diff --git a/Userland/Libraries/LibC/fcntl.h b/Userland/Libraries/LibC/fcntl.h index 7e61ec3324..463e3ba060 100644 --- a/Userland/Libraries/LibC/fcntl.h +++ b/Userland/Libraries/LibC/fcntl.h @@ -18,6 +18,9 @@ __BEGIN_DECLS #define F_GETFL 3 #define F_SETFL 4 #define F_ISTTY 5 +#define F_GETLK 6 +#define F_SETLK 7 +#define F_SETLKW 8 #define FD_CLOEXEC 1 @@ -48,12 +51,9 @@ int create_inode_watcher(unsigned flags); int inode_watcher_add_watch(int fd, const char* path, size_t path_length, unsigned event_mask); int inode_watcher_remove_watch(int fd, int wd); -#define F_RDLCK 0 -#define F_WRLCK 1 -#define F_UNLCK 2 -#define F_GETLK 5 -#define F_SETLK 6 -#define F_SETLKW 7 +#define F_RDLCK ((short)0) +#define F_WRLCK ((short)1) +#define F_UNLCK ((short)2) struct flock { short l_type;