Coverage Report

Created: 2026-05-23 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ostree/src/libostree/ostree-repo-static-delta-core.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2013 Colin Walters <walters@verbum.org>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.0+
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library. If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
#include "config.h"
21
22
#include "ostree-checksum-input-stream.h"
23
#include "ostree-cmd-private.h"
24
#include "ostree-core-private.h"
25
#include "ostree-lzma-decompressor.h"
26
#include "ostree-repo-private.h"
27
#include "ostree-repo-static-delta-private.h"
28
#include "otutil.h"
29
#include <gio/gfiledescriptorbased.h>
30
#include <gio/gunixinputstream.h>
31
#include <gio/gunixoutputstream.h>
32
33
gboolean
34
_ostree_static_delta_parse_checksum_array (GVariant *array, guint8 **out_checksums_array,
35
                                           guint *out_n_checksums, GError **error)
36
0
{
37
0
  const gsize n = g_variant_n_children (array);
38
0
  const guint n_checksums = n / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN;
39
40
0
  if (G_UNLIKELY (n > (G_MAXUINT32 / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN)
41
0
                  || (n_checksums * OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) != n))
42
0
    {
43
0
      return glnx_throw (error, "Invalid checksum array length %" G_GSIZE_FORMAT, n);
44
0
    }
45
46
0
  *out_checksums_array = (gpointer)g_variant_get_data (array);
47
0
  *out_n_checksums = n_checksums;
48
49
0
  return TRUE;
50
0
}
51
52
GVariant *
53
_ostree_repo_static_delta_superblock_digest (OstreeRepo *repo, const char *from, const char *to,
54
                                             GCancellable *cancellable, GError **error)
55
0
{
56
0
  g_autofree char *superblock
57
0
      = _ostree_get_relative_static_delta_superblock_path ((from && from[0]) ? from : NULL, to);
58
0
  glnx_autofd int superblock_file_fd = -1;
59
0
  guint8 digest[OSTREE_SHA256_DIGEST_LEN];
60
61
0
  if (!glnx_openat_rdonly (repo->repo_dir_fd, superblock, TRUE, &superblock_file_fd, error))
62
0
    return NULL;
63
64
0
  g_autoptr (GBytes) superblock_content = ot_fd_readall_or_mmap (superblock_file_fd, 0, error);
65
0
  if (!superblock_content)
66
0
    return NULL;
67
68
0
  ot_checksum_bytes (superblock_content, digest);
69
70
0
  return ot_gvariant_new_bytearray (digest, sizeof (digest));
71
0
}
72
73
/**
74
 * ostree_repo_list_static_delta_names:
75
 * @self: Repo
76
 * @out_deltas: (out) (element-type utf8) (transfer container): String name of deltas
77
 * (checksum-checksum.delta)
78
 * @cancellable: Cancellable
79
 * @error: Error
80
 *
81
 * This function synchronously enumerates all static deltas in the
82
 * repository, returning its result in @out_deltas.
83
 */
84
gboolean
85
ostree_repo_list_static_delta_names (OstreeRepo *self, GPtrArray **out_deltas,
86
                                     GCancellable *cancellable, GError **error)
87
0
{
88
0
  g_autoptr (GPtrArray) ret_deltas = g_ptr_array_new_with_free_func (g_free);
89
90
0
  g_auto (GLnxDirFdIterator) dfd_iter = {
91
0
    0,
92
0
  };
93
0
  gboolean exists;
94
0
  if (!ot_dfd_iter_init_allow_noent (self->repo_dir_fd, "deltas", &dfd_iter, &exists, error))
95
0
    return FALSE;
96
0
  if (!exists)
97
0
    {
98
      /* Note early return */
99
0
      ot_transfer_out_value (out_deltas, &ret_deltas);
100
0
      return TRUE;
101
0
    }
102
103
0
  while (TRUE)
104
0
    {
105
0
      g_auto (GLnxDirFdIterator) sub_dfd_iter = {
106
0
        0,
107
0
      };
108
0
      struct dirent *dent;
109
110
0
      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
111
0
        return FALSE;
112
0
      if (dent == NULL)
113
0
        break;
114
0
      if (dent->d_type != DT_DIR)
115
0
        continue;
116
117
0
      if (!glnx_dirfd_iterator_init_at (dfd_iter.fd, dent->d_name, FALSE, &sub_dfd_iter, error))
118
0
        return FALSE;
119
120
0
      while (TRUE)
121
0
        {
122
0
          struct dirent *sub_dent;
123
0
          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&sub_dfd_iter, &sub_dent, cancellable,
124
0
                                                           error))
125
0
            return FALSE;
126
0
          if (sub_dent == NULL)
127
0
            break;
128
0
          if (sub_dent->d_type != DT_DIR)
129
0
            continue;
130
131
0
          const char *name1 = dent->d_name;
132
0
          const char *name2 = sub_dent->d_name;
133
134
0
          g_autofree char *superblock_subpath = g_strconcat (name2, "/superblock", NULL);
135
0
          if (!glnx_fstatat_allow_noent (sub_dfd_iter.fd, superblock_subpath, NULL, 0, error))
136
0
            return FALSE;
137
0
          if (errno == ENOENT)
138
0
            continue;
139
140
0
          g_autofree char *buf = g_strconcat (name1, name2, NULL);
141
0
          GString *out = g_string_new ("");
142
0
          char checksum[OSTREE_SHA256_STRING_LEN + 1];
143
0
          guchar csum[OSTREE_SHA256_DIGEST_LEN];
144
0
          const char *dash = strchr (buf, '-');
145
146
0
          ostree_checksum_b64_inplace_to_bytes (buf, csum);
147
0
          ostree_checksum_inplace_from_bytes (csum, checksum);
148
0
          g_string_append (out, checksum);
149
0
          if (dash)
150
0
            {
151
0
              g_string_append_c (out, '-');
152
0
              ostree_checksum_b64_inplace_to_bytes (dash + 1, csum);
153
0
              ostree_checksum_inplace_from_bytes (csum, checksum);
154
0
              g_string_append (out, checksum);
155
0
            }
156
157
0
          g_ptr_array_add (ret_deltas, g_string_free (out, FALSE));
158
0
        }
159
0
    }
160
161
0
  ot_transfer_out_value (out_deltas, &ret_deltas);
162
0
  return TRUE;
163
0
}
164
165
/**
166
 * ostree_repo_list_static_delta_indexes:
167
 * @self: Repo
168
 * @out_indexes: (out) (element-type utf8) (transfer container): String name of delta indexes
169
 * (checksum)
170
 * @cancellable: Cancellable
171
 * @error: Error
172
 *
173
 * This function synchronously enumerates all static delta indexes in the
174
 * repository, returning its result in @out_indexes.
175
 *
176
 * Since: 2020.8
177
 */
178
gboolean
179
ostree_repo_list_static_delta_indexes (OstreeRepo *self, GPtrArray **out_indexes,
180
                                       GCancellable *cancellable, GError **error)
