blob: 9a2af062c6be24b4914b7a6da71408053d1d0d96 [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;
Richard Hughesb768e4d2019-02-26 13:55:18 +0000523 guint timeout_id;
Richard Hughes049ccc82017-08-09 15:26:56 +0100524} FuCommonSpawnHelper;
525
526static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
527
528static gboolean
529fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
530{
531 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
532 gchar buffer[1024];
533 gssize sz;
534 g_auto(GStrv) split = NULL;
535 g_autoptr(GError) error = NULL;
536
537 /* read from stream */
538 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
539 buffer,
540 sizeof(buffer) - 1,
541 NULL,
542 &error);
543 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100544 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
545 g_warning ("failed to get read from nonblocking fd: %s",
546 error->message);
547 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100548 return G_SOURCE_REMOVE;
549 }
550
551 /* no read possible */
552 if (sz == 0)
553 g_main_loop_quit (helper->loop);
554
555 /* emit lines */
556 if (helper->handler_cb != NULL) {
557 buffer[sz] = '\0';
558 split = g_strsplit (buffer, "\n", -1);
559 for (guint i = 0; split[i] != NULL; i++) {
560 if (split[i][0] == '\0')
561 continue;
562 helper->handler_cb (split[i], helper->handler_user_data);
563 }
564 }
565
566 /* set up the source for the next read */
567 fu_common_spawn_create_pollable_source (helper);
568 return G_SOURCE_REMOVE;
569}
570
571static void
572fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
573{
574 if (helper->source != NULL)
575 g_source_destroy (helper->source);
576 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
577 helper->cancellable);
578 g_source_attach (helper->source, NULL);
579 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
580}
581
582static void
583fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
584{
Richard Hughesb768e4d2019-02-26 13:55:18 +0000585 g_object_unref (helper->cancellable);
Richard Hughes049ccc82017-08-09 15:26:56 +0100586 if (helper->stream != NULL)
587 g_object_unref (helper->stream);
588 if (helper->source != NULL)
589 g_source_destroy (helper->source);
590 if (helper->loop != NULL)
591 g_main_loop_unref (helper->loop);
Richard Hughesb768e4d2019-02-26 13:55:18 +0000592 if (helper->timeout_id != 0)
593 g_source_remove (helper->timeout_id);
Richard Hughes049ccc82017-08-09 15:26:56 +0100594 g_free (helper);
595}
596
Mario Limoncielloa98df552018-04-16 12:15:51 -0500597#pragma clang diagnostic push
598#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100599G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500600#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100601
Richard Hughesb768e4d2019-02-26 13:55:18 +0000602static gboolean
603fu_common_spawn_timeout_cb (gpointer user_data)
604{
605 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
606 g_cancellable_cancel (helper->cancellable);
607 g_main_loop_quit (helper->loop);
608 helper->timeout_id = 0;
609 return G_SOURCE_REMOVE;
610}
611
612static void
613fu_common_spawn_cancelled_cb (GCancellable *cancellable, FuCommonSpawnHelper *helper)
614{
615 /* just propagate */
616 g_cancellable_cancel (helper->cancellable);
617}
618
Richard Hughes049ccc82017-08-09 15:26:56 +0100619/**
620 * fu_common_spawn_sync:
621 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100622 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
623 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughesb768e4d2019-02-26 13:55:18 +0000624 * @timeout_ms: a timeout in ms, or 0 for no limit
Richard Hughes049ccc82017-08-09 15:26:56 +0100625 * @cancellable: a #GCancellable, or %NULL
626 * @error: A #GError or %NULL
627 *
628 * Runs a subprocess and waits for it to exit. Any output on standard out or
629 * standard error will be forwarded to @handler_cb as whole lines.
630 *
631 * Returns: %TRUE for success
632 **/
633gboolean
634fu_common_spawn_sync (const gchar * const * argv,
635 FuOutputHandler handler_cb,
636 gpointer handler_user_data,
Richard Hughesb768e4d2019-02-26 13:55:18 +0000637 guint timeout_ms,
Richard Hughes049ccc82017-08-09 15:26:56 +0100638 GCancellable *cancellable, GError **error)
639{
640 g_autoptr(FuCommonSpawnHelper) helper = NULL;
641 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100642 g_autofree gchar *argv_str = NULL;
Richard Hughesb768e4d2019-02-26 13:55:18 +0000643 gulong cancellable_id = 0;
Richard Hughes049ccc82017-08-09 15:26:56 +0100644
645 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100646 argv_str = g_strjoinv (" ", (gchar **) argv);
647 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100648 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
649 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
650 if (subprocess == NULL)
651 return FALSE;
652
653 /* watch for process to exit */
654 helper = g_new0 (FuCommonSpawnHelper, 1);
655 helper->handler_cb = handler_cb;
656 helper->handler_user_data = handler_user_data;
657 helper->loop = g_main_loop_new (NULL, FALSE);
658 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
Richard Hughesb768e4d2019-02-26 13:55:18 +0000659
660 /* always create a cancellable, and connect up the parent */
661 helper->cancellable = g_cancellable_new ();
662 if (cancellable != NULL) {
663 cancellable_id = g_cancellable_connect (cancellable,
664 G_CALLBACK (fu_common_spawn_cancelled_cb),
665 helper, NULL);
666 }
667
668 /* allow timeout */
669 if (timeout_ms > 0) {
670 helper->timeout_id = g_timeout_add (timeout_ms,
671 fu_common_spawn_timeout_cb,
672 helper);
673 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100674 fu_common_spawn_create_pollable_source (helper);
675 g_main_loop_run (helper->loop);
Richard Hughesb768e4d2019-02-26 13:55:18 +0000676 g_cancellable_disconnect (cancellable, cancellable_id);
677 if (g_cancellable_set_error_if_cancelled (helper->cancellable, error))
678 return FALSE;
Richard Hughes049ccc82017-08-09 15:26:56 +0100679 return g_subprocess_wait_check (subprocess, cancellable, error);
680}
Richard Hughesae252cd2017-12-08 10:48:15 +0000681
682/**
683 * fu_common_write_uint16:
684 * @buf: A writable buffer
685 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100686 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000687 *
688 * Writes a value to a buffer using a specified endian.
689 **/
690void
691fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
692{
693 guint16 val_hw;
694 switch (endian) {
695 case G_BIG_ENDIAN:
696 val_hw = GUINT16_TO_BE(val_native);
697 break;
698 case G_LITTLE_ENDIAN:
699 val_hw = GUINT16_TO_LE(val_native);
700 break;
701 default:
702 g_assert_not_reached ();
703 }
704 memcpy (buf, &val_hw, sizeof(val_hw));
705}
706
707/**
708 * fu_common_write_uint32:
709 * @buf: A writable buffer
710 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100711 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000712 *
713 * Writes a value to a buffer using a specified endian.
714 **/
715void
716fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
717{
718 guint32 val_hw;
719 switch (endian) {
720 case G_BIG_ENDIAN:
721 val_hw = GUINT32_TO_BE(val_native);
722 break;
723 case G_LITTLE_ENDIAN:
724 val_hw = GUINT32_TO_LE(val_native);
725 break;
726 default:
727 g_assert_not_reached ();
728 }
729 memcpy (buf, &val_hw, sizeof(val_hw));
730}
731
732/**
733 * fu_common_read_uint16:
734 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100735 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000736 *
737 * Read a value from a buffer using a specified endian.
738 *
739 * Returns: a value in host byte-order
740 **/
741guint16
742fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
743{
744 guint16 val_hw, val_native;
745 memcpy (&val_hw, buf, sizeof(val_hw));
746 switch (endian) {
747 case G_BIG_ENDIAN:
748 val_native = GUINT16_FROM_BE(val_hw);
749 break;
750 case G_LITTLE_ENDIAN:
751 val_native = GUINT16_FROM_LE(val_hw);
752 break;
753 default:
754 g_assert_not_reached ();
755 }
756 return val_native;
757}
758
759/**
760 * fu_common_read_uint32:
761 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100762 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000763 *
764 * Read a value from a buffer using a specified endian.
765 *
766 * Returns: a value in host byte-order
767 **/
768guint32
769fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
770{
771 guint32 val_hw, val_native;
772 memcpy (&val_hw, buf, sizeof(val_hw));
773 switch (endian) {
774 case G_BIG_ENDIAN:
775 val_native = GUINT32_FROM_BE(val_hw);
776 break;
777 case G_LITTLE_ENDIAN:
778 val_native = GUINT32_FROM_LE(val_hw);
779 break;
780 default:
781 g_assert_not_reached ();
782 }
783 return val_native;
784}
Richard Hughese82eef32018-05-20 10:41:26 +0100785
Richard Hughes73bf2332018-08-28 09:38:09 +0100786/**
787 * fu_common_strtoull:
788 * @str: A string, e.g. "0x1234"
789 *
790 * Converts a string value to an integer. Values are assumed base 10, unless
791 * prefixed with "0x" where they are parsed as base 16.
792 *
793 * Returns: integer value, or 0x0 for error
794 **/
795guint64
796fu_common_strtoull (const gchar *str)
797{
798 guint base = 10;
799 if (str == NULL)
800 return 0x0;
801 if (g_str_has_prefix (str, "0x")) {
802 str += 2;
803 base = 16;
804 }
805 return g_ascii_strtoull (str, NULL, base);
806}
807
Richard Hughesa574a752018-08-31 13:31:03 +0100808/**
809 * fu_common_strstrip:
810 * @str: A string, e.g. " test "
811 *
812 * Removes leading and trailing whitespace from a constant string.
813 *
814 * Returns: newly allocated string
815 **/
816gchar *
817fu_common_strstrip (const gchar *str)
818{
819 guint head = G_MAXUINT;
820 guint tail = 0;
821
822 g_return_val_if_fail (str != NULL, NULL);
823
824 /* find first non-space char */
825 for (guint i = 0; str[i] != '\0'; i++) {
826 if (str[i] != ' ') {
827 head = i;
828 break;
829 }
830 }
831 if (head == G_MAXUINT)
832 return g_strdup ("");
833
834 /* find last non-space char */
835 for (guint i = head; str[i] != '\0'; i++) {
836 if (str[i] != ' ')
837 tail = i;
838 }
839 return g_strndup (str + head, tail - head + 1);
840}
841
Richard Hughese82eef32018-05-20 10:41:26 +0100842static const GError *
843fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
844{
845 for (guint j = 0; j < errors->len; j++) {
846 const GError *error = g_ptr_array_index (errors, j);
847 if (g_error_matches (error, FWUPD_ERROR, error_code))
848 return error;
849 }
850 return NULL;
851}
852
853static guint
854fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
855{
856 guint cnt = 0;
857 for (guint j = 0; j < errors->len; j++) {
858 const GError *error = g_ptr_array_index (errors, j);
859 if (g_error_matches (error, FWUPD_ERROR, error_code))
860 cnt++;
861 }
862 return cnt;
863}
864
865static gboolean
866fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
867{
868 for (guint j = 0; j < errors->len; j++) {
869 const GError *error = g_ptr_array_index (errors, j);
870 gboolean matches_any = FALSE;
871 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
872 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
873 matches_any = TRUE;
874 break;
875 }
876 }
877 if (!matches_any)
878 return FALSE;
879 }
880 return TRUE;
881}
882
883/**
884 * fu_common_error_array_get_best:
885 * @errors: (element-type GError): array of errors
886 *
887 * Finds the 'best' error to show the user from a array of errors, creating a
888 * completely bespoke error where required.
889 *
890 * Returns: (transfer full): a #GError, never %NULL
891 **/
892GError *
893fu_common_error_array_get_best (GPtrArray *errors)
894{
895 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
896 FWUPD_ERROR_VERSION_SAME,
897 FWUPD_ERROR_VERSION_NEWER,
898 FWUPD_ERROR_NOT_SUPPORTED,
899 FWUPD_ERROR_INTERNAL,
900 FWUPD_ERROR_NOT_FOUND,
901 FWUPD_ERROR_LAST };
902 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
903 FWUPD_ERROR_NOT_FOUND,
904 FWUPD_ERROR_NOT_SUPPORTED,
905 FWUPD_ERROR_LAST };
906 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
907 FWUPD_ERROR_VERSION_SAME,
908 FWUPD_ERROR_NOT_FOUND,
909 FWUPD_ERROR_NOT_SUPPORTED,
910 FWUPD_ERROR_LAST };
911
912 /* are all the errors either GUID-not-matched or version-same? */
913 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
914 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
915 return g_error_new (FWUPD_ERROR,
916 FWUPD_ERROR_NOTHING_TO_DO,
917 "All updatable firmware is already installed");
918 }
919
920 /* are all the errors either GUID-not-matched or version same or newer? */
921 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
922 fu_common_error_array_matches_any (errors, err_all_newer)) {
923 return g_error_new (FWUPD_ERROR,
924 FWUPD_ERROR_NOTHING_TO_DO,
925 "All updatable devices already have newer versions");
926 }
927
928 /* get the most important single error */
929 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
930 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
931 if (error_tmp != NULL)
932 return g_error_copy (error_tmp);
933 }
934
935 /* fall back to something */
936 return g_error_new (FWUPD_ERROR,
937 FWUPD_ERROR_NOT_FOUND,
938 "No supported devices found");
939}
Richard Hughes4be17d12018-05-30 20:36:29 +0100940
941/**
942 * fu_common_get_path:
943 * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
944 *
945 * Gets a fwupd-specific system path. These can be overridden with various
946 * environment variables, for instance %FWUPD_DATADIR.
947 *
948 * Returns: a system path, or %NULL if invalid
949 **/
950gchar *
951fu_common_get_path (FuPathKind path_kind)
952{
953 const gchar *tmp;
954 g_autofree gchar *basedir = NULL;
955
956 switch (path_kind) {
957 /* /var */
958 case FU_PATH_KIND_LOCALSTATEDIR:
959 tmp = g_getenv ("FWUPD_LOCALSTATEDIR");
960 if (tmp != NULL)
961 return g_strdup (tmp);
962 tmp = g_getenv ("SNAP_USER_DATA");
963 if (tmp != NULL)
964 return g_build_filename (tmp, LOCALSTATEDIR, NULL);
965 return g_build_filename (LOCALSTATEDIR, NULL);
Richard Hughes282b10d2018-06-22 14:48:00 +0100966 /* /sys/firmware */
967 case FU_PATH_KIND_SYSFSDIR_FW:
968 tmp = g_getenv ("FWUPD_SYSFSFWDIR");
969 if (tmp != NULL)
970 return g_strdup (tmp);
971 return g_strdup ("/sys/firmware");
Richard Hughesb56015e2018-12-12 09:25:32 +0000972 /* /sys/firmware */
973 case FU_PATH_KIND_SYSFSDIR_TPM:
974 tmp = g_getenv ("FWUPD_SYSFSTPMDIR");
975 if (tmp != NULL)
976 return g_strdup (tmp);
977 return g_strdup ("/sys/class/tpm");
Richard Hughes83390f62018-06-22 20:36:46 +0100978 /* /sys/bus/platform/drivers */
979 case FU_PATH_KIND_SYSFSDIR_DRIVERS:
980 tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR");
981 if (tmp != NULL)
982 return g_strdup (tmp);
983 return g_strdup ("/sys/bus/platform/drivers");
Richard Hughes4be17d12018-05-30 20:36:29 +0100984 /* /etc */
985 case FU_PATH_KIND_SYSCONFDIR:
986 tmp = g_getenv ("FWUPD_SYSCONFDIR");
987 if (tmp != NULL)
988 return g_strdup (tmp);
989 tmp = g_getenv ("SNAP_USER_DATA");
990 if (tmp != NULL)
991 return g_build_filename (tmp, SYSCONFDIR, NULL);
992 return g_strdup (SYSCONFDIR);
993 /* /usr/lib/<triplet>/fwupd-plugins-3 */
994 case FU_PATH_KIND_PLUGINDIR_PKG:
995 tmp = g_getenv ("FWUPD_PLUGINDIR");
996 if (tmp != NULL)
997 return g_strdup (tmp);
998 tmp = g_getenv ("SNAP");
999 if (tmp != NULL)
1000 return g_build_filename (tmp, PLUGINDIR, NULL);
1001 return g_build_filename (PLUGINDIR, NULL);
1002 /* /usr/share/fwupd */
1003 case FU_PATH_KIND_DATADIR_PKG:
1004 tmp = g_getenv ("FWUPD_DATADIR");
1005 if (tmp != NULL)
1006 return g_strdup (tmp);
1007 tmp = g_getenv ("SNAP");
1008 if (tmp != NULL)
1009 return g_build_filename (tmp, DATADIR, PACKAGE_NAME, NULL);
1010 return g_build_filename (DATADIR, PACKAGE_NAME, NULL);
Mario Limoncielloe6e2bf92018-07-10 12:11:25 -05001011 /* /usr/libexec/fwupd/efi */
1012 case FU_PATH_KIND_EFIAPPDIR:
1013 tmp = g_getenv ("FWUPD_EFIAPPDIR");
1014 if (tmp != NULL)
1015 return g_strdup (tmp);
1016#ifdef EFI_APP_LOCATION
1017 tmp = g_getenv ("SNAP");
1018 if (tmp != NULL)
1019 return g_build_filename (tmp, EFI_APP_LOCATION, NULL);
1020 return g_strdup (EFI_APP_LOCATION);
1021#else
1022 return NULL;
1023#endif
Richard Hughes4be17d12018-05-30 20:36:29 +01001024 /* /etc/fwupd */
1025 case FU_PATH_KIND_SYSCONFDIR_PKG:
1026 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
1027 return g_build_filename (basedir, PACKAGE_NAME, NULL);
1028 /* /var/lib/fwupd */
1029 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
1030 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
1031 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
1032 /* /var/cache/fwupd */
1033 case FU_PATH_KIND_CACHEDIR_PKG:
1034 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
1035 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
1036 /* this shouldn't happen */
1037 default:
Richard Hughesbeb47a82018-09-11 18:28:53 +01001038 g_warning ("cannot build path for unknown kind %u", path_kind);
Richard Hughes4be17d12018-05-30 20:36:29 +01001039 }
1040
1041 return NULL;
1042}
Richard Hughes83e56c12018-10-10 20:24:41 +01001043
1044/**
1045 * fu_common_string_replace:
1046 * @string: The #GString to operate on
1047 * @search: The text to search for
1048 * @replace: The text to use for substitutions
1049 *
1050 * Performs multiple search and replace operations on the given string.
1051 *
1052 * Returns: the number of replacements done, or 0 if @search is not found.
1053 *
1054 * Since: 1.2.0
1055 **/
1056guint
1057fu_common_string_replace (GString *string, const gchar *search, const gchar *replace)
1058{
1059 gchar *tmp;
1060 guint count = 0;
1061 gsize search_idx = 0;
1062 gsize replace_len;
1063 gsize search_len;
1064
1065 g_return_val_if_fail (string != NULL, 0);
1066 g_return_val_if_fail (search != NULL, 0);
1067 g_return_val_if_fail (replace != NULL, 0);
1068
1069 /* nothing to do */
1070 if (string->len == 0)
1071 return 0;
1072
1073 search_len = strlen (search);
1074 replace_len = strlen (replace);
1075
1076 do {
1077 tmp = g_strstr_len (string->str + search_idx, -1, search);
1078 if (tmp == NULL)
1079 break;
1080
1081 /* advance the counter in case @replace contains @search */
1082 search_idx = (gsize) (tmp - string->str);
1083
1084 /* reallocate the string if required */
1085 if (search_len > replace_len) {
1086 g_string_erase (string,
1087 (gssize) search_idx,
1088 (gssize) (search_len - replace_len));
1089 memcpy (tmp, replace, replace_len);
1090 } else if (search_len < replace_len) {
1091 g_string_insert_len (string,
1092 (gssize) search_idx,
1093 replace,
1094 (gssize) (replace_len - search_len));
1095 /* we have to treat this specially as it could have
1096 * been reallocated when the insertion happened */
1097 memcpy (string->str + search_idx, replace, replace_len);
1098 } else {
1099 /* just memcmp in the new string */
1100 memcpy (tmp, replace, replace_len);
1101 }
1102 search_idx += replace_len;
1103 count++;
1104 } while (TRUE);
1105
1106 return count;
1107}
Richard Hughese59cb9a2018-12-05 14:37:40 +00001108
1109/**
Richard Hughes35481862019-01-06 12:01:58 +00001110 * fu_common_dump_full:
1111 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1112 * @title: prefix title, or %NULL
1113 * @data: buffer to print
1114 * @len: the size of @data
1115 * @columns: break new lines after this many bytes
1116 * @flags: some #FuDumpFlags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII
1117 *
1118 * Dumps a raw buffer to the screen.
1119 *
1120 * Since: 1.2.4
1121 **/
1122void
1123fu_common_dump_full (const gchar *log_domain,
1124 const gchar *title,
1125 const guint8 *data,
1126 gsize len,
1127 guint columns,
1128 FuDumpFlags flags)
1129{
1130 g_autoptr(GString) str = g_string_new (NULL);
1131
1132 /* optional */
1133 if (title != NULL)
1134 g_string_append_printf (str, "%s:", title);
1135
1136 /* if more than can fit on one line then start afresh */
1137 if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1138 g_string_append (str, "\n");
1139 } else {
1140 for (gsize i = str->len; i < 16; i++)
1141 g_string_append (str, " ");
1142 }
1143
1144 /* offset line */
1145 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1146 g_string_append (str, " │ ");
1147 for (gsize i = 0; i < columns; i++)
1148 g_string_append_printf (str, "%02x ", (guint) i);
1149 g_string_append (str, "\n───────┼");
1150 for (gsize i = 0; i < columns; i++)
1151 g_string_append (str, "───");
1152 g_string_append_printf (str, "\n0x%04x │ ", (guint) 0);
1153 }
1154
1155 /* print each row */
1156 for (gsize i = 0; i < len; i++) {
1157 g_string_append_printf (str, "%02x ", data[i]);
1158
1159 /* optionally print ASCII char */
1160 if (flags & FU_DUMP_FLAGS_SHOW_ASCII) {
1161 if (g_ascii_isprint (data[i]))
1162 g_string_append_printf (str, "[%c] ", data[i]);
1163 else
1164 g_string_append (str, "[?] ");
1165 }
1166
1167 /* new row required */
1168 if (i > 0 && i != len - 1 && (i + 1) % columns == 0) {
1169 g_string_append (str, "\n");
1170 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES)
1171 g_string_append_printf (str, "0x%04x │ ", (guint) i + 1);
1172 }
1173 }
1174 g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str);
1175}
1176
1177/**
Richard Hughese59cb9a2018-12-05 14:37:40 +00001178 * fu_common_dump_raw:
1179 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1180 * @title: prefix title, or %NULL
1181 * @data: buffer to print
1182 * @len: the size of @data
1183 *
1184 * Dumps a raw buffer to the screen.
1185 *
1186 * Since: 1.2.2
1187 **/
1188void
1189fu_common_dump_raw (const gchar *log_domain,
1190 const gchar *title,
1191 const guint8 *data,
1192 gsize len)
1193{
Richard Hughes35481862019-01-06 12:01:58 +00001194 FuDumpFlags flags = FU_DUMP_FLAGS_NONE;
1195 if (len > 64)
1196 flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES;
1197 fu_common_dump_full (log_domain, title, data, len, 32, flags);
Richard Hughese59cb9a2018-12-05 14:37:40 +00001198}
1199
1200/**
1201 * fu_common_dump_raw:
1202 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1203 * @title: prefix title, or %NULL
1204 * @bytes: a #GBytes
1205 *
1206 * Dumps a byte buffer to the screen.
1207 *
1208 * Since: 1.2.2
1209 **/
1210void
1211fu_common_dump_bytes (const gchar *log_domain,
1212 const gchar *title,
1213 GBytes *bytes)
1214{
1215 gsize len = 0;
1216 const guint8 *data = g_bytes_get_data (bytes, &len);
1217 fu_common_dump_raw (log_domain, title, data, len);
1218}
Richard Hughesfc90f392019-01-15 21:21:16 +00001219
1220/**
1221 * fu_common_bytes_align:
1222 * @bytes: a #GBytes
1223 * @blksz: block size in bytes
1224 * @padval: the byte used to pad the byte buffer
1225 *
1226 * Aligns a block of memory to @blksize using the @padval value; if
1227 * the block is already aligned then the original @bytes is returned.
1228 *
1229 * Returns: (transfer full): a #GBytes, possibly @bytes
1230 *
1231 * Since: 1.2.4
1232 **/
1233GBytes *
1234fu_common_bytes_align (GBytes *bytes, gsize blksz, gchar padval)
1235{
1236 const guint8 *data;
1237 gsize sz;
1238
1239 g_return_val_if_fail (bytes != NULL, NULL);
1240 g_return_val_if_fail (blksz > 0, NULL);
1241
1242 /* pad */
1243 data = g_bytes_get_data (bytes, &sz);
1244 if (sz % blksz != 0) {
1245 gsize sz_align = ((sz / blksz) + 1) * blksz;
1246 guint8 *data_align = g_malloc (sz_align);
1247 memcpy (data_align, data, sz);
1248 memset (data_align + sz, padval, sz_align - sz);
1249 g_debug ("aligning 0x%x bytes to 0x%x",
1250 (guint) sz, (guint) sz_align);
1251 return g_bytes_new_take (data_align, sz_align);
1252 }
1253
1254 /* perfectly aligned */
1255 return g_bytes_ref (bytes);
1256}
Richard Hughes36999462019-03-19 20:23:29 +00001257
1258/**
1259 * fu_common_bytes_is_empty:
1260 * @bytes: a #GBytes
1261 *
1262 * Checks if a byte array are just empty (0xff) bytes.
1263 *
1264 * Return value: %TRUE if @bytes is empty
1265 **/
1266gboolean
1267fu_common_bytes_is_empty (GBytes *bytes)
1268{
1269 gsize sz = 0;
1270 const guint8 *buf = g_bytes_get_data (bytes, &sz);
1271 for (gsize i = 0; i < sz; i++) {
1272 if (buf[i] != 0xff)
1273 return FALSE;
1274 }
1275 return TRUE;
1276}