Coverage Report

Created: 2025-12-14 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rauc/src/artifacts.c
Line
Count
Source
1
#include <errno.h>
2
#include <fcntl.h>
3
4
#include "artifacts.h"
5
6
#include "artifacts_composefs.h"
7
#include "context.h"
8
#include "glib/gstdio.h"
9
#include "slot.h"
10
#include "update_utils.h"
11
#include "utils.h"
12
13
G_DEFINE_QUARK(r-artifacts-error-quark, r_artifacts_error);
14
15
G_GNUC_UNUSED
16
static void show_repo(RArtifactRepo *repo)
17
0
{
18
0
  GHashTableIter iter;
19
20
0
  g_return_if_fail(repo);
21
22
0
  g_debug("repo %s:", repo->name);
23
0
  g_debug("  artifacts:");
24
25
0
  GHashTable *inner = NULL;
26
0
  g_hash_table_iter_init(&iter, repo->artifacts);
27
0
  const gchar *a_name = NULL;
28
0
  while (g_hash_table_iter_next(&iter, (gpointer*)&a_name, (gpointer*)&inner)) {
29
0
    g_debug("    %s (%p):", a_name, a_name);
30
0
    g_assert(a_name == g_intern_string(a_name));
31
32
0
    GHashTableIter inner_iter;
33
0
    g_hash_table_iter_init(&inner_iter, inner);
34
0
    const gchar *a_digest = NULL;
35
0
    RArtifact *artifact = NULL;
36
0
    while (g_hash_table_iter_next(&inner_iter, (gpointer*)&a_digest, (gpointer*)&artifact)) {
37
0
      g_debug("      %s (%p):", a_digest, a_digest);
38
39
0
      g_assert(a_name == artifact->name); /* intern strings */
40
0
      g_assert(a_digest == g_intern_string(artifact->checksum.digest)); /* intern strings */
41
42
0
      for (guint i = 0; i < artifact->references->len; i++) {
43
0
        const gchar *parent = g_ptr_array_index(artifact->references, i);
44
0
        g_debug("        referenced by: '%s'", parent);
45
0
        g_assert(parent == g_intern_string(parent));
46
0
      }
47
0
    }
48
0
  }
49
0
}
50
51
void r_artifact_free(gpointer value)
52
0
{
53
0
  RArtifact *artifact = (RArtifact*)value;
54
55
0
  if (!artifact)
56
0
    return;
57
58
0
  g_free(artifact->bundle_compatible);
59
0
  g_free(artifact->bundle_version);
60
0
  g_free(artifact->bundle_description);
61
0
  g_free(artifact->bundle_build);
62
0
  g_free(artifact->bundle_hash);
63
0
  g_free(artifact->checksum.digest);
64
0
  g_ptr_array_free(artifact->references, TRUE);
65
0
  g_free(artifact->path);
66
0
  g_free(artifact->path_tmp);
67
0
  g_free(artifact);
68
0
}
69
70
void r_artifact_repo_free(gpointer value)
71
0
{
72
0
  RArtifactRepo *repo = (RArtifactRepo*)value;
73
74
0
  if (!repo)
75
0
    return;
76
77
0
  if (g_strcmp0(repo->type, "composefs") == 0) {
78
0
    g_clear_pointer(&repo->composefs.local_store_objects, g_hash_table_destroy);
79
0
  }
80
81
0
  g_free(repo->description);
82
0
  g_free(repo->path);
83
0
  g_free(repo->type);
84
0
  g_free(repo->data_directory);
85
0
  g_clear_pointer(&repo->artifacts, g_hash_table_destroy);
86
0
  if (repo->possible_references)
87
0
    g_ptr_array_free(repo->possible_references, TRUE);
88
0
  g_free(repo);
89
0
}
90
91
typedef struct {
92
  const gchar *name;
93
} RArtifactRepoType;
94
95
RArtifactRepoType supported_repo_types[] = {
96
  {"files"},
97
  {"trees"},
98
#if ENABLE_COMPOSEFS == 1
99
  {"composefs"},
100
#endif
101
  {},
102
};
103
104
gboolean r_artifact_repo_is_valid_type(const gchar *type)
105
0
{
106
0
  for (RArtifactRepoType *repo_type = supported_repo_types; repo_type->name != NULL; repo_type++) {
107
0
    if (g_strcmp0(type, repo_type->name) == 0) {
108
0
      return TRUE;
109
0
    }
110
0
  }
111
112
0
  return FALSE;
113
0
}
114
115
gboolean r_artifact_repo_insert(RArtifactRepo *repo, RArtifact *artifact, GError **error)
116
0
{
117
0
  g_return_val_if_fail(repo, FALSE);
118
0
  g_return_val_if_fail(artifact, FALSE);
119
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
120
121
0
  g_assert(repo->artifacts);
122
0
  g_assert(artifact->repo == NULL);
123
0
  g_assert(artifact->path == NULL);
124
0
  g_assert(artifact->path_tmp == NULL);
125
0
  g_assert(g_quark_try_string(artifact->name));
126
127
0
  artifact->repo = repo;
128
0
  artifact->path = g_strdup_printf("%s/.artifact-%s-%s", repo->path, artifact->name, artifact->checksum.digest);
129
0
  artifact->path_tmp = g_strdup_printf("%s.tmp", artifact->path);
130
131
  /* the artifact may exist already (for calls from _prepare) or not (for
132
   * calls from installation)
133
   *
134
   * TODO perhaps refactor this into explicit functions for each case?
135
   **/
136
0
  g_assert(!g_file_test(artifact->path, G_FILE_TEST_IS_SYMLINK));
137
0
  g_assert(!g_file_test(artifact->path_tmp, G_FILE_TEST_IS_SYMLINK));
138
139
0
  GHashTable *inner = g_hash_table_lookup(repo->artifacts, artifact->name);
140
0
  if (!inner) {
141
0
    inner = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)r_artifact_free);
