blob: b69f79abbff25596a5bf7d13f643d54eac893eb7 [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;
Mario Limonciello37b59582018-08-13 08:38:01 -0500378 g_autofree gchar *bwrap_fn = 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
Mario Limonciello37b59582018-08-13 08:38:01 -0500393 /* find bwrap in the path */
394 bwrap_fn = g_find_program_in_path ("bwrap");
395 if (bwrap_fn == NULL) {
396 g_set_error (error,
397 FWUPD_ERROR,
398 FWUPD_ERROR_NOT_SUPPORTED,
399 "missing executable bwrap in PATH");
Richard Hughesddb3e202018-08-23 11:29:57 +0100400 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500401 }
402
403 /* test if CONFIG_USER_NS is valid */
404 if (!g_file_test ("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) {
405 g_set_error (error,
406 FWUPD_ERROR,
407 FWUPD_ERROR_NOT_SUPPORTED,
408 "missing CONFIG_USER_NS in kernel");
Richard Hughesddb3e202018-08-23 11:29:57 +0100409 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500410 }
411 if (g_file_test ("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) {
412 g_autofree gchar *clone = NULL;
413 if (!g_file_get_contents ("/proc/sys/kernel/unprivileged_userns_clone", &clone, NULL, error))
Richard Hughesddb3e202018-08-23 11:29:57 +0100414 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500415 if (g_ascii_strtoll (clone, NULL, 10) == 0) {
416 g_set_error (error,
417 FWUPD_ERROR,
418 FWUPD_ERROR_NOT_SUPPORTED,
419 "unprivileged user namespace clones disabled by distro");
Richard Hughesddb3e202018-08-23 11:29:57 +0100420 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500421 }
422 }
423
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100424 /* untar file to temp location */
425 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
426 if (tmpdir == NULL)
427 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100428 if (!fu_common_extract_archive (bytes, tmpdir, error))
429 return NULL;
430
431 /* this is shared with the plugins */
Richard Hughes4be17d12018-05-30 20:36:29 +0100432 localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
433 localstatebuilderdir = g_build_filename (localstatedir, "builder", NULL);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100434
435 /* launch bubblewrap and generate firmware */
Mario Limonciello37b59582018-08-13 08:38:01 -0500436 g_ptr_array_add (argv, g_steal_pointer (&bwrap_fn));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100437 fu_common_add_argv (argv, "--die-with-parent");
438 fu_common_add_argv (argv, "--ro-bind /usr /usr");
Mario Limonciellob8215572018-07-13 09:49:55 -0500439 fu_common_add_argv (argv, "--ro-bind /lib /lib");
440 fu_common_add_argv (argv, "--ro-bind /lib64 /lib64");
441 fu_common_add_argv (argv, "--ro-bind /bin /bin");
442 fu_common_add_argv (argv, "--ro-bind /sbin /sbin");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100443 fu_common_add_argv (argv, "--dir /tmp");
444 fu_common_add_argv (argv, "--dir /var");
445 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
Richard Hughes4be17d12018-05-30 20:36:29 +0100446 if (g_file_test (localstatebuilderdir, G_FILE_TEST_EXISTS))
447 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatebuilderdir);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100448 fu_common_add_argv (argv, "--dev /dev");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100449 fu_common_add_argv (argv, "--chdir /tmp");
450 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100451 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100452 g_ptr_array_add (argv, NULL);
453 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
454 g_debug ("running '%s' in %s", argv_str, tmpdir);
455 if (!g_spawn_sync ("/tmp",
456 (gchar **) argv->pdata,
457 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100458 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100459 NULL, NULL, /* child_setup */
460 &standard_output,
461 &standard_error,
462 &rc,
463 error)) {
464 g_prefix_error (error, "failed to run '%s': ", argv_str);
465 return NULL;
466 }
467 if (standard_output != NULL && standard_output[0] != '\0')
468 g_debug ("console output was: %s", standard_output);
469 if (rc != 0) {
Mario Limonciello37b59582018-08-13 08:38:01 -0500470 FwupdError code = FWUPD_ERROR_INTERNAL;
471 if (errno == ENOTTY)
472 code = FWUPD_ERROR_PERMISSION_DENIED;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100473 g_set_error (error,
474 FWUPD_ERROR,
Mario Limonciello37b59582018-08-13 08:38:01 -0500475 code,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100476 "failed to build firmware: %s",
477 standard_error);
478 return NULL;
479 }
480
481 /* get generated file */
482 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
483 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
484 if (firmware_blob == NULL)
485 return NULL;
486
487 /* cleanup temp directory */
488 if (!fu_common_rmtree (tmpdir, error))
489 return NULL;
490
491 /* success */
492 return g_steal_pointer (&firmware_blob);
493}
Richard Hughes049ccc82017-08-09 15:26:56 +0100494
495typedef struct {
496 FuOutputHandler handler_cb;
497 gpointer handler_user_data;
498 GMainLoop *loop;
499 GSource *source;
500 GInputStream *stream;
501 GCancellable *cancellable;
502} FuCommonSpawnHelper;
503
504static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
505
506static gboolean
507fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
508{
509 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
510 gchar buffer[1024];
511 gssize sz;
512 g_auto(GStrv) split = NULL;
513 g_autoptr(GError) error = NULL;
514
515 /* read from stream */
516 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
517 buffer,
518 sizeof(buffer) - 1,
519 NULL,
520 &error);
521 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100522 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
523 g_warning ("failed to get read from nonblocking fd: %s",
524 error->message);
525 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100526 return G_SOURCE_REMOVE;
527 }
528
529 /* no read possible */
530 if (sz == 0)
531 g_main_loop_quit (helper->loop);
532
533 /* emit lines */
534 if (helper->handler_cb != NULL) {
535 buffer[sz] = '\0';
536 split = g_strsplit (buffer, "\n", -1);
537 for (guint i = 0; split[i] != NULL; i++) {
538 if (split[i][0] == '\0')
539 continue;
540 helper->handler_cb (split[i], helper->handler_user_data);
541 }
542 }
543
544 /* set up the source for the next read */
545 fu_common_spawn_create_pollable_source (helper);
546 return G_SOURCE_REMOVE;
547}
548
549static void
550fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
551{
552 if (helper->source != NULL)
553 g_source_destroy (helper->source);
554 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
555 helper->cancellable);
556 g_source_attach (helper->source, NULL);
557 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
558}
559
560static void
561fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
562{
563 if (helper->stream != NULL)
564 g_object_unref (helper->stream);
565 if (helper->source != NULL)
566 g_source_destroy (helper->source);
567 if (helper->loop != NULL)
568 g_main_loop_unref (helper->loop);
569 g_free (helper);
570}
571
Mario Limoncielloa98df552018-04-16 12:15:51 -0500572#pragma clang diagnostic push
573#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100574G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500575#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100576
577/**
578 * fu_common_spawn_sync:
579 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100580 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
581 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughes049ccc82017-08-09 15:26:56 +0100582 * @cancellable: a #GCancellable, or %NULL
583 * @error: A #GError or %NULL
584 *
585 * Runs a subprocess and waits for it to exit. Any output on standard out or
586 * standard error will be forwarded to @handler_cb as whole lines.
587 *
588 * Returns: %TRUE for success
589 **/
590gboolean
591fu_common_spawn_sync (const gchar * const * argv,
592 FuOutputHandler handler_cb,
593 gpointer handler_user_data,
594 GCancellable *cancellable, GError **error)
595{
596 g_autoptr(FuCommonSpawnHelper) helper = NULL;
597 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100598 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100599
600 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100601 argv_str = g_strjoinv (" ", (gchar **) argv);
602 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100603 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
604 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
605 if (subprocess == NULL)
606 return FALSE;
607
608 /* watch for process to exit */
609 helper = g_new0 (FuCommonSpawnHelper, 1);
610 helper->handler_cb = handler_cb;
611 helper->handler_user_data = handler_user_data;
612 helper->loop = g_main_loop_new (NULL, FALSE);
613 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
614 helper->cancellable = cancellable;
615 fu_common_spawn_create_pollable_source (helper);
616 g_main_loop_run (helper->loop);
617 return g_subprocess_wait_check (subprocess, cancellable, error);
618}
Richard Hughesae252cd2017-12-08 10:48:15 +0000619
620/**
621 * fu_common_write_uint16:
622 * @buf: A writable buffer
623 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100624 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000625 *
626 * Writes a value to a buffer using a specified endian.
627 **/
628void
629fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
630{
631 guint16 val_hw;
632 switch (endian) {
633 case G_BIG_ENDIAN:
634 val_hw = GUINT16_TO_BE(val_native);
635 break;
636 case G_LITTLE_ENDIAN:
637 val_hw = GUINT16_TO_LE(val_native);
638 break;
639 default:
640 g_assert_not_reached ();
641 }
642 memcpy (buf, &val_hw, sizeof(val_hw));
643}
644
645/**
646 * fu_common_write_uint32:
647 * @buf: A writable buffer
648 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100649 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000650 *
651 * Writes a value to a buffer using a specified endian.
652 **/
653void
654fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
655{
656 guint32 val_hw;
657 switch (endian) {
658 case G_BIG_ENDIAN:
659 val_hw = GUINT32_TO_BE(val_native);
660 break;
661 case G_LITTLE_ENDIAN:
662 val_hw = GUINT32_TO_LE(val_native);
663 break;
664 default:
665 g_assert_not_reached ();
666 }
667 memcpy (buf, &val_hw, sizeof(val_hw));
668}
669
670/**
671 * fu_common_read_uint16:
672 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100673 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000674 *
675 * Read a value from a buffer using a specified endian.
676 *
677 * Returns: a value in host byte-order
678 **/
679guint16
680fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
681{
682 guint16 val_hw, val_native;
683 memcpy (&val_hw, buf, sizeof(val_hw));
684 switch (endian) {
685 case G_BIG_ENDIAN:
686 val_native = GUINT16_FROM_BE(val_hw);
687 break;
688 case G_LITTLE_ENDIAN:
689 val_native = GUINT16_FROM_LE(val_hw);
690 break;
691 default:
692 g_assert_not_reached ();
693 }
694 return val_native;
695}
696
697/**
698 * fu_common_read_uint32:
699 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100700 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000701 *
702 * Read a value from a buffer using a specified endian.
703 *
704 * Returns: a value in host byte-order
705 **/
706guint32
707fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
708{
709 guint32 val_hw, val_native;
710 memcpy (&val_hw, buf, sizeof(val_hw));
711 switch (endian) {
712 case G_BIG_ENDIAN:
713 val_native = GUINT32_FROM_BE(val_hw);
714 break;
715 case G_LITTLE_ENDIAN:
716 val_native = GUINT32_FROM_LE(val_hw);
717 break;
718 default:
719 g_assert_not_reached ();
720 }
721 return val_native;
722}
Richard Hughese82eef32018-05-20 10:41:26 +0100723
724static const GError *
725fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
726{
727 for (guint j = 0; j < errors->len; j++) {
728 const GError *error = g_ptr_array_index (errors, j);
729 if (g_error_matches (error, FWUPD_ERROR, error_code))
730 return error;
731 }
732 return NULL;
733}
734
735static guint
736fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
737{
738 guint cnt = 0;
739 for (guint j = 0; j < errors->len; j++) {
740 const GError *error = g_ptr_array_index (errors, j);
741 if (g_error_matches (error, FWUPD_ERROR, error_code))
742 cnt++;
743 }
744 return cnt;
745}
746
747static gboolean
748fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
749{
750 for (guint j = 0; j < errors->len; j++) {
751 const GError *error = g_ptr_array_index (errors, j);
752 gboolean matches_any = FALSE;
753 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
754 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
755 matches_any = TRUE;
756 break;
757 }
758 }
759 if (!matches_any)
760 return FALSE;
761 }
762 return TRUE;
763}
764
765/**
766 * fu_common_error_array_get_best:
767 * @errors: (element-type GError): array of errors
768 *
769 * Finds the 'best' error to show the user from a array of errors, creating a
770 * completely bespoke error where required.
771 *
772 * Returns: (transfer full): a #GError, never %NULL
773 **/
774GError *
775fu_common_error_array_get_best (GPtrArray *errors)
776{
777 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
778 FWUPD_ERROR_VERSION_SAME,
779 FWUPD_ERROR_VERSION_NEWER,
780 FWUPD_ERROR_NOT_SUPPORTED,
781 FWUPD_ERROR_INTERNAL,
782 FWUPD_ERROR_NOT_FOUND,
783 FWUPD_ERROR_LAST };
784 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
785 FWUPD_ERROR_NOT_FOUND,
786 FWUPD_ERROR_NOT_SUPPORTED,
787 FWUPD_ERROR_LAST };
788 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
789 FWUPD_ERROR_VERSION_SAME,
790 FWUPD_ERROR_NOT_FOUND,
791 FWUPD_ERROR_NOT_SUPPORTED,
792 FWUPD_ERROR_LAST };
793
794 /* are all the errors either GUID-not-matched or version-same? */
795 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
796 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
797 return g_error_new (FWUPD_ERROR,
798 FWUPD_ERROR_NOTHING_TO_DO,
799 "All updatable firmware is already installed");
800 }
801
802 /* are all the errors either GUID-not-matched or version same or newer? */
803 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
804 fu_common_error_array_matches_any (errors, err_all_newer)) {
805 return g_error_new (FWUPD_ERROR,
806 FWUPD_ERROR_NOTHING_TO_DO,
807 "All updatable devices already have newer versions");
808 }
809
810 /* get the most important single error */
811 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
812 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
813 if (error_tmp != NULL)
814 return g_error_copy (error_tmp);
815 }
816
817 /* fall back to something */
818 return g_error_new (FWUPD_ERROR,
819 FWUPD_ERROR_NOT_FOUND,
820 "No supported devices found");
821}
Richard Hughes4be17d12018-05-30 20:36:29 +0100822
823/**
824 * fu_common_get_path:
825 * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
826 *
827 * Gets a fwupd-specific system path. These can be overridden with various
828 * environment variables, for instance %FWUPD_DATADIR.
829 *
830 * Returns: a system path, or %NULL if invalid
831 **/
832gchar *
833fu_common_get_path (FuPathKind path_kind)
834{
835 const gchar *tmp;
836 g_autofree gchar *basedir = NULL;
837
838 switch (path_kind) {
839 /* /var */
840 case FU_PATH_KIND_LOCALSTATEDIR:
841 tmp = g_getenv ("FWUPD_LOCALSTATEDIR");
842 if (tmp != NULL)
843 return g_strdup (tmp);
844 tmp = g_getenv ("SNAP_USER_DATA");
845 if (tmp != NULL)
846 return g_build_filename (tmp, LOCALSTATEDIR, NULL);
847 return g_build_filename (LOCALSTATEDIR, NULL);
Richard Hughes282b10d2018-06-22 14:48:00 +0100848 /* /sys/firmware */
849 case FU_PATH_KIND_SYSFSDIR_FW:
850 tmp = g_getenv ("FWUPD_SYSFSFWDIR");
851 if (tmp != NULL)
852 return g_strdup (tmp);
853 return g_strdup ("/sys/firmware");
Richard Hughes83390f62018-06-22 20:36:46 +0100854 /* /sys/bus/platform/drivers */
855 case FU_PATH_KIND_SYSFSDIR_DRIVERS:
856 tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR");
857 if (tmp != NULL)
858 return g_strdup (tmp);
859 return g_strdup ("/sys/bus/platform/drivers");
Richard Hughes4be17d12018-05-30 20:36:29 +0100860 /* /etc */
861 case FU_PATH_KIND_SYSCONFDIR:
862 tmp = g_getenv ("FWUPD_SYSCONFDIR");
863 if (tmp != NULL)
864 return g_strdup (tmp);
865 tmp = g_getenv ("SNAP_USER_DATA");
866 if (tmp != NULL)
867 return g_build_filename (tmp, SYSCONFDIR, NULL);
868 return g_strdup (SYSCONFDIR);
869 /* /usr/lib/<triplet>/fwupd-plugins-3 */
870 case FU_PATH_KIND_PLUGINDIR_PKG:
871 tmp = g_getenv ("FWUPD_PLUGINDIR");
872 if (tmp != NULL)
873 return g_strdup (tmp);
874 tmp = g_getenv ("SNAP");
875 if (tmp != NULL)
876 return g_build_filename (tmp, PLUGINDIR, NULL);
877 return g_build_filename (PLUGINDIR, NULL);
878 /* /usr/share/fwupd */
879 case FU_PATH_KIND_DATADIR_PKG:
880 tmp = g_getenv ("FWUPD_DATADIR");
881 if (tmp != NULL)
882 return g_strdup (tmp);
883 tmp = g_getenv ("SNAP");
884 if (tmp != NULL)
885 return g_build_filename (tmp, DATADIR, PACKAGE_NAME, NULL);
886 return g_build_filename (DATADIR, PACKAGE_NAME, NULL);
Mario Limoncielloe6e2bf92018-07-10 12:11:25 -0500887 /* /usr/libexec/fwupd/efi */
888 case FU_PATH_KIND_EFIAPPDIR:
889 tmp = g_getenv ("FWUPD_EFIAPPDIR");
890 if (tmp != NULL)
891 return g_strdup (tmp);
892#ifdef EFI_APP_LOCATION
893 tmp = g_getenv ("SNAP");
894 if (tmp != NULL)
895 return g_build_filename (tmp, EFI_APP_LOCATION, NULL);
896 return g_strdup (EFI_APP_LOCATION);
897#else
898 return NULL;
899#endif
Richard Hughes4be17d12018-05-30 20:36:29 +0100900 /* /etc/fwupd */
901 case FU_PATH_KIND_SYSCONFDIR_PKG:
902 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
903 return g_build_filename (basedir, PACKAGE_NAME, NULL);
904 /* /var/lib/fwupd */
905 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
906 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
907 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
908 /* /var/cache/fwupd */
909 case FU_PATH_KIND_CACHEDIR_PKG:
910 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
911 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
912 /* this shouldn't happen */
913 default:
914 g_assert_not_reached ();
915 }
916
917 return NULL;
918}