blob: e2803c2401a13030c08556d77a05de28555e2454 [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");
Mario Limonciello39602652019-04-29 21:08:58 -0500974 /* /sys/class/tpm */
Richard Hughesb56015e2018-12-12 09:25:32 +0000975 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);
Mario Limonciello057c67a2019-05-23 10:44:19 -05001038 case FU_PATH_KIND_POLKIT_ACTIONS:
1039#ifdef POLKIT_ACTIONDIR
1040 return g_strdup (POLKIT_ACTIONDIR);
1041#else
1042 return NULL;
1043#endif
Richard Hughes4be17d12018-05-30 20:36:29 +01001044 /* this shouldn't happen */
1045 default:
Richard Hughesbeb47a82018-09-11 18:28:53 +01001046 g_warning ("cannot build path for unknown kind %u", path_kind);
Richard Hughes4be17d12018-05-30 20:36:29 +01001047 }
1048
1049 return NULL;
1050}
Richard Hughes83e56c12018-10-10 20:24:41 +01001051
1052/**
1053 * fu_common_string_replace:
1054 * @string: The #GString to operate on
1055 * @search: The text to search for
1056 * @replace: The text to use for substitutions
1057 *
1058 * Performs multiple search and replace operations on the given string.
1059 *
1060 * Returns: the number of replacements done, or 0 if @search is not found.
1061 *
1062 * Since: 1.2.0
1063 **/
1064guint
1065fu_common_string_replace (GString *string, const gchar *search, const gchar *replace)
1066{
1067 gchar *tmp;
1068 guint count = 0;
1069 gsize search_idx = 0;
1070 gsize replace_len;
1071 gsize search_len;
1072
1073 g_return_val_if_fail (string != NULL, 0);
1074 g_return_val_if_fail (search != NULL, 0);
1075 g_return_val_if_fail (replace != NULL, 0);
1076
1077 /* nothing to do */
1078 if (string->len == 0)
1079 return 0;
1080
1081 search_len = strlen (search);
1082 replace_len = strlen (replace);
1083
1084 do {
1085 tmp = g_strstr_len (string->str + search_idx, -1, search);
1086 if (tmp == NULL)
1087 break;
1088
1089 /* advance the counter in case @replace contains @search */
1090 search_idx = (gsize) (tmp - string->str);
1091
1092 /* reallocate the string if required */
1093 if (search_len > replace_len) {
1094 g_string_erase (string,
1095 (gssize) search_idx,
1096 (gssize) (search_len - replace_len));
1097 memcpy (tmp, replace, replace_len);
1098 } else if (search_len < replace_len) {
1099 g_string_insert_len (string,
1100 (gssize) search_idx,
1101 replace,
1102 (gssize) (replace_len - search_len));
1103 /* we have to treat this specially as it could have
1104 * been reallocated when the insertion happened */
1105 memcpy (string->str + search_idx, replace, replace_len);
1106 } else {
1107 /* just memcmp in the new string */
1108 memcpy (tmp, replace, replace_len);
1109 }
1110 search_idx += replace_len;
1111 count++;
1112 } while (TRUE);
1113
1114 return count;
1115}
Richard Hughese59cb9a2018-12-05 14:37:40 +00001116
1117/**
Richard Hughes35481862019-01-06 12:01:58 +00001118 * fu_common_dump_full:
1119 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1120 * @title: prefix title, or %NULL
1121 * @data: buffer to print
1122 * @len: the size of @data
1123 * @columns: break new lines after this many bytes
1124 * @flags: some #FuDumpFlags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII
1125 *
1126 * Dumps a raw buffer to the screen.
1127 *
1128 * Since: 1.2.4
1129 **/
1130void
1131fu_common_dump_full (const gchar *log_domain,
1132 const gchar *title,
1133 const guint8 *data,
1134 gsize len,
1135 guint columns,
1136 FuDumpFlags flags)
1137{
1138 g_autoptr(GString) str = g_string_new (NULL);
1139
1140 /* optional */
1141 if (title != NULL)
1142 g_string_append_printf (str, "%s:", title);
1143
1144 /* if more than can fit on one line then start afresh */
1145 if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1146 g_string_append (str, "\n");
1147 } else {
1148 for (gsize i = str->len; i < 16; i++)
1149 g_string_append (str, " ");
1150 }
1151
1152 /* offset line */
1153 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1154 g_string_append (str, " │ ");
1155 for (gsize i = 0; i < columns; i++)
1156 g_string_append_printf (str, "%02x ", (guint) i);
1157 g_string_append (str, "\n───────┼");
1158 for (gsize i = 0; i < columns; i++)
1159 g_string_append (str, "───");
1160 g_string_append_printf (str, "\n0x%04x │ ", (guint) 0);
1161 }
1162
1163 /* print each row */
1164 for (gsize i = 0; i < len; i++) {
1165 g_string_append_printf (str, "%02x ", data[i]);
1166
1167 /* optionally print ASCII char */
1168 if (flags & FU_DUMP_FLAGS_SHOW_ASCII) {
1169 if (g_ascii_isprint (data[i]))
1170 g_string_append_printf (str, "[%c] ", data[i]);
1171 else
1172 g_string_append (str, "[?] ");
1173 }
1174
1175 /* new row required */
1176 if (i > 0 && i != len - 1 && (i + 1) % columns == 0) {
1177 g_string_append (str, "\n");
1178 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES)
1179 g_string_append_printf (str, "0x%04x │ ", (guint) i + 1);
1180 }
1181 }
1182 g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str);
1183}
1184
1185/**
Richard Hughese59cb9a2018-12-05 14:37:40 +00001186 * fu_common_dump_raw:
1187 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1188 * @title: prefix title, or %NULL
1189 * @data: buffer to print
1190 * @len: the size of @data
1191 *
1192 * Dumps a raw buffer to the screen.
1193 *
1194 * Since: 1.2.2
1195 **/
1196void
1197fu_common_dump_raw (const gchar *log_domain,
1198 const gchar *title,
1199 const guint8 *data,
1200 gsize len)
1201{
Richard Hughes35481862019-01-06 12:01:58 +00001202 FuDumpFlags flags = FU_DUMP_FLAGS_NONE;
1203 if (len > 64)
1204 flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES;
1205 fu_common_dump_full (log_domain, title, data, len, 32, flags);
Richard Hughese59cb9a2018-12-05 14:37:40 +00001206}
1207
1208/**
Mario Limonciello39602652019-04-29 21:08:58 -05001209 * fu_common_dump_bytes:
Richard Hughese59cb9a2018-12-05 14:37:40 +00001210 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1211 * @title: prefix title, or %NULL
1212 * @bytes: a #GBytes
1213 *
1214 * Dumps a byte buffer to the screen.
1215 *
1216 * Since: 1.2.2
1217 **/
1218void
1219fu_common_dump_bytes (const gchar *log_domain,
1220 const gchar *title,
1221 GBytes *bytes)
1222{
1223 gsize len = 0;
1224 const guint8 *data = g_bytes_get_data (bytes, &len);
1225 fu_common_dump_raw (log_domain, title, data, len);
1226}
Richard Hughesfc90f392019-01-15 21:21:16 +00001227
1228/**
1229 * fu_common_bytes_align:
1230 * @bytes: a #GBytes
1231 * @blksz: block size in bytes
1232 * @padval: the byte used to pad the byte buffer
1233 *
1234 * Aligns a block of memory to @blksize using the @padval value; if
1235 * the block is already aligned then the original @bytes is returned.
1236 *
1237 * Returns: (transfer full): a #GBytes, possibly @bytes
1238 *
1239 * Since: 1.2.4
1240 **/
1241GBytes *
1242fu_common_bytes_align (GBytes *bytes, gsize blksz, gchar padval)
1243{
1244 const guint8 *data;
1245 gsize sz;
1246
1247 g_return_val_if_fail (bytes != NULL, NULL);
1248 g_return_val_if_fail (blksz > 0, NULL);
1249
1250 /* pad */
1251 data = g_bytes_get_data (bytes, &sz);
1252 if (sz % blksz != 0) {
1253 gsize sz_align = ((sz / blksz) + 1) * blksz;
1254 guint8 *data_align = g_malloc (sz_align);
1255 memcpy (data_align, data, sz);
1256 memset (data_align + sz, padval, sz_align - sz);
1257 g_debug ("aligning 0x%x bytes to 0x%x",
1258 (guint) sz, (guint) sz_align);
1259 return g_bytes_new_take (data_align, sz_align);
1260 }
1261
1262 /* perfectly aligned */
1263 return g_bytes_ref (bytes);
1264}
Richard Hughes36999462019-03-19 20:23:29 +00001265
1266/**
1267 * fu_common_bytes_is_empty:
1268 * @bytes: a #GBytes
1269 *
1270 * Checks if a byte array are just empty (0xff) bytes.
1271 *
1272 * Return value: %TRUE if @bytes is empty
1273 **/
1274gboolean
1275fu_common_bytes_is_empty (GBytes *bytes)
1276{
1277 gsize sz = 0;
1278 const guint8 *buf = g_bytes_get_data (bytes, &sz);
1279 for (gsize i = 0; i < sz; i++) {
1280 if (buf[i] != 0xff)
1281 return FALSE;
1282 }
1283 return TRUE;
1284}
Richard Hughes2aad1042019-03-21 09:03:32 +00001285
1286/**
1287 * fu_common_bytes_compare:
1288 * @bytes1: a #GBytes
1289 * @bytes2: another #GBytes
1290 * @error: A #GError or %NULL
1291 *
1292 * Checks if a byte array are just empty (0xff) bytes.
1293 *
1294 * Return value: %TRUE if @bytes1 and @bytes2 are identical
1295 **/
1296gboolean
1297fu_common_bytes_compare (GBytes *bytes1, GBytes *bytes2, GError **error)
1298{
1299 const guint8 *buf1;
1300 const guint8 *buf2;
1301 gsize bufsz1;
1302 gsize bufsz2;
1303
1304 g_return_val_if_fail (bytes1 != NULL, FALSE);
1305 g_return_val_if_fail (bytes2 != NULL, FALSE);
1306 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1307
1308 /* not the same length */
1309 buf1 = g_bytes_get_data (bytes1, &bufsz1);
1310 buf2 = g_bytes_get_data (bytes2, &bufsz2);
1311 if (bufsz1 != bufsz2) {
1312 g_set_error (error,
1313 G_IO_ERROR,
1314 G_IO_ERROR_INVALID_DATA,
1315 "got %" G_GSIZE_FORMAT " bytes, expected "
1316 "%" G_GSIZE_FORMAT, bufsz1, bufsz2);
1317 return FALSE;
1318 }
1319
1320 /* check matches */
1321 for (guint i = 0x0; i < bufsz1; i++) {
1322 if (buf1[i] != buf2[i]) {
1323 g_set_error (error,
1324 G_IO_ERROR,
1325 G_IO_ERROR_INVALID_DATA,
1326 "got 0x%02x, expected 0x%02x @ 0x%04x",
1327 buf1[i], buf2[i], i);
1328 return FALSE;
1329 }
1330 }
1331
1332 /* success */
1333 return TRUE;
1334}
Richard Hughes484ee292019-03-22 16:10:50 +00001335
1336/**
Richard Hughes7afd7cb2019-08-07 11:42:42 +01001337 * fu_common_bytes_pad:
1338 * @bytes: a #GBytes
1339 * @sz: the desired size in bytes
1340 *
1341 * Pads a GBytes to a given @sz with `0xff`.
1342 *
1343 * Return value: (transfer full): a #GBytes
1344 **/
1345GBytes *
1346fu_common_bytes_pad (GBytes *bytes, gsize sz)
1347{
1348 gsize bytes_sz;
1349
1350 g_return_val_if_fail (g_bytes_get_size (bytes) <= sz, NULL);
1351
1352 /* pad */
1353 bytes_sz = g_bytes_get_size (bytes);
1354 if (bytes_sz < sz) {
1355 const guint8 *data = g_bytes_get_data (bytes, NULL);
1356 guint8 *data_new = g_malloc (sz);
1357 memcpy (data_new, data, bytes_sz);
1358 memset (data_new + bytes_sz, 0xff, sz - bytes_sz);
1359 return g_bytes_new_take (data_new, sz);
1360 }
1361
1362 /* exactly right */
1363 return g_bytes_ref (bytes);
1364}
1365
1366/**
Richard Hughes484ee292019-03-22 16:10:50 +00001367 * fu_common_realpath:
1368 * @filename: a filename
1369 * @error: A #GError or %NULL
1370 *
1371 * Finds the canonicalized absolute filename for a path.
1372 *
1373 * Return value: A filename, or %NULL if invalid or not found
1374 **/
1375gchar *
1376fu_common_realpath (const gchar *filename, GError **error)
1377{
1378 char full_tmp[PATH_MAX];
1379
1380 g_return_val_if_fail (filename != NULL, NULL);
1381
1382 if (realpath (filename, full_tmp) == NULL) {
1383 g_set_error (error,
1384 G_IO_ERROR,
1385 G_IO_ERROR_INVALID_DATA,
1386 "cannot resolve path: %s",
1387 strerror (errno));
1388 return NULL;
1389 }
1390 return g_strdup (full_tmp);
1391}
Richard Hughes7afd7cb2019-08-07 11:42:42 +01001392
1393/**
1394 * fu_common_strnsplit:
1395 * @str: a string to split
1396 * @sz: size of @str
1397 * @delimiter: a string which specifies the places at which to split the string
1398 * @max_tokens: the maximum number of pieces to split @str into
1399 *
1400 * Splits a string into a maximum of @max_tokens pieces, using the given
1401 * delimiter. If @max_tokens is reached, the remainder of string is appended
1402 * to the last token.
1403 *
1404 * Return value: a newly-allocated NULL-terminated array of strings
1405 **/
1406gchar **
1407fu_common_strnsplit (const gchar *str, gsize sz,
1408 const gchar *delimiter, gint max_tokens)
1409{
1410 if (str[sz - 1] != '\0') {
1411 g_autofree gchar *str2 = g_strndup (str, sz);
1412 return g_strsplit (str2, delimiter, max_tokens);
1413 }
1414 return g_strsplit (str, delimiter, max_tokens);
1415}