142
0
    g_hash_table_insert(repo->artifacts, (gpointer)artifact->name, inner);
143
0
  }
144
145
0
  if (g_hash_table_lookup(inner, g_intern_string(artifact->checksum.digest))) {
146
0
    g_set_error(
147
0
        error,
148
0
        R_ARTIFACTS_ERROR,
149
0
        R_ARTIFACTS_ERROR_DUPLICATE,
150
0
        "Failed to insert artifact '%s' into repo '%s', as it exists already.",
151
0
        artifact->name, repo->name);
152
0
    return FALSE;
153
0
  }
154
0
  g_hash_table_insert(inner, (gpointer)g_intern_string(artifact->checksum.digest), artifact);
155
156
0
  g_message("Inserted artifact into repo '%s': '%s' %s", repo->name, artifact->name, artifact->checksum.digest);
157
158
0
  return TRUE;
159
0
}
160
161
/**
162
 * Reads all potential symlinks in repo and resolves them to their target artifact.
163
 *
164
 * In a valid artifact repo, all non-hidden files should be symlinks to an
165
 * internal artifact path (.artifact-<name>-<hash>).
166
 *
167
 * @param repo The repo to read
168
 * @param parent parent name or "" for no parent
169
 * @param[out] error Return location for a GError, or NULL
170
 *
171
 * @return TRUE if successful, FALSE otherwise
172
 */
173
static gboolean artifact_repo_read_links(RArtifactRepo *repo, const gchar *parent, GError **error)
174
0
{
175
0
  GError *ierror = NULL;
176
177
0
  g_return_val_if_fail(repo, FALSE);
178
0
  g_return_val_if_fail(parent, FALSE);
179
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
180
181
0
  parent = g_intern_string(parent);
182
183
0
  g_return_val_if_fail(g_ptr_array_find(repo->possible_references, parent, NULL), FALSE);
184
185
0
  g_autoptr(GRegex) artifact_regex = g_regex_new(
186
0
      strlen(parent) ? "^\\.\\./\\.artifact-(.*)-([0-9a-f]+)$" : "^\\.artifact-(.*)-([0-9a-f]+)$",
187
0
      0,
188
0
      0,
189
0
      &ierror);
190
0
  g_assert_no_error(ierror);
191
192
0
  g_autofree gchar *path = g_build_filename(repo->path, parent, NULL);
193
0
  g_autoptr(GDir) dir = g_dir_open(path, 0, &ierror);
194
0
  if (dir == NULL) {
195
    /* all parent directories need to exist */
196
0
    g_propagate_error(error, ierror);
197
0
    return FALSE;
198
0
  }
199
200
0
  show_repo(repo);
201
0
  const gchar *name;
202
0
  while ((name = g_dir_read_name(dir))) {
203
    /* skip .artifact-* entries which are not supposed to be artifact symlinks */
204
0
    if (g_str_has_prefix(name, "."))
205
0
      continue;
206
207
0
    g_autofree gchar *entry_path = g_build_filename(path, name, NULL);
208
0
    g_autofree gchar *target = g_file_read_link(entry_path, &ierror);
209
0
    if (target == NULL) {
210
0
      g_warning("invalid artifact link %s in repo '%s' (%s)", entry_path, repo->name, ierror->message);
211
0
      g_clear_error(&ierror);
212
0
      continue;
213
0
    }
214
215
0
    g_autoptr(GMatchInfo) match = NULL;
216
0
    if (!g_regex_match(artifact_regex, target, 0, &match)) {
217
0
      g_warning("invalid artifact link %s in repo '%s' (invalid target '%s')", entry_path, repo->name, target);
218
0
      continue;
219
0
    }
220
221
0
    g_autofree gchar *a_name = g_match_info_fetch(match, 1);
222
0
    g_autofree gchar *a_digest = g_match_info_fetch(match, 2);
223
224
0
    GHashTable *inner = g_hash_table_lookup(repo->artifacts, g_intern_string(a_name));
225
0
    if (inner == NULL) {
226
0
      g_warning("invalid artifact link %s in repo '%s' (unknown artifact name '%s')", entry_path, repo->name, a_name);
227
0
      continue;
228
0
    }
229
230
0
    RArtifact *artifact = g_hash_table_lookup(inner, g_intern_string(a_digest));
231
0
    if (artifact == NULL) {
232
0
      g_warning("invalid artifact link %s in repo '%s' (unknown artifact digest '%s')", entry_path, repo->name, a_digest);
233
0
      continue;
234
0
    }
235
236
0
    g_ptr_array_add(artifact->references, (gpointer)parent);
237
0
  }
238
239
0
  return TRUE;
240
0
}
241
242
gboolean r_artifact_repo_prepare(RArtifactRepo *repo, GError **error)
243
0
{
244
0
  GError *ierror = NULL;
245
246
0
  g_return_val_if_fail(repo, FALSE);
247
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
248
249
0
  if (repo->artifacts)
250
0
    g_hash_table_destroy(repo->artifacts);
251
0
  if (repo->possible_references)
252
0
    g_ptr_array_free(repo->possible_references, TRUE);
253
254
0
  g_autoptr(GRegex) artifact_regex = g_regex_new(
255
0
      "^\\.artifact-(.*)-([0-9a-f]+)$",
256
0
      0,
257
0
      0,
258
0
      &ierror);
259
0
  g_assert_no_error(ierror);
260
261
0
  g_autoptr(GDir) dir = g_dir_open(repo->path, 0, &ierror);
262
0
  if (dir == NULL) {
263
0
    g_propagate_error(error, ierror);
264
0
    return FALSE;
265
0
  }
266
267
0
  repo->artifacts = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_hash_table_destroy);
