blob: 46abc10970ebb93a5a9215e1e1dfd7637e6ac787 [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
Richard Hughes22367e72018-08-30 10:24:04 +0100352gchar *
353fu_common_find_program_in_path (const gchar *basename, GError **error)
354{
355 gchar *fn = g_find_program_in_path (basename);
356 if (fn == NULL) {
357 g_set_error (error,
358 FWUPD_ERROR,
359 FWUPD_ERROR_NOT_SUPPORTED,
360 "missing executable %s in PATH",
361 basename);
362 return NULL;
363 }
364 return fn;
365}
366
367static gboolean
368fu_common_test_namespace_support (GError **error)
369{
370 /* test if CONFIG_USER_NS is valid */
371 if (!g_file_test ("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) {
372 g_set_error (error,
373 FWUPD_ERROR,
374 FWUPD_ERROR_NOT_SUPPORTED,
375 "missing CONFIG_USER_NS in kernel");
376 return FALSE;
377 }
378 if (g_file_test ("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) {
379 g_autofree gchar *clone = NULL;
380 if (!g_file_get_contents ("/proc/sys/kernel/unprivileged_userns_clone", &clone, NULL, error))
381 return FALSE;
382 if (g_ascii_strtoll (clone, NULL, 10) == 0) {
383 g_set_error (error,
384 FWUPD_ERROR,
385 FWUPD_ERROR_NOT_SUPPORTED,
386 "unprivileged user namespace clones disabled by distro");
387 return FALSE;
388 }
389 }
390 return TRUE;
391}
392
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100393/**
394 * fu_common_firmware_builder:
395 * @bytes: The data to use
Richard Hughes4eada342017-10-03 21:20:32 +0100396 * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
397 * @output_fn: Name of the generated firmware, e.g. `firmware.bin`
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100398 * @error: A #GError, or %NULL
399 *
400 * Builds a firmware file using tools from the host session in a bubblewrap
401 * jail. Several things happen during build:
402 *
403 * 1. The @bytes data is untarred to a temporary location
404 * 2. A bubblewrap container is set up
405 * 3. The startup.sh script is run inside the container
406 * 4. The firmware.bin is extracted from the container
407 * 5. The temporary location is deleted
408 *
409 * Returns: a new #GBytes, or %NULL for error
410 **/
411GBytes *
412fu_common_firmware_builder (GBytes *bytes,
413 const gchar *script_fn,
414 const gchar *output_fn,
415 GError **error)
416{
417 gint rc = 0;
418 g_autofree gchar *argv_str = NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500419 g_autofree gchar *bwrap_fn = NULL;
Richard Hughes4be17d12018-05-30 20:36:29 +0100420 g_autofree gchar *localstatebuilderdir = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100421 g_autofree gchar *localstatedir = NULL;
422 g_autofree gchar *output2_fn = NULL;
423 g_autofree gchar *standard_error = NULL;
424 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100425 g_autofree gchar *tmpdir = NULL;
426 g_autoptr(GBytes) firmware_blob = NULL;
427 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
428
429 g_return_val_if_fail (bytes != NULL, NULL);
430 g_return_val_if_fail (script_fn != NULL, NULL);
431 g_return_val_if_fail (output_fn != NULL, NULL);
432 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
433
Mario Limonciello37b59582018-08-13 08:38:01 -0500434 /* find bwrap in the path */
Richard Hughes22367e72018-08-30 10:24:04 +0100435 bwrap_fn = fu_common_find_program_in_path ("bwrap", error);
436 if (bwrap_fn == NULL)
Richard Hughesddb3e202018-08-23 11:29:57 +0100437 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500438
439 /* test if CONFIG_USER_NS is valid */
Richard Hughes22367e72018-08-30 10:24:04 +0100440 if (!fu_common_test_namespace_support (error))
Richard Hughesddb3e202018-08-23 11:29:57 +0100441 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500442
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100443 /* untar file to temp location */
444 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
445 if (tmpdir == NULL)
446 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100447 if (!fu_common_extract_archive (bytes, tmpdir, error))
448 return NULL;
449
450 /* this is shared with the plugins */
Richard Hughes4be17d12018-05-30 20:36:29 +0100451 localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
452 localstatebuilderdir = g_build_filename (localstatedir, "builder", NULL);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100453
454 /* launch bubblewrap and generate firmware */
Mario Limonciello37b59582018-08-13 08:38:01 -0500455 g_ptr_array_add (argv, g_steal_pointer (&bwrap_fn));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100456 fu_common_add_argv (argv, "--die-with-parent");
457 fu_common_add_argv (argv, "--ro-bind /usr /usr");
Mario Limonciellob8215572018-07-13 09:49:55 -0500458 fu_common_add_argv (argv, "--ro-bind /lib /lib");
459 fu_common_add_argv (argv, "--ro-bind /lib64 /lib64");
460 fu_common_add_argv (argv, "--ro-bind /bin /bin");
461 fu_common_add_argv (argv, "--ro-bind /sbin /sbin");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100462 fu_common_add_argv (argv, "--dir /tmp");
463 fu_common_add_argv (argv, "--dir /var");
464 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
Richard Hughes4be17d12018-05-30 20:36:29 +0100465 if (g_file_test (localstatebuilderdir, G_FILE_TEST_EXISTS))
466 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatebuilderdir);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100467 fu_common_add_argv (argv, "--dev /dev");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100468 fu_common_add_argv (argv, "--chdir /tmp");
469 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100470 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100471 g_ptr_array_add (argv, NULL);
472 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
473 g_debug ("running '%s' in %s", argv_str, tmpdir);
474 if (!g_spawn_sync ("/tmp",
475 (gchar **) argv->pdata,
476 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100477 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100478 NULL, NULL, /* child_setup */
479 &standard_output,
480 &standard_error,
481 &rc,
482 error)) {
483 g_prefix_error (error, "failed to run '%s': ", argv_str);
484 return NULL;
485 }
486 if (standard_output != NULL && standard_output[0] != '\0')
487 g_debug ("console output was: %s", standard_output);
488 if (rc != 0) {
Mario Limonciello37b59582018-08-13 08:38:01 -0500489 FwupdError code = FWUPD_ERROR_INTERNAL;
490 if (errno == ENOTTY)
491 code = FWUPD_ERROR_PERMISSION_DENIED;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100492 g_set_error (error,
493 FWUPD_ERROR,
Mario Limonciello37b59582018-08-13 08:38:01 -0500494 code,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100495 "failed to build firmware: %s",
496 standard_error);
497 return NULL;
498 }
499
500 /* get generated file */
501 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
502 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
503 if (firmware_blob == NULL)
504 return NULL;
505
506 /* cleanup temp directory */
507 if (!fu_common_rmtree (tmpdir, error))
508 return NULL;
509
510 /* success */
511 return g_steal_pointer (&firmware_blob);
512}
Richard Hughes049ccc82017-08-09 15:26:56 +0100513
514typedef struct {
515 FuOutputHandler handler_cb;
516 gpointer handler_user_data;
517 GMainLoop *loop;
518 GSource *source;
519 GInputStream *stream;
520 GCancellable *cancellable;
521} FuCommonSpawnHelper;
522
523static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
524
525static gboolean
526fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
527{
528 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
529 gchar buffer[1024];
530 gssize sz;
531 g_auto(GStrv) split = NULL;
532 g_autoptr(GError) error = NULL;
533
534 /* read from stream */
535 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
536 buffer,
537 sizeof(buffer) - 1,
538 NULL,
539 &error);
540 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100541 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
542 g_warning ("failed to get read from nonblocking fd: %s",
543 error->message);
544 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100545 return G_SOURCE_REMOVE;
546 }
547
548 /* no read possible */
549 if (sz == 0)
550 g_main_loop_quit (helper->loop);
551
552 /* emit lines */
553 if (helper->handler_cb != NULL) {
554 buffer[sz] = '\0';
555 split = g_strsplit (buffer, "\n", -1);
556 for (guint i = 0; split[i] != NULL; i++) {
557 if (split[i][0] == '\0')
558 continue;
559 helper->handler_cb (split[i], helper->handler_user_data);
560 }
561 }
562
563 /* set up the source for the next read */
564 fu_common_spawn_create_pollable_source (helper);
565 return G_SOURCE_REMOVE;
566}
567
568static void
569fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
570{
571 if (helper->source != NULL)
572 g_source_destroy (helper->source);
573 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
574 helper->cancellable);
575 g_source_attach (helper->source, NULL);
576 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
577}
578
579static void
580fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
581{
582 if (helper->stream != NULL)
583 g_object_unref (helper->stream);
584 if (helper->source != NULL)
585 g_source_destroy (helper->source);
586 if (helper->loop != NULL)
587 g_main_loop_unref (helper->loop);
588 g_free (helper);
589}
590
Mario Limoncielloa98df552018-04-16 12:15:51 -0500591#pragma clang diagnostic push
592#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100593G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500594#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100595
596/**
597 * fu_common_spawn_sync:
598 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100599 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
600 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughes049ccc82017-08-09 15:26:56 +0100601 * @cancellable: a #GCancellable, or %NULL
602 * @error: A #GError or %NULL
603 *
604 * Runs a subprocess and waits for it to exit. Any output on standard out or
605 * standard error will be forwarded to @handler_cb as whole lines.
606 *
607 * Returns: %TRUE for success
608 **/
609gboolean
610fu_common_spawn_sync (const gchar * const * argv,
611 FuOutputHandler handler_cb,
612 gpointer handler_user_data,
613 GCancellable *cancellable, GError **error)
614{
615 g_autoptr(FuCommonSpawnHelper) helper = NULL;
616 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100617 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100618
619 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100620 argv_str = g_strjoinv (" ", (gchar **) argv);
621 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100622 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
623 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
624 if (subprocess == NULL)
625 return FALSE;
626
627 /* watch for process to exit */
628 helper = g_new0 (FuCommonSpawnHelper, 1);
629 helper->handler_cb = handler_cb;
630 helper->handler_user_data = handler_user_data;
631 helper->loop = g_main_loop_new (NULL, FALSE);
632 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
633 helper->cancellable = cancellable;
634 fu_common_spawn_create_pollable_source (helper);
635 g_main_loop_run (helper->loop);
636 return g_subprocess_wait_check (subprocess, cancellable, error);
637}
Richard Hughesae252cd2017-12-08 10:48:15 +0000638
639/**
640 * fu_common_write_uint16:
641 * @buf: A writable buffer
642 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100643 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000644 *
645 * Writes a value to a buffer using a specified endian.
646 **/
647void
648fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
649{
650 guint16 val_hw;
651 switch (endian) {
652 case G_BIG_ENDIAN:
653 val_hw = GUINT16_TO_BE(val_native);
654 break;
655 case G_LITTLE_ENDIAN:
656 val_hw = GUINT16_TO_LE(val_native);
657 break;
658 default:
659 g_assert_not_reached ();
660 }
661 memcpy (buf, &val_hw, sizeof(val_hw));
662}
663
664/**
665 * fu_common_write_uint32:
666 * @buf: A writable buffer
667 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100668 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000669 *
670 * Writes a value to a buffer using a specified endian.
671 **/
672void
673fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
674{
675 guint32 val_hw;
676 switch (endian) {
677 case G_BIG_ENDIAN:
678 val_hw = GUINT32_TO_BE(val_native);
679 break;
680 case G_LITTLE_ENDIAN:
681 val_hw = GUINT32_TO_LE(val_native);
682 break;
683 default:
684 g_assert_not_reached ();
685 }
686 memcpy (buf, &val_hw, sizeof(val_hw));
687}
688
689/**
690 * fu_common_read_uint16:
691 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100692 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000693 *
694 * Read a value from a buffer using a specified endian.
695 *
696 * Returns: a value in host byte-order
697 **/
698guint16
699fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
700{
701 guint16 val_hw, val_native;
702 memcpy (&val_hw, buf, sizeof(val_hw));
703 switch (endian) {
704 case G_BIG_ENDIAN:
705 val_native = GUINT16_FROM_BE(val_hw);
706 break;
707 case G_LITTLE_ENDIAN:
708 val_native = GUINT16_FROM_LE(val_hw);
709 break;
710 default:
711 g_assert_not_reached ();
712 }
713 return val_native;
714}
715
716/**
717 * fu_common_read_uint32:
718 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100719 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000720 *
721 * Read a value from a buffer using a specified endian.
722 *
723 * Returns: a value in host byte-order
724 **/
725guint32
726fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
727{
728 guint32 val_hw, val_native;
729 memcpy (&val_hw, buf, sizeof(val_hw));
730 switch (endian) {
731 case G_BIG_ENDIAN:
732 val_native = GUINT32_FROM_BE(val_hw);
733 break;
734 case G_LITTLE_ENDIAN:
735 val_native = GUINT32_FROM_LE(val_hw);
736 break;
737 default:
738 g_assert_not_reached ();
739 }
740 return val_native;
741}
Richard Hughese82eef32018-05-20 10:41:26 +0100742
Richard Hughes73bf2332018-08-28 09:38:09 +0100743/**
744 * fu_common_strtoull:
745 * @str: A string, e.g. "0x1234"
746 *
747 * Converts a string value to an integer. Values are assumed base 10, unless
748 * prefixed with "0x" where they are parsed as base 16.
749 *
750 * Returns: integer value, or 0x0 for error
751 **/
752guint64
753fu_common_strtoull (const gchar *str)
754{
755 guint base = 10;
756 if (str == NULL)
757 return 0x0;
758 if (g_str_has_prefix (str, "0x")) {
759 str += 2;
760 base = 16;
761 }
762 return g_ascii_strtoull (str, NULL, base);
763}
764
Richard Hughesa574a752018-08-31 13:31:03 +0100765/**
766 * fu_common_strstrip:
767 * @str: A string, e.g. " test "
768 *
769 * Removes leading and trailing whitespace from a constant string.
770 *
771 * Returns: newly allocated string
772 **/
773gchar *
774fu_common_strstrip (const gchar *str)
775{
776 guint head = G_MAXUINT;
777 guint tail = 0;
778
779 g_return_val_if_fail (str != NULL, NULL);
780
781 /* find first non-space char */
782 for (guint i = 0; str[i] != '\0'; i++) {
783 if (str[i] != ' ') {
784 head = i;
785 break;
786 }
787 }
788 if (head == G_MAXUINT)
789 return g_strdup ("");
790
791 /* find last non-space char */
792 for (guint i = head; str[i] != '\0'; i++) {
793 if (str[i] != ' ')
794 tail = i;
795 }
796 return g_strndup (str + head, tail - head + 1);
797}
798
Richard Hughese82eef32018-05-20 10:41:26 +0100799static const GError *
800fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
801{
802 for (guint j = 0; j < errors->len; j++) {
803 const GError *error = g_ptr_array_index (errors, j);
804 if (g_error_matches (error, FWUPD_ERROR, error_code))
805 return error;
806 }
807 return NULL;
808}
809
810static guint
811fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
812{
813 guint cnt = 0;
814 for (guint j = 0; j < errors->len; j++) {
815 const GError *error = g_ptr_array_index (errors, j);
816 if (g_error_matches (error, FWUPD_ERROR, error_code))
817 cnt++;
818 }
819 return cnt;
820}
821
822static gboolean
823fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
824{
825 for (guint j = 0; j < errors->len; j++) {
826 const GError *error = g_ptr_array_index (errors, j);
827 gboolean matches_any = FALSE;
828 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
829 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
830 matches_any = TRUE;
831 break;
832 }
833 }
834 if (!matches_any)
835 return FALSE;
836 }
837 return TRUE;
838}
839
840/**
841 * fu_common_error_array_get_best:
842 * @errors: (element-type GError): array of errors
843 *
844 * Finds the 'best' error to show the user from a array of errors, creating a
845 * completely bespoke error where required.
846 *
847 * Returns: (transfer full): a #GError, never %NULL
848 **/
849GError *
850fu_common_error_array_get_best (GPtrArray *errors)
851{
852 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
853 FWUPD_ERROR_VERSION_SAME,
854 FWUPD_ERROR_VERSION_NEWER,
855 FWUPD_ERROR_NOT_SUPPORTED,
856 FWUPD_ERROR_INTERNAL,
857 FWUPD_ERROR_NOT_FOUND,
858 FWUPD_ERROR_LAST };
859 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
860 FWUPD_ERROR_NOT_FOUND,
861 FWUPD_ERROR_NOT_SUPPORTED,
862 FWUPD_ERROR_LAST };
863 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
864 FWUPD_ERROR_VERSION_SAME,
865 FWUPD_ERROR_NOT_FOUND,
866 FWUPD_ERROR_NOT_SUPPORTED,
867 FWUPD_ERROR_LAST };
868
869 /* are all the errors either GUID-not-matched or version-same? */
870 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
871 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
872 return g_error_new (FWUPD_ERROR,
873 FWUPD_ERROR_NOTHING_TO_DO,
874 "All updatable firmware is already installed");
875 }
876
877 /* are all the errors either GUID-not-matched or version same or newer? */
878 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
879 fu_common_error_array_matches_any (errors, err_all_newer)) {
880 return g_error_new (FWUPD_ERROR,
881 FWUPD_ERROR_NOTHING_TO_DO,
882 "All updatable devices already have newer versions");
883 }
884
885 /* get the most important single error */
886 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
887 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
888 if (error_tmp != NULL)
889 return g_error_copy (error_tmp);
890 }
891
892 /* fall back to something */
893 return g_error_new (FWUPD_ERROR,
894 FWUPD_ERROR_NOT_FOUND,
895 "No supported devices found");
896}
Richard Hughes4be17d12018-05-30 20:36:29 +0100897
898/**
899 * fu_common_get_path:
900 * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
901 *
902 * Gets a fwupd-specific system path. These can be overridden with various
903 * environment variables, for instance %FWUPD_DATADIR.
904 *
905 * Returns: a system path, or %NULL if invalid
906 **/
907gchar *
908fu_common_get_path (FuPathKind path_kind)
909{
910 const gchar *tmp;
911 g_autofree gchar *basedir = NULL;
912
913 switch (path_kind) {
914 /* /var */
915 case FU_PATH_KIND_LOCALSTATEDIR:
916 tmp = g_getenv ("FWUPD_LOCALSTATEDIR");
917 if (tmp != NULL)
918 return g_strdup (tmp);
919 tmp = g_getenv ("SNAP_USER_DATA");
920 if (tmp != NULL)
921 return g_build_filename (tmp, LOCALSTATEDIR, NULL);
922 return g_build_filename (LOCALSTATEDIR, NULL);
Richard Hughes282b10d2018-06-22 14:48:00 +0100923 /* /sys/firmware */
924 case FU_PATH_KIND_SYSFSDIR_FW:
925 tmp = g_getenv ("FWUPD_SYSFSFWDIR");
926 if (tmp != NULL)
927 return g_strdup (tmp);
928 return g_strdup ("/sys/firmware");
Richard Hughes83390f62018-06-22 20:36:46 +0100929 /* /sys/bus/platform/drivers */
930 case FU_PATH_KIND_SYSFSDIR_DRIVERS:
931 tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR");
932 if (tmp != NULL)
933 return g_strdup (tmp);
934 return g_strdup ("/sys/bus/platform/drivers");
Richard Hughes4be17d12018-05-30 20:36:29 +0100935 /* /etc */
936 case FU_PATH_KIND_SYSCONFDIR:
937 tmp = g_getenv ("FWUPD_SYSCONFDIR");
938 if (tmp != NULL)
939 return g_strdup (tmp);
940 tmp = g_getenv ("SNAP_USER_DATA");
941 if (tmp != NULL)
942 return g_build_filename (tmp, SYSCONFDIR, NULL);
943 return g_strdup (SYSCONFDIR);
944 /* /usr/lib/<triplet>/fwupd-plugins-3 */
945 case FU_PATH_KIND_PLUGINDIR_PKG:
946 tmp = g_getenv ("FWUPD_PLUGINDIR");
947 if (tmp != NULL)
948 return g_strdup (tmp);
949 tmp = g_getenv ("SNAP");
950 if (tmp != NULL)
951 return g_build_filename (tmp, PLUGINDIR, NULL);
952 return g_build_filename (PLUGINDIR, NULL);
953 /* /usr/share/fwupd */
954 case FU_PATH_KIND_DATADIR_PKG:
955 tmp = g_getenv ("FWUPD_DATADIR");
956 if (tmp != NULL)
957 return g_strdup (tmp);
958 tmp = g_getenv ("SNAP");
959 if (tmp != NULL)
960 return g_build_filename (tmp, DATADIR, PACKAGE_NAME, NULL);
961 return g_build_filename (DATADIR, PACKAGE_NAME, NULL);
Mario Limoncielloe6e2bf92018-07-10 12:11:25 -0500962 /* /usr/libexec/fwupd/efi */
963 case FU_PATH_KIND_EFIAPPDIR:
964 tmp = g_getenv ("FWUPD_EFIAPPDIR");
965 if (tmp != NULL)
966 return g_strdup (tmp);
967#ifdef EFI_APP_LOCATION
968 tmp = g_getenv ("SNAP");
969 if (tmp != NULL)
970 return g_build_filename (tmp, EFI_APP_LOCATION, NULL);
971 return g_strdup (EFI_APP_LOCATION);
972#else
973 return NULL;
974#endif
Richard Hughes4be17d12018-05-30 20:36:29 +0100975 /* /etc/fwupd */
976 case FU_PATH_KIND_SYSCONFDIR_PKG:
977 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
978 return g_build_filename (basedir, PACKAGE_NAME, NULL);
979 /* /var/lib/fwupd */
980 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
981 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
982 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
983 /* /var/cache/fwupd */
984 case FU_PATH_KIND_CACHEDIR_PKG:
985 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
986 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
987 /* this shouldn't happen */
988 default:
989 g_assert_not_reached ();
990 }
991
992 return NULL;
993}