blob: 718480972fb125460bb77f833d15278ec85bd25a [file] [log] [blame]
Richard Hughes943d2c92017-06-21 09:04:39 +01001/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
4 *
Mario Limonciello51308e62018-05-28 20:05:46 -05005 * SPDX-License-Identifier: LGPL-2.1+
Richard Hughes943d2c92017-06-21 09:04:39 +01006 */
7
8#include <config.h>
9
10#include <gio/gunixinputstream.h>
Richard Hughes954dd9f2017-08-08 13:36:25 +010011#include <glib/gstdio.h>
12
Richard Hughes94f939a2017-08-08 12:21:39 +010013#include <archive_entry.h>
14#include <archive.h>
Richard Hughes7ee42fe2017-08-15 14:06:21 +010015#include <errno.h>
Richard Hughesae252cd2017-12-08 10:48:15 +000016#include <string.h>
Richard Hughes943d2c92017-06-21 09:04:39 +010017
18#include "fwupd-error.h"
19
20#include "fu-common.h"
21
22/**
Richard Hughes4eada342017-10-03 21:20:32 +010023 * SECTION:fu-common
24 * @short_description: common functionality for plugins to use
25 *
26 * Helper functions that can be used by the daemon and plugins.
27 *
28 * See also: #FuPlugin
29 */
30
31/**
Richard Hughes954dd9f2017-08-08 13:36:25 +010032 * fu_common_rmtree:
33 * @directory: a directory name
34 * @error: A #GError or %NULL
35 *
36 * Recursively removes a directory.
37 *
38 * Returns: %TRUE for success, %FALSE otherwise
39 **/
40gboolean
41fu_common_rmtree (const gchar *directory, GError **error)
42{
43 const gchar *filename;
44 g_autoptr(GDir) dir = NULL;
45
46 /* try to open */
Richard Hughes455fdd32017-08-16 12:26:44 +010047 g_debug ("removing %s", directory);
Richard Hughes954dd9f2017-08-08 13:36:25 +010048 dir = g_dir_open (directory, 0, error);
49 if (dir == NULL)
50 return FALSE;
51
52 /* find each */
53 while ((filename = g_dir_read_name (dir))) {
54 g_autofree gchar *src = NULL;
55 src = g_build_filename (directory, filename, NULL);
56 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
57 if (!fu_common_rmtree (src, error))
58 return FALSE;
59 } else {
60 if (g_unlink (src) != 0) {
61 g_set_error (error,
62 FWUPD_ERROR,
63 FWUPD_ERROR_INTERNAL,
64 "Failed to delete: %s", src);
65 return FALSE;
66 }
67 }
68 }
69 if (g_remove (directory) != 0) {
70 g_set_error (error,
71 FWUPD_ERROR,
72 FWUPD_ERROR_INTERNAL,
73 "Failed to delete: %s", directory);
74 return FALSE;
75 }
76 return TRUE;
77}
78
Richard Hughes89e968b2018-03-07 10:01:08 +000079static gboolean
80fu_common_get_file_list_internal (GPtrArray *files, const gchar *directory, GError **error)
81{
82 const gchar *filename;
83 g_autoptr(GDir) dir = NULL;
84
85 /* try to open */
86 dir = g_dir_open (directory, 0, error);
87 if (dir == NULL)
88 return FALSE;
89
90 /* find each */
91 while ((filename = g_dir_read_name (dir))) {
92 g_autofree gchar *src = g_build_filename (directory, filename, NULL);
93 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
94 if (!fu_common_get_file_list_internal (files, src, error))
95 return FALSE;
96 } else {
97 g_ptr_array_add (files, g_steal_pointer (&src));
98 }
99 }
100 return TRUE;
101
102}
103
104/**
105 * fu_common_get_files_recursive:
Richard Hughes8aa72392018-05-02 08:38:43 +0100106 * @path: a directory name
Richard Hughes89e968b2018-03-07 10:01:08 +0000107 * @error: A #GError or %NULL
108 *
109 * Returns every file found under @directory, and any subdirectory.
110 * If any path under @directory cannot be accessed due to permissions an error
111 * will be returned.
112 *
113 * Returns: (element-type: utf8) (transfer container): array of files, or %NULL for error
114 **/
115GPtrArray *
116fu_common_get_files_recursive (const gchar *path, GError **error)
117{
118 g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free);
119 if (!fu_common_get_file_list_internal (files, path, error))
120 return NULL;
121 return g_steal_pointer (&files);
122}
Richard Hughes954dd9f2017-08-08 13:36:25 +0100123/**
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100124 * fu_common_mkdir_parent:
125 * @filename: A full pathname
126 * @error: A #GError, or %NULL
127 *
128 * Creates any required directories, including any parent directories.
129 *
130 * Returns: %TRUE for success
131 **/
132gboolean
133fu_common_mkdir_parent (const gchar *filename, GError **error)
134{
135 g_autofree gchar *parent = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100136
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100137 parent = g_path_get_dirname (filename);
Richard Hughes455fdd32017-08-16 12:26:44 +0100138 g_debug ("creating path %s", parent);
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100139 if (g_mkdir_with_parents (parent, 0755) == -1) {
140 g_set_error (error,
141 FWUPD_ERROR,
142 FWUPD_ERROR_INTERNAL,
143 "Failed to create '%s': %s",
144 parent, g_strerror (errno));
145 return FALSE;
146 }
147 return TRUE;
148}
149
150/**
Richard Hughes943d2c92017-06-21 09:04:39 +0100151 * fu_common_set_contents_bytes:
152 * @filename: A filename
153 * @bytes: The data to write
154 * @error: A #GError, or %NULL
155 *
156 * Writes a blob of data to a filename, creating the parent directories as
157 * required.
158 *
159 * Returns: %TRUE for success
160 **/
161gboolean
162fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error)
163{
164 const gchar *data;
165 gsize size;
166 g_autoptr(GFile) file = NULL;
167 g_autoptr(GFile) file_parent = NULL;
168
169 file = g_file_new_for_path (filename);
170 file_parent = g_file_get_parent (file);
171 if (!g_file_query_exists (file_parent, NULL)) {
172 if (!g_file_make_directory_with_parents (file_parent, NULL, error))
173 return FALSE;
174 }
175 data = g_bytes_get_data (bytes, &size);
Richard Hughes455fdd32017-08-16 12:26:44 +0100176 g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
Richard Hughes943d2c92017-06-21 09:04:39 +0100177 return g_file_set_contents (filename, data, size, error);
178}
179
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100180/**
181 * fu_common_get_contents_bytes:
182 * @filename: A filename
183 * @error: A #GError, or %NULL
184 *
185 * Reads a blob of data from a file.
186 *
187 * Returns: a #GBytes, or %NULL for failure
188 **/
189GBytes *
190fu_common_get_contents_bytes (const gchar *filename, GError **error)
191{
192 gchar *data = NULL;
193 gsize len = 0;
194 if (!g_file_get_contents (filename, &data, &len, error))
195 return NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100196 g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100197 return g_bytes_new_take (data, len);
198}
Richard Hughes943d2c92017-06-21 09:04:39 +0100199
200/**
201 * fu_common_get_contents_fd:
202 * @fd: A file descriptor
203 * @count: The maximum number of bytes to read
204 * @error: A #GError, or %NULL
205 *
206 * Reads a blob from a specific file descriptor.
207 *
208 * Note: this will close the fd when done
209 *
Richard Hughes4eada342017-10-03 21:20:32 +0100210 * Returns: (transfer full): a #GBytes, or %NULL
Richard Hughes943d2c92017-06-21 09:04:39 +0100211 **/
212GBytes *
213fu_common_get_contents_fd (gint fd, gsize count, GError **error)
214{
215 g_autoptr(GBytes) blob = NULL;
216 g_autoptr(GError) error_local = NULL;
217 g_autoptr(GInputStream) stream = NULL;
218
219 g_return_val_if_fail (fd > 0, NULL);
Richard Hughes943d2c92017-06-21 09:04:39 +0100220 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
221
Richard Hughes919f8ab2018-02-14 10:24:56 +0000222 /* this is invalid */
223 if (count == 0) {
224 g_set_error_literal (error,
225 FWUPD_ERROR,
226 FWUPD_ERROR_NOT_SUPPORTED,
227 "A maximum read size must be specified");
228 return NULL;
229 }
230
Richard Hughes943d2c92017-06-21 09:04:39 +0100231 /* read the entire fd to a data blob */
232 stream = g_unix_input_stream_new (fd, TRUE);
233 blob = g_input_stream_read_bytes (stream, count, NULL, &error_local);
234 if (blob == NULL) {
235 g_set_error_literal (error,
236 FWUPD_ERROR,
237 FWUPD_ERROR_INVALID_FILE,
238 error_local->message);
239 return NULL;
240 }
241 return g_steal_pointer (&blob);
242}
Richard Hughes94f939a2017-08-08 12:21:39 +0100243
244static gboolean
245fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir)
246{
247 const gchar *tmp;
248 g_autofree gchar *buf = NULL;
249
250 /* no output file */
251 if (archive_entry_pathname (entry) == NULL)
252 return FALSE;
253
254 /* update output path */
255 tmp = archive_entry_pathname (entry);
256 buf = g_build_filename (dir, tmp, NULL);
257 archive_entry_update_pathname_utf8 (entry, buf);
258 return TRUE;
259}
260
261/**
262 * fu_common_extract_archive:
263 * @blob: a #GBytes archive as a blob
Richard Hughes4eada342017-10-03 21:20:32 +0100264 * @dir: a directory name to extract to
Richard Hughes94f939a2017-08-08 12:21:39 +0100265 * @error: A #GError, or %NULL
266 *
267 * Extracts an achive to a directory.
268 *
269 * Returns: %TRUE for success
270 **/
271gboolean
272fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error)
273{
274 gboolean ret = TRUE;
275 int r;
276 struct archive *arch = NULL;
277 struct archive_entry *entry;
278
279 /* decompress anything matching either glob */
Richard Hughes455fdd32017-08-16 12:26:44 +0100280 g_debug ("decompressing into %s", dir);
Richard Hughes94f939a2017-08-08 12:21:39 +0100281 arch = archive_read_new ();
282 archive_read_support_format_all (arch);
283 archive_read_support_filter_all (arch);
284 r = archive_read_open_memory (arch,
285 (void *) g_bytes_get_data (blob, NULL),
286 (size_t) g_bytes_get_size (blob));
287 if (r != 0) {
288 ret = FALSE;
289 g_set_error (error,
290 FWUPD_ERROR,
291 FWUPD_ERROR_INTERNAL,
292 "Cannot open: %s",
293 archive_error_string (arch));
294 goto out;
295 }
296 for (;;) {
297 gboolean valid;
Richard Hughes94f939a2017-08-08 12:21:39 +0100298 r = archive_read_next_header (arch, &entry);
299 if (r == ARCHIVE_EOF)
300 break;
301 if (r != ARCHIVE_OK) {
302 ret = FALSE;
303 g_set_error (error,
304 FWUPD_ERROR,
305 FWUPD_ERROR_INTERNAL,
306 "Cannot read header: %s",
307 archive_error_string (arch));
308 goto out;
309 }
310
311 /* only extract if valid */
312 valid = fu_common_extract_archive_entry (entry, dir);
313 if (!valid)
314 continue;
315 r = archive_read_extract (arch, entry, 0);
316 if (r != ARCHIVE_OK) {
317 ret = FALSE;
318 g_set_error (error,
319 FWUPD_ERROR,
320 FWUPD_ERROR_INTERNAL,
321 "Cannot extract: %s",
322 archive_error_string (arch));
323 goto out;
324 }
325 }
326out:
327 if (arch != NULL) {
328 archive_read_close (arch);
329 archive_read_free (arch);
330 }
331 return ret;
332}
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100333
334static void
Yehezkel Bernate43f7fb2017-08-30 12:09:34 +0300335fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3);
336
337static void
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100338fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...)
339{
340 va_list args;
341 g_autofree gchar *tmp = NULL;
342 g_auto(GStrv) split = NULL;
343
344 va_start (args, fmt);
345 tmp = g_strdup_vprintf (fmt, args);
346 va_end (args);
347
348 split = g_strsplit (tmp, " ", -1);
349 for (guint i = 0; split[i] != NULL; i++)
350 g_ptr_array_add (argv, g_strdup (split[i]));
351}
352
353/**
354 * fu_common_firmware_builder:
355 * @bytes: The data to use
Richard Hughes4eada342017-10-03 21:20:32 +0100356 * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
357 * @output_fn: Name of the generated firmware, e.g. `firmware.bin`
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100358 * @error: A #GError, or %NULL
359 *
360 * Builds a firmware file using tools from the host session in a bubblewrap
361 * jail. Several things happen during build:
362 *
363 * 1. The @bytes data is untarred to a temporary location
364 * 2. A bubblewrap container is set up
365 * 3. The startup.sh script is run inside the container
366 * 4. The firmware.bin is extracted from the container
367 * 5. The temporary location is deleted
368 *
369 * Returns: a new #GBytes, or %NULL for error
370 **/
371GBytes *
372fu_common_firmware_builder (GBytes *bytes,
373 const gchar *script_fn,
374 const gchar *output_fn,
375 GError **error)
376{
377 gint rc = 0;
378 g_autofree gchar *argv_str = NULL;
379 g_autofree gchar *localstatedir = NULL;
380 g_autofree gchar *output2_fn = NULL;
381 g_autofree gchar *standard_error = NULL;
382 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100383 g_autofree gchar *tmpdir = NULL;
384 g_autoptr(GBytes) firmware_blob = NULL;
385 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
386
387 g_return_val_if_fail (bytes != NULL, NULL);
388 g_return_val_if_fail (script_fn != NULL, NULL);
389 g_return_val_if_fail (output_fn != NULL, NULL);
390 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
391
392 /* untar file to temp location */
393 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
394 if (tmpdir == NULL)
395 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100396 if (!fu_common_extract_archive (bytes, tmpdir, error))
397 return NULL;
398
399 /* this is shared with the plugins */
400 localstatedir = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", "builder", NULL);
401
402 /* launch bubblewrap and generate firmware */
Richard Hughesf6f72a42017-08-09 16:25:25 +0100403 g_ptr_array_add (argv, g_strdup ("bwrap"));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100404 fu_common_add_argv (argv, "--die-with-parent");
405 fu_common_add_argv (argv, "--ro-bind /usr /usr");
406 fu_common_add_argv (argv, "--dir /tmp");
407 fu_common_add_argv (argv, "--dir /var");
408 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
409 if (g_file_test (localstatedir, G_FILE_TEST_EXISTS))
410 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatedir);
411 fu_common_add_argv (argv, "--dev /dev");
412 fu_common_add_argv (argv, "--symlink usr/lib /lib");
413 fu_common_add_argv (argv, "--symlink usr/lib64 /lib64");
414 fu_common_add_argv (argv, "--symlink usr/bin /bin");
415 fu_common_add_argv (argv, "--symlink usr/sbin /sbin");
416 fu_common_add_argv (argv, "--chdir /tmp");
417 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100418 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100419 g_ptr_array_add (argv, NULL);
420 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
421 g_debug ("running '%s' in %s", argv_str, tmpdir);
422 if (!g_spawn_sync ("/tmp",
423 (gchar **) argv->pdata,
424 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100425 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100426 NULL, NULL, /* child_setup */
427 &standard_output,
428 &standard_error,
429 &rc,
430 error)) {
431 g_prefix_error (error, "failed to run '%s': ", argv_str);
432 return NULL;
433 }
434 if (standard_output != NULL && standard_output[0] != '\0')
435 g_debug ("console output was: %s", standard_output);
436 if (rc != 0) {
437 g_set_error (error,
438 FWUPD_ERROR,
439 FWUPD_ERROR_INTERNAL,
440 "failed to build firmware: %s",
441 standard_error);
442 return NULL;
443 }
444
445 /* get generated file */
446 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
447 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
448 if (firmware_blob == NULL)
449 return NULL;
450
451 /* cleanup temp directory */
452 if (!fu_common_rmtree (tmpdir, error))
453 return NULL;
454
455 /* success */
456 return g_steal_pointer (&firmware_blob);
457}
Richard Hughes049ccc82017-08-09 15:26:56 +0100458
459typedef struct {
460 FuOutputHandler handler_cb;
461 gpointer handler_user_data;
462 GMainLoop *loop;
463 GSource *source;
464 GInputStream *stream;
465 GCancellable *cancellable;
466} FuCommonSpawnHelper;
467
468static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
469
470static gboolean
471fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
472{
473 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
474 gchar buffer[1024];
475 gssize sz;
476 g_auto(GStrv) split = NULL;
477 g_autoptr(GError) error = NULL;
478
479 /* read from stream */
480 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
481 buffer,
482 sizeof(buffer) - 1,
483 NULL,
484 &error);
485 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100486 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
487 g_warning ("failed to get read from nonblocking fd: %s",
488 error->message);
489 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100490 return G_SOURCE_REMOVE;
491 }
492
493 /* no read possible */
494 if (sz == 0)
495 g_main_loop_quit (helper->loop);
496
497 /* emit lines */
498 if (helper->handler_cb != NULL) {
499 buffer[sz] = '\0';
500 split = g_strsplit (buffer, "\n", -1);
501 for (guint i = 0; split[i] != NULL; i++) {
502 if (split[i][0] == '\0')
503 continue;
504 helper->handler_cb (split[i], helper->handler_user_data);
505 }
506 }
507
508 /* set up the source for the next read */
509 fu_common_spawn_create_pollable_source (helper);
510 return G_SOURCE_REMOVE;
511}
512
513static void
514fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
515{
516 if (helper->source != NULL)
517 g_source_destroy (helper->source);
518 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
519 helper->cancellable);
520 g_source_attach (helper->source, NULL);
521 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
522}
523
524static void
525fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
526{
527 if (helper->stream != NULL)
528 g_object_unref (helper->stream);
529 if (helper->source != NULL)
530 g_source_destroy (helper->source);
531 if (helper->loop != NULL)
532 g_main_loop_unref (helper->loop);
533 g_free (helper);
534}
535
Mario Limoncielloa98df552018-04-16 12:15:51 -0500536#pragma clang diagnostic push
537#pragma clang diagnostic ignored "-Wunused-function"
Richard Hughes049ccc82017-08-09 15:26:56 +0100538G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
Mario Limoncielloa98df552018-04-16 12:15:51 -0500539#pragma clang diagnostic pop
Richard Hughes049ccc82017-08-09 15:26:56 +0100540
541/**
542 * fu_common_spawn_sync:
543 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100544 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
545 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughes049ccc82017-08-09 15:26:56 +0100546 * @cancellable: a #GCancellable, or %NULL
547 * @error: A #GError or %NULL
548 *
549 * Runs a subprocess and waits for it to exit. Any output on standard out or
550 * standard error will be forwarded to @handler_cb as whole lines.
551 *
552 * Returns: %TRUE for success
553 **/
554gboolean
555fu_common_spawn_sync (const gchar * const * argv,
556 FuOutputHandler handler_cb,
557 gpointer handler_user_data,
558 GCancellable *cancellable, GError **error)
559{
560 g_autoptr(FuCommonSpawnHelper) helper = NULL;
561 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100562 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100563
564 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100565 argv_str = g_strjoinv (" ", (gchar **) argv);
566 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100567 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
568 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
569 if (subprocess == NULL)
570 return FALSE;
571
572 /* watch for process to exit */
573 helper = g_new0 (FuCommonSpawnHelper, 1);
574 helper->handler_cb = handler_cb;
575 helper->handler_user_data = handler_user_data;
576 helper->loop = g_main_loop_new (NULL, FALSE);
577 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
578 helper->cancellable = cancellable;
579 fu_common_spawn_create_pollable_source (helper);
580 g_main_loop_run (helper->loop);
581 return g_subprocess_wait_check (subprocess, cancellable, error);
582}
Richard Hughesae252cd2017-12-08 10:48:15 +0000583
584/**
585 * fu_common_write_uint16:
586 * @buf: A writable buffer
587 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100588 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000589 *
590 * Writes a value to a buffer using a specified endian.
591 **/
592void
593fu_common_write_uint16 (guint8 *buf, guint16 val_native, FuEndianType endian)
594{
595 guint16 val_hw;
596 switch (endian) {
597 case G_BIG_ENDIAN:
598 val_hw = GUINT16_TO_BE(val_native);
599 break;
600 case G_LITTLE_ENDIAN:
601 val_hw = GUINT16_TO_LE(val_native);
602 break;
603 default:
604 g_assert_not_reached ();
605 }
606 memcpy (buf, &val_hw, sizeof(val_hw));
607}
608
609/**
610 * fu_common_write_uint32:
611 * @buf: A writable buffer
612 * @val_native: a value in host byte-order
Richard Hughes8aa72392018-05-02 08:38:43 +0100613 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000614 *
615 * Writes a value to a buffer using a specified endian.
616 **/
617void
618fu_common_write_uint32 (guint8 *buf, guint32 val_native, FuEndianType endian)
619{
620 guint32 val_hw;
621 switch (endian) {
622 case G_BIG_ENDIAN:
623 val_hw = GUINT32_TO_BE(val_native);
624 break;
625 case G_LITTLE_ENDIAN:
626 val_hw = GUINT32_TO_LE(val_native);
627 break;
628 default:
629 g_assert_not_reached ();
630 }
631 memcpy (buf, &val_hw, sizeof(val_hw));
632}
633
634/**
635 * fu_common_read_uint16:
636 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100637 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000638 *
639 * Read a value from a buffer using a specified endian.
640 *
641 * Returns: a value in host byte-order
642 **/
643guint16
644fu_common_read_uint16 (const guint8 *buf, FuEndianType endian)
645{
646 guint16 val_hw, val_native;
647 memcpy (&val_hw, buf, sizeof(val_hw));
648 switch (endian) {
649 case G_BIG_ENDIAN:
650 val_native = GUINT16_FROM_BE(val_hw);
651 break;
652 case G_LITTLE_ENDIAN:
653 val_native = GUINT16_FROM_LE(val_hw);
654 break;
655 default:
656 g_assert_not_reached ();
657 }
658 return val_native;
659}
660
661/**
662 * fu_common_read_uint32:
663 * @buf: A readable buffer
Richard Hughes8aa72392018-05-02 08:38:43 +0100664 * @endian: A #FuEndianType, e.g. %G_LITTLE_ENDIAN
Richard Hughesae252cd2017-12-08 10:48:15 +0000665 *
666 * Read a value from a buffer using a specified endian.
667 *
668 * Returns: a value in host byte-order
669 **/
670guint32
671fu_common_read_uint32 (const guint8 *buf, FuEndianType endian)
672{
673 guint32 val_hw, val_native;
674 memcpy (&val_hw, buf, sizeof(val_hw));
675 switch (endian) {
676 case G_BIG_ENDIAN:
677 val_native = GUINT32_FROM_BE(val_hw);
678 break;
679 case G_LITTLE_ENDIAN:
680 val_native = GUINT32_FROM_LE(val_hw);
681 break;
682 default:
683 g_assert_not_reached ();
684 }
685 return val_native;
686}
Richard Hughese82eef32018-05-20 10:41:26 +0100687
688static const GError *
689fu_common_error_array_find (GPtrArray *errors, FwupdError error_code)
690{
691 for (guint j = 0; j < errors->len; j++) {
692 const GError *error = g_ptr_array_index (errors, j);
693 if (g_error_matches (error, FWUPD_ERROR, error_code))
694 return error;
695 }
696 return NULL;
697}
698
699static guint
700fu_common_error_array_count (GPtrArray *errors, FwupdError error_code)
701{
702 guint cnt = 0;
703 for (guint j = 0; j < errors->len; j++) {
704 const GError *error = g_ptr_array_index (errors, j);
705 if (g_error_matches (error, FWUPD_ERROR, error_code))
706 cnt++;
707 }
708 return cnt;
709}
710
711static gboolean
712fu_common_error_array_matches_any (GPtrArray *errors, FwupdError *error_codes)
713{
714 for (guint j = 0; j < errors->len; j++) {
715 const GError *error = g_ptr_array_index (errors, j);
716 gboolean matches_any = FALSE;
717 for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
718 if (g_error_matches (error, FWUPD_ERROR, error_codes[i])) {
719 matches_any = TRUE;
720 break;
721 }
722 }
723 if (!matches_any)
724 return FALSE;
725 }
726 return TRUE;
727}
728
729/**
730 * fu_common_error_array_get_best:
731 * @errors: (element-type GError): array of errors
732 *
733 * Finds the 'best' error to show the user from a array of errors, creating a
734 * completely bespoke error where required.
735 *
736 * Returns: (transfer full): a #GError, never %NULL
737 **/
738GError *
739fu_common_error_array_get_best (GPtrArray *errors)
740{
741 FwupdError err_prio[] = { FWUPD_ERROR_INVALID_FILE,
742 FWUPD_ERROR_VERSION_SAME,
743 FWUPD_ERROR_VERSION_NEWER,
744 FWUPD_ERROR_NOT_SUPPORTED,
745 FWUPD_ERROR_INTERNAL,
746 FWUPD_ERROR_NOT_FOUND,
747 FWUPD_ERROR_LAST };
748 FwupdError err_all_uptodate[] = { FWUPD_ERROR_VERSION_SAME,
749 FWUPD_ERROR_NOT_FOUND,
750 FWUPD_ERROR_NOT_SUPPORTED,
751 FWUPD_ERROR_LAST };
752 FwupdError err_all_newer[] = { FWUPD_ERROR_VERSION_NEWER,
753 FWUPD_ERROR_VERSION_SAME,
754 FWUPD_ERROR_NOT_FOUND,
755 FWUPD_ERROR_NOT_SUPPORTED,
756 FWUPD_ERROR_LAST };
757
758 /* are all the errors either GUID-not-matched or version-same? */
759 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
760 fu_common_error_array_matches_any (errors, err_all_uptodate)) {
761 return g_error_new (FWUPD_ERROR,
762 FWUPD_ERROR_NOTHING_TO_DO,
763 "All updatable firmware is already installed");
764 }
765
766 /* are all the errors either GUID-not-matched or version same or newer? */
767 if (fu_common_error_array_count (errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
768 fu_common_error_array_matches_any (errors, err_all_newer)) {
769 return g_error_new (FWUPD_ERROR,
770 FWUPD_ERROR_NOTHING_TO_DO,
771 "All updatable devices already have newer versions");
772 }
773
774 /* get the most important single error */
775 for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
776 const GError *error_tmp = fu_common_error_array_find (errors, err_prio[i]);
777 if (error_tmp != NULL)
778 return g_error_copy (error_tmp);
779 }
780
781 /* fall back to something */
782 return g_error_new (FWUPD_ERROR,
783 FWUPD_ERROR_NOT_FOUND,
784 "No supported devices found");
785}