181
0
{
182
0
  g_autoptr (GPtrArray) ret_indexes = g_ptr_array_new_with_free_func (g_free);
183
184
0
  g_auto (GLnxDirFdIterator) dfd_iter = {
185
0
    0,
186
0
  };
187
0
  gboolean exists;
188
0
  if (!ot_dfd_iter_init_allow_noent (self->repo_dir_fd, "delta-indexes", &dfd_iter, &exists, error))
189
0
    return FALSE;
190
0
  if (!exists)
191
0
    {
192
      /* Note early return */
193
0
      ot_transfer_out_value (out_indexes, &ret_indexes);
194
0
      return TRUE;
195
0
    }
196
197
0
  while (TRUE)
198
0
    {
199
0
      g_auto (GLnxDirFdIterator) sub_dfd_iter = {
200
0
        0,
201
0
      };
202
0
      struct dirent *dent;
203
204
0
      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
205
0
        return FALSE;
206
0
      if (dent == NULL)
207
0
        break;
208
0
      if (dent->d_type != DT_DIR)
209
0
        continue;
210
0
      if (strlen (dent->d_name) != 2)
211
0
        continue;
212
213
0
      if (!glnx_dirfd_iterator_init_at (dfd_iter.fd, dent->d_name, FALSE, &sub_dfd_iter, error))
214
0
        return FALSE;
215
216
0
      while (TRUE)
217
0
        {
218
0
          struct dirent *sub_dent;
219
220
0
          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&sub_dfd_iter, &sub_dent, cancellable,
221
0
                                                           error))
222
0
            return FALSE;
223
0
          if (sub_dent == NULL)
224
0
            break;
225
0
          if (sub_dent->d_type != DT_REG)
226
0
            continue;
227
228
0
          const char *name1 = dent->d_name;
229
0
          const char *name2 = sub_dent->d_name;
230
231
          /* base64 len is 43, but 2 chars are in the parent dir name */
232
0
          if (strlen (name2) != 41 + strlen (".index") || !g_str_has_suffix (name2, ".index"))
233
0
            continue;
234
235
0
          g_autoptr (GString) out = g_string_new (name1);
236
0
          g_string_append_len (out, name2, 41);
237
238
0
          char checksum[OSTREE_SHA256_STRING_LEN + 1];
239
0
          guchar csum[OSTREE_SHA256_DIGEST_LEN];
240
241
0
          ostree_checksum_b64_inplace_to_bytes (out->str, csum);
242
0
          ostree_checksum_inplace_from_bytes (csum, checksum);
243
244
0
          g_ptr_array_add (ret_indexes, g_strdup (checksum));
245
0
        }
246
0
    }
247
248
0
  ot_transfer_out_value (out_indexes, &ret_indexes);
249
0
  return TRUE;
250
0
}
251
252
gboolean
253
_ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo, GVariant *checksum_array,
254
                                                 gboolean *out_have_all, GCancellable *cancellable,
255
                                                 GError **error)
256
0
{
257
0
  guint8 *checksums_data = NULL;
258
0
  guint n_checksums = 0;
259
0
  gboolean have_object = TRUE;
260
261
0
  if (!_ostree_static_delta_parse_checksum_array (checksum_array, &checksums_data, &n_checksums,
262
0
                                                  error))
263
0
    return FALSE;
264
265
0
  for (guint i = 0; i < n_checksums; i++)
266
0
    {
267
0
      guint8 objtype = *checksums_data;
268
0
      const guint8 *csum = checksums_data + 1;
269
0
      char tmp_checksum[OSTREE_SHA256_STRING_LEN + 1];
270
271
0
      if (G_UNLIKELY (!ostree_validate_structureof_objtype (objtype, error)))
272
0
        return FALSE;
273
274
0
      ostree_checksum_inplace_from_bytes (csum, tmp_checksum);
275
276
0
      if (!ostree_repo_has_object (repo, (OstreeObjectType)objtype, tmp_checksum, &have_object,
277
0
                                   cancellable, error))
278
0
        return FALSE;
279
280
0
      if (!have_object)
281
0
        break;
282
283
0
      checksums_data += OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN;
284
0
    }
285
286
0
  *out_have_all = have_object;
287
0
  return TRUE;
288
0
}
289
290
static gboolean
291
_ostree_repo_static_delta_is_signed (OstreeRepo *self, int fd, GPtrArray **out_value,
292
                                     GError **error)
293
0
{
294
0
  g_autoptr (GVariant) delta = NULL;
295
0
  g_autoptr (GVariant) delta_sign_magic = NULL;
296
0
  g_autoptr (GVariant) delta_sign = NULL;
297
0
  GVariantIter iter;
298
0
  GVariant *item;
299
0
  g_autoptr (GPtrArray) signatures = NULL;
300
0
  gboolean ret = FALSE;
301
302
0
  if (out_value)
303
0
    *out_value = NULL;
304
305
0
  if (!ot_variant_read_fd (fd, 0, (GVariantType *)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE, &delta,
306
0
                           error))
307
0
    return FALSE;
308
309
0
  delta_sign_magic = g_variant_get_child_value (delta, 0);
310
0
  if (delta_sign_magic == NULL)
311
0
    return glnx_throw (error, "no signatures in static-delta");
312
313
0
  if (GUINT64_FROM_BE (g_variant_get_uint64 (delta_sign_magic)) != OSTREE_STATIC_DELTA_SIGNED_MAGIC)
314
0
    return glnx_throw (error, "no signatures in static-delta");
315
316
0
  delta_sign = g_variant_get_child_value (delta, 2);
317
0
  if (delta_sign == NULL)
318
0
    return glnx_throw (error, "no signatures in static-delta");
319
320
0
  if (out_value)
321
0
    signatures = g_ptr_array_new_with_free_func (g_free);
322
323
  /* Check if there are signatures in the superblock */
324
0
  g_variant_iter_init (&iter, delta_sign);
325
0
  while ((item = g_variant_iter_next_value (&iter)))
326
0
    {
327
0
      g_autoptr (GVariant) key_v = g_variant_get_child_value (item, 0);
328
0
      const char *str = g_variant_get_string (key_v, NULL);
329
0
      if (g_str_has_prefix (str, "ostree.sign."))
330
0
        {
331
0
          ret = TRUE;
332
0
          if (signatures)
333
0
            g_ptr_array_add (signatures, g_strdup (str + strlen ("ostree.sign.")));
334
0
        }
335
0
      g_variant_unref (item);
336
0
    }
337
338
0
  if (out_value && ret)
339
0
    ot_transfer_out_value (out_value, &signatures);
340
341
0
  return ret;
342
0
}
343
344
static gboolean
345
_ostree_repo_static_delta_verify_signature (OstreeRepo *self, int fd, OstreeSign *sign,
346
                                            char **out_success_message, GError **error)
347
0
{
348
0
  g_autoptr (GVariant) delta = NULL;
349
350
0
  if (!ot_variant_read_fd (fd, 0, (GVariantType *)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE, &delta,
351
0
                           error))
352
0
    return FALSE;
353
354
  /* Check if there are signatures for signature engine */
355
0
  const gchar *signature_key = ostree_sign_metadata_key (sign);
356
0
  GVariantType *signature_format = (GVariantType *)ostree_sign_metadata_format (sign);
357
0
  g_autoptr (GVariant) delta_meta = g_variant_get_child_value (delta, 2);
358
0
  if (delta_meta == NULL)
359
0
    return glnx_throw (error, "no metadata in static-delta superblock");
360
0
  g_autoptr (GVariant) signatures
361
0
      = g_variant_lookup_value (delta_meta, signature_key, signature_format);
362
0
  if (!signatures)
363
0
    return glnx_throw (error, "no signature for '%s' in static-delta superblock", signature_key);
364
365
  /* Get static delta superblock */
366
0
  g_autoptr (GVariant) child = g_variant_get_child_value (delta, 1);
367
0
  if (child == NULL)
368
0
    return glnx_throw (error, "no metadata in static-delta superblock");
369
0
  g_autoptr (GBytes) signed_data = g_variant_get_data_as_bytes (child);
370
371
0
  return ostree_sign_data_verify (sign, signed_data, signatures, out_success_message, error);
372
0
}
373
374
/**
375
 * ostree_repo_static_delta_execute_offline_with_signature:
376
 * @self: Repo
377
 * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock
378
 * @sign: Signature engine used to check superblock
379
 * @skip_validation: If %TRUE, assume data integrity
380
 * @cancellable: Cancellable
381
 * @error: Error
382
 *
383
 * Given a directory representing an already-downloaded static delta
384
 * on disk, apply it, generating a new commit.
385
 * If sign is passed, the static delta signature is verified.
386
 * If sign-verify-deltas configuration option is set and static delta is signed,
387
 * signature verification will be mandatory before apply the static delta.
388
 * The directory must be named with the form "FROM-TO", where both are
389
 * checksums, and it must contain a file named "superblock", along with at least
390
 * one part.
391
 *
392
 * Since: 2020.7
393
 */
