blob: bd6faeefa98120006fabdedaf62c876de5acccae [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 Hughes484ee292019-03-22 16:10:50 +000017#include <limits.h>
Richard Hughesae252cd2017-12-08 10:48:15 +000018#include <string.h>
Richard Hughes484ee292019-03-22 16:10:50 +000019#include <stdlib.h>
Richard Hughes943d2c92017-06-21 09:04:39 +010020
21#include "fwupd-error.h"
22
23#include "fu-common.h"
24
25/**
Richard Hughes4eada342017-10-03 21:20:32 +010026 * SECTION:fu-common
27 * @short_description: common functionality for plugins to use
28 *
29 * Helper functions that can be used by the daemon and plugins.
30 *
31 * See also: #FuPlugin
32 */
33
34/**
Richard Hughes954dd9f2017-08-08 13:36:25 +010035 * fu_common_rmtree:
36 * @directory: a directory name
37 * @error: A #GError or %NULL
38 *
39 * Recursively removes a directory.
40 *
41 * Returns: %TRUE for success, %FALSE otherwise
42 **/
43gboolean
44fu_common_rmtree (const gchar *directory, GError **error)
45{
46 const gchar *filename;
47 g_autoptr(GDir) dir = NULL;
48
49 /* try to open */
Richard Hughes455fdd32017-08-16 12:26:44 +010050 g_debug ("removing %s", directory);
Richard Hughes954dd9f2017-08-08 13:36:25 +010051 dir = g_dir_open (directory, 0, error);
52 if (dir == NULL)
53 return FALSE;
54
55 /* find each */
56 while ((filename = g_dir_read_name (dir))) {
57 g_autofree gchar *src = NULL;
58 src = g_build_filename (directory, filename, NULL);
59 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
60 if (!fu_common_rmtree (src, error))
61 return FALSE;
62 } else {
63 if (g_unlink (src) != 0) {
64 g_set_error (error,
65 FWUPD_ERROR,
66 FWUPD_ERROR_INTERNAL,
67 "Failed to delete: %s", src);
68 return FALSE;
69 }
70 }
71 }
72 if (g_remove (directory) != 0) {
73 g_set_error (error,
74 FWUPD_ERROR,
75 FWUPD_ERROR_INTERNAL,
76 "Failed to delete: %s", directory);
77 return FALSE;
78 }
79 return TRUE;
80}
81
Richard Hughes89e968b2018-03-07 10:01:08 +000082static gboolean
83fu_common_get_file_list_internal (GPtrArray *files, const gchar *directory, GError **error)
84{
85 const gchar *filename;
86 g_autoptr(GDir) dir = NULL;
87
88 /* try to open */
89 dir = g_dir_open (directory, 0, error);
90 if (dir == NULL)
91 return FALSE;
92
93 /* find each */
94 while ((filename = g_dir_read_name (dir))) {
95 g_autofree gchar *src = g_build_filename (directory, filename, NULL);
96 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
97 if (!fu_common_get_file_list_internal (files, src, error))
98 return FALSE;
99 } else {
100 g_ptr_array_add (files, g_steal_pointer (&src));
101 }
102 }
103 return TRUE;
104
105}
106
107/**
108 * fu_common_get_files_recursive:
Richard Hughes8aa72392018-05-02 08:38:43 +0100109 * @path: a directory name
Richard Hughes89e968b2018-03-07 10:01:08 +0000110 * @error: A #GError or %NULL
111 *
112 * Returns every file found under @directory, and any subdirectory.
113 * If any path under @directory cannot be accessed due to permissions an error
114 * will be returned.
115 *
116 * Returns: (element-type: utf8) (transfer container): array of files, or %NULL for error
117 **/
118GPtrArray *
119fu_common_get_files_recursive (const gchar *path, GError **error)
120{
121 g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free);
122 if (!fu_common_get_file_list_internal (files, path, error))
123 return NULL;
124 return g_steal_pointer (&files);
125}
Richard Hughes954dd9f2017-08-08 13:36:25 +0100126/**
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100127 * fu_common_mkdir_parent:
128 * @filename: A full pathname
129 * @error: A #GError, or %NULL
130 *
131 * Creates any required directories, including any parent directories.
132 *
133 * Returns: %TRUE for success
134 **/
135gboolean
136fu_common_mkdir_parent (const gchar *filename, GError **error)
137{
138 g_autofree gchar *parent = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100139
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100140 parent = g_path_get_dirname (filename);
Richard Hughes455fdd32017-08-16 12:26:44 +0100141 g_debug ("creating path %s", parent);
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100142 if (g_mkdir_with_parents (parent, 0755) == -1) {
143 g_set_error (error,
144 FWUPD_ERROR,
145 FWUPD_ERROR_INTERNAL,
146 "Failed to create '%s': %s",
147 parent, g_strerror (errno));
148 return FALSE;
149 }
150 return TRUE;
151}
152
153/**
Richard Hughes943d2c92017-06-21 09:04:39 +0100154 * fu_common_set_contents_bytes:
155 * @filename: A filename
156 * @bytes: The data to write
157 * @error: A #GError, or %NULL
158 *
159 * Writes a blob of data to a filename, creating the parent directories as
160 * required.
161 *
162 * Returns: %TRUE for success
163 **/
164gboolean
165fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error)
166{
167 const gchar *data;
168 gsize size;
169 g_autoptr(GFile) file = NULL;
170 g_autoptr(GFile) file_parent = NULL;
171
172 file = g_file_new_for_path (filename);
173 file_parent = g_file_get_parent (file);
174 if (!g_file_query_exists (file_parent, NULL)) {
175 if (!g_file_make_directory_with_parents (file_parent, NULL, error))
176 return FALSE;
177 }
178 data = g_bytes_get_data (bytes, &size);
Richard Hughes455fdd32017-08-16 12:26:44 +0100179 g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
Richard Hughes943d2c92017-06-21 09:04:39 +0100180 return g_file_set_contents (filename, data, size, error);
181}
182
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100183/**
184 * fu_common_get_contents_bytes:
185 * @filename: A filename
186 * @error: A #GError, or %NULL
187 *
188 * Reads a blob of data from a file.
189 *
190 * Returns: a #GBytes, or %NULL for failure
191 **/
192GBytes *
193fu_common_get_contents_bytes (const gchar *filename, GError **error)
194{
195 gchar *data = NULL;
196 gsize len = 0;
197 if (!g_file_get_contents (filename, &data, &len, error))
198 return NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100199 g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100200 return g_bytes_new_take (data, len);
201}
Richard Hughes943d2c92017-06-21 09:04:39 +0100202
203/**
204 * fu_common_get_contents_fd:
205 * @fd: A file descriptor
206 * @count: The maximum number of bytes to read
207 * @error: A #GError, or %NULL
208 *
209 * Reads a blob from a specific file descriptor.
210 *
211 * Note: this will close the fd when done
212 *
Richard Hughes4eada342017-10-03 21:20:32 +0100213 * Returns: (transfer full): a #GBytes, or %NULL
Richard Hughes943d2c92017-06-21 09:04:39 +0100214 **/
215GBytes *
216fu_common_get_contents_fd (gint fd, gsize count, GError **error)
217{
218 g_autoptr(GBytes) blob = NULL;
219 g_autoptr(GError) error_local = NULL;
220 g_autoptr(GInputStream) stream = NULL;
221
222 g_return_val_if_fail (fd > 0, NULL);
Richard Hughes943d2c92017-06-21 09:04:39 +0100223 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
224
Richard Hughes919f8ab2018-02-14 10:24:56 +0000225 /* this is invalid */
226 if (count == 0) {
227 g_set_error_literal (error,
228 FWUPD_ERROR,
229 FWUPD_ERROR_NOT_SUPPORTED,
230 "A maximum read size must be specified");
231 return NULL;
232 }
233
Richard Hughes943d2c92017-06-21 09:04:39 +0100234 /* read the entire fd to a data blob */
235 stream = g_unix_input_stream_new (fd, TRUE);
236 blob = g_input_stream_read_bytes (stream, count, NULL, &error_local);
237 if (blob == NULL) {
238 g_set_error_literal (error,
239 FWUPD_ERROR,
240 FWUPD_ERROR_INVALID_FILE,
241 error_local->message);
242 return NULL;
243 }
244 return g_steal_pointer (&blob);
245}
Richard Hughes94f939a2017-08-08 12:21:39 +0100246
247static gboolean
248fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir)
249{
250 const gchar *tmp;
251 g_autofree gchar *buf = NULL;
252
253 /* no output file */
254 if (archive_entry_pathname (entry) == NULL)
255 return FALSE;
256
257 /* update output path */
258 tmp = archive_entry_pathname (entry);
259 buf = g_build_filename (dir, tmp, NULL);
260 archive_entry_update_pathname_utf8 (entry, buf);
261 return TRUE;
262}
263
264/**
265 * fu_common_extract_archive:
266 * @blob: a #GBytes archive as a blob
Richard Hughes4eada342017-10-03 21:20:32 +0100267 * @dir: a directory name to extract to
Richard Hughes94f939a2017-08-08 12:21:39 +0100268 * @error: A #GError, or %NULL
269 *
270 * Extracts an achive to a directory.
271 *
272 * Returns: %TRUE for success
273 **/
274gboolean
275fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error)
276{
277 gboolean ret = TRUE;
278 int r;
279 struct archive *arch = NULL;
280 struct archive_entry *entry;
281
282 /* decompress anything matching either glob */
Richard Hughes455fdd32017-08-16 12:26:44 +0100283 g_debug ("decompressing into %s", dir);
Richard Hughes94f939a2017-08-08 12:21:39 +0100284 arch = archive_read_new ();
285 archive_read_support_format_all (arch);
286 archive_read_support_filter_all (arch);
287 r = archive_read_open_memory (arch,
288 (void *) g_bytes_get_data (blob, NULL),
289 (size_t) g_bytes_get_size (blob));
290 if (r != 0) {
291 ret = FALSE;
292 g_set_error (error,
293 FWUPD_ERROR,
294 FWUPD_ERROR_INTERNAL,
295 "Cannot open: %s",
296 archive_error_string (arch));
297 goto out;
298 }
299 for (;;) {
300 gboolean valid;
Richard Hughes94f939a2017-08-08 12:21:39 +0100301 r = archive_read_next_header (arch, &entry);
302 if (r == ARCHIVE_EOF)
303 break;
304 if (r != ARCHIVE_OK) {
305 ret = FALSE;
306 g_set_error (error,
307 FWUPD_ERROR,
308 FWUPD_ERROR_INTERNAL,
309 "Cannot read header: %s",
310 archive_error_string (arch));
311 goto out;
312 }
313
314 /* only extract if valid */
315 valid = fu_common_extract_archive_entry (entry, dir);
316 if (!valid)
317 continue;
318 r = archive_read_extract (arch, entry, 0);
319 if (r != ARCHIVE_OK) {
320 ret = FALSE;
321 g_set_error (error,
322 FWUPD_ERROR,
323 FWUPD_ERROR_INTERNAL,
324 "Cannot extract: %s",
325 archive_error_string (arch));
326 goto out;
327 }
328 }
329out:
330 if (arch != NULL) {
331 archive_read_close (arch);
332 archive_read_free (arch);
333 }
334 return ret;
335}
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100336
337static void
Yehezkel Bernate43f7fb2017-08-30 12:09:34 +0300338fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3);
339
340static void
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100341fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...)
342{
343 va_list args;
344 g_autofree gchar *tmp = NULL;
345 g_auto(GStrv) split = NULL;
346
347 va_start (args, fmt);
348 tmp = g_strdup_vprintf (fmt, args);
349 va_end (args);
350
351 split = g_strsplit (tmp, " ", -1);
352 for (guint i = 0; split[i] != NULL; i++)
353 g_ptr_array_add (argv, g_strdup (split[i]));
354}
355
Richard Hughes22367e72018-08-30 10:24:04 +0100356gchar *
357fu_common_find_program_in_path (const gchar *basename, GError **error)
358{
359 gchar *fn = g_find_program_in_path (basename);
360 if (fn == NULL) {
361 g_set_error (error,
362 FWUPD_ERROR,
363 FWUPD_ERROR_NOT_SUPPORTED,
364 "missing executable %s in PATH",
365 basename);
366 return NULL;
367 }
368 return fn;
369}
370
371static gboolean
372fu_common_test_namespace_support (GError **error)
373{
374 /* test if CONFIG_USER_NS is valid */
375 if (!g_file_test ("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) {
376 g_set_error (error,
377 FWUPD_ERROR,
378 FWUPD_ERROR_NOT_SUPPORTED,
379 "missing CONFIG_USER_NS in kernel");
380 return FALSE;
381 }
382 if (g_file_test ("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) {
383 g_autofree gchar *clone = NULL;
384 if (!g_file_get_contents ("/proc/sys/kernel/unprivileged_userns_clone", &clone, NULL, error))
385 return FALSE;
386 if (g_ascii_strtoll (clone, NULL, 10) == 0) {
387 g_set_error (error,
388 FWUPD_ERROR,
389 FWUPD_ERROR_NOT_SUPPORTED,
390 "unprivileged user namespace clones disabled by distro");
391 return FALSE;
392 }
393 }
394 return TRUE;
395}
396
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100397/**
398 * fu_common_firmware_builder:
399 * @bytes: The data to use
Richard Hughes4eada342017-10-03 21:20:32 +0100400 * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
401 * @output_fn: Name of the generated firmware, e.g. `firmware.bin`
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100402 * @error: A #GError, or %NULL
403 *
404 * Builds a firmware file using tools from the host session in a bubblewrap
405 * jail. Several things happen during build:
406 *
407 * 1. The @bytes data is untarred to a temporary location
408 * 2. A bubblewrap container is set up
409 * 3. The startup.sh script is run inside the container
410 * 4. The firmware.bin is extracted from the container
411 * 5. The temporary location is deleted
412 *
413 * Returns: a new #GBytes, or %NULL for error
414 **/
415GBytes *
416fu_common_firmware_builder (GBytes *bytes,
417 const gchar *script_fn,
418 const gchar *output_fn,
419 GError **error)
420{
421 gint rc = 0;
422 g_autofree gchar *argv_str = NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500423 g_autofree gchar *bwrap_fn = NULL;
Richard Hughes4be17d12018-05-30 20:36:29 +0100424 g_autofree gchar *localstatebuilderdir = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100425 g_autofree gchar *localstatedir = NULL;
426 g_autofree gchar *output2_fn = NULL;
427 g_autofree gchar *standard_error = NULL;
428 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100429 g_autofree gchar *tmpdir = NULL;
430 g_autoptr(GBytes) firmware_blob = NULL;
431 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
432
433 g_return_val_if_fail (bytes != NULL, NULL);
434 g_return_val_if_fail (script_fn != NULL, NULL);
435 g_return_val_if_fail (output_fn != NULL, NULL);
436 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
437
Mario Limonciello37b59582018-08-13 08:38:01 -0500438 /* find bwrap in the path */
Richard Hughes22367e72018-08-30 10:24:04 +0100439 bwrap_fn = fu_common_find_program_in_path ("bwrap", error);
440 if (bwrap_fn == NULL)
Richard Hughesddb3e202018-08-23 11:29:57 +0100441 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500442
443 /* test if CONFIG_USER_NS is valid */
Richard Hughes22367e72018-08-30 10:24:04 +0100444 if (!fu_common_test_namespace_support (error))
Richard Hughesddb3e202018-08-23 11:29:57 +0100445 return NULL;
Mario Limonciello37b59582018-08-13 08:38:01 -0500446
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100447 /* untar file to temp location */
448 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
449 if (tmpdir == NULL)
450 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100451 if (!fu_common_extract_archive (bytes, tmpdir, error))
452 return NULL;
453
454 /* this is shared with the plugins */
Richard Hughes4be17d12018-05-30 20:36:29 +0100455 localstatedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
456 localstatebuilderdir = g_build_filename (localstatedir, "builder", NULL);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100457
458 /* launch bubblewrap and generate firmware */
Mario Limonciello37b59582018-08-13 08:38:01 -0500459 g_ptr_array_add (argv, g_steal_pointer (&bwrap_fn));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100460 fu_common_add_argv (argv, "--die-with-parent");
461 fu_common_add_argv (argv, "--ro-bind /usr /usr");
Mario Limonciellob8215572018-07-13 09:49:55 -0500462 fu_common_add_argv (argv, "--ro-bind /lib /lib");
463 fu_common_add_argv (argv, "--ro-bind /lib64 /lib64");
464 fu_common_add_argv (argv, "--ro-bind /bin /bin");
465 fu_common_add_argv (argv, "--ro-bind /sbin /sbin");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100466 fu_common_add_argv (argv, "--dir /tmp");
467 fu_common_add_argv (argv, "--dir /var");
468 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
Richard Hughes4be17d12018-05-30 20:36:29 +0100469 if (g_file_test (localstatebuilderdir, G_FILE_TEST_EXISTS))
470 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatebuilderdir);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100471 fu_common_add_argv (argv, "--dev /dev");
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100472 fu_common_add_argv (argv, "--chdir /tmp");
473 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100474 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100475 g_ptr_array_add (argv, NULL);
476 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
477 g_debug ("running '%s' in %s", argv_str, tmpdir);
478 if (!g_spawn_sync ("/tmp",
479 (gchar **) argv->pdata,
480 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100481 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100482 NULL, NULL, /* child_setup */
483 &standard_output,
484 &standard_error,
485 &rc,
486 error)) {
487 g_prefix_error (error, "failed to run '%s': ", argv_str);
488 return NULL;
489 }
490 if (standard_output != NULL && standard_output[0] != '\0')
491 g_debug ("console output was: %s", standard_output);
492 if (rc != 0) {
Mario Limonciello37b59582018-08-13 08:38:01 -0500493 FwupdError code = FWUPD_ERROR_INTERNAL;
494 if (errno == ENOTTY)
495 code = FWUPD_ERROR_PERMISSION_DENIED;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100496 g_set_error (error,
497 FWUPD_ERROR,
Mario Limonciello37b59582018-08-13 08:38:01 -0500498 code,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100499 "failed to build firmware: %s",
500 standard_error);
501 return NULL;
502 }
503
504 /* get generated file */
505 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
506 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
507 if (firmware_blob == NULL)
508 return NULL;
509
510 /* cleanup temp directory */
511 if (!fu_common_rmtree (tmpdir, error))
512 return NULL;
513
514 /* success */
515 return g_steal_pointer (&firmware_blob);
516}
Richard Hughes049ccc82017-08-09 15:26:56 +0100517
518typedef struct {
519 FuOutputHandler handler_cb;
520 gpointer handler_user_data;
521 GMainLoop *loop;
522 GSource *source;
523 GInputStream *stream;
524 GCancellable *cancellable;
Richard Hughesb768e4d2019-02-26 13:55:18 +0000525 guint timeout_id;
Richard Hughes049ccc82017-08-09 15:26:56 +0100526} FuCommonSpawnHelper;
527
528static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
529
530static gboolean
531fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
532{
533 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
534 gchar buffer[1024];
535 gssize sz;
536 g_auto(GStrv) split = NULL;
537 g_autoptr(GError) error = NULL;
538
539 /* read from stream */
540 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
541 buffer,
542 sizeof(buffer) - 1,
543 NULL,
544 &error);
545 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100546 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
547 g_warning ("failed to get read from nonblocking fd: %s",
548 error->message);
549 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100550 return G_SOURCE_REMOVE;
551 }
552
553 /* no read possible */
554 if (sz == 0)
555 g_main_loop_quit (helper->loop);
556
557 /* emit lines */
558 if (helper->handler_cb != NULL) {
559 buffer[sz] = '\0';
560 split = g_strsplit (buffer, "\n", -1);
561 for (guint i = 0; split[i] != NULL; i++) {
562 if (split[i][0] == '\0')
563 continue;
564 helper->handler_cb (split[i], helper->handler_user_data);
565 }
566 }
567
568 /* set up the source for the next read */
569 fu_common_spawn_create_pollable_source (helper);
570 return G_SOURCE_REMOVE;
571}
572
573static void
574fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
575{
576 if (helper->source != NULL)
577 g_source_destroy (helper->source);
578 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
579 helper->cancellable);
580 g_source_attach (helper->source, NULL);
581 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
582}
583
584static void
585fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
586{
Richard Hughesb768e4d2019-02-26 13:55:18 +0000587 g_object_unref (helper->cancellable);
Richard Hughes049ccc82017-08-09 15:26:56 +0100588 if (helper->stream != NULL)
589 g_object_unref (helper->stream);
590 if (helper->source != NULL)
591 g_source_destroy (helper->source);
592 if (helper->loop != NULL)
593 g_main_loop_unref (helper->loop);
Richard Hughesb768e4d2019-02-26 13:55:18 +0000594 if (helper->timeout_id != 0)
595 g_source_remove (helper->timeout_id);
Richard Hughes049ccc82017-08-09 15:26:56 +0100596 g_free (helper);
597}
598
Mario Limoncielloa98df552018-04-16 12:15:51 -0500599#pragma clang diagnostic push
600#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100601G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500602#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100603
Richard Hughesb768e4d2019-02-26 13:55:18 +0000604static gboolean
605fu_common_spawn_timeout_cb (gpointer user_data)
606{
607 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
608 g_cancellable_cancel (helper->cancellable);
609 g_main_loop_quit (helper->loop);
610 helper->timeout_id = 0;
611 return G_SOURCE_REMOVE;
612}
613
614static void
615fu_common_spawn_cancelled_cb (GCancellable *cancellable, FuCommonSpawnHelper *helper)
616{
617 /* just propagate */
618 g_cancellable_cancel (helper->cancellable);
619}
620
Richard Hughes049ccc82017-08-09 15:26:56 +0100621/**
622 * fu_common_spawn_sync:
623 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100624 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
625 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughesb768e4d2019-02-26 13:55:18 +0000626 * @timeout_ms: a timeout in ms, or 0 for no limit
Richard Hughes049ccc82017-08-09 15:26:56 +0100627 * @cancellable: a #GCancellable, or %NULL
628 * @error: A #GError or %NULL
629 *
630 * Runs a subprocess and waits for it to exit. Any output on standard out or
631 * standard error will be forwarded to @handler_cb as whole lines.
632 *
633 * Returns: %TRUE for success
634 **/
635gboolean
636fu_common_spawn_sync (const gchar * const * argv,
637 FuOutputHandler handler_cb,
638 gpointer handler_user_data,
Richard Hughesb768e4d2019-02-26 13:55:18 +0000639 guint timeout_ms,
Richard Hughes049ccc82017-08-09 15:26:56 +0100640 GCancellable *cancellable, GError **error)
641{
642 g_autoptr(FuCommonSpawnHelper) helper = NULL;
643 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100644 g_autofree gchar *argv_str = NULL;
Richard Hughesb768e4d2019-02-26 13:55:18 +0000645 gulong cancellable_id = 0;
Richard Hughes049ccc82017-08-09 15:26:56 +0100646
647 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100648 argv_str = g_strjoinv (" ", (gchar **) argv);
649 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100650 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
651 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
652 if (subprocess == NULL)
653 return FALSE;
654
655 /* watch for process to exit */
656 helper = g_new0 (FuCommonSpawnHelper, 1);
657 helper->handler_cb = handler_cb;
658 helper->handler_user_data = handler_user_data;
659 helper->loop = g_main_loop_new (NULL, FALSE);
660 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
Richard Hughesb768e4d2019-02-26 13:55:18 +0000661
662 /* always create a cancellable, and connect up the parent */
663 helper->cancellable = g_cancellable_new ();
664 if (cancellable != NULL) {
665 cancellable_id = g_cancellable_connect (cancellable,
666 G_CALLBACK (fu_common_spawn_cancelled_cb),
667 helper, NULL);
668 }
669
670 /* allow timeout */
671 if (timeout_ms > 0) {
672 helper->timeout_id = g_timeout_add (timeout_ms,
673 fu_common_spawn_timeout_cb,
674 helper);
675 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100676 fu_common_spawn_create_pollable_source (helper);
677 g_main_loop_run (helper->loop);
Richard Hughesb768e4d2019-02-26 13:55:18 +0000678 g_cancellable_disconnect (cancellable, cancellable_id);
679 if (g_cancellable_set_error_if_cancelled (helper->cancellable, error))
680 return FALSE;
Richard Hughes049ccc82017-08-09 15:26:56 +0100681 return g_subprocess_wait_check (subprocess, cancellable, error);
682}
Richard Hughesae252cd2017-12-08 10:48:15 +0000683
684/**
685 * fu_common_write_uint16:
686 * @buf: A writable buffer
687 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100688 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000689 *
690 * Writes a value to a buffer using a specified endian.
691 **/
692void
693fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
694{
695 guint16 val_hw;
696 switch (endian) {
697 case G_BIG_ENDIAN:
698 val_hw = GUINT16_TO_BE(val_native);
699 break;
700 case G_LITTLE_ENDIAN:
701 val_hw = GUINT16_TO_LE(val_native);
702 break;
703 default:
704 g_assert_not_reached ();
705 }
706 memcpy (buf, &val_hw, sizeof(val_hw));
707}
708
709/**
710 * fu_common_write_uint32:
711 * @buf: A writable buffer
712 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100713 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000714 *
715 * Writes a value to a buffer using a specified endian.
716 **/
717void
718fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
719{
720 guint32 val_hw;
721 switch (endian) {
722 case G_BIG_ENDIAN:
723 val_hw = GUINT32_TO_BE(val_native);
724 break;
725 case G_LITTLE_ENDIAN:
726 val_hw = GUINT32_TO_LE(val_native);
727 break;
728 default:
729 g_assert_not_reached ();
730 }
731 memcpy (buf, &val_hw, sizeof(val_hw));
732}
733
734/**
735 * fu_common_read_uint16:
736 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100737 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000738 *
739 * Read a value from a buffer using a specified endian.
740 *
741 * Returns: a value in host byte-order
742 **/
743guint16
744fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
745{
746 guint16 val_hw, val_native;
747 memcpy (&val_hw, buf, sizeof(val_hw));
748 switch (endian) {
749 case G_BIG_ENDIAN:
750 val_native = GUINT16_FROM_BE(val_hw);
751 break;
752 case G_LITTLE_ENDIAN:
753 val_native = GUINT16_FROM_LE(val_hw);
754 break;
755 default:
756 g_assert_not_reached ();
757 }
758 return val_native;
759}
760
761/**
762 * fu_common_read_uint32:
763 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100764 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000765 *
766 * Read a value from a buffer using a specified endian.
767 *
768 * Returns: a value in host byte-order
769 **/
770guint32
771fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
772{
773 guint32 val_hw, val_native;
774 memcpy (&val_hw, buf, sizeof(val_hw));
775 switch (endian) {
776 case G_BIG_ENDIAN:
777 val_native = GUINT32_FROM_BE(val_hw);
778 break;
779 case G_LITTLE_ENDIAN:
780 val_native = GUINT32_FROM_LE(val_hw);
781 break;
782 default:
783 g_assert_not_reached ();
784 }
785 return val_native;
786}
Richard Hughese82eef32018-05-20 10:41:26 +0100787
Richard Hughes73bf2332018-08-28 09:38:09 +0100788/**
789 * fu_common_strtoull:
790 * @str: A string, e.g. "0x1234"
791 *
792 * Converts a string value to an integer. Values are assumed base 10, unless
793 * prefixed with "0x" where they are parsed as base 16.
794 *
795 * Returns: integer value, or 0x0 for error
796 **/
797guint64
798fu_common_strtoull (const gchar *str)
799{
800 guint base = 10;
801 if (str == NULL)
802 return 0x0;
803 if (g_str_has_prefix (str, "0x")) {
804 str += 2;
805 base = 16;
806 }
807 return g_ascii_strtoull (str, NULL, base);
808}
809
Richard Hughesa574a752018-08-31 13:31:03 +0100810/**
811 * fu_common_strstrip:
812 * @str: A string, e.g. " test "
813 *
814 * Removes leading and trailing whitespace from a constant string.
815 *
816 * Returns: newly allocated string
817 **/
818gchar *
819fu_common_strstrip (const gchar *str)
820{
821 guint head = G_MAXUINT;
822 guint tail = 0;
823
824 g_return_val_if_fail (str != NULL, NULL);
825
826 /* find first non-space char */
827 for (guint i = 0; str[i] != '\0'; i++) {
828 if (str[i] != ' ') {
829 head = i;
830 break;
831 }
832 }
833 if (head == G_MAXUINT)
834 return g_strdup ("");
835
836 /* find last non-space char */
837 for (guint i = head; str[i] != '\0'; i++) {
838 if (str[i] != ' ')
839 tail = i;
840 }
841 return g_strndup (str + head, tail - head + 1);
842}
843
Richard Hughese82eef32018-05-20 10:41:26 +0100844static const GError *
845fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
846{
847 for (guint j = 0; j < errors->len; j++) {
848 const GError *error = g_ptr_array_index (errors, j);
849 if (g_error_matches (error, FWUPD_ERROR, error_code))
850 return error;
851 }
852 return NULL;
853}
854
855static guint
856fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
857{
858 guint cnt = 0;
859 for (guint j = 0; j < errors->len; j++) {
860 const GError *error = g_ptr_array_index (errors, j);
861 if (g_error_matches (error, FWUPD_ERROR, error_code))
862 cnt++;
863 }
864 return cnt;
865}
866
867static gboolean
868fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
869{
870 for (guint j = 0; j < errors->len; j++) {
871 const GError *error = g_ptr_array_index (errors, j);
872 gboolean matches_any = FALSE;
873 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
874 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
875 matches_any = TRUE;
876 break;
877 }
878 }
879 if (!matches_any)
880 return FALSE;
881 }
882 return TRUE;
883}
884
885/**
886 * fu_common_error_array_get_best:
887 * @errors: (element-type GError): array of errors
888 *
889 * Finds the 'best' error to show the user from a array of errors, creating a
890 * completely bespoke error where required.
891 *
892 * Returns: (transfer full): a #GError, never %NULL
893 **/
894GError *
895fu_common_error_array_get_best (GPtrArray *errors)
896{
897 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
898 FWUPD_ERROR_VERSION_SAME,
899 FWUPD_ERROR_VERSION_NEWER,
900 FWUPD_ERROR_NOT_SUPPORTED,
901 FWUPD_ERROR_INTERNAL,
902 FWUPD_ERROR_NOT_FOUND,
903 FWUPD_ERROR_LAST };
904 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
905 FWUPD_ERROR_NOT_FOUND,
906 FWUPD_ERROR_NOT_SUPPORTED,
907 FWUPD_ERROR_LAST };
908 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
909 FWUPD_ERROR_VERSION_SAME,
910 FWUPD_ERROR_NOT_FOUND,
911 FWUPD_ERROR_NOT_SUPPORTED,
912 FWUPD_ERROR_LAST };
913
914 /* are all the errors either GUID-not-matched or version-same? */
915 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
916 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
917 return g_error_new (FWUPD_ERROR,
918 FWUPD_ERROR_NOTHING_TO_DO,
919 "All updatable firmware is already installed");
920 }
921
922 /* are all the errors either GUID-not-matched or version same or newer? */
923 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
924 fu_common_error_array_matches_any (errors, err_all_newer)) {
925 return g_error_new (FWUPD_ERROR,
926 FWUPD_ERROR_NOTHING_TO_DO,
927 "All updatable devices already have newer versions");
928 }
929
930 /* get the most important single error */
931 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
932 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
933 if (error_tmp != NULL)
934 return g_error_copy (error_tmp);
935 }
936
937 /* fall back to something */
938 return g_error_new (FWUPD_ERROR,
939 FWUPD_ERROR_NOT_FOUND,
940 "No supported devices found");
941}
Richard Hughes4be17d12018-05-30 20:36:29 +0100942
943/**
944 * fu_common_get_path:
945 * @path_kind: A #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
946 *
947 * Gets a fwupd-specific system path. These can be overridden with various
948 * environment variables, for instance %FWUPD_DATADIR.
949 *
950 * Returns: a system path, or %NULL if invalid
951 **/
952gchar *
953fu_common_get_path (FuPathKind path_kind)
954{
955 const gchar *tmp;
956 g_autofree gchar *basedir = NULL;
957
958 switch (path_kind) {
959 /* /var */
960 case FU_PATH_KIND_LOCALSTATEDIR:
961 tmp = g_getenv ("FWUPD_LOCALSTATEDIR");
962 if (tmp != NULL)
963 return g_strdup (tmp);
964 tmp = g_getenv ("SNAP_USER_DATA");
965 if (tmp != NULL)
966 return g_build_filename (tmp, LOCALSTATEDIR, NULL);
967 return g_build_filename (LOCALSTATEDIR, NULL);
Richard Hughes282b10d2018-06-22 14:48:00 +0100968 /* /sys/firmware */
969 case FU_PATH_KIND_SYSFSDIR_FW:
970 tmp = g_getenv ("FWUPD_SYSFSFWDIR");
971 if (tmp != NULL)
972 return g_strdup (tmp);
973 return g_strdup ("/sys/firmware");
Richard Hughesb56015e2018-12-12 09:25:32 +0000974 /* /sys/firmware */
975 case FU_PATH_KIND_SYSFSDIR_TPM:
976 tmp = g_getenv ("FWUPD_SYSFSTPMDIR");
977 if (tmp != NULL)
978 return g_strdup (tmp);
979 return g_strdup ("/sys/class/tpm");
Richard Hughes83390f62018-06-22 20:36:46 +0100980 /* /sys/bus/platform/drivers */
981 case FU_PATH_KIND_SYSFSDIR_DRIVERS:
982 tmp = g_getenv ("FWUPD_SYSFSDRIVERDIR");
983 if (tmp != NULL)
984 return g_strdup (tmp);
985 return g_strdup ("/sys/bus/platform/drivers");
Richard Hughes4be17d12018-05-30 20:36:29 +0100986 /* /etc */
987 case FU_PATH_KIND_SYSCONFDIR:
988 tmp = g_getenv ("FWUPD_SYSCONFDIR");
989 if (tmp != NULL)
990 return g_strdup (tmp);
991 tmp = g_getenv ("SNAP_USER_DATA");
992 if (tmp != NULL)
993 return g_build_filename (tmp, SYSCONFDIR, NULL);
994 return g_strdup (SYSCONFDIR);
995 /* /usr/lib/<triplet>/fwupd-plugins-3 */
996 case FU_PATH_KIND_PLUGINDIR_PKG:
997 tmp = g_getenv ("FWUPD_PLUGINDIR");
998 if (tmp != NULL)
999 return g_strdup (tmp);
1000 tmp = g_getenv ("SNAP");
1001 if (tmp != NULL)
1002 return g_build_filename (tmp, PLUGINDIR, NULL);
1003 return g_build_filename (PLUGINDIR, NULL);
1004 /* /usr/share/fwupd */
1005 case FU_PATH_KIND_DATADIR_PKG:
1006 tmp = g_getenv ("FWUPD_DATADIR");
1007 if (tmp != NULL)
1008 return g_strdup (tmp);
1009 tmp = g_getenv ("SNAP");
1010 if (tmp != NULL)
1011 return g_build_filename (tmp, DATADIR, PACKAGE_NAME, NULL);
1012 return g_build_filename (DATADIR, PACKAGE_NAME, NULL);
Mario Limoncielloe6e2bf92018-07-10 12:11:25 -05001013 /* /usr/libexec/fwupd/efi */
1014 case FU_PATH_KIND_EFIAPPDIR:
1015 tmp = g_getenv ("FWUPD_EFIAPPDIR");
1016 if (tmp != NULL)
1017 return g_strdup (tmp);
1018#ifdef EFI_APP_LOCATION
1019 tmp = g_getenv ("SNAP");
1020 if (tmp != NULL)
1021 return g_build_filename (tmp, EFI_APP_LOCATION, NULL);
1022 return g_strdup (EFI_APP_LOCATION);
1023#else
1024 return NULL;
1025#endif
Richard Hughes4be17d12018-05-30 20:36:29 +01001026 /* /etc/fwupd */
1027 case FU_PATH_KIND_SYSCONFDIR_PKG:
1028 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
1029 return g_build_filename (basedir, PACKAGE_NAME, NULL);
1030 /* /var/lib/fwupd */
1031 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
1032 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
1033 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
1034 /* /var/cache/fwupd */
1035 case FU_PATH_KIND_CACHEDIR_PKG:
1036 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
1037 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
1038 /* this shouldn't happen */
1039 default:
Richard Hughesbeb47a82018-09-11 18:28:53 +01001040 g_warning ("cannot build path for unknown kind %u", path_kind);
Richard Hughes4be17d12018-05-30 20:36:29 +01001041 }
1042
1043 return NULL;
1044}
Richard Hughes83e56c12018-10-10 20:24:41 +01001045
1046/**
1047 * fu_common_string_replace:
1048 * @string: The #GString to operate on
1049 * @search: The text to search for
1050 * @replace: The text to use for substitutions
1051 *
1052 * Performs multiple search and replace operations on the given string.
1053 *
1054 * Returns: the number of replacements done, or 0 if @search is not found.
1055 *
1056 * Since: 1.2.0
1057 **/
1058guint
1059fu_common_string_replace (GString *string, const gchar *search, const gchar *replace)
1060{
1061 gchar *tmp;
1062 guint count = 0;
1063 gsize search_idx = 0;
1064 gsize replace_len;
1065 gsize search_len;
1066
1067 g_return_val_if_fail (string != NULL, 0);
1068 g_return_val_if_fail (search != NULL, 0);
1069 g_return_val_if_fail (replace != NULL, 0);
1070
1071 /* nothing to do */
1072 if (string->len == 0)
1073 return 0;
1074
1075 search_len = strlen (search);
1076 replace_len = strlen (replace);
1077
1078 do {
1079 tmp = g_strstr_len (string->str + search_idx, -1, search);
1080 if (tmp == NULL)
1081 break;
1082
1083 /* advance the counter in case @replace contains @search */
1084 search_idx = (gsize) (tmp - string->str);
1085
1086 /* reallocate the string if required */
1087 if (search_len > replace_len) {
1088 g_string_erase (string,
1089 (gssize) search_idx,
1090 (gssize) (search_len - replace_len));
1091 memcpy (tmp, replace, replace_len);
1092 } else if (search_len < replace_len) {
1093 g_string_insert_len (string,
1094 (gssize) search_idx,
1095 replace,
1096 (gssize) (replace_len - search_len));
1097 /* we have to treat this specially as it could have
1098 * been reallocated when the insertion happened */
1099 memcpy (string->str + search_idx, replace, replace_len);
1100 } else {
1101 /* just memcmp in the new string */
1102 memcpy (tmp, replace, replace_len);
1103 }
1104 search_idx += replace_len;
1105 count++;
1106 } while (TRUE);
1107
1108 return count;
1109}
Richard Hughese59cb9a2018-12-05 14:37:40 +00001110
1111/**
Richard Hughes35481862019-01-06 12:01:58 +00001112 * fu_common_dump_full:
1113 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1114 * @title: prefix title, or %NULL
1115 * @data: buffer to print
1116 * @len: the size of @data
1117 * @columns: break new lines after this many bytes
1118 * @flags: some #FuDumpFlags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII
1119 *
1120 * Dumps a raw buffer to the screen.
1121 *
1122 * Since: 1.2.4
1123 **/
1124void
1125fu_common_dump_full (const gchar *log_domain,
1126 const gchar *title,
1127 const guint8 *data,
1128 gsize len,
1129 guint columns,
1130 FuDumpFlags flags)
1131{
1132 g_autoptr(GString) str = g_string_new (NULL);
1133
1134 /* optional */
1135 if (title != NULL)
1136 g_string_append_printf (str, "%s:", title);
1137
1138 /* if more than can fit on one line then start afresh */
1139 if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1140 g_string_append (str, "\n");
1141 } else {
1142 for (gsize i = str->len; i < 16; i++)
1143 g_string_append (str, " ");
1144 }
1145
1146 /* offset line */
1147 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1148 g_string_append (str, " │ ");
1149 for (gsize i = 0; i < columns; i++)
1150 g_string_append_printf (str, "%02x ", (guint) i);
1151 g_string_append (str, "\n───────┼");
1152 for (gsize i = 0; i < columns; i++)
1153 g_string_append (str, "───");
1154 g_string_append_printf (str, "\n0x%04x │ ", (guint) 0);
1155 }
1156
1157 /* print each row */
1158 for (gsize i = 0; i < len; i++) {
1159 g_string_append_printf (str, "%02x ", data[i]);
1160
1161 /* optionally print ASCII char */
1162 if (flags & FU_DUMP_FLAGS_SHOW_ASCII) {
1163 if (g_ascii_isprint (data[i]))
1164 g_string_append_printf (str, "[%c] ", data[i]);
1165 else
1166 g_string_append (str, "[?] ");
1167 }
1168
1169 /* new row required */
1170 if (i > 0 && i != len - 1 && (i + 1) % columns == 0) {
1171 g_string_append (str, "\n");
1172 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES)
1173 g_string_append_printf (str, "0x%04x │ ", (guint) i + 1);
1174 }
1175 }
1176 g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str);
1177}
1178
1179/**
Richard Hughese59cb9a2018-12-05 14:37:40 +00001180 * fu_common_dump_raw:
1181 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1182 * @title: prefix title, or %NULL
1183 * @data: buffer to print
1184 * @len: the size of @data
1185 *
1186 * Dumps a raw buffer to the screen.
1187 *
1188 * Since: 1.2.2
1189 **/
1190void
1191fu_common_dump_raw (const gchar *log_domain,
1192 const gchar *title,
1193 const guint8 *data,
1194 gsize len)
1195{
Richard Hughes35481862019-01-06 12:01:58 +00001196 FuDumpFlags flags = FU_DUMP_FLAGS_NONE;
1197 if (len > 64)
1198 flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES;
1199 fu_common_dump_full (log_domain, title, data, len, 32, flags);
Richard Hughese59cb9a2018-12-05 14:37:40 +00001200}
1201
1202/**
1203 * fu_common_dump_raw:
1204 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1205 * @title: prefix title, or %NULL
1206 * @bytes: a #GBytes
1207 *
1208 * Dumps a byte buffer to the screen.
1209 *
1210 * Since: 1.2.2
1211 **/
1212void
1213fu_common_dump_bytes (const gchar *log_domain,
1214 const gchar *title,
1215 GBytes *bytes)
1216{
1217 gsize len = 0;
1218 const guint8 *data = g_bytes_get_data (bytes, &len);
1219 fu_common_dump_raw (log_domain, title, data, len);
1220}
Richard Hughesfc90f392019-01-15 21:21:16 +00001221
1222/**
1223 * fu_common_bytes_align:
1224 * @bytes: a #GBytes
1225 * @blksz: block size in bytes
1226 * @padval: the byte used to pad the byte buffer
1227 *
1228 * Aligns a block of memory to @blksize using the @padval value; if
1229 * the block is already aligned then the original @bytes is returned.
1230 *
1231 * Returns: (transfer full): a #GBytes, possibly @bytes
1232 *
1233 * Since: 1.2.4
1234 **/
1235GBytes *
1236fu_common_bytes_align (GBytes *bytes, gsize blksz, gchar padval)
1237{
1238 const guint8 *data;
1239 gsize sz;
1240
1241 g_return_val_if_fail (bytes != NULL, NULL);
1242 g_return_val_if_fail (blksz > 0, NULL);
1243
1244 /* pad */
1245 data = g_bytes_get_data (bytes, &sz);
1246 if (sz % blksz != 0) {
1247 gsize sz_align = ((sz / blksz) + 1) * blksz;
1248 guint8 *data_align = g_malloc (sz_align);
1249 memcpy (data_align, data, sz);
1250 memset (data_align + sz, padval, sz_align - sz);
1251 g_debug ("aligning 0x%x bytes to 0x%x",
1252 (guint) sz, (guint) sz_align);
1253 return g_bytes_new_take (data_align, sz_align);
1254 }
1255
1256 /* perfectly aligned */
1257 return g_bytes_ref (bytes);
1258}
Richard Hughes36999462019-03-19 20:23:29 +00001259
1260/**
1261 * fu_common_bytes_is_empty:
1262 * @bytes: a #GBytes
1263 *
1264 * Checks if a byte array are just empty (0xff) bytes.
1265 *
1266 * Return value: %TRUE if @bytes is empty
1267 **/
1268gboolean
1269fu_common_bytes_is_empty (GBytes *bytes)
1270{
1271 gsize sz = 0;
1272 const guint8 *buf = g_bytes_get_data (bytes, &sz);
1273 for (gsize i = 0; i < sz; i++) {
1274 if (buf[i] != 0xff)
1275 return FALSE;
1276 }
1277 return TRUE;
1278}
Richard Hughes2aad1042019-03-21 09:03:32 +00001279
1280/**
1281 * fu_common_bytes_compare:
1282 * @bytes1: a #GBytes
1283 * @bytes2: another #GBytes
1284 * @error: A #GError or %NULL
1285 *
1286 * Checks if a byte array are just empty (0xff) bytes.
1287 *
1288 * Return value: %TRUE if @bytes1 and @bytes2 are identical
1289 **/
1290gboolean
1291fu_common_bytes_compare (GBytes *bytes1, GBytes *bytes2, GError **error)
1292{
1293 const guint8 *buf1;
1294 const guint8 *buf2;
1295 gsize bufsz1;
1296 gsize bufsz2;
1297
1298 g_return_val_if_fail (bytes1 != NULL, FALSE);
1299 g_return_val_if_fail (bytes2 != NULL, FALSE);
1300 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1301
1302 /* not the same length */
1303 buf1 = g_bytes_get_data (bytes1, &bufsz1);
1304 buf2 = g_bytes_get_data (bytes2, &bufsz2);
1305 if (bufsz1 != bufsz2) {
1306 g_set_error (error,
1307 G_IO_ERROR,
1308 G_IO_ERROR_INVALID_DATA,
1309 "got %" G_GSIZE_FORMAT " bytes, expected "
1310 "%" G_GSIZE_FORMAT, bufsz1, bufsz2);
1311 return FALSE;
1312 }
1313
1314 /* check matches */
1315 for (guint i = 0x0; i < bufsz1; i++) {
1316 if (buf1[i] != buf2[i]) {
1317 g_set_error (error,
1318 G_IO_ERROR,
1319 G_IO_ERROR_INVALID_DATA,
1320 "got 0x%02x, expected 0x%02x @ 0x%04x",
1321 buf1[i], buf2[i], i);
1322 return FALSE;
1323 }
1324 }
1325
1326 /* success */
1327 return TRUE;
1328}
Richard Hughes484ee292019-03-22 16:10:50 +00001329
1330/**
1331 * fu_common_realpath:
1332 * @filename: a filename
1333 * @error: A #GError or %NULL
1334 *
1335 * Finds the canonicalized absolute filename for a path.
1336 *
1337 * Return value: A filename, or %NULL if invalid or not found
1338 **/
1339gchar *
1340fu_common_realpath (const gchar *filename, GError **error)
1341{
1342 char full_tmp[PATH_MAX];
1343
1344 g_return_val_if_fail (filename != NULL, NULL);
1345
1346 if (realpath (filename, full_tmp) == NULL) {
1347 g_set_error (error,
1348 G_IO_ERROR,
1349 G_IO_ERROR_INVALID_DATA,
1350 "cannot resolve path: %s",
1351 strerror (errno));
1352 return NULL;
1353 }
1354 return g_strdup (full_tmp);
1355}