blob: 38a35529303f773e5e9382405f6b0634c046b4a4 [file] [log] [blame]
/*
* 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 = (interval % 1000) * 1000000;
/* 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 (type=%d err=%d)", G_STRLOC, type, errno);
}
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;
}