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