blob: 1462cdffd8d70fb443cbf61ce62fd4f193e83269 [file]
/*
* Copyright 2026 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*
* Exercises the epoll syscall surface (epoll_create1/epoll_ctl/epoll_wait):
* the interest set, the ADD/MOD/DEL ops with their error returns, readiness
* derivation over a pipe, and that the opaque `data` is echoed back.
*/
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
int main(void) {
// Unknown creation flags are rejected; EPOLL_CLOEXEC is accepted (a no-op).
assert(epoll_create1(999) == -1 && errno == EINVAL);
int cl = epoll_create1(EPOLL_CLOEXEC);
assert(cl >= 0);
close(cl);
int ep = epoll_create1(0);
assert(ep >= 0);
int p[2];
assert(pipe(p) == 0);
struct epoll_event ev = { .events = EPOLLIN };
ev.data.fd = p[0];
assert(epoll_ctl(ep, EPOLL_CTL_ADD, p[0], &ev) == 0);
assert(epoll_ctl(ep, EPOLL_CTL_ADD, p[0], &ev) == -1 && errno == EEXIST);
struct epoll_event out[4];
// Nothing written yet: the read end is not readable.
assert(epoll_wait(ep, out, 4, 0) == 0);
// Make the read end readable.
assert(write(p[1], "x", 1) == 1);
assert(epoll_wait(ep, out, 4, 0) == 1);
assert(out[0].events & EPOLLIN);
assert(out[0].data.fd == p[0]);
// MOD to a condition that is not satisfied (writable on the read end).
ev.events = EPOLLOUT;
assert(epoll_ctl(ep, EPOLL_CTL_MOD, p[0], &ev) == 0);
assert(epoll_wait(ep, out, 4, 0) == 0);
// DEL, and DEL again -> ENOENT.
assert(epoll_ctl(ep, EPOLL_CTL_DEL, p[0], &ev) == 0);
assert(epoll_ctl(ep, EPOLL_CTL_DEL, p[0], &ev) == -1 && errno == ENOENT);
assert(epoll_wait(ep, out, 4, 0) == 0);
// Bad epoll fd and bad target fd.
assert(epoll_ctl(ep, EPOLL_CTL_ADD, 9999, &ev) == -1 && errno == EBADF);
assert(epoll_ctl(9999, EPOLL_CTL_ADD, p[0], &ev) == -1 && errno == EBADF);
// Bad op.
ev.events = EPOLLIN;
assert(epoll_ctl(ep, 999, p[0], &ev) == -1 && errno == EINVAL);
// An epoll cannot watch itself.
assert(epoll_ctl(ep, EPOLL_CTL_ADD, ep, &ev) == -1 && errno == EINVAL);
// Regular files are not epoll-capable (no readiness derivation -> EPERM).
int rf = open("/tmp/epoll_regular", O_CREAT | O_RDWR, 0600);
assert(rf >= 0);
assert(epoll_ctl(ep, EPOLL_CTL_ADD, rf, &ev) == -1 && errno == EPERM);
close(rf);
// maxevents must be positive.
assert(epoll_ctl(ep, EPOLL_CTL_ADD, p[0], &ev) == 0);
assert(epoll_wait(ep, out, 0, 0) == -1 && errno == EINVAL);
assert(epoll_wait(ep, out, -1, 0) == -1 && errno == EINVAL);
// fd reuse: a registration keys on the open file description, so closing a
// watched fd and reusing its number for a different open must not resurrect
// the registration onto the new fd (which would report wrong readiness).
assert(epoll_ctl(ep, EPOLL_CTL_DEL, p[0], &ev) == 0); // empty the set
int a[2];
assert(pipe(a) == 0);
ev.data.fd = a[0];
assert(epoll_ctl(ep, EPOLL_CTL_ADD, a[0], &ev) == 0);
close(a[0]); // the registration is now stale
int b[2];
assert(pipe(b) == 0);
assert(b[0] == a[0]); // b[0] reused a[0]'s freed number
assert(write(b[1], "y", 1) == 1); // the reused fd is readable
assert(epoll_wait(ep, out, 4, 0) == 0); // stale registration must not fire
// The stale entry is gone, so the reused fd adds fresh (evicted, not EEXIST)
// and then reports normally.
ev.data.fd = b[0];
assert(epoll_ctl(ep, EPOLL_CTL_ADD, b[0], &ev) == 0);
assert(epoll_wait(ep, out, 4, 0) == 1 && out[0].data.fd == b[0]);
close(ep);
close(p[0]);
close(p[1]);
printf("done\n");
return 0;
}