blob: 6a63719ee0d6b743e4ce8503b7fe4084a4880122 [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 Hughes954dd9f2017-08-08 13:36:25 +010036 * fu_common_rmtree:
37 * @directory: a directory name
38 * @error: A #GError or %NULL
39 *
40 * Recursively removes a directory.
41 *
42 * Returns: %TRUE for success, %FALSE otherwise
43 **/
44gboolean
45fu_common_rmtree (const gchar *directory, GError **error)
46{
47 const gchar *filename;
48 g_autoptr(GDir) dir = NULL;
49
50 /* try to open */
Richard Hughes455fdd32017-08-16 12:26:44 +010051 g_debug ("removing %s", directory);
Richard Hughes954dd9f2017-08-08 13:36:25 +010052 dir = g_dir_open (directory, 0, error);
53 if (dir == NULL)
54 return FALSE;
55
56 /* find each */
57 while ((filename = g_dir_read_name (dir))) {
58 g_autofree gchar *src = NULL;
59 src = g_build_filename (directory, filename, NULL);
60 if (g_file_test (src, G_FILE_TEST_IS_DIR)) {
61 if (!fu_common_rmtree (src, error))
62 return FALSE;
63 } else {
64 if (g_unlink (src) != 0) {
65 g_set_error (error,
66 FWUPD_ERROR,
67 FWUPD_ERROR_INTERNAL,
68 "Failed to delete: %s", src);
69 return FALSE;
70 }
71 }
72 }
73 if (g_remove (directory) != 0) {
74 g_set_error (error,
75 FWUPD_ERROR,
76 FWUPD_ERROR_INTERNAL,
77 "Failed to delete: %s", directory);
78 return FALSE;
79 }
80 return TRUE;
81}
82
83/**
Richard Hughes7ee42fe2017-08-15 14:06:21 +010084 * fu_common_mkdir_parent:
85 * @filename: A full pathname
86 * @error: A #GError, or %NULL
87 *
88 * Creates any required directories, including any parent directories.
89 *
90 * Returns: %TRUE for success
91 **/
92gboolean
93fu_common_mkdir_parent (const gchar *filename, GError **error)
94{
95 g_autofree gchar *parent = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +010096
Richard Hughes7ee42fe2017-08-15 14:06:21 +010097 parent = g_path_get_dirname (filename);
Richard Hughes455fdd32017-08-16 12:26:44 +010098 g_debug ("creating path %s", parent);
Richard Hughes7ee42fe2017-08-15 14:06:21 +010099 if (g_mkdir_with_parents (parent, 0755) == -1) {
100 g_set_error (error,
101 FWUPD_ERROR,
102 FWUPD_ERROR_INTERNAL,
103 "Failed to create '%s': %s",
104 parent, g_strerror (errno));
105 return FALSE;
106 }
107 return TRUE;
108}
109
110/**
Richard Hughes943d2c92017-06-21 09:04:39 +0100111 * fu_common_set_contents_bytes:
112 * @filename: A filename
113 * @bytes: The data to write
114 * @error: A #GError, or %NULL
115 *
116 * Writes a blob of data to a filename, creating the parent directories as
117 * required.
118 *
119 * Returns: %TRUE for success
120 **/
121gboolean
122fu_common_set_contents_bytes (const gchar *filename, GBytes *bytes, GError **error)
123{
124 const gchar *data;
125 gsize size;
126 g_autoptr(GFile) file = NULL;
127 g_autoptr(GFile) file_parent = NULL;
128
129 file = g_file_new_for_path (filename);
130 file_parent = g_file_get_parent (file);
131 if (!g_file_query_exists (file_parent, NULL)) {
132 if (!g_file_make_directory_with_parents (file_parent, NULL, error))
133 return FALSE;
134 }
135 data = g_bytes_get_data (bytes, &size);
Richard Hughes455fdd32017-08-16 12:26:44 +0100136 g_debug ("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
Richard Hughes943d2c92017-06-21 09:04:39 +0100137 return g_file_set_contents (filename, data, size, error);
138}
139
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100140/**
141 * fu_common_get_contents_bytes:
142 * @filename: A filename
143 * @error: A #GError, or %NULL
144 *
145 * Reads a blob of data from a file.
146 *
147 * Returns: a #GBytes, or %NULL for failure
148 **/
149GBytes *
150fu_common_get_contents_bytes (const gchar *filename, GError **error)
151{
152 gchar *data = NULL;
153 gsize len = 0;
154 if (!g_file_get_contents (filename, &data, &len, error))
155 return NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100156 g_debug ("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
Richard Hughesd0d2ae62017-08-08 12:22:30 +0100157 return g_bytes_new_take (data, len);
158}
Richard Hughes943d2c92017-06-21 09:04:39 +0100159
160/**
161 * fu_common_get_contents_fd:
162 * @fd: A file descriptor
163 * @count: The maximum number of bytes to read
164 * @error: A #GError, or %NULL
165 *
166 * Reads a blob from a specific file descriptor.
167 *
168 * Note: this will close the fd when done
169 *
170 * Returns: (transfer container): a #GBytes, or %NULL
171 **/
172GBytes *
173fu_common_get_contents_fd (gint fd, gsize count, GError **error)
174{
175 g_autoptr(GBytes) blob = NULL;
176 g_autoptr(GError) error_local = NULL;
177 g_autoptr(GInputStream) stream = NULL;
178
179 g_return_val_if_fail (fd > 0, NULL);
180 g_return_val_if_fail (count > 0, NULL);
181 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
182
183 /* read the entire fd to a data blob */
184 stream = g_unix_input_stream_new (fd, TRUE);
185 blob = g_input_stream_read_bytes (stream, count, NULL, &error_local);
186 if (blob == NULL) {
187 g_set_error_literal (error,
188 FWUPD_ERROR,
189 FWUPD_ERROR_INVALID_FILE,
190 error_local->message);
191 return NULL;
192 }
193 return g_steal_pointer (&blob);
194}
Richard Hughes94f939a2017-08-08 12:21:39 +0100195
196static gboolean
197fu_common_extract_archive_entry (struct archive_entry *entry, const gchar *dir)
198{
199 const gchar *tmp;
200 g_autofree gchar *buf = NULL;
201
202 /* no output file */
203 if (archive_entry_pathname (entry) == NULL)
204 return FALSE;
205
206 /* update output path */
207 tmp = archive_entry_pathname (entry);
208 buf = g_build_filename (dir, tmp, NULL);
209 archive_entry_update_pathname_utf8 (entry, buf);
210 return TRUE;
211}
212
213/**
214 * fu_common_extract_archive:
215 * @blob: a #GBytes archive as a blob
216 * @directory: a directory name to extract to
217 * @error: A #GError, or %NULL
218 *
219 * Extracts an achive to a directory.
220 *
221 * Returns: %TRUE for success
222 **/
223gboolean
224fu_common_extract_archive (GBytes *blob, const gchar *dir, GError **error)
225{
226 gboolean ret = TRUE;
227 int r;
228 struct archive *arch = NULL;
229 struct archive_entry *entry;
230
231 /* decompress anything matching either glob */
Richard Hughes455fdd32017-08-16 12:26:44 +0100232 g_debug ("decompressing into %s", dir);
Richard Hughes94f939a2017-08-08 12:21:39 +0100233 arch = archive_read_new ();
234 archive_read_support_format_all (arch);
235 archive_read_support_filter_all (arch);
236 r = archive_read_open_memory (arch,
237 (void *) g_bytes_get_data (blob, NULL),
238 (size_t) g_bytes_get_size (blob));
239 if (r != 0) {
240 ret = FALSE;
241 g_set_error (error,
242 FWUPD_ERROR,
243 FWUPD_ERROR_INTERNAL,
244 "Cannot open: %s",
245 archive_error_string (arch));
246 goto out;
247 }
248 for (;;) {
249 gboolean valid;
Richard Hughes94f939a2017-08-08 12:21:39 +0100250 r = archive_read_next_header (arch, &entry);
251 if (r == ARCHIVE_EOF)
252 break;
253 if (r != ARCHIVE_OK) {
254 ret = FALSE;
255 g_set_error (error,
256 FWUPD_ERROR,
257 FWUPD_ERROR_INTERNAL,
258 "Cannot read header: %s",
259 archive_error_string (arch));
260 goto out;
261 }
262
263 /* only extract if valid */
264 valid = fu_common_extract_archive_entry (entry, dir);
265 if (!valid)
266 continue;
267 r = archive_read_extract (arch, entry, 0);
268 if (r != ARCHIVE_OK) {
269 ret = FALSE;
270 g_set_error (error,
271 FWUPD_ERROR,
272 FWUPD_ERROR_INTERNAL,
273 "Cannot extract: %s",
274 archive_error_string (arch));
275 goto out;
276 }
277 }
278out:
279 if (arch != NULL) {
280 archive_read_close (arch);
281 archive_read_free (arch);
282 }
283 return ret;
284}
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100285
286static void
Yehezkel Bernate43f7fb2017-08-30 12:09:34 +0300287fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3);
288
289static void
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100290fu_common_add_argv (GPtrArray *argv, const gchar *fmt, ...)
291{
292 va_list args;
293 g_autofree gchar *tmp = NULL;
294 g_auto(GStrv) split = NULL;
295
296 va_start (args, fmt);
297 tmp = g_strdup_vprintf (fmt, args);
298 va_end (args);
299
300 split = g_strsplit (tmp, " ", -1);
301 for (guint i = 0; split[i] != NULL; i++)
302 g_ptr_array_add (argv, g_strdup (split[i]));
303}
304
305/**
306 * fu_common_firmware_builder:
307 * @bytes: The data to use
308 * @script_fn: Name of the script to run in the tarball, e.g. "startup.sh"
309 * @output_fn: Name of the generated firmware, e.g. "firmware.bin"
310 * @error: A #GError, or %NULL
311 *
312 * Builds a firmware file using tools from the host session in a bubblewrap
313 * jail. Several things happen during build:
314 *
315 * 1. The @bytes data is untarred to a temporary location
316 * 2. A bubblewrap container is set up
317 * 3. The startup.sh script is run inside the container
318 * 4. The firmware.bin is extracted from the container
319 * 5. The temporary location is deleted
320 *
321 * Returns: a new #GBytes, or %NULL for error
322 **/
323GBytes *
324fu_common_firmware_builder (GBytes *bytes,
325 const gchar *script_fn,
326 const gchar *output_fn,
327 GError **error)
328{
329 gint rc = 0;
330 g_autofree gchar *argv_str = NULL;
331 g_autofree gchar *localstatedir = NULL;
332 g_autofree gchar *output2_fn = NULL;
333 g_autofree gchar *standard_error = NULL;
334 g_autofree gchar *standard_output = NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100335 g_autofree gchar *tmpdir = NULL;
336 g_autoptr(GBytes) firmware_blob = NULL;
337 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
338
339 g_return_val_if_fail (bytes != NULL, NULL);
340 g_return_val_if_fail (script_fn != NULL, NULL);
341 g_return_val_if_fail (output_fn != NULL, NULL);
342 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
343
344 /* untar file to temp location */
345 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
346 if (tmpdir == NULL)
347 return NULL;
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100348 if (!fu_common_extract_archive (bytes, tmpdir, error))
349 return NULL;
350
351 /* this is shared with the plugins */
352 localstatedir = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", "builder", NULL);
353
354 /* launch bubblewrap and generate firmware */
Richard Hughesf6f72a42017-08-09 16:25:25 +0100355 g_ptr_array_add (argv, g_strdup ("bwrap"));
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100356 fu_common_add_argv (argv, "--die-with-parent");
357 fu_common_add_argv (argv, "--ro-bind /usr /usr");
358 fu_common_add_argv (argv, "--dir /tmp");
359 fu_common_add_argv (argv, "--dir /var");
360 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
361 if (g_file_test (localstatedir, G_FILE_TEST_EXISTS))
362 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatedir);
363 fu_common_add_argv (argv, "--dev /dev");
364 fu_common_add_argv (argv, "--symlink usr/lib /lib");
365 fu_common_add_argv (argv, "--symlink usr/lib64 /lib64");
366 fu_common_add_argv (argv, "--symlink usr/bin /bin");
367 fu_common_add_argv (argv, "--symlink usr/sbin /sbin");
368 fu_common_add_argv (argv, "--chdir /tmp");
369 fu_common_add_argv (argv, "--unshare-all");
Richard Hughes443e4092017-08-09 16:07:31 +0100370 fu_common_add_argv (argv, "/tmp/%s", script_fn);
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100371 g_ptr_array_add (argv, NULL);
372 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
373 g_debug ("running '%s' in %s", argv_str, tmpdir);
374 if (!g_spawn_sync ("/tmp",
375 (gchar **) argv->pdata,
376 NULL,
Richard Hughesf6f72a42017-08-09 16:25:25 +0100377 G_SPAWN_SEARCH_PATH,
Richard Hughes41cbe2a2017-08-08 14:13:35 +0100378 NULL, NULL, /* child_setup */
379 &standard_output,
380 &standard_error,
381 &rc,
382 error)) {
383 g_prefix_error (error, "failed to run '%s': ", argv_str);
384 return NULL;
385 }
386 if (standard_output != NULL && standard_output[0] != '\0')
387 g_debug ("console output was: %s", standard_output);
388 if (rc != 0) {
389 g_set_error (error,
390 FWUPD_ERROR,
391 FWUPD_ERROR_INTERNAL,
392 "failed to build firmware: %s",
393 standard_error);
394 return NULL;
395 }
396
397 /* get generated file */
398 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
399 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
400 if (firmware_blob == NULL)
401 return NULL;
402
403 /* cleanup temp directory */
404 if (!fu_common_rmtree (tmpdir, error))
405 return NULL;
406
407 /* success */
408 return g_steal_pointer (&firmware_blob);
409}
Richard Hughes049ccc82017-08-09 15:26:56 +0100410
411typedef struct {
412 FuOutputHandler handler_cb;
413 gpointer handler_user_data;
414 GMainLoop *loop;
415 GSource *source;
416 GInputStream *stream;
417 GCancellable *cancellable;
418} FuCommonSpawnHelper;
419
420static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
421
422static gboolean
423fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
424{
425 FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
426 gchar buffer[1024];
427 gssize sz;
428 g_auto(GStrv) split = NULL;
429 g_autoptr(GError) error = NULL;
430
431 /* read from stream */
432 sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
433 buffer,
434 sizeof(buffer) - 1,
435 NULL,
436 &error);
437 if (sz < 0) {
Richard Hughes67cbe642017-08-16 12:26:14 +0100438 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
439 g_warning ("failed to get read from nonblocking fd: %s",
440 error->message);
441 }
Richard Hughes049ccc82017-08-09 15:26:56 +0100442 return G_SOURCE_REMOVE;
443 }
444
445 /* no read possible */
446 if (sz == 0)
447 g_main_loop_quit (helper->loop);
448
449 /* emit lines */
450 if (helper->handler_cb != NULL) {
451 buffer[sz] = '\0';
452 split = g_strsplit (buffer, "\n", -1);
453 for (guint i = 0; split[i] != NULL; i++) {
454 if (split[i][0] == '\0')
455 continue;
456 helper->handler_cb (split[i], helper->handler_user_data);
457 }
458 }
459
460 /* set up the source for the next read */
461 fu_common_spawn_create_pollable_source (helper);
462 return G_SOURCE_REMOVE;
463}
464
465static void
466fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
467{
468 if (helper->source != NULL)
469 g_source_destroy (helper->source);
470 helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
471 helper->cancellable);
472 g_source_attach (helper->source, NULL);
473 g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
474}
475
476static void
477fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
478{
479 if (helper->stream != NULL)
480 g_object_unref (helper->stream);
481 if (helper->source != NULL)
482 g_source_destroy (helper->source);
483 if (helper->loop != NULL)
484 g_main_loop_unref (helper->loop);
485 g_free (helper);
486}
487
488G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
489
490/**
491 * fu_common_spawn_sync:
492 * @argv: The argument list to run
493 * @handler_cb: A #FuOutputHandler or %NULL
494 * @handler_user_data: the user data to pass to @handler
495 * @cancellable: a #GCancellable, or %NULL
496 * @error: A #GError or %NULL
497 *
498 * Runs a subprocess and waits for it to exit. Any output on standard out or
499 * standard error will be forwarded to @handler_cb as whole lines.
500 *
501 * Returns: %TRUE for success
502 **/
503gboolean
504fu_common_spawn_sync (const gchar * const * argv,
505 FuOutputHandler handler_cb,
506 gpointer handler_user_data,
507 GCancellable *cancellable, GError **error)
508{
509 g_autoptr(FuCommonSpawnHelper) helper = NULL;
510 g_autoptr(GSubprocess) subprocess = NULL;
Richard Hughes455fdd32017-08-16 12:26:44 +0100511 g_autofree gchar *argv_str = NULL;
Richard Hughes049ccc82017-08-09 15:26:56 +0100512
513 /* create subprocess */
Richard Hughes455fdd32017-08-16 12:26:44 +0100514 argv_str = g_strjoinv (" ", (gchar **) argv);
515 g_debug ("running '%s'", argv_str);
Richard Hughes049ccc82017-08-09 15:26:56 +0100516 subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
517 G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
518 if (subprocess == NULL)
519 return FALSE;
520
521 /* watch for process to exit */
522 helper = g_new0 (FuCommonSpawnHelper, 1);
523 helper->handler_cb = handler_cb;
524 helper->handler_user_data = handler_user_data;
525 helper->loop = g_main_loop_new (NULL, FALSE);
526 helper->stream = g_subprocess_get_stdout_pipe (subprocess);
527 helper->cancellable = cancellable;
528 fu_common_spawn_create_pollable_source (helper);
529 g_main_loop_run (helper->loop);
530 return g_subprocess_wait_check (subprocess, cancellable, error);
531}