rpm-ostree/src/daemon/rpmostreed-daemon.c
Jonathan Lebon 4ad627f69b daemon: bump idle exit timeout to 60s
Since it usually takes more than 10s for users to enter consecutive
commands, let's bump the timeout to 60s. That way, we avoid the churn of
starting up twice and e.g. polluting the journal.

On a user interface level, this doesn't make a big difference: a
`status` from cold takes around 100ms, whereas with the daemon running,
it takes slightly less than 50ms. Slightly noticeable, but a non-issue.

However, auto-update will require some more work at startup, and a cold
`status` will bump to about 350ms, which is definitely more noticeable.
Bumping the timeout will ensure that at least within the span of one
"interaction" (multiple commands), we only do this work once.

Closes: #1192
Approved by: cgwalters
2018-01-10 18:53:50 +00:00

675 lines
21 KiB
C

/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 "config.h"
#include "rpmostreed-daemon.h"
#include "rpmostreed-sysroot.h"
#include "rpmostreed-types.h"
#include "rpmostreed-utils.h"
#include <libglnx.h>
#include <systemd/sd-journal.h>
#include <systemd/sd-daemon.h>
#include <stdio.h>
#define RPMOSTREE_MESSAGE_TRANSACTION_STARTED SD_ID128_MAKE(d5,be,a3,7a,8f,c8,4f,f5,9d,bc,fd,79,17,7b,7d,f8)
/* let's not exit super fast; our startup is non-trivial so staying around will ensure
* follow-up requests are more responsive */
#define IDLE_EXIT_TIMEOUT_SECONDS 60
/**
* SECTION: daemon
* @title: RpmostreedDaemon
* @short_description: Main daemon object
*
* Object holding all global state.
*/
typedef struct _RpmostreedDaemonClass RpmostreedDaemonClass;
/**
* RpmostreedDaemon:
*
* The #RpmostreedDaemon structure contains only private data and should
* only be accessed using the provided API.
*/
struct _RpmostreedDaemon {
GObject parent_instance;
GHashTable *bus_clients; /* <utf8 busname, struct RpmOstreeClient> */
gboolean running;
GDBusProxy *bus_proxy;
GSource *idle_exit_source;
guint rerender_status_id;
RpmostreedSysroot *sysroot;
gchar *sysroot_path;
GDBusConnection *connection;
GDBusObjectManagerServer *object_manager;
};
struct _RpmostreedDaemonClass {
GObjectClass parent_class;
};
static RpmostreedDaemon *_daemon_instance;
enum
{
PROP_0,
PROP_CONNECTION,
PROP_OBJECT_MANAGER,
PROP_SYSROOT_PATH
};
static void rpmostreed_daemon_initable_iface_init (GInitableIface *iface);
static void update_status (RpmostreedDaemon *self);
G_DEFINE_TYPE_WITH_CODE (RpmostreedDaemon, rpmostreed_daemon, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
rpmostreed_daemon_initable_iface_init))
struct RpmOstreeClient {
char *address;
guint name_watch_id;
gboolean uid_valid;
uid_t uid;
};
static void
rpmostree_client_free (struct RpmOstreeClient *client)
{
g_free (client->address);
g_free (client);
}
static char *
rpmostree_client_to_string (struct RpmOstreeClient *client)
{
g_autoptr(GString) buf = g_string_new ("client ");
g_string_append (buf, client->address);
if (client->uid_valid)
g_string_append_printf (buf, " (uid %lu)", (unsigned long) client->uid);
else
g_string_append (buf, " (unknown uid)");
return g_string_free (g_steal_pointer (&buf), FALSE);
}
typedef struct {
GAsyncReadyCallback callback;
gpointer callback_data;
gpointer proxy_source;
} DaemonTaskData;
static void
daemon_finalize (GObject *object)
{
RpmostreedDaemon *self = RPMOSTREED_DAEMON (object);
g_clear_object (&self->object_manager);
self->object_manager = NULL;
g_clear_object (&self->sysroot);
g_clear_object (&self->bus_proxy);
g_object_unref (self->connection);
g_hash_table_unref (self->bus_clients);
g_clear_pointer (&self->idle_exit_source, (GDestroyNotify)g_source_unref);
if (self->rerender_status_id > 0)
g_source_remove (self->rerender_status_id);
g_free (self->sysroot_path);
G_OBJECT_CLASS (rpmostreed_daemon_parent_class)->finalize (object);
_daemon_instance = NULL;
}
static void
daemon_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
RpmostreedDaemon *self = RPMOSTREED_DAEMON (object);
switch (prop_id)
{
case PROP_OBJECT_MANAGER:
g_value_set_object (value, self->object_manager);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
daemon_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
RpmostreedDaemon *self = RPMOSTREED_DAEMON (object);
switch (prop_id)
{
case PROP_CONNECTION:
g_assert (self->connection == NULL);
self->connection = g_value_dup_object (value);
break;
case PROP_SYSROOT_PATH:
g_assert (self->sysroot_path == NULL);
self->sysroot_path = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
rpmostreed_daemon_init (RpmostreedDaemon *self)
{
g_assert (_daemon_instance == NULL);
_daemon_instance = self;
self->sysroot_path = NULL;
self->sysroot = NULL;
self->bus_clients = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)rpmostree_client_free);
}
static void
on_active_txn_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
RpmostreedDaemon *self = user_data;
g_autoptr(GVariant) active_txn = NULL;
const char *method, *sender, *path;
g_object_get (rpmostreed_sysroot_get (), "active-transaction", &active_txn, NULL);
if (active_txn)
{
g_variant_get (active_txn, "(&s&s&s)", &method, &sender, &path);
if (*method)
{
g_autofree char *client_str = rpmostreed_daemon_client_get_string (self, sender);
struct RpmOstreeClient *clientdata = g_hash_table_lookup (self->bus_clients, sender);
g_autofree char *client_data_msg = NULL;
if (clientdata && clientdata->uid_valid)
client_data_msg = g_strdup_printf ("CLIENT_UID=%u", clientdata->uid);
sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_TRANSACTION_STARTED),
"MESSAGE=Initiated txn %s for %s: %s", method, client_str, path,
"BUS_ADDRESS=%s", sender,
client_data_msg ?: NULL,
NULL);
}
}
update_status (self);
}
static gboolean
rpmostreed_daemon_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
RpmostreedDaemon *self = RPMOSTREED_DAEMON (initable);
self->object_manager = g_dbus_object_manager_server_new (BASE_DBUS_PATH);
/* Export the ObjectManager */
g_dbus_object_manager_server_set_connection (self->object_manager, self->connection);
g_debug ("exported object manager");
g_autofree gchar *path =
rpmostreed_generate_object_path (BASE_DBUS_PATH, "Sysroot", NULL);
self->sysroot = g_object_new (RPMOSTREED_TYPE_SYSROOT,
"path", self->sysroot_path,
NULL);
if (!rpmostreed_sysroot_populate (rpmostreed_sysroot_get (), cancellable, error))
return glnx_prefix_error (error, "Error setting up sysroot");
g_signal_connect (rpmostreed_sysroot_get (), "notify::active-transaction",
G_CALLBACK (on_active_txn_changed), self);
self->bus_proxy =
g_dbus_proxy_new_sync (self->connection,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
NULL,
error);
if (!self->bus_proxy)
return FALSE;
rpmostreed_daemon_publish (self, path, FALSE, self->sysroot);
g_dbus_connection_start_message_processing (self->connection);
g_debug ("daemon constructed");
return TRUE;
}
static void
rpmostreed_daemon_class_init (RpmostreedDaemonClass *klass)
{
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = daemon_finalize;
gobject_class->set_property = daemon_set_property;
gobject_class->get_property = daemon_get_property;
/**
* Daemon:connection:
*
* The #GDBusConnection the daemon is for.
*/
g_object_class_install_property (gobject_class,
PROP_CONNECTION,
g_param_spec_object ("connection",
"Connection",
"The D-Bus connection the daemon is for",
G_TYPE_DBUS_CONNECTION,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* Daemon:object-manager:
*
* The #GDBusObjectManager used by the daemon
*/
g_object_class_install_property (gobject_class,
PROP_OBJECT_MANAGER,
g_param_spec_object ("object-manager",
"Object Manager",
"The D-Bus Object Manager server used by the daemon",
G_TYPE_DBUS_OBJECT_MANAGER_SERVER,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_SYSROOT_PATH,
g_param_spec_string ("sysroot-path",
"Sysroot Path",
"Sysroot location on the filesystem",
FALSE,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
static void
rpmostreed_daemon_initable_iface_init (GInitableIface *iface)
{
iface->init = rpmostreed_daemon_initable_init;
}
/**
* rpmostreed_daemon_get:
*
* Returns: (transfer none): The singleton #RpmostreedDaemon instance
*/
RpmostreedDaemon *
rpmostreed_daemon_get (void)
{
g_assert (_daemon_instance);
return _daemon_instance;
}
static void
on_name_owner_changed (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
RpmostreedDaemon *self = user_data;
const char *name;
const char *old_owner;
gboolean has_old_owner;
const char *new_owner;
gboolean has_new_owner;
if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
return;
g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
has_old_owner = old_owner && *old_owner;
has_new_owner = new_owner && *new_owner;
if (has_old_owner && !has_new_owner)
rpmostreed_daemon_remove_client (self, name);
}
static gboolean
idle_update_status (void *data)
{
RpmostreedDaemon *self = data;
update_status (self);
return TRUE;
}
static gboolean
on_idle_exit (void *data)
{
RpmostreedDaemon *self = data;
g_clear_pointer (&self->idle_exit_source, (GDestroyNotify)g_source_unref);
sd_notifyf (0, "STATUS=Exiting due to idle");
self->running = FALSE;
g_main_context_wakeup (NULL);
return FALSE;
}
static void
update_status (RpmostreedDaemon *self)
{
g_autoptr(GVariant) active_txn = NULL;
gboolean have_active_txn = FALSE;
const char *method, *sender, *path;
const guint n_clients = g_hash_table_size (self->bus_clients);
gboolean currently_idle = FALSE;
g_object_get (rpmostreed_sysroot_get (), "active-transaction", &active_txn, NULL);
if (active_txn)
{
g_variant_get (active_txn, "(&s&s&s)", &method, &sender, &path);
if (*method)
have_active_txn = TRUE;
}
if (!getenv ("RPMOSTREE_DEBUG_DISABLE_DAEMON_IDLE_EXIT"))
currently_idle = !have_active_txn && n_clients == 0;
if (currently_idle && !self->idle_exit_source)
{
/* I think adding some randomness is a good idea, to mitigate
* pathological cases where someone is talking to us at the same
* frequency as our exit timer. */
const guint idle_exit_secs = IDLE_EXIT_TIMEOUT_SECONDS + g_random_int_range (0, 5);
self->idle_exit_source = g_timeout_source_new_seconds (idle_exit_secs);
g_source_set_callback (self->idle_exit_source, on_idle_exit, self, NULL);
g_source_attach (self->idle_exit_source, NULL);
/* This source ensures we update the systemd status to show admins
* when we may auto-exit.
*/
self->rerender_status_id = g_timeout_add_seconds (1, idle_update_status, self);
sd_journal_print (LOG_INFO, "In idle state; will auto-exit in %u seconds", idle_exit_secs);
}
else if (!currently_idle && self->idle_exit_source)
{
g_source_destroy (self->idle_exit_source);
g_clear_pointer (&self->idle_exit_source, (GDestroyNotify)g_source_unref);
g_source_remove (self->rerender_status_id);
}
if (have_active_txn)
{
sd_notifyf (0, "STATUS=clients=%u; txn=%s caller=%s path=%s", g_hash_table_size (self->bus_clients),
method, sender, path);
}
else if (n_clients > 0)
sd_notifyf (0, "STATUS=clients=%u; idle", n_clients);
else if (currently_idle)
{
guint64 readytime = g_source_get_ready_time (self->idle_exit_source);
guint64 curtime = g_source_get_time (self->idle_exit_source);
guint64 timeout_micros = readytime - curtime;
if (readytime < curtime)
timeout_micros = 0;
g_assert (currently_idle && self->idle_exit_source);
sd_notifyf (0, "STATUS=clients=%u; idle exit in %" G_GUINT64_FORMAT " seconds", n_clients,
timeout_micros / G_USEC_PER_SEC);
}
}
gboolean
rpmostreed_get_client_uid (RpmostreedDaemon *self,
const char *client,
uid_t *out_uid)
{
g_autoptr(GError) local_error = NULL;
g_autoptr(GVariant) uidcall = g_dbus_proxy_call_sync (self->bus_proxy,
"GetConnectionUnixUser",
g_variant_new ("(s)", client),
G_DBUS_CALL_FLAGS_NONE,
2000, NULL, &local_error);
if (!uidcall)
{
sd_journal_print (LOG_WARNING, "Failed to GetConnectionUnixUser for client %s: %s",
client, local_error->message);
return FALSE;
}
g_variant_get (uidcall, "(u)", out_uid);
return TRUE;
}
void
rpmostreed_daemon_add_client (RpmostreedDaemon *self,
const char *client)
{
if (g_hash_table_lookup (self->bus_clients, client))
return;
struct RpmOstreeClient *clientdata = g_new0 (struct RpmOstreeClient, 1);
clientdata->address = g_strdup (client);
clientdata->name_watch_id =
g_dbus_connection_signal_subscribe (self->connection,
"org.freedesktop.DBus",
"org.freedesktop.DBus",
"NameOwnerChanged",
"/org/freedesktop/DBus",
client,
G_DBUS_SIGNAL_FLAGS_NONE,
on_name_owner_changed,
g_object_ref (self),
g_object_unref);
if (rpmostreed_get_client_uid (self, client, &clientdata->uid))
clientdata->uid_valid = TRUE;
g_hash_table_insert (self->bus_clients, (char*)clientdata->address, clientdata);
g_autofree char *clientstr = rpmostree_client_to_string (clientdata);
sd_journal_print (LOG_INFO, "%s added; new total=%u", clientstr, g_hash_table_size (self->bus_clients));
update_status (self);
}
/* Returns a string representing the state of the bus name @client.
* If @client is unknown (i.e. has not called RegisterClient), we just
* return "caller".
*/
char *
rpmostreed_daemon_client_get_string (RpmostreedDaemon *self, const char *client)
{
struct RpmOstreeClient *clientdata = g_hash_table_lookup (self->bus_clients, client);
if (!clientdata)
return g_strdup_printf ("caller %s", client);
else
return rpmostree_client_to_string (clientdata);
}
void
rpmostreed_daemon_remove_client (RpmostreedDaemon *self,
const char *client)
{
gpointer origkey, clientdatap;
struct RpmOstreeClient *clientdata;
if (!g_hash_table_lookup_extended (self->bus_clients, client, &origkey, &clientdatap))
return;
clientdata = clientdatap;
g_dbus_connection_signal_unsubscribe (self->connection, clientdata->name_watch_id);
g_autofree char *clientstr = rpmostree_client_to_string (clientdata);
g_hash_table_remove (self->bus_clients, client);
const guint remaining = g_hash_table_size (self->bus_clients);
sd_journal_print (LOG_INFO, "%s vanished; remaining=%u", clientstr, remaining);
update_status (self);
}
void
rpmostreed_daemon_exit_now (RpmostreedDaemon *self)
{
self->running = FALSE;
}
void
rpmostreed_daemon_run_until_idle_exit (RpmostreedDaemon *self)
{
self->running = TRUE;
update_status (self);
while (self->running)
g_main_context_iteration (NULL, TRUE);
}
void
rpmostreed_daemon_publish (RpmostreedDaemon *self,
const gchar *path,
gboolean uniquely,
gpointer thing)
{
GDBusInterface *prev = NULL;
GDBusInterfaceInfo *info = NULL;
GDBusObjectSkeleton *object = NULL;
g_autoptr(GDBusObjectSkeleton) owned_object = NULL;
g_return_if_fail (RPMOSTREED_IS_DAEMON (self));
g_return_if_fail (path != NULL);
if (G_IS_DBUS_INTERFACE (thing))
{
g_debug ("%spublishing iface: %s %s", uniquely ? "uniquely " : "", path,
g_dbus_interface_get_info (thing)->name);
object = G_DBUS_OBJECT_SKELETON (g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->object_manager), path));
if (object != NULL)
{
if (uniquely)
{
info = g_dbus_interface_get_info (thing);
prev = g_dbus_object_get_interface (G_DBUS_OBJECT (object), info->name);
if (prev)
{
g_object_unref (prev);
g_object_unref (object);
object = NULL;
}
}
}
if (object == NULL)
object = owned_object = g_dbus_object_skeleton_new (path);
g_dbus_object_skeleton_add_interface (object, thing);
}
else
{
g_critical ("Unsupported type to publish: %s", G_OBJECT_TYPE_NAME (thing));
return;
}
if (uniquely)
g_dbus_object_manager_server_export_uniquely (self->object_manager, object);
else
g_dbus_object_manager_server_export (self->object_manager, object);
}
void
rpmostreed_daemon_unpublish (RpmostreedDaemon *self,
const gchar *path,
gpointer thing)
{
GDBusObject *object;
gboolean unexport = FALSE;
GList *interfaces, *l;
g_return_if_fail (RPMOSTREED_IS_DAEMON (self));
g_return_if_fail (path != NULL);
if (self->object_manager == NULL)
return;
object = g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (self->object_manager), path);
if (object == NULL)
return;
path = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
if (G_IS_DBUS_INTERFACE (thing))
{
g_debug ("unpublishing interface: %s %s", path,
g_dbus_interface_get_info (thing)->name);
unexport = TRUE;
interfaces = g_dbus_object_get_interfaces (object);
for (l = interfaces; l != NULL; l = g_list_next (l))
{
if (G_DBUS_INTERFACE (l->data) != G_DBUS_INTERFACE (thing))
unexport = FALSE;
}
g_list_free_full (interfaces, g_object_unref);
/*
* HACK: GDBusObjectManagerServer is broken ... and sends InterfaceRemoved
* too many times, if you remove all interfaces manually, and then unexport
* a GDBusObject. So only do it here if we're not unexporting the object.
*/
if (!unexport)
g_dbus_object_skeleton_remove_interface (G_DBUS_OBJECT_SKELETON (object), thing);
else
g_debug ("(unpublishing object, too)");
}
else if (thing == NULL)
{
unexport = TRUE;
}
else
{
g_critical ("Unsupported type to unpublish: %s", G_OBJECT_TYPE_NAME (thing));
}
if (unexport)
g_dbus_object_manager_server_unexport (self->object_manager, path);
g_object_unref (object);
}