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