268
269
  /* build RArtifacts from .artifact-<name>-<digest> entries */
270
0
  const gchar *entry_name;
271
0
  while ((entry_name = g_dir_read_name(dir))) {
272
0
    g_autoptr(GMatchInfo) match = NULL;
273
0
    if (!g_regex_match(artifact_regex, entry_name, 0, &match))
274
0
      continue;
275
276
0
    g_autoptr(RArtifact) artifact = g_new0(RArtifact, 1);
277
0
    g_autofree gchar *name = g_match_info_fetch(match, 1);
278
0
    artifact->name = g_intern_string(name);
279
0
    artifact->checksum.digest = g_match_info_fetch(match, 2);
280
0
    artifact->references = g_ptr_array_new();
281
282
    /* TODO load status information, analogous to slot status */
283
284
0
    if (r_artifact_repo_insert(repo, artifact, &ierror)) {
285
0
      artifact = NULL;
286
0
    } else {
287
0
      g_propagate_error(error, ierror);
288
0
      return FALSE;
289
0
    }
290
0
  }
291
292
0
  repo->possible_references = g_ptr_array_new();
293
294
0
  if (repo->parent_class) {
295
0
    g_autoptr(GList) parent_slots = r_slot_get_all_of_class(r_context()->config->slots, repo->parent_class);
296
297
0
    for (GList *l = parent_slots; l != NULL; l = l->next) {
298
0
      const RaucSlot *parent_slot = l->data;
299
0
      g_autofree gchar *symlink_dir = g_build_filename(repo->path, parent_slot->name, NULL);
300
301
      /* mode 0755, as access can be controlled at the repo directory level */
302
0
      if (g_mkdir_with_parents(symlink_dir, 0755) != 0) {
303
0
        int err = errno;
304
0
        g_set_error(
305
0
            error,
306
0
            G_FILE_ERROR,
307
0
            g_file_error_from_errno(err),
308
0
            "Failed to create artifact directory '%s': %s",
309
0
            symlink_dir,
310
0
            g_strerror(err));
311
0
        return FALSE;
312
0
      }
313
314
0
      g_ptr_array_add(repo->possible_references, (gpointer)g_intern_string(parent_slot->name));
315
316
0
      if (!artifact_repo_read_links(repo, parent_slot->name, &ierror)) {
317
0
        g_propagate_error(error, ierror);
318
0
        return FALSE;
319
0
      }
320
0
    }
321
0
  } else { /* no parent */
322
0
    g_ptr_array_add(repo->possible_references, (gpointer)g_intern_static_string(""));
323
324
0
    if (!artifact_repo_read_links(repo, "", &ierror)) {
325
0
      g_propagate_error(error, ierror);
326
0
      return FALSE;
327
0
    }
328
0
  }
329
330
0
  if (g_strcmp0(repo->type, "composefs") == 0) {
331
0
    if (!r_composefs_artifact_repo_prepare(repo, &ierror)) {
332
0
      g_propagate_error(error, ierror);
333
0
      return FALSE;
334
0
    }
335
0
  }
336
337
0
  return TRUE;
