blob: cb2d24f86f055ffa5806fb4ef76008fefc36b74d [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 *
5 * Licensed under the GNU General Public License Version 2
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22#include <config.h>
23
24#include <gio/gunixinputstream.h>
Richard Hughes954dd9f2017-08-08 13:36:25 +010025#include <glib/gstdio.h>
26
Richard Hughes94f939a2017-08-08 12:21:39 +010027#include <archive_entry.h>
28#include <archive.h>
Richard Hughes7ee42fe2017-08-15 14:06:21 +010029#include <errno.h>
Richard Hughes943d2c92017-06-21 09:04:39 +010030
31#include "fwupd-error.h"
32
33#include "fu-common.h"
34
35/**
Richard Hughes4eada342017-10-03 21:20:32 +010036 * SECTION:fu-common
37 * @short_description: common functionality for plugins to use
38 *
39 * Helper functions that can be used by the daemon and plugins.
40 *
41 * See also: #FuPlugin
42 */
43
44/**
Richard Hughes954dd9f2017-08-08 13:36:25 +010045 * fu_common_rmtree:
46 * @directory: a directory name
47 * @error: A #GError or %NULL
48 *
49 * Recursively removes a directory.
50 *
51 * Returns: %TRUE for success, %FALSE otherwise
52 **/
53gboolean
54fu_common_rmtree (const gchar *directory, GError **error)
55{
56 const gchar *filename;
57 g_autoptr(GDir) dir = NULL;
58
59 /* try to open */
Richard Hughes455fdd32017-08-16 12:26:44 +010060 g_debug ("removing %s", directory);
Richard Hughes954dd9f2017-08-08 13:36:25 +010061 dir = g_dir_open (directory, 0, error);
62 if (dir == NULL)
63 return FALSE;
64
65 /* find each */
66 while ((filename = g_dir_read_name (dir))) {
67 g_autofree gchar *src = NULL;
68 src = g_build_filename (directory, filename, NULL);
69 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
70 if (!fu_common_rmtree (src, error))
71 return FALSE;
72 } else {
73 if (g_unlink (src) != 0) {
74 g_set_error (error,
75 FWUPD_ERROR,
76 FWUPD_ERROR_INTERNAL,
77 "Failed to delete: %s", src);
78 return FALSE;
79 }
80 }
81 }
82 if (g_remove (directory) != 0) {
83 g_set_error (error,
84 FWUPD_ERROR,
85 FWUPD_ERROR_INTERNAL,
86 "Failed to delete: %s", directory);
87 return FALSE;
88 }
89 return TRUE;
90}
91
92/**
Richard Hughes7ee42fe2017-08-15 14:06:21 +010093 * fu_common_mkdir_parent:
94 * @filename: A full pathname
95 * @error: A #GError, or %NULL
96 *
97 * Creates any required directories, including any parent directories.
98 *
99 * Returns: %TRUE for success
100 **/
101gboolean
102fu_common_mkdir_parent (const gchar *filename, GError **error)
103{
104 g_autofree gchar *parent = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100105
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100106 parent = g_path_get_dirname (filename);
Richard Hughes455fdd32017-08-16 12:26:44 +0100107 g_debug ("creating path %s", parent);
Richard Hughes7ee42fe2017-08-15 14:06:21 +0100108 if (g_mkdir_with_parents (parent, 0755) == -1) {
109 g_set_error (error,
110 FWUPD_ERROR,
111 FWUPD_ERROR_INTERNAL,
112 "Failed to create '%s': %s",
113 parent, g_strerror (errno));
114 return FALSE;
115 }
116 return TRUE;
117}
118
119/**
Richard Hughes943d2c92017-06-21 09:04:39 +0100120 * fu_common_set_contents_bytes:
121 * @filename: A filename
122 * @bytes: The data to write
123 * @error: A #GError, or %NULL
124 *
125 * Writes a blob of data to a filename, creating the parent directories as
126 * required.
127 *
128 * Returns: %TRUE for success
129 **/
130gboolean
131fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error)
132{
133 const gchar *data;
134 gsize size;
135 g_autoptr(GFile) file = NULL;
136 g_autoptr(GFile) file_parent = NULL;
137
138 file = g_file_new_for_path (filename);
139 file_parent = g_file_get_parent (file);
140 if (!g_file_query_exists (file_parent, NULL)) {
141 if (!g_file_make_directory_with_parents (file_parent, NULL, error))
142 return FALSE;
143 }
144 data = g_bytes_get_data (bytes, &size);
Richard Hughes455fdd32017-08-16 12:26:44 +0100145 g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
Richard Hughes943d2c92017-06-21 09:04:39 +0100146 return g_file_set_contents (filename, data, size, error);
147}
148
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100149/**
150 * fu_common_get_contents_bytes:
151 * @filename: A filename
152 * @error: A #GError, or %NULL
153 *
154 * Reads a blob of data from a file.
155 *
156 * Returns: a #GBytes, or %NULL for failure
157 **/
158GBytes *
159fu_common_get_contents_bytes (const gchar *filename, GError **error)
160{
161 gchar *data = NULL;
162 gsize len = 0;
163 if (!g_file_get_contents (filename, &data, &len, error))
164 return NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100165 g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100166 return g_bytes_new_take (data, len);
167}
Richard Hughes943d2c92017-06-21 09:04:39 +0100168
169/**
170 * fu_common_get_contents_fd:
171 * @fd: A file descriptor
172 * @count: The maximum number of bytes to read
173 * @error: A #GError, or %NULL
174 *
175 * Reads a blob from a specific file descriptor.
176 *
177 * Note: this will close the fd when done
178 *
Richard Hughes4eada342017-10-03 21:20:32 +0100179 * Returns: (transfer full): a #GBytes, or %NULL
Richard Hughes943d2c92017-06-21 09:04:39 +0100180 **/
181GBytes *
182fu_common_get_contents_fd (gint fd, gsize count, GError **error)
183{
184 g_autoptr(GBytes) blob = NULL;
185 g_autoptr(GError) error_local = NULL;
186 g_autoptr(GInputStream) stream = NULL;
187
188 g_return_val_if_fail (fd > 0, NULL);
189 g_return_val_if_fail (count > 0, NULL);
190 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
191
192 /* read the entire fd to a data blob */
193 stream = g_unix_input_stream_new (fd, TRUE);
194 blob = g_input_stream_read_bytes (stream, count, NULL, &error_local);
195 if (blob == NULL) {
196 g_set_error_literal (error,
197 FWUPD_ERROR,
198 FWUPD_ERROR_INVALID_FILE,
199 error_local->message);
200 return NULL;
201 }
202 return g_steal_pointer (&blob);
203}
Richard Hughes94f939a2017-08-08 12:21:39 +0100204
205static gboolean
206fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir)
207{
208 const gchar *tmp;
209 g_autofree gchar *buf = NULL;
210
211 /* no output file */
212 if (archive_entry_pathname (entry) == NULL)
213 return FALSE;
214
215 /* update output path */
216 tmp = archive_entry_pathname (entry);
217 buf = g_build_filename (dir, tmp, NULL);
218 archive_entry_update_pathname_utf8 (entry, buf);
219 return TRUE;
220}
221
222/**
223 * fu_common_extract_archive:
224 * @blob: a #GBytes archive as a blob
Richard Hughes4eada342017-10-03 21:20:32 +0100225 * @dir: a directory name to extract to
Richard Hughes94f939a2017-08-08 12:21:39 +0100226 * @error: A #GError, or %NULL
227 *
228 * Extracts an achive to a directory.
229 *
230 * Returns: %TRUE for success
231 **/
232gboolean
233fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error)
234{
235 gboolean ret = TRUE;
236 int r;
237 struct archive *arch = NULL;
238 struct archive_entry *entry;
239
240 /* decompress anything matching either glob */
Richard Hughes455fdd32017-08-16 12:26:44 +0100241 g_debug ("decompressing into %s", dir);
Richard Hughes94f939a2017-08-08 12:21:39 +0100242 arch = archive_read_new ();
243 archive_read_support_format_all (arch);
244 archive_read_support_filter_all (arch);
245 r = archive_read_open_memory (arch,
246 (void *) g_bytes_get_data (blob, NULL),
247 (size_t) g_bytes_get_size (blob));
248 if (r != 0) {
249 ret = FALSE;
250 g_set_error (error,
251 FWUPD_ERROR,
252 FWUPD_ERROR_INTERNAL,
253 "Cannot open: %s",
254 archive_error_string (arch));
255 goto out;
256 }
257 for (;;) {
258 gboolean valid;
Richard Hughes94f939a2017-08-08 12:21:39 +0100259 r = archive_read_next_header (arch, &entry);
260 if (r == ARCHIVE_EOF)
261 break;
262 if (r != ARCHIVE_OK) {
263 ret = FALSE;
264 g_set_error (error,
265 FWUPD_ERROR,
266 FWUPD_ERROR_INTERNAL,
267 "Cannot read header: %s",
268 archive_error_string (arch));
269 goto out;
270 }
271
272 /* only extract if valid */
273 valid = fu_common_extract_archive_entry (entry, dir);
274 if (!valid)
275 continue;
276 r = archive_read_extract (arch, entry, 0);
277 if (r != ARCHIVE_OK) {
278 ret = FALSE;
279 g_set_error (error,
280 FWUPD_ERROR,
281 FWUPD_ERROR_INTERNAL,
282 "Cannot extract: %s",
283 archive_error_string (arch));
284 goto out;
285 }
286 }
287out:
288 if (arch != NULL) {
289 archive_read_close (arch);
290 archive_read_free (arch);
291 }
292 return ret;
293}
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100294
295static void
Yehezkel Bernate43f7fb2017-08-30 12:09:34 +0300296fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3);
297
298static void
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100299fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...)
300{
301 va_list args;
302 g_autofree gchar *tmp = NULL;
303 g_auto(GStrv) split = NULL;
304
305 va_start (args, fmt);
306 tmp = g_strdup_vprintf (fmt, args);
307 va_end (args);
308
309 split = g_strsplit (tmp, " ", -1);
310 for (guint i = 0; split[i] != NULL; i++)
311 g_ptr_array_add (argv, g_strdup (split[i]));
312}
313
314/**
315 * fu_common_firmware_builder:
316 * @bytes: The data to use
Richard Hughes4eada342017-10-03 21:20:32 +0100317 * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
318 * @output_fn: Name of the generated firmware, e.g. `firmware.bin`
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100319 * @error: A #GError, or %NULL
320 *
321 * Builds a firmware file using tools from the host session in a bubblewrap
322 * jail. Several things happen during build:
323 *
324 * 1. The @bytes data is untarred to a temporary location
325 * 2. A bubblewrap container is set up
326 * 3. The startup.sh script is run inside the container
327 * 4. The firmware.bin is extracted from the container
328 * 5. The temporary location is deleted
329 *
330 * Returns: a new #GBytes, or %NULL for error
331 **/
332GBytes *
333fu_common_firmware_builder (GBytes *bytes,
334 const gchar *script_fn,
335 const gchar *output_fn,
336 GError **error)
337{
338 gint rc = 0;
339 g_autofree gchar *argv_str = NULL;
340 g_autofree gchar *localstatedir = NULL;
341 g_autofree gchar *output2_fn = NULL;
342 g_autofree gchar *standard_error = NULL;
343 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100344 g_autofree gchar *tmpdir = NULL;
345 g_autoptr(GBytes) firmware_blob = NULL;
346 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
347
348 g_return_val_if_fail (bytes != NULL, NULL);
349 g_return_val_if_fail (script_fn != NULL, NULL);
350 g_return_val_if_fail (output_fn != NULL, NULL);
351 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
352
353 /* untar file to temp location */
354 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
355 if (tmpdir == NULL)
356 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100357 if (!fu_common_extract_archive (bytes, tmpdir, error))
358 return NULL;
359
360 /* this is shared with the plugins */
361 localstatedir = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", "builder", NULL);
362
363 /* launch bubblewrap and generate firmware */
Richard Hughesf6f72a42017-08-09 16:25:25 +0100364 g_ptr_array_add (argv, g_strdup ("bwrap"));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100365 fu_common_add_argv (argv, "--die-with-parent");
366 fu_common_add_argv (argv, "--ro-bind /usr /usr");
367 fu_common_add_argv (argv, "--dir /tmp");
368 fu_common_add_argv (argv, "--dir /var");
369 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
370 if (g_file_test (localstatedir, G_FILE_TEST_EXISTS))
371 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatedir);
372 fu_common_add_argv (argv, "--dev /dev");
373 fu_common_add_argv (argv, "--symlink usr/lib /lib");
374 fu_common_add_argv (argv, "--symlink usr/lib64 /lib64");
375 fu_common_add_argv (argv, "--symlink usr/bin /bin");
376 fu_common_add_argv (argv, "--symlink usr/sbin /sbin");
377 fu_common_add_argv (argv, "--chdir /tmp");
378 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100379 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100380 g_ptr_array_add (argv, NULL);
381 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
382 g_debug ("running '%s' in %s", argv_str, tmpdir);
383 if (!g_spawn_sync ("/tmp",
384 (gchar **) argv->pdata,
385 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100386 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100387 NULL, NULL, /* child_setup */
388 &standard_output,
389 &standard_error,
390 &rc,
391 error)) {
392 g_prefix_error (error, "failed to run '%s': ", argv_str);
393 return NULL;
394 }
395 if (standard_output != NULL && standard_output[0] != '\0')
396 g_debug ("console output was: %s", standard_output);
397 if (rc != 0) {
398 g_set_error (error,
399 FWUPD_ERROR,
400 FWUPD_ERROR_INTERNAL,
401 "failed to build firmware: %s",
402 standard_error);
403 return NULL;
404 }
405
406 /* get generated file */
407 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
408 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
409 if (firmware_blob == NULL)
410 return NULL;
411
412 /* cleanup temp directory */
413 if (!fu_common_rmtree (tmpdir, error))
414 return NULL;
415
416 /* success */
417 return g_steal_pointer (&firmware_blob);
418}
Richard Hughes049ccc82017-08-09 15:26:56 +0100419
420typedef struct {
421 FuOutputHandler handler_cb;
422 gpointer handler_user_data;
423 GMainLoop *loop;
424 GSource *source;
425 GInputStream *stream;
426 GCancellable *cancellable;
427} FuCommonSpawnHelper;
428
429static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
430
431static gboolean
432fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
433{
434 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
435 gchar buffer[1024];
436 gssize sz;
437 g_auto(GStrv) split = NULL;
438 g_autoptr(GError) error = NULL;
439
440 /* read from stream */
441 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
442 buffer,
443 sizeof(buffer) - 1,
444 NULL,
445 &error);
446 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100447 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
448 g_warning ("failed to get read from nonblocking fd: %s",
449 error->message);
450 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100451 return G_SOURCE_REMOVE;
452 }
453
454 /* no read possible */
455 if (sz == 0)
456 g_main_loop_quit (helper->loop);
457
458 /* emit lines */
459 if (helper->handler_cb != NULL) {
460 buffer[sz] = '\0';
461 split = g_strsplit (buffer, "\n", -1);
462 for (guint i = 0; split[i] != NULL; i++) {
463 if (split[i][0] == '\0')
464 continue;
465 helper->handler_cb (split[i], helper->handler_user_data);
466 }
467 }
468
469 /* set up the source for the next read */
470 fu_common_spawn_create_pollable_source (helper);
471 return G_SOURCE_REMOVE;
472}
473
474static void
475fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
476{
477 if (helper->source != NULL)
478 g_source_destroy (helper->source);
479 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
480 helper->cancellable);
481 g_source_attach (helper->source, NULL);
482 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
483}
484
485static void
486fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
487{
488 if (helper->stream != NULL)
489 g_object_unref (helper->stream);
490 if (helper->source != NULL)
491 g_source_destroy (helper->source);
492 if (helper->loop != NULL)
493 g_main_loop_unref (helper->loop);
494 g_free (helper);
495}
496
497G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
498
499/**
500 * fu_common_spawn_sync:
501 * @argv: The argument list to run
Richard Hughes4eada342017-10-03 21:20:32 +0100502 * @handler_cb: (scope call): A #FuOutputHandler or %NULL
503 * @handler_user_data: the user data to pass to @handler_cb
Richard Hughes049ccc82017-08-09 15:26:56 +0100504 * @cancellable: a #GCancellable, or %NULL
505 * @error: A #GError or %NULL
506 *
507 * Runs a subprocess and waits for it to exit. Any output on standard out or
508 * standard error will be forwarded to @handler_cb as whole lines.
509 *
510 * Returns: %TRUE for success
511 **/
512gboolean
513fu_common_spawn_sync (const gchar * const * argv,
514 FuOutputHandler handler_cb,
515 gpointer handler_user_data,
516 GCancellable *cancellable, GError **error)
517{
518 g_autoptr(FuCommonSpawnHelper) helper = NULL;
519 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100520 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100521
522 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100523 argv_str = g_strjoinv (" ", (gchar **) argv);
524 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100525 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
526 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
527 if (subprocess == NULL)
528 return FALSE;
529
530 /* watch for process to exit */
531 helper = g_new0 (FuCommonSpawnHelper, 1);
532 helper->handler_cb = handler_cb;
533 helper->handler_user_data = handler_user_data;
534 helper->loop = g_main_loop_new (NULL, FALSE);
535 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
536 helper->cancellable = cancellable;
537 fu_common_spawn_create_pollable_source (helper);
538 g_main_loop_run (helper->loop);
539 return g_subprocess_wait_check (subprocess, cancellable, error);
540}