a_loop_{add,mod,del}_fd

These functions can fail, however, they are declared void, so there is no good way to get error information from the functions.

This is the internal code of these functions:

void a_loop_add_fd(int epfd, int fd, uint32_t events)
{
    if (fd < 0) {
        L_WARNING("Tried to add invalid fd to epoll: %d", fd);
        return;
    } else if (fd == 0) {
        L_WARNING("Adding fd 0 to epoll. This is usually stdin.");
    }
    static struct epoll_event ev;
    ev.events = events;
    ev.data.fd = fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}

void a_loop_del_fd(int epfd, int fd)
{
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}

void a_loop_mod_fd(int epfd, int fd, uint32_t events)
{
    static struct epoll_event ev;
    ev.events = events;
    ev.data.fd = fd;
    epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
}

epoll_ctl may return -1 and set errno, but that is lost for the caller.

We recommend to use epoll_ctl directly, or using wrappers that return the status of the function call, e.g.:

int a_loop_add_fd(int epfd, int fd, uint32_t events)
{
    if (fd == 0) {
        L_WARNING("Adding fd 0 to epoll. This is usually stdin.");
    }
    static struct epoll_event ev;
    ev.events = events;
    ev.data.fd = fd;
    return epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}

int a_loop_del_fd(int epfd, int fd)
{
    return epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}

int a_loop_mod_fd(int epfd, int fd, uint32_t events)
{
    static struct epoll_event ev;
    ev.events = events;
    ev.data.fd = fd;
    return epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
}

a_timer_create

This function may clobber errno and takes the struct itimerspec by value, it also returns a non-standard negative value on error (-2).

This is the internal code of the function:

int a_timer_create(struct itimerspec ts)
{
    int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (timer_fd == -1) {
        L_ERROR("timerfd_create() failed: errno=%d", errno);
        return -1;
    }
    if (timerfd_settime(timer_fd, 0, &ts, NULL) < 0) {
        L_ERROR("timerfd_settime() failed: errno=%d", errno);
        close(timer_fd);
        return -2;
    }
    return timer_fd;
}

close can clobber the errno value, not taking the struct itimerspec by const reference involves an implicit memcpy each call.

We recommend to use timerfd_create and timerfd_settime directly, or using a wrapper that takes the struct itimerspec by const reference, and preserves errno:

int a_timer_create(const struct itimerspec* ts)
{
    int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (timer_fd == -1) {
        L_ERROR("timerfd_create() failed: errno=%d", errno);
        return -1;
    }
    if (timerfd_settime(timer_fd, 0, ts, NULL) < 0) {
        int saved_errno = errno;
        L_ERROR("timerfd_settime() failed: errno=%d", errno);
        close(timer_fd);
        errno = saved_errno;
        return -1;
    }
    return timer_fd;
}