/* * LegacyClonk * * Copyright (c) 2020-2021, The LegacyClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ #include "C4NtSync.h" #include #include #include #include #include class C4NtSync { public: C4NtSync() { fd = open("/dev/ntsync", O_CLOEXEC); if (fd == -1) { throw std::runtime_error{"ntsync not available"}; } } ~C4NtSync() { close(fd); } C4NtSync(const C4NtSync &) = delete; C4NtSync &operator=(const C4NtSync &) = delete; C4NtSync(C4NtSync &&) = delete; C4NtSync &operator=(C4NtSync &&) = delete; public: template int IoControl(Args &&...args) { return ioctl(fd, std::forward(args)...); } int get() const noexcept { return fd; } private: int fd; }; static C4NtSync NtSync; static HANDLE FdToHandle(const int fd) { return reinterpret_cast(static_cast(static_cast(fd))); } static int HandleToFd(const HANDLE handle) { return static_cast(static_cast(reinterpret_cast(handle))); } static int ThrowIfFailed(const int result, const char *const message) { if (result == -1) { throw std::runtime_error{std::format("{} failed: {}", message, std::strerror(errno))}; } return result; } HANDLE CreateEvent(std::nullptr_t, bool manualReset, bool initialState, std::nullptr_t) { const ntsync_event_args args{ .manual = manualReset, .signaled = initialState }; return FdToHandle(ThrowIfFailed(NtSync.IoControl(NTSYNC_IOC_CREATE_EVENT, &args), "CreateEvent")); } bool SetEvent(const HANDLE handle) { const int fd{HandleToFd(handle)}; __u32 previous; return ioctl(fd, NTSYNC_IOC_EVENT_SET, &previous) == 0; } bool ResetEvent(const HANDLE handle) { const int fd{HandleToFd(handle)}; __u32 previous; return ioctl(fd, NTSYNC_IOC_EVENT_RESET, &previous) == 0; } bool CloseHandle(const HANDLE handle) { return close(HandleToFd(handle)) == 0; } std::uint32_t WaitForSingleObject(const HANDLE handle, const std::uint32_t milliseconds) { return WaitForMultipleObjects(1, &handle, false, milliseconds); } #include std::uint32_t WaitForMultipleObjects(const std::uint32_t count, const HANDLE *const handles, const bool waitAll, const std::uint32_t milliseconds) { if (count > NTSYNC_MAX_WAIT_COUNT) { errno = EINVAL; return WAIT_FAILED; } std::array objs{}; std::ranges::copy(std::span{handles, count} | std::views::transform(&HandleToFd), objs.begin()); std::uint64_t nanoseconds; if (milliseconds == std::numeric_limits::max()) { nanoseconds = std::numeric_limits<__u64>::max(); } else { nanoseconds = static_cast<__u64>(milliseconds) * 1'000'000; } int efd = eventfd(1, EFD_CLOEXEC); ntsync_wait_args args{ .timeout = nanoseconds, .objs = static_cast<__u64>(reinterpret_cast(objs.data())), .count = count, .owner = static_cast<__u32>(gettid()), .alert = (__u32) efd }; for (;;) { if (NtSync.IoControl(waitAll ? NTSYNC_IOC_WAIT_ALL : NTSYNC_IOC_WAIT_ANY, &args) == 0) { return WAIT_OBJECT_0 + args.index; } else { switch (errno) { case EINTR: continue; case ETIMEDOUT: return WAIT_TIMEOUT; case EOWNERDEAD: return WAIT_ABANDONED_0 + args.index; default: return WAIT_FAILED; } } } }