338
0
}
339
340
static gboolean artifact_repo_prune_subdir(RArtifactRepo *repo, const gchar *parent, GError **error)
341
0
{
342
0
  GError *ierror = NULL;
343
344
0
  g_autofree gchar *path = g_build_filename(repo->path, parent, NULL);
345
0
  g_autoptr(GDir) dir = g_dir_open(path, 0, &ierror);
346
0
  if (dir == NULL) {
347
0
    g_propagate_error(error, ierror);
348
0
    return TRUE;
349
0
  }
350
351
0
  const gchar *name;
352
0
  while ((name = g_dir_read_name(dir))) {
353
0
    g_autofree gchar *full_name = g_build_filename(path, name, NULL);
354
355
    /* symlinks are expected */
356
0
    if (g_file_test(full_name, G_FILE_TEST_IS_SYMLINK))
357
0
      continue;
358
359
0
    g_message("Removing unexpected data in artifact subdir repo: %s", full_name);
360
0
    if (!rm_tree(full_name, &ierror)) {
361
0
      g_propagate_error(error, ierror);
362
0
      return FALSE;
363
0
    }
364
0
  }
365
366
0
  return TRUE;
367
0
}
368
369
gboolean r_artifact_repo_prune(RArtifactRepo *repo, GError **error)
370
0
{
371
0
  GError *ierror = NULL;
372
373
0
  g_return_val_if_fail(repo, FALSE);
374
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
375
376
0
  g_debug("pruning repo '%s' at '%s'", repo->name, repo->path);
377
378
0
  g_autoptr(GRegex) artifact_regex = g_regex_new(
379
0
      "^\\.artifact-(.*)-([0-9a-f]+)$",
380
0
      0,
381
0
      0,
382
0
      &ierror);
383
0
  g_assert_no_error(ierror);
384
385
0
  if (repo->parent_class) {
386
0
    g_autoptr(GList) parent_slots = r_slot_get_all_of_class(r_context()->config->slots, repo->parent_class);
387
388
0
    for (GList *l = parent_slots; l != NULL; l = l->next) {
389
0
      const RaucSlot *parent_slot = l->data;
390
391
0
      if (!artifact_repo_prune_subdir(repo, parent_slot->name, &ierror)) {
392
0
        g_propagate_prefixed_error(
393
0
            error,
394
0
            ierror,
395
0
            "Failed to prune parent subdirectory '%s' in repo '%s':",
396
0
            parent_slot->name,
397
0
            repo->name
398
0
            );
399
0
        return FALSE;
400
0
      }
401
0
    }
402
0
  }
403
404
0
  g_autoptr(GDir) dir = g_dir_open(repo->path, 0, &ierror);
405
0
  if (dir == NULL) {
406
0
    g_propagate_error(error, ierror);
407
0
    return FALSE;
408
0
  }
409
410
  /* remove unexpected data (non-symlinks that do not match internal arifact pattern) */
411
0
  const gchar *name;
412
0
  while ((name = g_dir_read_name(dir))) {
413
0
    g_autofree gchar *full_name = g_build_filename(repo->path, name, NULL);
414
0
    g_autoptr(GMatchInfo) match = NULL;
415
416
0
    if (g_regex_match(artifact_regex, name, 0, &match))
417
0
      continue;
418
419
0
    if (repo->parent_class) {
420
      /* only parent subdir should remain */
421
0
      if (g_ptr_array_find(repo->possible_references, g_intern_string(name), NULL) &&
422
0
          g_file_test(full_name, G_FILE_TEST_IS_DIR) &&
423
0
          !g_file_test(full_name, G_FILE_TEST_IS_SYMLINK))
424
0
        continue;
425
0
    } else { /* no parent */
426
      /* symlinks are expected */
427
0
      if (g_file_test(full_name, G_FILE_TEST_IS_SYMLINK))
428
0
        continue;
429
0
    }
430
431
    /* allow repo type specific files and dirs */
432
0
    if (g_strcmp0(repo->type, "composefs") == 0) {
433
0
      if (g_strcmp0(name, ".rauc-cfs-store") == 0)
434
0
        continue;
435
0
    }
436
437
0
    g_message("Removing unexpected data in artifact repo: %s", full_name);
438
0
    if (!rm_tree(full_name, &ierror)) {
439
0
      g_propagate_error(error, ierror);
440
0
      return FALSE;
441
0
    }
442
0
  }
443
444
  /* remove unused artifacts */
445
0
  GHashTableIter iter;
446
0
  GHashTable *inner = NULL;
447
0
  g_hash_table_iter_init(&iter, repo->artifacts);
448
0
  while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&inner)) {
449
0
    GHashTableIter inner_iter;
450
0
    RArtifact *artifact = NULL;
451
452
0
    g_hash_table_iter_init(&inner_iter, inner);
453
0
    while (g_hash_table_iter_next(&inner_iter, NULL, (gpointer*)&artifact)) {
454
0
      if (artifact->references->len)
455
0
        continue;
456
457
0
      g_debug("Checking for open files in %s", artifact->path);
458
      /* do not remove artifacts which are in use */
459
0
      if (!r_tree_check_open(artifact->path, &ierror)) {
460
0
        if (g_error_matches(ierror, R_UTILS_ERROR, R_UTILS_ERROR_OPEN_FILE)) {
461
0
          g_message("Skipping removal of artifact '%s' with hash '%s' from repo '%s': %s",
462
0
              artifact->name, artifact->checksum.digest, repo->name, ierror->message
463
0
              );
464
0
          g_clear_error(&ierror);
465
0
          continue;
466
0
        }
467
0
        g_propagate_error(error, ierror);
468
0
        return FALSE;
469
0
      }
470
471
0
      g_message("Removing unused artifact '%s' with hash '%s' from repo '%s'",
472
0
          artifact->name, artifact->checksum.digest, repo->name
473
0
          );
474
475
0
      if (!rm_tree(artifact->path, &ierror)) {
476
0
        g_propagate_error(error, ierror);
477
0
        return FALSE;
478
0
      }
479
480
0
      g_hash_table_iter_remove(&inner_iter);
481
0
    }
482
483
0
    if (!g_hash_table_size(inner))
484
0
      g_hash_table_iter_remove(&iter);
485
0
  }
486
487
0
  if (g_strcmp0(repo->type, "composefs") == 0) {
488
0
    if (!r_composefs_artifact_repo_prune(repo, &ierror)) {
489
0
      g_propagate_error(error, ierror);
490
0
      return FALSE;
491
0
    }
492
0
  }
493
494
0
  return TRUE;
