diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in index 8481851c0..374272f4c 100644 --- a/winsup/cygwin/Makefile.in +++ b/winsup/cygwin/Makefile.in @@ -396,6 +396,7 @@ DLL_OFILES:= \ termios.o \ thread.o \ timer.o \ + timerfd.o \ times.o \ tls_pbuf.o \ tty.o \ diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index 497612ae1..97804ed30 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -2682,6 +2682,7 @@ class fhandler_timerfd : public fhandler_base public: fhandler_timerfd (); fhandler_timerfd (void *) {} + ~fhandler_timerfd (); fhandler_timerfd *is_timerfd () { return this; } @@ -2701,6 +2702,7 @@ class fhandler_timerfd : public fhandler_base HANDLE get_timerfd_handle (); + void fixup_after_fork (HANDLE); void fixup_after_exec (); select_record *select_read (select_stuff *); diff --git a/winsup/cygwin/fhandler_timerfd.cc b/winsup/cygwin/fhandler_timerfd.cc index c7ff9c162..8d3085692 100644 --- a/winsup/cygwin/fhandler_timerfd.cc +++ b/winsup/cygwin/fhandler_timerfd.cc @@ -1,4 +1,4 @@ -/* fhandler_timerfd.cc: fhandler for timerfd +/* fhandler_timerfd.cc: fhandler for timerfd, public timerfd API This file is part of Cygwin. @@ -12,7 +12,7 @@ details. */ #include "pinfo.h" #include "dtable.h" #include "cygheap.h" -#include "timer.h" +#include "timerfd.h" #include #include @@ -39,7 +39,19 @@ fhandler_timerfd::get_proc_fd_name (char *buf) int fhandler_timerfd::timerfd (clockid_t clock_id, int flags) { - timerid = (timer_t) cnew (timer_tracker, clock_id, NULL, true); + timerfd_tracker *tfd = cnew (timerfd_tracker); + if (!tfd) + { + set_errno (ENOMEM); + return -1; + } + int ret = tfd->create (clock_id); + if (ret < 0) + { + cfree (tfd); + set_errno (-ret); + return -1; + } if (flags & TFD_NONBLOCK) set_nonblocking (true); if (flags & TFD_CLOEXEC) @@ -48,19 +60,25 @@ fhandler_timerfd::timerfd (clockid_t clock_id, int flags) set_unique_id (); set_ino (get_unique_id ()); set_flags (O_RDWR | O_BINARY); + timerid = (timer_t) tfd; return 0; } int -fhandler_timerfd::settime (int flags, const itimerspec *value, - itimerspec *ovalue) +fhandler_timerfd::settime (int flags, const struct itimerspec *new_value, + struct itimerspec *old_value) { int ret = -1; __try { - timer_tracker *tt = (timer_tracker *) timerid; - ret = tt->settime (flags, value, ovalue); + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + ret = tfd->settime (flags, new_value, old_value); + if (ret < 0) + { + set_errno (-ret); + ret = -1; + } } __except (EFAULT) {} __endtry @@ -68,15 +86,19 @@ fhandler_timerfd::settime (int flags, const itimerspec *value, } int -fhandler_timerfd::gettime (itimerspec *ovalue) +fhandler_timerfd::gettime (struct itimerspec *ovalue) { int ret = -1; __try { - timer_tracker *tt = (timer_tracker *) timerid; - tt->gettime (ovalue); - ret = 0; + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + ret = tfd->gettime (ovalue); + if (ret < 0) + { + set_errno (-ret); + ret = -1; + } } __except (EFAULT) {} __endtry @@ -108,12 +130,14 @@ fhandler_timerfd::read (void *ptr, size_t& len) __try { - timer_tracker *tt = (timer_tracker *) timerid; - LONG64 ret = tt->wait (is_nonblocking ()); - if (ret == -1) - __leave; - PLONG64 pl64 = (PLONG64) ptr; - *pl64 = ret + 1; + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + LONG64 ret = tfd->wait (is_nonblocking ()); + if (ret < 0) + { + set_errno (-ret); + __leave; + } + *(PLONG64) ptr = ret; len = sizeof (LONG64); return; } @@ -135,8 +159,8 @@ fhandler_timerfd::get_timerfd_handle () { __try { - timer_tracker *tt = (timer_tracker *) timerid; - return tt->get_timerfd_handle (); + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + return tfd->get_timerfd_handle (); } __except (EFAULT) {} __endtry @@ -153,8 +177,8 @@ fhandler_timerfd::dup (fhandler_base *child, int flags) fhandler_timerfd *fhc = (fhandler_timerfd *) child; __try { - timer_tracker *tt = (timer_tracker *) fhc->timerid; - tt->increment_instances (); + timerfd_tracker *tfd = (timerfd_tracker *) fhc->timerid; + tfd->increment_instances (); ret = 0; } __except (EFAULT) {} @@ -164,14 +188,27 @@ fhandler_timerfd::dup (fhandler_base *child, int flags) } void -fhandler_timerfd::fixup_after_exec () +fhandler_timerfd::fixup_after_fork (HANDLE) { - if (close_on_exec ()) - return; __try { - timer_tracker *tt = (timer_tracker *) timerid; - tt->fixup_after_exec (); + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + tfd->fixup_after_fork (); + } + __except (EFAULT) {} + __endtry +} + +void +fhandler_timerfd::fixup_after_exec () +{ + __try + { + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + if (close_on_exec ()) + tfd->decrement_instances (); + else + tfd->fixup_after_exec (); } __except (EFAULT) {} __endtry @@ -188,7 +225,7 @@ fhandler_timerfd::ioctl (unsigned int cmd, void *p) case TFD_IOC_SET_TICKS: __try { - timer_tracker *tt = (timer_tracker *) timerid; + timerfd_tracker *tfd = (timerfd_tracker *) timerid; ov_cnt = *(uint64_t *) p; if (!ov_cnt) @@ -196,11 +233,11 @@ fhandler_timerfd::ioctl (unsigned int cmd, void *p) set_errno (EINVAL); break; } - tt->set_event (ov_cnt); + tfd->ioctl_set_ticks (ov_cnt); + ret = 0; } __except (EFAULT) {} __endtry - ret = 0; break; default: ret = fhandler_base::ioctl (cmd, p); @@ -210,6 +247,17 @@ fhandler_timerfd::ioctl (unsigned int cmd, void *p) return ret; } +fhandler_timerfd::~fhandler_timerfd () +{ + __try + { + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + timerfd_tracker::dtor (tfd); + } + __except (EFAULT) {} + __endtry +} + int fhandler_timerfd::close () { @@ -217,11 +265,94 @@ fhandler_timerfd::close () __try { - timer_tracker *tt = (timer_tracker *) timerid; - timer_tracker::close (tt); + timerfd_tracker *tfd = (timerfd_tracker *) timerid; + tfd->close (); ret = 0; } __except (EFAULT) {} __endtry return ret; } + +extern "C" int +timerfd_create (clockid_t clock_id, int flags) +{ + int ret = -1; + fhandler_timerfd *fh; + + debug_printf ("timerfd_create (%lu, %y)", clock_id, flags); + + if (clock_id != CLOCK_REALTIME + && clock_id != CLOCK_MONOTONIC + && clock_id != CLOCK_BOOTTIME) + { + set_errno (EINVAL); + goto done; + } + if ((flags & ~(TFD_NONBLOCK | TFD_CLOEXEC)) != 0) + { + set_errno (EINVAL); + goto done; + } + + { + /* Create new timerfd descriptor. */ + cygheap_fdnew fd; + + if (fd < 0) + goto done; + fh = (fhandler_timerfd *) build_fh_dev (*timerfd_dev); + if (fh && fh->timerfd (clock_id, flags) == 0) + { + fd = fh; + if (fd <= 2) + set_std_handle (fd); + ret = fd; + } + else + delete fh; + } + +done: + syscall_printf ("%R = timerfd_create (%lu, %y)", ret, clock_id, flags); + return ret; +} + +extern "C" int +timerfd_settime (int fd_in, int flags, const struct itimerspec *value, + struct itimerspec *ovalue) +{ + /* TODO: There's no easy way to implement TFD_TIMER_CANCEL_ON_SET, + but we should at least accept the flag. */ + if ((flags & ~(TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET)) != 0) + { + set_errno (EINVAL); + return -1; + } + + cygheap_fdget fd (fd_in); + if (fd < 0) + return -1; + fhandler_timerfd *fh = fd->is_timerfd (); + if (!fh) + { + set_errno (EINVAL); + return -1; + } + return fh->settime (flags, value, ovalue); +} + +extern "C" int +timerfd_gettime (int fd_in, struct itimerspec *ovalue) +{ + cygheap_fdget fd (fd_in); + if (fd < 0) + return -1; + fhandler_timerfd *fh = fd->is_timerfd (); + if (!fh) + { + set_errno (EINVAL); + return -1; + } + return fh->gettime (ovalue); +} diff --git a/winsup/cygwin/timer.cc b/winsup/cygwin/timer.cc index e817dab81..e24edd5e9 100644 --- a/winsup/cygwin/timer.cc +++ b/winsup/cygwin/timer.cc @@ -15,15 +15,14 @@ details. */ #include "dtable.h" #include "cygheap.h" #include "timer.h" -#include #include #define EVENT_DISARMED 0 #define EVENT_ARMED -1 #define EVENT_LOCK 1 -/* Must not be NO_COPY, otherwise timerfd breaks. */ -timer_tracker ttstart (CLOCK_REALTIME, NULL, false); +/* Must not be NO_COPY to avoid memory leak after fork. */ +timer_tracker ttstart (CLOCK_REALTIME, NULL); class lock_timer_tracker { @@ -60,46 +59,34 @@ timer_tracker::cancel () timer_tracker::~timer_tracker () { - HANDLE hdl; - - deleting = true; if (cancel ()) { - HANDLE hdl = InterlockedExchangePointer (&hcancel, NULL); - CloseHandle (hdl); - hdl = InterlockedExchangePointer (&timerfd_event, NULL); - if (hdl) - CloseHandle (hdl); + CloseHandle (hcancel); +#ifdef DEBUGGING + hcancel = NULL; +#endif } - hdl = InterlockedExchangePointer (&syncthread, NULL); - if (hdl) - CloseHandle (hdl); + if (syncthread) + CloseHandle (syncthread); magic = 0; } -/* fd is true for timerfd timers. */ -timer_tracker::timer_tracker (clockid_t c, const sigevent *e, bool fd) -: magic (TT_MAGIC), instance_count (1), clock_id (c), deleting (false), - hcancel (NULL), syncthread (NULL), timerfd_event (NULL), - interval_us(0), sleepto_us(0), event_running (EVENT_DISARMED), - overrun_count_curr (0), overrun_count (0) +timer_tracker::timer_tracker (clockid_t c, const sigevent *e) { if (e != NULL) evp = *e; - else if (fd) - { - evp.sigev_notify = SIGEV_NONE; - evp.sigev_signo = 0; - evp.sigev_value.sival_ptr = this; - } else { evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGALRM; evp.sigev_value.sival_ptr = this; } - if (fd) - timerfd_event = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); + clock_id = c; + magic = TT_MAGIC; + hcancel = NULL; + event_running = EVENT_DISARMED; + overrun_count_curr = 0; + overrun_count = 0; if (this != &ttstart) { lock_timer_tracker here; @@ -132,24 +119,8 @@ timer_tracker::arm_event () return ret; } -void -timer_tracker::set_event (uint64_t ov_cnt) -{ - LONG ret; - - while ((ret = InterlockedCompareExchange (&event_running, EVENT_LOCK, - EVENT_DISARMED)) == EVENT_LOCK) - yield (); - InterlockedExchange64 (&overrun_count, ov_cnt); - if (ret == EVENT_DISARMED) - { - SetEvent (get_timerfd_handle ()); - InterlockedExchange (&event_running, EVENT_ARMED); - } -} - -LONG64 -timer_tracker::_disarm_event () +LONG +timer_tracker::disarm_event () { LONG ret; @@ -158,7 +129,13 @@ timer_tracker::_disarm_event () yield (); if (ret == EVENT_ARMED) { - InterlockedExchange64 (&overrun_count_curr, overrun_count); + LONG64 ov_cnt; + + InterlockedExchange64 (&ov_cnt, overrun_count); + if (ov_cnt > DELAYTIMER_MAX || ov_cnt < 0) + overrun_count_curr = DELAYTIMER_MAX; + else + overrun_count_curr = ov_cnt; ret = overrun_count_curr; InterlockedExchange64 (&overrun_count, 0); InterlockedExchange (&event_running, EVENT_DISARMED); @@ -166,51 +143,6 @@ timer_tracker::_disarm_event () return ret; } -unsigned int -timer_tracker::disarm_event () -{ - LONG64 ov = _disarm_event (); - if (ov > DELAYTIMER_MAX || ov < 0) - return DELAYTIMER_MAX; - return (unsigned int) ov; -} - -LONG64 -timer_tracker::wait (bool nonblocking) -{ - HANDLE w4[3] = { NULL, hcancel, get_timerfd_handle () }; - LONG64 ret = -1; - - wait_signal_arrived here (w4[0]); -repeat: - switch (WaitForMultipleObjects (3, w4, FALSE, nonblocking ? 0 : INFINITE)) - { - case WAIT_OBJECT_0: /* signal */ - if (_my_tls.call_signal_handler ()) - goto repeat; - set_errno (EINTR); - break; - case WAIT_OBJECT_0 + 1: /* settime oder timer delete */ - if (deleting) - { - set_errno (EIO); - break; - } - /*FALLTHRU*/ - case WAIT_OBJECT_0 + 2: /* timer event */ - ret = _disarm_event (); - ResetEvent (timerfd_event); - break; - case WAIT_TIMEOUT: - set_errno (EAGAIN); - break; - default: - __seterrno (); - break; - } - return ret; -} - static void * notify_thread_wrapper (void *arg) { @@ -267,18 +199,6 @@ timer_tracker::thread_func () switch (evp.sigev_notify) { - case SIGEV_NONE: - { - if (!timerfd_event) - break; - if (arm_event ()) - { - debug_printf ("%p timerfd already queued", this); - break; - } - SetEvent (timerfd_event); - break; - } case SIGEV_SIGNAL: { if (arm_event ()) @@ -342,6 +262,17 @@ timer_thread (VOID *x) return tt->thread_func (); } +static inline bool +timespec_bad (const timespec& t) +{ + if (t.tv_nsec < 0 || t.tv_nsec >= NSPERSEC || t.tv_sec < 0) + { + set_errno (EINVAL); + return true; + } + return false; +} + int timer_tracker::settime (int in_flags, const itimerspec *value, itimerspec *ovalue) { @@ -355,12 +286,8 @@ timer_tracker::settime (int in_flags, const itimerspec *value, itimerspec *ovalu __leave; } - if (!valid_timespec (value->it_value) - || !valid_timespec (value->it_interval)) - { - set_errno (EINVAL); - __leave; - } + if (timespec_bad (value->it_value) || timespec_bad (value->it_interval)) + __leave; lock_timer_tracker here; cancel (); @@ -411,17 +338,9 @@ timer_tracker::gettime (itimerspec *ovalue) } } -/* Returns - - 1 if we still have to keep the timer around - 0 if we can delete the timer - -1 if we can't find the timer in the list -*/ int timer_tracker::clean_and_unhook () { - if (decrement_instances () > 0) - return 1; for (timer_tracker *tt = &ttstart; tt->next != NULL; tt = tt->next) if (tt->next == this) { @@ -431,74 +350,19 @@ timer_tracker::clean_and_unhook () return -1; } -int -timer_tracker::close (timer_tracker *tt) -{ - lock_timer_tracker here; - int ret = tt->clean_and_unhook (); - if (ret >= 0) - { - if (ret == 0) - delete tt; - ret = 0; - } - else - set_errno (EINVAL); - return ret; -} - -void -timer_tracker::restart () -{ - timerfd_event = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); - if (interval_us != 0 || sleepto_us != 0) - { - hcancel = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); - syncthread = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); - new cygthread (timer_thread, this, "itimer", syncthread); - } -} - -/* Only called from fhandler_timerfd::fixup_after_exec. Note that - we don't touch the instance count. This is handled by closing - the timer from fhandler_timerfd::close on O_CLOEXEC. Ultimately - the instance count should be correct after execve. */ -void -timer_tracker::fixup_after_exec () -{ - lock_timer_tracker here; - /* Check if timer is already in the list. If so, skip it. */ - for (timer_tracker *tt = &ttstart; tt->next != NULL; tt = tt->next) - if (tt->next == this) - return; - next = ttstart.next; - ttstart.next = this; - restart (); -} - void timer_tracker::fixup_after_fork () { ttstart.hcancel = ttstart.syncthread = NULL; - ttstart.interval_us = ttstart.sleepto_us = 0; ttstart.event_running = EVENT_DISARMED; - ttstart.overrun_count_curr = ttstart.overrun_count = 0; + ttstart.overrun_count_curr = 0; + ttstart.overrun_count = 0; for (timer_tracker *tt = &ttstart; tt->next != NULL; /* nothing */) { timer_tracker *deleteme = tt->next; - if (deleteme->get_timerfd_handle ()) - { - tt = deleteme; - tt->restart (); - } - else - { - tt->next = deleteme->next; - deleteme->timerfd_event = NULL; - deleteme->hcancel = NULL; - deleteme->syncthread = NULL; - delete deleteme; - } + tt->next = deleteme->next; + deleteme->hcancel = deleteme->syncthread = NULL; + delete deleteme; } } @@ -536,21 +400,21 @@ timer_create (clockid_t clock_id, struct sigevent *__restrict evp, { int ret = -1; - if (CLOCKID_IS_PROCESS (clock_id) || CLOCKID_IS_THREAD (clock_id)) - { - set_errno (ENOTSUP); - return -1; - } - - if (clock_id >= MAX_CLOCKS) - { - set_errno (EINVAL); - return -1; - } - __try { - *timerid = (timer_t) new timer_tracker (clock_id, evp, false); + if (CLOCKID_IS_PROCESS (clock_id) || CLOCKID_IS_THREAD (clock_id)) + { + set_errno (ENOTSUP); + return -1; + } + + if (clock_id >= MAX_CLOCKS) + { + set_errno (EINVAL); + return -1; + } + + *timerid = (timer_t) new timer_tracker (clock_id, evp); ret = 0; } __except (EFAULT) {} @@ -617,7 +481,15 @@ timer_delete (timer_t timerid) set_errno (EINVAL); __leave; } - ret = timer_tracker::close (in_tt); + + lock_timer_tracker here; + if (in_tt->clean_and_unhook () == 0) + { + delete in_tt; + ret = 0; + } + else + set_errno (EINVAL); } __except (EFAULT) {} __endtry @@ -724,86 +596,3 @@ ualarm (useconds_t value, useconds_t interval) syscall_printf ("%d = ualarm(%ld , %ld)", ret, value, interval); return ret; } - -extern "C" int -timerfd_create (clockid_t clock_id, int flags) -{ - int ret = -1; - fhandler_timerfd *fh; - - debug_printf ("timerfd (%lu, %y)", clock_id, flags); - - if (clock_id != CLOCK_REALTIME - && clock_id != CLOCK_MONOTONIC - && clock_id != CLOCK_BOOTTIME) - { - set_errno (EINVAL); - return -1; - } - if ((flags & ~(TFD_NONBLOCK | TFD_CLOEXEC)) != 0) - { - set_errno (EINVAL); - goto done; - } - - { - /* Create new timerfd descriptor. */ - cygheap_fdnew fd; - - if (fd < 0) - goto done; - fh = (fhandler_timerfd *) build_fh_dev (*timerfd_dev); - if (fh && fh->timerfd (clock_id, flags) == 0) - { - fd = fh; - if (fd <= 2) - set_std_handle (fd); - ret = fd; - } - else - delete fh; - } - -done: - syscall_printf ("%R = timerfd (%lu, %y)", ret, clock_id, flags); - return ret; -} - -extern "C" int -timerfd_settime (int fd_in, int flags, const struct itimerspec *value, - struct itimerspec *ovalue) -{ - /* There's no easy way to implement TFD_TIMER_CANCEL_ON_SET, but - we should at least accept the flag. */ - if ((flags & ~(TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET)) != 0) - { - set_errno (EINVAL); - return -1; - } - - cygheap_fdget fd (fd_in); - if (fd < 0) - return -1; - fhandler_timerfd *fh = fd->is_timerfd (); - if (!fh) - { - set_errno (EINVAL); - return -1; - } - return fh->settime (flags, value, ovalue); -} - -extern "C" int -timerfd_gettime (int fd_in, struct itimerspec *ovalue) -{ - cygheap_fdget fd (fd_in); - if (fd < 0) - return -1; - fhandler_timerfd *fh = fd->is_timerfd (); - if (!fh) - { - set_errno (EINVAL); - return -1; - } - return fh->gettime (ovalue); -} diff --git a/winsup/cygwin/timer.h b/winsup/cygwin/timer.h index b9a072e6d..dd5b165c7 100644 --- a/winsup/cygwin/timer.h +++ b/winsup/cygwin/timer.h @@ -14,56 +14,35 @@ class timer_tracker { unsigned magic; timer_tracker *next; - LONG instance_count; clockid_t clock_id; sigevent evp; timespec it_interval; - bool deleting; HANDLE hcancel; HANDLE syncthread; - HANDLE timerfd_event; int64_t interval_us; int64_t sleepto_us; LONG event_running; - LONG64 overrun_count_curr; + LONG overrun_count_curr; LONG64 overrun_count; bool cancel (); - LONG decrement_instances () { return InterlockedDecrement (&instance_count); } - int clean_and_unhook (); - LONG64 _disarm_event (); - void restart (); public: - void *operator new (size_t size) __attribute__ ((nothrow)) - { return malloc (size); } - void *operator new (size_t, void *p) __attribute__ ((nothrow)) {return p;} - void operator delete(void *p) { incygheap (p) ? cfree (p) : free (p); } - - timer_tracker (clockid_t, const sigevent *, bool); + timer_tracker (clockid_t, const sigevent *); ~timer_tracker (); inline bool is_timer_tracker () const { return magic == TT_MAGIC; } - - - void increment_instances () { InterlockedIncrement (&instance_count); } - LONG64 wait (bool nonblocking); - HANDLE get_timerfd_handle () const { return timerfd_event; } - inline sigevent_t *sigevt () { return &evp; } - inline LONG64 getoverrun () const { return overrun_count_curr; } + inline int getoverrun () const { return overrun_count_curr; } void gettime (itimerspec *); int settime (int, const itimerspec *, itimerspec *); - + int clean_and_unhook (); LONG arm_event (); - void set_event (uint64_t ov_cnt); - unsigned int disarm_event (); + LONG disarm_event (); DWORD thread_func (); - void fixup_after_exec (); static void fixup_after_fork (); - static int close (timer_tracker *tt); }; extern void fixup_timers_after_fork (); diff --git a/winsup/cygwin/timerfd.cc b/winsup/cygwin/timerfd.cc new file mode 100644 index 000000000..145f3a665 --- /dev/null +++ b/winsup/cygwin/timerfd.cc @@ -0,0 +1,515 @@ +/* timerfd.cc: timerfd helper classes + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include "path.h" +#include "fhandler.h" +#include "pinfo.h" +#include "dtable.h" +#include "cygheap.h" +#include "cygerrno.h" +#include +#include "timerfd.h" + +DWORD +timerfd_tracker::thread_func () +{ + /* Outer loop: Is the timer armed? If not, wait for it. */ + HANDLE armed[2] = { tfd_shared->arm_evt (), + cancel_evt }; + + while (1) + { + switch (WaitForMultipleObjects (2, armed, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + goto canceled; + default: + continue; + } + + /* Inner loop: Timer expired? If not, wait for it. */ + HANDLE expired[3] = { tfd_shared->timer (), + tfd_shared->disarm_evt (), + cancel_evt }; + while (1) + { + switch (WaitForMultipleObjects (3, expired, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + goto disarmed; + case WAIT_OBJECT_0 + 2: + goto canceled; + default: + continue; + } + + if (!enter_critical_section ()) + continue; + /* One-shot timer? */ + if (!get_interval ()) + { + /* Set overrun count, disarm timer */ + increment_overrun_count (1); + disarm_timer (); + } + else + { + /* Compute overrun count. */ + LONG64 now = get_clock_now (); + LONG64 ts = get_exp_ts (); + + increment_overrun_count ((now - ts + get_interval () - 1) + / get_interval ()); + /* Set exp_ts to current timestamp. Make sure exp_ts ends up + bigger than "now" and fix overrun count as required */ + while ((ts += get_interval ()) <= (now = get_clock_now ())) + increment_overrun_count ((now - ts + get_interval () - 1) + / get_interval ()); + set_exp_ts (ts); + /* NtSetTimer allows periods of up to 24 days only. If the time + is longer, we set the timer up as one-shot timer for each + interval. Restart timer here with new due time. */ + if (get_interval () > INT_MAX * (NS100PERSEC / MSPERSEC)) + { + /* TODO: CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM + See comment in arm_timer */ + BOOL Resume = FALSE; + LARGE_INTEGER DueTime = { QuadPart: -get_interval () }; + + NtSetTimer (tfd_shared->timer (), &DueTime, NULL, NULL, + Resume, 0, NULL); + } + } + /* Arm the expiry object */ + timer_expired (); + leave_critical_section (); + } +disarmed: + ; + } + +canceled: + _my_tls._ctinfo->auto_release (); /* automatically return the cygthread to the cygthread pool */ + return 0; +} + +static DWORD WINAPI +timerfd_thread (VOID *arg) +{ + timerfd_tracker *tt = ((timerfd_tracker *) arg); + return tt->thread_func (); +} + +int +timerfd_shared::create (clockid_t clock_id) +{ + int ret; + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + + /* Create access mutex */ + InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL, + sec_none.lpSecurityDescriptor); + status = NtCreateMutant (&_access_mtx, MUTEX_ALL_ACCESS, &attr, FALSE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err; + } + /* Create "timer is armed" event, set to "Unsignaled" at creation time */ + status = NtCreateEvent (&_arm_evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err_close_access_mtx; + } + /* Create "timer is disarmed" event, set to "Signaled" at creation time */ + status = NtCreateEvent (&_disarm_evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, TRUE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err_close_arm_evt; + } + /* Create timer */ + status = NtCreateTimer (&_timer, TIMER_ALL_ACCESS, &attr, + SynchronizationTimer); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err_close_disarm_evt; + } + /* Create "timer expired" semaphore */ + status = NtCreateEvent (&_expired_evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err_close_timer; + } + instance_count = 1; + _clockid = clock_id; + return 0; + +err_close_timer: + NtClose (_timer); +err_close_disarm_evt: + NtClose (_disarm_evt); +err_close_arm_evt: + NtClose (_arm_evt); +err_close_access_mtx: + NtClose (_access_mtx); +err: + return ret; +} + +int +timerfd_tracker::create (clockid_t clock_id) +{ + int ret; + clk_t *clock; + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + + const ACCESS_MASK access = STANDARD_RIGHTS_REQUIRED + | SECTION_MAP_READ | SECTION_MAP_WRITE; + SIZE_T vsize = PAGE_SIZE; + LARGE_INTEGER sectionsize = { QuadPart: PAGE_SIZE }; + + /* Valid clock? */ + clock = get_clock (clock_id); + if (!clock) + { + ret = -EINVAL; + goto err; + } + /* Create shared section. */ + InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL, + sec_none.lpSecurityDescriptor); + status = NtCreateSection (&tfd_shared_hdl, access, &attr, + §ionsize, PAGE_READWRITE, + SEC_COMMIT, NULL); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err; + } + /* Create section mapping (has to be repeated after fork/exec */ + status = NtMapViewOfSection (tfd_shared_hdl, NtCurrentProcess (), + (void **) &tfd_shared, 0, PAGE_SIZE, NULL, + &vsize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err_close_tfd_shared_hdl; + } + /* Create cancel even for this processes timer thread */ + InitializeObjectAttributes (&attr, NULL, 0, NULL, + sec_none_nih.lpSecurityDescriptor); + status = NtCreateEvent (&cancel_evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + goto err_unmap_tfd_shared; + } + ret = tfd_shared->create (clock_id); + if (ret < 0) + goto err_close_cancel_evt; + winpid = GetCurrentProcessId (); + new cygthread (timerfd_thread, this, "timerfd", sync_thr); + return 0; + +err_close_cancel_evt: + NtClose (cancel_evt); +err_unmap_tfd_shared: + NtUnmapViewOfSection (NtCurrentProcess (), tfd_shared); +err_close_tfd_shared_hdl: + NtClose (tfd_shared_hdl); +err: + return ret; +} + +/* Return true if this was the last instance of a timerfd, session-wide, + false otherwise */ +bool +timerfd_shared::dtor () +{ + if (instance_count > 0) + { + return false; + } + timer_expired (); + disarm_timer (); + NtClose (_timer); + NtClose (_arm_evt); + NtClose (_disarm_evt); + NtClose (_expired_evt); + NtClose (_access_mtx); + return true; +} + +/* Return true if this was the last instance of a timerfd, session-wide, + false otherwise. Basically this is a destructor, but one which may + notify the caller NOT to deleted the object. */ +bool +timerfd_tracker::dtor () +{ + if (enter_critical_section ()) + { + if (local_instance_count > 0) + { + leave_critical_section (); + return false; + } + SetEvent (cancel_evt); + WaitForSingleObject (sync_thr, INFINITE); + if (tfd_shared->dtor ()) + { + NtUnmapViewOfSection (NtCurrentProcess (), tfd_shared); + NtClose (tfd_shared_hdl); + } + else + leave_critical_section (); + } + NtClose (cancel_evt); + NtClose (sync_thr); + return true; +} + +void +timerfd_tracker::dtor (timerfd_tracker *tfd) +{ + if (tfd->dtor ()) + cfree (tfd); +} + +void +timerfd_tracker::close () +{ + InterlockedDecrement (&local_instance_count); + InterlockedDecrement (&tfd_shared->instance_count); +} + +void +timerfd_tracker::ioctl_set_ticks (uint64_t ov_cnt) +{ + set_overrun_count (ov_cnt); + timer_expired (); +} + +void +timerfd_tracker::fixup_after_fork_exec (bool execing) +{ + NTSTATUS status; + OBJECT_ATTRIBUTES attr; + SIZE_T vsize = PAGE_SIZE; + + /* Run this only once per process */ + if (winpid == GetCurrentProcessId ()) + return; + /* Recreate shared section mapping */ + status = NtMapViewOfSection (tfd_shared_hdl, NtCurrentProcess (), + (void **) &tfd_shared, 0, PAGE_SIZE, NULL, + &vsize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE); + if (!NT_SUCCESS (status)) + api_fatal ("Can't recreate shared timerfd section during %s!", + execing ? "execve" : "fork"); + /* Increment global instance count by the number of instances in this + process */ + InterlockedAdd (&tfd_shared->instance_count, local_instance_count); + /* Create cancel even for this processes timer thread */ + InitializeObjectAttributes (&attr, NULL, 0, NULL, + sec_none_nih.lpSecurityDescriptor); + status = NtCreateEvent (&cancel_evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + api_fatal ("Can't recreate timerfd cancel event during %s!", + execing ? "execve" : "fork"); + /* Set winpid so we don't run this twice */ + winpid = GetCurrentProcessId (); + new cygthread (timerfd_thread, this, "timerfd", sync_thr); +} + +LONG64 +timerfd_tracker::wait (bool nonblocking) +{ + HANDLE w4[2] = { get_timerfd_handle (), NULL }; + LONG64 ret; + + wait_signal_arrived here (w4[1]); +repeat: + switch (WaitForMultipleObjects (2, w4, FALSE, nonblocking ? 0 : INFINITE)) + { + case WAIT_OBJECT_0: /* timer event */ + if (!enter_critical_section ()) + ret = -EIO; + else + { + ret = read_and_reset_overrun_count (); + leave_critical_section (); + if (ret) + break; + /* A 0 overrun count indicates another read was quicker. + Continue as if we didn't catch the expiry. */ + if (!nonblocking) + { + Sleep (100L); + goto repeat; + } + ret = -EAGAIN; + } + break; + case WAIT_OBJECT_0 + 1: /* signal */ + if (_my_tls.call_signal_handler ()) + goto repeat; + ret = -EINTR; + break; + case WAIT_TIMEOUT: + ret = -EAGAIN; + break; + default: + ret = -geterrno_from_win_error (); + break; + } + return ret; +} + +int +timerfd_tracker::gettime (struct itimerspec *curr_value) +{ + int ret = 0; + + __try + { + if (!enter_critical_section ()) + { + ret = -EBADF; + __leave; + } + LONG64 next_relative_exp = get_exp_ts () - get_clock_now (); + curr_value->it_value.tv_sec = next_relative_exp / NS100PERSEC; + next_relative_exp -= curr_value->it_value.tv_sec * NS100PERSEC; + curr_value->it_value.tv_nsec = next_relative_exp + * (NSPERSEC / NS100PERSEC); + leave_critical_section (); + ret = 0; + } + __except (NO_ERROR) + { + ret = -EFAULT; + } + __endtry + return ret; +} + +int +timerfd_shared::arm_timer (int flags, const struct itimerspec *new_value) +{ + LONG64 ts; + NTSTATUS status; + LARGE_INTEGER DueTime; + BOOLEAN Resume; + LONG Period; + + ResetEvent (_disarm_evt); + + /* Convert incoming itimerspec into 100ns interval and timestamp */ + _interval = new_value->it_interval.tv_sec * NS100PERSEC + + (new_value->it_interval.tv_nsec + (NSPERSEC / NS100PERSEC) - 1) + / (NSPERSEC / NS100PERSEC); + ts = new_value->it_value.tv_sec * NS100PERSEC + + (new_value->it_value.tv_nsec + (NSPERSEC / NS100PERSEC) - 1) + / (NSPERSEC / NS100PERSEC); + _flags = flags; + if (flags & TFD_TIMER_ABSTIME) + { + if (_clockid == CLOCK_REALTIME) + DueTime.QuadPart = ts + FACTOR; + else /* non-REALTIME clocks require relative DueTime. */ + { + DueTime.QuadPart = ts - get_clock_now (); + /* If the timestamp was earlier than now, compute number + of overruns and offset DueTime to expire immediately. */ + if (DueTime.QuadPart >= 0) + { + LONG64 num_intervals = DueTime.QuadPart / _interval; + increment_overrun_count (num_intervals); + DueTime.QuadPart = -1LL; + } + } + } + else + { + /* Keep relative timestamps relative for the timer, but store the + expiry timestamp absolute for the timer thread. */ + DueTime.QuadPart = -ts; + ts += get_clock_now (); + } + set_exp_ts (ts); + /* TODO: CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM + Note: Advanced Power Settings -> Sleep -> Allow Wake Timers + since W10 1709 */ + Resume = FALSE; + if (_interval > INT_MAX * (NS100PERSEC / MSPERSEC)) + Period = 0; + else + Period = (_interval + (NS100PERSEC / MSPERSEC) - 1) + / (NS100PERSEC / MSPERSEC); + status = NtSetTimer (timer (), &DueTime, NULL, NULL, Resume, Period, NULL); + if (!NT_SUCCESS (status)) + { + disarm_timer (); + return -geterrno_from_nt_status (status); + } + + SetEvent (_arm_evt); + return 0; +} + +int +timerfd_tracker::settime (int flags, const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + int ret = 0; + + __try + { + if (!valid_timespec (new_value->it_value) + || !valid_timespec (new_value->it_interval)) + { + ret = -EINVAL; + __leave; + } + if (!enter_critical_section ()) + { + ret = -EBADF; + __leave; + } + if (old_value) + gettime (old_value); + if (new_value->it_value.tv_sec == 0 && new_value->it_value.tv_nsec == 0) + ret = disarm_timer (); + else + ret = arm_timer (flags, new_value); + leave_critical_section (); + ret = 0; + } + __except (NO_ERROR) + { + ret = -EFAULT; + } + __endtry + return ret; +} diff --git a/winsup/cygwin/timerfd.h b/winsup/cygwin/timerfd.h new file mode 100644 index 000000000..6c42d91f4 --- /dev/null +++ b/winsup/cygwin/timerfd.h @@ -0,0 +1,160 @@ +/* timerfd.h: Define timerfd classes + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef __TIMERFD_H__ +#define __TIMERFD_H__ + +#include "clock.h" +#include "ntdll.h" + +class timerfd_shared +{ + HANDLE _access_mtx; /* controls access to shared data */ + HANDLE _arm_evt; /* settimer sets event when timer is armed, + unsets event when timer gets disarmed. */ + HANDLE _disarm_evt; /* settimer sets event when timer is armed, + unsets event when timer gets disarmed. */ + HANDLE _timer; /* SynchronizationTimer */ + HANDLE _expired_evt; /* Signal if timer expired, Unsignal on read. */ + LONG instance_count; /* each open fd increments this. + If 0 -> delete timerfd_shared */ + + clockid_t _clockid; /* clockid */ + struct itimerspec _time_spec; /* original incoming itimerspec */ + LARGE_INTEGER _exp_ts; /* start timestamp or next expire timestamp + in 100ns */ + LONG64 _interval; /* timer interval in 100ns */ + LONG64 _overrun_count; /* expiry counter */ + int _flags; /* settime flags */ + + int create (clockid_t); + bool dtor (); + + /* read access methods */ + HANDLE arm_evt () const { return _arm_evt; } + HANDLE disarm_evt () const { return _disarm_evt; } + HANDLE timer () const { return _timer; } + HANDLE expired_evt () const { return _expired_evt; } + LONG64 get_clock_now () const { return get_clock (_clockid)->n100secs (); } + struct itimerspec &time_spec () { return _time_spec; } + int flags () const { return _flags; } + LONG64 overrun_count () const { return _overrun_count; } + void increment_overrun_count (LONG64 add) + { InterlockedAdd64 (&_overrun_count, add); } + void set_overrun_count (LONG64 newval) + { InterlockedExchange64 (&_overrun_count, newval); } + LONG64 read_and_reset_overrun_count () + { + LONG64 ret = InterlockedExchange64 (&_overrun_count, 0); + if (ret) + ResetEvent (_expired_evt); + return ret; + } + + /* write access methods */ + bool enter_cs () + { + return WaitForSingleObject (_access_mtx, INFINITE) == WAIT_OBJECT_0; + } + void leave_cs () + { + ReleaseMutex (_access_mtx); + } + int arm_timer (int, const struct itimerspec *); + int disarm_timer () + { + ResetEvent (_arm_evt); + memset (&_time_spec, 0, sizeof _time_spec); + _exp_ts.QuadPart = 0; + _interval = 0; + _flags = 0; + NtCancelTimer (timer (), NULL); + SetEvent (_disarm_evt); + return 0; + } + void timer_expired () { SetEvent (_expired_evt); } + void set_exp_ts (LONG64 ts) { _exp_ts.QuadPart = ts; } + + friend class timerfd_tracker; +}; + +class timerfd_tracker /* cygheap! */ +{ + DWORD winpid; /* This is used @ fork/exec time to know if + this tracker already has been fixed up. */ + HANDLE tfd_shared_hdl; /* handle auf shared mem */ + timerfd_shared *tfd_shared; /* pointer auf shared mem, needs + NtMapViewOfSection in each new process. */ + + HANDLE cancel_evt; /* Signal thread to exit. */ + HANDLE sync_thr; /* cygthread sync object. */ + LONG local_instance_count; /* each open fd increments this. + If 0 -> cancel thread. */ + + bool dtor (); + + bool enter_critical_section () const { return tfd_shared->enter_cs (); } + void leave_critical_section () const { tfd_shared->leave_cs (); } + + int arm_timer (int flags, const struct itimerspec *new_value) const + { return tfd_shared->arm_timer (flags, new_value); } + int disarm_timer () const { return tfd_shared->disarm_timer (); } + void timer_expired () const { tfd_shared->timer_expired (); } + + void increment_overrun_count (LONG64 add) const + { tfd_shared->increment_overrun_count (add); } + void set_overrun_count (uint64_t ov_cnt) const + { tfd_shared->set_overrun_count ((LONG64) ov_cnt); } + LONG64 read_and_reset_overrun_count () const + { return tfd_shared->read_and_reset_overrun_count (); } + + struct timespec it_value () const + { return tfd_shared->time_spec ().it_value; } + struct timespec it_interval () const + { return tfd_shared->time_spec ().it_interval; } + + LONG64 get_clock_now () const { return tfd_shared->get_clock_now (); } + LONG64 get_exp_ts () const { return tfd_shared->_exp_ts.QuadPart; } + LONG64 get_interval () const { return tfd_shared->_interval; } + + void set_exp_ts (LONG64 ts) const { tfd_shared->set_exp_ts (ts); } + + public: + void *operator new (size_t, void *p) __attribute__ ((nothrow)) {return p;} + timerfd_tracker () + : tfd_shared_hdl (NULL), tfd_shared (NULL), cancel_evt (NULL), + sync_thr (NULL), local_instance_count (1) {} + int create (clockid_t); + int gettime (struct itimerspec *); + int settime (int, const struct itimerspec *, struct itimerspec *); + static void dtor (timerfd_tracker *); + void close (); + void ioctl_set_ticks (uint64_t); + void fixup_after_fork_exec (bool); + void fixup_after_fork () { fixup_after_fork_exec (false); } + void fixup_after_exec () { fixup_after_fork_exec (true); } + HANDLE get_timerfd_handle () const { return tfd_shared->expired_evt (); } + HANDLE get_disarm_evt () const { return tfd_shared->disarm_evt (); } + LONG64 wait (bool); + void increment_global_instances () + { InterlockedIncrement (&tfd_shared->instance_count); } + void increment_instances () + { + InterlockedIncrement (&tfd_shared->instance_count); + InterlockedIncrement (&local_instance_count); + } + void decrement_instances () + { + InterlockedDecrement (&tfd_shared->instance_count); + InterlockedDecrement (&local_instance_count); + } + + DWORD thread_func (); +}; + +#endif /* __TIMERFD_H__ */