blob: 767d2ec8a54e098590099214b515d14a5aa28b7e [file] [log] [blame]
Richard Hughes8dbfb1c2015-02-26 13:07:40 +00001/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
4 *
5 * Licensed under the GNU General Public License Version 2
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22#include "config.h"
23
24#include <stdlib.h>
25#include <gio/gio.h>
26#include <gio/gunixfdlist.h>
27#include <glib/gi18n.h>
28#include <locale.h>
Richard Hughesf508e762015-02-27 12:49:36 +000029#include <polkit/polkit.h>
Richard Hughes8dbfb1c2015-02-26 13:07:40 +000030
31#include "fu-cleanup.h"
32#include "fu-common.h"
Richard Hughes8bbfdf42015-02-26 22:28:09 +000033#include "fu-debug.h"
Richard Hughes8dbfb1c2015-02-26 13:07:40 +000034#include "fu-device.h"
35#include "fu-provider-uefi.h"
36#include "fu-resources.h"
37
Richard Hughes8dbfb1c2015-02-26 13:07:40 +000038typedef struct {
39 GDBusConnection *connection;
40 GDBusNodeInfo *introspection_daemon;
41 GMainLoop *loop;
42 GPtrArray *devices;
Richard Hughes8bbfdf42015-02-26 22:28:09 +000043 GPtrArray *providers;
Richard Hughesf508e762015-02-27 12:49:36 +000044 PolkitAuthority *authority;
Richard Hughes8dbfb1c2015-02-26 13:07:40 +000045} FuMainPrivate;
46
Richard Hughesf508e762015-02-27 12:49:36 +000047typedef struct {
48 FuDevice *device;
49 FuProvider *provider;
50} FuDeviceItem;
51
Richard Hughes8dbfb1c2015-02-26 13:07:40 +000052/**
53 * fu_main_get_device_list_as_strv:
54 **/
55static gchar **
56fu_main_get_device_list_as_strv (FuMainPrivate *priv)
57{
58 gchar **val;
59 guint i;
60 FuDevice *dev_tmp;
61
62 val = g_new0 (gchar *, priv->devices->len + 1);
63 for (i = 0; i < priv->devices->len; i++) {
64 dev_tmp = g_ptr_array_index (priv->devices, i);
65 val[i] = g_strdup (fu_device_get_id (dev_tmp));
66 }
67 return val;
68}
69
70/**
Richard Hughesf508e762015-02-27 12:49:36 +000071 * fu_main_item_free:
Richard Hughes8bbfdf42015-02-26 22:28:09 +000072 **/
Richard Hughesf508e762015-02-27 12:49:36 +000073static void
74fu_main_item_free (FuDeviceItem *item)
Richard Hughes8bbfdf42015-02-26 22:28:09 +000075{
Richard Hughesf508e762015-02-27 12:49:36 +000076 g_object_unref (item->device);
77 g_object_unref (item->provider);
78 g_free (item);
79}
80
81/**
82 * fu_main_get_item_by_id:
83 **/
84static FuDeviceItem *
85fu_main_get_item_by_id (FuMainPrivate *priv, const gchar *id)
86{
87 FuDeviceItem *item;
Richard Hughes8bbfdf42015-02-26 22:28:09 +000088 guint i;
89
90 for (i = 0; i < priv->devices->len; i++) {
Richard Hughesf508e762015-02-27 12:49:36 +000091 item = g_ptr_array_index (priv->devices, i);
92 if (g_strcmp0 (fu_device_get_id (item->device), id) == 0)
93 return item;
Richard Hughes8bbfdf42015-02-26 22:28:09 +000094 }
95 return NULL;
96}
97
Richard Hughesf508e762015-02-27 12:49:36 +000098typedef struct {
99 GDBusMethodInvocation *invocation;
100 FuMainPrivate *priv;
101 gint fd;
102 gchar *id;
103} FuMainAuthHelper;
104
105/**
106 * fu_main_helper_free:
107 **/
108static void
109fu_main_helper_free (FuMainAuthHelper *helper)
110{
111 g_object_unref (helper->invocation);
112 g_free (helper->id);
113 g_free (helper);
114}
115
116/**
117 * fu_main_check_authorization_cb:
118 **/
119static void
120fu_main_check_authorization_cb (GObject *source, GAsyncResult *res, gpointer user_data)
121{
122 FuMainAuthHelper *helper = (FuMainAuthHelper *) user_data;
123 FuDeviceItem *item;
124 _cleanup_error_free_ GError *error = NULL;
125 _cleanup_object_unref_ PolkitAuthorizationResult *auth = NULL;
126
127 /* get result */
128 auth = polkit_authority_check_authorization_finish (POLKIT_AUTHORITY (source),
129 res, &error);
130 if (auth == NULL) {
131 g_dbus_method_invocation_return_error (helper->invocation,
132 FU_ERROR,
133 FU_ERROR_INTERNAL,
134 "could not check for auth: %s",
135 error->message);
136 fu_main_helper_free (helper);
137 return;
138 }
139
140 /* did not auth */
141 if (!polkit_authorization_result_get_is_authorized (auth)) {
142 g_dbus_method_invocation_return_error (helper->invocation,
143 FU_ERROR,
144 FU_ERROR_INTERNAL,
145 "failed to obtain auth");
146 fu_main_helper_free (helper);
147 return;
148 }
149
150 /* check the device still exists */
151 item = fu_main_get_item_by_id (helper->priv, helper->id);
152 if (item == NULL) {
153 g_dbus_method_invocation_return_error (helper->invocation,
154 FU_ERROR,
155 FU_ERROR_INTERNAL,
156 "device %s was removed",
157 helper->id);
158 fu_main_helper_free (helper);
159 return;
160 }
161
162 /* run the correct provider that added this */
163 if (!fu_provider_update_offline (item->provider,
164 item->device,
165 helper->fd, &error)) {
166 g_dbus_method_invocation_return_gerror (helper->invocation,
167 error);
168 fu_main_helper_free (helper);
169 return;
170 }
171
172 /* success */
173 g_dbus_method_invocation_return_value (helper->invocation, NULL);
174 fu_main_helper_free (helper);
175}
176
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000177/**
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000178 * fu_main_daemon_method_call:
179 **/
180static void
181fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender,
182 const gchar *object_path, const gchar *interface_name,
183 const gchar *method_name, GVariant *parameters,
184 GDBusMethodInvocation *invocation, gpointer user_data)
185{
186 FuMainPrivate *priv = (FuMainPrivate *) user_data;
187 GVariant *val;
188
189 /* return 'as' */
190 if (g_strcmp0 (method_name, "GetDevices") == 0) {
191 _cleanup_strv_free_ gchar **devices = NULL;
Richard Hughesf508e762015-02-27 12:49:36 +0000192 g_debug ("Called %s()", method_name);
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000193 devices = fu_main_get_device_list_as_strv (priv);
194 val = g_variant_new ("(^as)", devices);
195 g_dbus_method_invocation_return_value (invocation, val);
196 return;
197 }
198
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000199 /* return '' */
200 if (g_strcmp0 (method_name, "UpdateOffline") == 0) {
201 GDBusMessage *message;
202 GUnixFDList *fd_list;
Richard Hughesf508e762015-02-27 12:49:36 +0000203 FuDeviceItem *item;
204 FuMainAuthHelper *helper;
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000205 const gchar *id = NULL;
206 gint32 fd_handle = 0;
207 gint fd;
208 _cleanup_error_free_ GError *error = NULL;
Richard Hughesf508e762015-02-27 12:49:36 +0000209 _cleanup_object_unref_ PolkitSubject *subject = NULL;
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000210
211 /* check the id exists */
212 g_variant_get (parameters, "(&sh)", &id, &fd_handle);
Richard Hughesf508e762015-02-27 12:49:36 +0000213 g_debug ("Called %s(%s,%i)", method_name, id, fd_handle);
214 item = fu_main_get_item_by_id (priv, id);
215 if (item == NULL) {
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000216 g_dbus_method_invocation_return_error (invocation,
217 FU_ERROR,
218 FU_ERROR_INTERNAL,
219 "no such ID %s",
220 id);
221 return;
222 }
223
224 /* get the fd */
225 message = g_dbus_method_invocation_get_message (invocation);
226 fd_list = g_dbus_message_get_unix_fd_list (message);
227 if (fd_list == NULL || g_unix_fd_list_get_length (fd_list) != 1) {
228 g_dbus_method_invocation_return_error (invocation,
229 FU_ERROR,
230 FU_ERROR_INTERNAL,
231 "invalid handle");
232 return;
233 }
234 fd = g_unix_fd_list_get (fd_list, fd_handle, &error);
235 if (fd < 0) {
236 g_dbus_method_invocation_return_gerror (invocation,
237 error);
238 return;
239 }
240
Richard Hughesf508e762015-02-27 12:49:36 +0000241
242 /* do authorization async */
243 helper = g_new0 (FuMainAuthHelper, 1);
244 helper->invocation = g_object_ref (invocation);
245 helper->fd = fd;
246 helper->id = g_strdup (id);
247 helper->priv = priv;
248 subject = polkit_system_bus_name_new (sender);
249 polkit_authority_check_authorization (priv->authority, subject,
250 "org.freedesktop.fwupd.update",
251 NULL,
252 POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
253 NULL,
254 fu_main_check_authorization_cb,
255 helper);
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000256 return;
257 }
258
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000259 /* we suck */
260 g_dbus_method_invocation_return_error (invocation,
261 FU_ERROR,
262 FU_ERROR_INTERNAL,
263 "no such method %s",
264 method_name);
265}
266
267/**
268 * fu_main_daemon_get_property:
269 **/
270static GVariant *
271fu_main_daemon_get_property (GDBusConnection *connection_, const gchar *sender,
272 const gchar *object_path, const gchar *interface_name,
273 const gchar *property_name, GError **error,
274 gpointer user_data)
275{
276 if (g_strcmp0 (property_name, "DaemonVersion") == 0)
277 return g_variant_new_string (VERSION);
278
279 /* return an error */
280 g_set_error (error,
281 FU_ERROR,
282 FU_ERROR_INTERNAL,
283 "failed to get daemon property %s",
284 property_name);
285 return NULL;
286}
287
288/**
289 * fu_main_on_bus_acquired_cb:
290 **/
291static void
292fu_main_on_bus_acquired_cb (GDBusConnection *connection,
293 const gchar *name,
294 gpointer user_data)
295{
296 FuMainPrivate *priv = (FuMainPrivate *) user_data;
297 guint registration_id;
298 static const GDBusInterfaceVTable interface_vtable = {
299 fu_main_daemon_method_call,
300 fu_main_daemon_get_property,
301 NULL
302 };
303
304 priv->connection = g_object_ref (connection);
305 registration_id = g_dbus_connection_register_object (connection,
306 FWUPD_DBUS_PATH,
307 priv->introspection_daemon->interfaces[0],
308 &interface_vtable,
309 priv, /* user_data */
310 NULL, /* user_data_free_func */
311 NULL); /* GError** */
312 g_assert (registration_id > 0);
313}
314
315/**
316 * fu_main_on_name_acquired_cb:
317 **/
318static void
319fu_main_on_name_acquired_cb (GDBusConnection *connection,
320 const gchar *name,
321 gpointer user_data)
322{
323 FuMainPrivate *priv = (FuMainPrivate *) user_data;
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000324 FuProvider *provider;
325 guint i;
326
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000327 g_debug ("FuMain: acquired name: %s", name);
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000328 for (i = 0; i < priv->providers->len; i++) {
329 _cleanup_error_free_ GError *error = NULL;
330 provider = g_ptr_array_index (priv->providers, i);
331 if (!fu_provider_coldplug (FU_PROVIDER (provider), &error))
332 g_warning ("Failed to coldplug: %s", error->message);
333 }
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000334}
335
336/**
337 * fu_main_on_name_lost_cb:
338 **/
339static void
340fu_main_on_name_lost_cb (GDBusConnection *connection,
341 const gchar *name,
342 gpointer user_data)
343{
344 FuMainPrivate *priv = (FuMainPrivate *) user_data;
345 g_debug ("FuMain: lost name: %s", name);
346 g_main_loop_quit (priv->loop);
347}
348
349/**
350 * fu_main_timed_exit_cb:
351 **/
352static gboolean
353fu_main_timed_exit_cb (gpointer user_data)
354{
355 GMainLoop *loop = (GMainLoop *) user_data;
356 g_main_loop_quit (loop);
357 return G_SOURCE_REMOVE;
358}
359
360/**
361 * fu_main_load_introspection:
362 **/
363static GDBusNodeInfo *
364fu_main_load_introspection (const gchar *filename, GError **error)
365{
366 _cleanup_bytes_unref_ GBytes *data = NULL;
367 _cleanup_free_ gchar *path = NULL;
368
369 /* lookup data */
370 path = g_build_filename ("/org/freedesktop/fwupd", filename, NULL);
371 data = g_resource_lookup_data (fu_get_resource (),
372 path,
373 G_RESOURCE_LOOKUP_FLAGS_NONE,
374 error);
375 if (data == NULL)
376 return NULL;
377
378 /* build introspection from XML */
379 return g_dbus_node_info_new_for_xml (g_bytes_get_data (data, NULL), error);
380}
381
382/**
383 * cd_main_provider_device_added_cb:
384 **/
385static void
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000386cd_main_provider_device_added_cb (FuProvider *provider,
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000387 FuDevice *device,
388 gpointer user_data)
389{
390 FuMainPrivate *priv = (FuMainPrivate *) user_data;
Richard Hughesf508e762015-02-27 12:49:36 +0000391 FuDeviceItem *item;
392
393 item = g_new0 (FuDeviceItem, 1);
394 item->device = g_object_ref (device);
395 item->provider = g_object_ref (provider);
396 g_ptr_array_add (priv->devices, item);
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000397}
398
399/**
400 * cd_main_provider_device_removed_cb:
401 **/
402static void
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000403cd_main_provider_device_removed_cb (FuProvider *provider,
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000404 FuDevice *device,
405 gpointer user_data)
406{
407 FuMainPrivate *priv = (FuMainPrivate *) user_data;
Richard Hughesf508e762015-02-27 12:49:36 +0000408 FuDeviceItem *item;
409
410 item = fu_main_get_item_by_id (priv, fu_device_get_id (device));
411 if (item == NULL) {
412 g_warning ("can't remove device %s", fu_device_get_id (device));
413 return;
414 }
415 g_ptr_array_remove (priv->devices, item);
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000416}
417
418/**
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000419 * fu_main_add_provider:
420 **/
421static void
422fu_main_add_provider (FuMainPrivate *priv, FuProvider *provider)
423{
424 g_signal_connect (provider, "device-added",
425 G_CALLBACK (cd_main_provider_device_added_cb),
426 priv);
427 g_signal_connect (provider, "device-removed",
428 G_CALLBACK (cd_main_provider_device_removed_cb),
429 priv);
430 g_ptr_array_add (priv->providers, provider);
431}
432
433/**
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000434 * main:
435 **/
436int
437main (int argc, char *argv[])
438{
439 FuMainPrivate *priv = NULL;
440 gboolean immediate_exit = FALSE;
441 gboolean ret;
442 gboolean timed_exit = FALSE;
443 GOptionContext *context;
444 guint owner_id = 0;
445 guint retval = 1;
446 const GOptionEntry options[] = {
447 { "timed-exit", '\0', 0, G_OPTION_ARG_NONE, &timed_exit,
448 /* TRANSLATORS: exit after we've started up, used for user profiling */
449 _("Exit after a small delay"), NULL },
450 { "immediate-exit", '\0', 0, G_OPTION_ARG_NONE, &immediate_exit,
451 /* TRANSLATORS: exit straight away, used for automatic profiling */
452 _("Exit after the engine has loaded"), NULL },
453 { NULL}
454 };
455 _cleanup_error_free_ GError *error = NULL;
456
457 setlocale (LC_ALL, "");
458
459 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
460 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
461 textdomain (GETTEXT_PACKAGE);
462
463 /* TRANSLATORS: program name */
464 g_set_application_name (_("Firmware Update"));
465 context = g_option_context_new (NULL);
466 g_option_context_add_main_entries (context, options, NULL);
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000467 g_option_context_add_group (context, fu_debug_get_option_group ());
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000468 g_option_context_set_summary (context, _("Firmware Update D-Bus Service"));
469 ret = g_option_context_parse (context, &argc, &argv, &error);
470 if (!ret) {
471 g_warning ("FuMain: failed to parse command line arguments: %s",
472 error->message);
473 goto out;
474 }
475
476 /* create new objects */
477 priv = g_new0 (FuMainPrivate, 1);
Richard Hughesf508e762015-02-27 12:49:36 +0000478 priv->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_main_item_free);
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000479 priv->loop = g_main_loop_new (NULL, FALSE);
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000480
481 /* add providers */
482 priv->providers = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
483 fu_main_add_provider (priv, fu_provider_uefi_new ());
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000484
485 /* load introspection from file */
486 priv->introspection_daemon = fu_main_load_introspection (FWUPD_DBUS_INTERFACE ".xml",
487 &error);
488 if (priv->introspection_daemon == NULL) {
489 g_warning ("FuMain: failed to load daemon introspection: %s",
490 error->message);
491 goto out;
492 }
493
Richard Hughesf508e762015-02-27 12:49:36 +0000494 /* get authority */
495 priv->authority = polkit_authority_get_sync (NULL, &error);
496 if (priv->authority == NULL) {
497 g_warning ("FuMain: failed to load polkit authority: %s",
498 error->message);
499 goto out;
500 }
501
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000502 /* own the object */
503 owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
504 FWUPD_DBUS_SERVICE,
505 G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
506 G_BUS_NAME_OWNER_FLAGS_REPLACE,
507 fu_main_on_bus_acquired_cb,
508 fu_main_on_name_acquired_cb,
509 fu_main_on_name_lost_cb,
510 priv, NULL);
511
512 /* Only timeout and close the mainloop if we have specified it
513 * on the command line */
514 if (immediate_exit)
515 g_idle_add (fu_main_timed_exit_cb, priv->loop);
516 else if (timed_exit)
517 g_timeout_add_seconds (5, fu_main_timed_exit_cb, priv->loop);
518
519 /* wait */
520 g_info ("Daemon ready for requests");
521 g_main_loop_run (priv->loop);
522
523 /* success */
524 retval = 0;
525out:
526 g_option_context_free (context);
527 if (owner_id > 0)
528 g_bus_unown_name (owner_id);
529 if (priv != NULL) {
530 if (priv->loop != NULL)
531 g_main_loop_unref (priv->loop);
532 if (priv->connection != NULL)
533 g_object_unref (priv->connection);
Richard Hughesf508e762015-02-27 12:49:36 +0000534 if (priv->authority != NULL)
535 g_object_unref (priv->authority);
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000536 if (priv->introspection_daemon != NULL)
537 g_dbus_node_info_unref (priv->introspection_daemon);
Richard Hughes8bbfdf42015-02-26 22:28:09 +0000538 g_ptr_array_unref (priv->providers);
Richard Hughes8dbfb1c2015-02-26 13:07:40 +0000539 g_ptr_array_unref (priv->devices);
540 g_free (priv);
541 }
542 return retval;
543}
544