blob: 684a21185b7051fad829063fc26ac7fa9503b7d0 [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:
Mario Limonciello277c1962019-08-26 23:42:23 -05001028 tmp = g_getenv ("CONFIGURATION_DIRECTORY");
1029 if (tmp != NULL)
1030 return g_build_filename (tmp, NULL);
Richard Hughes4be17d12018-05-30 20:36:29 +01001031 basedir = fu_common_get_path (FU_PATH_KIND_SYSCONFDIR);
1032 return g_build_filename (basedir, PACKAGE_NAME, NULL);
1033 /* /var/lib/fwupd */
1034 case FU_PATH_KIND_LOCALSTATEDIR_PKG:
Mario Limonciello277c1962019-08-26 23:42:23 -05001035 tmp = g_getenv ("STATE_DIRECTORY");
1036 if (tmp != NULL)
1037 return g_build_filename (tmp, NULL);
Richard Hughes4be17d12018-05-30 20:36:29 +01001038 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
1039 return g_build_filename (basedir, "lib", PACKAGE_NAME, NULL);
1040 /* /var/cache/fwupd */
1041 case FU_PATH_KIND_CACHEDIR_PKG:
Mario Limonciello277c1962019-08-26 23:42:23 -05001042 tmp = g_getenv ("CACHE_DIRECTORY");
1043 if (tmp != NULL)
1044 return g_build_filename (tmp, NULL);
Richard Hughes4be17d12018-05-30 20:36:29 +01001045 basedir = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR);
1046 return g_build_filename (basedir, "cache", PACKAGE_NAME, NULL);
Mario Limonciello057c67a2019-05-23 10:44:19 -05001047 case FU_PATH_KIND_POLKIT_ACTIONS:
1048#ifdef POLKIT_ACTIONDIR
1049 return g_strdup (POLKIT_ACTIONDIR);
1050#else
1051 return NULL;
1052#endif
Richard Hughes4be17d12018-05-30 20:36:29 +01001053 /* this shouldn't happen */
1054 default:
Richard Hughesbeb47a82018-09-11 18:28:53 +01001055 g_warning ("cannot build path for unknown kind %u", path_kind);
Richard Hughes4be17d12018-05-30 20:36:29 +01001056 }
1057
1058 return NULL;
1059}
Richard Hughes83e56c12018-10-10 20:24:41 +01001060
1061/**
1062 * fu_common_string_replace:
1063 * @string: The #GString to operate on
1064 * @search: The text to search for
1065 * @replace: The text to use for substitutions
1066 *
1067 * Performs multiple search and replace operations on the given string.
1068 *
1069 * Returns: the number of replacements done, or 0 if @search is not found.
1070 *
1071 * Since: 1.2.0
1072 **/
1073guint
1074fu_common_string_replace (GString *string, const gchar *search, const gchar *replace)
1075{
1076 gchar *tmp;
1077 guint count = 0;
1078 gsize search_idx = 0;
1079 gsize replace_len;
1080 gsize search_len;
1081
1082 g_return_val_if_fail (string != NULL, 0);
1083 g_return_val_if_fail (search != NULL, 0);
1084 g_return_val_if_fail (replace != NULL, 0);
1085
1086 /* nothing to do */
1087 if (string->len == 0)
1088 return 0;
1089
1090 search_len = strlen (search);
1091 replace_len = strlen (replace);
1092
1093 do {
1094 tmp = g_strstr_len (string->str + search_idx, -1, search);
1095 if (tmp == NULL)
1096 break;
1097
1098 /* advance the counter in case @replace contains @search */
1099 search_idx = (gsize) (tmp - string->str);
1100
1101 /* reallocate the string if required */
1102 if (search_len > replace_len) {
1103 g_string_erase (string,
1104 (gssize) search_idx,
1105 (gssize) (search_len - replace_len));
1106 memcpy (tmp, replace, replace_len);
1107 } else if (search_len < replace_len) {
1108 g_string_insert_len (string,
1109 (gssize) search_idx,
1110 replace,
1111 (gssize) (replace_len - search_len));
1112 /* we have to treat this specially as it could have
1113 * been reallocated when the insertion happened */
1114 memcpy (string->str + search_idx, replace, replace_len);
1115 } else {
1116 /* just memcmp in the new string */
1117 memcpy (tmp, replace, replace_len);
1118 }
1119 search_idx += replace_len;
1120 count++;
1121 } while (TRUE);
1122
1123 return count;
1124}
Richard Hughese59cb9a2018-12-05 14:37:40 +00001125
Richard Hughescea28de2019-08-09 11:16:40 +01001126void
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001127fu_common_string_append_kv (GString *str, guint idt, const gchar *key, const gchar *value)
Richard Hughescea28de2019-08-09 11:16:40 +01001128{
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001129 const guint align = 23;
Richard Hughescea28de2019-08-09 11:16:40 +01001130
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001131 g_return_if_fail (idt * 2 < align);
Richard Hughescea28de2019-08-09 11:16:40 +01001132
1133 /* ignore */
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001134 if (key == NULL)
Richard Hughescea28de2019-08-09 11:16:40 +01001135 return;
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001136 for (gsize i = 0; i < idt; i++)
1137 g_string_append (str, " ");
Mario Limonciellofee8f492019-08-18 12:16:07 -05001138 if (key[0] != '\0') {
1139 g_string_append_printf (str, "%s:", key);
1140 idt++;
1141 }
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001142 if (value != NULL) {
Mario Limonciellofee8f492019-08-18 12:16:07 -05001143 for (gsize i = strlen (key) + idt; i < align; i++)
Richard Hughes6e3e62b2019-08-14 10:43:08 +01001144 g_string_append (str, " ");
1145 g_string_append (str, value);
1146 }
1147 g_string_append (str, "\n");
1148}
1149
1150void
1151fu_common_string_append_ku (GString *str, guint idt, const gchar *key, guint64 value)
1152{
1153 g_autofree gchar *tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, value);
1154 fu_common_string_append_kv (str, idt, key, tmp);
1155}
1156
1157void
1158fu_common_string_append_kx (GString *str, guint idt, const gchar *key, guint64 value)
1159{
1160 g_autofree gchar *tmp = g_strdup_printf ("0x%x", (guint) value);
1161 fu_common_string_append_kv (str, idt, key, tmp);
1162}
1163
1164void
1165fu_common_string_append_kb (GString *str, guint idt, const gchar *key, gboolean value)
1166{
1167 fu_common_string_append_kv (str, idt, key, value ? "true" : "false");
Richard Hughescea28de2019-08-09 11:16:40 +01001168}
1169
Richard Hughese59cb9a2018-12-05 14:37:40 +00001170/**
Richard Hughes35481862019-01-06 12:01:58 +00001171 * fu_common_dump_full:
1172 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1173 * @title: prefix title, or %NULL
1174 * @data: buffer to print
1175 * @len: the size of @data
1176 * @columns: break new lines after this many bytes
1177 * @flags: some #FuDumpFlags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII
1178 *
1179 * Dumps a raw buffer to the screen.
1180 *
1181 * Since: 1.2.4
1182 **/
1183void
1184fu_common_dump_full (const gchar *log_domain,
1185 const gchar *title,
1186 const guint8 *data,
1187 gsize len,
1188 guint columns,
1189 FuDumpFlags flags)
1190{
1191 g_autoptr(GString) str = g_string_new (NULL);
1192
1193 /* optional */
1194 if (title != NULL)
1195 g_string_append_printf (str, "%s:", title);
1196
1197 /* if more than can fit on one line then start afresh */
1198 if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1199 g_string_append (str, "\n");
1200 } else {
1201 for (gsize i = str->len; i < 16; i++)
1202 g_string_append (str, " ");
1203 }
1204
1205 /* offset line */
1206 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
1207 g_string_append (str, " │ ");
1208 for (gsize i = 0; i < columns; i++)
1209 g_string_append_printf (str, "%02x ", (guint) i);
1210 g_string_append (str, "\n───────┼");
1211 for (gsize i = 0; i < columns; i++)
1212 g_string_append (str, "───");
1213 g_string_append_printf (str, "\n0x%04x │ ", (guint) 0);
1214 }
1215
1216 /* print each row */
1217 for (gsize i = 0; i < len; i++) {
1218 g_string_append_printf (str, "%02x ", data[i]);
1219
1220 /* optionally print ASCII char */
1221 if (flags & FU_DUMP_FLAGS_SHOW_ASCII) {
1222 if (g_ascii_isprint (data[i]))
1223 g_string_append_printf (str, "[%c] ", data[i]);
1224 else
1225 g_string_append (str, "[?] ");
1226 }
1227
1228 /* new row required */
1229 if (i > 0 && i != len - 1 && (i + 1) % columns == 0) {
1230 g_string_append (str, "\n");
1231 if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES)
1232 g_string_append_printf (str, "0x%04x │ ", (guint) i + 1);
1233 }
1234 }
1235 g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str);
1236}
1237
1238/**
Richard Hughese59cb9a2018-12-05 14:37:40 +00001239 * fu_common_dump_raw:
1240 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1241 * @title: prefix title, or %NULL
1242 * @data: buffer to print
1243 * @len: the size of @data
1244 *
1245 * Dumps a raw buffer to the screen.
1246 *
1247 * Since: 1.2.2
1248 **/
1249void
1250fu_common_dump_raw (const gchar *log_domain,
1251 const gchar *title,
1252 const guint8 *data,
1253 gsize len)
1254{
Richard Hughes35481862019-01-06 12:01:58 +00001255 FuDumpFlags flags = FU_DUMP_FLAGS_NONE;
1256 if (len > 64)
1257 flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES;
1258 fu_common_dump_full (log_domain, title, data, len, 32, flags);
Richard Hughese59cb9a2018-12-05 14:37:40 +00001259}
1260
1261/**
Mario Limonciello39602652019-04-29 21:08:58 -05001262 * fu_common_dump_bytes:
Richard Hughese59cb9a2018-12-05 14:37:40 +00001263 * @log_domain: log domain, typically %G_LOG_DOMAIN or %NULL
1264 * @title: prefix title, or %NULL
1265 * @bytes: a #GBytes
1266 *
1267 * Dumps a byte buffer to the screen.
1268 *
1269 * Since: 1.2.2
1270 **/
1271void
1272fu_common_dump_bytes (const gchar *log_domain,
1273 const gchar *title,
1274 GBytes *bytes)
1275{
1276 gsize len = 0;
1277 const guint8 *data = g_bytes_get_data (bytes, &len);
1278 fu_common_dump_raw (log_domain, title, data, len);
1279}
Richard Hughesfc90f392019-01-15 21:21:16 +00001280
1281/**
1282 * fu_common_bytes_align:
1283 * @bytes: a #GBytes
1284 * @blksz: block size in bytes
1285 * @padval: the byte used to pad the byte buffer
1286 *
1287 * Aligns a block of memory to @blksize using the @padval value; if
1288 * the block is already aligned then the original @bytes is returned.
1289 *
1290 * Returns: (transfer full): a #GBytes, possibly @bytes
1291 *
1292 * Since: 1.2.4
1293 **/
1294GBytes *
1295fu_common_bytes_align (GBytes *bytes, gsize blksz, gchar padval)
1296{
1297 const guint8 *data;
1298 gsize sz;
1299
1300 g_return_val_if_fail (bytes != NULL, NULL);
1301 g_return_val_if_fail (blksz > 0, NULL);
1302
1303 /* pad */
1304 data = g_bytes_get_data (bytes, &sz);
1305 if (sz % blksz != 0) {
1306 gsize sz_align = ((sz / blksz) + 1) * blksz;
1307 guint8 *data_align = g_malloc (sz_align);
1308 memcpy (data_align, data, sz);
1309 memset (data_align + sz, padval, sz_align - sz);
1310 g_debug ("aligning 0x%x bytes to 0x%x",
1311 (guint) sz, (guint) sz_align);
1312 return g_bytes_new_take (data_align, sz_align);
1313 }
1314
1315 /* perfectly aligned */
1316 return g_bytes_ref (bytes);
1317}
Richard Hughes36999462019-03-19 20:23:29 +00001318
1319/**
1320 * fu_common_bytes_is_empty:
1321 * @bytes: a #GBytes
1322 *
1323 * Checks if a byte array are just empty (0xff) bytes.
1324 *
1325 * Return value: %TRUE if @bytes is empty
1326 **/
1327gboolean
1328fu_common_bytes_is_empty (GBytes *bytes)
1329{
1330 gsize sz = 0;
1331 const guint8 *buf = g_bytes_get_data (bytes, &sz);
1332 for (gsize i = 0; i < sz; i++) {
1333 if (buf[i] != 0xff)
1334 return FALSE;
1335 }
1336 return TRUE;
1337}
Richard Hughes2aad1042019-03-21 09:03:32 +00001338
1339/**
1340 * fu_common_bytes_compare:
1341 * @bytes1: a #GBytes
1342 * @bytes2: another #GBytes
1343 * @error: A #GError or %NULL
1344 *
1345 * Checks if a byte array are just empty (0xff) bytes.
1346 *
1347 * Return value: %TRUE if @bytes1 and @bytes2 are identical
1348 **/
1349gboolean
1350fu_common_bytes_compare (GBytes *bytes1, GBytes *bytes2, GError **error)
1351{
1352 const guint8 *buf1;
1353 const guint8 *buf2;
1354 gsize bufsz1;
1355 gsize bufsz2;
1356
1357 g_return_val_if_fail (bytes1 != NULL, FALSE);
1358 g_return_val_if_fail (bytes2 != NULL, FALSE);
1359 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1360
1361 /* not the same length */
1362 buf1 = g_bytes_get_data (bytes1, &bufsz1);
1363 buf2 = g_bytes_get_data (bytes2, &bufsz2);
1364 if (bufsz1 != bufsz2) {
1365 g_set_error (error,
1366 G_IO_ERROR,
1367 G_IO_ERROR_INVALID_DATA,
1368 "got %" G_GSIZE_FORMAT " bytes, expected "
1369 "%" G_GSIZE_FORMAT, bufsz1, bufsz2);
1370 return FALSE;
1371 }
1372
1373 /* check matches */
1374 for (guint i = 0x0; i < bufsz1; i++) {
1375 if (buf1[i] != buf2[i]) {
1376 g_set_error (error,
1377 G_IO_ERROR,
1378 G_IO_ERROR_INVALID_DATA,
1379 "got 0x%02x, expected 0x%02x @ 0x%04x",
1380 buf1[i], buf2[i], i);
1381 return FALSE;
1382 }
1383 }
1384
1385 /* success */
1386 return TRUE;
1387}
Richard Hughes484ee292019-03-22 16:10:50 +00001388
1389/**
Richard Hughes7afd7cb2019-08-07 11:42:42 +01001390 * fu_common_bytes_pad:
1391 * @bytes: a #GBytes
1392 * @sz: the desired size in bytes
1393 *
1394 * Pads a GBytes to a given @sz with `0xff`.
1395 *
1396 * Return value: (transfer full): a #GBytes
1397 **/
1398GBytes *
1399fu_common_bytes_pad (GBytes *bytes, gsize sz)
1400{
1401 gsize bytes_sz;
1402
1403 g_return_val_if_fail (g_bytes_get_size (bytes) <= sz, NULL);
1404
1405 /* pad */
1406 bytes_sz = g_bytes_get_size (bytes);
1407 if (bytes_sz < sz) {
1408 const guint8 *data = g_bytes_get_data (bytes, NULL);
1409 guint8 *data_new = g_malloc (sz);
1410 memcpy (data_new, data, bytes_sz);
1411 memset (data_new + bytes_sz, 0xff, sz - bytes_sz);
1412 return g_bytes_new_take (data_new, sz);
1413 }
1414
1415 /* exactly right */
1416 return g_bytes_ref (bytes);
1417}
1418
1419/**
Richard Hughes484ee292019-03-22 16:10:50 +00001420 * fu_common_realpath:
1421 * @filename: a filename
1422 * @error: A #GError or %NULL
1423 *
1424 * Finds the canonicalized absolute filename for a path.
1425 *
1426 * Return value: A filename, or %NULL if invalid or not found
1427 **/
1428gchar *
1429fu_common_realpath (const gchar *filename, GError **error)
1430{
1431 char full_tmp[PATH_MAX];
1432
1433 g_return_val_if_fail (filename != NULL, NULL);
1434
1435 if (realpath (filename, full_tmp) == NULL) {
1436 g_set_error (error,
1437 G_IO_ERROR,
1438 G_IO_ERROR_INVALID_DATA,
1439 "cannot resolve path: %s",
1440 strerror (errno));
1441 return NULL;
1442 }
1443 return g_strdup (full_tmp);
1444}
Richard Hughes7afd7cb2019-08-07 11:42:42 +01001445
1446/**
1447 * fu_common_strnsplit:
1448 * @str: a string to split
1449 * @sz: size of @str
1450 * @delimiter: a string which specifies the places at which to split the string
1451 * @max_tokens: the maximum number of pieces to split @str into
1452 *
1453 * Splits a string into a maximum of @max_tokens pieces, using the given
1454 * delimiter. If @max_tokens is reached, the remainder of string is appended
1455 * to the last token.
1456 *
1457 * Return value: a newly-allocated NULL-terminated array of strings
1458 **/
1459gchar **
1460fu_common_strnsplit (const gchar *str, gsize sz,
1461 const gchar *delimiter, gint max_tokens)
1462{
1463 if (str[sz - 1] != '\0') {
1464 g_autofree gchar *str2 = g_strndup (str, sz);
1465 return g_strsplit (str2, delimiter, max_tokens);
1466 }
1467 return g_strsplit (str, delimiter, max_tokens);
1468}
Richard Hughes5308ea42019-08-09 12:25:13 +01001469
1470/**
1471 * fu_memcpy_safe:
1472 * @dst: destination buffer
1473 * @dst_sz: maximum size of @dst, typically `sizeof(dst)`
1474 * @dst_offset: offset in bytes into @dst to copy to
1475 * @src: source buffer
1476 * @src_sz: maximum size of @dst, typically `sizeof(src)`
1477 * @src_offset: offset in bytes into @src to copy from
1478 * @n: number of bytes to copy from @src+@offset from
1479 * @error: A #GError or %NULL
1480 *
1481 * Copies some memory using memcpy in a safe way. Providing the buffer sizes
1482 * of both the destination and the source allows us to check for buffer overflow.
1483 *
1484 * Providing the buffer offsets also allows us to check reading past the end of
1485 * the source buffer. For this reason the caller should NEVER add an offset to
1486 * @src or @dst.
1487 *
1488 * You don't need to use this function in "obviously correct" cases, nor should
1489 * you use it when performance is a concern. Only us it when you're not sure if
1490 * malicious data from a device or firmware could cause memory corruption.
1491 *
1492 * Return value: %TRUE if the bytes were copied, %FALSE otherwise
1493 **/
1494gboolean
1495fu_memcpy_safe (guint8 *dst, gsize dst_sz, gsize dst_offset,
1496 const guint8 *src, gsize src_sz, gsize src_offset,
1497 gsize n, GError **error)
1498{
1499 if (n == 0)
1500 return TRUE;
1501
1502 if (n > src_sz) {
1503 g_set_error (error,
1504 FWUPD_ERROR,
1505 FWUPD_ERROR_READ,
1506 "attempted to read 0x%02x bytes from buffer of 0x%02x",
1507 (guint) n, (guint) src_sz);
1508 return FALSE;
1509 }
1510 if (n + src_offset > src_sz) {
1511 g_set_error (error,
1512 FWUPD_ERROR,
1513 FWUPD_ERROR_READ,
1514 "attempted to read 0x%02x bytes at offset 0x%02x from buffer of 0x%02x",
1515 (guint) n, (guint) src_offset, (guint) src_sz);
1516 return FALSE;
1517 }
1518 if (n > dst_sz) {
1519 g_set_error (error,
1520 FWUPD_ERROR,
1521 FWUPD_ERROR_WRITE,
1522 "attempted to write 0x%02x bytes to buffer of 0x%02x",
1523 (guint) n, (guint) dst_sz);
1524 return FALSE;
1525 }
1526 if (n + dst_offset > dst_sz) {
1527 g_set_error (error,
1528 FWUPD_ERROR,
1529 FWUPD_ERROR_WRITE,
1530 "attempted to write 0x%02x bytes at offset 0x%02x to buffer of 0x%02x",
1531 (guint) n, (guint) dst_offset, (guint) dst_sz);
1532 return FALSE;
1533 }
1534
1535 /* phew! */
1536 memcpy (dst + dst_offset, src + src_offset, n);
1537 return TRUE;
1538}
Richard Hughes37c6a7b2019-08-14 21:57:43 +01001539
1540void
1541fu_byte_array_append_uint8 (GByteArray *array, guint8 data)
1542{
1543 g_byte_array_append (array, &data, sizeof(data));
1544}
1545
1546void
1547fu_byte_array_append_uint16 (GByteArray *array, guint16 data, FuEndianType endian)
1548{
1549 guint8 buf[2];
1550 fu_common_write_uint16 (buf, data, endian);
1551 g_byte_array_append (array, buf, sizeof(buf));
1552}
1553
1554void
1555fu_byte_array_append_uint32 (GByteArray *array, guint32 data, FuEndianType endian)
1556{
1557 guint8 buf[4];
1558 fu_common_write_uint32 (buf, data, endian);
1559 g_byte_array_append (array, buf, sizeof(buf));
1560}