The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#define GNU_STRERROR_R
#include <string.h>
#include <sys/eventfd.h>
#include <sys/signalfd.h>
#include <sys/timerfd.h>
#define PERL_NO_GET_CONTEXT
#define PERL_REENTR_API 1
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#define die_sys(format) Perl_croak(aTHX_ format, strerror(errno))
#define NANO_SECONDS 1000000000
static NV timespec_to_nv(struct timespec* time) {
return time->tv_sec + time->tv_nsec / (double)NANO_SECONDS;
}
typedef struct { const char* key; size_t length; int value; } map[];
static map clocks = {
{ STR_WITH_LEN("monotonic") , CLOCK_MONOTONIC },
{ STR_WITH_LEN("realtime") , CLOCK_REALTIME },
#ifdef CLOCK_BOOTTIME
{ STR_WITH_LEN("boottime") , CLOCK_BOOTTIME },
#endif
#ifdef CLOCK_REALTIME_ALARM
{ STR_WITH_LEN("realtime_alarm"), CLOCK_REALTIME_ALARM },
#endif
#ifdef CLOCK_BOOTTIME_ALARM
{ STR_WITH_LEN("boottime_alarm"), CLOCK_BOOTTIME_ALARM },
#endif
};
static clockid_t S_get_clock(pTHX_ SV* clock, const char* funcname) {
if (SvROK(clock)) {
SV* value;
if (!SvROK(clock) || !(value = SvRV(clock)))
Perl_croak(aTHX_ "Could not %s: this variable is not a clock", funcname);
return SvIV(value);
} else {
int i;
const char* clock_name = SvPV_nolen(clock);
for (i = 0; i < sizeof clocks / sizeof *clocks; ++i) {
if (strEQ(clock_name, clocks[i].key))
return clocks[i].value;
}
Perl_croak(aTHX_ "No such timer '%s' known", clock_name);
}
}
#define get_clock(ref, func) S_get_clock(aTHX_ ref, func)
static SV* S_io_fdopen(pTHX_ int fd, const char* classname, char type) {
PerlIO* pio = PerlIO_fdopen(fd, "r");
GV* gv = newGVgen(classname);
SV* ret = newRV_noinc((SV*)gv);
IO* io = GvIOn(gv);
HV* stash = gv_stashpv(classname, FALSE);
IoTYPE(io) = type;
IoIFP(io) = pio;
IoOFP(io) = pio;
sv_bless(ret, stash);
return ret;
}
#define io_fdopen(fd, classname, type) S_io_fdopen(aTHX_ fd, classname, type)
#define SET_HASH_IMPL(key,value) hv_store(hash, key, sizeof key - 1, value, 0)
#define SET_HASH_U(key) SET_HASH_IMPL(#key, newSVuv(buffer.ssi_##key))
#define SET_HASH_I(key) SET_HASH_IMPL(#key, newSViv(buffer.ssi_##key))
static UV S_get_flag(pTHX_ map flags, size_t map_size, SV* flag_name) {
int i;
for (i = 0; i < map_size / sizeof *flags; ++i)
if (strEQ(SvPV_nolen(flag_name), flags[i].key))
return flags[i].value;
Perl_croak(aTHX_ "No such flag '%s' known", SvPV_nolen(flag_name));
}
#define get_flag(map, name) S_get_flag(aTHX_ map, sizeof(map), name)
static map event_flags = {
{ STR_WITH_LEN("non-blocking") , EFD_NONBLOCK },
{ STR_WITH_LEN("semaphore") , EFD_SEMAPHORE },
};
#define get_event_flag(name) get_flag(event_flags, name)
static SV* S_new_eventfd(pTHX_ const char* classname, UV initial, int flags) {
int fd = eventfd(initial, flags);
if (fd < 0)
die_sys("Can't open eventfd descriptor: %s");
return io_fdopen(fd, classname, '|');
}
#define new_eventfd(classname, initial, flags) S_new_eventfd(aTHX_ classname, initial, flags)
static map signal_flags = {
{ STR_WITH_LEN("non-blocking") , SFD_NONBLOCK },
};
#define get_signal_flag(name) get_flag(signal_flags, name)
static SV* S_new_signalfd(pTHX_ const char* classname, const sigset_t* sigmask, int flags) {
int fd = signalfd(-1, sigmask, flags);
if (fd < 0)
die_sys("Can't open signalfd descriptor: %s");
return io_fdopen(fd, classname, '<');
}
#define new_signalfd(classname, sigset, flags) S_new_signalfd(aTHX_ classname, sigset, flags)
static map timer_flags = {
{ STR_WITH_LEN("non-blocking") , TFD_NONBLOCK },
};
#define get_timer_flag(name) get_flag(timer_flags, name)
static SV* S_new_timerfd(pTHX_ const char* classname, SV* clock, int flags, const char* funcname) {
clockid_t clock_id = get_clock(clock, funcname);
int fd = timerfd_create(clock_id, flags);
if (fd < 0)
die_sys("Can't open timerfd descriptor: %s");
return io_fdopen(fd, classname, '<');
}
#define new_timerfd(classname, clock, flags, func) S_new_timerfd(aTHX_ classname, clock, flags, func)
static int S_interrupted(pTHX_ int value) {
if (value == -1 && errno == EINTR) {
PERL_ASYNC_CHECK();
return 1;
}
return 0;
}
#define interrupted(value) S_interrupted(aTHX_ value)
#define NEVER (struct timespec) { 0 }
typedef int Fd;
MODULE = Linux::FD PACKAGE = Linux::FD
BOOT:
load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("IO::Handle"), NULL);
av_push(get_av("Linux::FD::Event::ISA" , GV_ADD), newSVpvs("IO::Handle"));
av_push(get_av("Linux::FD::Signal::ISA", GV_ADD), newSVpvs("IO::Handle"));
av_push(get_av("Linux::FD::Timer::ISA" , GV_ADD), newSVpvs("IO::Handle"));
SV* eventfd(UV initial = 0, ...)
PREINIT:
int i, flags = EFD_CLOEXEC;
CODE:
for (i = 1; i < items; i++)
flags |= get_event_flag(ST(i));
RETVAL = new_eventfd("Linux::FD::Event", initial, flags);
OUTPUT:
RETVAL
SV* signalfd(sigset_t* sigmask, ...)
PREINIT:
int i, flags = SFD_CLOEXEC;
CODE:
for (i = 1; i < items; i++)
flags |= get_signal_flag(ST(i));
RETVAL = new_signalfd("Linux::FD::Signal", sigmask, flags);
OUTPUT:
RETVAL
SV* timerfd(SV* clock, ...)
PREINIT:
int i, flags = TFD_CLOEXEC;
CODE:
for (i = 1; i < items; i++)
flags |= get_timer_flag(ST(i));
RETVAL = new_timerfd("Linux::FD::Timer", clock, flags, "timerfd");
OUTPUT:
RETVAL
MODULE = Linux::FD PACKAGE = Linux::FD::Event
SV* new(const char* classname, UV initial = 0, ...)
PREINIT:
int i, flags = EFD_CLOEXEC;
CODE:
for (i = 2; i < items; i++)
flags |= get_event_flag(ST(i));
RETVAL = new_eventfd(classname, initial, flags);
OUTPUT:
RETVAL
UV get(Fd eventfd)
PREINIT:
uint64_t buffer;
int ret;
CODE:
do {
ret = read(eventfd, &buffer, sizeof buffer);
} while (interrupted(ret));
if (ret == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't read from eventfd: %s");
}
RETVAL = buffer;
OUTPUT:
RETVAL
UV add(Fd eventfd, UV value)
PREINIT:
uint64_t buffer;
int ret;
CODE:
buffer = value;
do {
ret = write(eventfd, &buffer, sizeof buffer);
} while (interrupted(ret));
if (ret == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't write to eventfd: %s");
}
RETVAL = value;
OUTPUT:
RETVAL
MODULE = Linux::FD PACKAGE = Linux::FD::Signal
SV* new(const char* classname, sigset_t* sigmask, ...)
PREINIT:
int i, flags = SFD_CLOEXEC;
CODE:
for (i = 2; i < items; i++)
flags |= get_signal_flag(ST(i));
RETVAL = new_signalfd(classname, sigmask, flags);
OUTPUT:
RETVAL
void set_mask(Fd fd, sigset_t* sigmask)
CODE:
if(signalfd(fd, sigmask, 0) == -1)
die_sys("Couldn't set_mask: %s");
struct signalfd_siginfo receive(Fd fd)
PREINIT:
int tmp;
CODE:
do {
tmp = read(fd, &RETVAL, sizeof(RETVAL));
} while (interrupted(tmp));
if (tmp == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't read from signalfd: %s");
}
OUTPUT:
RETVAL
MODULE = Linux::FD PACKAGE = Linux::FD::Timer
SV* new(const char* classname, SV* clock, ...)
PREINIT:
int i, flags = TFD_CLOEXEC;
CODE:
for (i = 2; i < items; i++)
flags |= get_timer_flag(ST(i));
RETVAL = new_timerfd(classname, clock, flags, "Linux::FD::Timer->new");
OUTPUT:
RETVAL
void get_timeout(Fd timerfd)
PREINIT:
struct itimerspec value;
PPCODE:
if (timerfd_gettime(timerfd, &value) == -1)
die_sys("Couldn't get_timeout: %s");
mXPUSHn(timespec_to_nv(&value.it_value));
if (GIMME_V == G_ARRAY)
mXPUSHn(timespec_to_nv(&value.it_interval));
SV* set_timeout(Fd timerfd, struct timespec new_value, struct timespec new_interval = NEVER, bool abstime = FALSE)
PREINIT:
struct itimerspec new_itimer, old_itimer;
PPCODE:
new_itimer.it_value = new_value;
new_itimer.it_interval = new_interval;
if (timerfd_settime(timerfd, (abstime ? TIMER_ABSTIME : 0), &new_itimer, &old_itimer) == -1)
die_sys("Couldn't set_timeout: %s");
mXPUSHn(timespec_to_nv(&old_itimer.it_value));
if (GIMME_V == G_ARRAY)
mXPUSHn(timespec_to_nv(&old_itimer.it_interval));
IV receive(Fd timerfd)
PREINIT:
uint64_t buffer;
int ret;
CODE:
do {
ret = read(timerfd, &buffer, sizeof buffer);
} while (interrupted(ret));
if (ret == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't read from timerfd: %s");
}
RETVAL = buffer;
OUTPUT:
RETVAL
void clocks(SV* classname)
INIT:
int i;
PPCODE:
for (i = 0; i < sizeof clocks / sizeof *clocks; ++i)
mXPUSHp(clocks[i].key, clocks[i].length);