#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);