/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2007-2011 Richard Hughes * * Licensed under the GNU General Public License Version 2 * * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ #include #include #include #include #include #include #include #include "pk-backend.h" #include "pk-dbus.h" #include "pk-engine.h" #include "pk-shared.h" #include "pk-transaction-db.h" #include "pk-transaction.h" #include "pk-scheduler.h" #ifndef HAVE_POLKIT_0_114 G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitAuthorizationResult, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitSubject, g_object_unref) #endif static void pk_engine_finalize (GObject *object); static void pk_engine_set_locked (PkEngine *engine, gboolean is_locked); #define PK_ENGINE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PK_TYPE_ENGINE, PkEnginePrivate)) /* how long to wait when we get the StateHasChanged method */ #define PK_ENGINE_STATE_CHANGED_TIMEOUT_PRIORITY 2 /* s */ /* how long to wait after the computer has been resumed or any system event */ #define PK_ENGINE_STATE_CHANGED_TIMEOUT_NORMAL 600 /* s */ struct PkEnginePrivate { GTimer *timer; gboolean notify_clients_of_upgrade; gboolean shutdown_as_soon_as_possible; PkScheduler *scheduler; PkTransactionDb *transaction_db; PkBackend *backend; GNetworkMonitor *network_monitor; GKeyFile *conf; PkDbus *dbus; GFileMonitor *monitor_conf; GFileMonitor *monitor_binary; GFileMonitor *monitor_offline; GFileMonitor *monitor_offline_upgrade; PkBitfield roles; PkBitfield groups; PkBitfield filters; gchar **mime_types; const gchar *backend_name; const gchar *backend_description; const gchar *backend_author; gchar *distro_id; guint timeout_priority_id; guint timeout_normal_id; PolkitAuthority *authority; gboolean locked; PkNetworkEnum network_state; guint owner_id; GDBusNodeInfo *introspection; GDBusConnection *connection; #ifdef HAVE_SYSTEMD_SD_LOGIN_H GDBusProxy *logind_proxy; gint logind_fd; #endif }; enum { SIGNAL_QUIT, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE (PkEngine, pk_engine, G_TYPE_OBJECT) /* prototype */ gboolean pk_engine_filter_check (const gchar *filter, GError **error); G_DEFINE_QUARK (pk-engine-error-quark, pk_engine_error) #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } GType pk_engine_error_get_type (void) { static GType etype = 0; if (etype == 0) { static const GEnumValue values[] = { ENUM_ENTRY (PK_ENGINE_ERROR_INVALID_STATE, "InvalidState"), ENUM_ENTRY (PK_ENGINE_ERROR_REFUSED_BY_POLICY, "RefusedByPolicy"), ENUM_ENTRY (PK_ENGINE_ERROR_CANNOT_SET_PROXY, "CannotSetProxy"), ENUM_ENTRY (PK_ENGINE_ERROR_NOT_SUPPORTED, "NotSupported"), ENUM_ENTRY (PK_ENGINE_ERROR_CANNOT_ALLOCATE_TID, "CannotAllocateTid"), ENUM_ENTRY (PK_ENGINE_ERROR_CANNOT_CHECK_AUTH, "CannotCheckAuth"), { 0, NULL, NULL } }; etype = g_enum_register_static ("PkEngineError", values); } return etype; } static void pk_engine_reset_timer (PkEngine *engine) { g_timer_reset (engine->priv->timer); } static void pk_engine_inhibit (PkEngine *engine); static void pk_engine_uninhibit (PkEngine *engine); static void pk_engine_set_inhibited (PkEngine *engine, gboolean inhibited) { g_return_if_fail (PK_IS_ENGINE (engine)); /* inhibit shutdown and suspend */ if (inhibited) pk_engine_inhibit (engine); else pk_engine_uninhibit (engine); } static void pk_engine_scheduler_changed_cb (PkScheduler *scheduler, PkEngine *engine) { g_auto(GStrv) transaction_list = NULL; g_return_if_fail (PK_IS_ENGINE (engine)); /* automatically locked if the transaction cannot be cancelled */ pk_engine_set_locked (engine, pk_scheduler_get_locked (scheduler)); pk_engine_set_inhibited (engine, pk_scheduler_get_inhibited (scheduler)); transaction_list = pk_scheduler_get_array (scheduler); g_dbus_connection_emit_signal (engine->priv->connection, NULL, PK_DBUS_PATH, PK_DBUS_INTERFACE, "TransactionListChanged", g_variant_new ("(^a&s)", transaction_list), NULL); pk_engine_reset_timer (engine); } static void pk_engine_emit_property_changed (PkEngine *engine, const gchar *property_name, GVariant *property_value) { GVariantBuilder builder; GVariantBuilder invalidated_builder; /* not yet connected */ if (engine->priv->connection == NULL) return; /* build the dict */ g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); g_variant_builder_add (&builder, "{sv}", property_name, property_value); g_dbus_connection_emit_signal (engine->priv->connection, NULL, PK_DBUS_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new ("(sa{sv}as)", PK_DBUS_INTERFACE, &builder, &invalidated_builder), NULL); } static void pk_engine_emit_offline_property_changed (PkEngine *engine, const gchar *property_name, GVariant *property_value) { GVariantBuilder builder; GVariantBuilder invalidated_builder; /* not yet connected */ if (engine->priv->connection == NULL) return; /* build the dict */ g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as")); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); if (property_value == NULL) { g_variant_builder_add (&invalidated_builder, "s", property_name); } else { g_variant_builder_add (&builder, "{sv}", property_name, property_value); } g_dbus_connection_emit_signal (engine->priv->connection, NULL, PK_DBUS_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new ("(sa{sv}as)", PK_DBUS_INTERFACE_OFFLINE, &builder, &invalidated_builder), NULL); } static void pk_engine_inhibit (PkEngine *engine) { #ifdef HAVE_SYSTEMD_SD_LOGIN_H g_autoptr(GError) error = NULL; g_autoptr(GUnixFDList) out_fd_list = NULL; g_autoptr(GVariant) res = NULL; /* already inhibited */ if (engine->priv->logind_fd != 0) return; /* not yet connected */ if (engine->priv->logind_proxy == NULL) { g_warning ("no logind connection to use"); return; } /* block shutdown and idle */ res = g_dbus_proxy_call_with_unix_fd_list_sync (engine->priv->logind_proxy, "Inhibit", g_variant_new ("(ssss)", "shutdown:idle", "Package Updater", "Package Update in Progress", "block"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* fd_list */ &out_fd_list, NULL, /* GCancellable */ &error); if (res == NULL) { g_warning ("Failed to Inhibit using logind: %s", error->message); return; } /* keep fd as cookie */ if (g_unix_fd_list_get_length (out_fd_list) != 1) { g_warning ("invalid response from logind"); return; } engine->priv->logind_fd = g_unix_fd_list_get (out_fd_list, 0, NULL); g_debug ("opened logind fd %i", engine->priv->logind_fd); #endif } static void pk_engine_uninhibit (PkEngine *engine) { #ifdef HAVE_SYSTEMD_SD_LOGIN_H if (engine->priv->logind_fd == 0) return; g_debug ("closed logind fd %i", engine->priv->logind_fd); close (engine->priv->logind_fd); engine->priv->logind_fd = 0; #endif } static void pk_engine_set_locked (PkEngine *engine, gboolean is_locked) { g_return_if_fail (PK_IS_ENGINE (engine)); /* already set */ if (engine->priv->locked == is_locked) return; engine->priv->locked = is_locked; /* emit */ pk_engine_emit_property_changed (engine, "Locked", g_variant_new_boolean (is_locked)); } static void pk_engine_backend_repo_list_changed_cb (PkBackend *backend, PkEngine *engine) { g_return_if_fail (PK_IS_ENGINE (engine)); g_debug ("emitting RepoListChanged"); g_dbus_connection_emit_signal (engine->priv->connection, NULL, PK_DBUS_PATH, PK_DBUS_INTERFACE, "RepoListChanged", NULL, NULL); } static void pk_engine_backend_updates_changed_cb (PkBackend *backend, PkEngine *engine) { g_return_if_fail (PK_IS_ENGINE (engine)); g_debug ("emitting UpdatesChanged"); g_dbus_connection_emit_signal (engine->priv->connection, NULL, PK_DBUS_PATH, PK_DBUS_INTERFACE, "UpdatesChanged", NULL, NULL); } static gboolean pk_engine_state_changed_cb (gpointer data) { PkEngine *engine = PK_ENGINE (data); g_autoptr(GError) error = NULL; g_return_val_if_fail (PK_IS_ENGINE (engine), FALSE); /* we're done something low-level */ if (!pk_offline_auth_invalidate (&error)) g_warning ("failed to invalidate: %s", error->message); /* if network is not up, then just reschedule */ if (!g_network_monitor_get_network_available (engine->priv->network_monitor)) { /* wait another timeout of PK_ENGINE_STATE_CHANGED_x_TIMEOUT */ return TRUE; } pk_backend_updates_changed (engine->priv->backend); /* reset, now valid */ engine->priv->timeout_priority_id = 0; engine->priv->timeout_normal_id = 0; /* reset the timer */ pk_engine_reset_timer (engine); return FALSE; } static void pk_engine_emit_restart_schedule (PkEngine *engine) { g_return_if_fail (PK_IS_ENGINE (engine)); g_debug ("emitting RestartSchedule"); g_dbus_connection_emit_signal (engine->priv->connection, NULL, PK_DBUS_PATH, PK_DBUS_INTERFACE, "RestartSchedule", NULL, NULL); } guint pk_engine_get_seconds_idle (PkEngine *engine) { guint idle; guint size; g_return_val_if_fail (PK_IS_ENGINE (engine), 0); g_return_val_if_fail (pk_is_thread_default (), 0); /* check for transactions running - a transaction that takes a *long* time might not * give sufficient percentage updates to not be marked as idle */ size = pk_scheduler_get_size (engine->priv->scheduler); if (size != 0) { g_debug ("engine idle zero as %i transactions in progress", size); return 0; } /* have we been updated? */ if (engine->priv->notify_clients_of_upgrade) { pk_engine_emit_restart_schedule (engine); return G_MAXUINT; } /* do we need to shutdown quickly */ if (engine->priv->shutdown_as_soon_as_possible) { g_debug ("need to restart daemon asap"); return G_MAXUINT; } idle = (guint) g_timer_elapsed (engine->priv->timer, NULL); return idle; } static gboolean pk_engine_set_proxy_internal (PkEngine *engine, const gchar *sender, const gchar *proxy_http, const gchar *proxy_https, const gchar *proxy_ftp, const gchar *proxy_socks, const gchar *no_proxy, const gchar *pac, GError **error) { gboolean ret; guint uid; g_autofree gchar *session = NULL; /* get uid */ uid = pk_dbus_get_uid (engine->priv->dbus, sender); if (uid == G_MAXUINT) { g_set_error_literal (error, PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "failed to get the uid"); return FALSE; } /* get session */ session = pk_dbus_get_session (engine->priv->dbus, sender); if (session == NULL) { g_set_error_literal (error, PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "failed to get the session"); return FALSE; } /* save to database */ ret = pk_transaction_db_set_proxy (engine->priv->transaction_db, uid, session, proxy_http, proxy_https, proxy_ftp, proxy_socks, no_proxy, pac); if (!ret) { g_set_error_literal (error, PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "failed to save the proxy in the database"); return FALSE; } return TRUE; } typedef struct { GDBusMethodInvocation *context; PkEngine *engine; gchar *sender; gchar *value1; gchar *value2; gchar *value3; gchar *value4; gchar *value5; gchar *value6; } PkEngineDbusState; static void pk_engine_action_obtain_proxy_authorization_finished_cb (PolkitAuthority *authority, GAsyncResult *res, PkEngineDbusState *state) { gboolean ret; PkEnginePrivate *priv = state->engine->priv; g_autoptr(GError) error = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(PolkitAuthorizationResult) result = NULL; /* finish the call */ result = polkit_authority_check_authorization_finish (priv->authority, res, &error_local); /* failed */ if (result == NULL) { error = g_error_new (PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "setting the proxy failed, could not check for auth: %s", error_local->message); g_dbus_method_invocation_return_gerror (state->context, error); goto out; } /* did not auth */ if (!polkit_authorization_result_get_is_authorized (result)) { error = g_error_new_literal (PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "failed to obtain auth"); g_dbus_method_invocation_return_gerror (state->context, error); goto out; } /* try to set the new proxy and save to database */ ret = pk_engine_set_proxy_internal (state->engine, state->sender, state->value1, state->value2, state->value3, state->value4, state->value5, state->value6, &error_local); if (!ret) { g_set_error (&error, PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "setting the proxy failed: %s", error_local->message); g_dbus_method_invocation_return_gerror (state->context, error); goto out; } /* only set after the auth success */ g_debug ("changing http proxy to %s for %s", state->value1, state->sender); g_debug ("changing https proxy to %s for %s", state->value2, state->sender); g_debug ("changing ftp proxy to %s for %s", state->value3, state->sender); g_debug ("changing socks proxy to %s for %s", state->value4, state->sender); g_debug ("changing no proxy to %s for %s", state->value5, state->sender); g_debug ("changing PAC proxy to %s for %s", state->value6, state->sender); /* all okay */ g_dbus_method_invocation_return_value (state->context, NULL); out: /* unref state, we're done */ g_object_unref (state->engine); g_free (state->sender); g_free (state->value1); g_free (state->value2); g_free (state->value3); g_free (state->value4); g_free (state->value5); g_free (state->value6); g_free (state); } static gboolean pk_engine_is_proxy_unchanged (PkEngine *engine, const gchar *sender, const gchar *proxy_http, const gchar *proxy_https, const gchar *proxy_ftp, const gchar *proxy_socks, const gchar *no_proxy, const gchar *pac) { guint uid; gboolean ret = FALSE; g_autofree gchar *session = NULL; g_autofree gchar *proxy_http_tmp = NULL; g_autofree gchar *proxy_https_tmp = NULL; g_autofree gchar *proxy_ftp_tmp = NULL; g_autofree gchar *proxy_socks_tmp = NULL; g_autofree gchar *no_proxy_tmp = NULL; g_autofree gchar *pac_tmp = NULL; /* get uid */ uid = pk_dbus_get_uid (engine->priv->dbus, sender); if (uid == G_MAXUINT) { g_warning ("failed to get the uid for %s", sender); return FALSE; } /* get session */ session = pk_dbus_get_session (engine->priv->dbus, sender); if (session == NULL) { g_warning ("failed to get the session for %s", sender); return FALSE; } /* find out if they are the same as what we tried to set before */ ret = pk_transaction_db_get_proxy (engine->priv->transaction_db, uid, session, &proxy_http_tmp, &proxy_https_tmp, &proxy_ftp_tmp, &proxy_socks_tmp, &no_proxy_tmp, &pac_tmp); if (!ret) return FALSE; /* are different? */ if (g_strcmp0 (proxy_http_tmp, proxy_http) != 0 || g_strcmp0 (proxy_https_tmp, proxy_https) != 0 || g_strcmp0 (proxy_ftp_tmp, proxy_ftp) != 0 || g_strcmp0 (proxy_socks_tmp, proxy_socks) != 0 || g_strcmp0 (no_proxy_tmp, no_proxy) != 0 || g_strcmp0 (pac_tmp, pac) != 0) return FALSE; return TRUE; } static void pk_engine_set_proxy (PkEngine *engine, const gchar *proxy_http, const gchar *proxy_https, const gchar *proxy_ftp, const gchar *proxy_socks, const gchar *no_proxy, const gchar *pac, GDBusMethodInvocation *context) { guint len; GError *error = NULL; gboolean ret; const gchar *sender; PkEngineDbusState *state; g_autoptr(PolkitSubject) subject = NULL; g_return_if_fail (PK_IS_ENGINE (engine)); /* blank is NULL */ if (proxy_http != NULL && proxy_http[0] == '\0') proxy_http = NULL; if (proxy_ftp != NULL && proxy_ftp[0] == '\0') proxy_ftp = NULL; g_debug ("SetProxy method called: %s, %s", proxy_http, proxy_ftp); /* check length of http */ len = pk_strlen (proxy_http, 1024); if (len == 1024) { error = g_error_new_literal (PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "http proxy was too long"); g_dbus_method_invocation_return_gerror (context, error); goto out; } /* check length of ftp */ len = pk_strlen (proxy_ftp, 1024); if (len == 1024) { error = g_error_new_literal (PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_SET_PROXY, "ftp proxy was too long"); g_dbus_method_invocation_return_gerror (context, error); goto out; } /* save sender */ sender = g_dbus_method_invocation_get_sender (context); /* is exactly the same proxy? */ ret = pk_engine_is_proxy_unchanged (engine, sender, proxy_http, proxy_https, proxy_ftp, proxy_socks, no_proxy, pac); if (ret) { g_debug ("not changing proxy as the same as before"); g_dbus_method_invocation_return_value (context, NULL); goto out; } /* check subject */ subject = polkit_system_bus_name_new (sender); /* cache state */ state = g_new0 (PkEngineDbusState, 1); state->context = context; state->engine = g_object_ref (engine); state->sender = g_strdup (sender); state->value1 = g_strdup (proxy_http); state->value2 = g_strdup (proxy_https); state->value3 = g_strdup (proxy_ftp); state->value4 = g_strdup (proxy_socks); state->value5 = g_strdup (no_proxy); state->value6 = g_strdup (pac); /* do authorization async */ polkit_authority_check_authorization (engine->priv->authority, subject, "org.freedesktop.packagekit.system-network-proxy-configure", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, (GAsyncReadyCallback) pk_engine_action_obtain_proxy_authorization_finished_cb, state); /* reset the timer */ pk_engine_reset_timer (engine); out: return; } static PkAuthorizeEnum pk_engine_can_authorize_action_id (PkEngine *engine, const gchar *action_id, const gchar *sender, GError **error) { g_autoptr(PolkitAuthorizationResult) res = NULL; g_autoptr(PolkitSubject) subject = NULL; /* check subject */ subject = polkit_system_bus_name_new (sender); /* check authorization (okay being sync as there's no blocking on the user) */ res = polkit_authority_check_authorization_sync (engine->priv->authority, subject, action_id, NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE, NULL, error); if (res == NULL) return PK_AUTHORIZE_ENUM_UNKNOWN; /* already yes */ if (polkit_authorization_result_get_is_authorized (res)) return PK_AUTHORIZE_ENUM_YES; /* could be yes with user input */ if (polkit_authorization_result_get_is_challenge (res)) return PK_AUTHORIZE_ENUM_INTERACTIVE; /* fall back to not letting user authenticate */ return PK_AUTHORIZE_ENUM_NO; } static void pk_engine_class_init (PkEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = pk_engine_finalize; /* signals */ signals[SIGNAL_QUIT] = g_signal_new ("quit", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_type_class_add_private (klass, sizeof (PkEnginePrivate)); } static void pk_engine_conf_file_changed_cb (GFileMonitor *file_monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, PkEngine *engine) { g_return_if_fail (PK_IS_ENGINE (engine)); g_debug ("setting shutdown_as_soon_as_possible TRUE"); engine->priv->shutdown_as_soon_as_possible = TRUE; } static void pk_engine_binary_file_changed_cb (GFileMonitor *file_monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, PkEngine *engine) { g_return_if_fail (PK_IS_ENGINE (engine)); g_debug ("setting notify_clients_of_upgrade TRUE"); engine->priv->notify_clients_of_upgrade = TRUE; } static void pk_engine_offline_file_changed_cb (GFileMonitor *file_monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, PkEngine *engine) { gboolean ret; g_return_if_fail (PK_IS_ENGINE (engine)); ret = g_file_test (PK_OFFLINE_PREPARED_FILENAME, G_FILE_TEST_EXISTS); pk_engine_emit_offline_property_changed (engine, "UpdatePrepared", g_variant_new_boolean (ret)); } static GVariant * pk_engine_offline_get_prepared_upgrade_property (GError **error) { GVariantBuilder builder; g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; if (!pk_offline_get_prepared_upgrade (&name, &version, error)) return NULL; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); if (name != NULL) g_variant_builder_add (&builder, "{sv}", "name", g_variant_new ("s", name)); if (version != NULL) g_variant_builder_add (&builder, "{sv}", "version", g_variant_new ("s", version)); return g_variant_builder_end (&builder); } static void pk_engine_offline_upgrade_file_changed_cb (GFileMonitor *file_monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, PkEngine *engine) { GVariant *prepared_upgrade; gboolean ret; g_return_if_fail (PK_IS_ENGINE (engine)); ret = g_file_test (PK_OFFLINE_PREPARED_UPGRADE_FILENAME, G_FILE_TEST_EXISTS); pk_engine_emit_offline_property_changed (engine, "UpgradePrepared", g_variant_new_boolean (ret)); prepared_upgrade = pk_engine_offline_get_prepared_upgrade_property (NULL); pk_engine_emit_offline_property_changed (engine, "PreparedUpgrade", prepared_upgrade); } static PkNetworkEnum pk_engine_get_network_state (GNetworkMonitor *network_monitor) { if (!g_network_monitor_get_network_available (network_monitor)) return PK_NETWORK_ENUM_OFFLINE; /* this isn't exactly true, but it's what the UI expects */ if (g_network_monitor_get_network_metered (network_monitor)) return PK_NETWORK_ENUM_MOBILE; return PK_NETWORK_ENUM_ONLINE; } static void pk_engine_network_state_changed_cb (GNetworkMonitor *network_monitor, gboolean available, PkEngine *engine) { PkNetworkEnum network_state; g_return_if_fail (PK_IS_ENGINE (engine)); network_state = pk_engine_get_network_state (network_monitor); if (network_state == engine->priv->network_state) return; engine->priv->network_state = network_state; /* emit */ pk_engine_emit_property_changed (engine, "NetworkState", g_variant_new_uint32 (network_state)); } static void pk_engine_setup_file_monitors (PkEngine *engine) { const gchar *filename = "/etc/PackageKit/PackageKit.conf"; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_binary = NULL; g_autoptr(GFile) file_conf = NULL; /* monitor the binary file for changes */ file_binary = g_file_new_for_path (LIBEXECDIR "/packagekitd"); engine->priv->monitor_binary = g_file_monitor_file (file_binary, G_FILE_MONITOR_NONE, NULL, &error); if (engine->priv->monitor_binary == NULL) { g_warning ("Failed to set watch on %s: %s", LIBEXECDIR "/packagekitd", error->message); return; } g_signal_connect (engine->priv->monitor_binary, "changed", G_CALLBACK (pk_engine_binary_file_changed_cb), engine); /* monitor config file for changes */ g_debug ("setting config file watch on %s", filename); file_conf = g_file_new_for_path (filename); engine->priv->monitor_conf = g_file_monitor_file (file_conf, G_FILE_MONITOR_NONE, NULL, &error); if (engine->priv->monitor_conf == NULL) { g_warning ("Failed to set watch on %s: %s", filename, error->message); return; } g_signal_connect (engine->priv->monitor_conf, "changed", G_CALLBACK (pk_engine_conf_file_changed_cb), engine); /* set up the prepared update monitor */ engine->priv->monitor_offline = pk_offline_get_prepared_monitor (NULL, &error); if (engine->priv->monitor_offline == NULL) { g_warning ("Failed to set watch on %s: %s", PK_OFFLINE_PREPARED_FILENAME, error->message); return; } g_signal_connect (engine->priv->monitor_offline, "changed", G_CALLBACK (pk_engine_offline_file_changed_cb), engine); /* set up the prepared system upgrade monitor */ engine->priv->monitor_offline_upgrade = pk_offline_get_prepared_upgrade_monitor (NULL, &error); if (engine->priv->monitor_offline_upgrade == NULL) { g_warning ("Failed to set watch on %s: %s", PK_OFFLINE_PREPARED_UPGRADE_FILENAME, error->message); return; } g_signal_connect (engine->priv->monitor_offline_upgrade, "changed", G_CALLBACK (pk_engine_offline_upgrade_file_changed_cb), engine); } gboolean pk_engine_load_backend (PkEngine *engine, GError **error) { /* load any backend init */ if (!pk_backend_load (engine->priv->backend, error)) return FALSE; /* load anything that can fail */ engine->priv->authority = polkit_authority_get_sync (NULL, error); if (engine->priv->authority == NULL) return FALSE; if (!pk_transaction_db_load (engine->priv->transaction_db, error)) return FALSE; /* create a new backend so we can get the static stuff */ engine->priv->roles = pk_backend_get_roles (engine->priv->backend); engine->priv->groups = pk_backend_get_groups (engine->priv->backend); engine->priv->filters = pk_backend_get_filters (engine->priv->backend); engine->priv->mime_types = pk_backend_get_mime_types (engine->priv->backend); engine->priv->backend_name = pk_backend_get_name (engine->priv->backend); engine->priv->backend_description = pk_backend_get_description (engine->priv->backend); engine->priv->backend_author = pk_backend_get_author (engine->priv->backend); return TRUE; } static GVariant * _g_variant_new_maybe_string (const gchar *value) { if (value == NULL) return g_variant_new_string (""); return g_variant_new_string (value); } static GVariant * pk_engine_offline_get_property (GDBusConnection *connection_, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { PkEngine *engine = PK_ENGINE (user_data); /* reset the timer */ pk_engine_reset_timer (engine); if (g_strcmp0 (property_name, "TriggerAction") == 0) { PkOfflineAction action = pk_offline_get_action (NULL); return g_variant_new_string (pk_offline_action_to_string (action)); } /* stat the file */ if (g_strcmp0 (property_name, "UpdatePrepared") == 0) { gboolean ret; ret = g_file_test (PK_OFFLINE_PREPARED_FILENAME, G_FILE_TEST_EXISTS); return g_variant_new_boolean (ret); } /* stat the file */ if (g_strcmp0 (property_name, "UpgradePrepared") == 0) { gboolean ret; ret = g_file_test (PK_OFFLINE_PREPARED_UPGRADE_FILENAME, G_FILE_TEST_EXISTS); return g_variant_new_boolean (ret); } /* look at the symlink target */ if (g_strcmp0 (property_name, "UpdateTriggered") == 0) { g_autofree gchar *link = NULL; link = g_file_read_link (PK_OFFLINE_TRIGGER_FILENAME, NULL); return g_variant_new_boolean (g_strcmp0 (link, PK_OFFLINE_PREPARED_FILENAME) == 0); } /* look at the symlink target */ if (g_strcmp0 (property_name, "UpgradeTriggered") == 0) { g_autofree gchar *link = NULL; link = g_file_read_link (PK_OFFLINE_TRIGGER_FILENAME, NULL); return g_variant_new_boolean (g_strcmp0 (link, PK_OFFLINE_PREPARED_UPGRADE_FILENAME) == 0); } if (g_strcmp0 (property_name, "PreparedUpgrade") == 0) { return pk_engine_offline_get_prepared_upgrade_property (error); } /* return an error */ g_set_error (error, PK_ENGINE_ERROR, PK_ENGINE_ERROR_NOT_SUPPORTED, "failed to get property '%s'", property_name); return NULL; } static GVariant * pk_engine_daemon_get_property (GDBusConnection *connection_, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { PkEngine *engine = PK_ENGINE (user_data); g_return_val_if_fail (pk_is_thread_default (), NULL); /* reset the timer */ pk_engine_reset_timer (engine); if (g_strcmp0 (property_name, "VersionMajor") == 0) return g_variant_new_uint32 (PK_MAJOR_VERSION); if (g_strcmp0 (property_name, "VersionMinor") == 0) return g_variant_new_uint32 (PK_MINOR_VERSION); if (g_strcmp0 (property_name, "VersionMicro") == 0) return g_variant_new_uint32 (PK_MICRO_VERSION); if (g_strcmp0 (property_name, "BackendName") == 0) return g_variant_new_string (engine->priv->backend_name); if (g_strcmp0 (property_name, "BackendDescription") == 0) return _g_variant_new_maybe_string (engine->priv->backend_description); if (g_strcmp0 (property_name, "BackendAuthor") == 0) return _g_variant_new_maybe_string (engine->priv->backend_author); if (g_strcmp0 (property_name, "Roles") == 0) return g_variant_new_uint64 (engine->priv->roles); if (g_strcmp0 (property_name, "Groups") == 0) return g_variant_new_uint64 (engine->priv->groups); if (g_strcmp0 (property_name, "Filters") == 0) return g_variant_new_uint64 (engine->priv->filters); if (g_strcmp0 (property_name, "MimeTypes") == 0) return g_variant_new_strv ((const gchar * const *) engine->priv->mime_types, -1); if (g_strcmp0 (property_name, "Locked") == 0) return g_variant_new_boolean (engine->priv->locked); if (g_strcmp0 (property_name, "NetworkState") == 0) return g_variant_new_uint32 (engine->priv->network_state); if (g_strcmp0 (property_name, "DistroId") == 0) return _g_variant_new_maybe_string (engine->priv->distro_id); /* return an error */ g_set_error (error, PK_ENGINE_ERROR, PK_ENGINE_ERROR_NOT_SUPPORTED, "failed to get property '%s'", property_name); return NULL; } static gboolean pk_engine_package_name_in_strv (gchar **strv, PkPackage *pkg) { guint i; for (i = 0; strv[i] != NULL; i++) { if (g_strcmp0 (strv[i], pk_package_get_name (pkg)) == 0) return TRUE; } return FALSE; } static GVariant * pk_engine_get_package_history_pkg (PkTransactionPast *item, PkPackage *pkg) { GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); g_variant_builder_add (&builder, "{sv}", "info", g_variant_new_uint32 (pk_package_get_info (pkg))); g_variant_builder_add (&builder, "{sv}", "source", g_variant_new_string (pk_package_get_data (pkg))); g_variant_builder_add (&builder, "{sv}", "version", g_variant_new_string (pk_package_get_version (pkg))); g_variant_builder_add (&builder, "{sv}", "timestamp", g_variant_new_uint64 (pk_transaction_past_get_timestamp (item))); g_variant_builder_add (&builder, "{sv}", "user-id", g_variant_new_uint32 (pk_transaction_past_get_uid (item))); return g_variant_builder_end (&builder); } static gboolean pk_engine_is_package_history_interesing (PkPackage *package) { gboolean ret; switch (pk_package_get_info (package)) { case PK_INFO_ENUM_INSTALLING: case PK_INFO_ENUM_REMOVING: case PK_INFO_ENUM_UPDATING: ret = TRUE; break; default: ret = FALSE; break; } return ret; } static GVariant * pk_engine_get_package_history (PkEngine *engine, gchar **package_names, guint max_size, GError **error) { const gchar *data; const gchar *pkgname; gboolean ret; gchar *key; gint64 timestamp; GList *l; GList *list; GPtrArray *array = NULL; guint i; GVariantBuilder builder; GVariant *value = NULL; PkTransactionPast *item; g_autoptr(GHashTable) deduplicate_hash = NULL; g_autoptr(GHashTable) pkgname_hash = NULL; g_autoptr(GList) keys = NULL; g_autoptr(PkPackage) package_tmp = NULL; list = pk_transaction_db_get_list (engine->priv->transaction_db, max_size); /* simplify the loop */ if (max_size == 0) max_size = G_MAXUINT; pkgname_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); deduplicate_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); package_tmp = pk_package_new (); for (l = list; l != NULL; l = l->next) { g_auto(GStrv) package_lines = NULL; item = PK_TRANSACTION_PAST (l->data); /* ignore anything that failed */ if (!pk_transaction_past_get_succeeded (item)) continue; /* split up data */ data = pk_transaction_past_get_data (item); if (data == NULL) continue; package_lines = g_strsplit (data, "\n", -1); for (i = 0; package_lines[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; ret = pk_package_parse (package_tmp, package_lines[i], &error_local); if (!ret) { g_warning ("Failed to parse package: '%s': %s", package_lines[i], error_local->message); continue; } /* not the package we care about */ if (!pk_engine_package_name_in_strv (package_names, package_tmp)) continue; /* not a state we care about */ if (!pk_engine_is_package_history_interesing (package_tmp)) continue; /* transactions without a timestamp are not interesting */ timestamp = pk_transaction_past_get_timestamp (item); if (timestamp == 0) continue; /* de-duplicate the entry, in the case of multiarch */ key = g_strdup_printf ("%s-%" G_GINT64_FORMAT, pk_package_get_name (package_tmp), timestamp); if (g_hash_table_lookup (deduplicate_hash, key) != NULL) { g_free (key); continue; } g_hash_table_insert (deduplicate_hash, key, package_lines[i]); /* get the blob for this data item */ value = pk_engine_get_package_history_pkg (item, package_tmp); if (value == NULL) continue; /* find the array */ pkgname = pk_package_get_name (package_tmp); array = g_hash_table_lookup (pkgname_hash, pkgname); if (array == NULL) { array = g_ptr_array_new (); g_hash_table_insert (pkgname_hash, g_strdup (pkgname), array); } g_ptr_array_add (array, value); } } /* no history returns an empty array */ if (g_hash_table_size (pkgname_hash) == 0) { value = g_variant_new_array (G_VARIANT_TYPE ("{saa{sv}}"), NULL, 0); goto out; } /* we have a hash of pkgname:GPtrArray where the GPtrArray is an array * of GVariants of type a{sv} */ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); keys = g_hash_table_get_keys (pkgname_hash); for (l = keys; l != NULL; l = l->next) { pkgname = l->data; array = g_hash_table_lookup (pkgname_hash, pkgname); /* create aa{sv} */ value = g_variant_new_array (NULL, (GVariant * const *) array->pdata, MIN (array->len, max_size)); g_variant_builder_add (&builder, "{s@aa{sv}}", pkgname, value); } value = g_variant_builder_end (&builder); out: g_list_free_full (list, (GDestroyNotify) g_object_unref); return value; } static void pk_engine_daemon_method_call (GDBusConnection *connection_, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { const gchar *tmp = NULL; gboolean ret; guint time_since; GVariant *value = NULL; GVariant *tuple = NULL; PkAuthorizeEnum result_enum; PkEngine *engine = PK_ENGINE (user_data); PkRoleEnum role; guint size; gboolean is_priority = TRUE; g_autoptr(GError) error = NULL; g_autofree gchar *data = NULL; g_auto(GStrv) array = NULL; g_return_if_fail (PK_IS_ENGINE (engine)); g_return_if_fail (pk_is_thread_default ()); /* reset the timer */ pk_engine_reset_timer (engine); if (g_strcmp0 (method_name, "GetTimeSinceAction") == 0) { g_variant_get (parameters, "(u)", &role); time_since = pk_transaction_db_action_time_since (engine->priv->transaction_db, role); value = g_variant_new ("(u)", time_since); g_dbus_method_invocation_return_value (invocation, value); return; } if (g_strcmp0 (method_name, "GetDaemonState") == 0) { data = pk_scheduler_get_state (engine->priv->scheduler); value = g_variant_new ("(s)", data); g_dbus_method_invocation_return_value (invocation, value); return; } if (g_strcmp0 (method_name, "GetPackageHistory") == 0) { g_autofree gchar **package_names = NULL; g_variant_get (parameters, "(^a&su)", &package_names, &size); if (package_names == NULL || g_strv_length (package_names) == 0) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_NOT_SUPPORTED, "history for package name invalid"); return; } value = pk_engine_get_package_history (engine, package_names, size, &error); if (value == NULL) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_NOT_SUPPORTED, "history for package name %s failed: %s", package_names[0], error->message); return; } tuple = g_variant_new_tuple (&value, 1); g_dbus_method_invocation_return_value (invocation, tuple); return; } if (g_strcmp0 (method_name, "CreateTransaction") == 0) { g_debug ("CreateTransaction method called"); data = pk_transaction_db_generate_id (engine->priv->transaction_db); g_assert (data != NULL); ret = pk_scheduler_create (engine->priv->scheduler, data, sender, &error); if (!ret) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_CHECK_AUTH, "could not create transaction %s: %s", data, error->message); return; } g_debug ("sending object path: '%s'", data); value = g_variant_new ("(o)", data); g_dbus_method_invocation_return_value (invocation, value); return; } if (g_strcmp0 (method_name, "GetTransactionList") == 0) { g_auto(GStrv) transaction_list = NULL; transaction_list = pk_scheduler_get_array (engine->priv->scheduler); value = g_variant_new ("(^a&o)", transaction_list); g_dbus_method_invocation_return_value (invocation, value); return; } if (g_strcmp0 (method_name, "SuggestDaemonQuit") == 0) { /* attempt to kill background tasks */ pk_scheduler_cancel_queued (engine->priv->scheduler); pk_scheduler_cancel_background (engine->priv->scheduler); /* can we exit straight away */ size = pk_scheduler_get_size (engine->priv->scheduler); if (size == 0) { g_debug ("emitting quit"); g_signal_emit (engine, signals[SIGNAL_QUIT], 0); g_dbus_method_invocation_return_value (invocation, NULL); return; } /* This will wait from 0..10 seconds, depending on the status of * pk_main_timeout_check_cb() - usually it should be a few seconds * after the last transaction */ engine->priv->shutdown_as_soon_as_possible = TRUE; g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (method_name, "StateHasChanged") == 0) { /* have we already scheduled priority? */ if (engine->priv->timeout_priority_id != 0) { g_debug ("Already asked to refresh priority state less than %i seconds ago", PK_ENGINE_STATE_CHANGED_TIMEOUT_PRIORITY); g_dbus_method_invocation_return_value (invocation, NULL); return; } /* don't bombard the user 10 seconds after resuming */ g_variant_get (parameters, "(&s)", &tmp); if (g_strcmp0 (tmp, "resume") == 0) is_priority = FALSE; /* are we normal, and already scheduled normal? */ if (!is_priority && engine->priv->timeout_normal_id != 0) { g_debug ("Already asked to refresh normal state less than %i seconds ago", PK_ENGINE_STATE_CHANGED_TIMEOUT_NORMAL); g_dbus_method_invocation_return_value (invocation, NULL); return; } /* are we priority, and already scheduled normal? */ if (is_priority && engine->priv->timeout_normal_id != 0) { /* clear normal, as we are about to schedule a priority */ g_source_remove (engine->priv->timeout_normal_id); engine->priv->timeout_normal_id = 0; } /* wait a little delay in case we get multiple requests */ if (is_priority) { engine->priv->timeout_priority_id = g_timeout_add_seconds (PK_ENGINE_STATE_CHANGED_TIMEOUT_PRIORITY, pk_engine_state_changed_cb, engine); g_source_set_name_by_id (engine->priv->timeout_priority_id, "[PkEngine] priority"); } else { engine->priv->timeout_normal_id = g_timeout_add_seconds (PK_ENGINE_STATE_CHANGED_TIMEOUT_NORMAL, pk_engine_state_changed_cb, engine); g_source_set_name_by_id (engine->priv->timeout_normal_id, "[PkEngine] normal"); } g_dbus_method_invocation_return_value (invocation, NULL); return; } if (g_strcmp0 (method_name, "SetProxy") == 0) { array = g_new0 (gchar *, 7); g_variant_get (parameters, "(ssssss)", &array[0], &array[1], &array[2], &array[3], &array[4], &array[5]); pk_engine_set_proxy (engine, array[0], array[1], array[2], array[3], array[4], array[5], invocation); return; } if (g_strcmp0 (method_name, "CanAuthorize") == 0) { g_variant_get (parameters, "(&s)", &tmp); result_enum = pk_engine_can_authorize_action_id (engine, tmp, sender, &error); if (result_enum == PK_AUTHORIZE_ENUM_UNKNOWN) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_CANNOT_CHECK_AUTH, "failed to check authorisation %s: %s", tmp, error->message); return; } /* all okay */ value = g_variant_new ("(u)", result_enum); g_dbus_method_invocation_return_value (invocation, value); return; } } typedef enum { PK_ENGINE_OFFLINE_ROLE_CANCEL, PK_ENGINE_OFFLINE_ROLE_CLEAR_RESULTS, PK_ENGINE_OFFLINE_ROLE_TRIGGER, PK_ENGINE_OFFLINE_ROLE_TRIGGER_UPGRADE, PK_ENGINE_OFFLINE_ROLE_LAST } PkEngineOfflineRole; typedef struct { GDBusMethodInvocation *invocation; PkEngine *engine; PkEngineOfflineRole role; PkOfflineAction action; } PkEngineOfflineAsyncHelper; static void pk_engine_offline_helper_free (PkEngineOfflineAsyncHelper *helper) { g_object_unref (helper->engine); g_object_unref (helper->invocation); g_free (helper); } static void pk_engine_offline_helper_cb (GObject *source, GAsyncResult *res, gpointer user_data) { PkEngineOfflineAsyncHelper *helper = (PkEngineOfflineAsyncHelper *) user_data; PkOfflineAction action; GVariant *prepared_upgrade; gboolean ret; g_autofree gchar *link = NULL; g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(PolkitAuthorizationResult) result = NULL; /* finish the call */ result = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source), res, &error); if (result == NULL) { g_dbus_method_invocation_return_error (helper->invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_DENIED, "could not check for auth: %s", error->message); pk_engine_offline_helper_free (helper); return; } /* did not auth */ if (!polkit_authorization_result_get_is_authorized (result)) { g_dbus_method_invocation_return_error (helper->invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_DENIED, "failed to obtain auth"); pk_engine_offline_helper_free (helper); return; } switch (helper->role) { case PK_ENGINE_OFFLINE_ROLE_CLEAR_RESULTS: ret = pk_offline_auth_clear_results (&error); break; case PK_ENGINE_OFFLINE_ROLE_CANCEL: ret = pk_offline_auth_cancel (&error); break; case PK_ENGINE_OFFLINE_ROLE_TRIGGER: ret = pk_offline_auth_trigger (helper->action, &error); break; case PK_ENGINE_OFFLINE_ROLE_TRIGGER_UPGRADE: ret = pk_offline_auth_trigger_upgrade (helper->action, &error); break; default: g_assert_not_reached (); } if (!ret) { g_dbus_method_invocation_return_error (helper->invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_INVALID_STATE, "%s", error->message); pk_engine_offline_helper_free (helper); return; } /* refresh cached dbus properties */ action = pk_offline_get_action (NULL); pk_engine_emit_offline_property_changed (helper->engine, "TriggerAction", g_variant_new_string (pk_offline_action_to_string (action))); link = g_file_read_link (PK_OFFLINE_TRIGGER_FILENAME, NULL); pk_engine_emit_offline_property_changed (helper->engine, "UpdateTriggered", g_variant_new_boolean (g_strcmp0 (link, PK_OFFLINE_PREPARED_FILENAME) == 0)); pk_engine_emit_offline_property_changed (helper->engine, "UpgradeTriggered", g_variant_new_boolean (g_strcmp0 (link, PK_OFFLINE_PREPARED_UPGRADE_FILENAME) == 0)); prepared_upgrade = pk_engine_offline_get_prepared_upgrade_property (NULL); pk_engine_emit_offline_property_changed (helper->engine, "PreparedUpgrade", prepared_upgrade); g_dbus_method_invocation_return_value (helper->invocation, NULL); pk_engine_offline_helper_free (helper); } static void pk_engine_offline_method_call (GDBusConnection *connection_, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { PkEngine *engine = PK_ENGINE (user_data); PkEngineOfflineAsyncHelper *helper; g_autoptr(GError) error = NULL; g_autoptr(PolkitSubject) subject = NULL; g_return_if_fail (PK_IS_ENGINE (engine)); /* reset the timer */ pk_engine_reset_timer (engine); /* set up polkit */ subject = polkit_system_bus_name_new (sender); if (g_strcmp0 (method_name, "Cancel") == 0) { helper = g_new0 (PkEngineOfflineAsyncHelper, 1); helper->engine = g_object_ref (engine); helper->role = PK_ENGINE_OFFLINE_ROLE_CANCEL; helper->invocation = g_object_ref (invocation); polkit_authority_check_authorization (engine->priv->authority, subject, "org.freedesktop.packagekit.trigger-offline-update", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, pk_engine_offline_helper_cb, helper); return; } if (g_strcmp0 (method_name, "ClearResults") == 0) { helper = g_new0 (PkEngineOfflineAsyncHelper, 1); helper->engine = g_object_ref (engine); helper->role = PK_ENGINE_OFFLINE_ROLE_CLEAR_RESULTS; helper->invocation = g_object_ref (invocation); polkit_authority_check_authorization (engine->priv->authority, subject, "org.freedesktop.packagekit.clear-offline-update", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, pk_engine_offline_helper_cb, helper); return; } if (g_strcmp0 (method_name, "Trigger") == 0) { const gchar *tmp; PkOfflineAction action; g_variant_get (parameters, "(&s)", &tmp); action = pk_offline_action_from_string (tmp); if (action == PK_OFFLINE_ACTION_UNKNOWN) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_NOT_SUPPORTED, "action %s unsupported", tmp); return; } helper = g_new0 (PkEngineOfflineAsyncHelper, 1); helper->engine = g_object_ref (engine); helper->role = PK_ENGINE_OFFLINE_ROLE_TRIGGER; helper->invocation = g_object_ref (invocation); helper->action = action; polkit_authority_check_authorization (engine->priv->authority, subject, "org.freedesktop.packagekit.trigger-offline-update", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, pk_engine_offline_helper_cb, helper); return; } if (g_strcmp0 (method_name, "TriggerUpgrade") == 0) { const gchar *tmp; PkOfflineAction action; g_variant_get (parameters, "(&s)", &tmp); action = pk_offline_action_from_string (tmp); if (action == PK_OFFLINE_ACTION_UNKNOWN) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_NOT_SUPPORTED, "action %s unsupported", tmp); return; } helper = g_new0 (PkEngineOfflineAsyncHelper, 1); helper->engine = g_object_ref (engine); helper->role = PK_ENGINE_OFFLINE_ROLE_TRIGGER_UPGRADE; helper->invocation = g_object_ref (invocation); helper->action = action; polkit_authority_check_authorization (engine->priv->authority, subject, "org.freedesktop.packagekit.trigger-offline-upgrade", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, pk_engine_offline_helper_cb, helper); return; } if (g_strcmp0 (method_name, "GetPrepared") == 0) { g_auto(GStrv) package_ids = NULL; GVariant *value = NULL; package_ids = pk_offline_get_prepared_ids (&error); if (package_ids == NULL && error->code != PK_OFFLINE_ERROR_NO_DATA) { g_dbus_method_invocation_return_error (invocation, PK_ENGINE_ERROR, PK_ENGINE_ERROR_INVALID_STATE, "%s", error->message); return; } if (package_ids != NULL) { value = g_variant_new ("(^as)", package_ids); } else { value = g_variant_new ("(as)", NULL); } g_dbus_method_invocation_return_value (invocation, value); return; } } #ifdef HAVE_SYSTEMD_SD_LOGIN_H static void pk_engine_proxy_logind_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) error = NULL; PkEngine *engine = PK_ENGINE (user_data); engine->priv->logind_proxy = g_dbus_proxy_new_finish (res, &error); if (engine->priv->logind_proxy == NULL) g_warning ("failed to connect to logind: %s", error->message); } #endif static void pk_engine_on_bus_acquired_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { PkEngine *engine = PK_ENGINE (user_data); guint registration_id; static const GDBusInterfaceVTable iface_daemon_vtable = { .method_call = pk_engine_daemon_method_call, .get_property = pk_engine_daemon_get_property, .set_property = NULL }; static const GDBusInterfaceVTable iface_offline_vtable = { .method_call = pk_engine_offline_method_call, .get_property = pk_engine_offline_get_property, .set_property = NULL }; /* save copy for emitting signals */ engine->priv->connection = g_object_ref (connection); #ifdef HAVE_SYSTEMD_SD_LOGIN_H /* connect to logind */ g_dbus_proxy_new (connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", NULL, /* GCancellable */ pk_engine_proxy_logind_cb, engine); #endif /* register org.freedesktop.PackageKit */ registration_id = g_dbus_connection_register_object (connection, PK_DBUS_PATH, engine->priv->introspection->interfaces[0], &iface_daemon_vtable, engine, /* user_data */ NULL, /* user_data_free_func */ NULL); /* GError** */ g_assert (registration_id > 0); registration_id = g_dbus_connection_register_object (connection, PK_DBUS_PATH, engine->priv->introspection->interfaces[1], &iface_offline_vtable, engine, /* user_data */ NULL, /* user_data_free_func */ NULL); /* GError** */ g_assert (registration_id > 0); } static void pk_engine_on_name_acquired_cb (GDBusConnection *connection_, const gchar *name, gpointer user_data) { g_debug ("PkEngine: acquired name: %s", name); } static void pk_engine_on_name_lost_cb (GDBusConnection *connection_, const gchar *name, gpointer user_data) { PkEngine *engine = PK_ENGINE (user_data); g_debug ("PkEngine: lost name: %s", name); g_signal_emit (engine, signals[SIGNAL_QUIT], 0); } static void pk_engine_init (PkEngine *engine) { g_autoptr(GError) error = NULL; g_autofree gchar *filename = NULL; engine->priv = PK_ENGINE_GET_PRIVATE (engine); /* load introspection */ engine->priv->introspection = pk_load_introspection (PK_DBUS_INTERFACE ".xml", &error); if (engine->priv->introspection == NULL) { g_error ("PkEngine: failed to load daemon introspection: %s", error->message); } /* clear the download cache */ filename = g_build_filename (LOCALSTATEDIR, "cache", "PackageKit", "downloads", NULL); if (g_file_test (filename, G_FILE_TEST_EXISTS)) { g_debug ("clearing download cache at %s", filename); pk_directory_remove_contents (filename); } /* proxy the network state */ engine->priv->network_monitor = g_network_monitor_get_default (); g_signal_connect (engine->priv->network_monitor, "network-changed", G_CALLBACK (pk_engine_network_state_changed_cb), engine); engine->priv->network_state = pk_engine_get_network_state (engine->priv->network_monitor); /* try to get the distro id */ engine->priv->distro_id = pk_get_distro_id (); engine->priv->timer = g_timer_new (); /* we need the uid and the session for the proxy setting mechanism */ engine->priv->dbus = pk_dbus_new (); /* we need to be able to clear this */ engine->priv->timeout_priority_id = 0; engine->priv->timeout_normal_id = 0; /* setup file watches */ pk_engine_setup_file_monitors (engine); /* we use a trasaction db to store old transactions */ engine->priv->transaction_db = pk_transaction_db_new (); /* own the object */ engine->priv->owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM, PK_DBUS_SERVICE, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE, pk_engine_on_bus_acquired_cb, pk_engine_on_name_acquired_cb, pk_engine_on_name_lost_cb, engine, NULL); } static void pk_engine_finalize (GObject *object) { PkEngine *engine; g_return_if_fail (object != NULL); g_return_if_fail (PK_IS_ENGINE (object)); engine = PK_ENGINE (object); g_return_if_fail (engine->priv != NULL); /* if we set an state changed notifier, clear */ if (engine->priv->timeout_priority_id != 0) { g_source_remove (engine->priv->timeout_priority_id); engine->priv->timeout_priority_id = 0; } if (engine->priv->timeout_normal_id != 0) { g_source_remove (engine->priv->timeout_normal_id); engine->priv->timeout_normal_id = 0; } /* unlock if we locked this */ if (!pk_backend_unload (engine->priv->backend)) g_warning ("couldn't unload the backend"); /* unown */ if (engine->priv->owner_id > 0) g_bus_unown_name (engine->priv->owner_id); if (engine->priv->introspection != NULL) g_dbus_node_info_unref (engine->priv->introspection); if (engine->priv->connection != NULL) g_object_unref (engine->priv->connection); #ifdef HAVE_SYSTEMD_SD_LOGIN_H /* uninhibit */ if (engine->priv->logind_fd != 0) close (engine->priv->logind_fd); if (engine->priv->logind_proxy != NULL) g_object_unref (engine->priv->logind_proxy); #endif /* compulsory gobjects */ g_timer_destroy (engine->priv->timer); g_object_unref (engine->priv->monitor_conf); g_object_unref (engine->priv->monitor_binary); g_object_unref (engine->priv->monitor_offline); g_object_unref (engine->priv->monitor_offline_upgrade); g_object_unref (engine->priv->scheduler); g_object_unref (engine->priv->transaction_db); if (engine->priv->authority != NULL) g_object_unref (engine->priv->authority); g_object_unref (engine->priv->backend); g_key_file_unref (engine->priv->conf); g_object_unref (engine->priv->dbus); g_strfreev (engine->priv->mime_types); g_free (engine->priv->distro_id); G_OBJECT_CLASS (pk_engine_parent_class)->finalize (object); } PkEngine * pk_engine_new (GKeyFile *conf) { PkEngine *engine; engine = g_object_new (PK_TYPE_ENGINE, NULL); engine->priv->conf = g_key_file_ref (conf); engine->priv->backend = pk_backend_new (engine->priv->conf); g_signal_connect (engine->priv->backend, "repo-list-changed", G_CALLBACK (pk_engine_backend_repo_list_changed_cb), engine); g_signal_connect (engine->priv->backend, "updates-changed", G_CALLBACK (pk_engine_backend_updates_changed_cb), engine); engine->priv->scheduler = pk_scheduler_new (engine->priv->conf); pk_scheduler_set_backend (engine->priv->scheduler, engine->priv->backend); g_signal_connect (engine->priv->scheduler, "changed", G_CALLBACK (pk_engine_scheduler_changed_cb), engine); return PK_ENGINE (engine); }