@@ -0,0 +1,8 @@ | |||||
noinst_LIBRARIES=libevloop.a | |||||
EXTRA_libevloop_a_SOURCES=epoll.c poll.c | |||||
libevloop_a_SOURCES=\ | |||||
evloop.h\ | |||||
timeheap.c timeheap.h timer.c | |||||
CFLAGS=@CFLAGS@ -D@EVLOOP_METHOD@ -I$(top_srcdir)/misc | |||||
libevloop_a_LIBADD=@EVLOOP_IMPL@ | |||||
libevloop_a_DEPENDENCIES=@EVLOOP_IMPL@ |
@@ -0,0 +1,118 @@ | |||||
#include <sys/epoll.h> | |||||
#include <errno.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include "evloop.h" | |||||
static int m_epfd; | |||||
static struct epoll_event m_evs[100]; | |||||
static uint8_t m_valid[100]; | |||||
int | |||||
evloop_init(void) | |||||
{ | |||||
if (timeheap_init() != 0) | |||||
return -1; | |||||
m_epfd = epoll_create(getdtablesize()); | |||||
return m_epfd >= 0 ? 0 : -1; | |||||
} | |||||
int | |||||
fdev_new(struct fdev *ev, int fd, uint16_t flags, evloop_cb_t cb, void *arg) | |||||
{ | |||||
ev->fd = fd; | |||||
ev->cb = cb; | |||||
ev->arg = arg; | |||||
ev->flags = 0; | |||||
ev->index = -1; | |||||
return fdev_enable(ev, flags); | |||||
} | |||||
int | |||||
fdev_enable(struct fdev *ev, uint16_t flags) | |||||
{ | |||||
struct epoll_event epev; | |||||
int err = 0; | |||||
uint16_t sf = ev->flags; | |||||
ev->flags |= flags; | |||||
if (sf != ev->flags) { | |||||
epev.data.ptr = ev; | |||||
epev.events = | |||||
((ev->flags & EV_READ) ? EPOLLIN : 0) | | |||||
((ev->flags & EV_WRITE) ? EPOLLOUT : 0); | |||||
if (sf == 0) | |||||
err = epoll_ctl(m_epfd, EPOLL_CTL_ADD, ev->fd, &epev); | |||||
else | |||||
err = epoll_ctl(m_epfd, EPOLL_CTL_MOD, ev->fd, &epev); | |||||
} | |||||
return err; | |||||
} | |||||
int | |||||
fdev_disable(struct fdev *ev, uint16_t flags) | |||||
{ | |||||
struct epoll_event epev; | |||||
int err = 0; | |||||
uint16_t sf = ev->flags; | |||||
ev->flags &= ~flags; | |||||
if (sf != ev->flags) { | |||||
epev.data.ptr = ev; | |||||
epev.events = | |||||
((ev->flags & EV_READ) ? EPOLLIN : 0) | | |||||
((ev->flags & EV_WRITE) ? EPOLLOUT : 0); | |||||
if (ev->flags == 0) | |||||
err = epoll_ctl(m_epfd, EPOLL_CTL_DEL, ev->fd, &epev); | |||||
else | |||||
err = epoll_ctl(m_epfd, EPOLL_CTL_MOD, ev->fd, &epev); | |||||
} | |||||
return err; | |||||
} | |||||
int | |||||
fdev_del(struct fdev *ev) | |||||
{ | |||||
if (ev->index >= 0) | |||||
m_valid[ev->index] = 0; | |||||
return fdev_disable(ev, EV_READ|EV_WRITE); | |||||
} | |||||
int | |||||
evloop(void) | |||||
{ | |||||
int nev, i, millisecs; | |||||
struct timespec delay; | |||||
while (1) { | |||||
timers_run(); | |||||
delay = timer_delay(); | |||||
if (delay.tv_sec >= 0) | |||||
millisecs = delay.tv_sec * 1000 + delay.tv_nsec / 1000000; | |||||
else | |||||
millisecs = -1; | |||||
if ((nev = epoll_wait(m_epfd, m_evs, 100, millisecs)) < 0) { | |||||
if (errno == EINTR) | |||||
continue; | |||||
else | |||||
return -1; | |||||
} | |||||
memset(m_valid, 1, nev); | |||||
for (i = 0; i < nev; i++) { | |||||
struct fdev *ev = m_evs[i].data.ptr; | |||||
ev->index = i; | |||||
} | |||||
for (i = 0; i < nev; i++) { | |||||
struct fdev *ev = m_evs[i].data.ptr; | |||||
if ((m_valid[i] && | |||||
ev->flags & EV_READ && | |||||
m_evs[i].events & (EPOLLIN|EPOLLERR|EPOLLHUP))) | |||||
ev->cb(ev->fd, EV_READ, ev->arg); | |||||
if ((m_valid[i] && ev->flags & EV_WRITE && | |||||
m_evs[i].events & (EPOLLOUT|EPOLLERR|EPOLLHUP))) | |||||
ev->cb(ev->fd, EV_WRITE, ev->arg); | |||||
if (m_valid[i]) | |||||
ev->index = -1; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,57 @@ | |||||
#ifndef BTPD_EVLOOP_H | |||||
#define BTPD_EVLOOP_H | |||||
#include <sys/time.h> | |||||
#include <stdint.h> | |||||
#include "timeheap.h" | |||||
#define EV_READ 1 | |||||
#define EV_WRITE 2 | |||||
#define EV_TIMEOUT 3 | |||||
typedef void (*evloop_cb_t)(int fd, short type, void *arg); | |||||
#if defined(EVLOOP_EPOLL) || defined(EVLOOP_KQUEUE) | |||||
struct fdev { | |||||
evloop_cb_t cb; | |||||
void *arg; | |||||
int fd; | |||||
uint16_t flags; | |||||
int16_t index; | |||||
}; | |||||
#elif defined(EVLOOP_POLL) | |||||
struct fdev { | |||||
int i; | |||||
}; | |||||
#else | |||||
#error No evloop method defined. | |||||
#endif | |||||
struct timeout { | |||||
evloop_cb_t cb; | |||||
void *arg; | |||||
struct th_handle th; | |||||
}; | |||||
int evloop_init(void); | |||||
int evloop(void); | |||||
int fdev_new(struct fdev *ev, int fd, uint16_t flags, evloop_cb_t cb, | |||||
void *arg); | |||||
int fdev_del(struct fdev *ev); | |||||
int fdev_enable(struct fdev *ev, uint16_t flags); | |||||
int fdev_disable(struct fdev *ev, uint16_t flags); | |||||
void timer_init(struct timeout *, evloop_cb_t, void *); | |||||
int timer_add(struct timeout *, struct timespec *); | |||||
void timer_del(struct timeout *); | |||||
void timers_run(void); | |||||
struct timespec timer_delay(void); | |||||
#endif |
@@ -0,0 +1,122 @@ | |||||
#include <sys/types.h> | |||||
#include <sys/event.h> | |||||
#include <sys/time.h> | |||||
#include <errno.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include "evloop.h" | |||||
static int m_kq; | |||||
static struct kevent m_evs[100]; | |||||
static uint8_t m_valid[100]; | |||||
int | |||||
evloop_init(void) | |||||
{ | |||||
if (timeheap_init() != 0) | |||||
return -1; | |||||
m_kq = kqueue(); | |||||
return m_kq >= 0 ? 0 : -1; | |||||
} | |||||
int | |||||
fdev_new(struct fdev *ev, int fd, uint16_t flags, evloop_cb_t cb, void *arg) | |||||
{ | |||||
ev->fd = fd; | |||||
ev->cb = cb; | |||||
ev->arg = arg; | |||||
ev->flags = 0; | |||||
ev->index = -1; | |||||
return fdev_enable(ev, flags); | |||||
} | |||||
int | |||||
fdev_enable(struct fdev *ev, uint16_t flags) | |||||
{ | |||||
struct kevent kev[2], *kp = NULL; | |||||
int count = 0; | |||||
uint16_t sf = ev->flags; | |||||
ev->flags |= flags; | |||||
if ((sf & EV_READ) == 0 && (flags & EV_READ) != 0) { | |||||
EV_SET(&kev[0], ev->fd, EVFILT_READ, EV_ADD, 0, 0, ev); | |||||
kp = kev; | |||||
count = 1; | |||||
} | |||||
if ((sf & EV_WRITE) == 0 && (flags & EV_WRITE) != 0) { | |||||
EV_SET(&kev[1], ev->fd, EVFILT_WRITE, EV_ADD, 0, 0, ev); | |||||
if (count == 0) | |||||
kp = &kev[1]; | |||||
count++; | |||||
} | |||||
return count > 0 ? kevent(m_kq, kp, count, NULL, 0, NULL) : 0; | |||||
} | |||||
int | |||||
fdev_disable(struct fdev *ev, uint16_t flags) | |||||
{ | |||||
struct kevent kev[2], *kp = NULL; | |||||
int count = 0; | |||||
uint16_t sf = ev->flags; | |||||
ev->flags &= ~flags; | |||||
if ((sf & EV_READ) != 0 && (flags & EV_READ) != 0) { | |||||
EV_SET(&kev[0], ev->fd, EVFILT_READ, EV_DELETE, 0, 0, ev); | |||||
kp = kev; | |||||
count = 1; | |||||
} | |||||
if ((sf & EV_WRITE) != 0 && (flags & EV_WRITE) != 0) { | |||||
EV_SET(&kev[1], ev->fd, EVFILT_WRITE, EV_DELETE, 0, 0, ev); | |||||
if (count == 0) | |||||
kp = &kev[1]; | |||||
count++; | |||||
} | |||||
return count > 0 ? kevent(m_kq, kp, count, NULL, 0, NULL) : 0; | |||||
} | |||||
int | |||||
fdev_del(struct fdev *ev) | |||||
{ | |||||
if (ev->index >= 0) | |||||
m_valid[ev->index] = 0; | |||||
return fdev_disable(ev, EV_READ|EV_WRITE); | |||||
} | |||||
int | |||||
evloop(void) | |||||
{ | |||||
int nev, i; | |||||
struct timespec delay; | |||||
while (1) { | |||||
timers_run(); | |||||
delay = timer_delay(); | |||||
if ((nev = kevent(m_kq, NULL, 0, m_evs, 100, &delay)) < 0) { | |||||
if (errno == EINTR) | |||||
continue; | |||||
else | |||||
return -1; | |||||
} | |||||
memset(m_valid, 1, nev); | |||||
for (i = 0; i < nev; i++) { | |||||
struct fdev *ev = (struct fdev *)m_evs[i].udata; | |||||
ev->index = i; | |||||
} | |||||
for (i = 0; i < nev; i++) { | |||||
if (m_evs[i].flags & EV_ERROR) { | |||||
errno = m_evs[i].data; | |||||
return -1; | |||||
} | |||||
struct fdev *ev = (struct fdev *)m_evs[i].udata; | |||||
if (m_valid[i] && ev->flags & EV_READ && | |||||
m_evs[i].filter == EVFILT_READ) | |||||
ev->cb(ev->fd, EV_READ, ev->arg); | |||||
if (m_valid[i] && ev->flags & EV_WRITE && | |||||
m_evs[i].filter == EVFILT_WRITE) | |||||
ev->cb(ev->fd, EV_WRITE, ev->arg); | |||||
if (m_valid[i]) | |||||
ev->index = -1; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,142 @@ | |||||
#include <assert.h> | |||||
#include <errno.h> | |||||
#include <poll.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include "evloop.h" | |||||
#define POLL_INIT_SIZE 64 | |||||
struct poll_ev { | |||||
struct fdev *ev; | |||||
evloop_cb_t cb; | |||||
void *arg; | |||||
}; | |||||
static struct pollfd *m_pfds; | |||||
static struct poll_ev *m_pevs; | |||||
static int m_cap, m_size; | |||||
static int m_cur = -1, m_curdel; | |||||
static int | |||||
poll_grow(void) | |||||
{ | |||||
int ncap = m_cap * 2; | |||||
struct pollfd *nm_pfds = realloc(m_pfds, ncap * sizeof(*m_pfds)); | |||||
struct poll_ev *nm_pevs = realloc(m_pevs, ncap * sizeof(*m_pevs)); | |||||
if (nm_pfds != NULL) | |||||
m_pfds = nm_pfds; | |||||
if (nm_pevs != NULL) | |||||
m_pevs = nm_pevs; | |||||
if (nm_pfds == NULL || nm_pevs == NULL) | |||||
return errno; | |||||
m_cap = ncap; | |||||
return 0; | |||||
} | |||||
int | |||||
evloop_init(void) | |||||
{ | |||||
if (timeheap_init() != 0) | |||||
return -1; | |||||
m_cap = POLL_INIT_SIZE; | |||||
m_size = 0; | |||||
if ((m_pfds = calloc(m_cap, sizeof(*m_pfds))) == NULL) | |||||
return -1; | |||||
if ((m_pevs = calloc(m_cap, sizeof(*m_pevs))) == NULL) { | |||||
free(m_pfds); | |||||
return -1; | |||||
} | |||||
return 0; | |||||
} | |||||
int | |||||
fdev_new(struct fdev *ev, int fd, uint16_t flags, evloop_cb_t cb, void *arg) | |||||
{ | |||||
if (m_size == m_cap && poll_grow() != 0) | |||||
return errno; | |||||
ev->i = m_size; | |||||
m_size++; | |||||
m_pfds[ev->i].fd = fd; | |||||
m_pfds[ev->i].events = | |||||
((flags & EV_READ) ? POLLIN : 0) | | |||||
((flags & EV_WRITE) ? POLLOUT : 0); | |||||
m_pevs[ev->i].ev = ev; | |||||
m_pevs[ev->i].cb = cb; | |||||
m_pevs[ev->i].arg = arg; | |||||
return 0; | |||||
} | |||||
int | |||||
fdev_enable(struct fdev *ev, uint16_t flags) | |||||
{ | |||||
m_pfds[ev->i].events |= | |||||
((flags & EV_READ) ? POLLIN : 0) | | |||||
((flags & EV_WRITE) ? POLLOUT : 0); | |||||
return 0; | |||||
} | |||||
int | |||||
fdev_disable(struct fdev *ev, uint16_t flags) | |||||
{ | |||||
short pflags = | |||||
((flags & EV_READ) ? POLLIN : 0) | | |||||
((flags & EV_WRITE) ? POLLOUT : 0); | |||||
m_pfds[ev->i].events &= ~pflags; | |||||
return 0; | |||||
} | |||||
int | |||||
fdev_del(struct fdev *ev) | |||||
{ | |||||
assert(ev->i < m_size); | |||||
m_size--; | |||||
m_pfds[ev->i] = m_pfds[m_size]; | |||||
m_pevs[ev->i] = m_pevs[m_size]; | |||||
m_pevs[ev->i].ev->i = ev->i; | |||||
if (ev->i == m_cur) | |||||
m_curdel = 1; | |||||
return 0; | |||||
} | |||||
int | |||||
evloop(void) | |||||
{ | |||||
int millisecs; | |||||
struct timespec delay; | |||||
while (1) { | |||||
timers_run(); | |||||
delay = timer_delay(); | |||||
if (delay.tv_sec >= 0) | |||||
millisecs = delay.tv_sec * 1000 + delay.tv_nsec / 1000000; | |||||
else | |||||
millisecs = -1; | |||||
if (poll(m_pfds, m_size, millisecs) < 0) { | |||||
if (errno == EINTR) | |||||
continue; | |||||
else | |||||
return -1; | |||||
} | |||||
m_cur = 0; | |||||
while (m_cur < m_size) { | |||||
struct pollfd *pfd = &m_pfds[m_cur]; | |||||
struct poll_ev *pev = &m_pevs[m_cur]; | |||||
if ((pfd->events & POLLIN && | |||||
pfd->revents & (POLLIN|POLLERR|POLLHUP))) | |||||
pev->cb(pfd->fd, EV_READ, pev->arg); | |||||
if ((!m_curdel && pfd->events & POLLOUT && | |||||
pfd->revents & (POLLOUT|POLLERR|POLLHUP))) | |||||
pev->cb(pfd->fd, EV_WRITE, pev->arg); | |||||
if (!m_curdel) | |||||
m_cur++; | |||||
else | |||||
m_curdel = 0; | |||||
} | |||||
m_cur = -1; | |||||
} | |||||
} |
@@ -0,0 +1,152 @@ | |||||
#include <sys/time.h> | |||||
#include <assert.h> | |||||
#include <stdlib.h> | |||||
#include "timeheap.h" | |||||
struct th_entry { | |||||
struct timespec t; | |||||
struct th_handle *h; | |||||
}; | |||||
static struct th_entry *heap; | |||||
static int heap_cap; | |||||
static int heap_use; | |||||
static int | |||||
cmptime_lt(struct timespec a, struct timespec b) | |||||
{ | |||||
if (a.tv_sec == b.tv_sec) | |||||
return a.tv_nsec < b.tv_nsec; | |||||
else | |||||
return a.tv_sec < b.tv_sec; | |||||
} | |||||
static int | |||||
cmpentry_lt(int a, int b) | |||||
{ | |||||
return cmptime_lt(heap[a].t, heap[b].t); | |||||
} | |||||
static void | |||||
swap(int i, int j) | |||||
{ | |||||
struct th_entry tmp = heap[i]; | |||||
heap[i] = heap[j]; | |||||
heap[i].h->i = i; | |||||
heap[j] = tmp; | |||||
heap[j].h->i = j; | |||||
} | |||||
static void | |||||
bubble_up(int i) | |||||
{ | |||||
while (i != 0) { | |||||
int p = (i-1)/2; | |||||
if (cmpentry_lt(i, p)) { | |||||
swap(i, p); | |||||
i = p; | |||||
} else | |||||
return; | |||||
} | |||||
} | |||||
static void | |||||
bubble_down(int i) | |||||
{ | |||||
int li, ri, ci; | |||||
loop: | |||||
li = 2*i+1; | |||||
ri = 2*i+2; | |||||
if (ri < heap_use) | |||||
ci = cmpentry_lt(li, ri) ? li : ri; | |||||
else if (li < heap_use) | |||||
ci = li; | |||||
else | |||||
return; | |||||
if (cmpentry_lt(ci, i)) { | |||||
swap(i, ci); | |||||
i = ci; | |||||
goto loop; | |||||
} | |||||
} | |||||
int | |||||
timeheap_init(void) | |||||
{ | |||||
heap_cap = 10; | |||||
heap_use = 0; | |||||
if ((heap = malloc(sizeof(struct th_entry) * heap_cap)) == NULL) | |||||
return -1; | |||||
else | |||||
return 0; | |||||
} | |||||
int | |||||
timeheap_size(void) | |||||
{ | |||||
return heap_use; | |||||
} | |||||
int | |||||
timeheap_insert(struct th_handle *h, struct timespec *t) | |||||
{ | |||||
if (heap_use == heap_cap) { | |||||
int ncap = heap_cap * 2; | |||||
struct th_entry *nheap = realloc(heap, ncap * sizeof(struct th_entry)); | |||||
if (nheap == NULL) | |||||
return -1; | |||||
heap_cap = ncap; | |||||
heap = nheap; | |||||
} | |||||
heap[heap_use].t = *t; | |||||
heap[heap_use].h = h; | |||||
h->i = heap_use; | |||||
heap_use++; | |||||
bubble_up(h->i); | |||||
return 0; | |||||
} | |||||
void | |||||
timeheap_remove(struct th_handle *h) | |||||
{ | |||||
assert(h->i >= 0 && h->i < heap_use); | |||||
heap_use--; | |||||
if (heap_use > 0) { | |||||
int i = h->i; | |||||
int earlier = cmpentry_lt(heap_use, i); | |||||
heap[i] = heap[heap_use]; | |||||
heap[i].h->i = i; | |||||
if (earlier) | |||||
bubble_up(i); | |||||
else | |||||
bubble_down(i); | |||||
} | |||||
} | |||||
void | |||||
timeheap_change(struct th_handle *h, struct timespec *t) | |||||
{ | |||||
assert(h->i >= 0 && h->i < heap_use); | |||||
int earlier = cmptime_lt(*t, heap[h->i].t); | |||||
heap[h->i].t = *t; | |||||
if (earlier) | |||||
bubble_up(h->i); | |||||
else | |||||
bubble_down(h->i); | |||||
} | |||||
struct timespec | |||||
timeheap_top(void) | |||||
{ | |||||
return heap[0].t; | |||||
} | |||||
void * | |||||
timeheap_remove_top(void) | |||||
{ | |||||
void *ret = heap[0].h->data; | |||||
struct th_handle h = { 0, NULL }; | |||||
timeheap_remove(&h); | |||||
return ret; | |||||
} |
@@ -0,0 +1,19 @@ | |||||
#ifndef BTPD_TIMEHEAP_H | |||||
#define BTPD_TIMEHEAP_H | |||||
struct th_handle { | |||||
int i; | |||||
void *data; | |||||
}; | |||||
int timeheap_init(void); | |||||
int timeheap_size(void); | |||||
int timeheap_insert(struct th_handle *h, struct timespec *t); | |||||
void timeheap_remove(struct th_handle *h); | |||||
void timeheap_change(struct th_handle *h, struct timespec *t); | |||||
void *timeheap_remove_top(void); | |||||
struct timespec timeheap_top(void); | |||||
#endif |
@@ -0,0 +1,104 @@ | |||||
#include <time.h> | |||||
#include "evloop.h" | |||||
#include "timeheap.h" | |||||
#if defined(CLOCK_MONOTONIC_FAST) | |||||
#define TIMER_CLOCK CLOCK_MONOTONIC_FAST | |||||
#elif defined(CLOCK_MONOTONIC) | |||||
#define TIMER_CLOCK CLOCK_MONOTONIC | |||||
#else | |||||
#error CLOCK_MONOTONIC needed! | |||||
#endif | |||||
static struct timespec | |||||
addtime(struct timespec a, struct timespec b) | |||||
{ | |||||
struct timespec ret; | |||||
ret.tv_sec = a.tv_sec + b.tv_sec; | |||||
ret.tv_nsec = a.tv_nsec + b.tv_nsec; | |||||
if (ret.tv_nsec >= 1000000000) { | |||||
ret.tv_sec += 1; | |||||
ret.tv_nsec -= 1000000000; | |||||
} | |||||
return ret; | |||||
} | |||||
static struct timespec | |||||
subtime(struct timespec a, struct timespec b) | |||||
{ | |||||
struct timespec ret; | |||||
ret.tv_sec = a.tv_sec - b.tv_sec; | |||||
ret.tv_nsec = a.tv_nsec - b.tv_nsec; | |||||
if (ret.tv_nsec < 0) { | |||||
ret.tv_sec -= 1; | |||||
ret.tv_nsec += 1000000000; | |||||
} | |||||
return ret; | |||||
} | |||||
void | |||||
timer_init(struct timeout *h, evloop_cb_t cb, void *arg) | |||||
{ | |||||
h->cb = cb; | |||||
h->arg = arg; | |||||
h->th.i = -1; | |||||
h->th.data = h; | |||||
} | |||||
int | |||||
timer_add(struct timeout *h, struct timespec *t) | |||||
{ | |||||
struct timespec now, sum; | |||||
clock_gettime(TIMER_CLOCK, &now); | |||||
sum = addtime(now, *t); | |||||
if (h->th.i == -1) | |||||
return timeheap_insert(&h->th, &sum); | |||||
else { | |||||
timeheap_change(&h->th, &sum); | |||||
return 0; | |||||
} | |||||
} | |||||
void | |||||
timer_del(struct timeout *h) | |||||
{ | |||||
if (h->th.i >= 0) { | |||||
timeheap_remove(&h->th); | |||||
h->th.i = -1; | |||||
} | |||||
} | |||||
void | |||||
timers_run(void) | |||||
{ | |||||
struct timespec now; | |||||
clock_gettime(TIMER_CLOCK, &now); | |||||
while (timeheap_size() > 0) { | |||||
struct timespec diff = subtime(timeheap_top(), now); | |||||
if (diff.tv_sec < 0) { | |||||
struct timeout *t = timeheap_remove_top(); | |||||
t->th.i = -1; | |||||
t->cb(-1, EV_TIMEOUT, t->arg); | |||||
} else | |||||
break; | |||||
} | |||||
} | |||||
struct timespec | |||||
timer_delay(void) | |||||
{ | |||||
struct timespec now, diff; | |||||
if (timeheap_size() == 0) { | |||||
diff.tv_sec = -1; | |||||
diff.tv_nsec = 0; | |||||
} else { | |||||
clock_gettime(TIMER_CLOCK, &now); | |||||
diff = subtime(timeheap_top(), now); | |||||
if (diff.tv_sec < 0) { | |||||
diff.tv_sec = 0; | |||||
diff.tv_nsec = 0; | |||||
} | |||||
} | |||||
return diff; | |||||
} |