/* timer.cc Copyright 2004, 2005 Red Hat, Inc. 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 #include #include "cygerrno.h" #include "security.h" #include "hires.h" #include "thread.h" #include "cygtls.h" #include "sigproc.h" #include "sync.h" #define TT_MAGIC 0x513e4a1c struct timer_tracker { static muto *protect; unsigned magic; clockid_t clock_id; sigevent evp; itimerspec it; HANDLE cancel; int flags; cygthread *th; struct timer_tracker *next; int settime (int, const itimerspec *, itimerspec *); timer_tracker (clockid_t, const sigevent *); timer_tracker (); }; timer_tracker ttstart; muto *timer_tracker::protect; timer_tracker::timer_tracker () { new_muto (protect); } timer_tracker::timer_tracker (clockid_t c, const sigevent *e) { if (e != NULL) evp = *e; else { evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGALRM; evp.sigev_value.sival_ptr = this; } clock_id = c; cancel = NULL; flags = 0; memset (&it, 0, sizeof (it)); protect->acquire (); next = ttstart.next; ttstart.next = this; protect->release (); magic = TT_MAGIC; } static long long to_us (timespec& ts) { long long res = ts.tv_sec; res *= 1000000; res += ts.tv_nsec / 1000 + ((ts.tv_nsec % 1000) >= 500 ? 1 : 0); return res; } static NO_COPY itimerspec itzero; static NO_COPY timespec tzero; static DWORD WINAPI timer_thread (VOID *x) { timer_tracker *tp = ((timer_tracker *) x); timer_tracker tt = *tp; for (bool first = true; ; first = false) { long long sleep_us = to_us (first ? tt.it.it_value : tt.it.it_interval); long long sleep_to = sleep_us; long long now = gtod.usecs (false); if (tt.flags & TIMER_ABSTIME) sleep_us -= now; else sleep_to += now; DWORD sleep_ms = (sleep_us < 0) ? 0 : (sleep_us / 1000); debug_printf ("%p waiting for %u ms, first %d", x, sleep_ms, first); tp->it.it_value = tzero; switch (WaitForSingleObject (tt.cancel, sleep_ms)) { case WAIT_TIMEOUT: debug_printf ("timed out"); break; case WAIT_OBJECT_0: now = gtod.usecs (false); sleep_us = sleep_to - now; if (sleep_us < 0) sleep_us = 0; tp->it.it_value.tv_sec = sleep_us / 1000000; tp->it.it_value.tv_nsec = (sleep_us % 1000000) * 1000; debug_printf ("%p cancelled, elapsed %D", x, sleep_us); goto out; default: debug_printf ("%p timer wait failed, %E", x); goto out; } switch (tt.evp.sigev_notify) { case SIGEV_SIGNAL: { siginfo_t si; memset (&si, 0, sizeof (si)); si.si_signo = tt.evp.sigev_signo; si.si_sigval.sival_ptr = tt.evp.sigev_value.sival_ptr; debug_printf ("%p sending sig %d", x, tt.evp.sigev_signo); sig_send (NULL, si); break; } case SIGEV_THREAD: { pthread_t notify_thread; debug_printf ("%p starting thread", x); int rc = pthread_create (¬ify_thread, tt.evp.sigev_notify_attributes, (void * (*) (void *)) tt.evp.sigev_notify_function, tt.evp.sigev_value.sival_ptr); if (rc) { debug_printf ("thread creation failed, %E"); return 0; } // FIXME: pthread_join? break; } } if (!tt.it.it_interval.tv_sec && !tt.it.it_interval.tv_nsec) break; tt.flags = 0; debug_printf ("looping"); } out: CloseHandle (tt.cancel); // FIXME: race here but is it inevitable? if (tt.cancel == tp->cancel) tp->cancel = NULL; return 0; } static bool it_bad (const timespec& t) { if (t.tv_nsec < 0 || t.tv_nsec >= 1000000000 || t.tv_sec < 0) { set_errno (EINVAL); return true; } return false; } int timer_tracker::settime (int in_flags, const itimerspec *value, itimerspec *ovalue) { if (!value) { set_errno (EINVAL); return -1; } if (__check_invalid_read_ptr_errno (value, sizeof (*value))) return -1; if (ovalue && check_null_invalid_struct_errno (ovalue)) return -1; itimerspec *elapsed; if (!cancel) elapsed = &itzero; else { SetEvent (cancel); // should be closed when the thread exits th->detach (); elapsed = ⁢ } if (ovalue) *ovalue = *elapsed; if (value->it_value.tv_sec || value->it_value.tv_nsec) { if (it_bad (value->it_value)) return -1; if (it_bad (value->it_interval)) return -1; flags = in_flags; cancel = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); it = *value; th = new cygthread (timer_thread, this, "itimer"); } return 0; } extern "C" int timer_create (clockid_t clock_id, struct sigevent *evp, timer_t *timerid) { if (evp && check_null_invalid_struct_errno (evp)) return -1; if (check_null_invalid_struct_errno (timerid)) return -1; if (clock_id != CLOCK_REALTIME) { set_errno (EINVAL); return -1; } *timerid = (timer_t) new timer_tracker (clock_id, evp); return 0; } extern "C" int timer_settime (timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue) { timer_tracker *tt = (timer_tracker *) timerid; if (check_null_invalid_struct_errno (tt) || tt->magic != TT_MAGIC) return -1; return tt->settime (flags, value, ovalue); } extern "C" int timer_delete (timer_t timerid) { timer_tracker *in_tt = (timer_tracker *) timerid; if (check_null_invalid_struct_errno (in_tt) || in_tt->magic != TT_MAGIC) return -1; timer_tracker::protect->acquire (); for (timer_tracker *tt = &ttstart; tt->next != NULL; tt = tt->next) if (tt->next == in_tt) { timer_tracker *deleteme = tt->next; tt->next = deleteme->next; delete deleteme; timer_tracker::protect->release (); return 0; } timer_tracker::protect->release (); set_errno (EINVAL); return 0; } void fixup_timers_after_fork () { for (timer_tracker *tt = &ttstart; tt->next != NULL; /* nothing */) { timer_tracker *deleteme = tt->next; tt->next = deleteme->next; delete deleteme; } }