394
gboolean
395
ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, GFile *dir_or_file,
396
                                                         OstreeSign *sign, gboolean skip_validation,
397
                                                         GCancellable *cancellable, GError **error)
398
0
{
399
0
  g_autofree char *basename = NULL;
400
0
  g_autoptr (GVariant) meta = NULL;
401
402
0
  const char *dir_or_file_path = gs_file_get_path_cached (dir_or_file);
403
404
  /* First, try opening it as a directory */
405
0
  glnx_autofd int dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE);
406
0
  if (dfd < 0)
407
0
    {
408
0
      if (errno != ENOTDIR)
409
0
        return glnx_throw_errno_prefix (error, "openat(O_DIRECTORY)");
410
0
      else
411
0
        {
412
0
          g_autofree char *tmpbuf = g_strdup (dir_or_file_path);
413
0
          const char *dir = dirname (tmpbuf);
414
0
          basename = g_path_get_basename (dir_or_file_path);
415
416
0
          if (!glnx_opendirat (AT_FDCWD, dir, TRUE, &dfd, error))
417
0
            return FALSE;
418
0
        }
419
0
    }
420
0
  else
421
0
    basename = g_strdup ("superblock");
422
423
0
  glnx_autofd int meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC);
424
0
  if (meta_fd < 0)
425
0
    return glnx_throw_errno_prefix (error, "openat(%s)", basename);
426
427
0
  gboolean is_signed = _ostree_repo_static_delta_is_signed (self, meta_fd, NULL, NULL);
428
0
  if (is_signed)
429
0
    {
430
0
      gboolean verify_deltas;
431
0
      gboolean verified;
432
433
0
      if (!ot_keyfile_get_boolean_with_default (self->config, "core", "sign-verify-deltas", FALSE,
434
0
                                                &verify_deltas, error))
435
0
        return FALSE;
436
437
0
      if (verify_deltas && !sign)
438
0
        return glnx_throw (error, "Key is mandatory to check delta signature");
439
440
0
      if (sign)
441
0
        {
442
0
          g_autoptr (GError) local_error = NULL;
443
444
0
          verified = _ostree_repo_static_delta_verify_signature (self, meta_fd, sign, NULL,
445
0
                                                                 &local_error);
446
0
          if (local_error != NULL)
447
0
            {
448
0
              g_propagate_error (error, g_steal_pointer (&local_error));
449
0
              return FALSE;
450
0
            }
451
452
0
          if (!verified)
453
0
            return glnx_throw (error, "Delta signature verification failed");
454
0
        }
455
456
0
      g_autoptr (GVariant) delta = NULL;
457
0
      if (!ot_variant_read_fd (meta_fd, 0, (GVariantType *)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE,
458
0
                               &delta, error))
459
0
        return FALSE;
460
461
0
      g_autoptr (GVariant) child = g_variant_get_child_value (delta, 1);
462
0
      g_autoptr (GBytes) bytes = g_variant_get_data_as_bytes (child);
463
0
      meta = g_variant_new_from_bytes ((GVariantType *)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, bytes,
464
0
                                       FALSE);
465
0
    }
466
0
  else
467
0
    {
468
0
      if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT),
469
0
                               FALSE, &meta, error))
470
0
        return FALSE;
471
0
    }
472
473
  /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */
474
475
0
  g_autoptr (GVariant) metadata = g_variant_get_child_value (meta, 0);
476
477
0
  g_autofree char *to_checksum = NULL;
478
0
  g_autofree char *from_checksum = NULL;
479
  /* Write the to-commit object */
480
0
  {
481
0
    g_autoptr (GVariant) to_csum_v = NULL;
482
0
    g_autoptr (GVariant) from_csum_v = NULL;
483
0
    g_autoptr (GVariant) to_commit = NULL;
484
0
    gboolean have_to_commit;
485
0
    gboolean have_from_commit;
486
487
0
    to_csum_v = g_variant_get_child_value (meta, 3);
488
0
    if (!ostree_validate_structureof_csum_v (to_csum_v, error))
489
0
      return FALSE;
490
0
    to_checksum = ostree_checksum_from_bytes_v (to_csum_v);
491
492
0
    from_csum_v = g_variant_get_child_value (meta, 2);
493
0
    if (g_variant_n_children (from_csum_v) > 0)
494
0
      {
495
0
        if (!ostree_validate_structureof_csum_v (from_csum_v, error))
496
0
          return FALSE;
497
0
        from_checksum = ostree_checksum_from_bytes_v (from_csum_v);
498
499
0
        if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, from_checksum,
500
0
                                     &have_from_commit, cancellable, error))
501
0
          return FALSE;
502
503
0
        if (!have_from_commit)
504
0
          return glnx_throw (error, "Commit %s, which is the delta source, is not in repository",
505
0
                             from_checksum);
506
0
      }
507
508
0
    if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, &have_to_commit,
509
0
                                 cancellable, error))
510
0
      return FALSE;
511
512
0
    if (!have_to_commit)
513
0
      {
514
0
        g_autofree char *detached_path
515
0
            = _ostree_get_relative_static_delta_path (from_checksum, to_checksum, "commitmeta");
516
0
        g_autoptr (GVariant) detached_data
517
0
            = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE ("a{sv}"));
518
0
        if (detached_data
519
0
            && !ostree_repo_write_commit_detached_metadata (self, to_checksum, detached_data,
520
0
                                                            cancellable, error))
521
0
          return FALSE;
522
523
0
        to_commit = g_variant_get_child_value (meta, 4);
524
0
        if (!ostree_repo_write_metadata (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, to_commit,
525
0
                                         NULL, cancellable, error))
526
0
          return FALSE;
527
0
      }
528
0
  }
529
530
0
  g_autoptr (GVariant) fallback = g_variant_get_child_value (meta, 7);
531
0
  if (g_variant_n_children (fallback) > 0)
532
0
    return glnx_throw (error,
533
0
                       "Cannot execute delta offline: contains nonempty http fallback entries");
534
535
0
  g_autoptr (GVariant) headers = g_variant_get_child_value (meta, 6);
536
0
  const guint n = g_variant_n_children (headers);
537
0
  for (guint i = 0; i < n; i++)
538
0
    {
539
0
      guint32 version;
540
0
      guint64 size;
541
0
      guint64 usize;
542
0
      char checksum[OSTREE_SHA256_STRING_LEN + 1];
543
0
      g_autoptr (GVariant) csum_v = NULL;
544
0
      g_autoptr (GVariant) objects = NULL;
545
0
      g_autoptr (GVariant) part = NULL;
546
0
      OstreeStaticDeltaOpenFlags delta_open_flags
547
0
          = skip_validation ? OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM : 0;
548
0
      g_autoptr (GVariant) header = g_variant_get_child_value (headers, i);
549
0
      g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects);
