Add epoll bindings to std::os::linux + misc (#2350)

* Add epoll to std::os::linux + misc

* Fix import in linux.c3

* epoll: Add unit tests

* epoll: Fix imports in unit tests

* epoll: Add libc import
This commit is contained in:
Velikiy Kirill
2025-08-13 03:41:09 +03:00
committed by GitHub
parent db99de9717
commit 8e8d0436ad
3 changed files with 292 additions and 1 deletions

139
lib/std/os/linux/epoll.c3 Normal file
View File

@@ -0,0 +1,139 @@
module std::os::linux @if(env::LINUX);
import libc;
// https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h
const uint EPOLLIN = EpollEvents.EPOLLIN;
const uint EPOLLPRI = EpollEvents.EPOLLPRI;
const uint EPOLLOUT = EpollEvents.EPOLLOUT;
const uint EPOLLRDNORM = EpollEvents.EPOLLRDNORM;
const uint EPOLLRDBAND = EpollEvents.EPOLLRDBAND;
const uint EPOLLWRNORM = EpollEvents.EPOLLWRNORM;
const uint EPOLLWRBAND = EpollEvents.EPOLLWRBAND;
const uint EPOLLMSG = EpollEvents.EPOLLMSG;
const uint EPOLLERR = EpollEvents.EPOLLERR;
const uint EPOLLHUP = EpollEvents.EPOLLHUP;
const uint EPOLLRDHUP = EpollEvents.EPOLLRDHUP;
const uint EPOLLEXCLUSIVE = EpollEvents.EPOLLEXCLUSIVE;
const uint EPOLLWAKEUP = EpollEvents.EPOLLWAKEUP;
const uint EPOLLONESHOT = EpollEvents.EPOLLONESHOT;
const uint EPOLLET = EpollEvents.EPOLLET;
enum EpollEvents: const inline uint
{
EPOLLIN = 0x001,
EPOLLPRI = 0x002,
EPOLLOUT = 0x004,
EPOLLRDNORM = 0x040,
EPOLLRDBAND = 0x080,
EPOLLWRNORM = 0x100,
EPOLLWRBAND = 0x200,
EPOLLMSG = 0x400,
EPOLLERR = 0x008,
EPOLLHUP = 0x010,
EPOLLRDHUP = 0x2000,
EPOLLEXCLUSIVE = 1u << 28,
EPOLLWAKEUP = 1u << 29,
EPOLLONESHOT = 1u << 30,
EPOLLET = 1u << 31
}
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
const uint EPOLL_CTL_ADD = 1; /* Add a file descriptor to the interface. */
const uint EPOLL_CTL_DEL = 2; /* Remove a file descriptor from the interface. */
const uint EPOLL_CTL_MOD = 3; /* Change file descriptor epoll_event structure. */
union EpollData
{
void* ptr;
int fd;
uint u32;
ulong u64;
}
struct EpollEvent @packed
{
uint events; /* Epoll events */
EpollData data; /* User data variable */
}
struct EpollParams
{
uint busy_poll_usecs;
ushort busy_poll_budget;
char prefer_busy_poll;
/* pad the struct to a multiple of 64bits */
char __pad;
}
// https://github.com/MatthiasWM/mosrun/blob/master/scratchpad.txt#L330-L348
/*
* Ioctl's have the command encoded in the lower word,
* and the size of any in or out parameters in the upper
* word. The high 2 bits of the upper word are used
* to encode the in/out status of the parameter; for now
* we restrict parameters to at most 256 bytes (disklabels are 216 bytes).
*/
const IOCPARM_MASK = 0xff; /* parameters must be < 256 bytes */
const IOC_VOID = 0x20000000; /* no parameters */
const IOC_OUT = 0x40000000; /* copy out parameters */
const IOC_IN = 0x80000000; /* copy in parameters */
const IOC_INOUT = (IOC_IN|IOC_OUT);
macro ulong @ioctl_IO ($x,$y) @const => (IOC_VOID | (($x)<<8)|y);
macro ulong @ioctl_IOR ($x,$y,$Type) @const => (IOC_OUT |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
macro ulong @ioctl_IOW ($x,$y,$Type) @const => (IOC_IN |(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
/* this should be _IORW, but stdio got there first */
macro ulong @ioctl_IOWR ($x,$y,$Type) @const => (IOC_INOUT|(($Type.sizeof&IOCPARM_MASK)<<16)|(($x)<<8)|$y);
macro ulong @ioctl_ION ($x,$y,$n) @const => (IOC_INOUT|((($n)&IOCPARM_MASK)<<16)|(($x)<<8)|y);
// https://github.com/bminor/glibc/blob/master/sysdeps/unix/sysv/linux/sys/epoll.h
const EPOLL_IOC_TYPE = 0x8A;
const EPIOCSPARAMS = @ioctl_IOW(EPOLL_IOC_TYPE, 0x01, EpollParams);
const EPIOCGPARAMS = @ioctl_IOR(EPOLL_IOC_TYPE, 0x02, EpollParams);
<*
* Creates an epoll instance. Returns an fd for the new instance.
* The "size" parameter is a hint specifying the number of file
* descriptors to be associated with the new instance.
* The fd returned by epoll_create() should be closed with close().
*>
extern fn int epoll_create(int);
<*
* Same as epoll_create but with an FLAGS parameter.
* The unused SIZE parameter has been dropped.
*>
extern fn int epoll_create1(int);
<*
* Manipulate an epoll instance "epfd". Returns 0 in case of success,
* -1 in case of error ( the "errno" variable will contain the
* specific error code ) The "op" parameter is one of the EPOLL_CTL_*
* constants defined above. The "fd" parameter is the target of the
* operation. The "event" parameter describes which events the caller
* is interested in and any associated user data.
*>
extern fn int epoll_ctl (int, int, int, EpollEvent*);
<*
* Wait for events on an epoll instance "epfd". Returns the number of
* triggered events returned in "events" buffer. Or -1 in case of
* error with the "errno" variable set to the specific error code. The
* "events" parameter is a buffer that will contain triggered
* events. The "maxevents" is the maximum number of events to be
* returned ( usually size of "events" ). The "timeout" parameter
* specifies the maximum wait time in milliseconds (-1 == infinite).
*>
extern fn int epoll_wait (int, EpollEvent*, int, int);
<*
* Same as epoll_wait, but the thread's signal mask is temporarily
* and atomically replaced with the one provided as parameter.
*>
extern fn int epoll_pwait (int, EpollEvent*, int, int, Sigset_t*);
<*
* Same as epoll_pwait, but the timeout as a timespec.
*>
extern fn int epoll_pwait2 (int, EpollEvent*, int, TimeSpec*, Sigset_t*);

View File

@@ -1,5 +1,34 @@
module std::os::linux @if(env::LINUX);
import libc, std::os, std::io, std::collections::list;
import libc, std::os, std::io, std::collections::list, std::net::os;
// https://man7.org/linux/man-pages/man3/inet_ntop.3.html
extern fn char** inet_ntop(int, void*, char*, Socklen_t);
// https://linux.die.net/man/3/ntohs
<*
* The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
*>
extern fn uint htonl(uint hostlong);
<*
* The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
*>
extern fn ushort htons(ushort hostshort);
<*
* The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
*>
extern fn uint ntohl(uint netlong);
<*
* The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
*>
extern fn ushort ntohs(ushort netshort);
// https://man7.org/linux/man-pages/man3/bzero.3.html
<*
* The bzero() function erases the data in the n bytes of the memory
* starting at the location pointed to by s, by writing zeros (bytes
* containing '\0') to that area.
*>
extern fn void bzero(char*, usz);
extern fn isz readlink(ZString path, char* buf, usz bufsize);

View File

@@ -0,0 +1,123 @@
module std::os::linux @test @if (env::LINUX);
import std::net::os;
import std::os::posix;
import libc;
fn void test_epoll_create()
{
// Test epoll_create with valid size
int epoll_fd = epoll_create(1);
assert(epoll_fd >= 0, "epoll_create failed");
libc::close(epoll_fd);
// Test epoll_create with size 0 (should work on Linux)
epoll_fd = epoll_create1(0);
assert(epoll_fd >= 0, "epoll_create1 failed");
libc::close(epoll_fd);
// Test epoll_create with negative size (should fail)
epoll_fd = epoll_create(-1);
assert(epoll_fd == -1, "epoll_create with negative size should fail");
}
fn void test_epoll_ctl()
{
int epoll_fd = epoll_create(1);
assert(epoll_fd >= 0, "epoll_create failed");
// Create a socket to monitor
int sock_fd = os::socket(os::AF_INET, os::SOCK_STREAM, 0);
assert(sock_fd >= 0, "socket creation failed");
EpollEvent event;
event.events = EPOLLIN;
event.data.fd = sock_fd;
// Test EPOLL_CTL_ADD
int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &event);
assert(ret == 0, "EPOLL_CTL_ADD failed");
// Test EPOLL_CTL_MOD
event.events = EPOLLIN | EPOLLOUT;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, sock_fd, &event);
assert(ret == 0, "EPOLL_CTL_MOD failed");
// Test EPOLL_CTL_DEL
ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, null);
assert(ret == 0, "EPOLL_CTL_DEL failed");
// Test invalid operations
ret = epoll_ctl(epoll_fd, -1, sock_fd, &event); // invalid op
assert(ret == -1, "Invalid operation should fail");
libc::close(sock_fd);
libc::close(epoll_fd);
}
fn void test_epoll_edge_triggered()
{
int epoll_fd = epoll_create(1);
assert(epoll_fd >= 0, "epoll_create failed");
int[2] pipe_fds;
assert(posix::pipe(&pipe_fds) == 0, "pipe creation failed");
// Set up edge-triggered monitoring
EpollEvent event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = pipe_fds[0];
assert(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipe_fds[0], &event) == 0,
"epoll_ctl add failed");
// Write to pipe
char[] buf = "test";
assert(libc::write(pipe_fds[1], buf, buf.len) == buf.len,
"write to pipe failed");
// First epoll_wait should get the event
EpollEvent[10] events;
int num_events = epoll_wait(epoll_fd, &events[0], events.len, 0);
assert(num_events == 1, "epoll_wait should return 1 event");
// Second epoll_wait shouldn't get the same event (edge-triggered behavior)
num_events = epoll_wait(epoll_fd, &events[0], events.len, 0);
assert(num_events == 0, "epoll_wait should not return event again for edge-triggered");
libc::close(pipe_fds[0]);
libc::close(pipe_fds[1]);
libc::close(epoll_fd);
}
fn void test_epoll_level_triggered()
{
int epoll_fd = epoll_create(1);
assert(epoll_fd >= 0, "epoll_create failed");
int[2] pipe_fds;
assert(posix::pipe(&pipe_fds) == 0, "pipe creation failed");
// Set up level-triggered monitoring
EpollEvent event;
event.events = EPOLLIN; // Default is level-triggered
event.data.fd = pipe_fds[0];
assert(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipe_fds[0], &event) == 0,
"epoll_ctl add failed");
// Write to pipe
char[] buf = "test";
assert(libc::write(pipe_fds[1], buf, buf.len) == buf.len,
"write to pipe failed");
// First epoll_wait should get the event
EpollEvent[10] events;
int num_events = epoll_wait(epoll_fd, &events[0], events.len, 0);
assert(num_events == 1, "epoll_wait should return 1 event");
// Second epoll_wait should get the same event again (level-triggered behavior)
num_events = epoll_wait(epoll_fd, &events[0], events.len, 0);
assert(num_events == 1, "epoll_wait should return event again for level-triggered");
libc::close(pipe_fds[0]);
libc::close(pipe_fds[1]);
libc::close(epoll_fd);
}