blob: b84d1fbd0c669a50113d1b9883c01a8bb1667e8f [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;
301 g_autofree gchar *startup_fn = NULL;
302 g_autofree gchar *tmpdir = NULL;
303 g_autoptr(GBytes) firmware_blob = NULL;
304 g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
305
306 g_return_val_if_fail (bytes != NULL, NULL);
307 g_return_val_if_fail (script_fn != NULL, NULL);
308 g_return_val_if_fail (output_fn != NULL, NULL);
309 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
310
311 /* untar file to temp location */
312 tmpdir = g_dir_make_tmp ("fwupd-gen-XXXXXX", error);
313 if (tmpdir == NULL)
314 return NULL;
315 startup_fn = g_build_filename (tmpdir, "startup.sh", NULL);
316 if (!fu_common_extract_archive (bytes, tmpdir, error))
317 return NULL;
318
319 /* this is shared with the plugins */
320 localstatedir = g_build_filename (LOCALSTATEDIR, "lib", "fwupd", "builder", NULL);
321
322 /* launch bubblewrap and generate firmware */
323 g_ptr_array_add (argv, g_strdup ("/usr/bin/bwrap"));
324 fu_common_add_argv (argv, "--die-with-parent");
325 fu_common_add_argv (argv, "--ro-bind /usr /usr");
326 fu_common_add_argv (argv, "--dir /tmp");
327 fu_common_add_argv (argv, "--dir /var");
328 fu_common_add_argv (argv, "--bind %s /tmp", tmpdir);
329 if (g_file_test (localstatedir, G_FILE_TEST_EXISTS))
330 fu_common_add_argv (argv, "--ro-bind %s /boot", localstatedir);
331 fu_common_add_argv (argv, "--dev /dev");
332 fu_common_add_argv (argv, "--symlink usr/lib /lib");
333 fu_common_add_argv (argv, "--symlink usr/lib64 /lib64");
334 fu_common_add_argv (argv, "--symlink usr/bin /bin");
335 fu_common_add_argv (argv, "--symlink usr/sbin /sbin");
336 fu_common_add_argv (argv, "--chdir /tmp");
337 fu_common_add_argv (argv, "--unshare-all");
338 fu_common_add_argv (argv, "/bin/sh /tmp/%s", script_fn);
339 g_ptr_array_add (argv, NULL);
340 argv_str = g_strjoinv (" ", (gchar **) argv->pdata);
341 g_debug ("running '%s' in %s", argv_str, tmpdir);
342 if (!g_spawn_sync ("/tmp",
343 (gchar **) argv->pdata,
344 NULL,
345 G_SPAWN_DEFAULT,
346 NULL, NULL, /* child_setup */
347 &standard_output,
348 &standard_error,
349 &rc,
350 error)) {
351 g_prefix_error (error, "failed to run '%s': ", argv_str);
352 return NULL;
353 }
354 if (standard_output != NULL && standard_output[0] != '\0')
355 g_debug ("console output was: %s", standard_output);
356 if (rc != 0) {
357 g_set_error (error,
358 FWUPD_ERROR,
359 FWUPD_ERROR_INTERNAL,
360 "failed to build firmware: %s",
361 standard_error);
362 return NULL;
363 }
364
365 /* get generated file */
366 output2_fn = g_build_filename (tmpdir, output_fn, NULL);
367 firmware_blob = fu_common_get_contents_bytes (output2_fn, error);
368 if (firmware_blob == NULL)
369 return NULL;
370
371 /* cleanup temp directory */
372 if (!fu_common_rmtree (tmpdir, error))
373 return NULL;
374
375 /* success */
376 return g_steal_pointer (&firmware_blob);
377}