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