From 0538b9455d47ac0c976c0f3c34be0d0fcb2832b5 Mon Sep 17 00:00:00 2001 From: Tom Englund Date: Wed, 20 Nov 2024 13:50:10 +0100 Subject: [PATCH] filedescriptor: implent a new FileDescriptor class makes you able to RAII the filedescriptor making it somewhat easier to not leak. --- CMakeLists.txt | 8 +++ include/hyprutils/os/FileDescriptor.hpp | 39 +++++++++++ src/os/FileDescriptor.cpp | 89 +++++++++++++++++++++++++ tests/filedescriptor.cpp | 49 ++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 include/hyprutils/os/FileDescriptor.hpp create mode 100644 src/os/FileDescriptor.cpp create mode 100644 tests/filedescriptor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ef8c4c0..b72ec2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,14 @@ add_test( COMMAND hyprutils_os "os") add_dependencies(tests hyprutils_os) +add_executable(hyprutils_filedescriptor "tests/filedescriptor.cpp") +target_link_libraries(hyprutils_filedescriptor PRIVATE hyprutils PkgConfig::deps) +add_test( + NAME "Filedescriptor" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests + COMMAND hyprutils_filedescriptor "filedescriptor") +add_dependencies(tests hyprutils_filedescriptor) + # Installation install(TARGETS hyprutils) install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/include/hyprutils/os/FileDescriptor.hpp b/include/hyprutils/os/FileDescriptor.hpp new file mode 100644 index 0000000..96761d0 --- /dev/null +++ b/include/hyprutils/os/FileDescriptor.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +namespace Hyprutils { + namespace OS { + class CFileDescriptor { + public: + CFileDescriptor() = default; + explicit CFileDescriptor(int const fd); + CFileDescriptor(CFileDescriptor&&); + CFileDescriptor& operator=(CFileDescriptor&&); + ~CFileDescriptor(); + + CFileDescriptor(const CFileDescriptor&) = delete; + CFileDescriptor& operator=(const CFileDescriptor&) = delete; + + bool operator==(const CFileDescriptor& rhs) const { + return m_fd == rhs.m_fd; + } + + bool isValid() const; + int get() const; + int getFlags() const; + bool setFlags(int flags); + int take(); + void reset(); + CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const; + + bool isReadable() const; + bool isClosed() const; + + static bool isReadable(int fd); + static bool isClosed(int fd); + + private: + int m_fd = -1; + }; + }; +}; diff --git a/src/os/FileDescriptor.cpp b/src/os/FileDescriptor.cpp new file mode 100644 index 0000000..8592f42 --- /dev/null +++ b/src/os/FileDescriptor.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include + +using namespace Hyprutils::OS; + +CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {} + +CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {} + +CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) { + if (this == &other) // Shit will go haywire if there is duplicate ownership + abort(); + + reset(); + m_fd = std::exchange(other.m_fd, -1); + return *this; +} + +CFileDescriptor::~CFileDescriptor() { + reset(); +} + +bool CFileDescriptor::isValid() const { + return m_fd != -1; +} + +int CFileDescriptor::get() const { + return m_fd; +} + +int CFileDescriptor::getFlags() const { + return fcntl(m_fd, F_GETFD); +} + +bool CFileDescriptor::setFlags(int flags) { + if (fcntl(m_fd, F_SETFD, flags) == -1) + return false; + + return true; +} + +int CFileDescriptor::take() { + return std::exchange(m_fd, -1); +} + +void CFileDescriptor::reset() { + if (m_fd != -1) { + close(m_fd); + m_fd = -1; + } +} + +CFileDescriptor CFileDescriptor::duplicate(int flags) const { + if (m_fd == -1) + return {}; + + return CFileDescriptor{fcntl(m_fd, flags, 0)}; +} + +bool CFileDescriptor::isClosed() const { + return isClosed(m_fd); +} + +bool CFileDescriptor::isReadable() const { + return isReadable(m_fd); +} + +bool CFileDescriptor::isClosed(int fd) { + pollfd pfd = { + .fd = fd, + .events = POLLIN, + .revents = 0, + }; + + if (poll(&pfd, 1, 0) < 0) + return true; + + return pfd.revents & (POLLHUP | POLLERR); +} + +bool CFileDescriptor::isReadable(int fd) { + pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0}; + + return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN); +} diff --git a/tests/filedescriptor.cpp b/tests/filedescriptor.cpp new file mode 100644 index 0000000..c2a2c79 --- /dev/null +++ b/tests/filedescriptor.cpp @@ -0,0 +1,49 @@ +#include +#include "shared.hpp" +#include +#include +#include + +using namespace Hyprutils::OS; + +int main(int argc, char** argv, char** envp) { + std::string name = "/test_filedescriptors"; + CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)); + + int ret = 0; + EXPECT(fd.isValid(), true); + EXPECT(fd.isReadable(), true); + + int flags = fd.getFlags(); + EXPECT(fd.getFlags(), FD_CLOEXEC); + flags &= ~FD_CLOEXEC; + fd.setFlags(flags); + EXPECT(fd.getFlags(), !FD_CLOEXEC); + + CFileDescriptor fd2 = fd.duplicate(); + EXPECT(fd.isValid(), true); + EXPECT(fd.isReadable(), true); + EXPECT(fd2.isValid(), true); + EXPECT(fd2.isReadable(), true); + + CFileDescriptor fd3(fd2.take()); + EXPECT(fd.isValid(), true); + EXPECT(fd.isReadable(), true); + EXPECT(fd2.isValid(), false); + EXPECT(fd2.isReadable(), false); + + // .duplicate default flags is FD_CLOEXEC + EXPECT(fd3.getFlags(), FD_CLOEXEC); + + fd.reset(); + fd2.reset(); + fd3.reset(); + + EXPECT(fd.isReadable(), false); + EXPECT(fd2.isReadable(), false); + EXPECT(fd3.isReadable(), false); + + shm_unlink(name.c_str()); + + return ret; +}