Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions core/libs/prx/libkernel/Equeue/src/Kqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,18 @@ int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::u
}
NotImplemented_nid_no_patch("kevent: EVFILT_READ on a kqueue descriptor");
}
if (!GuestSockets::IsOpen(descriptor)) {
NotImplemented_nid_no_patch(change.filter == EVFILT_READ ? "kevent: EVFILT_READ on a non-socket descriptor" : "kevent: EVFILT_WRITE on a non-socket descriptor");
const bool socket = GuestSockets::IsOpen(descriptor);
std::int64_t data = 0;
bool eof = false;
const bool pipe = !socket && change.filter == EVFILT_READ && GuestSockets::PipeReadState(descriptor, &data, &eof);
if (!socket && !pipe) {
NotImplemented_nid_no_patch(change.filter == EVFILT_READ ? "kevent: EVFILT_READ on a descriptor that is neither a socket nor the read end of a pipe" : "kevent: EVFILT_WRITE on a non-socket descriptor");
}
if ((change.fflags & NOTE_LOWAT) != 0) {
NotImplemented_nid_no_patch("kevent: NOTE_LOWAT");
}
if ((flags & EV_CLEAR) != 0) {
NotImplemented_nid_no_patch("kevent: EV_CLEAR on a socket");
NotImplemented_nid_no_patch(pipe ? "kevent: EV_CLEAR on a pipe" : "kevent: EV_CLEAR on a socket");
}
auto event = EqueueDescriptorEvent(descriptor, change.filter, flags, change.udata);
change.fflags = 0;
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct Interest {
std::uintptr_t CurrentWaker();
void Wake(std::uintptr_t waker);
bool WaitAny(const std::vector<Interest>& interests, std::uint64_t deadlineNanos);
bool PipeReadState(int descriptor, std::int64_t* data, bool* eof);
std::int64_t Readv(int descriptor, const Iovec* iov, int iovcnt);
std::int64_t Writev(int descriptor, const Iovec* iov, int iovcnt);
}
Expand Down
45 changes: 43 additions & 2 deletions core/libs/prx/libkernel/Socket/src/Udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ struct Socket {
struct Waker {
NativeSocket value = Invalid;
Waker() {
#ifdef _WIN32
static const int startup = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }();
if (startup) throw std::runtime_error("equeue waker: WSAStartup failed, error " + std::to_string(startup));
#endif
value = ::socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in address{};
address.sin_family = AF_INET;
Expand Down Expand Up @@ -335,6 +339,7 @@ bool GuestSockets::WaitAny(const std::vector<Interest>& interests, std::uint64_t
std::vector<WSAPOLLFD> entries;
constexpr SHORT ReadMask = POLLRDNORM;
constexpr SHORT WriteMask = POLLWRNORM;
bool pipes = false;
#else
std::vector<pollfd> entries;
constexpr short ReadMask = POLLIN;
Expand All @@ -344,7 +349,14 @@ bool GuestSockets::WaitAny(const std::vector<Interest>& interests, std::uint64_t
std::lock_guard lock(socketsMutex);
for (const auto& interest : interests) {
const auto found = sockets.find(interest.descriptor);
if (found == sockets.end()) continue;
if (found == sockets.end()) {
#ifdef _WIN32
pipes = true;
#else
entries.push_back({interest.descriptor, ReadMask, 0});
#endif
continue;
}
held.push_back(found->second);
entries.push_back({found->second->value, interest.write ? WriteMask : ReadMask, 0});
}
Expand All @@ -362,6 +374,7 @@ bool GuestSockets::WaitAny(const std::vector<Interest>& interests, std::uint64_t
if (remaining < 1000000) return false;
timeout = static_cast<INT>(std::min<std::uint64_t>(remaining / 1000000, INT_MAX));
}
if (pipes && (timeout < 0 || timeout > 1)) timeout = 1;
if (WSAPoll(entries.data(), static_cast<ULONG>(entries.size()), timeout) == SOCKET_ERROR)
throw std::runtime_error("equeue wait: WSAPoll failed, guest errno " + std::to_string(NativeError()));
#else
Expand Down Expand Up @@ -701,7 +714,7 @@ bool GuestSockets::Ready(int descriptor, bool write, std::int64_t* data, bool* e
{
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
if (found == sockets.end()) return false;
if (found == sockets.end()) return !write && PipeReadState(descriptor, data, eof) && (*data > 0 || *eof);
socket = found->second;
}
#ifdef _WIN32
Expand Down Expand Up @@ -730,6 +743,34 @@ bool GuestSockets::Ready(int descriptor, bool write, std::int64_t* data, bool* e
return true;
}

bool GuestSockets::PipeReadState(int descriptor, std::int64_t* data, bool* eof) {
#ifdef _WIN32
const auto handle = reinterpret_cast<HANDLE>(::_get_osfhandle(descriptor));
if (handle == INVALID_HANDLE_VALUE || GetFileType(handle) != FILE_TYPE_PIPE) return false;
static const auto query = reinterpret_cast<QueryInformationFile>(
reinterpret_cast<void*>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationFile")));
if (!query) throw std::runtime_error("kevent: NtQueryInformationFile is unavailable");
PipeIoStatus status{};
ACCESS_MASK access = 0;
PipeLocalInformation pipe{};
if (query(handle, &status, &access, sizeof(access), FileAccessInformationClass) < 0 ||
query(handle, &status, &pipe, sizeof(pipe), FilePipeLocalInformationClass) < 0)
throw std::runtime_error("kevent: querying pipe descriptor " + std::to_string(descriptor) + " failed");
if (!(access & FILE_READ_DATA)) return false;
*data = pipe.readDataAvailable;
*eof = pipe.state == PipeClosingState;
#else
if (!IsPipe(descriptor, 0) || (::fcntl(descriptor, F_GETFL) & O_ACCMODE) == O_WRONLY) return false;
pollfd entry{descriptor, POLLIN, 0};
int available = 0;
if (::poll(&entry, 1, 0) < 0 || ::ioctl(descriptor, FIONREAD, &available) != 0)
throw std::runtime_error("kevent: querying pipe descriptor " + std::to_string(descriptor) + " failed");
*data = available;
*eof = (entry.revents & POLLHUP) != 0;
#endif
return true;
}

int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) {
std::vector<std::shared_ptr<Socket>> held;
std::vector<NativePollDescriptor> native;
Expand Down
41 changes: 40 additions & 1 deletion core/libs/tests/GuestKqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI socketpair_nid_postfix(int, int, int, int*);
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI mkdir_nid_postfix(const char*, unsigned short);
int APS5_VABI rmdir_nid_postfix(const char*);
Expand Down Expand Up @@ -60,6 +61,7 @@ static constexpr std::uint16_t EvReceipt = 0x0040;
static constexpr std::uint16_t EvDispatch = 0x0080;
static constexpr std::uint16_t EvForceoneshot = 0x0100;
static constexpr std::uint16_t EvError = 0x4000;
static constexpr std::uint16_t EvEof = 0x8000;
static constexpr std::uint32_t NoteTrigger = 0x01000000;
static constexpr std::uint32_t NoteFfcopy = 0xc0000000;
static constexpr std::uint32_t NoteFfor = 0x80000000;
Expand Down Expand Up @@ -208,6 +210,40 @@ static void UserEvents() {
Require(close_nid_postfix(kq) == 0);
}

static void PipeRead() {
const int kq = kqueue_nid_postfix();
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
const auto readEnd = static_cast<std::uintptr_t>(pipe[0]);
int marker = 0;
KernelEvent events[4]{};
Require(Apply(kq, Change(readEnd, EvfiltRead, EvAdd, 0, 0, &marker)) == 0);
Require(Poll(kq, events, 4) == 0);
Require(write_nid_postfix(pipe[1], "abc", 3) == 3);
Require(Poll(kq, events, 4) == 1);
Require(events[0].ident == readEnd && events[0].filter == EvfiltRead && events[0].data == 3 && events[0].udata == &marker && (events[0].flags & EvEof) == 0);
char buffer[4]{};
Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 3);
Require(Poll(kq, events, 4) == 0);
std::thread writer([&pipe] {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Require(write_nid_postfix(pipe[1], "d", 1) == 1);
});
const KernelTimespec limit{5, 0};
const auto start = std::chrono::steady_clock::now();
Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, &limit) == 1);
const auto waited = std::chrono::steady_clock::now() - start;
writer.join();
Require(waited >= std::chrono::milliseconds(40) && waited < std::chrono::seconds(4));
Require(events[0].ident == readEnd && events[0].data == 1);
Require(close_nid_postfix(pipe[1]) == 0);
Require(Poll(kq, events, 4) == 1 && events[0].data == 1 && (events[0].flags & EvEof) != 0);
Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 1);
Require(Poll(kq, events, 4) == 1 && events[0].data == 0 && (events[0].flags & EvEof) != 0);
Require(close_nid_postfix(pipe[0]) == 0);
Require(close_nid_postfix(kq) == 0);
}

static void SocketPair() {
const int kq = kqueue_nid_postfix();
int pair[2];
Expand Down Expand Up @@ -306,7 +342,9 @@ static void Errors() {
RequireFailure(kevent_nid_postfix(kq, nullptr, 0, events, -1, nullptr), GuestEinval);
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(Throws([kq, &pipe] { Apply(kq, Change(static_cast<std::uintptr_t>(pipe[0]), EvfiltRead, EvAdd)); }));
Require(Throws([kq, &pipe] { Apply(kq, Change(static_cast<std::uintptr_t>(pipe[1]), EvfiltRead, EvAdd)); }));
Require(Throws([kq, &pipe] { Apply(kq, Change(static_cast<std::uintptr_t>(pipe[1]), EvfiltWrite, EvAdd)); }));
Require(Throws([kq, &pipe] { Apply(kq, Change(static_cast<std::uintptr_t>(pipe[0]), EvfiltRead, EvAdd | EvClear)); }));
Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0);
const KernelEvent skipped = Change(1, 0, EvAdd);
const KernelTimespec zero{0, 0};
Expand Down Expand Up @@ -584,6 +622,7 @@ static void Timeout() {
int main() {
CreateAndClose();
Timers();
PipeRead();
UserEvents();
SocketPair();
DisableAndDispatch();
Expand Down
Loading
Loading