blob: ebc5dbba94461427acdf94f9087f4af433f942ca [file] [log] [blame]
Richard Hughes943d2c92017-06-21 09:04:39 +01001/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
4 *
Mario Limonciello51308e62018-05-28 20:05:46 -05005 * SPDX-License-Identifier: LGPL-2.1+
Richard Hughes943d2c92017-06-21 09:04:39 +01006 */
7
8#include <config.h>
9
10#include <gio/gunixinputstream.h>
Richard Hughes954dd9f2017-08-08 13:36:25 +010011#include <glib/gstdio.h>
12
Richard Hughes94f939a2017-08-08 12:21:39 +010013#include <archive_entry.h>
14#include <archive.h>
Richard Hughes7ee42fe2017-08-15 14:06:21 +010015#include <errno.h>
Richard Hughesae252cd2017-12-08 10:48:15 +000016#include <string.h>
Richard Hughes943d2c92017-06-21 09:04:39 +010017
18#include "fwupd-error.h"
19
20#include "fu-common.h"
21
22/**
Richard Hughes4eada342017-10-03 21:20:32 +010023 * SECTION:fu-common
24 * @short_description: common functionality for plugins to use
25 *
26 * Helper functions that can be used by the daemon and plugins.
27 *
28 * See also: #FuPlugin
29 */
30
31/**
Richard Hughes954dd9f2017-08-08 13:36:25 +010032 * fu_common_rmtree:
33 * @directory: a directory name
34 * @error: A #GError or %NULL
35 *
36 * Recursively removes a directory.
37 *
38 * Returns: %TRUE for success, %FALSE otherwise
39 **/
40gboolean
41fu_common_rmtree (const gchar *directory, GError **error)
42{
43 const gchar *filename;
44 g_autoptr(GDir) dir = NULL;
45
46 /* try to open */
Richard Hughes455fdd32017-08-16 12:26:44 +010047 g_debug ("removing %s", directory);
Richard Hughes954dd9f2017-08-08 13:36:25 +010048 dir = g_dir_open (directory, 0, error);
49 if (dir == NULL)
50 return FALSE;
51
52 /* find each */
53 while ((filename = g_dir_read_name (dir))) {
54 g_autofree gchar *src = NULL;
55 src = g_build_filename (directory, filename, NULL);
56 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
57 if (!fu_common_rmtree (src, error))
58 return FALSE;
59 } else {
60 if (g_unlink (src) != 0) {
61 g_set_error (error,
62 FWUPD_ERROR,
63 FWUPD_ERROR_INTERNAL,
64 "Failed to delete: %s", src);
65 return FALSE;
66 }
67 }
68 }
69 if (g_remove (directory) != 0) {
70 g_set_error (error,
71 FWUPD_ERROR,
72 FWUPD_ERROR_INTERNAL,
73 "Failed to delete: %s", directory);
74 return FALSE;
75 }
76 return TRUE;
77}
78
Richard Hughes89e968b2018-03-07 10:01:08 +000079static gboolean
80fu_common_get_file_list_internal (GPtrArray *files, const gchar *directory, GError **error)
81{
82 const gchar *filename;
83 g_autoptr(GDir) dir = NULL;
84
85 /* try to open */
86 dir = g_dir_open (directory, 0, error);
87 if (dir == NULL)
88 return FALSE;
89
90 /* find each */
91 while ((filename = g_dir_read_name (dir))) {
92 g_autofree gchar *src = g_build_filename (directory, filename, NULL);
93 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
94 if (!fu_common_get_file_list_internal (files, src, error))
95 return FALSE;
96 } else {
97 g_ptr_array_add (files, g_steal_pointer (&src));
98 }
99 }
100 return TRUE;
101
102}
103
104/**
105 * fu_common_get_files_recursive:
Richard Hughes8aa72392018-05-02 08:38:43 +0100106 * @path: a directory name
Richard Hughes89e968b2018-03-07 10:01:08 +0000107 * @error: A #GError or %NULL
108 *
109 * Returns every file found under @directory, and any subdirectory.
110 * If any path under @directory cannot be accessed due to permissions an error
111 * will be returned.
112 *
113 * Returns: (element-type: utf8) (transfer container): array of files, or %NULL for error
114 **/
115GPtrArray *
116fu_common_get_files_recursive (const gchar *path, GError **error)
117{
118 g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free);
119 if (!fu_common_get_file_list_internal (files, path, error))
120 return NULL;
121 return g_steal_pointer (&files);
122}
Richard Hughes954dd9f2017-08-08 13:36:25 +0100123/**
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100124 * fu_common_mkdir_parent:
125 * @filename: A full pathname
126 * @error: A #GError, or %NULL
127 *
128 * Creates any required directories, including any parent directories.
129 *
130 * Returns: %TRUE for success
131 **/
132gboolean
133fu_common_mkdir_parent (const gchar *filename, GError **error)
134{
135 g_autofree gchar *parent = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100136
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100137 parent = g_path_get_dirname (filename);
Richard Hughes455fdd32017-08-16 12:26:44 +0100138 g_debug ("creating path %s", parent);
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100139 if (g_mkdir_with_parents (parent, 0755) == -1) {
140 g_set_error (error,
141 FWUPD_ERROR,
142 FWUPD_ERROR_INTERNAL,
143 "Failed to create '%s': %s",
144 parent, g_strerror (errno));
145 return FALSE;
146 }
147 return TRUE;
148}
149
150/**
Richard Hughes943d2c92017-06-21 09:04:39 +0100151 * fu_common_set_contents_bytes:
152 * @filename: A filename
153 * @bytes: The data to write
154 * @error: A #GError, or %NULL
155 *
156 * Writes a blob of data to a filename, creating the parent directories as
157 * required.
158 *
159 * Returns: %TRUE for success
160 **/
161gboolean
162fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error)
163{
164 const gchar *data;
165 gsize size;
166 g_autoptr(GFile) file = NULL;
167 g_autoptr(GFile) file_parent = NULL;
168
169 file = g_file_new_for_path (filename);
170 file_parent = g_file_get_parent (file);
171 if (!g_file_query_exists (file_parent, NULL)) {
172 if (!g_file_make_directory_with_parents (file_parent, NULL, error))
173 return FALSE;
174 }
175 data = g_bytes_get_data (bytes, &size);
Richard Hughes455fdd32017-08-16 12:26:44 +0100176 g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
Richard Hughes943d2c92017-06-21 09:04:39 +0100177 return g_file_set_contents (filename, data, size, error);
178}
179
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100180/**
181 * fu_common_get_contents_bytes:
182 * @filename: A filename
183 * @error: A #GError, or %NULL
184 *
185 * Reads a blob of data from a file.
186 *
187 * Returns: a #GBytes, or %NULL for failure
188 **/
189GBytes *
190fu_common_get_contents_bytes (const gchar *filename, GError **error)
191{
192 gchar *data = NULL;
193 gsize len = 0;
194 if (!g_file_get_contents (filename, &data, &len, error))
195 return NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100196 g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100197 return g_bytes_new_take (data, len);
198}
Richard Hughes943d2c92017-06-21 09:04:39 +0100199
200/**
201 * fu_common_get_contents_fd:
202 * @fd: A file descriptor
203 * @count: The maximum number of bytes to read
204 * @error: A #GError, or %NULL
205 *
206 * Reads a blob from a specific file descriptor.
207 *
208 * Note: this will close the fd when done
209 *
Richard Hughes4eada342017-10-03 21:20:32 +0100210 * Returns: (transfer full): a #GBytes, or %NULL
Richard Hughes943d2c92017-06-21 09:04:39 +0100211 **/
212GBytes *
213fu_common_get_contents_fd (gint fd, gsize count, GError **error)
214{
215 g_autoptr(GBytes) blob = NULL;
216 g_autoptr(GError) error_local = NULL;
217 g_autoptr(GInputStream) stream = NULL;
218
219 g_return_val_if_fail (fd > 0, NULL);
Richard Hughes943d2c92017-06-21 09:04:39 +0100220 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
221
Richard Hughes919f8ab2018-02-14 10:24:56 +0000222 /* this is invalid */
223 if (count == 0) {
224 g_set_error_literal (error,
225 FWUPD_ERROR,
226 FWUPD_ERROR_NOT_SUPPORTED,
227 "A maximum read size must be specified");
228 return NULL;
229 }
230
Richard Hughes943d2c92017-06-21 09:04:39 +0100231 /* read the entire fd to a data blob */
232 stream = g_unix_input_stream_new (fd, TRUE);
233 blob = g_input_stream_read_bytes (stream, count, NULL, &error_local);
234 if (blob == NULL) {
235 g_set_error_literal (error,
236 FWUPD_ERROR,
237 FWUPD_ERROR_INVALID_FILE,
238 error_local->message);
239 return NULL;
240 }
241 return g_steal_pointer (&blob);
242}
Richard Hughes94f939a2017-08-08 12:21:39 +0100243
244static gboolean
245fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir)
246{
247 const gchar *tmp;
248 g_autofree gchar *buf = NULL;
249
250 /* no output file */
251 if (archive_entry_pathname (entry) == NULL)
252 return FALSE;
253
254 /* update output path */
255 tmp = archive_entry_pathname (entry);
256 buf = g_build_filename (dir, tmp, NULL);
257 archive_entry_update_pathname_utf8 (entry, buf);
258 return TRUE;
259}
260
261/**
262 * fu_common_extract_archive:
263 * @blob: a #GBytes archive as a blob
Richard Hughes4eada342017-10-03 21:20:32 +0100264 * @dir: a directory name to extract to
Richard Hughes94f939a2017-08-08 12:21:39 +0100265 * @error: A #GError, or %NULL
266 *
267 * Extracts an achive to a directory.
268 *
269 * Returns: %TRUE for success
270 **/
271gboolean
272fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error)
273{
274 gboolean ret = TRUE;
275 int r;
276 struct archive *arch = NULL;
277 struct archive_entry *entry;
278
279 /* decompress anything matching either glob */
Richard Hughes455fdd32017-08-16 12:26:44 +0100280 g_debug ("decompressing into %s", dir);
Richard Hughes94f939a2017-08-08 12:21:39 +0100281 arch = archive_read_new ();
282 archive_read_support_format_all (arch);
283 archive_read_support_filter_all (arch);
284 r = archive_read_open_memory (arch,
285 (void *) g_bytes_get_data (blob, NULL),
286 (size_t) g_bytes_get_size (blob));
287 if (r != 0) {
288 ret = FALSE;
289 g_set_error (error,
290 FWUPD_ERROR,
291 FWUPD_ERROR_INTERNAL,
292 "Cannot open: %s",
293 archive_error_string (arch));
294 goto out;
295 }
296 for (;;) {
297 gboolean valid;
Richard Hughes94f939a2017-08-08 12:21:39 +0100298 r = archive_read_next_header (arch, &entry);
299 if (r == ARCHIVE_EOF)
300 break;
301 if (r != ARCHIVE_OK) {
302 ret = FALSE;
303 g_set_error (error,
304 FWUPD_ERROR,
305 FWUPD_ERROR_INTERNAL,
306 "Cannot read header: %s",
307 archive_error_string (arch));
308 goto out;
309 }
310
311 /* only extract if valid */
312 valid = fu_common_extract_archive_entry (entry, dir);
313 if (!valid)
314 continue;
315 r = archive_read_extract (arch, entry, 0);
316 if (r != ARCHIVE_OK) {
317 ret = FALSE;
318 g_set_error (error,
319 FWUPD_ERROR,
320 FWUPD_ERROR_INTERNAL,
321 "Cannot extract: %s",
322 archive_error_string (arch));
323 goto out;
324 }
325 }
326out:
327 if (arch != NULL) {
328 archive_read_close (arch);
329 archive_read_free (arch);
330 }
331 return ret;
332}
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100333
334static void
Yehezkel Bernate43f7fb2017-08-30 12:09:34 +0300335fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3);
336
337static void
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100338fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...)
339{
340 va_list args;
341 g_autofree gchar *tmp = NULL;
342 g_auto(GStrv) split = NULL;
343
344 va_start (args, fmt);
345 tmp = g_strdup_vprintf (fmt, args);
346 va_end (args);
347
348 split = g_strsplit (tmp, " ", -1);
349 for (guint i = 0; split[i] != NULL; i++)
350 g_ptr_array_add (argv, g_strdup (split[i]));
351}
352
353/**
354 * fu_common_firmware_builder:
355 * @bytes: The data to use
Richard Hughes4eada342017-10-03 21:20:32 +0100356 * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
357 * @output_fn: Name of the generated firmware, e.g. `firmware.bin`
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100358 * @error: A #GError, or %NULL
359 *
360 * Builds a firmware file using tools from the host session in a bubblewrap
361 * jail. Several things happen during build:
362 *
363 * 1. The @bytes data is untarred to a temporary location
364 * 2. A bubblewrap container is set up
365 * 3. The startup.sh script is run inside the container
366 * 4. The firmware.bin is extracted from the container
367 * 5. The temporary location is deleted
368 *
369 * Returns: a new #GBytes, or %NULL for error
370 **/
371GBytes *
372fu_common_firmware_builder (GBytes *bytes,
373 const gchar *script_fn,
374 const gchar *output_fn,
375 GError **error)
376{
377 gint rc = 0;
378 g_autofree gchar *argv_str = NULL;
Richard Hughes4be17d12018-05-30 20:36:29 +0100379 g_autofree gchar *localstatebuilderdir = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100380 g_autofree gchar *localstatedir = NULL;
381 g_autofree gchar *output2_fn = NULL;
382 g_autofree gchar *standard_error = NULL;
383 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100384 g_autofree gchar *tmpdir = NULL;
385 g_autoptr(GBytes) firmware_blob = NULL;
386 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
387
388 g_return_val_if_fail (bytes != NULL, NULL);
389 g_return_val_if_fail (script_fn != NULL, NULL);
390 g_return_val_if_fail (output_fn != NULL, NULL);
391 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
392
393 /* untar file to temp location */
394 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
395 if (tmpdir == NULL)
396 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100397 if (!fu_common_extract_archive (bytes, tmpdir, error))
398 return NULL;
399
400 /* this is shared with the plugins */
Richard Hughes4be17d12018-05-30 20:36:29 +0100401 localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
402 localstatebuilderdir = g_build_filename (localstatedir, "builder", NULL);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100403
404 /* launch bubblewrap and generate firmware */
Richard Hughesf6f72a42017-08-09 16:25:25 +0100405 g_ptr_array_add (argv, g_strdup ("bwrap"));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100406 fu_common_add_argv (argv, "--die-with-parent");
407 fu_common_add_argv (argv, "--ro-bind /usr /usr");
408 fu_common_add_argv (argv, "--dir /tmp");
409 fu_common_add_argv (argv, "--dir /var");
410 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
Richard Hughes4be17d12018-05-30 20:36:29 +0100411 if (g_file_test (localstatebuilderdir, G_FILE_TEST_EXISTS))
412 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatebuilderdir);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100413 fu_common_add_argv (argv, "--dev /dev");
414 fu_common_add_argv (argv, "--symlink usr/lib /lib");
415 fu_common_add_argv (argv, "--symlink usr/lib64 /lib64");
416 fu_common_add_argv (argv, "--symlink usr/bin /bin");
417 fu_common_add_argv (argv, "--symlink usr/sbin /sbin");
418 fu_common_add_argv (argv, "--chdir /tmp");
419 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100420 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100421 g_ptr_array_add (argv, NULL);
422 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
423 g_debug ("running '%s' in %s", argv_str, tmpdir);
424 if (!g_spawn_sync ("/tmp",
425 (gchar **) argv->pdata,
426 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100427 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100428 NULL, NULL, /* child_setup */
429 &standard_output,
430 &standard_error,
431 &rc,
432 error)) {
433 g_prefix_error (error, "failed to run '%s': ", argv_str);
434 return NULL;
435 }
436 if (standard_output != NULL && standard_output[0] != '\0')
437 g_debug ("console output was: %s", standard_output);
438 if (rc != 0) {
439 g_set_error (error,
440 FWUPD_ERROR,
441 FWUPD_ERROR_INTERNAL,
442 "failed to build firmware: %s",
443 standard_error);
444 return NULL;
445 }
446
447 /* get generated file */
448 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
449 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
450 if (firmware_blob == NULL)
451 return NULL;
452
453 /* cleanup temp directory */
454 if (!fu_common_rmtree (tmpdir, error))
455 return NULL;
456
457 /* success */
458 return g_steal_pointer (&firmware_blob);
459}
Richard Hughes049ccc82017-08-09 15:26:56 +0100460
461typedef struct {
462 FuOutputHandler handler_cb;
463 gpointer handler_user_data;
464 GMainLoop *loop;
465 GSource *source;
466 GInputStream *stream;
467 GCancellable *cancellable;
468} FuCommonSpawnHelper;
469
470static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
471
472static gboolean
473fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
474{
475 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
476 gchar buffer[1024];
477 gssize sz;
478 g_auto(GStrv) split = NULL;
479 g_autoptr(GError) error = NULL;
480
481 /* read from stream */
482 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
483 buffer,
484 sizeof(buffer) - 1,
485 NULL,
486 &error);
487 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100488 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
489 g_warning ("failed to get read from nonblocking fd: %s",
490 error->message);
491 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100492 return G_SOURCE_REMOVE;
493 }
494
495 /* no read possible */
496 if (sz == 0)
497 g_main_loop_quit (helper->loop);
498
499 /* emit lines */
500 if (helper->handler_cb != NULL) {
501 buffer[sz] = '\0';
502 split = g_strsplit (buffer, "\n", -1);
503 for (guint i = 0; split[i] != NULL; i++) {
504 if (split[i][0] == '\0')
505 continue;
506 helper->handler_cb (split[i], helper->handler_user_data);
507 }
508 }
509
510 /* set up the source for the next read */
511 fu_common_spawn_create_pollable_source (helper);
512 return G_SOURCE_REMOVE;
513}
514
515static void
516fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
517{
518 if (helper->source != NULL)
519 g_source_destroy (helper->source);
520 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
521 helper->cancellable);
522 g_source_attach (helper->source, NULL);
523 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
524}
525
526static void
527fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
528{
529 if (helper->stream != NULL)
530 g_object_unref (helper->stream);
531 if (helper->source != NULL)
532 g_source_destroy (helper->source);
533 if (helper->loop != NULL)
534 g_main_loop_unref (helper->loop);
535 g_free (helper);
536}
537
Mario Limoncielloa98df552018-04-16 12:15:51 -0500538#pragma clang diagnostic push
539#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100540G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500541#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100542
543/**
544 * fu_common_spawn_sync:
545 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100546 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
547 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughes049ccc82017-08-09 15:26:56 +0100548 * @cancellable: a #GCancellable, or %NULL
549 * @error: A #GError or %NULL
550 *
551 * Runs a subprocess and waits for it to exit. Any output on standard out or
552 * standard error will be forwarded to @handler_cb as whole lines.
553 *
554 * Returns: %TRUE for success
555 **/
556gboolean
557fu_common_spawn_sync (const gchar * const * argv,
558 FuOutputHandler handler_cb,
559 gpointer handler_user_data,
560 GCancellable *cancellable, GError **error)
561{
562 g_autoptr(FuCommonSpawnHelper) helper = NULL;
563 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100564 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100565
566 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100567 argv_str = g_strjoinv (" ", (gchar **) argv);
568 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100569 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
570 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
571 if (subprocess == NULL)
572 return FALSE;
573
574 /* watch for process to exit */
575 helper = g_new0 (FuCommonSpawnHelper, 1);
576 helper->handler_cb = handler_cb;
577 helper->handler_user_data = handler_user_data;
578 helper->loop = g_main_loop_new (NULL, FALSE);
579 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
580 helper->cancellable = cancellable;
581 fu_common_spawn_create_pollable_source (helper);
582 g_main_loop_run (helper->loop);
583 return g_subprocess_wait_check (subprocess, cancellable, error);
584}
Richard Hughesae252cd2017-12-08 10:48:15 +0000585
586/**
587 * fu_common_write_uint16:
588 * @buf: A writable buffer
589 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100590 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000591 *
592 * Writes a value to a buffer using a specified endian.
593 **/
594void
595fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
596{
597 guint16 val_hw;
598 switch (endian) {
599 case G_BIG_ENDIAN:
600 val_hw = GUINT16_TO_BE(val_native);
601 break;
602 case G_LITTLE_ENDIAN:
603 val_hw = GUINT16_TO_LE(val_native);
604 break;
605 default:
606 g_assert_not_reached ();
607 }
608 memcpy (buf, &val_hw, sizeof(val_hw));
609}
610
611/**
612 * fu_common_write_uint32:
613 * @buf: A writable buffer
614 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100615 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000616 *
617 * Writes a value to a buffer using a specified endian.
618 **/
619void
620fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
621{
622 guint32 val_hw;
623 switch (endian) {
624 case G_BIG_ENDIAN:
625 val_hw = GUINT32_TO_BE(val_native);
626 break;
627 case G_LITTLE_ENDIAN:
628 val_hw = GUINT32_TO_LE(val_native);
629 break;
630 default:
631 g_assert_not_reached ();
632 }
633 memcpy (buf, &val_hw, sizeof(val_hw));
634}
635
636/**
637 * fu_common_read_uint16:
638 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100639 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000640 *
641 * Read a value from a buffer using a specified endian.
642 *
643 * Returns: a value in host byte-order
644 **/
645guint16
646fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
647{
648 guint16 val_hw, val_native;
649 memcpy (&val_hw, buf, sizeof(val_hw));
650 switch (endian) {
651 case G_BIG_ENDIAN:
652 val_native = GUINT16_FROM_BE(val_hw);
653 break;
654 case G_LITTLE_ENDIAN:
655 val_native = GUINT16_FROM_LE(val_hw);
656 break;
657 default:
658 g_assert_not_reached ();
659 }
660 return val_native;
661}
662
663/**
664 * fu_common_read_uint32:
665 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100666 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000667 *
668 * Read a value from a buffer using a specified endian.
669 *
670 * Returns: a value in host byte-order
671 **/
672guint32
673fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
674{
675 guint32 val_hw, val_native;
676 memcpy (&val_hw, buf, sizeof(val_hw));
677 switch (endian) {
678 case G_BIG_ENDIAN:
679 val_native = GUINT32_FROM_BE(val_hw);
680 break;
681 case G_LITTLE_ENDIAN:
682 val_native = GUINT32_FROM_LE(val_hw);
683 break;
684 default:
685 g_assert_not_reached ();
686 }
687 return val_native;
688}
Richard Hughese82eef32018-05-20 10:41:26 +0100689
690static const GError *
691fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
692{
693 for (guint j = 0; j < errors->len; j++) {
694 const GError *error = g_ptr_array_index (errors, j);
695 if (g_error_matches (error, FWUPD_ERROR, error_code))
696 return error;
697 }
698 return NULL;
699}
700
701static guint
702fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
703{
704 guint cnt = 0;
705 for (guint j = 0; j < errors->len; j++) {
706 const GError *error = g_ptr_array_index (errors, j);
707 if (g_error_matches (error, FWUPD_ERROR, error_code))
708 cnt++;
709 }
710 return cnt;
711}
712
713static gboolean
714fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
715{
716 for (guint j = 0; j < errors->len; j++) {
717 const GError *error = g_ptr_array_index (errors, j);
718 gboolean matches_any = FALSE;
719 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
720 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
721 matches_any = TRUE;
722 break;
723 }
724 }
725 if (!matches_any)
726 return FALSE;
727 }
728 return TRUE;
729}
730
731/**
732 * fu_common_error_array_get_best:
733 * @errors: (element-type GError): array of errors
734 *
735 * Finds the 'best' error to show the user from a array of errors, creating a
736 * completely bespoke error where required.
737 *
738 * Returns: (transfer full): a #GError, never %NULL
739 **/
740GError *
741fu_common_error_array_get_best (GPtrArray *errors)
742{
743 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
744 FWUPD_ERROR_VERSION_SAME,
745 FWUPD_ERROR_VERSION_NEWER,
746 FWUPD_ERROR_NOT_SUPPORTED,
747 FWUPD_ERROR_INTERNAL,
748 FWUPD_ERROR_NOT_FOUND,
749 FWUPD_ERROR_LAST };
750 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
751 FWUPD_ERROR_NOT_FOUND,
752 FWUPD_ERROR_NOT_SUPPORTED,
753 FWUPD_ERROR_LAST };
754 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
755 FWUPD_ERROR_VERSION_SAME,
756 FWUPD_ERROR_NOT_FOUND,
757 FWUPD_ERROR_NOT_SUPPORTED,
758 FWUPD_ERROR_LAST };
759
760 /* are all the errors either GUID-not-matched or version-same? */
761 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
762 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
763 return g_error_new (FWUPD_ERROR,
764 FWUPD_ERROR_NOTHING_TO_DO,
765 "All updatable firmware is already installed");
766 }
767
768 /* are all the errors either GUID-not-matched or version same or newer? */
769 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
770 fu_common_error_array_matches_any (errors, err_all_newer)) {
771 return g_error_new (FWUPD_ERROR,
772 FWUPD_ERROR_NOTHING_TO_DO,
773 "All updatable devices already have newer versions");
774 }
775
776 /* get the most important single error */
777 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
778 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
779 if (error_tmp != NULL)
780 return g_error_copy (error_tmp);
781 }
782
783 /* fall back to something */
784 return g_error_new (FWUPD_ERROR,
785 FWUPD_ERROR_NOT_FOUND,
786 "No supported devices found");
787}
Richard Hughes4be17d12018-05-30 20:36:29 +0100788
789/**
790 * fu_common_get_path:
791 * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
792 *
793 * Gets a fwupd-specific system path. These can be overridden with various
794 * environment variables, for instance %FWUPD_DATADIR.
795 *
796 * Returns: a system path, or %NULL if invalid
797 **/
798gchar *
799fu_common_get_path (FuPathKind path_kind)
800{
801 const gchar *tmp;
802 g_autofree gchar *basedir = NULL;
803
804 switch (path_kind) {
805 /* /var */
806 case FU_PATH_KIND_LOCALSTATEDIR:
807 tmp = g_getenv ("FWUPD_LOCALSTATEDIR");
808 if (tmp != NULL)
809 return g_strdup (tmp);
810 tmp = g_getenv ("SNAP_USER_DATA");
811 if (tmp != NULL)
812 return g_build_filename (tmp, LOCALSTATEDIR, NULL);
813 return g_build_filename (LOCALSTATEDIR, NULL);
Richard Hughes282b10d2018-06-22 14:48:00 +0100814 /* /sys/firmware */
815 case FU_PATH_KIND_SYSFSDIR_FW:
816 tmp = g_getenv ("FWUPD_SYSFSFWDIR");
817 if (tmp != NULL)
818 return g_strdup (tmp);
819 return g_strdup ("/sys/firmware");
Richard Hughes83390f62018-06-22 20:36:46 +0100820 /* /sys/bus/platform/drivers */
821 case FU_PATH_KIND_SYSFSDIR_DRIVERS:
822 tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR");
823 if (tmp != NULL)
824 return g_strdup (tmp);
825 return g_strdup ("/sys/bus/platform/drivers");
Richard Hughes4be17d12018-05-30 20:36:29 +0100826 /* /etc */
827 case FU_PATH_KIND_SYSCONFDIR:
828 tmp = g_getenv ("FWUPD_SYSCONFDIR");
829 if (tmp != NULL)
830 return g_strdup (tmp);
831 tmp = g_getenv ("SNAP_USER_DATA");
832 if (tmp != NULL)
833 return g_build_filename (tmp, SYSCONFDIR, NULL);
834 return g_strdup (SYSCONFDIR);
835 /* /usr/lib/<triplet>/fwupd-plugins-3 */
836 case FU_PATH_KIND_PLUGINDIR_PKG:
837 tmp = g_getenv ("FWUPD_PLUGINDIR");
838 if (tmp != NULL)
839 return g_strdup (tmp);
840 tmp = g_getenv ("SNAP");
841 if (tmp != NULL)
842 return g_build_filename (tmp, PLUGINDIR, NULL);
843 return g_build_filename (PLUGINDIR, NULL);
844 /* /usr/share/fwupd */
845 case FU_PATH_KIND_DATADIR_PKG:
846 tmp = g_getenv ("FWUPD_DATADIR");
847 if (tmp != NULL)
848 return g_strdup (tmp);
849 tmp = g_getenv ("SNAP");
850 if (tmp != NULL)
851 return g_build_filename (tmp, DATADIR, PACKAGE_NAME, NULL);
852 return g_build_filename (DATADIR, PACKAGE_NAME, NULL);
853 /* /etc/fwupd */
854 case FU_PATH_KIND_SYSCONFDIR_PKG:
855 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
856 return g_build_filename (basedir, PACKAGE_NAME, NULL);
857 /* /var/lib/fwupd */
858 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
859 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
860 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
861 /* /var/cache/fwupd */
862 case FU_PATH_KIND_CACHEDIR_PKG:
863 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
864 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
865 /* this shouldn't happen */
866 default:
867 g_assert_not_reached ();
868 }
869
870 return NULL;
871}