| /* |
| * Realtime timer with GLib integration |
| * |
| * Copyright (C) 2014 Nest Labs. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <errno.h> |
| |
| #include "timer.h" |
| |
| /* |
| * A GLib custom event source using a POSIX fd timer that becomes |
| * 'ready' on timer expiration. Timer 'advances' across a suspend but |
| * has no ability to bring system out of suspend. Timer will fire upon |
| * resume if expired. |
| */ |
| |
| typedef struct _GRTimeoutSource GRTimeoutSource; |
| |
| struct _GRTimeoutSource |
| { |
| GSource source; |
| GPollFD pfd; |
| guint interval; |
| gpointer tag; |
| }; |
| |
| static gboolean g_rttimeout_prepare (GSource *source, gint *timeout); |
| static gboolean g_rttimeout_check (GSource *source); |
| static gboolean g_rttimeout_dispatch (GSource *source, GSourceFunc callback, |
| gpointer user_data); |
| |
| GSourceFuncs g_rttimeout_funcs = |
| { |
| g_rttimeout_prepare, |
| g_rttimeout_check, |
| g_rttimeout_dispatch, |
| NULL |
| }; |
| |
| void g_rttimeout_info(const char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| vsyslog(LOG_INFO, format, ap); |
| va_end(ap); |
| } |
| |
| static gboolean g_rttimeout_prepare (GSource *source, gint *timeout) |
| { |
| *timeout = -1; |
| |
| return FALSE; |
| } |
| |
| static gboolean g_rttimeout_check (GSource *source) |
| { |
| GRTimeoutSource *timeout_source = (GRTimeoutSource *)source; |
| |
| return timeout_source->pfd.revents & G_IO_IN; |
| } |
| |
| static gboolean |
| g_rttimeout_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) |
| { |
| GRTimeoutSource *timeout_source = (GRTimeoutSource *)source; |
| gboolean again; |
| |
| g_rttimeout_info("DHCP: %s: fd=%d, source=%p, user_data=%p", |
| __func__, timeout_source->pfd.fd, source, user_data); |
| if (!callback) { |
| g_warning ("GR timeout source dispatched without callback\n" |
| "You must call g_source_set_callback()."); |
| if (!g_source_is_destroyed (source)) { |
| g_source_remove_poll (source, &timeout_source->pfd); |
| close(timeout_source->pfd.fd); |
| } |
| return FALSE; |
| } |
| |
| again = callback (user_data); |
| |
| if (again) { |
| struct itimerspec timer_value; |
| int result; |
| |
| /* arm the timer as a single-shot */ |
| timer_value.it_value.tv_sec = timeout_source->interval; |
| timer_value.it_value.tv_nsec = 0; |
| timer_value.it_interval.tv_sec = 0; |
| timer_value.it_interval.tv_nsec = 0; |
| |
| result = timerfd_settime (timeout_source->pfd.fd, 0, |
| &timer_value, NULL); |
| if (result == -1) |
| g_error ("%s: error calling timer_settime", G_STRLOC); |
| } else { |
| if (!g_source_is_destroyed (source)) { |
| g_source_remove_poll (source, &timeout_source->pfd); |
| close(timeout_source->pfd.fd); |
| } |
| } |
| |
| return again; |
| } |
| |
| /** |
| * g_rttimeout_add_seconds_full: |
| */ |
| guint g_rttimeout_add_seconds_full (gint type, gint priority, guint32 interval, |
| GSourceFunc function, gpointer data, |
| GDestroyNotify notify) |
| { |
| return g_rttimeout_add_full(type, priority, interval * 1000, |
| function, data, notify); |
| } |
| |
| /** |
| * g_rttimeout_add_full: |
| */ |
| guint g_rttimeout_add_full (gint type, gint priority, guint32 interval, |
| GSourceFunc function, gpointer data, |
| GDestroyNotify notify) |
| { |
| GSource *source; |
| int fd; |
| guint id; |
| gpointer tag; |
| |
| g_return_val_if_fail (function != NULL, 0); |
| |
| source = g_source_new (&g_rttimeout_funcs, sizeof (GRTimeoutSource)); |
| if (priority != G_PRIORITY_DEFAULT) |
| g_source_set_priority (source, priority); |
| g_source_set_callback (source, function, data, notify); |
| id = g_source_attach (source, NULL); |
| g_source_unref (source); |
| |
| fd = timerfd_create(type, 0); |
| if (fd != -1) { |
| GRTimeoutSource *timeout_source = (GRTimeoutSource *)source; |
| struct itimerspec timer_value; |
| int result; |
| |
| g_rttimeout_info("DHCP: %s: id=%d fd=%d, source=%p, seconds=%d", |
| __func__, id, fd, source, interval/1000); |
| memset(&timer_value, 0, sizeof(struct itimerspec)); |
| |
| timer_value.it_value.tv_sec = interval / 1000; |
| timer_value.it_value.tv_nsec = 0; |
| |
| /* arm the timer as a single-shot */ |
| result = timerfd_settime (fd, 0, &timer_value, |
| NULL); |
| if (result != -1) { |
| timeout_source->interval = interval; |
| timeout_source->pfd.fd = fd; |
| timeout_source->pfd.events = G_IO_IN; |
| |
| tag = g_source_add_unix_fd(source, fd, G_IO_IN); |
| |
| if (tag != NULL) { |
| timeout_source->tag = tag; |
| } else { |
| g_rttimeout_info("DHCP: %s: cannot attach fd to source\n", __func__); |
| g_error ("%s: error calling g_source_add_unix_fd", G_STRLOC); |
| } |
| } else { |
| g_rttimeout_info("DHCP: %s: error timerfd_settime, err=%d\n", |
| __func__, errno); |
| g_error ("%s: error calling timerfd_settime", G_STRLOC); |
| } |
| } else { |
| g_error ("%s: error calling timerfd_create", G_STRLOC); |
| } |
| |
| return id; |
| } |
| |
| gboolean g_rttimeout_source_remove(guint id) |
| { |
| GSource *source; |
| |
| g_return_val_if_fail (id > 0, FALSE); |
| source = g_main_context_find_source_by_id (NULL, id); |
| if (source) { |
| GRTimeoutSource *timeout_source = (GRTimeoutSource *)source; |
| |
| if (!g_source_is_destroyed (source)) { |
| g_rttimeout_info("DHCP: %s: id=%d fd=%d source=%p", |
| __func__, id, timeout_source->pfd.fd, |
| source); |
| g_source_remove_unix_fd(source, timeout_source->tag); |
| close(timeout_source->pfd.fd); |
| g_source_destroy (source); |
| } |
| } else { |
| g_rttimeout_info("DHCP: %s: id=%d not found", __func__, id); |
| } |
| |
| return source != NULL; |
| } |