shell: Add ShellTimeChangeSource to track system clock changes

This is a `GSource` which is dispatched when the offset of the system
real/wall clock changes with respect to the system monotonic clock. This
can happen when the user explicitly changes the system clock, or when
there’s an NTP sync after a prolonged period offline.

The source can’t tell you *what* the offset change was, just that there
was one.

This will be used in an upcoming commit.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3397>
This commit is contained in:
Philip Withnall 2024-06-28 16:39:37 +01:00 committed by Marge Bot
parent 3bc3a4da68
commit 1ceb803686
3 changed files with 207 additions and 0 deletions

View File

@ -122,6 +122,7 @@ libshell_public_headers = [
'shell-screenshot.h',
'shell-square-bin.h',
'shell-stack.h',
'shell-time-change-source.h',
'shell-util.h',
'shell-window-preview.h',
'shell-window-preview-layout.h',
@ -177,6 +178,7 @@ libshell_sources = [
'shell-secure-text-buffer.h',
'shell-square-bin.c',
'shell-stack.c',
'shell-time-change-source.c',
'shell-util.c',
'shell-window-preview.c',
'shell-window-preview-layout.c',

View File

@ -0,0 +1,172 @@
/*
* Copyright 2024 GNOME Foundation, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Philip Withnall <pwithnall@gnome.org>
*/
#include "config.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <stdint.h>
#include <sys/timerfd.h>
#include "shell-time-change-source.h"
typedef struct
{
GSource source;
int fd; /* (owned) (nullable) */
void *tag; /* (owned) (nullable) */
} ShellTimeChangeSource;
static int
arm_timerfd (int fd)
{
struct itimerspec its = {
/* Get the biggest value we can in the `time_t`, as the timerfd will fire
* spuriously when that time is reached. Unfortunately there is no
* `TIME_T_MAX`. */
.it_value.tv_sec = (sizeof (time_t) >= 8) ? UINT64_MAX : UINT32_MAX,
};
int flags = TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET;
if (timerfd_settime (fd, flags, &its, NULL) == 0)
return 0;
if (errno != EINVAL)
return -1;
/* Try again with a smaller timeout. Its possible that libc supports
* 64-bit time while the kernel doesnt. */
its.it_value.tv_sec = UINT32_MAX;
return timerfd_settime (fd, flags, &its, NULL);
}
static void
shell_time_change_source_cleanup_fd (ShellTimeChangeSource *self)
{
/* Make sure the FD is closed. */
if (self->tag != NULL)
{
g_source_remove_unix_fd ((GSource *) self, self->tag);
self->tag = NULL;
}
g_clear_fd (&self->fd, NULL);
}
static gboolean
shell_time_change_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
ShellTimeChangeSource *self = (ShellTimeChangeSource *) source;
if (callback == NULL)
{
g_warning ("ShellTimeChangeSource dispatched without callback. "
"You must call g_source_set_callback().");
return G_SOURCE_REMOVE;
}
if (callback (user_data))
{
/* The timerfd_settime() call cant really fail in this situation.
* The man page says it can return ECANCELED, but will still be re-armed. */
int retval = arm_timerfd (self->fd);
int errsv = errno;
g_assert (retval == 0 ||
(retval < 0 && errsv == ECANCELED));
return G_SOURCE_CONTINUE;
}
/* Clean up the sources resources early, as FDs are precious, and the user
* might leave the source hanging around for a long time before finalising it. */
shell_time_change_source_cleanup_fd (self);
return G_SOURCE_REMOVE;
}
static void
shell_time_change_source_finalize (GSource *source)
{
ShellTimeChangeSource *self = (ShellTimeChangeSource *) source;
shell_time_change_source_cleanup_fd (self);
}
static const GSourceFuncs shell_time_change_source_funcs = {
NULL, /* prepare */
NULL, /* check */
shell_time_change_source_dispatch,
shell_time_change_source_finalize,
NULL, NULL
};
/**
* shell_time_change_source_new:
* @error: return location for a #GError, or %NULL
*
* Creates a #GSource which is dispatched every time the system realtime clock
* changes relative to the monotonic clock.
*
* This typically happens after NTP synchronisation.
*
* On error, a #GFileError will be returned. This happens if a timerfd cannot be
* created.
*
* Any callback attached to the returned #GSource must have type
* #GSourceFunc.
*
* Returns: (transfer full): the newly created #GSource, or %NULL on error
*/
GSource *
shell_time_change_source_new (GError **error)
{
ShellTimeChangeSource *self;
g_autoptr(GSource) source = NULL;
g_autofd int fd = -1;
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* Create a timerfd with the maximum possible timeout, but set
* `TFD_TIMER_CANCEL_ON_SET` so that it fires if the realtime clock changes
* relative to the monotonic clock.
*
* This is a one-shot source: itll need to be recreated after that. */
fd = timerfd_create (CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
if (fd < 0 || arm_timerfd (fd) < 0)
{
int errsv = errno;
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
"Error creating timerfd: %s", g_strerror (errsv));
return NULL;
}
source = g_source_new ((GSourceFuncs *) &shell_time_change_source_funcs,
sizeof (ShellTimeChangeSource));
self = (ShellTimeChangeSource *) source;
self->tag = g_source_add_unix_fd (source, fd, G_IO_IN);
self->fd = g_steal_fd (&fd);
return g_steal_pointer (&source);
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2024 GNOME Foundation, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Philip Withnall <pwithnall@gnome.org>
*/
#ifndef __SHELL_TIME_CHANGE_SOURCE_H__
#define __SHELL_TIME_CHANGE_SOURCE_H__
#include <glib.h>
G_BEGIN_DECLS
GSource *shell_time_change_source_new (GError **error);
G_END_DECLS
#endif /* __SHELL_TIME_CHANGE_SOURCE_H__ */