blob: e7117e30e6f097406979485be7d89577557655d0 [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
Richard Hughesb08e7bc2018-09-11 10:51:13 +01007#define G_LOG_DOMAIN "FuCommon"
8
Richard Hughes943d2c92017-06-21 09:04:39 +01009#include <config.h>
10
11#include <gio/gunixinputstream.h>
Richard Hughes954dd9f2017-08-08 13:36:25 +010012#include <glib/gstdio.h>
13
Richard Hughes94f939a2017-08-08 12:21:39 +010014#include <archive_entry.h>
15#include <archive.h>
Richard Hughes7ee42fe2017-08-15 14:06:21 +010016#include <errno.h>
Richard Hughesae252cd2017-12-08 10:48:15 +000017#include <string.h>
Richard Hughes943d2c92017-06-21 09:04:39 +010018
19#include "fwupd-error.h"
20
21#include "fu-common.h"
22
23/**
Richard Hughes4eada342017-10-03 21:20:32 +010024 * SECTION:fu-common
25 * @short_description: common functionality for plugins to use
26 *
27 * Helper functions that can be used by the daemon and plugins.
28 *
29 * See also: #FuPlugin
30 */
31
32/**
Richard Hughes954dd9f2017-08-08 13:36:25 +010033 * fu_common_rmtree:
34 * @directory: a directory name
35 * @error: A #GError or %NULL
36 *
37 * Recursively removes a directory.
38 *
39 * Returns: %TRUE for success, %FALSE otherwise
40 **/
41gboolean
42fu_common_rmtree (const gchar *directory, GError **error)
43{
44 const gchar *filename;
45 g_autoptr(GDir) dir = NULL;
46
47 /* try to open */
Richard Hughes455fdd32017-08-16 12:26:44 +010048 g_debug ("removing %s", directory);
Richard Hughes954dd9f2017-08-08 13:36:25 +010049 dir = g_dir_open (directory, 0, error);
50 if (dir == NULL)
51 return FALSE;
52
53 /* find each */
54 while ((filename = g_dir_read_name (dir))) {
55 g_autofree gchar *src = NULL;
56 src = g_build_filename (directory, filename, NULL);
57 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
58 if (!fu_common_rmtree (src, error))
59 return FALSE;
60 } else {
61 if (g_unlink (src) != 0) {
62 g_set_error (error,
63 FWUPD_ERROR,
64 FWUPD_ERROR_INTERNAL,
65 "Failed to delete: %s", src);
66 return FALSE;
67 }
68 }
69 }
70 if (g_remove (directory) != 0) {
71 g_set_error (error,
72 FWUPD_ERROR,
73 FWUPD_ERROR_INTERNAL,
74 "Failed to delete: %s", directory);
75 return FALSE;
76 }
77 return TRUE;
78}
79
Richard Hughes89e968b2018-03-07 10:01:08 +000080static gboolean
81fu_common_get_file_list_internal (GPtrArray *files, const gchar *directory, GError **error)
82{
83 const gchar *filename;
84 g_autoptr(GDir) dir = NULL;
85
86 /* try to open */
87 dir = g_dir_open (directory, 0, error);
88 if (dir == NULL)
89 return FALSE;
90
91 /* find each */
92 while ((filename = g_dir_read_name (dir))) {
93 g_autofree gchar *src = g_build_filename (directory, filename, NULL);
94 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
95 if (!fu_common_get_file_list_internal (files, src, error))
96 return FALSE;
97 } else {
98 g_ptr_array_add (files, g_steal_pointer (&src));
99 }
100 }
101 return TRUE;
102
103}
104
105/**
106 * fu_common_get_files_recursive:
Richard Hughes8aa72392018-05-02 08:38:43 +0100107 * @path: a directory name
Richard Hughes89e968b2018-03-07 10:01:08 +0000108 * @error: A #GError or %NULL
109 *
110 * Returns every file found under @directory, and any subdirectory.
111 * If any path under @directory cannot be accessed due to permissions an error
112 * will be returned.
113 *
114 * Returns: (element-type: utf8) (transfer container): array of files, or %NULL for error
115 **/
116GPtrArray *
117fu_common_get_files_recursive (const gchar *path, GError **error)
118{
119 g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free);
120 if (!fu_common_get_file_list_internal (files, path, error))
121 return NULL;
122 return g_steal_pointer (&files);
123}
Richard Hughes954dd9f2017-08-08 13:36:25 +0100124/**
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100125 * fu_common_mkdir_parent:
126 * @filename: A full pathname
127 * @error: A #GError, or %NULL
128 *
129 * Creates any required directories, including any parent directories.
130 *
131 * Returns: %TRUE for success
132 **/
133gboolean
134fu_common_mkdir_parent (const gchar *filename, GError **error)
135{
136 g_autofree gchar *parent = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100137
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100138 parent = g_path_get_dirname (filename);
Richard Hughes455fdd32017-08-16 12:26:44 +0100139 g_debug ("creating path %s", parent);
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100140 if (g_mkdir_with_parents (parent, 0755) == -1) {
141 g_set_error (error,
142 FWUPD_ERROR,
143 FWUPD_ERROR_INTERNAL,
144 "Failed to create '%s': %s",
145 parent, g_strerror (errno));
146 return FALSE;
147 }
148 return TRUE;
149}
150
151/**
Richard Hughes943d2c92017-06-21 09:04:39 +0100152 * fu_common_set_contents_bytes:
153 * @filename: A filename
154 * @bytes: The data to write
155 * @error: A #GError, or %NULL
156 *
157 * Writes a blob of data to a filename, creating the parent directories as
158 * required.
159 *
160 * Returns: %TRUE for success
161 **/
162gboolean
163fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error)
164{
165 const gchar *data;
166 gsize size;
167 g_autoptr(GFile) file = NULL;
168 g_autoptr(GFile) file_parent = NULL;
169
170 file = g_file_new_for_path (filename);
171 file_parent = g_file_get_parent (file);
172 if (!g_file_query_exists (file_parent, NULL)) {
173 if (!g_file_make_directory_with_parents (file_parent, NULL, error))
174 return FALSE;
175 }
176 data = g_bytes_get_data (bytes, &size);
Richard Hughes455fdd32017-08-16 12:26:44 +0100177 g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
Richard Hughes943d2c92017-06-21 09:04:39 +0100178 return g_file_set_contents (filename, data, size, error);
179}
180
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100181/**
182 * fu_common_get_contents_bytes:
183 * @filename: A filename
184 * @error: A #GError, or %NULL
185 *
186 * Reads a blob of data from a file.
187 *
188 * Returns: a #GBytes, or %NULL for failure
189 **/
190GBytes *
191fu_common_get_contents_bytes (const gchar *filename, GError **error)
192{
193 gchar *data = NULL;
194 gsize len = 0;
195 if (!g_file_get_contents (filename, &data, &len, error))
196 return NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100197 g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100198 return g_bytes_new_take (data, len);
199}
Richard Hughes943d2c92017-06-21 09:04:39 +0100200
201/**
202 * fu_common_get_contents_fd:
203 * @fd: A file descriptor
204 * @count: The maximum number of bytes to read
205 * @error: A #GError, or %NULL
206 *
207 * Reads a blob from a specific file descriptor.
208 *
209 * Note: this will close the fd when done
210 *
Richard Hughes4eada342017-10-03 21:20:32 +0100211 * Returns: (transfer full): a #GBytes, or %NULL
Richard Hughes943d2c92017-06-21 09:04:39 +0100212 **/
213GBytes *
214fu_common_get_contents_fd (gint fd, gsize count, GError **error)
215{
216 g_autoptr(GBytes) blob = NULL;
217 g_autoptr(GError) error_local = NULL;
218 g_autoptr(GInputStream) stream = NULL;
219
220 g_return_val_if_fail (fd > 0, NULL);
Richard Hughes943d2c92017-06-21 09:04:39 +0100221 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
222
Richard Hughes919f8ab2018-02-14 10:24:56 +0000223 /* this is invalid */
224 if (count == 0) {
225 g_set_error_literal (error,
226 FWUPD_ERROR,
227 FWUPD_ERROR_NOT_SUPPORTED,
228 "A maximum read size must be specified");
229 return NULL;
230 }
231
Richard Hughes943d2c92017-06-21 09:04:39 +0100232 /* read the entire fd to a data blob */
233 stream = g_unix_input_stream_new (fd, TRUE);
234 blob = g_input_stream_read_bytes (stream, count, NULL, &error_local);
235 if (blob == NULL) {
236 g_set_error_literal (error,
237 FWUPD_ERROR,
238 FWUPD_ERROR_INVALID_FILE,
239 error_local->message);
240 return NULL;
241 }
242 return g_steal_pointer (&blob);
243}
Richard Hughes94f939a2017-08-08 12:21:39 +0100244
245static gboolean
246fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir)
247{
248 const gchar *tmp;
249 g_autofree gchar *buf = NULL;
250
251 /* no output file */
252 if (archive_entry_pathname (entry) == NULL)
253 return FALSE;
254
255 /* update output path */
256 tmp = archive_entry_pathname (entry);
257 buf = g_build_filename (dir, tmp, NULL);
258 archive_entry_update_pathname_utf8 (entry, buf);
259 return TRUE;
260}
261
262/**
263 * fu_common_extract_archive:
264 * @blob: a #GBytes archive as a blob
Richard Hughes4eada342017-10-03 21:20:32 +0100265 * @dir: a directory name to extract to
Richard Hughes94f939a2017-08-08 12:21:39 +0100266 * @error: A #GError, or %NULL
267 *
268 * Extracts an achive to a directory.
269 *
270 * Returns: %TRUE for success
271 **/
272gboolean
273fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error)
274{
275 gboolean ret = TRUE;
276 int r;
277 struct archive *arch = NULL;
278 struct archive_entry *entry;
279
280 /* decompress anything matching either glob */
Richard Hughes455fdd32017-08-16 12:26:44 +0100281 g_debug ("decompressing into %s", dir);
Richard Hughes94f939a2017-08-08 12:21:39 +0100282 arch = archive_read_new ();
283 archive_read_support_format_all (arch);
284 archive_read_support_filter_all (arch);
285 r = archive_read_open_memory (arch,
286 (void *) g_bytes_get_data (blob, NULL),
287 (size_t) g_bytes_get_size (blob));
288 if (r != 0) {
289 ret = FALSE;
290 g_set_error (error,
291 FWUPD_ERROR,
292 FWUPD_ERROR_INTERNAL,
293 "Cannot open: %s",
294 archive_error_string (arch));
295 goto out;
296 }
297 for (;;) {
298 gboolean valid;
Richard Hughes94f939a2017-08-08 12:21:39 +0100299 r = archive_read_next_header (arch, &entry);
300 if (r == ARCHIVE_EOF)
301 break;
302 if (r != ARCHIVE_OK) {
303 ret = FALSE;
304 g_set_error (error,
305 FWUPD_ERROR,
306 FWUPD_ERROR_INTERNAL,
307 "Cannot read header: %s",
308 archive_error_string (arch));
309 goto out;
310 }
311
312 /* only extract if valid */
313 valid = fu_common_extract_archive_entry (entry, dir);
314 if (!valid)
315 continue;
316 r = archive_read_extract (arch, entry, 0);
317 if (r != ARCHIVE_OK) {
318 ret = FALSE;
319 g_set_error (error,
320 FWUPD_ERROR,
321 FWUPD_ERROR_INTERNAL,
322 "Cannot extract: %s",
323 archive_error_string (arch));
324 goto out;
325 }
326 }
327out:
328 if (arch != NULL) {
329 archive_read_close (arch);
330 archive_read_free (arch);
331 }
332 return ret;
333}
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100334
335static void
Yehezkel Bernate43f7fb2017-08-30 12:09:34 +0300336fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3);
337
338static void
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100339fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...)
340{
341 va_list args;
342 g_autofree gchar *tmp = NULL;
343 g_auto(GStrv) split = NULL;
344
345 va_start (args, fmt);
346 tmp = g_strdup_vprintf (fmt, args);
347 va_end (args);
348
349 split = g_strsplit (tmp, " ", -1);
350 for (guint i = 0; split[i] != NULL; i++)
351 g_ptr_array_add (argv, g_strdup (split[i]));
352}
353
Richard Hughes22367e72018-08-30 10:24:04 +0100354gchar *
355fu_common_find_program_in_path (const gchar *basename, GError **error)
356{
357 gchar *fn = g_find_program_in_path (basename);
358 if (fn == NULL) {
359 g_set_error (error,
360 FWUPD_ERROR,
361 FWUPD_ERROR_NOT_SUPPORTED,
362 "missing executable %s in PATH",
363 basename);
364 return NULL;
365 }
366 return fn;
367}
368
369static gboolean
370fu_common_test_namespace_support (GError **error)
371{
372 /* test if CONFIG_USER_NS is valid */
373 if (!g_file_test ("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) {
374 g_set_error (error,
375 FWUPD_ERROR,
376 FWUPD_ERROR_NOT_SUPPORTED,
377 "missing CONFIG_USER_NS in kernel");
378 return FALSE;
379 }
380 if (g_file_test ("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) {
381 g_autofree gchar *clone = NULL;
382 if (!g_file_get_contents ("/proc/sys/kernel/unprivileged_userns_clone", &clone, NULL, error))
383 return FALSE;
384 if (g_ascii_strtoll (clone, NULL, 10) == 0) {
385 g_set_error (error,
386 FWUPD_ERROR,
387 FWUPD_ERROR_NOT_SUPPORTED,
388 "unprivileged user namespace clones disabled by distro");
389 return FALSE;
390 }
391 }
392 return TRUE;
393}
394
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100395/**
396 * fu_common_firmware_builder:
397 * @bytes: The data to use
Richard Hughes4eada342017-10-03 21:20:32 +0100398 * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
399 * @output_fn: Name of the generated firmware, e.g. `firmware.bin`
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100400 * @error: A #GError, or %NULL
401 *
402 * Builds a firmware file using tools from the host session in a bubblewrap
403 * jail. Several things happen during build:
404 *
405 * 1. The @bytes data is untarred to a temporary location
406 * 2. A bubblewrap container is set up
407 * 3. The startup.sh script is run inside the container
408 * 4. The firmware.bin is extracted from the container
409 * 5. The temporary location is deleted
410 *
411 * Returns: a new #GBytes, or %NULL for error
412 **/
413GBytes *
414fu_common_firmware_builder (GBytes *bytes,
415 const gchar *script_fn,
416 const gchar *output_fn,
417 GError **error)
418{
419 gint rc = 0;
420 g_autofree gchar *argv_str = NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500421 g_autofree gchar *bwrap_fn = NULL;
Richard Hughes4be17d12018-05-30 20:36:29 +0100422 g_autofree gchar *localstatebuilderdir = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100423 g_autofree gchar *localstatedir = NULL;
424 g_autofree gchar *output2_fn = NULL;
425 g_autofree gchar *standard_error = NULL;
426 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100427 g_autofree gchar *tmpdir = NULL;
428 g_autoptr(GBytes) firmware_blob = NULL;
429 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
430
431 g_return_val_if_fail (bytes != NULL, NULL);
432 g_return_val_if_fail (script_fn != NULL, NULL);
433 g_return_val_if_fail (output_fn != NULL, NULL);
434 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
435
Mario Limonciello37b59582018-08-13 08:38:01 -0500436 /* find bwrap in the path */
Richard Hughes22367e72018-08-30 10:24:04 +0100437 bwrap_fn = fu_common_find_program_in_path ("bwrap", error);
438 if (bwrap_fn == NULL)
Richard Hughesddb3e202018-08-23 11:29:57 +0100439 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500440
441 /* test if CONFIG_USER_NS is valid */
Richard Hughes22367e72018-08-30 10:24:04 +0100442 if (!fu_common_test_namespace_support (error))
Richard Hughesddb3e202018-08-23 11:29:57 +0100443 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500444
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100445 /* untar file to temp location */
446 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
447 if (tmpdir == NULL)
448 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100449 if (!fu_common_extract_archive (bytes, tmpdir, error))
450 return NULL;
451
452 /* this is shared with the plugins */
Richard Hughes4be17d12018-05-30 20:36:29 +0100453 localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
454 localstatebuilderdir = g_build_filename (localstatedir, "builder", NULL);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100455
456 /* launch bubblewrap and generate firmware */
Mario Limonciello37b59582018-08-13 08:38:01 -0500457 g_ptr_array_add (argv, g_steal_pointer (&bwrap_fn));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100458 fu_common_add_argv (argv, "--die-with-parent");
459 fu_common_add_argv (argv, "--ro-bind /usr /usr");
Mario Limonciellob8215572018-07-13 09:49:55 -0500460 fu_common_add_argv (argv, "--ro-bind /lib /lib");
461 fu_common_add_argv (argv, "--ro-bind /lib64 /lib64");
462 fu_common_add_argv (argv, "--ro-bind /bin /bin");
463 fu_common_add_argv (argv, "--ro-bind /sbin /sbin");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100464 fu_common_add_argv (argv, "--dir /tmp");
465 fu_common_add_argv (argv, "--dir /var");
466 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
Richard Hughes4be17d12018-05-30 20:36:29 +0100467 if (g_file_test (localstatebuilderdir, G_FILE_TEST_EXISTS))
468 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatebuilderdir);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100469 fu_common_add_argv (argv, "--dev /dev");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100470 fu_common_add_argv (argv, "--chdir /tmp");
471 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100472 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100473 g_ptr_array_add (argv, NULL);
474 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
475 g_debug ("running '%s' in %s", argv_str, tmpdir);
476 if (!g_spawn_sync ("/tmp",
477 (gchar **) argv->pdata,
478 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100479 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100480 NULL, NULL, /* child_setup */
481 &standard_output,
482 &standard_error,
483 &rc,
484 error)) {
485 g_prefix_error (error, "failed to run '%s': ", argv_str);
486 return NULL;
487 }
488 if (standard_output != NULL && standard_output[0] != '\0')
489 g_debug ("console output was: %s", standard_output);
490 if (rc != 0) {
Mario Limonciello37b59582018-08-13 08:38:01 -0500491 FwupdError code = FWUPD_ERROR_INTERNAL;
492 if (errno == ENOTTY)
493 code = FWUPD_ERROR_PERMISSION_DENIED;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100494 g_set_error (error,
495 FWUPD_ERROR,
Mario Limonciello37b59582018-08-13 08:38:01 -0500496 code,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100497 "failed to build firmware: %s",
498 standard_error);
499 return NULL;
500 }
501
502 /* get generated file */
503 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
504 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
505 if (firmware_blob == NULL)
506 return NULL;
507
508 /* cleanup temp directory */
509 if (!fu_common_rmtree (tmpdir, error))
510 return NULL;
511
512 /* success */
513 return g_steal_pointer (&firmware_blob);
514}
Richard Hughes049ccc82017-08-09 15:26:56 +0100515
516typedef struct {
517 FuOutputHandler handler_cb;
518 gpointer handler_user_data;
519 GMainLoop *loop;
520 GSource *source;
521 GInputStream *stream;
522 GCancellable *cancellable;
523} FuCommonSpawnHelper;
524
525static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
526
527static gboolean
528fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
529{
530 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
531 gchar buffer[1024];
532 gssize sz;
533 g_auto(GStrv) split = NULL;
534 g_autoptr(GError) error = NULL;
535
536 /* read from stream */
537 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
538 buffer,
539 sizeof(buffer) - 1,
540 NULL,
541 &error);
542 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100543 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
544 g_warning ("failed to get read from nonblocking fd: %s",
545 error->message);
546 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100547 return G_SOURCE_REMOVE;
548 }
549
550 /* no read possible */
551 if (sz == 0)
552 g_main_loop_quit (helper->loop);
553
554 /* emit lines */
555 if (helper->handler_cb != NULL) {
556 buffer[sz] = '\0';
557 split = g_strsplit (buffer, "\n", -1);
558 for (guint i = 0; split[i] != NULL; i++) {
559 if (split[i][0] == '\0')
560 continue;
561 helper->handler_cb (split[i], helper->handler_user_data);
562 }
563 }
564
565 /* set up the source for the next read */
566 fu_common_spawn_create_pollable_source (helper);
567 return G_SOURCE_REMOVE;
568}
569
570static void
571fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
572{
573 if (helper->source != NULL)
574 g_source_destroy (helper->source);
575 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
576 helper->cancellable);
577 g_source_attach (helper->source, NULL);
578 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
579}
580
581static void
582fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
583{
584 if (helper->stream != NULL)
585 g_object_unref (helper->stream);
586 if (helper->source != NULL)
587 g_source_destroy (helper->source);
588 if (helper->loop != NULL)
589 g_main_loop_unref (helper->loop);
590 g_free (helper);
591}
592
Mario Limoncielloa98df552018-04-16 12:15:51 -0500593#pragma clang diagnostic push
594#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100595G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500596#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100597
598/**
599 * fu_common_spawn_sync:
600 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100601 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
602 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughes049ccc82017-08-09 15:26:56 +0100603 * @cancellable: a #GCancellable, or %NULL
604 * @error: A #GError or %NULL
605 *
606 * Runs a subprocess and waits for it to exit. Any output on standard out or
607 * standard error will be forwarded to @handler_cb as whole lines.
608 *
609 * Returns: %TRUE for success
610 **/
611gboolean
612fu_common_spawn_sync (const gchar * const * argv,
613 FuOutputHandler handler_cb,
614 gpointer handler_user_data,
615 GCancellable *cancellable, GError **error)
616{
617 g_autoptr(FuCommonSpawnHelper) helper = NULL;
618 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100619 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100620
621 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100622 argv_str = g_strjoinv (" ", (gchar **) argv);
623 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100624 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
625 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
626 if (subprocess == NULL)
627 return FALSE;
628
629 /* watch for process to exit */
630 helper = g_new0 (FuCommonSpawnHelper, 1);
631 helper->handler_cb = handler_cb;
632 helper->handler_user_data = handler_user_data;
633 helper->loop = g_main_loop_new (NULL, FALSE);
634 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
635 helper->cancellable = cancellable;
636 fu_common_spawn_create_pollable_source (helper);
637 g_main_loop_run (helper->loop);
638 return g_subprocess_wait_check (subprocess, cancellable, error);
639}
Richard Hughesae252cd2017-12-08 10:48:15 +0000640
641/**
642 * fu_common_write_uint16:
643 * @buf: A writable buffer
644 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100645 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000646 *
647 * Writes a value to a buffer using a specified endian.
648 **/
649void
650fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
651{
652 guint16 val_hw;
653 switch (endian) {
654 case G_BIG_ENDIAN:
655 val_hw = GUINT16_TO_BE(val_native);
656 break;
657 case G_LITTLE_ENDIAN:
658 val_hw = GUINT16_TO_LE(val_native);
659 break;
660 default:
661 g_assert_not_reached ();
662 }
663 memcpy (buf, &val_hw, sizeof(val_hw));
664}
665
666/**
667 * fu_common_write_uint32:
668 * @buf: A writable buffer
669 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100670 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000671 *
672 * Writes a value to a buffer using a specified endian.
673 **/
674void
675fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
676{
677 guint32 val_hw;
678 switch (endian) {
679 case G_BIG_ENDIAN:
680 val_hw = GUINT32_TO_BE(val_native);
681 break;
682 case G_LITTLE_ENDIAN:
683 val_hw = GUINT32_TO_LE(val_native);
684 break;
685 default:
686 g_assert_not_reached ();
687 }
688 memcpy (buf, &val_hw, sizeof(val_hw));
689}
690
691/**
692 * fu_common_read_uint16:
693 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100694 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000695 *
696 * Read a value from a buffer using a specified endian.
697 *
698 * Returns: a value in host byte-order
699 **/
700guint16
701fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
702{
703 guint16 val_hw, val_native;
704 memcpy (&val_hw, buf, sizeof(val_hw));
705 switch (endian) {
706 case G_BIG_ENDIAN:
707 val_native = GUINT16_FROM_BE(val_hw);
708 break;
709 case G_LITTLE_ENDIAN:
710 val_native = GUINT16_FROM_LE(val_hw);
711 break;
712 default:
713 g_assert_not_reached ();
714 }
715 return val_native;
716}
717
718/**
719 * fu_common_read_uint32:
720 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100721 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000722 *
723 * Read a value from a buffer using a specified endian.
724 *
725 * Returns: a value in host byte-order
726 **/
727guint32
728fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
729{
730 guint32 val_hw, val_native;
731 memcpy (&val_hw, buf, sizeof(val_hw));
732 switch (endian) {
733 case G_BIG_ENDIAN:
734 val_native = GUINT32_FROM_BE(val_hw);
735 break;
736 case G_LITTLE_ENDIAN:
737 val_native = GUINT32_FROM_LE(val_hw);
738 break;
739 default:
740 g_assert_not_reached ();
741 }
742 return val_native;
743}
Richard Hughese82eef32018-05-20 10:41:26 +0100744
Richard Hughes73bf2332018-08-28 09:38:09 +0100745/**
746 * fu_common_strtoull:
747 * @str: A string, e.g. "0x1234"
748 *
749 * Converts a string value to an integer. Values are assumed base 10, unless
750 * prefixed with "0x" where they are parsed as base 16.
751 *
752 * Returns: integer value, or 0x0 for error
753 **/
754guint64
755fu_common_strtoull (const gchar *str)
756{
757 guint base = 10;
758 if (str == NULL)
759 return 0x0;
760 if (g_str_has_prefix (str, "0x")) {
761 str += 2;
762 base = 16;
763 }
764 return g_ascii_strtoull (str, NULL, base);
765}
766
Richard Hughesa574a752018-08-31 13:31:03 +0100767/**
768 * fu_common_strstrip:
769 * @str: A string, e.g. " test "
770 *
771 * Removes leading and trailing whitespace from a constant string.
772 *
773 * Returns: newly allocated string
774 **/
775gchar *
776fu_common_strstrip (const gchar *str)
777{
778 guint head = G_MAXUINT;
779 guint tail = 0;
780
781 g_return_val_if_fail (str != NULL, NULL);
782
783 /* find first non-space char */
784 for (guint i = 0; str[i] != '\0'; i++) {
785 if (str[i] != ' ') {
786 head = i;
787 break;
788 }
789 }
790 if (head == G_MAXUINT)
791 return g_strdup ("");
792
793 /* find last non-space char */
794 for (guint i = head; str[i] != '\0'; i++) {
795 if (str[i] != ' ')
796 tail = i;
797 }
798 return g_strndup (str + head, tail - head + 1);
799}
800
Richard Hughese82eef32018-05-20 10:41:26 +0100801static const GError *
802fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
803{
804 for (guint j = 0; j < errors->len; j++) {
805 const GError *error = g_ptr_array_index (errors, j);
806 if (g_error_matches (error, FWUPD_ERROR, error_code))
807 return error;
808 }
809 return NULL;
810}
811
812static guint
813fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
814{
815 guint cnt = 0;
816 for (guint j = 0; j < errors->len; j++) {
817 const GError *error = g_ptr_array_index (errors, j);
818 if (g_error_matches (error, FWUPD_ERROR, error_code))
819 cnt++;
820 }
821 return cnt;
822}
823
824static gboolean
825fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
826{
827 for (guint j = 0; j < errors->len; j++) {
828 const GError *error = g_ptr_array_index (errors, j);
829 gboolean matches_any = FALSE;
830 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
831 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
832 matches_any = TRUE;
833 break;
834 }
835 }
836 if (!matches_any)
837 return FALSE;
838 }
839 return TRUE;
840}
841
842/**
843 * fu_common_error_array_get_best:
844 * @errors: (element-type GError): array of errors
845 *
846 * Finds the 'best' error to show the user from a array of errors, creating a
847 * completely bespoke error where required.
848 *
849 * Returns: (transfer full): a #GError, never %NULL
850 **/
851GError *
852fu_common_error_array_get_best (GPtrArray *errors)
853{
854 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
855 FWUPD_ERROR_VERSION_SAME,
856 FWUPD_ERROR_VERSION_NEWER,
857 FWUPD_ERROR_NOT_SUPPORTED,
858 FWUPD_ERROR_INTERNAL,
859 FWUPD_ERROR_NOT_FOUND,
860 FWUPD_ERROR_LAST };
861 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
862 FWUPD_ERROR_NOT_FOUND,
863 FWUPD_ERROR_NOT_SUPPORTED,
864 FWUPD_ERROR_LAST };
865 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
866 FWUPD_ERROR_VERSION_SAME,
867 FWUPD_ERROR_NOT_FOUND,
868 FWUPD_ERROR_NOT_SUPPORTED,
869 FWUPD_ERROR_LAST };
870
871 /* are all the errors either GUID-not-matched or version-same? */
872 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
873 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
874 return g_error_new (FWUPD_ERROR,
875 FWUPD_ERROR_NOTHING_TO_DO,
876 "All updatable firmware is already installed");
877 }
878
879 /* are all the errors either GUID-not-matched or version same or newer? */
880 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
881 fu_common_error_array_matches_any (errors, err_all_newer)) {
882 return g_error_new (FWUPD_ERROR,
883 FWUPD_ERROR_NOTHING_TO_DO,
884 "All updatable devices already have newer versions");
885 }
886
887 /* get the most important single error */
888 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
889 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
890 if (error_tmp != NULL)
891 return g_error_copy (error_tmp);
892 }
893
894 /* fall back to something */
895 return g_error_new (FWUPD_ERROR,
896 FWUPD_ERROR_NOT_FOUND,
897 "No supported devices found");
898}
Richard Hughes4be17d12018-05-30 20:36:29 +0100899
900/**
901 * fu_common_get_path:
902 * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
903 *
904 * Gets a fwupd-specific system path. These can be overridden with various
905 * environment variables, for instance %FWUPD_DATADIR.
906 *
907 * Returns: a system path, or %NULL if invalid
908 **/
909gchar *
910fu_common_get_path (FuPathKind path_kind)
911{
912 const gchar *tmp;
913 g_autofree gchar *basedir = NULL;
914
915 switch (path_kind) {
916 /* /var */
917 case FU_PATH_KIND_LOCALSTATEDIR:
918 tmp = g_getenv ("FWUPD_LOCALSTATEDIR");
919 if (tmp != NULL)
920 return g_strdup (tmp);
921 tmp = g_getenv ("SNAP_USER_DATA");
922 if (tmp != NULL)
923 return g_build_filename (tmp, LOCALSTATEDIR, NULL);
924 return g_build_filename (LOCALSTATEDIR, NULL);
Richard Hughes282b10d2018-06-22 14:48:00 +0100925 /* /sys/firmware */
926 case FU_PATH_KIND_SYSFSDIR_FW:
927 tmp = g_getenv ("FWUPD_SYSFSFWDIR");
928 if (tmp != NULL)
929 return g_strdup (tmp);
930 return g_strdup ("/sys/firmware");
Richard Hughes83390f62018-06-22 20:36:46 +0100931 /* /sys/bus/platform/drivers */
932 case FU_PATH_KIND_SYSFSDIR_DRIVERS:
933 tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR");
934 if (tmp != NULL)
935 return g_strdup (tmp);
936 return g_strdup ("/sys/bus/platform/drivers");
Richard Hughes4be17d12018-05-30 20:36:29 +0100937 /* /etc */
938 case FU_PATH_KIND_SYSCONFDIR:
939 tmp = g_getenv ("FWUPD_SYSCONFDIR");
940 if (tmp != NULL)
941 return g_strdup (tmp);
942 tmp = g_getenv ("SNAP_USER_DATA");
943 if (tmp != NULL)
944 return g_build_filename (tmp, SYSCONFDIR, NULL);
945 return g_strdup (SYSCONFDIR);
946 /* /usr/lib/<triplet>/fwupd-plugins-3 */
947 case FU_PATH_KIND_PLUGINDIR_PKG:
948 tmp = g_getenv ("FWUPD_PLUGINDIR");
949 if (tmp != NULL)
950 return g_strdup (tmp);
951 tmp = g_getenv ("SNAP");
952 if (tmp != NULL)
953 return g_build_filename (tmp, PLUGINDIR, NULL);
954 return g_build_filename (PLUGINDIR, NULL);
955 /* /usr/share/fwupd */
956 case FU_PATH_KIND_DATADIR_PKG:
957 tmp = g_getenv ("FWUPD_DATADIR");
958 if (tmp != NULL)
959 return g_strdup (tmp);
960 tmp = g_getenv ("SNAP");
961 if (tmp != NULL)
962 return g_build_filename (tmp, DATADIR, PACKAGE_NAME, NULL);
963 return g_build_filename (DATADIR, PACKAGE_NAME, NULL);
Mario Limoncielloe6e2bf92018-07-10 12:11:25 -0500964 /* /usr/libexec/fwupd/efi */
965 case FU_PATH_KIND_EFIAPPDIR:
966 tmp = g_getenv ("FWUPD_EFIAPPDIR");
967 if (tmp != NULL)
968 return g_strdup (tmp);
969#ifdef EFI_APP_LOCATION
970 tmp = g_getenv ("SNAP");
971 if (tmp != NULL)
972 return g_build_filename (tmp, EFI_APP_LOCATION, NULL);
973 return g_strdup (EFI_APP_LOCATION);
974#else
975 return NULL;
976#endif
Richard Hughes4be17d12018-05-30 20:36:29 +0100977 /* /etc/fwupd */
978 case FU_PATH_KIND_SYSCONFDIR_PKG:
979 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
980 return g_build_filename (basedir, PACKAGE_NAME, NULL);
981 /* /var/lib/fwupd */
982 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
983 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
984 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
985 /* /var/cache/fwupd */
986 case FU_PATH_KIND_CACHEDIR_PKG:
987 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
988 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
989 /* this shouldn't happen */
990 default:
Richard Hughesbeb47a82018-09-11 18:28:53 +0100991 g_warning ("cannot build path for unknown kind %u", path_kind);
Richard Hughes4be17d12018-05-30 20:36:29 +0100992 }
993
994 return NULL;
995}
Richard Hughes83e56c12018-10-10 20:24:41 +0100996
997/**
998 * fu_common_string_replace:
999 * @string: The #GString to operate on
1000 * @search: The text to search for
1001 * @replace: The text to use for substitutions
1002 *
1003 * Performs multiple search and replace operations on the given string.
1004 *
1005 * Returns: the number of replacements done, or 0 if @search is not found.
1006 *
1007 * Since: 1.2.0
1008 **/
1009guint
1010fu_common_string_replace (GString *string, const gchar *search, const gchar *replace)
1011{
1012 gchar *tmp;
1013 guint count = 0;
1014 gsize search_idx = 0;
1015 gsize replace_len;
1016 gsize search_len;
1017
1018 g_return_val_if_fail (string != NULL, 0);
1019 g_return_val_if_fail (search != NULL, 0);
1020 g_return_val_if_fail (replace != NULL, 0);
1021
1022 /* nothing to do */
1023 if (string->len == 0)
1024 return 0;
1025
1026 search_len = strlen (search);
1027 replace_len = strlen (replace);
1028
1029 do {
1030 tmp = g_strstr_len (string->str + search_idx, -1, search);
1031 if (tmp == NULL)
1032 break;
1033
1034 /* advance the counter in case @replace contains @search */
1035 search_idx = (gsize) (tmp - string->str);
1036
1037 /* reallocate the string if required */
1038 if (search_len > replace_len) {
1039 g_string_erase (string,
1040 (gssize) search_idx,
1041 (gssize) (search_len - replace_len));
1042 memcpy (tmp, replace, replace_len);
1043 } else if (search_len < replace_len) {
1044 g_string_insert_len (string,
1045 (gssize) search_idx,
1046 replace,
1047 (gssize) (replace_len - search_len));
1048 /* we have to treat this specially as it could have
1049 * been reallocated when the insertion happened */
1050 memcpy (string->str + search_idx, replace, replace_len);
1051 } else {
1052 /* just memcmp in the new string */
1053 memcpy (tmp, replace, replace_len);
1054 }
1055 search_idx += replace_len;
1056 count++;
1057 } while (TRUE);
1058
1059 return count;
1060}
Richard Hughese59cb9a2018-12-05 14:37:40 +00001061
1062/**
1063 * fu_common_dump_raw:
1064 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1065 * @title: prefix title, or %NULL
1066 * @data: buffer to print
1067 * @len: the size of @data
1068 *
1069 * Dumps a raw buffer to the screen.
1070 *
1071 * Since: 1.2.2
1072 **/
1073void
1074fu_common_dump_raw (const gchar *log_domain,
1075 const gchar *title,
1076 const guint8 *data,
1077 gsize len)
1078{
1079 g_autoptr(GString) str = g_string_new (NULL);
1080 if (title != NULL)
1081 g_string_append_printf (str, "%s:", title);
1082 for (gsize i = str->len; i < 16; i++)
1083 g_string_append (str, " ");
1084 for (gsize i = 0; i < len; i++) {
1085 g_string_append_printf (str, "%02x ", data[i]);
1086 if (i > 0 && i % 32 == 0)
1087 g_string_append (str, "\n");
1088 }
1089 g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str);
1090}
1091
1092/**
1093 * fu_common_dump_raw:
1094 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1095 * @title: prefix title, or %NULL
1096 * @bytes: a #GBytes
1097 *
1098 * Dumps a byte buffer to the screen.
1099 *
1100 * Since: 1.2.2
1101 **/
1102void
1103fu_common_dump_bytes (const gchar *log_domain,
1104 const gchar *title,
1105 GBytes *bytes)
1106{
1107 gsize len = 0;
1108 const guint8 *data = g_bytes_get_data (bytes, &len);
1109 fu_common_dump_raw (log_domain, title, data, len);
1110}