495
0
}
496
497
gboolean r_artifact_repo_commit(RArtifactRepo *repo, GError **error)
498
0
{
499
0
  GError *ierror = NULL;
500
501
0
  g_return_val_if_fail(repo, FALSE);
502
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
503
504
0
  g_debug("committing repo '%s' at '%s'", repo->name, repo->path);
505
506
  /* update symlinks for each parent */
507
0
  for (guint i = 0; i < repo->possible_references->len; i++) {
508
0
    const gchar *parent = g_ptr_array_index(repo->possible_references, i);
509
510
0
    GHashTableIter iter;
511
0
    g_hash_table_iter_init(&iter, repo->artifacts);
512
0
    GHashTable *inner = NULL;
513
0
    const gchar *a_name = NULL;
514
0
    while (g_hash_table_iter_next(&iter, (gpointer*)&a_name, (gpointer*)&inner)) {
515
      /* build symlink name */
516
0
      g_autofree gchar *symlink = g_build_filename(
517
0
          repo->path,
518
0
          parent, /* can be "" if repo has no parent and is ignored in that case */
519
0
          a_name,
520
0
          NULL
521
0
          );
522
0
      g_debug("link for %s would be %s", a_name, symlink);
523
524
0
      GHashTableIter inner_iter;
525
0
      g_hash_table_iter_init(&inner_iter, inner);
526
0
      RArtifact *artifact = NULL;
527
0
      g_autofree gchar *target = NULL;
528
0
      while (g_hash_table_iter_next(&inner_iter, NULL, (gpointer*)&artifact)) {
529
0
        g_assert(artifact->references != NULL);
530
0
        g_assert(g_file_test(artifact->path, G_FILE_TEST_EXISTS));
531
0
        g_assert(repo->possible_references->len > 0);
532
533
0
        gboolean enabled = g_ptr_array_find(artifact->references, parent, NULL);
534
0
        if (enabled) {
535
0
          gboolean has_parent = g_strcmp0(parent, "") != 0;
536
          /* build artifact target name */
537
0
          target = g_strdup_printf(
538
0
              "%s.artifact-%s-%s",
539
0
              has_parent ? "../" : "",
540
0
              artifact->name,
541
0
              artifact->checksum.digest
542
0
              );
543
0
          break;
544
0
        } else {
545
0
          g_debug("disabled");
546
0
        }
547
0
      }
548
549
0
      if (target) {
550
0
        if (!r_update_symlink(target, symlink, &ierror)) {
551
0
          g_propagate_error(error, ierror);
552
0
          return FALSE;
553
0
        }
554
0
      } else {
555
0
        if (unlink(symlink) == -1) {
556
0
          int err = errno;
557
0
          if (err == ENOENT)
558
0
            continue;
559
0
          g_set_error(error,
560
0
              G_FILE_ERROR,
561
0
              g_file_error_from_errno(err),
562
0
              "Failed to remove symlink: %s", g_strerror(err));
563
0
          return FALSE;
564
0
        }
565
0
      }
566
0
    }
567
0
  }
568
569
  /* TODO save additional meta-data for artifacts and instances here? */
570
571
0
  if (!r_syncfs(repo->path, &ierror)) {
572
0
    g_propagate_error(error, ierror);
573
0
    return FALSE;
574
0
  }
575
576
0
  show_repo(repo);
577
578
0
  return TRUE;
579
0
}
580
581
gboolean r_artifacts_init(GError **error)
582
0
{
583
0
  GError *ierror = NULL;
584
585
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
586
587
0
  g_assert(r_context()->config);
588
0
  g_assert(r_context()->config->artifact_repos);
589
590
0
  GHashTableIter iter;
591
0
  g_hash_table_iter_init(&iter, r_context()->config->artifact_repos);
592
0
  RArtifactRepo *repo;
593
0
  while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&repo)) {
594
0
    if (!r_artifact_repo_prepare(repo, &ierror)) {
595
0
      g_propagate_error(error, ierror);
596
0
      return FALSE;
597
0
    }
598
0
  }
599
600
0
  return TRUE;
601
0
}
602
603
gboolean r_artifacts_prune(GError **error)
604
0
{
605
0
  GError *ierror = NULL;
606
607
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
608
609
0
  g_assert(r_context()->config);
610
0
  g_assert(r_context()->config->artifact_repos);
611
612
0
  GHashTableIter iter;
613
0
  g_hash_table_iter_init(&iter, r_context()->config->artifact_repos);
614
0
  RArtifactRepo *repo;
615
0
  while (g_hash_table_iter_next(&iter, NULL, (gpointer*)&repo)) {
616
0
    if (!r_artifact_repo_prune(repo, &ierror)) {
617
0
      g_propagate_error(error, ierror);
618
0
      return FALSE;
619
0
    }
620
0
  }
621
622
0
  return TRUE;
623
0
}
624
625
/*
626
 * aa{sv}  // Array of dictionaries, where each dictionary represents a repository
627
 * [
628
 *     {
629
 *         "name": s,           // string: Name of the repository
630
 *         "description": s,    // string: Optional description of the repository
631
 *         "path": s,           // string: Filesystem path to the repository
632
 *         "type": s,           // string: Type of the repository
633
 *         "parent-class": s,   // string: Optional parent class of the repository
634
 *         "artifacts": aa{sv}  // Array of artifact dictionaries
635
 *         [
636
 *             {
637
 *                 "name": s,           // string: Name of the artifact
638
 *                 "checksums:" aa{sv}  // array of artifact instance dictionaries
639
 *                 [
640
 *                     {
641
 *                         "checksum": s,   // string: Checksum of the artifact
642
 *                         "references": as // array of strings: Optional references for the artifact
643
 *                     },
644
 *                     {
645
 *                         "checksum": s,   // string: Checksum of the artifact
646
 *                         "references": as // array of strings: Optional references for the artifact
647
 *                     },
648
 *                 ...
649
 *             }
650
 *         ]
651
 *     },
652
 *     ...
653
 * ]
654
 */
655
GVariant *r_artifacts_to_dict(void)
656
0
{
657
0
  g_assert(r_context()->config);
658
0
  g_assert(r_context()->config->artifact_repos);
659
660
0
  GHashTableIter repo_iter;
661
0
  g_hash_table_iter_init(&repo_iter, r_context()->config->artifact_repos);
662
0
  RArtifactRepo *repo;
663
0
  g_auto(GVariantBuilder) repos_builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("aa{sv}"));