550
551
0
      if (version > OSTREE_DELTAPART_VERSION)
552
0
        return glnx_throw (error, "Delta part has too new version %u", version);
553
554
0
      gboolean have_all;
555
0
      if (!_ostree_repo_static_delta_part_have_all_objects (self, objects, &have_all, cancellable,
556
0
                                                            error))
557
0
        return FALSE;
558
559
      /* If we already have these objects, don't bother executing the
560
       * static delta.
561
       */
562
0
      if (have_all)
563
0
        continue;
564
565
0
      const guchar *csum = ostree_checksum_bytes_peek_validate (csum_v, error);
566
0
      if (!csum)
567
0
        return FALSE;
568
0
      ostree_checksum_inplace_from_bytes (csum, checksum);
569
570
0
      g_autofree char *deltapart_path
571
0
          = _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i);
572
573
0
      g_autoptr (GInputStream) part_in = NULL;
574
0
      g_autoptr (GVariant) inline_part_data
575
0
          = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE ("(yay)"));
576
0
      if (inline_part_data)
577
0
        {
578
0
          g_autoptr (GBytes) inline_part_bytes = g_variant_get_data_as_bytes (inline_part_data);
579
0
          part_in = g_memory_input_stream_new_from_bytes (inline_part_bytes);
580
581
          /* For inline parts, we don't checksum, because it's
582
           * included with the metadata, so we're not trying to
583
           * protect against MITM or such.  Non-security related
584
           * checksums should be done at the underlying storage layer.
585
           */
586
0
          delta_open_flags |= OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM;
587
588
0
          if (!_ostree_static_delta_part_open (part_in, inline_part_bytes, delta_open_flags, NULL,
589
0
                                               &part, cancellable, error))
590
0
            return FALSE;
591
0
        }
592
0
      else
593
0
        {
594
0
          g_autofree char *relpath = g_strdup_printf ("%u", i); /* TODO avoid malloc here */
595
0
          glnx_autofd int part_fd = openat (dfd, relpath, O_RDONLY | O_CLOEXEC);
596
0
          if (part_fd < 0)
597
0
            return glnx_throw_errno_prefix (error, "Opening deltapart '%s'", relpath);
598
599
0
          part_in = g_unix_input_stream_new (part_fd, FALSE);
600
601
0
          if (!_ostree_static_delta_part_open (part_in, NULL, delta_open_flags, checksum, &part,
602
0
                                               cancellable, error))
603
0
            return FALSE;
604
0
        }
605
606
0
      if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation, NULL,
607
0
                                              cancellable, error))
608
0
        return glnx_prefix_error (error, "Executing delta part %i", i);
609
0
    }
610
611
0
  return TRUE;
612
0
}
613
614
/**
615
 * ostree_repo_static_delta_execute_offline:
616
 * @self: Repo
617
 * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock
618
 * @skip_validation: If %TRUE, assume data integrity
619
 * @cancellable: Cancellable
620
 * @error: Error
621
 *
622
 * Given a directory representing an already-downloaded static delta
623
 * on disk, apply it, generating a new commit.  The directory must be
624
 * named with the form "FROM-TO", where both are checksums, and it
625
 * must contain a file named "superblock", along with at least one part.
626
 */
627
gboolean
628
ostree_repo_static_delta_execute_offline (OstreeRepo *self, GFile *dir_or_file,
629
                                          gboolean skip_validation, GCancellable *cancellable,
630
                                          GError **error)
631
0
{
632
0
  return ostree_repo_static_delta_execute_offline_with_signature (
633
0
      self, dir_or_file, NULL, skip_validation, cancellable, error);
634
0
}
635
636
gboolean
637
_ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes,
638
                                OstreeStaticDeltaOpenFlags flags, const char *expected_checksum,
639
                                GVariant **out_part, GCancellable *cancellable, GError **error)
640
0
{
641
0
  const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0;
642
0
  const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0;
643
644
  /* We either take a fd or a GBytes reference */
645
0
  g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE);
646
0
  g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE);
647
648
0
  g_autoptr (GChecksum) checksum = NULL;
649
0
  g_autoptr (GInputStream) checksum_in = NULL;
650
0
  GInputStream *source_in;
651
0
  if (!skip_checksum)
652
0
    {
653
0
      checksum = g_checksum_new (G_CHECKSUM_SHA256);
654
0
      checksum_in = (GInputStream *)ostree_checksum_input_stream_new (part_in, checksum);
655
0
      source_in = checksum_in;
656
0
    }
657
0
  else
658
0
    {
659
0
      source_in = part_in;
660
0
    }
661
662
0
  guint8 comptype;
663
0
  {
664
0
    guint8 buf[1];
665
0
    gsize bytes_read;
666
    /* First byte is compression type */
667
0
    if (!g_input_stream_read_all (source_in, buf, sizeof (buf), &bytes_read, cancellable, error))
668
0
      return glnx_prefix_error (error, "Reading initial compression flag byte");
669
0
    comptype = buf[0];
670
0
  }
671
672
0
  g_autoptr (GVariant) ret_part = NULL;
673
0
  switch (comptype)
674
0
    {
675
0
    case 0:
676
0
      if (!inline_part_bytes)
677
0
        {
678
0
          int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased *)part_in);
679
680
          /* No compression, no checksums - a fast path */
681
0
          if (!ot_variant_read_fd (part_fd, 1,
682
0
                                   G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
683
0
                                   trusted, &ret_part, error))
684
0
            return FALSE;
685
0
        }
686
0
      else
687
0
        {
688
0
          g_autoptr (GBytes) content_bytes = g_bytes_new_from_bytes (
689
0
              inline_part_bytes, 1, g_bytes_get_size (inline_part_bytes) - 1);
690
0
          ret_part = g_variant_new_from_bytes (
691
0
              G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), content_bytes, trusted);
692
0
          g_variant_ref_sink (ret_part);
693
0
        }
694
695
0
      if (!skip_checksum)
696
0
        g_checksum_update (checksum, g_variant_get_data (ret_part), g_variant_get_size (ret_part));
697
698
0
      break;
699
0
    case 'x':
700
0
      {
701
0
        g_autoptr (GConverter) decomp = (GConverter *)_ostree_lzma_decompressor_new ();
702
0
        g_autoptr (GInputStream) convin = g_converter_input_stream_new (source_in, decomp);
703
0
        g_autoptr (GBytes) buf = ot_map_anonymous_tmpfile_from_content (convin, cancellable, error);
704
0
        if (!buf)
705
0
          return FALSE;
706
707
0
        ret_part = g_variant_new_from_bytes (
708
0
            G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), buf, FALSE);
709
0
      }
710
0
      break;
711
0
    default:
712
0
      return glnx_throw (error, "Invalid compression type '%u'", comptype);
713
0
    }
714
715
0
  if (checksum)
716
0
    {
717
0
      const char *actual_checksum = g_checksum_get_string (checksum);
718
0
      g_assert (expected_checksum != NULL);
719
0
      if (strcmp (actual_checksum, expected_checksum) != 0)
720
0
        return glnx_throw (error, "Checksum mismatch in static delta part; expected=%s actual=%s",
721
0
                           expected_checksum, actual_checksum);
722
0
    }
723
724
0
  *out_part = g_steal_pointer (&ret_part);
725
0
  return TRUE;
726
0
}
727
728
/*
729
 * Displaying static delta parts
730
 */
