#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
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#define get_fd(self) PerlIO_fileno(IoOFP(sv_2io(SvRV(self))));
static void get_sys_error(char* buffer, size_t buffer_size) {
#if HAVE_STRERROR_R
# if STRERROR_R_PROTO == REENTRANT_PROTO_B_IBW
const char* message = strerror_r(errno, buffer, buffer_size);
if (message != buffer)
memcpy(buffer, message, buffer_size);
# else
strerror_r(errno, buffer, buffer_size);
# endif
#else
const char* message = strerror(errno);
strncpy(buffer, message, buffer_size - 1);
buffer[buffer_size - 1] = '\0';
#endif
}
static void S_die_sys(pTHX_ const char* format) {
char buffer[128];
get_sys_error(buffer, sizeof buffer);
Perl_croak(aTHX_ format, buffer);
}
#define die_sys(format) S_die_sys(aTHX_ format)
static sigset_t* S_sv_to_sigset(pTHX_ SV* sigmask, const char* name) {
IV tmp;
if (!SvOK(sigmask))
return NULL;
if (!SvROK(sigmask) || !sv_derived_from(sigmask, "POSIX::SigSet"))
Perl_croak(aTHX_ "%s is not of type POSIX::SigSet");
#if PERL_VERSION > 15 || PERL_VERSION == 15 && PERL_SUBVERSION > 2
return (sigset_t *) SvPV_nolen(SvRV(sigmask));
#else
tmp = SvIV((SV*)SvRV(sigmask));
return INT2PTR(sigset_t*, tmp);
#endif
}
#define sv_to_sigset(sigmask, name) S_sv_to_sigset(aTHX_ sigmask, name)
static sigset_t* S_get_sigset(pTHX_ SV* signal, const char* name) {
if (SvROK(signal))
return sv_to_sigset(signal, name);
else {
int signo = (SvIOK(signal) || looks_like_number(signal)) && SvIV(signal) ? SvIV(signal) : whichsig(SvPV_nolen(signal));
SV* buffer = sv_2mortal(newSVpvn("", 0));
SvGROW(buffer, sizeof(sigset_t));
sigset_t* ret = (sigset_t*)SvPV_nolen(buffer);
sigemptyset(ret);
sigaddset(ret, signo);
return ret;
}
}
#define get_sigset(sigmask, name) S_get_sigset(aTHX_ sigmask, name)
#define NANO_SECONDS 1000000000
static NV timespec_to_nv(struct timespec* time) {
return time->tv_sec + time->tv_nsec / (double)NANO_SECONDS;
}
static void nv_to_timespec(NV input, struct timespec* output) {
output->tv_sec = (time_t) floor(input);
output->tv_nsec = (long) ((input - output->tv_sec) * NANO_SECONDS);
}
typedef struct { const char* key; clockid_t value; } map[];
static map clocks = {
{ "realtime" , CLOCK_REALTIME },
{ "monotonic", CLOCK_MONOTONIC }
};
static clockid_t S_get_clockid(pTHX_ const char* clock_name) {
int i;
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_clockid(name) S_get_clockid(aTHX_ name)
static SV* S_io_fdopen(pTHX_ int fd, SV* classname) {
PerlIO* pio = PerlIO_fdopen(fd, "r");
GV* gv = newGVgen("Symbol");
SV* ret = newRV_noinc((SV*)gv);
IO* io = GvIOn(gv);
HV* stash = gv_stashsv(classname, FALSE);
IoTYPE(io) = '<';
IoIFP(io) = pio;
IoOFP(io) = pio;
sv_bless(ret, stash);
return ret;
}
#define io_fdopen(fd, classname) S_io_fdopen(aTHX_ fd, classname)
#ifndef EFD_CLOEXEC
#define EFD_CLOEXEC 0
#endif
#ifndef SFD_CLOEXEC
#define SFD_CLOEXEC 0
#endif
#ifndef TFD_CLOEXEC
#define TFD_CLOEXEC 0
#endif
#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 map flags = {
#ifdef EFD_NONBLOCK
{ "non-blocking", EFD_NONBLOCK },
#endif
#ifdef EFD_SEMAPHORE
{ "semaphore", EFD_SEMAPHORE },
#endif
};
static UV S_get_event_flag(pTHX_ SV* flag_name) {
int i;
for (i = 0; i < sizeof flags / sizeof *flags; ++i)
if (strEQ(SvPV_nolen(flag_name), flags[i].key))
return flags[i].value;
Perl_croak(aTHX_ "No such flag '%s' known", flag_name);
}
#define get_event_flag(name) S_get_event_flag(aTHX_ name)
MODULE = Linux::FD PACKAGE = Linux::FD::Event
SV*
new(classname, initial = 0, ...)
SV* classname;
UV initial;
PREINIT:
HV* stash;
int fd, i, flags = EFD_CLOEXEC;
CODE:
for (i = 2; i < items; i++)
flags |= get_event_flag(ST(i));
fd = eventfd(initial, flags);
if (fd < 0)
Perl_croak(aTHX_ "Can't open eventfd descriptor: %s");
RETVAL = io_fdopen(fd, classname);
OUTPUT:
RETVAL
UV
get(self)
SV* self;
PREINIT:
uint64_t buffer;
int ret, events;
CODE:
events = get_fd(self);
do {
ret = read(events, &buffer, sizeof buffer);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't read from eventfd: %s");
}
RETVAL = buffer;
OUTPUT:
RETVAL
UV
add(self, value)
SV* self;
UV value;
PREINIT:
uint64_t buffer;
int ret, events;
CODE:
events = get_fd(self);
buffer = value;
do {
ret = write(events, &buffer, sizeof buffer);
} while (ret == -1 && errno == EINTR);
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(classname, sigmask)
SV* classname;
SV* sigmask;
PREINIT:
int fd;
HV* stash;
CODE:
fd = signalfd(-1, get_sigset(sigmask, "signalfd"), SFD_CLOEXEC);
if (fd < 0)
Perl_croak(aTHX_ "Can't open signalfd descriptor: %s");
RETVAL = io_fdopen(fd, classname);
OUTPUT:
RETVAL
void set_mask(self, sigmask)
SV* self;
SV* sigmask;
PREINIT:
int fd;
CODE:
fd = get_fd(self);
if(signalfd(fd, sv_to_sigset(sigmask, "signalfd"), 0) == -1)
die_sys("Couldn't set_mask: %s");
SV*
receive(self)
SV* self;
PREINIT:
struct signalfd_siginfo buffer;
int tmp, timer;
HV* hash;
CODE:
timer = get_fd(self);
do {
tmp = read(timer, &buffer, sizeof buffer);
} while (tmp == -1 && errno == EINTR);
if (tmp == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't read from signalfd: %s");
}
hash = newHV();
SET_HASH_U(signo);
SET_HASH_I(errno);
SET_HASH_I(code);
SET_HASH_U(pid);
SET_HASH_U(uid);
SET_HASH_I(fd);
SET_HASH_U(tid);
SET_HASH_U(band);
SET_HASH_U(overrun);
SET_HASH_U(trapno);
SET_HASH_I(status);
SET_HASH_I(int);
SET_HASH_U(ptr);
SET_HASH_U(utime);
SET_HASH_U(stime);
SET_HASH_U(addr);
RETVAL = newRV_noinc((SV*)hash);
OUTPUT:
RETVAL
MODULE = Linux::FD PACKAGE = Linux::FD::Timer
SV*
new(classname, clock_name)
SV* classname;
const char* clock_name;
PREINIT:
clockid_t clock_id;
int fd;
CODE:
clock_id = get_clockid(clock_name);
fd = timerfd_create(clock_id, TFD_CLOEXEC);
if (fd < 0)
Perl_croak(aTHX_ "Can't open signalfd descriptor: %s");
RETVAL = io_fdopen(fd, classname);
OUTPUT:
RETVAL
void
get_timeout(self)
SV* self;
PREINIT:
int timer;
struct itimerspec value;
PPCODE:
timer = get_fd(self);
if (timerfd_gettime(timer, &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(self, new_value, new_interval = 0, abstime = 0)
SV* self;
NV new_value;
NV new_interval;
IV abstime;
PREINIT:
int timer;
struct itimerspec new_itimer, old_itimer;
PPCODE:
timer = get_fd(self);
nv_to_timespec(new_value, &new_itimer.it_value);
nv_to_timespec(new_interval, &new_itimer.it_interval);
if (timerfd_settime(timer, (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(self)
SV* self;
PREINIT:
uint64_t buffer;
int ret, timer;
CODE:
timer = get_fd(self);
do {
ret = read(timer, &buffer, sizeof buffer);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
if (errno == EAGAIN)
XSRETURN_EMPTY;
else
die_sys("Couldn't read from timerfd: %s");
}
RETVAL = buffer;
OUTPUT:
RETVAL