664
0
  while (g_hash_table_iter_next(&repo_iter, NULL, (gpointer*)&repo)) {
665
0
    g_variant_builder_open(&repos_builder, G_VARIANT_TYPE("a{sv}"));
666
667
0
    g_variant_builder_add(&repos_builder, "{sv}", "name", g_variant_new_string(repo->name));
668
0
    if (repo->description)
669
0
      g_variant_builder_add(&repos_builder, "{sv}", "description", g_variant_new_string(repo->description));
670
0
    if (repo->path)
671
0
      g_variant_builder_add(&repos_builder, "{sv}", "path", g_variant_new_string(repo->path));
672
0
    if (repo->type)
673
0
      g_variant_builder_add(&repos_builder, "{sv}", "type", g_variant_new_string(repo->type));
674
0
    if (repo->parent_class)
675
0
      g_variant_builder_add(&repos_builder, "{sv}", "parent-class", g_variant_new_string(repo->parent_class));
676
677
0
    GHashTableIter a_iter_name;
678
0
    g_hash_table_iter_init(&a_iter_name, repo->artifacts);
679
0
    GHashTable *inner = NULL;
680
0
    g_auto(GVariantBuilder) artifacts_builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("aa{sv}"));
681
0
    const gchar *a_name = NULL;
682
0
    while (g_hash_table_iter_next(&a_iter_name, (gpointer*)&a_name, (gpointer*)&inner)) {
683
0
      g_variant_builder_open(&artifacts_builder, G_VARIANT_TYPE("a{sv}"));
684
0
      g_variant_builder_add(&artifacts_builder, "{sv}", "name", g_variant_new_string(a_name));
685
686
0
      GHashTableIter a_iter_digest;
687
0
      g_hash_table_iter_init(&a_iter_digest, inner);
688
0
      RArtifact *artifact = NULL;
689
0
      g_auto(GVariantBuilder) instances_builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("aa{sv}"));
690
0
      while (g_hash_table_iter_next(&a_iter_digest, NULL, (gpointer*)&artifact)) {
691
0
        g_variant_builder_open(&instances_builder, G_VARIANT_TYPE("a{sv}"));
692
693
0
        g_variant_builder_add(&instances_builder, "{sv}", "checksum", g_variant_new_string(artifact->checksum.digest));
694
        /* TODO add bundle metadata */
695
0
        g_variant_builder_add(&instances_builder, "{sv}", "references",
696
0
            g_variant_new_strv((const gchar **)artifact->references->pdata, artifact->references->len));
697
698
0
        g_variant_builder_close(&instances_builder);
699
0
      }
700
0
      g_variant_builder_add(&artifacts_builder, "{sv}", "instances", g_variant_builder_end(&instances_builder));
701
702
0
      g_variant_builder_close(&artifacts_builder);
703
0
    }
704
0
    g_variant_builder_add(&repos_builder, "{sv}", "artifacts", g_variant_builder_end(&artifacts_builder));
705
706
0
    g_variant_builder_close(&repos_builder);
707
0
  }
708
709
0
  return g_variant_builder_end(&repos_builder);
710
0
}
711
712
static gboolean files_artifact_install(const RArtifact *artifact, const RaucImage *image, GError **error)
713
0
{
714
0
  GError *ierror = NULL;
715
716
0
  g_return_val_if_fail(artifact, FALSE);
717
0
  g_return_val_if_fail(image, FALSE);
718
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
719
720
  /* open input image as stream */
721
0
  int in_fd = -1;
722
0
  g_autoptr(GUnixInputStream) in_stream = r_open_unix_input_stream(image->filename, &in_fd, &ierror);
723
0
  if (!in_stream) {
724
0
    g_propagate_error(error, ierror);
725
0
    return FALSE;
726
0
  }
727
728
  /* open output artifact as stream */
729
0
  int out_fd = -1;
730
0
  g_autoptr(GUnixOutputStream) out_stream = r_unix_output_stream_create_file(artifact->path_tmp, &out_fd, &ierror);
731
0
  if (!out_stream) {
732
0
    g_propagate_error(error, ierror);
733
0
    return FALSE;
734
0
  }
735
736
  /* call copy with progress */
737
0
  if (!r_copy_stream_with_progress(G_INPUT_STREAM(in_stream), G_OUTPUT_STREAM(out_stream), image->checksum.size, &ierror)) {
738
0
    g_propagate_error(error, ierror);
739
0
    return FALSE;
740
0
  }
741
742
  /* flush to output before closing to assure content is written to disk */
743
0
  if (fsync(out_fd) == -1) {
744
0
    int err = errno;
745
0
    g_set_error(error, R_ARTIFACTS_ERROR, R_ARTIFACTS_ERROR_INSTALL, "Syncing content to disk failed: %s", strerror(err));
746
0
    return FALSE;
747
0
  }
748
0
  if (!g_output_stream_close(G_OUTPUT_STREAM(out_stream), NULL, &ierror)) {
749
0
    g_propagate_error(error, ierror);
750
0
    return FALSE;
751
0
  }
752
753
0
  return TRUE;
754
0
}
755
756
static gboolean tree_artifact_install_tar(const RArtifact *artifact, const RaucImage *image, const gchar *name, GError **error)
757
0
{
758
0
  GError *ierror = NULL;
759
760
0
  g_return_val_if_fail(artifact, FALSE);
761
0
  g_return_val_if_fail(image, FALSE);
762
0
  g_return_val_if_fail(name, FALSE);
763
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
764
765
0
  if (g_mkdir(artifact->path_tmp, S_IRWXU)) {
766
0
    int err = errno;
767
0
    g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(err),
768
0
        "Failed to create directory '%s': %s", artifact->path_tmp, g_strerror(err));
769
0
    return FALSE;
770
0
  }