731
732
static gboolean
733
show_one_part (OstreeRepo *self, gboolean swap_endian, const char *from, const char *to,
734
               GVariant *meta_entries, guint i, guint64 *total_size_ref, guint64 *total_usize_ref,
735
               GCancellable *cancellable, GError **error)
736
0
{
737
0
  g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i);
738
739
0
  guint32 version;
740
0
  guint64 size, usize;
741
0
  g_autoptr (GVariant) objects = NULL;
742
0
  g_variant_get_child (meta_entries, i, "(u@aytt@ay)", &version, NULL, &size, &usize, &objects);
743
0
  size = maybe_swap_endian_u64 (swap_endian, size);
744
0
  usize = maybe_swap_endian_u64 (swap_endian, usize);
745
0
  *total_size_ref += size;
746
0
  *total_usize_ref += usize;
747
0
  g_print ("PartMeta%u: nobjects=%u size=%" G_GUINT64_FORMAT " usize=%" G_GUINT64_FORMAT "\n", i,
748
0
           (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN), size,
749
0
           usize);
750
751
0
  glnx_autofd int part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC);
752
0
  if (part_fd < 0)
753
0
    return glnx_throw_errno_prefix (error, "openat(%s)", part_path);
754
0
  g_autoptr (GInputStream) part_in = g_unix_input_stream_new (part_fd, FALSE);
755
756
0
  g_autoptr (GVariant) part = NULL;
757
0
  if (!_ostree_static_delta_part_open (part_in, NULL, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM,
758
0
                                       NULL, &part, cancellable, error))
759
0
    return FALSE;
760
761
0
  {
762
0
    g_autoptr (GVariant) modes = NULL;
763
0
    g_autoptr (GVariant) xattrs = NULL;
764
0
    g_autoptr (GVariant) blob = NULL;
765
0
    g_autoptr (GVariant) ops = NULL;
766
0
    OstreeDeltaExecuteStats stats = {
767
0
      {
768
0
          0,
769
0
      },
770
0
    };
771
772
0
    g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)", &modes, &xattrs, &blob, &ops);
773
774
0
    g_print ("PartPayload%u: nmodes=%" G_GUINT64_FORMAT " nxattrs=%" G_GUINT64_FORMAT
775
0
             " blobsize=%" G_GUINT64_FORMAT " opsize=%" G_GUINT64_FORMAT "\n",
776
0
             i, (guint64)g_variant_n_children (modes), (guint64)g_variant_n_children (xattrs),
777
0
             (guint64)g_variant_n_children (blob), (guint64)g_variant_n_children (ops));
778
779
0
    if (!_ostree_static_delta_part_execute (self, objects, part, TRUE, &stats, cancellable, error))
780
0
      return FALSE;
781
782
0
    {
783
0
      const guint *n_ops = stats.n_ops_executed;
784
0
      g_print ("PartPayloadOps%u: openspliceclose=%u open=%u write=%u setread=%u "
785
0
               "unsetread=%u close=%u bspatch=%u\n",
786
0
               i, n_ops[0], n_ops[1], n_ops[2], n_ops[3], n_ops[4], n_ops[5], n_ops[6]);
787
0
    }
788
0
  }
789
790
0
  return TRUE;
791
0
}
792
793
OstreeDeltaEndianness
794
_ostree_delta_get_endianness (GVariant *superblock, gboolean *out_was_heuristic)
795
0
{
796
0
  g_autoptr (GVariant) delta_meta = g_variant_get_child_value (superblock, 0);
797
0
  g_autoptr (GVariantDict) delta_metadict = g_variant_dict_new (delta_meta);
798
799
0
  if (out_was_heuristic)
800
0
    *out_was_heuristic = FALSE;
801
802
0
  guint8 endianness_char;
803
0
  if (g_variant_dict_lookup (delta_metadict, "ostree.endianness", "y", &endianness_char))
804
0
    {
805
0
      switch (endianness_char)
806
0
        {
807
0
        case 'l':
808
0
          return OSTREE_DELTA_ENDIAN_LITTLE;
809
0
        case 'B':
810
0
          return OSTREE_DELTA_ENDIAN_BIG;
811
0
        default:
812
0
          return OSTREE_DELTA_ENDIAN_INVALID;
813
0
        }
814
0
    }
815
816
0
  if (out_was_heuristic)
817
0
    *out_was_heuristic = TRUE;
818
819
0
  guint64 total_size = 0;
820
0
  guint64 total_usize = 0;
821
0
  guint total_objects = 0;
822
0
  {
823
0
    g_autoptr (GVariant) meta_entries = NULL;
824
0
    gboolean is_byteswapped = FALSE;
825
826
0
    g_variant_get_child (superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries);
827
0
    const guint n_parts = g_variant_n_children (meta_entries);
828
829
0
    for (guint i = 0; i < n_parts; i++)
830
0
      {
831
0
        g_autoptr (GVariant) objects = NULL;
832
0
        guint64 size, usize;
833
0
        guint n_objects;
834
835
0
        g_variant_get_child (meta_entries, i, "(u@aytt@ay)", NULL, NULL, &size, &usize, &objects);
836
0
        n_objects = (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN);
837
838
0
        total_objects += n_objects;
839
0
        total_size += size;
840
0
        total_usize += usize;
841
842
0
        if (size > usize)
843
0
          {
844
0
            double ratio = ((double)size) / ((double)usize);
845
846
            /* This should really never happen where compressing things makes it more than 50%
847
             * bigger.
848
             */
849
0
            if (ratio > 1.2)
850
0
              {
851
0
                is_byteswapped = TRUE;
852
0
                break;
853
0
              }
854
0
          }
855
0
      }
856
857
0
    if (!is_byteswapped)
858
0
      {
859
        /* If the average object size is greater than 4GiB, let's assume
860
         * we're dealing with opposite endianness.  I'm fairly confident
861
         * no one is going to be shipping peta- or exa- byte size ostree
862
         * deltas, period.  Past the gigabyte scale you really want
863
         * bittorrent or something.
864
         */
865
0
        if (total_objects > 0 && (total_size / total_objects) > G_MAXUINT32)
866
0
          {
867
0
            is_byteswapped = TRUE;
868
0
          }
869
0
      }
870
871
0
    if (is_byteswapped)
872
0
      {
873
0
        switch (G_BYTE_ORDER)
874
0
          {
875
0
          case G_BIG_ENDIAN:
876
0
            return OSTREE_DELTA_ENDIAN_LITTLE;
877
0
          case G_LITTLE_ENDIAN:
878
0
            return OSTREE_DELTA_ENDIAN_BIG;
879
0
          default:
880
0
            g_assert_not_reached ();
881
0
          }
882
0
      }
883
884
0
    return OSTREE_DELTA_ENDIAN_INVALID;
885
0
  }
886
0
}
887
888
gboolean
889
_ostree_delta_needs_byteswap (GVariant *superblock)
890
0
{
891
0
  switch (_ostree_delta_get_endianness (superblock, NULL))
892
0
    {
893
0
    case OSTREE_DELTA_ENDIAN_BIG:
894
0
      return G_BYTE_ORDER == G_LITTLE_ENDIAN;
895
0
    case OSTREE_DELTA_ENDIAN_LITTLE:
896
0
      return G_BYTE_ORDER == G_BIG_ENDIAN;
897
0
    default:
898
0
      return FALSE;
899
0
    }
900
0
}
901
902
gboolean
903
_ostree_repo_static_delta_delete (OstreeRepo *self, const char *delta_id, GCancellable *cancellable,
904
                                  GError **error)
