Line | Count | Source (jump to first uncovered line) |
1 | | #include <string.h> |
2 | | |
3 | | #include "checksum.h" |
4 | | #include "config_file.h" |
5 | | #include "context.h" |
6 | | #include "manifest.h" |
7 | | #include "signature.h" |
8 | | #include "utils.h" |
9 | | |
10 | 0 | #define RAUC_IMAGE_PREFIX "image" |
11 | | |
12 | 0 | #define R_MANIFEST_ERROR r_manifest_error_quark() |
13 | | GQuark r_manifest_error_quark(void) |
14 | 0 | { |
15 | 0 | return g_quark_from_static_string("r_manifest_error_quark"); |
16 | 0 | } |
17 | | |
18 | | static gboolean parse_image(GKeyFile *key_file, const gchar *group, RaucImage **image, GError **error) |
19 | 9.91k | { |
20 | 9.91k | g_autoptr(RaucImage) iimage = r_new_image(); |
21 | 9.91k | g_auto(GStrv) groupsplit = NULL; |
22 | 9.91k | gchar *value; |
23 | 9.91k | g_auto(GStrv) hooks = NULL; |
24 | 9.91k | gsize entries; |
25 | 9.91k | g_auto(GStrv) converted = NULL; |
26 | 9.91k | GError *ierror = NULL; |
27 | | |
28 | 9.91k | g_return_val_if_fail(key_file != NULL, FALSE); |
29 | 9.91k | g_return_val_if_fail(group != NULL, FALSE); |
30 | 9.91k | g_return_val_if_fail(image == NULL || *image == NULL, FALSE); |
31 | 9.91k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
32 | | |
33 | | /* We support several formats: |
34 | | * - [image.rootfs] |
35 | | * - [image.rootfs.product-a] |
36 | | * - [image.appfs/app-1] |
37 | | * - [image.appfs/app-1.product-a] |
38 | | */ |
39 | | |
40 | 9.91k | groupsplit = g_strsplit(group, ".", 3); |
41 | 9.91k | g_assert_cmpint(g_strv_length(groupsplit), >=, 2); |
42 | 9.91k | g_assert_cmpstr(groupsplit[0], ==, "image"); |
43 | | |
44 | 9.91k | g_auto(GStrv) targetsplit = NULL; |
45 | 9.91k | targetsplit = g_strsplit(groupsplit[1], "/", 2); |
46 | 9.91k | iimage->slotclass = g_strdup(targetsplit[0]); |
47 | | |
48 | | /* Do we have an artifact name for this image? */ |
49 | 9.91k | if (g_strv_length(targetsplit) == 2) |
50 | 977 | iimage->artifact = g_strdup(targetsplit[1]); |
51 | | |
52 | | /* Do we have a variant name for this image? */ |
53 | 9.91k | if (g_strv_length(groupsplit) == 3) |
54 | 3.28k | iimage->variant = g_strdup(groupsplit[2]); |
55 | | |
56 | 9.91k | value = key_file_consume_string(key_file, group, "sha256", NULL); |
57 | 9.91k | if (value) { |
58 | 101 | iimage->checksum.type = G_CHECKSUM_SHA256; |
59 | 101 | iimage->checksum.digest = value; |
60 | 101 | } |
61 | 9.91k | iimage->checksum.size = g_key_file_get_uint64(key_file, |
62 | 9.91k | group, "size", &ierror); |
63 | 9.91k | if (g_error_matches(ierror, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { |
64 | | /* restore size to the default of -1 */ |
65 | 9.80k | iimage->checksum.size = -1; |
66 | 9.80k | g_clear_error(&ierror); |
67 | 9.80k | } else if (ierror) { |
68 | 23 | g_propagate_error(error, ierror); |
69 | 23 | return FALSE; |
70 | 23 | } |
71 | 9.88k | g_key_file_remove_key(key_file, group, "size", NULL); |
72 | | |
73 | 9.88k | hooks = g_key_file_get_string_list(key_file, group, "hooks", &entries, NULL); |
74 | 12.6k | for (gsize j = 0; j < entries; j++) { |
75 | 2.94k | if (g_strcmp0(hooks[j], "pre-install") == 0) { |
76 | 426 | iimage->hooks.pre_install = TRUE; |
77 | 2.51k | } else if (g_strcmp0(hooks[j], "install") == 0) { |
78 | 1.88k | iimage->hooks.install = TRUE; |
79 | 1.88k | } else if (g_strcmp0(hooks[j], "post-install") == 0) { |
80 | 453 | iimage->hooks.post_install = TRUE; |
81 | 453 | } else { |
82 | 180 | g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE, |
83 | 180 | "slot hook type '%s' not supported", hooks[j]); |
84 | 180 | return FALSE; |
85 | 180 | } |
86 | 2.94k | } |
87 | 9.70k | g_key_file_remove_key(key_file, group, "hooks", NULL); |
88 | | |
89 | 9.70k | iimage->filename = key_file_consume_string(key_file, group, "filename", &ierror); |
90 | | /* 'filename' is optional only for 'install' hooks */ |
91 | 9.70k | if (iimage->filename == NULL) { |
92 | 1.27k | if (!iimage->hooks.install) { |
93 | 332 | g_propagate_error(error, ierror); |
94 | 332 | return FALSE; |
95 | 944 | } else { |
96 | 944 | g_clear_error(&ierror); |
97 | 944 | } |
98 | 1.27k | } |
99 | | |
100 | 9.37k | g_key_file_remove_key(key_file, group, "version", NULL); |
101 | 9.37k | g_key_file_remove_key(key_file, group, "description", NULL); |
102 | 9.37k | g_key_file_remove_key(key_file, group, "build", NULL); |
103 | | |
104 | 9.37k | iimage->adaptive = g_key_file_get_string_list(key_file, group, "adaptive", NULL, NULL); |
105 | 9.37k | g_key_file_remove_key(key_file, group, "adaptive", NULL); |
106 | | |
107 | 9.37k | iimage->convert = g_key_file_get_string_list(key_file, group, "convert", NULL, NULL); |
108 | 9.37k | g_key_file_remove_key(key_file, group, "convert", NULL); |
109 | | |
110 | 9.37k | converted = g_key_file_get_string_list(key_file, group, "converted", NULL, NULL); |
111 | 9.37k | g_key_file_remove_key(key_file, group, "converted", NULL); |
112 | 9.37k | if (converted) { |
113 | 0 | iimage->converted = g_ptr_array_new_with_free_func(g_free); |
114 | 0 | r_ptr_array_addv(iimage->converted, converted, TRUE); |
115 | 0 | } |
116 | | |
117 | 9.37k | if (!check_remaining_keys(key_file, group, &ierror)) { |
118 | 102 | g_propagate_error(error, ierror); |
119 | 102 | return FALSE; |
120 | 102 | } |
121 | 9.27k | g_key_file_remove_group(key_file, group, NULL); |
122 | | |
123 | 9.27k | *image = g_steal_pointer(&iimage); |
124 | | |
125 | 9.27k | return TRUE; |
126 | 9.37k | } |
127 | | |
128 | | static gboolean parse_meta(GKeyFile *key_file, const gchar *group, RaucManifest *raucm, GError **error) |
129 | 3.29k | { |
130 | 3.29k | g_auto(GStrv) groupsplit = NULL; |
131 | 3.29k | g_auto(GStrv) keys = NULL; |
132 | 3.29k | g_autoptr(GHashTable) kvs = NULL; |
133 | 3.29k | g_autofree gchar *env_section = NULL; |
134 | 3.29k | GError *ierror = NULL; |
135 | | |
136 | 3.29k | g_return_val_if_fail(key_file != NULL, FALSE); |
137 | 3.29k | g_return_val_if_fail(group != NULL, FALSE); |
138 | 3.29k | g_return_val_if_fail(raucm != NULL, FALSE); |
139 | 3.29k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
140 | | |
141 | 3.29k | groupsplit = g_strsplit(group, ".", 2); |
142 | 3.29k | if ((g_strv_length(groupsplit) != 2) || strchr(groupsplit[1], '.')) { |
143 | 11 | g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE, |
144 | 11 | "invalid metadata section name '%s' (must contain a single '.')", group); |
145 | 11 | return FALSE; |
146 | 11 | } |
147 | | |
148 | 3.28k | env_section = r_prepare_env_key(groupsplit[1], &ierror); |
149 | 3.28k | if (!env_section) { |
150 | 88 | g_propagate_prefixed_error( |
151 | 88 | error, |
152 | 88 | ierror, |
153 | 88 | "Invalid metadata section name '%s': ", groupsplit[1]); |
154 | 88 | return FALSE; |
155 | 88 | } |
156 | | |
157 | 3.20k | kvs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
158 | | |
159 | 3.20k | keys = g_key_file_get_keys(key_file, group, NULL, NULL); |
160 | 8.90k | for (GStrv key = keys; *key; key++) { |
161 | 5.85k | g_autofree gchar *value = key_file_consume_string(key_file, group, *key, &ierror); |
162 | 5.85k | g_autofree gchar *env_key = NULL; |
163 | | |
164 | 5.85k | if (!value) { |
165 | 92 | g_propagate_error(error, ierror); |
166 | 92 | return FALSE; |
167 | 92 | } |
168 | | |
169 | 5.76k | env_key = r_prepare_env_key(*key, &ierror); |
170 | 5.76k | if (!env_key) { |
171 | 58 | g_propagate_prefixed_error( |
172 | 58 | error, |
173 | 58 | ierror, |
174 | 58 | "Invalid metadata key name '%s': ", *key); |
175 | 58 | return FALSE; |
176 | 58 | } |
177 | | |
178 | 5.70k | g_hash_table_insert(kvs, g_strdup(*key), g_steal_pointer(&value)); |
179 | 5.70k | } |
180 | | |
181 | 3.05k | g_hash_table_insert(raucm->meta, g_strdup(groupsplit[1]), g_steal_pointer(&kvs)); |
182 | 3.05k | g_key_file_remove_group(key_file, group, NULL); |
183 | | |
184 | 3.05k | return TRUE; |
185 | 3.20k | } |
186 | | |
187 | | /* Parses key_file into RaucManifest structure |
188 | | * |
189 | | * key_file - input key file |
190 | | * manifest - address of manifest pointer, pointer must be NULL and will be set |
191 | | * to point to a newly allocated RaucManifest if parsing succeeded. |
192 | | * Otherwise it will remain untouched. |
193 | | * error - Return location for GError |
194 | | * |
195 | | * Returns TRUE if manifest was parsed without error, otherwise FALSE |
196 | | */ |
197 | | static gboolean parse_manifest(GKeyFile *key_file, RaucManifest **manifest, GError **error) |
198 | 3.06k | { |
199 | 3.06k | GError *ierror = NULL; |
200 | 3.06k | g_autoptr(RaucManifest) raucm = g_new0(RaucManifest, 1); |
201 | 3.06k | g_autofree gchar *tmp = NULL; |
202 | 3.06k | g_auto(GStrv) groups = NULL; |
203 | 3.06k | gsize group_count; |
204 | 3.06k | g_auto(GStrv) bundle_hooks = NULL; |
205 | 3.06k | gsize hook_entries; |
206 | | |
207 | 3.06k | g_return_val_if_fail(key_file != NULL, FALSE); |
208 | 3.06k | g_return_val_if_fail(manifest != NULL && *manifest == NULL, FALSE); |
209 | 3.06k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
210 | | |
211 | | /* initialize empty warnings array */ |
212 | 3.06k | raucm->warnings = g_ptr_array_new_with_free_func(g_free); |
213 | | |
214 | | /* parse [update] section */ |
215 | 3.06k | raucm->update_compatible = key_file_consume_string(key_file, "update", "compatible", &ierror); |
216 | 3.06k | if (!raucm->update_compatible) { |
217 | 1.19k | g_propagate_error(error, ierror); |
218 | 1.19k | return FALSE; |
219 | 1.19k | } |
220 | 1.87k | raucm->update_version = key_file_consume_string(key_file, "update", "version", NULL); |
221 | 1.87k | raucm->update_description = key_file_consume_string(key_file, "update", "description", NULL); |
222 | 1.87k | raucm->update_build = key_file_consume_string(key_file, "update", "build", NULL); |
223 | 1.87k | raucm->update_min_rauc_version = key_file_consume_string(key_file, "update", "min-rauc-version", NULL); |
224 | 1.87k | if (!check_remaining_keys(key_file, "update", &ierror)) { |
225 | 97 | g_propagate_error(error, ierror); |
226 | 97 | return FALSE; |
227 | 97 | } |
228 | 1.78k | g_key_file_remove_group(key_file, "update", NULL); |
229 | | |
230 | | /* parse [bundle] section */ |
231 | 1.78k | tmp = key_file_consume_string(key_file, "bundle", "format", NULL); |
232 | 1.78k | if (tmp == NULL) { |
233 | 1.59k | g_ptr_array_add(raucm->warnings, g_strdup( |
234 | 1.59k | "WARNING: The manifest does not specify a bundle format, defaulting to 'plain'.")); |
235 | 1.59k | g_ptr_array_add(raucm->warnings, g_strdup( |
236 | 1.59k | " We recommend using the 'verity' format instead, if possible.")); |
237 | 1.59k | g_ptr_array_add(raucm->warnings, g_strdup( |
238 | 1.59k | " To silence this warning, select the 'plain' format explicitly.")); |
239 | 1.59k | g_ptr_array_add(raucm->warnings, g_strdup( |
240 | 1.59k | " See https://rauc.readthedocs.io/en/latest/reference.html#sec-ref-formats for details.'")); |
241 | 1.59k | } else { |
242 | 189 | raucm->bundle_format_explicit = TRUE; |
243 | 189 | } |
244 | 1.78k | if (tmp == NULL || g_strcmp0(tmp, "plain") == 0) { |
245 | 1.59k | raucm->bundle_format = R_MANIFEST_FORMAT_PLAIN; |
246 | 1.59k | } else if ((g_strcmp0(tmp, "verity") == 0) || (g_strcmp0(tmp, "crypt") == 0)) { |
247 | | /* only SHA256 is supported for now */ |
248 | 17 | raucm->bundle_format = g_strcmp0(tmp, "crypt") == 0 ? R_MANIFEST_FORMAT_CRYPT : R_MANIFEST_FORMAT_VERITY; |
249 | 17 | raucm->bundle_verity_hash = key_file_consume_string(key_file, "bundle", "verity-hash", NULL); |
250 | 17 | raucm->bundle_verity_salt = key_file_consume_string(key_file, "bundle", "verity-salt", NULL); |
251 | 17 | raucm->bundle_verity_size = g_key_file_get_uint64(key_file, "bundle", "verity-size", NULL); |
252 | | /* values are checked in check_manifest */ |
253 | 17 | g_key_file_remove_key(key_file, "bundle", "verity-size", NULL); |
254 | 170 | } else { |
255 | 170 | g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE, |
256 | 170 | "Invalid format value '%s' in group '[bundle]'", tmp); |
257 | 170 | return FALSE; |
258 | 170 | } |
259 | | /* crypt format requires additional dm-crypt key */ |
260 | 1.61k | if (g_strcmp0(tmp, "crypt") == 0) { |
261 | 13 | raucm->bundle_crypt_key = key_file_consume_string(key_file, "bundle", "crypt-key", NULL); |
262 | 13 | } |
263 | 1.61k | if (!check_remaining_keys(key_file, "bundle", &ierror)) { |
264 | 7 | g_propagate_error(error, ierror); |
265 | 7 | return FALSE; |
266 | 7 | } |
267 | 1.60k | g_key_file_remove_group(key_file, "bundle", NULL); |
268 | | |
269 | | /* parse [handler] section */ |
270 | 1.60k | raucm->handler_name = key_file_consume_string(key_file, "handler", "filename", NULL); |
271 | 1.60k | raucm->handler_args = key_file_consume_string(key_file, "handler", "args", NULL); |
272 | 1.60k | if (raucm->handler_args && !raucm->handler_name) { |
273 | 2 | g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE, |
274 | 2 | "Setting 'args' requires a full custom handler to be defined under 'filename' in group '[handler]'."); |
275 | 2 | return FALSE; |
276 | 2 | } |
277 | 1.60k | if (!check_remaining_keys(key_file, "handler", &ierror)) { |
278 | 2 | g_propagate_error(error, ierror); |
279 | 2 | return FALSE; |
280 | 2 | } |
281 | 1.59k | g_key_file_remove_group(key_file, "handler", NULL); |
282 | | |
283 | | /* parse [hooks] section */ |
284 | 1.59k | raucm->hook_name = key_file_consume_string(key_file, "hooks", "filename", NULL); |
285 | 1.59k | bundle_hooks = g_key_file_get_string_list(key_file, "hooks", "hooks", &hook_entries, NULL); |
286 | 1.59k | g_key_file_remove_key(key_file, "hooks", "hooks", NULL); |
287 | 2.01k | for (gsize j = 0; j < hook_entries; j++) { |
288 | 521 | if (g_strcmp0(bundle_hooks[j], "install-check") == 0) { |
289 | 416 | raucm->hooks.install_check = TRUE; |
290 | 416 | } else { |
291 | 105 | g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE, |
292 | 105 | "install hook type '%s' not supported", bundle_hooks[j]); |
293 | 105 | return FALSE; |
294 | 105 | } |
295 | 521 | } |
296 | | |
297 | 1.49k | if (!check_remaining_keys(key_file, "hooks", &ierror)) { |
298 | 1 | g_propagate_error(error, ierror); |
299 | 1 | return FALSE; |
300 | 1 | } |
301 | 1.49k | g_key_file_remove_group(key_file, "hooks", NULL); |
302 | | |
303 | 1.49k | raucm->meta = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); |
304 | | |
305 | 1.49k | groups = g_key_file_get_groups(key_file, &group_count); |
306 | 23.4k | for (gsize i = 0; i < group_count; i++) { |
307 | | /* parse [image.<slotclass>] sections */ |
308 | 22.8k | if (g_str_has_prefix(groups[i], RAUC_IMAGE_PREFIX ".")) { |
309 | 9.91k | RaucImage *image = NULL; |
310 | | |
311 | 9.91k | if (!parse_image(key_file, groups[i], &image, &ierror)) { |
312 | 637 | g_propagate_error(error, ierror); |
313 | 637 | return FALSE; |
314 | 637 | } |
315 | | |
316 | 9.27k | raucm->images = g_list_append(raucm->images, image); |
317 | 9.27k | } |
318 | | /* parse [meta.<label>] sections */ |
319 | 22.2k | if (g_str_has_prefix(groups[i], "meta.")) { |
320 | 3.29k | if (!parse_meta(key_file, groups[i], raucm, &ierror)) { |
321 | 249 | g_propagate_error(error, ierror); |
322 | 249 | return FALSE; |
323 | 249 | } |
324 | 3.29k | } |
325 | 22.2k | } |
326 | | |
327 | | /* ignore [rollout] section for now, so that we can add hints/overrides |
328 | | * for rollout/polling behaviour later */ |
329 | 607 | g_key_file_remove_group(key_file, "rollout", NULL); |
330 | | |
331 | 607 | if (!check_remaining_groups(key_file, &ierror)) { |
332 | 230 | g_propagate_error(error, ierror); |
333 | 230 | return FALSE; |
334 | 230 | } |
335 | | |
336 | 377 | *manifest = g_steal_pointer(&raucm); |
337 | | |
338 | 377 | return TRUE; |
339 | 607 | } |
340 | | |
341 | | gboolean load_manifest_mem(GBytes *mem, RaucManifest **manifest, GError **error) |
342 | 5.08k | { |
343 | 5.08k | GError *ierror = NULL; |
344 | 5.08k | g_autoptr(GKeyFile) key_file = NULL; |
345 | 5.08k | const gchar *data; |
346 | 5.08k | gsize length; |
347 | 5.08k | g_autofree gchar *manifest_checksum = NULL; |
348 | | |
349 | 5.08k | g_return_val_if_fail(mem, FALSE); |
350 | 5.08k | g_return_val_if_fail(manifest != NULL && *manifest == NULL, FALSE); |
351 | 5.08k | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
352 | | |
353 | 5.08k | data = g_bytes_get_data(mem, &length); |
354 | 5.08k | if (data == NULL) { |
355 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_ERROR_NO_DATA, "No data available"); |
356 | 0 | return FALSE; |
357 | 0 | } |
358 | | |
359 | 5.08k | manifest_checksum = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar*) data, length); |
360 | | |
361 | 5.08k | key_file = g_key_file_new(); |
362 | | |
363 | 5.08k | if (!g_key_file_load_from_data(key_file, data, length, G_KEY_FILE_NONE, &ierror)) { |
364 | 2.01k | g_propagate_error(error, ierror); |
365 | 2.01k | return FALSE; |
366 | 2.01k | } |
367 | | |
368 | 3.06k | if (!parse_manifest(key_file, manifest, &ierror)) { |
369 | 2.69k | g_propagate_error(error, ierror); |
370 | 2.69k | return FALSE; |
371 | 2.69k | } |
372 | | |
373 | 377 | (*manifest)->hash = g_steal_pointer(&manifest_checksum); |
374 | | |
375 | 377 | return TRUE; |
376 | 3.06k | } |
377 | | |
378 | | gboolean load_manifest_file(const gchar *filename, RaucManifest **manifest, GError **error) |
379 | 0 | { |
380 | 0 | GError *ierror = NULL; |
381 | 0 | g_autofree gchar *data; |
382 | 0 | gsize length; |
383 | 0 | g_autoptr(GKeyFile) key_file = NULL; |
384 | 0 | g_autofree gchar *manifest_checksum = NULL; |
385 | |
|
386 | 0 | g_return_val_if_fail(filename, FALSE); |
387 | 0 | g_return_val_if_fail(manifest != NULL && *manifest == NULL, FALSE); |
388 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
389 | | |
390 | 0 | if (!g_file_get_contents(filename, &data, &length, &ierror)) { |
391 | 0 | g_propagate_error(error, ierror); |
392 | 0 | return FALSE; |
393 | 0 | } |
394 | 0 | manifest_checksum = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar*) data, length); |
395 | |
|
396 | 0 | key_file = g_key_file_new(); |
397 | |
|
398 | 0 | if (!g_key_file_load_from_data(key_file, data, length, G_KEY_FILE_NONE, &ierror)) { |
399 | 0 | g_propagate_error(error, ierror); |
400 | 0 | return FALSE; |
401 | 0 | } |
402 | | |
403 | 0 | if (!parse_manifest(key_file, manifest, &ierror)) { |
404 | 0 | g_propagate_error(error, ierror); |
405 | 0 | return FALSE; |
406 | 0 | } |
407 | | |
408 | 0 | (*manifest)->hash = g_steal_pointer(&manifest_checksum); |
409 | |
|
410 | 0 | return TRUE; |
411 | 0 | } |
412 | | |
413 | | static gboolean check_manifest_common(const RaucManifest *mf, GError **error) |
414 | 0 | { |
415 | 0 | gboolean have_hooks = FALSE; |
416 | |
|
417 | 0 | g_return_val_if_fail(mf, FALSE); |
418 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
419 | | |
420 | 0 | if (mf->update_min_rauc_version) { |
421 | 0 | if (!r_semver_less_equal("0", mf->update_min_rauc_version, NULL)) { |
422 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
423 | 0 | "Failed to parse 'min-rauc-version'. Expected 'Major[.Minor[.Patch]][-pre_release]]', got '%s'", |
424 | 0 | mf->update_min_rauc_version |
425 | 0 | ); |
426 | 0 | return FALSE; |
427 | 0 | } |
428 | 0 | if (!r_semver_less_equal(mf->update_min_rauc_version, RAUC_MESON_VERSION, NULL)) { |
429 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
430 | 0 | "Minimum RAUC version in manifest (%s) is newer than current version (%s)", |
431 | 0 | mf->update_min_rauc_version, RAUC_MESON_VERSION |
432 | 0 | ); |
433 | 0 | return FALSE; |
434 | 0 | } |
435 | 0 | if (r_semver_less_equal(mf->update_min_rauc_version, "1.13", NULL)) { |
436 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
437 | 0 | "Minimum RAUC version field in manifest is only supported since 1.14 (not '%s')", |
438 | 0 | mf->update_min_rauc_version |
439 | 0 | ); |
440 | 0 | return FALSE; |
441 | 0 | } |
442 | 0 | } |
443 | | |
444 | 0 | switch (mf->bundle_format) { |
445 | 0 | case R_MANIFEST_FORMAT_PLAIN: |
446 | 0 | break; /* no additional data needed */ |
447 | 0 | case R_MANIFEST_FORMAT_VERITY: |
448 | 0 | case R_MANIFEST_FORMAT_CRYPT: |
449 | 0 | break; /* data checked in _detached/_inline */ |
450 | 0 | default: { |
451 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unsupported bundle format"); |
452 | 0 | return FALSE; |
453 | 0 | } |
454 | 0 | } |
455 | | |
456 | | /* Check for hook file set if hooks are enabled */ |
457 | | |
458 | 0 | if (mf->hooks.install_check == TRUE) |
459 | 0 | have_hooks = TRUE; |
460 | |
|
461 | 0 | for (GList *l = mf->images; l != NULL; l = l->next) { |
462 | 0 | RaucImage *image = l->data; |
463 | 0 | if (image->hooks.pre_install == TRUE) { |
464 | 0 | have_hooks = TRUE; |
465 | 0 | break; |
466 | 0 | } |
467 | 0 | if (image->hooks.install == TRUE) { |
468 | 0 | have_hooks = TRUE; |
469 | 0 | break; |
470 | 0 | } |
471 | 0 | if (image->hooks.post_install == TRUE) { |
472 | 0 | have_hooks = TRUE; |
473 | 0 | break; |
474 | 0 | } |
475 | 0 | } |
476 | |
|
477 | 0 | if (have_hooks && !mf->hook_name) { |
478 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Hooks used, but no hook 'filename' defined in [hooks] section"); |
479 | 0 | return FALSE; |
480 | 0 | } |
481 | | |
482 | 0 | return TRUE; |
483 | 0 | } |
484 | | |
485 | | static gboolean check_manifest_plain(const RaucManifest *mf, GError **error) |
486 | 0 | { |
487 | 0 | g_return_val_if_fail(mf, FALSE); |
488 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
489 | | |
490 | 0 | g_assert(mf->bundle_format == R_MANIFEST_FORMAT_PLAIN); |
491 | | |
492 | 0 | for (GList *elem = mf->images; elem != NULL; elem = elem->next) { |
493 | 0 | RaucImage *image = elem->data; |
494 | | |
495 | | /* Check for features not supported in plain bundles */ |
496 | 0 | if (image->artifact) { |
497 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Artifacts are not supported in plain bundles"); |
498 | 0 | return FALSE; |
499 | 0 | } |
500 | 0 | if (image->convert || (image->converted && image->converted->len)) { |
501 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Image converters are not supported in plain bundles"); |
502 | 0 | return FALSE; |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | 0 | return TRUE; |
507 | 0 | } |
508 | | |
509 | | /** |
510 | | * Check a loaded manifest for consistency. Manifests generated by 'rauc bundle' |
511 | | * should pass this check if they are compatible with the running version. |
512 | | * |
513 | | * This function is called for both internal and external manifests. |
514 | | */ |
515 | | static gboolean check_manifest_bundled(const RaucManifest *mf, GError **error) |
516 | 0 | { |
517 | 0 | g_return_val_if_fail(mf, FALSE); |
518 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
519 | | |
520 | 0 | for (GList *l = mf->images; l != NULL; l = l->next) { |
521 | 0 | RaucImage *image = l->data; |
522 | |
|
523 | 0 | g_assert(image); |
524 | | |
525 | | /* Having no 'filename' set is valid for 'install' hook only. |
526 | | * This is already ensured during manifest parsing, thus simply |
527 | | * skip further checks here */ |
528 | 0 | if (!image->filename) |
529 | 0 | continue; |
530 | | |
531 | 0 | if (image->checksum.type != G_CHECKSUM_SHA256) { |
532 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unsupported checksum algorithm for image %s", image->filename); |
533 | 0 | return FALSE; |
534 | 0 | } |
535 | 0 | if (!image->checksum.digest) { |
536 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing digest for image %s", image->filename); |
537 | 0 | return FALSE; |
538 | 0 | } |
539 | 0 | if (image->checksum.size < 0) { |
540 | | /* RAUC versions before v1.5 allowed zero-size images but did not handle this explicitly. |
541 | | * Thus, bundles created did have a valid 'filename=' manifest entry |
542 | | * but the 'size=' entry was considered as empty and not set at all. |
543 | | * Retain support for this case, at least for the 'install' per-slot hook use-case |
544 | | * where an image file can be optional. */ |
545 | 0 | if (image->hooks.install) { |
546 | 0 | g_message("Missing size parameter for image '%s'", image->filename); |
547 | 0 | image->checksum.size = 0; |
548 | 0 | } else { |
549 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing size for image %s", image->filename); |
550 | 0 | return FALSE; |
551 | 0 | } |
552 | 0 | } |
553 | | |
554 | 0 | if (image->convert) { |
555 | 0 | guint expected_len = g_strv_length(image->convert); |
556 | |
|
557 | 0 | if (!image->converted) { |
558 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing converted outputs for image %s", image->filename); |
559 | 0 | return FALSE; |
560 | 0 | } |
561 | | |
562 | 0 | if (expected_len != image->converted->len) { |
563 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Inconsistent number of converted inputs/outputs for image %s", image->filename); |
564 | 0 | return FALSE; |
565 | 0 | } |
566 | 0 | } |
567 | 0 | } |
568 | | |
569 | 0 | return TRUE; |
570 | 0 | } |
571 | | |
572 | | gboolean check_manifest_input(const RaucManifest *mf, GError **error) |
573 | 0 | { |
574 | 0 | GError *ierror = NULL; |
575 | |
|
576 | 0 | g_return_val_if_fail(mf, FALSE); |
577 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
578 | | |
579 | 0 | if (!check_manifest_common(mf, &ierror)) { |
580 | 0 | g_propagate_error(error, ierror); |
581 | 0 | return FALSE; |
582 | 0 | } |
583 | | |
584 | 0 | switch (mf->bundle_format) { |
585 | 0 | case R_MANIFEST_FORMAT_PLAIN: |
586 | 0 | case R_MANIFEST_FORMAT_CRYPT: |
587 | 0 | case R_MANIFEST_FORMAT_VERITY: |
588 | 0 | break; |
589 | 0 | default: { |
590 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unsupported bundle format in input manifest"); |
591 | 0 | return FALSE; |
592 | 0 | } |
593 | 0 | } |
594 | | |
595 | 0 | if (mf->bundle_format == R_MANIFEST_FORMAT_PLAIN) { |
596 | 0 | if (!check_manifest_plain(mf, &ierror)) { |
597 | 0 | g_propagate_error(error, ierror); |
598 | 0 | return FALSE; |
599 | 0 | } |
600 | 0 | } |
601 | | |
602 | 0 | for (GList *l = mf->images; l != NULL; l = l->next) { |
603 | 0 | RaucImage *image = l->data; |
604 | |
|
605 | 0 | g_assert(image); |
606 | | |
607 | 0 | if (image->filename && strchr(image->filename, '/')) { |
608 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
609 | 0 | "Image filename %s must not contain '/'", image->filename); |
610 | 0 | return FALSE; |
611 | 0 | } |
612 | 0 | if (image->checksum.digest) { |
613 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
614 | 0 | "Unexpected digest for image %s in input manifest", image->filename); |
615 | 0 | return FALSE; |
616 | 0 | } |
617 | 0 | if (image->checksum.size != -1) { |
618 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
619 | 0 | "Unexpected size %"G_GOFFSET_FORMAT " for image %s in input manifest", image->checksum.size, image->filename); |
620 | 0 | return FALSE; |
621 | 0 | } |
622 | 0 | if (image->converted && image->converted->len) { |
623 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
624 | 0 | "Unexpected 'converted' option in input manifest"); |
625 | 0 | return FALSE; |
626 | 0 | } |
627 | 0 | } |
628 | | |
629 | 0 | return TRUE; |
630 | 0 | } |
631 | | |
632 | | gboolean check_manifest_internal(const RaucManifest *mf, GError **error) |
633 | 0 | { |
634 | 0 | GError *ierror = NULL; |
635 | 0 | gboolean res = FALSE; |
636 | |
|
637 | 0 | g_return_val_if_fail(mf, FALSE); |
638 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
639 | | |
640 | 0 | r_context_begin_step("check_manifest", "Checking manifest contents", 0); |
641 | |
|
642 | 0 | if (!check_manifest_common(mf, &ierror)) { |
643 | 0 | g_propagate_error(error, ierror); |
644 | 0 | goto out; |
645 | 0 | } |
646 | | |
647 | 0 | switch (mf->bundle_format) { |
648 | 0 | case R_MANIFEST_FORMAT_PLAIN: |
649 | 0 | break; /* no additional data needed */ |
650 | 0 | case R_MANIFEST_FORMAT_CRYPT: |
651 | 0 | case R_MANIFEST_FORMAT_VERITY: |
652 | 0 | default: { |
653 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Bundle format '%s' not allowed for internal manifest", r_manifest_bundle_format_to_str(mf->bundle_format)); |
654 | 0 | goto out; |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | 0 | if (!check_manifest_bundled(mf, &ierror)) { |
659 | 0 | g_propagate_error(error, ierror); |
660 | 0 | goto out; |
661 | 0 | } |
662 | | |
663 | 0 | if (!check_manifest_plain(mf, &ierror)) { |
664 | 0 | g_propagate_error(error, ierror); |
665 | 0 | goto out; |
666 | 0 | } |
667 | | |
668 | 0 | if (mf->bundle_crypt_key) { |
669 | 0 | g_set_error_literal(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unexpected key for crypt bundle in internal manifest"); |
670 | 0 | goto out; |
671 | 0 | } |
672 | 0 | if (mf->bundle_verity_hash) { |
673 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unexpected verity hash for %s bundle in internal manifest", r_manifest_bundle_format_to_str(mf->bundle_format)); |
674 | 0 | goto out; |
675 | 0 | } |
676 | 0 | if (mf->bundle_verity_salt) { |
677 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unexpected verity salt for %s bundle in internal manifest", r_manifest_bundle_format_to_str(mf->bundle_format)); |
678 | 0 | goto out; |
679 | 0 | } |
680 | 0 | if (mf->bundle_verity_size) { |
681 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unexpected verity size for %s bundle in internal manifest", r_manifest_bundle_format_to_str(mf->bundle_format)); |
682 | 0 | goto out; |
683 | 0 | } |
684 | | |
685 | 0 | res = TRUE; |
686 | 0 | out: |
687 | 0 | r_context_end_step("check_manifest", res); |
688 | 0 | return res; |
689 | 0 | } |
690 | | |
691 | | gboolean check_manifest_external(const RaucManifest *mf, GError **error) |
692 | 0 | { |
693 | 0 | GError *ierror = NULL; |
694 | 0 | gboolean res = FALSE; |
695 | 0 | const gchar *format = NULL; |
696 | |
|
697 | 0 | g_return_val_if_fail(mf, FALSE); |
698 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
699 | | |
700 | 0 | r_context_begin_step("check_manifest", "Checking manifest contents", 0); |
701 | |
|
702 | 0 | format = r_manifest_bundle_format_to_str(mf->bundle_format); |
703 | 0 | if (g_strcmp0(format, "invalid") == 0) { |
704 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unsupported bundle format"); |
705 | 0 | goto out; |
706 | 0 | } |
707 | | |
708 | 0 | if (!check_manifest_common(mf, &ierror)) { |
709 | 0 | g_propagate_error(error, ierror); |
710 | 0 | goto out; |
711 | 0 | } |
712 | | |
713 | 0 | switch (mf->bundle_format) { |
714 | 0 | case R_MANIFEST_FORMAT_PLAIN: { |
715 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unsupported bundle format 'plain' for external manifest"); |
716 | 0 | goto out; |
717 | 0 | } |
718 | 0 | case R_MANIFEST_FORMAT_CRYPT: { |
719 | 0 | guint8 *tmp; |
720 | |
|
721 | 0 | if (!mf->bundle_crypt_key) { |
722 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing key for crypt bundle"); |
723 | 0 | goto out; |
724 | 0 | } |
725 | 0 | tmp = r_hex_decode(mf->bundle_crypt_key, 32); |
726 | 0 | if (!tmp) { |
727 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Invalid key for crypt bundle"); |
728 | 0 | goto out; |
729 | 0 | } |
730 | 0 | g_free(tmp); |
731 | 0 | }; |
732 | | /* Fallthrough */ |
733 | 0 | case R_MANIFEST_FORMAT_VERITY: { |
734 | 0 | guint8 *tmp; |
735 | |
|
736 | 0 | if (!mf->bundle_verity_hash) { |
737 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing hash for %s bundle", format); |
738 | 0 | goto out; |
739 | 0 | } |
740 | 0 | tmp = r_hex_decode(mf->bundle_verity_hash, 32); |
741 | 0 | if (!tmp) { |
742 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Invalid hash for %s bundle", format); |
743 | 0 | goto out; |
744 | 0 | } |
745 | 0 | g_free(tmp); |
746 | |
|
747 | 0 | if (!mf->bundle_verity_salt) { |
748 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing salt for %s bundle", format); |
749 | 0 | goto out; |
750 | 0 | } |
751 | 0 | tmp = r_hex_decode(mf->bundle_verity_salt, 32); |
752 | 0 | if (!tmp) { |
753 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Invalid salt for %s bundle", format); |
754 | 0 | goto out; |
755 | 0 | } |
756 | 0 | g_free(tmp); |
757 | |
|
758 | 0 | if (!mf->bundle_verity_size) { |
759 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing size for %s bundle", format); |
760 | 0 | goto out; |
761 | 0 | } |
762 | | |
763 | 0 | if (mf->bundle_verity_size % 4096) { |
764 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unaligned size for %s bundle", format); |
765 | 0 | goto out; |
766 | 0 | } |
767 | | |
768 | 0 | break; |
769 | 0 | }; |
770 | 0 | default: { |
771 | | /* should not be reached as this is checked before */ |
772 | 0 | g_error("Unsupported bundle format"); |
773 | 0 | goto out; |
774 | 0 | } |
775 | 0 | } |
776 | | |
777 | 0 | if (!check_manifest_bundled(mf, &ierror)) { |
778 | 0 | g_propagate_error(error, ierror); |
779 | 0 | goto out; |
780 | 0 | } |
781 | | |
782 | 0 | res = TRUE; |
783 | 0 | out: |
784 | 0 | r_context_end_step("check_manifest", res); |
785 | 0 | return res; |
786 | 0 | } |
787 | | |
788 | | gboolean check_manifest_create(const RaucManifest *mf, GError **error) |
789 | 0 | { |
790 | 0 | g_return_val_if_fail(mf, FALSE); |
791 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
792 | | |
793 | 0 | for (GList *l = mf->images; l != NULL; l = l->next) { |
794 | 0 | RaucImage *image = l->data; |
795 | |
|
796 | 0 | g_assert(image); |
797 | | |
798 | 0 | if (image->hooks.install && (image->hooks.pre_install || image->hooks.post_install)) { |
799 | 0 | g_set_error_literal(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, |
800 | 0 | "An 'install' hook must not be combined with 'pre-install' or 'post-install' hooks"); |
801 | 0 | return FALSE; |
802 | 0 | } |
803 | 0 | } |
804 | | |
805 | 0 | return TRUE; |
806 | 0 | } |
807 | | |
808 | | static GKeyFile *prepare_manifest(const RaucManifest *mf) |
809 | 0 | { |
810 | 0 | g_autoptr(GKeyFile) key_file = NULL; |
811 | 0 | GPtrArray *hooks = g_ptr_array_new_full(3, g_free); |
812 | 0 | GHashTableIter iter; |
813 | 0 | GHashTable *meta_kvs; |
814 | 0 | const gchar *meta_group; |
815 | |
|
816 | 0 | g_return_val_if_fail(mf, FALSE); |
817 | | |
818 | 0 | key_file = g_key_file_new(); |
819 | |
|
820 | 0 | if (mf->update_compatible) |
821 | 0 | g_key_file_set_string(key_file, "update", "compatible", mf->update_compatible); |
822 | |
|
823 | 0 | if (mf->update_version) |
824 | 0 | g_key_file_set_string(key_file, "update", "version", mf->update_version); |
825 | |
|
826 | 0 | if (mf->update_description) |
827 | 0 | g_key_file_set_string(key_file, "update", "description", mf->update_description); |
828 | |
|
829 | 0 | if (mf->update_build) |
830 | 0 | g_key_file_set_string(key_file, "update", "build", mf->update_build); |
831 | |
|
832 | 0 | switch (mf->bundle_format) { |
833 | 0 | case R_MANIFEST_FORMAT_PLAIN: |
834 | 0 | if (mf->bundle_format_explicit) |
835 | 0 | g_key_file_set_string(key_file, "bundle", "format", "plain"); |
836 | 0 | break; |
837 | 0 | case R_MANIFEST_FORMAT_CRYPT: { |
838 | 0 | if (mf->bundle_crypt_key) |
839 | 0 | g_key_file_set_string(key_file, "bundle", "crypt-key", mf->bundle_crypt_key); |
840 | 0 | }; |
841 | | /* Fallthrough */ |
842 | 0 | case R_MANIFEST_FORMAT_VERITY: { |
843 | 0 | g_key_file_set_string(key_file, "bundle", "format", r_manifest_bundle_format_to_str(mf->bundle_format)); |
844 | 0 | if (mf->bundle_verity_hash) |
845 | 0 | g_key_file_set_string(key_file, "bundle", "verity-hash", mf->bundle_verity_hash); |
846 | 0 | if (mf->bundle_verity_salt) |
847 | 0 | g_key_file_set_string(key_file, "bundle", "verity-salt", mf->bundle_verity_salt); |
848 | 0 | if (mf->bundle_verity_size) |
849 | 0 | g_key_file_set_uint64(key_file, "bundle", "verity-size", mf->bundle_verity_size); |
850 | |
|
851 | 0 | break; |
852 | 0 | }; |
853 | 0 | default: |
854 | 0 | break; |
855 | 0 | } |
856 | | |
857 | 0 | if (mf->handler_name) |
858 | 0 | g_key_file_set_string(key_file, "handler", "filename", mf->handler_name); |
859 | |
|
860 | 0 | if (mf->handler_args) |
861 | 0 | g_key_file_set_string(key_file, "handler", "args", mf->handler_args); |
862 | |
|
863 | 0 | if (mf->hook_name) |
864 | 0 | g_key_file_set_string(key_file, "hooks", "filename", mf->hook_name); |
865 | |
|
866 | 0 | if (mf->hooks.install_check == TRUE) { |
867 | 0 | g_ptr_array_add(hooks, g_strdup("install-check")); |
868 | 0 | } |
869 | 0 | g_ptr_array_add(hooks, NULL); |
870 | 0 | if (hooks->pdata && *hooks->pdata) { |
871 | 0 | g_key_file_set_string_list(key_file, "hooks", "hooks", |
872 | 0 | (const gchar **)hooks->pdata, hooks->len); |
873 | 0 | } |
874 | 0 | g_ptr_array_unref(hooks); |
875 | |
|
876 | 0 | for (GList *l = mf->images; l != NULL; l = l->next) { |
877 | 0 | g_autoptr(GPtrArray) hooklist = g_ptr_array_new_full(3, g_free); |
878 | 0 | RaucImage *image = l->data; |
879 | 0 | g_autofree gchar *group = NULL; |
880 | |
|
881 | 0 | if (!image || !image->slotclass) |
882 | 0 | continue; |
883 | | |
884 | 0 | group = g_strconcat(RAUC_IMAGE_PREFIX ".", image->slotclass, NULL); |
885 | |
|
886 | 0 | if (image->artifact) { |
887 | 0 | gchar *tmp = group; |
888 | 0 | group = g_strconcat(group, "/", image->artifact, NULL); |
889 | 0 | g_free(tmp); |
890 | 0 | } |
891 | |
|
892 | 0 | if (image->variant) { |
893 | 0 | gchar *tmp = group; |
894 | 0 | group = g_strconcat(group, ".", image->variant, NULL); |
895 | 0 | g_free(tmp); |
896 | 0 | } |
897 | |
|
898 | 0 | if (image->checksum.type == G_CHECKSUM_SHA256) |
899 | 0 | g_key_file_set_string(key_file, group, "sha256", image->checksum.digest); |
900 | 0 | if (image->checksum.size >= 0) |
901 | 0 | g_key_file_set_uint64(key_file, group, "size", image->checksum.size); |
902 | |
|
903 | 0 | if (image->filename) |
904 | 0 | g_key_file_set_string(key_file, group, "filename", image->filename); |
905 | |
|
906 | 0 | if (image->hooks.pre_install == TRUE) { |
907 | 0 | g_ptr_array_add(hooklist, g_strdup("pre-install")); |
908 | 0 | } |
909 | 0 | if (image->hooks.install == TRUE) { |
910 | 0 | g_ptr_array_add(hooklist, g_strdup("install")); |
911 | 0 | } |
912 | 0 | if (image->hooks.post_install == TRUE) { |
913 | 0 | g_ptr_array_add(hooklist, g_strdup("post-install")); |
914 | 0 | } |
915 | 0 | g_ptr_array_add(hooklist, NULL); |
916 | |
|
917 | 0 | if (hooklist->pdata && *hooklist->pdata) { |
918 | 0 | g_key_file_set_string_list(key_file, group, "hooks", |
919 | 0 | (const gchar **)hooklist->pdata, hooklist->len); |
920 | 0 | } |
921 | |
|
922 | 0 | if (image->adaptive) |
923 | 0 | g_key_file_set_string_list(key_file, group, "adaptive", |
924 | 0 | (const gchar * const *)image->adaptive, g_strv_length(image->adaptive)); |
925 | |
|
926 | 0 | if (image->convert) |
927 | 0 | g_key_file_set_string_list(key_file, group, "convert", |
928 | 0 | (const gchar * const *)image->convert, g_strv_length(image->convert)); |
929 | 0 | if (image->converted && image->converted->len) |
930 | 0 | g_key_file_set_string_list(key_file, group, "converted", |
931 | 0 | (const gchar * const *)image->converted->pdata, image->converted->len); |
932 | 0 | } |
933 | |
|
934 | 0 | if (mf->meta) { |
935 | 0 | g_hash_table_iter_init(&iter, mf->meta); |
936 | 0 | while (g_hash_table_iter_next(&iter, (gpointer*)&meta_group, (gpointer*)&meta_kvs)) { |
937 | 0 | GHashTableIter kvs_iter; |
938 | 0 | const gchar *key, *value; |
939 | |
|
940 | 0 | g_hash_table_iter_init(&kvs_iter, meta_kvs); |
941 | 0 | while (g_hash_table_iter_next(&kvs_iter, (gpointer*)&key, (gpointer*)&value)) { |
942 | 0 | g_autofree gchar *group = g_strdup_printf("meta.%s", meta_group); |
943 | 0 | g_key_file_set_string(key_file, group, key, value); |
944 | 0 | } |
945 | 0 | } |
946 | 0 | } |
947 | |
|
948 | 0 | return g_steal_pointer(&key_file); |
949 | 0 | } |
950 | | |
951 | | gboolean save_manifest_mem(GBytes **mem, const RaucManifest *mf) |
952 | 0 | { |
953 | 0 | g_autoptr(GKeyFile) key_file = NULL; |
954 | 0 | guint8 *data = NULL; |
955 | 0 | gsize length = 0; |
956 | |
|
957 | 0 | g_return_val_if_fail(mem != NULL && *mem == NULL, FALSE); |
958 | 0 | g_return_val_if_fail(mf != NULL, FALSE); |
959 | | |
960 | 0 | key_file = prepare_manifest(mf); |
961 | | |
962 | | /* according to the docs, this never fails */ |
963 | 0 | data = (guint8*)g_key_file_to_data(key_file, &length, NULL); |
964 | 0 | g_assert(data != NULL); |
965 | 0 | g_assert(length > 0); |
966 | | |
967 | 0 | *mem = g_bytes_new_take(data, length); |
968 | |
|
969 | 0 | return TRUE; |
970 | 0 | } |
971 | | |
972 | | gboolean save_manifest_file(const gchar *filename, const RaucManifest *mf, GError **error) |
973 | 0 | { |
974 | 0 | GError *ierror = NULL; |
975 | 0 | g_autoptr(GKeyFile) key_file = NULL; |
976 | 0 | gboolean res = FALSE; |
977 | |
|
978 | 0 | g_return_val_if_fail(filename, FALSE); |
979 | 0 | g_return_val_if_fail(mf, FALSE); |
980 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
981 | | |
982 | 0 | key_file = prepare_manifest(mf); |
983 | |
|
984 | 0 | res = g_key_file_save_to_file(key_file, filename, &ierror); |
985 | 0 | if (!res) |
986 | 0 | g_propagate_error(error, ierror); |
987 | |
|
988 | 0 | return res; |
989 | 0 | } |
990 | | |
991 | | GVariant* r_manifest_to_dict(const RaucManifest *manifest) |
992 | 0 | { |
993 | 0 | GVariantDict root_dict; |
994 | 0 | GVariantDict grp_dict; |
995 | 0 | g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_ARRAY); |
996 | 0 | GHashTableIter iter; |
997 | 0 | GHashTable *kvs; |
998 | 0 | const gchar *group; |
999 | |
|
1000 | 0 | g_return_val_if_fail(manifest, NULL); |
1001 | | |
1002 | 0 | g_variant_dict_init(&root_dict, NULL); |
1003 | |
|
1004 | 0 | if (manifest->hash) |
1005 | 0 | g_variant_dict_insert(&root_dict, "manifest-hash", "s", manifest->hash); |
1006 | | |
1007 | | /* construct 'update' dict */ |
1008 | 0 | g_variant_dict_init(&grp_dict, NULL); |
1009 | 0 | if (manifest->update_compatible) |
1010 | 0 | g_variant_dict_insert(&grp_dict, "compatible", "s", manifest->update_compatible); |
1011 | 0 | if (manifest->update_version) |
1012 | 0 | g_variant_dict_insert(&grp_dict, "version", "s", manifest->update_version); |
1013 | 0 | if (manifest->update_description) |
1014 | 0 | g_variant_dict_insert(&grp_dict, "description", "s", manifest->update_description); |
1015 | 0 | if (manifest->update_build) |
1016 | 0 | g_variant_dict_insert(&grp_dict, "build", "s", manifest->update_build); |
1017 | 0 | g_variant_dict_insert(&root_dict, "update", "v", g_variant_dict_end(&grp_dict)); |
1018 | | |
1019 | | /* construct 'bundle' dict */ |
1020 | 0 | g_variant_dict_init(&grp_dict, NULL); |
1021 | 0 | g_variant_dict_insert(&grp_dict, "format", "s", r_manifest_bundle_format_to_str(manifest->bundle_format)); |
1022 | |
|
1023 | 0 | if (manifest->bundle_verity_hash) |
1024 | 0 | g_variant_dict_insert(&grp_dict, "verity-hash", "s", manifest->bundle_verity_hash); |
1025 | 0 | if (manifest->bundle_verity_salt) |
1026 | 0 | g_variant_dict_insert(&grp_dict, "verity-salt", "s", manifest->bundle_verity_salt); |
1027 | 0 | if (manifest->bundle_verity_size) |
1028 | 0 | g_variant_dict_insert(&grp_dict, "verity-size", "t", manifest->bundle_verity_size); |
1029 | 0 | g_variant_dict_insert(&root_dict, "bundle", "v", g_variant_dict_end(&grp_dict)); |
1030 | | |
1031 | | /* construct 'hooks' dict */ |
1032 | 0 | if (manifest->hook_name) { |
1033 | 0 | g_variant_dict_init(&grp_dict, NULL); |
1034 | 0 | if (manifest->hook_name) |
1035 | 0 | g_variant_dict_insert(&grp_dict, "filename", "s", manifest->hook_name); |
1036 | 0 | g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); |
1037 | 0 | if (manifest->hooks.install_check) |
1038 | 0 | g_variant_builder_add(&builder, "s", "install-check"); |
1039 | 0 | g_variant_dict_insert(&grp_dict, "hooks", "v", g_variant_builder_end(&builder)); |
1040 | 0 | g_variant_dict_insert(&root_dict, "hooks", "v", g_variant_dict_end(&grp_dict)); |
1041 | 0 | } |
1042 | | |
1043 | | /* construct 'handler' dict */ |
1044 | 0 | if (manifest->handler_name) { |
1045 | 0 | g_variant_dict_init(&grp_dict, NULL); |
1046 | 0 | if (manifest->handler_name) |
1047 | 0 | g_variant_dict_insert(&grp_dict, "filename", "s", manifest->handler_name); |
1048 | 0 | if (manifest->handler_args) |
1049 | 0 | g_variant_dict_insert(&grp_dict, "args", "s", manifest->handler_args); |
1050 | 0 | g_variant_dict_insert(&root_dict, "handler", "v", g_variant_dict_end(&grp_dict)); |
1051 | 0 | } |
1052 | | |
1053 | | /* construct 'images' array of dicts */ |
1054 | 0 | g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); |
1055 | 0 | for (GList *l = manifest->images; l != NULL; l = l->next) { |
1056 | 0 | const RaucImage *img = l->data; |
1057 | 0 | g_auto(GVariantBuilder) hooks = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as")); |
1058 | |
|
1059 | 0 | g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); |
1060 | 0 | g_variant_builder_add(&builder, "{sv}", "slot-class", g_variant_new_string(img->slotclass)); |
1061 | 0 | if (img->artifact) |
1062 | 0 | g_variant_builder_add(&builder, "{sv}", "artifact", g_variant_new_string(img->artifact)); |
1063 | 0 | if (img->variant) |
1064 | 0 | g_variant_builder_add(&builder, "{sv}", "variant", g_variant_new_string(img->variant)); |
1065 | 0 | if (img->filename) |
1066 | 0 | g_variant_builder_add(&builder, "{sv}", "filename", g_variant_new_string(img->filename)); |
1067 | 0 | if (img->checksum.digest) |
1068 | 0 | g_variant_builder_add(&builder, "{sv}", "checksum", g_variant_new_string(img->checksum.digest)); |
1069 | 0 | if (img->checksum.size) |
1070 | 0 | g_variant_builder_add(&builder, "{sv}", "size", g_variant_new_uint64(img->checksum.size)); |
1071 | |
|
1072 | 0 | if (img->hooks.pre_install) |
1073 | 0 | g_variant_builder_add(&hooks, "s", "pre-install"); |
1074 | 0 | if (img->hooks.install) |
1075 | 0 | g_variant_builder_add(&hooks, "s", "install"); |
1076 | 0 | if (img->hooks.post_install) |
1077 | 0 | g_variant_builder_add(&hooks, "s", "post-install"); |
1078 | 0 | g_variant_builder_add(&builder, "{sv}", "hooks", g_variant_builder_end(&hooks)); |
1079 | |
|
1080 | 0 | if (img->adaptive) |
1081 | 0 | g_variant_builder_add(&builder, "{sv}", "adaptive", g_variant_new_strv((const gchar * const*)(img->adaptive), -1)); |
1082 | |
|
1083 | 0 | if (img->convert) |
1084 | 0 | g_variant_builder_add(&builder, "{sv}", "convert", g_variant_new_strv((const gchar * const*)(img->convert), -1)); |
1085 | 0 | if (img->converted) |
1086 | 0 | g_variant_builder_add(&builder, "{sv}", "converted", g_variant_new_strv((const gchar * const*)(img->converted->pdata), img->converted->len)); |
1087 | |
|
1088 | 0 | g_variant_builder_close(&builder); |
1089 | 0 | } |
1090 | 0 | g_variant_dict_insert(&root_dict, "images", "v", g_variant_builder_end(&builder)); |
1091 | | |
1092 | | /* construct 'meta' nested dicts */ |
1093 | 0 | g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sa{ss}}")); |
1094 | 0 | g_hash_table_iter_init(&iter, manifest->meta); |
1095 | 0 | while (g_hash_table_iter_next(&iter, (gpointer*)&group, (gpointer*)&kvs)) { |
1096 | 0 | GHashTableIter kvs_iter; |
1097 | 0 | const gchar *key, *value; |
1098 | |
|
1099 | 0 | g_variant_builder_open(&builder, G_VARIANT_TYPE("{sa{ss}}")); |
1100 | 0 | g_variant_builder_add(&builder, "s", group); |
1101 | |
|
1102 | 0 | g_variant_builder_open(&builder, G_VARIANT_TYPE("a{ss}")); |
1103 | 0 | g_hash_table_iter_init(&kvs_iter, kvs); |
1104 | 0 | while (g_hash_table_iter_next(&kvs_iter, (gpointer*)&key, (gpointer*)&value)) { |
1105 | 0 | g_variant_builder_open(&builder, G_VARIANT_TYPE("{ss}")); |
1106 | 0 | g_variant_builder_add(&builder, "s", key); |
1107 | 0 | g_variant_builder_add(&builder, "s", value); |
1108 | 0 | g_variant_builder_close(&builder); |
1109 | 0 | } |
1110 | 0 | g_variant_builder_close(&builder); |
1111 | |
|
1112 | 0 | g_variant_builder_close(&builder); |
1113 | 0 | } |
1114 | 0 | g_variant_dict_insert(&root_dict, "meta", "v", g_variant_builder_end(&builder)); |
1115 | |
|
1116 | 0 | return g_variant_dict_end(&root_dict); |
1117 | 0 | } |
1118 | | |
1119 | | gboolean r_manifest_has_artifact_image(const RaucManifest *manifest, const gchar *repo, const gchar *artifact) |
1120 | 0 | { |
1121 | 0 | g_return_val_if_fail(manifest != NULL, FALSE); |
1122 | 0 | g_return_val_if_fail((!repo && !artifact) || |
1123 | 0 | (repo && !artifact) || |
1124 | 0 | (repo && artifact), FALSE); |
1125 | | |
1126 | 0 | for (GList *l = manifest->images; l != NULL; l = l->next) { |
1127 | 0 | const RaucImage *img = l->data; |
1128 | | |
1129 | | /* skip images which are not an artifact */ |
1130 | 0 | if (!img->artifact) |
1131 | 0 | continue; |
1132 | | |
1133 | | /* skip images for different repos */ |
1134 | 0 | if (repo && g_strcmp0(repo, img->slotclass) != 0) |
1135 | 0 | continue; |
1136 | | |
1137 | | /* skip images with different artifact names */ |
1138 | 0 | if (artifact && g_strcmp0(artifact, img->artifact) != 0) |
1139 | 0 | continue; |
1140 | | |
1141 | 0 | return TRUE; |
1142 | 0 | } |
1143 | | |
1144 | 0 | return FALSE; |
1145 | 0 | } |
1146 | | |
1147 | | RaucImage *r_new_image(void) |
1148 | 9.91k | { |
1149 | 9.91k | RaucImage *image = g_new0(RaucImage, 1); |
1150 | | |
1151 | 9.91k | image->checksum.size = -1; |
1152 | | |
1153 | 9.91k | return image; |
1154 | 9.91k | } |
1155 | | |
1156 | | void r_free_image(gpointer data) |
1157 | 9.91k | { |
1158 | 9.91k | RaucImage *image = (RaucImage*) data; |
1159 | | |
1160 | 9.91k | if (!image) |
1161 | 0 | return; |
1162 | | |
1163 | 9.91k | g_free(image->slotclass); |
1164 | 9.91k | g_free(image->artifact); |
1165 | 9.91k | g_free(image->variant); |
1166 | 9.91k | g_free(image->checksum.digest); |
1167 | 9.91k | g_free(image->filename); |
1168 | 9.91k | g_strfreev(image->adaptive); |
1169 | 9.91k | g_strfreev(image->convert); |
1170 | 9.91k | g_clear_pointer(&image->converted, g_ptr_array_unref); |
1171 | 9.91k | g_free(image); |
1172 | 9.91k | } |
1173 | | |
1174 | | void free_manifest(RaucManifest *manifest) |
1175 | 3.06k | { |
1176 | 3.06k | if (!manifest) |
1177 | 0 | return; |
1178 | | |
1179 | 3.06k | g_free(manifest->update_compatible); |
1180 | 3.06k | g_free(manifest->update_version); |
1181 | 3.06k | g_free(manifest->update_description); |
1182 | 3.06k | g_free(manifest->update_build); |
1183 | 3.06k | g_free(manifest->update_min_rauc_version); |
1184 | 3.06k | g_free(manifest->bundle_verity_hash); |
1185 | 3.06k | g_free(manifest->bundle_verity_salt); |
1186 | 3.06k | g_free(manifest->bundle_crypt_key); |
1187 | 3.06k | g_free(manifest->handler_name); |
1188 | 3.06k | g_free(manifest->handler_args); |
1189 | 3.06k | g_free(manifest->hook_name); |
1190 | 3.06k | g_list_free_full(manifest->images, r_free_image); |
1191 | 3.06k | g_clear_pointer(&manifest->meta, g_hash_table_destroy); |
1192 | 3.06k | g_free(manifest->hash); |
1193 | 3.06k | g_clear_pointer(&manifest->warnings, g_ptr_array_unref); |
1194 | 3.06k | g_free(manifest); |
1195 | 3.06k | } |
1196 | | |
1197 | | /** |
1198 | | * Updates checksums for images listed in the manifest and found in |
1199 | | * the bundle directory. |
1200 | | * |
1201 | | * @param manifest pointer to the manifest |
1202 | | * @param dir Directory with the bundle content |
1203 | | * @param error return location for a GError, or NULL |
1204 | | * |
1205 | | * @return TRUE on success, FALSE if an error occurred |
1206 | | */ |
1207 | | static gboolean update_manifest_checksums(RaucManifest *manifest, const gchar *dir, GError **error) |
1208 | 0 | { |
1209 | 0 | GError *ierror = NULL; |
1210 | 0 | gboolean res = TRUE; |
1211 | 0 | gboolean had_errors = FALSE; |
1212 | |
|
1213 | 0 | g_return_val_if_fail(manifest, FALSE); |
1214 | 0 | g_return_val_if_fail(dir, FALSE); |
1215 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
1216 | | |
1217 | 0 | for (GList *elem = manifest->images; elem != NULL; elem = elem->next) { |
1218 | 0 | RaucImage *image = elem->data; |
1219 | 0 | g_autofree gchar *filename = NULL; |
1220 | | |
1221 | | /* If no filename is set (valid for 'install' hook) explicitly set size to -1 */ |
1222 | 0 | if (!image->filename) { |
1223 | 0 | image->checksum.size = -1; |
1224 | 0 | continue; |
1225 | 0 | } |
1226 | | |
1227 | 0 | filename = g_build_filename(dir, image->filename, NULL); |
1228 | 0 | res = compute_checksum(&image->checksum, filename, &ierror); |
1229 | 0 | if (!res) { |
1230 | 0 | g_warning("Failed updating checksum: %s", ierror->message); |
1231 | 0 | g_clear_error(&ierror); |
1232 | 0 | had_errors = TRUE; |
1233 | 0 | break; |
1234 | 0 | } |
1235 | 0 | } |
1236 | |
|
1237 | 0 | if (had_errors) { |
1238 | 0 | res = FALSE; |
1239 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_ERROR_CHECKSUM, "Failed updating all checksums"); |
1240 | 0 | } |
1241 | |
|
1242 | 0 | return res; |
1243 | 0 | } |
1244 | | |
1245 | | gboolean sync_manifest_with_contentdir(RaucManifest *manifest, const gchar *dir, GError **error) |
1246 | 0 | { |
1247 | 0 | g_return_val_if_fail(manifest, FALSE); |
1248 | 0 | g_return_val_if_fail(dir, FALSE); |
1249 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
1250 | | |
1251 | | /* Check for missing image files */ |
1252 | 0 | for (GList *elem = manifest->images; elem != NULL; elem = elem->next) { |
1253 | 0 | RaucImage *image = elem->data; |
1254 | 0 | g_autofree gchar *filename = g_build_filename(dir, image->filename, NULL); |
1255 | 0 | if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { |
1256 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_ERROR_CHECKSUM, "image file '%s' for slot '%s' does not exist in bundle content dir (%s)", image->filename, image->slotclass, dir); |
1257 | 0 | return FALSE; |
1258 | 0 | } |
1259 | 0 | } |
1260 | | |
1261 | | /* Check for missing hook file */ |
1262 | 0 | if (manifest->hook_name) { |
1263 | 0 | g_autofree gchar *hookpath = NULL; |
1264 | 0 | hookpath = g_build_filename(dir, manifest->hook_name, NULL); |
1265 | 0 | if (!g_file_test(hookpath, G_FILE_TEST_EXISTS)) { |
1266 | 0 | g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_ERROR_CHECKSUM, "hook file '%s' does not exist in bundle content dir (%s)", manifest->hook_name, dir); |
1267 | 0 | return FALSE; |
1268 | 0 | } |
1269 | 0 | } |
1270 | | |
1271 | 0 | return update_manifest_checksums(manifest, dir, error); |
1272 | 0 | } |