771
772
0
  g_autoptr(GPtrArray) args = g_ptr_array_new_full(10, g_free);
773
0
  g_ptr_array_add(args, g_strdup("tar"));
774
0
  g_ptr_array_add(args, g_strdup("--numeric-owner"));
775
0
  g_ptr_array_add(args, g_strdup("--acl"));
776
0
  g_ptr_array_add(args, g_strdup("--selinux"));
777
0
  g_ptr_array_add(args, g_strdup("--xattrs"));
778
0
  g_ptr_array_add(args, g_strdup("-xf"));
779
0
  g_ptr_array_add(args, g_strdup(name));
780
0
  g_ptr_array_add(args, g_strdup("-C"));
781
0
  g_ptr_array_add(args, g_strdup(artifact->path_tmp));
782
0
  g_ptr_array_add(args, NULL);
783
784
0
  if (!r_subprocess_runv(args, G_SUBPROCESS_FLAGS_NONE, &ierror)) {
785
0
    g_propagate_prefixed_error(error, ierror, "Failed to extract archive (tar -xf): ");
786
0
    return FALSE;
787
0
  }
788
0
  return TRUE;
789
0
}
790
791
/* also used by composefs for the metadata */
792
gboolean r_tree_artifact_install_extracted(const RArtifact *artifact, const RaucImage *image, const gchar *name, GError **error)
793
0
{
794
0
  GError *ierror = NULL;
795
796
0
  g_return_val_if_fail(artifact, FALSE);
797
0
  g_return_val_if_fail(image, FALSE);
798
0
  g_return_val_if_fail(name, FALSE);
799
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
800
801
0
  g_autoptr(GPtrArray) args = g_ptr_array_new_full(5, g_free);
802
0
  g_ptr_array_add(args, g_strdup("cp"));
803
0
  g_ptr_array_add(args, g_strdup("-a"));
804
0
  g_ptr_array_add(args, g_strdup(name));
805
0
  g_ptr_array_add(args, g_strdup(artifact->path_tmp));
806
0
  g_ptr_array_add(args, NULL);
807
808
0
  if (!r_subprocess_runv(args, G_SUBPROCESS_FLAGS_NONE, &ierror)) {
809
0
    g_propagate_prefixed_error(error, ierror, "Failed to copy tree (cp -a): ");
810
0
    return FALSE;
811
0
  }
812
0
  return TRUE;
813
0
}
814
815
/* Try to find the converted output for the given method. */
816
static gboolean artifact_get_converted(const RaucImage *image, const gchar *method, gchar **name)
817
0
{
818
0
  g_return_val_if_fail(image, FALSE);
819
0
  g_return_val_if_fail(method, FALSE);
820
0
  g_return_val_if_fail(name != NULL && *name == NULL, FALSE);
821
822
0
  if (!image->convert || image->converted->len == 0)
823
0
    return FALSE;
824
825
0
  guint len = g_strv_length(image->convert);
826
827
0
  if (len != image->converted->len)
828
0
    return FALSE;
829
830
  /* search for the correct method */
831
0
  for (guint i = 0; i < len; i++) {
832
    /* return the corresponding converted name */
833
0
    if (g_strcmp0(image->convert[i], method) == 0) {
834
0
      *name = g_strdup(image->converted->pdata[i]);
835
0
      return TRUE;
836
0
    }
837
0
  }
838
839
0
  return FALSE;
840
0
}
841
842
RArtifact *r_artifact_find(const RArtifactRepo *repo, const gchar *name, const gchar *digest)
843
0
{
844
0
  g_return_val_if_fail(repo, NULL);
845
0
  g_return_val_if_fail(name, NULL);
846
0
  g_return_val_if_fail(digest, NULL);
847
848
0
  name = g_intern_string(name);
849
0
  digest = g_intern_string(digest);
850
851
0
  GHashTable *inner = g_hash_table_lookup(repo->artifacts, name);
852
0
  if (!inner)
853
0
    return NULL;
854
855
0
  return g_hash_table_lookup(inner, digest);
856
0
}
857
858
gboolean r_artifact_install(const RArtifact *artifact, const RaucImage *image, GError **error)
859
0
{
860
0
  GError *ierror = NULL;
861
862
0
  g_return_val_if_fail(artifact, FALSE);
863
0
  g_return_val_if_fail(artifact->path, FALSE);
864
0
  g_return_val_if_fail(artifact->path_tmp, FALSE);
865
0
  g_return_val_if_fail(image, FALSE);
866
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
867
868
0
  g_assert(!g_file_test(artifact->path, G_FILE_TEST_EXISTS));
869
0
  g_assert(!g_file_test(artifact->path_tmp, G_FILE_TEST_EXISTS));
870
0
  g_assert(g_path_is_absolute(image->filename));
871
872
0
  if (g_strcmp0(artifact->repo->type, "files") == 0) {
873
0
    if (!files_artifact_install(artifact, image, &ierror)) {
874
0
      g_propagate_error(error, ierror);
875
0
      return FALSE;
876
0
    }
877
0
  } else if (g_strcmp0(artifact->repo->type, "trees") == 0) {
878
0
    g_autofree gchar *converted = NULL;
879
0
    if (artifact_get_converted(image, "tar-extract", &converted)) {
880
0
      if (!r_tree_artifact_install_extracted(artifact, image, converted, &ierror)) {
881
0
        g_propagate_error(error, ierror);
882
0
        return FALSE;
883
0
      }
884
0
    } else {
885
      /* fall back to either the 'keep' method or the original file */
886
0
      if (!artifact_get_converted(image, "keep", &converted)) {
887
0
        if (!g_file_test(image->filename, G_FILE_TEST_EXISTS)) {
888
0
          g_set_error(error, R_ARTIFACTS_ERROR, R_ARTIFACTS_ERROR_INSTALL,
889
0
              "Image '%s/%s has unsupported format for repo type '%s'",
890
0
              image->slotclass, image->artifact, artifact->repo->type);
891
0
          return FALSE;
892
0
        }
893
0
        converted = g_strdup(image->filename);
894
0
      }
895
896
0
      if (!tree_artifact_install_tar(artifact, image, converted, &ierror)) {
897
0
        g_propagate_error(error, ierror);
898
0
        return FALSE;
899
0
      }
900
0
    }
901
0
  } else if (g_strcmp0(artifact->repo->type, "composefs") == 0) {
902
0
    g_autofree gchar *converted = NULL;
903
0
    if (artifact_get_converted(image, "composefs", &converted)) {
904
0
      if (!r_composefs_artifact_install(artifact, image, converted, &ierror)) {
905
0
        g_propagate_error(error, ierror);
906
0
        return FALSE;
907
0
      }
908
0
    } else {
909
0
      g_set_error(error, R_ARTIFACTS_ERROR, R_ARTIFACTS_ERROR_INSTALL,
910
0
          "Image '%s/%s' has unsupported format for repo type '%s'",
911
0
          image->slotclass, image->artifact, artifact->repo->type);
912
0
      return FALSE;
913
0
    }
914
0
  } else {
915
    /* should never happen, as this is checked during startup */
916
0
    g_error("Unsupported artifacts repo type '%s'", artifact->repo->type);
917
0
    return FALSE;
918
0
  }
919
920
0
  g_auto(filedesc) dir_fd = g_open(artifact->repo->path, O_RDONLY | O_CLOEXEC, 0);
921
0
  if (dir_fd < 0) {
922
0
    int err = errno;
923
0
    g_set_error(error,
924
0
        G_FILE_ERROR,
925
0
        g_file_error_from_errno(err),
926
0
        "Failed to open artifact repo directory %s: %s", artifact->repo->path,
927
0
        g_strerror(err));
928
0
    return FALSE;
929
0
  }
930
931
0
  if (syncfs(dir_fd) < 0) {
932
0
    int err = errno;
933
0
    g_set_error(error,
934
0
        G_FILE_ERROR,
935
0
        g_file_error_from_errno(err),
936
0
        "Failed to call syncfs for artifact repo '%s': %s", artifact->repo->path,
937
0
        g_strerror(err));
938
0
    return FALSE;
939
0
  }
940
941
0
  if (g_rename(artifact->path_tmp, artifact->path) != 0) {
942
0
    int err = errno;
943
0
    g_set_error(error,
944
0
        G_FILE_ERROR,
945
0
        g_file_error_from_errno(err),
946
0
        "Renaming artifact from %s to %s failed: %s",
947
0
        artifact->path_tmp,
948
0
        artifact->path,
949
0
        g_strerror(err));
950
0
    return FALSE;
951
0
  }
952
953
0
  if (fsync(dir_fd) < 0) {
954
0
    int err = errno;
955
0
    g_set_error(error,
956
0
        G_FILE_ERROR,
957
0
        g_file_error_from_errno(err),
958
0
        "Failed to call fsync for artifact repo '%s': %s", artifact->repo->path,
959
0
        g_strerror(err));
960
0
    return FALSE;
961
0
  }
962
963
0
  return TRUE;
964
0
}
965
966
void r_artifact_activate(const RArtifact *artifact, const gchar *parent)
967
0
{
968
0
  g_return_if_fail(artifact);
969
0
  g_return_if_fail(artifact->repo);
970
0
  g_return_if_fail(parent);
971
972
0
  parent = g_intern_string(parent);
973
974
0
  g_return_if_fail(g_ptr_array_find(artifact->repo->possible_references, parent, NULL));
975
976
0
  GHashTable *inner = g_hash_table_lookup(artifact->repo->artifacts, artifact->name);
977
0
  g_assert(inner);
978
979
0
  GHashTableIter inner_iter;
980
0
  RArtifact *other_artifact = NULL;
981
0
  g_hash_table_iter_init(&inner_iter, inner);
982
0
  while (g_hash_table_iter_next(&inner_iter, NULL, (gpointer*)&other_artifact)) {
983
0
    if (other_artifact == artifact)
984
0
      continue;
985
0
    r_artifact_deactivate(other_artifact, parent);
986
0
  }
987
988
0
  if (!g_ptr_array_find(artifact->references, (gpointer)parent, NULL)) {
989
0
    g_ptr_array_add(artifact->references, (gpointer)parent);
990
0
  }
991
0
}
992
993
void r_artifact_deactivate(const RArtifact *artifact, const gchar *parent)
994
0
{
995
0
  g_return_if_fail(artifact);
996
0
  g_return_if_fail(artifact->repo);
997
0
  g_return_if_fail(parent);
998
999
0
  parent = g_intern_string(parent);
1000
1001
0
  g_return_if_fail(g_ptr_array_find(artifact->repo->possible_references, parent, NULL));
1002
1003
0
  g_assert(artifact->references != NULL);
1004
0
  g_ptr_array_remove(artifact->references, (gpointer)parent);
1005
0
}