905
0
{
906
0
  g_autofree char *from = NULL;
907
0
  g_autofree char *to = NULL;
908
0
  if (!_ostree_parse_delta_name (delta_id, &from, &to, error))
909
0
    return FALSE;
910
911
0
  g_autofree char *deltadir = _ostree_get_relative_static_delta_path (from, to, NULL);
912
0
  struct stat buf;
913
0
  if (fstatat (self->repo_dir_fd, deltadir, &buf, 0) != 0)
914
0
    {
915
0
      if (errno == ENOENT)
916
0
        {
917
0
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Can't find delta %s", delta_id);
918
0
          return FALSE;
919
0
        }
920
0
      else
921
0
        return glnx_throw_errno_prefix (error, "fstatat(%s)", deltadir);
922
0
    }
923
924
0
  if (!glnx_shutil_rm_rf_at (self->repo_dir_fd, deltadir, cancellable, error))
925
0
    return FALSE;
926
927
0
  return TRUE;
928
0
}
929
930
gboolean
931
_ostree_repo_static_delta_query_exists (OstreeRepo *self, const char *delta_id,
932
                                        gboolean *out_exists, GCancellable *cancellable,
933
                                        GError **error)
934
0
{
935
0
  g_autofree char *from = NULL;
936
0
  g_autofree char *to = NULL;
937
0
  if (!_ostree_parse_delta_name (delta_id, &from, &to, error))
938
0
    return FALSE;
939
940
0
  g_autofree char *superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to);
941
0
  if (!glnx_fstatat_allow_noent (self->repo_dir_fd, superblock_path, NULL, 0, error))
942
0
    return FALSE;
943
944
0
  *out_exists = (errno == 0);
945
0
  return TRUE;
946
0
}
947
948
gboolean
949
_ostree_repo_static_delta_dump (OstreeRepo *self, const char *delta_id, GCancellable *cancellable,
950
                                GError **error)
