mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
Cloudberry Kingdom (PPSA23827, #3177) registers its first kevent change as a file watch: ident 3, EVFILT_VNODE, flags 0x25 (EV_ADD|EV_ENABLE|EV_CLEAR), fflags 0x37 (NOTE_DELETE|WRITE|EXTEND|LINK|RENAME). Palworld's Discord module (PPSA23126, #2648) imports sceKernelAddFileEvent and sceKernelDeleteFileEvent, which no library exported. EVFILT_VNODE watches the host file or directory behind the descriptor and turns host changes into FreeBSD 11 vnode hints, added whole when they share a bit with the requested fflags as filt_vfsvnode does (sys/kern/vfs_subr.c, release/11.0.1 34bd3475): a growing write is NOTE_WRITE|NOTE_EXTEND, a shrinking truncate or an attribute change NOTE_ATTRIB, a link count change NOTE_LINK or NOTE_DELETE, a pending delete NOTE_DELETE, a new path NOTE_RENAME, and in a directory a new, removed or renamed entry NOTE_WRITE (with NOTE_LINK for a subdirectory, as vop_mkdir_post/vop_rmdir_post). Windows uses FindFirstChangeNotification on the parent directory (and the watched directory) and compares handle snapshots every 50 ms, since a write through an open handle is not reported until it is flushed or closed (measured); Linux uses inotify. Closing the descriptor, deleting the event or the queue stops the watcher. sceKernelAddFileEvent(eq, fd, watch, udata) and sceKernelDeleteFileEvent(eq, fd) take the OpenOrbis 0a1aaf9d (libkernel.h:35,109) and SharpProspero 5dfdb4b0 (KernelEqueue.cs:164,168) signatures; NIDs cG3t15OK4Ow and UmXngHKB6is confirmed with tools/nid_names.py. The watch bits are the fflags and the event is added with EV_CLEAR, which is not confirmed on a title. Throws: NOTE_REVOKE, NOTE_OPEN, NOTE_CLOSE, NOTE_CLOSE_WRITE, NOTE_READ and unknown bits, NOTE_EXTEND on a directory on Windows (a move from another directory cannot be told from a creation) and descriptors that are not files, directories, pipes or sockets (pipes, sockets and kqueues fail with EINVAL as in FreeBSD). Recorded in TechnicalDebt. guest_kqueue adds a vnode watch that sees a write to a temporary file with Cloudberry's flags, fflags filtering, a re-registration, the sce pair on an equeue, EINVAL for sockets, pipes and the kqueue itself, the close of a watched descriptor and a directory watch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
128 lines
4.6 KiB
C++
128 lines
4.6 KiB
C++
#ifndef CORE_LIBS_PRX_LIBKERNEL_EQUEUE_EQUEUE_HPP
|
|
#define CORE_LIBS_PRX_LIBKERNEL_EQUEUE_EQUEUE_HPP
|
|
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libkernel/KernelErrors.hpp"
|
|
#include "prx/libkernel/Time/include/TimedWait.hpp"
|
|
|
|
static constexpr int16_t EVFILT_READ = -1;
|
|
static constexpr int16_t EVFILT_WRITE = -2;
|
|
static constexpr int16_t EVFILT_VNODE = -4;
|
|
static constexpr int16_t EVFILT_TIMER = -7;
|
|
static constexpr int16_t EVFILT_USER = -11;
|
|
static constexpr int16_t EVFILT_VIDEO_OUT = -13;
|
|
static constexpr int16_t EVFILT_HRTIMER = -15;
|
|
static constexpr int16_t EVFILT_AMPR = -25;
|
|
|
|
static constexpr uint16_t EV_ADD = 0x0001;
|
|
static constexpr uint16_t EV_DELETE = 0x0002;
|
|
static constexpr uint16_t EV_ENABLE = 0x0004;
|
|
static constexpr uint16_t EV_DISABLE = 0x0008;
|
|
static constexpr uint16_t EV_ONESHOT = 0x0010;
|
|
static constexpr uint16_t EV_CLEAR = 0x0020;
|
|
static constexpr uint16_t EV_RECEIPT = 0x0040;
|
|
static constexpr uint16_t EV_DISPATCH = 0x0080;
|
|
static constexpr uint16_t EV_FORCEONESHOT = 0x0100;
|
|
static constexpr uint16_t EV_SYSFLAGS = 0xF000;
|
|
static constexpr uint16_t EV_ERROR = 0x4000;
|
|
static constexpr uint16_t EV_EOF = 0x8000;
|
|
|
|
static constexpr int EQUEUE_OK = 0;
|
|
|
|
struct KernelEqueueEvent;
|
|
|
|
using EqueueTriggerFunc = void (*)(KernelEqueueEvent* event, void* triggerData);
|
|
using EqueueResetFunc = void (*)(KernelEqueueEvent* event);
|
|
using EqueueDeleteFunc = void (*)(KernelEqueue eq, KernelEqueueEvent* event);
|
|
using EqueuePollFunc = bool (*)(KernelEqueueEvent* event);
|
|
|
|
struct KernelFilter {
|
|
void* data = nullptr;
|
|
std::shared_ptr<void> owner;
|
|
EqueueTriggerFunc triggerFunc = nullptr;
|
|
EqueueResetFunc resetFunc = nullptr;
|
|
EqueueDeleteFunc deleteEventFunc = nullptr;
|
|
EqueuePollFunc pollFunc = nullptr;
|
|
};
|
|
|
|
struct KernelEqueueEvent {
|
|
bool triggered = false;
|
|
uint64_t deadlineNs = 0;
|
|
uint64_t intervalNs = 0;
|
|
KernelEvent event;
|
|
KernelFilter filter;
|
|
std::deque<KernelEvent> pendingEvents;
|
|
};
|
|
|
|
class KernelEqueuePrivate {
|
|
public:
|
|
explicit KernelEqueuePrivate(KernelEqueue handle, bool reportOncePerWait = false);
|
|
~KernelEqueuePrivate();
|
|
|
|
KernelEqueuePrivate(const KernelEqueuePrivate&) = delete;
|
|
KernelEqueuePrivate& operator=(const KernelEqueuePrivate&) = delete;
|
|
|
|
const std::string& GetName() const { return m_name; }
|
|
void SetName(std::string name) { m_name = std::move(name); }
|
|
|
|
int AddEvent(const KernelEqueueEvent& event);
|
|
int TriggerEvent(uintptr_t ident, int16_t filter, void* triggerData);
|
|
int DeleteEvent(uintptr_t ident, int16_t filter);
|
|
bool HasEvent(uintptr_t ident, int16_t filter);
|
|
int EditEvent(uintptr_t ident, int16_t filter, const std::function<void(KernelEqueueEvent&)>& edit);
|
|
int GetTriggeredEvents(KernelEvent* ev, int num);
|
|
int WaitForEvents(KernelEvent* ev, int num, uint32_t micros);
|
|
int WaitForEventsUntil(KernelEvent* ev, int num, std::uint64_t deadlineNanos);
|
|
void RemoveDescriptorEvents(uintptr_t ident);
|
|
void Close();
|
|
static uint64_t MonotonicNs();
|
|
|
|
private:
|
|
void TriggerExpiredTimers(uint64_t nowNs);
|
|
bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const;
|
|
int CollectTriggered(KernelEvent* ev, int num);
|
|
int CollectOncePerWait(KernelEvent* ev, int num);
|
|
bool PollEvents();
|
|
void WakePollers();
|
|
void WaitForDescriptors(std::unique_lock<std::mutex>& lock, std::uint64_t deadlineNanos);
|
|
|
|
std::list<KernelEqueueEvent> m_events;
|
|
std::vector<std::uintptr_t> m_pollers;
|
|
std::mutex m_mutex;
|
|
TimedWait::Condition m_cond;
|
|
std::string m_name;
|
|
KernelEqueue m_handle;
|
|
bool m_reportOncePerWait;
|
|
bool m_closed = false;
|
|
};
|
|
|
|
using KernelEqueueRef = std::shared_ptr<KernelEqueuePrivate>;
|
|
void EqueueDescriptorClosed(int descriptor);
|
|
void KqueueDescriptorClosed(int descriptor);
|
|
bool IsKqueueDescriptor(int descriptor);
|
|
KernelEqueueEvent EqueueDescriptorEvent(int descriptor, int16_t filter, uint16_t flags, void* udata);
|
|
void RequireVnodeNotes(uint32_t fflags);
|
|
void SetVnodeNotes(KernelEqueueEvent& event, uint32_t fflags);
|
|
int AddVnodeEvent(const KernelEqueueRef& queue, int descriptor, uint32_t fflags, uint16_t flags, void* udata);
|
|
extern "C" {
|
|
|
|
KernelEqueueRef EqueuePin_nid_postfix(KernelEqueue eq);
|
|
int APS5_VABI EqueueAddEvent_nid_postfix(KernelEqueue eq, const KernelEqueueEvent& event);
|
|
int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter, void* triggerData);
|
|
int APS5_VABI EqueueDeleteEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter);
|
|
|
|
}
|
|
|
|
#endif
|