951
0
{
952
0
  glnx_autofd int superblock_fd = -1;
953
0
  g_autoptr (GVariant) delta = NULL;
954
0
  g_autoptr (GVariant) delta_superblock = NULL;
955
956
0
  if (strchr (delta_id, '/'))
957
0
    {
958
0
      if (!glnx_openat_rdonly (AT_FDCWD, delta_id, TRUE, &superblock_fd, error))
959
0
        return FALSE;
960
0
    }
961
0
  else
962
0
    {
963
0
      g_autofree char *from = NULL;
964
0
      g_autofree char *to = NULL;
965
0
      if (!_ostree_parse_delta_name (delta_id, &from, &to, error))
966
0
        return FALSE;
967
968
0
      g_autofree char *superblock_path
969
0
          = _ostree_get_relative_static_delta_superblock_path (from, to);
970
0
      if (!glnx_openat_rdonly (self->repo_dir_fd, superblock_path, TRUE, &superblock_fd, error))
971
0
        return FALSE;
972
0
    }
973
974
0
  gboolean is_signed = _ostree_repo_static_delta_is_signed (self, superblock_fd, NULL, NULL);
975
0
  if (is_signed)
976
0
    {
977
0
      if (!ot_variant_read_fd (superblock_fd, 0, (GVariantType *)OSTREE_STATIC_DELTA_SIGNED_FORMAT,
978
0
                               TRUE, &delta, error))
979
0
        return FALSE;
980
981
0
      g_autoptr (GVariant) child = g_variant_get_child_value (delta, 1);
982
0
      g_autoptr (GBytes) bytes = g_variant_get_data_as_bytes (child);
983
0
      delta_superblock = g_variant_new_from_bytes (
984
0
          (GVariantType *)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, bytes, FALSE);
985
0
    }
986
0
  else
987
0
    {
988
0
      if (!ot_variant_read_fd (superblock_fd, 0,
989
0
                               (GVariantType *)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, TRUE,
990
0
                               &delta_superblock, error))
991
0
        return FALSE;
992
0
    }
993
994
0
  g_print ("Delta: %s\n", delta_id);
995
0
  g_print ("Signed: %s\n", is_signed ? "yes" : "no");
996
0
  g_autoptr (GVariant) from_commit_v = NULL;
997
0
  g_variant_get_child (delta_superblock, 2, "@ay", &from_commit_v);
998
0
  g_autofree char *from_commit = NULL;
999
0
  if (g_variant_n_children (from_commit_v) > 0)
1000
0
    {
1001
0
      if (!ostree_checksum_bytes_peek_validate (from_commit_v, error))
1002
0
        return FALSE;
1003
0
      from_commit = ostree_checksum_from_bytes_v (from_commit_v);
1004
0
      g_print ("From: %s\n", from_commit);
1005
0
    }
1006
0
  else
1007
0
    {
1008
0
      g_print ("From <scratch>\n");
1009
0
    }
1010
0
  g_autoptr (GVariant) to_commit_v = NULL;
1011
0
  g_variant_get_child (delta_superblock, 3, "@ay", &to_commit_v);
1012
0
  if (!ostree_checksum_bytes_peek_validate (to_commit_v, error))
1013
0
    return FALSE;
1014
0
  g_autofree char *to_commit = ostree_checksum_from_bytes_v (to_commit_v);
1015
0
  g_print ("To: %s\n", to_commit);
1016
1017
0
  gboolean swap_endian = FALSE;
1018
0
  OstreeDeltaEndianness endianness;
1019
0
  {
1020
0
    const char *endianness_description;
1021
0
    gboolean was_heuristic;
1022
1023
0
    endianness = _ostree_delta_get_endianness (delta_superblock, &was_heuristic);
1024
1025
0
    switch (endianness)
1026
0
      {
1027
0
      case OSTREE_DELTA_ENDIAN_BIG:
1028
0
        if (was_heuristic)
1029
0
          endianness_description = "big (heuristic)";
1030
0
        else
1031
0
          endianness_description = "big";
1032
0
        if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
1033
0
          swap_endian = TRUE;
1034
0
        break;
1035
0
      case OSTREE_DELTA_ENDIAN_LITTLE:
1036
0
        if (was_heuristic)
1037
0
          endianness_description = "little (heuristic)";
1038
0
        else
1039
0
          endianness_description = "little";
1040
0
        if (G_BYTE_ORDER == G_BIG_ENDIAN)
1041
0
          swap_endian = TRUE;
1042
0
        break;
1043
0
      case OSTREE_DELTA_ENDIAN_INVALID:
1044
0
        endianness_description = "invalid";
1045
0
        break;
1046
0
      default:
1047
0
        g_assert_not_reached ();
1048
0
      }
1049
1050
0
    g_print ("Endianness: %s\n", endianness_description);
1051
0
  }
1052
1053
0
  guint64 ts;
1054
0
  g_variant_get_child (delta_superblock, 1, "t", &ts);
1055
0
  g_print ("Timestamp: %" G_GUINT64_FORMAT "\n", GUINT64_FROM_BE (ts));
1056
1057
0
  g_autoptr (GVariant) recurse = NULL;
1058
0
  g_variant_get_child (delta_superblock, 5, "@ay", &recurse);
1059
0
  g_print ("Number of parents: %u\n",
1060
0
           (guint)(g_variant_get_size (recurse) / (OSTREE_SHA256_DIGEST_LEN * 2)));
1061
1062
0
  g_autoptr (GVariant) fallback = NULL;
1063
0
  g_variant_get_child (delta_superblock, 7, "@a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT, &fallback);
1064
0
  const guint n_fallback = g_variant_n_children (fallback);
1065
1066
0
  g_print ("Number of fallback entries: %u\n", n_fallback);
1067
1068
0
  guint64 total_size = 0, total_usize = 0;
1069
0
  guint64 total_fallback_size = 0, total_fallback_usize = 0;
1070
0
  for (guint i = 0; i < n_fallback; i++)
1071
0
    {
1072
0
      guint64 size, usize;
1073
0
      g_autoptr (GVariant) checksum_v = NULL;
1074
0
      char checksum[OSTREE_SHA256_STRING_LEN + 1];
1075
0
      g_variant_get_child (fallback, i, "(y@aytt)", NULL, &checksum_v, &size, &usize);
1076
0
      ostree_checksum_inplace_from_bytes (ostree_checksum_bytes_peek (checksum_v), checksum);
1077
0
      size = maybe_swap_endian_u64 (swap_endian, size);
1078
0
      usize = maybe_swap_endian_u64 (swap_endian, usize);
1079
0
      g_print ("  %s\n", checksum);
1080
0
      total_fallback_size += size;
1081
0
      total_fallback_usize += usize;
1082
0
    }
1083
0
  {
1084
0
    g_autofree char *sizestr = g_format_size (total_fallback_size);
1085
0
    g_autofree char *usizestr = g_format_size (total_fallback_usize);
1086
0
    g_print ("Total Fallback Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_size, sizestr);
1087
0
    g_print ("Total Fallback Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_usize,
1088
0
             usizestr);
1089
0
  }
1090
1091
0
  g_autoptr (GVariant) meta_entries = NULL;
1092
0
  guint n_parts;
1093
1094
0
  g_variant_get_child (delta_superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT,
1095
0
                       &meta_entries);
1096
0
  n_parts = g_variant_n_children (meta_entries);
1097
0
  g_print ("Number of parts: %u\n", n_parts);
1098
1099
0
  for (guint i = 0; i < n_parts; i++)
1100
0
    {
1101
0
      if (!show_one_part (self, swap_endian, from_commit, to_commit, meta_entries, i, &total_size,
1102
0
                          &total_usize, cancellable, error))
1103
0
        return FALSE;
1104
0
    }
1105
1106
0
  {
1107
0
    g_autofree char *sizestr = g_format_size (total_size);
1108
0
    g_autofree char *usizestr = g_format_size (total_usize);
1109
0
    g_print ("Total Part Size: %" G_GUINT64_FORMAT " (%s)\n", total_size, sizestr);
1110
0
    g_print ("Total Part Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_usize, usizestr);
1111
0
  }
1112
1113
0
  {
1114
0
    guint64 overall_size = total_size + total_fallback_size;
1115
0
    guint64 overall_usize = total_usize + total_fallback_usize;
1116
0
    g_autofree char *sizestr = g_format_size (overall_size);
1117
0
    g_autofree char *usizestr = g_format_size (overall_usize);
1118
0
    g_print ("Total Size: %" G_GUINT64_FORMAT " (%s)\n", overall_size, sizestr);
1119
0
    g_print ("Total Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", overall_usize, usizestr);
1120
0
  }
1121
1122
0
  return TRUE;
1123
0
}
1124
1125
/**
1126
 * ostree_repo_static_delta_verify_signature:
1127
 * @self: Repo
1128
 * @delta_id: delta path
1129
 * @sign: Signature engine used to check superblock
1130
 * @out_success_message: (out) (nullable) (optional): success message
1131
 * @error: Error
1132
 *
1133
 * Verify static delta file signature.
1134
 *
1135
 * Returns: TRUE if the signature of static delta file is valid using the
1136
 * signature engine provided, FALSE otherwise.
1137
 *
1138
 * Since: 2020.7
1139
 */
1140
gboolean
1141
ostree_repo_static_delta_verify_signature (OstreeRepo *self, const char *delta_id, OstreeSign *sign,
1142
                                           char **out_success_message, GError **error)
1143
0
{
1144
0
  g_autoptr (GVariant) delta_meta = NULL;
1145
0
  glnx_autofd int delta_fd = -1;
1146
1147
0
  if (strchr (delta_id, '/'))
1148
0
    {
1149
0
      if (!glnx_openat_rdonly (AT_FDCWD, delta_id, TRUE, &delta_fd, error))
1150
0
        return FALSE;
1151
0
    }
1152
0
  else
1153
0
    {
1154
0
      g_autofree char *from = NULL;
1155
0
      g_autofree char *to = NULL;
1156
0
      if (!_ostree_parse_delta_name (delta_id, &from, &to, error))
1157
0
        return FALSE;
1158
1159
0
      g_autofree char *delta_path = _ostree_get_relative_static_delta_superblock_path (from, to);
1160
0
      if (!glnx_openat_rdonly (self->repo_dir_fd, delta_path, TRUE, &delta_fd, error))
1161
0
        return FALSE;
1162
0
    }
1163
1164
0
  if (!_ostree_repo_static_delta_is_signed (self, delta_fd, NULL, error))
1165
0
    return FALSE;
1166
1167
0
  return _ostree_repo_static_delta_verify_signature (self, delta_fd, sign, out_success_message,
1168
0
                                                     error);
1169
0
}
1170
1171
static void
1172
null_or_ptr_array_unref (GPtrArray *array)
1173
0
{
1174
0
  if (array != NULL)
1175
0
    g_ptr_array_unref (array);
1176
0
}
1177
1178
static gboolean
1179
file_has_content (OstreeRepo *repo, const char *subpath, GBytes *data, GCancellable *cancellable)
1180
0
{
1181
0
  struct stat stbuf;
1182
0
  glnx_autofd int existing_fd = -1;
1183
1184
0
  if (!glnx_fstatat (repo->repo_dir_fd, subpath, &stbuf, 0, NULL))
1185
0
    return FALSE;
1186
1187
0
  if (stbuf.st_size != g_bytes_get_size (data))
1188
0
    return FALSE;
1189
1190
0
  if (!glnx_openat_rdonly (repo->repo_dir_fd, subpath, TRUE, &existing_fd, NULL))
1191
0
    return FALSE;
1192
1193
0
  g_autoptr (GBytes) existing_data = glnx_fd_readall_bytes (existing_fd, cancellable, NULL);
1194
0
  if (existing_data == NULL)
1195
0
    return FALSE;
1196
1197
0
  return g_bytes_equal (existing_data, data);
1198
0
}
1199
1200
/**
1201
 * ostree_repo_static_delta_reindex:
1202
 * @repo: Repo
1203
 * @flags: Flags affecting the indexing operation
1204
 * @opt_to_commit: ASCII SHA256 checksum of target commit, or %NULL to index all targets
1205
 * @cancellable: Cancellable
1206
 * @error: Error
1207
 *
1208
 * The delta index for a particular commit lists all the existing deltas that can be used
1209
 * when downloading that commit. This operation regenerates these indexes, either for
1210
 * a particular commit (if @opt_to_commit is non-%NULL), or for all commits that
1211
 * are reachable by an existing delta (if @opt_to_commit is %NULL).
1212
 *
1213
 * This is normally called automatically when the summary is updated in
1214
 * ostree_repo_regenerate_summary().
1215
 *
1216
 * Locking: shared
1217
 *
1218
 * Since: 2020.8
1219
 */
1220
gboolean
1221
ostree_repo_static_delta_reindex (OstreeRepo *repo, OstreeStaticDeltaIndexFlags flags,
1222
                                  const char *opt_to_commit, GCancellable *cancellable,
1223
                                  GError **error)
1224
0
{
1225
0
  g_autoptr (GPtrArray) all_deltas = NULL;
1226
0
  g_autoptr (GHashTable) deltas_to_commit_ht
1227
0
      = NULL; /* map: to checksum -> ptrarray of from checksums (or NULL) */
1228
0
  gboolean opt_indexed_deltas;
1229
1230
  /* Protect against parallel prune operation */
1231
0
  g_autoptr (OstreeRepoAutoLock) lock
1232
0
      = ostree_repo_auto_lock_push (repo, OSTREE_REPO_LOCK_SHARED, cancellable, error);
1233
0
  if (!lock)
1234
0
    return FALSE;
1235
1236
  /* Enusre that the "indexed-deltas" option is set on the config, so we know this when pulling */
1237
0
  if (!ot_keyfile_get_boolean_with_default (repo->config, "core", "indexed-deltas", FALSE,
1238
0
                                            &opt_indexed_deltas, error))
1239
0
    return FALSE;
1240
1241
0
  if (!opt_indexed_deltas)
1242
0
    {
1243
0
      g_autoptr (GKeyFile) config = ostree_repo_copy_config (repo);
1244
0
      g_key_file_set_boolean (config, "core", "indexed-deltas", TRUE);
1245
0
      if (!ostree_repo_write_config (repo, config, error))
1246
0
        return FALSE;
1247
0
    }
1248
1249
0
  deltas_to_commit_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1250
0
                                               (GDestroyNotify)null_or_ptr_array_unref);
1251
1252
0
  if (opt_to_commit == NULL)
1253
0
    {
1254
0
      g_autoptr (GPtrArray) old_indexes = NULL;
1255
1256
      /* To ensure all old index files either is regenerated, or
1257
       * removed, we initialize all existing indexes to NULL in the
1258
       * hashtable. */
1259
0
      if (!ostree_repo_list_static_delta_indexes (repo, &old_indexes, cancellable, error))
1260
0
        return FALSE;
1261
1262
0
      for (int i = 0; i < old_indexes->len; i++)
1263
0
        {
1264
0
          const char *old_index = g_ptr_array_index (old_indexes, i);
1265
0
          g_hash_table_insert (deltas_to_commit_ht, g_strdup (old_index), NULL);
1266
0
        }
1267
0
    }
1268
0
  else
1269
0
    {
1270
0
      if (!ostree_validate_checksum_string (opt_to_commit, error))
1271
0
        return FALSE;
1272
1273
      /* We ensure the specific old index either is regenerated, or removed */
1274
0
      g_hash_table_insert (deltas_to_commit_ht, g_strdup (opt_to_commit), NULL);
1275
0
    }
1276
1277
0
  if (!ostree_repo_list_static_delta_names (repo, &all_deltas, cancellable, error))
1278
0
    return FALSE;
1279
1280
0
  for (int i = 0; i < all_deltas->len; i++)
1281
0
    {
1282
0
      const char *delta_name = g_ptr_array_index (all_deltas, i);
1283
0
      g_autofree char *from = NULL;
1284
0
      g_autofree char *to = NULL;
1285
0
      GPtrArray *deltas_to_commit = NULL;
1286
1287
0
      if (!_ostree_parse_delta_name (delta_name, &from, &to, error))
1288
0
        return FALSE;
1289
1290
0
      if (opt_to_commit != NULL && strcmp (to, opt_to_commit) != 0)
1291
0
        continue;
1292
1293
0
      deltas_to_commit = g_hash_table_lookup (deltas_to_commit_ht, to);
1294
0
      if (deltas_to_commit == NULL)
1295
0
        {
1296
0
          deltas_to_commit = g_ptr_array_new_with_free_func (g_free);
1297
0
          g_hash_table_insert (deltas_to_commit_ht, g_steal_pointer (&to), deltas_to_commit);
1298
0
        }
1299
1300
0
      g_ptr_array_add (deltas_to_commit, g_steal_pointer (&from));
1301
0
    }
1302
1303
0
  GLNX_HASH_TABLE_FOREACH_KV (deltas_to_commit_ht, const char *, to, GPtrArray *, froms)
1304
0
    {
1305
0
      g_autofree char *index_path = _ostree_get_relative_static_delta_index_path (to);
1306
1307
0
      if (froms == NULL)
1308
0
        {
1309
          /* No index to this checksum seen, delete if it exists */
1310
1311
0
          g_debug ("Removing delta index for %s", to);
1312
0
          if (!ot_ensure_unlinked_at (repo->repo_dir_fd, index_path, error))
1313
0
            return FALSE;
1314
0
        }
1315
0
      else
1316
0
        {
1317
0
          g_auto (GVariantDict) index_builder = OT_VARIANT_BUILDER_INITIALIZER;
1318
0
          g_auto (GVariantDict) deltas_builder = OT_VARIANT_BUILDER_INITIALIZER;
1319
0
          g_autoptr (GVariant) index_variant = NULL;
1320
0
          g_autoptr (GBytes) index = NULL;
1321
1322
          /* We sort on from here so that the index file is reproducible */
1323
0
          g_ptr_array_sort (froms, (GCompareFunc)g_strcmp0);
1324
1325
0
          g_variant_dict_init (&deltas_builder, NULL);
1326
1327
0
          for (int i = 0; i < froms->len; i++)
1328
0
            {
1329
0
              const char *from = g_ptr_array_index (froms, i);
1330
0
              g_autofree char *delta_name = NULL;
1331
0
              GVariant *digest;
1332
1333
0
              digest = _ostree_repo_static_delta_superblock_digest (repo, from, to, cancellable,
1334
0
                                                                    error);
1335
0
              if (digest == NULL)
1336
0
                return FALSE;
1337
1338
0
              if (from != NULL)
1339
0
                delta_name = g_strconcat (from, "-", to, NULL);
1340
0
              else
1341
0
                delta_name = g_strdup (to);
1342
1343
0
              g_variant_dict_insert_value (&deltas_builder, delta_name, digest);
1344
0
            }
1345
1346
          /* The toplevel of the index is an a{sv} for extensibility, and we use same key name (and
1347
           * format) as when storing deltas in the summary. */
1348
0
          g_variant_dict_init (&index_builder, NULL);
1349
1350
0
          g_variant_dict_insert_value (&index_builder, OSTREE_SUMMARY_STATIC_DELTAS,
1351
0
                                       g_variant_dict_end (&deltas_builder));
1352
1353
0
          index_variant = g_variant_ref_sink (g_variant_dict_end (&index_builder));
1354
0
          index = g_variant_get_data_as_bytes (index_variant);
1355
1356
0
          g_autofree char *index_dirname = g_path_get_dirname (index_path);
1357
0
          if (!glnx_shutil_mkdir_p_at (repo->repo_dir_fd, index_dirname, DEFAULT_DIRECTORY_MODE,
1358
0
                                       cancellable, error))
1359
0
            return FALSE;
1360
1361
          /* delta indexes are generally small and static, so reading it back and comparing is
1362
             cheap, and it will lower the write load (and particular sync-load) on the disk during
1363
             reindexing (i.e. summary updates), */
1364
0
          if (file_has_content (repo, index_path, index, cancellable))
1365
0
            continue;
1366
1367
0
          g_debug ("Updating delta index for %s", to);
1368
0
          if (!glnx_file_replace_contents_at (repo->repo_dir_fd, index_path,
1369
0
                                              g_bytes_get_data (index, NULL),
1370
0
                                              g_bytes_get_size (index), 0, cancellable, error))
1371
0
            return FALSE;
1372
0
        }
1373
0
    }
1374
1375
0
  return TRUE;
1376
0
}