Coverage Report

Created: 2025-12-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/pack-revindex.c
Line
Count
Source
1
#include "git-compat-util.h"
2
#include "gettext.h"
3
#include "pack-revindex.h"
4
#include "odb.h"
5
#include "packfile.h"
6
#include "strbuf.h"
7
#include "trace2.h"
8
#include "parse.h"
9
#include "repository.h"
10
#include "midx.h"
11
#include "csum-file.h"
12
13
struct revindex_entry {
14
  off_t offset;
15
  unsigned int nr;
16
};
17
18
/*
19
 * Pack index for existing packs give us easy access to the offsets into
20
 * corresponding pack file where each object's data starts, but the entries
21
 * do not store the size of the compressed representation (uncompressed
22
 * size is easily available by examining the pack entry header).  It is
23
 * also rather expensive to find the sha1 for an object given its offset.
24
 *
25
 * The pack index file is sorted by object name mapping to offset;
26
 * this revindex array is a list of offset/index_nr pairs
27
 * ordered by offset, so if you know the offset of an object, next offset
28
 * is where its packed representation ends and the index_nr can be used to
29
 * get the object sha1 from the main index.
30
 */
31
32
/*
33
 * This is a least-significant-digit radix sort.
34
 *
35
 * It sorts each of the "n" items in "entries" by its offset field. The "max"
36
 * parameter must be at least as large as the largest offset in the array,
37
 * and lets us quit the sort early.
38
 */
39
static void sort_revindex(struct revindex_entry *entries, unsigned n, off_t max)
40
0
{
41
  /*
42
   * We use a "digit" size of 16 bits. That keeps our memory
43
   * usage reasonable, and we can generally (for a 4G or smaller
44
   * packfile) quit after two rounds of radix-sorting.
45
   */
46
0
#define DIGIT_SIZE (16)
47
0
#define BUCKETS (1 << DIGIT_SIZE)
48
  /*
49
   * We want to know the bucket that a[i] will go into when we are using
50
   * the digit that is N bits from the (least significant) end.
51
   */
52
0
#define BUCKET_FOR(a, i, bits) (((a)[(i)].offset >> (bits)) & (BUCKETS-1))
53
54
  /*
55
   * We need O(n) temporary storage. Rather than do an extra copy of the
56
   * partial results into "entries", we sort back and forth between the
57
   * real array and temporary storage. In each iteration of the loop, we
58
   * keep track of them with alias pointers, always sorting from "from"
59
   * to "to".
60
   */
61
0
  struct revindex_entry *tmp, *from, *to;
62
0
  int bits;
63
0
  unsigned *pos;
64
65
0
  ALLOC_ARRAY(pos, BUCKETS);
66
0
  ALLOC_ARRAY(tmp, n);
67
0
  from = entries;
68
0
  to = tmp;
69
70
  /*
71
   * If (max >> bits) is zero, then we know that the radix digit we are
72
   * on (and any higher) will be zero for all entries, and our loop will
73
   * be a no-op, as everybody lands in the same zero-th bucket.
74
   */
75
0
  for (bits = 0; max >> bits; bits += DIGIT_SIZE) {
76
0
    unsigned i;
77
78
0
    MEMZERO_ARRAY(pos, BUCKETS);
79
80
    /*
81
     * We want pos[i] to store the index of the last element that
82
     * will go in bucket "i" (actually one past the last element).
83
     * To do this, we first count the items that will go in each
84
     * bucket, which gives us a relative offset from the last
85
     * bucket. We can then cumulatively add the index from the
86
     * previous bucket to get the true index.
87
     */
88
0
    for (i = 0; i < n; i++)
89
0
      pos[BUCKET_FOR(from, i, bits)]++;
90
0
    for (i = 1; i < BUCKETS; i++)
91
0
      pos[i] += pos[i-1];
92
93
    /*
94
     * Now we can drop the elements into their correct buckets (in
95
     * our temporary array).  We iterate the pos counter backwards
96
     * to avoid using an extra index to count up. And since we are
97
     * going backwards there, we must also go backwards through the
98
     * array itself, to keep the sort stable.
99
     *
100
     * Note that we use an unsigned iterator to make sure we can
101
     * handle 2^32-1 objects, even on a 32-bit system. But this
102
     * means we cannot use the more obvious "i >= 0" loop condition
103
     * for counting backwards, and must instead check for
104
     * wrap-around with UINT_MAX.
105
     */
106
0
    for (i = n - 1; i != UINT_MAX; i--)
107
0
      to[--pos[BUCKET_FOR(from, i, bits)]] = from[i];
108
109
    /*
110
     * Now "to" contains the most sorted list, so we swap "from" and
111
     * "to" for the next iteration.
112
     */
113
0
    SWAP(from, to);
114
0
  }
115
116
  /*
117
   * If we ended with our data in the original array, great. If not,
118
   * we have to move it back from the temporary storage.
119
   */
120
0
  if (from != entries)
121
0
    COPY_ARRAY(entries, tmp, n);
122
0
  free(tmp);
123
0
  free(pos);
124
125
0
#undef BUCKET_FOR
126
0
#undef BUCKETS
127
0
#undef DIGIT_SIZE
128
0
}
129
130
/*
131
 * Ordered list of offsets of objects in the pack.
132
 */
133
static void create_pack_revindex(struct packed_git *p)
134
0
{
135
0
  const unsigned num_ent = p->num_objects;
136
0
  unsigned i;
137
0
  const char *index = p->index_data;
138
0
  const unsigned hashsz = p->repo->hash_algo->rawsz;
139
140
0
  ALLOC_ARRAY(p->revindex, num_ent + 1);
141
0
  index += 4 * 256;
142
143
0
  if (p->index_version > 1) {
144
0
    const uint32_t *off_32 =
145
0
      (uint32_t *)(index + 8 + (size_t)p->num_objects * (hashsz + 4));
146
0
    const uint32_t *off_64 = off_32 + p->num_objects;
147
0
    for (i = 0; i < num_ent; i++) {
148
0
      const uint32_t off = ntohl(*off_32++);
149
0
      if (!(off & 0x80000000)) {
150
0
        p->revindex[i].offset = off;
151
0
      } else {
152
0
        p->revindex[i].offset = get_be64(off_64);
153
0
        off_64 += 2;
154
0
      }
155
0
      p->revindex[i].nr = i;
156
0
    }
157
0
  } else {
158
0
    for (i = 0; i < num_ent; i++) {
159
0
      const uint32_t hl = *((uint32_t *)(index + (hashsz + 4) * i));
160
0
      p->revindex[i].offset = ntohl(hl);
161
0
      p->revindex[i].nr = i;
162
0
    }
163
0
  }
164
165
  /*
166
   * This knows the pack format -- the hash trailer
167
   * follows immediately after the last object data.
168
   */
169
0
  p->revindex[num_ent].offset = p->pack_size - hashsz;
170
0
  p->revindex[num_ent].nr = -1;
171
0
  sort_revindex(p->revindex, num_ent, p->pack_size);
172
0
}
173
174
static int create_pack_revindex_in_memory(struct packed_git *p)
175
0
{
176
0
  if (git_env_bool(GIT_TEST_REV_INDEX_DIE_IN_MEMORY, 0))
177
0
    die("dying as requested by '%s'",
178
0
        GIT_TEST_REV_INDEX_DIE_IN_MEMORY);
179
0
  if (open_pack_index(p))
180
0
    return -1;
181
0
  create_pack_revindex(p);
182
0
  return 0;
183
0
}
184
185
static char *pack_revindex_filename(struct packed_git *p)
186
0
{
187
0
  size_t len;
188
0
  if (!strip_suffix(p->pack_name, ".pack", &len))
189
0
    BUG("pack_name does not end in .pack");
190
0
  return xstrfmt("%.*s.rev", (int)len, p->pack_name);
191
0
}
192
193
0
#define RIDX_HEADER_SIZE (12)
194
195
static size_t ridx_min_size(const struct git_hash_algo *algo)
196
0
{
197
0
  return RIDX_HEADER_SIZE + (2 * algo->rawsz);
198
0
}
199
200
struct revindex_header {
201
  uint32_t signature;
202
  uint32_t version;
203
  uint32_t hash_id;
204
};
205
206
static int load_revindex_from_disk(const struct git_hash_algo *algo,
207
           char *revindex_name,
208
           uint32_t num_objects,
209
           const uint32_t **data_p, size_t *len_p)
210
0
{
211
0
  int fd, ret = 0;
212
0
  struct stat st;
213
0
  void *data = NULL;
214
0
  size_t revindex_size;
215
0
  struct revindex_header *hdr;
216
217
0
  if (git_env_bool(GIT_TEST_REV_INDEX_DIE_ON_DISK, 0))
218
0
    die("dying as requested by '%s'", GIT_TEST_REV_INDEX_DIE_ON_DISK);
219
220
0
  fd = git_open(revindex_name);
221
222
0
  if (fd < 0) {
223
    /* "No file" means return 1. */
224
0
    ret = 1;
225
0
    goto cleanup;
226
0
  }
227
0
  if (fstat(fd, &st)) {
228
0
    ret = error_errno(_("failed to read %s"), revindex_name);
229
0
    goto cleanup;
230
0
  }
231
232
0
  revindex_size = xsize_t(st.st_size);
233
234
0
  if (revindex_size < ridx_min_size(algo)) {
235
0
    ret = error(_("reverse-index file %s is too small"), revindex_name);
236
0
    goto cleanup;
237
0
  }
238
239
0
  if (revindex_size - ridx_min_size(algo) != st_mult(sizeof(uint32_t), num_objects)) {
240
0
    ret = error(_("reverse-index file %s is corrupt"), revindex_name);
241
0
    goto cleanup;
242
0
  }
243
244
0
  data = xmmap(NULL, revindex_size, PROT_READ, MAP_PRIVATE, fd, 0);
245
0
  hdr = data;
246
247
0
  if (ntohl(hdr->signature) != RIDX_SIGNATURE) {
248
0
    ret = error(_("reverse-index file %s has unknown signature"), revindex_name);
249
0
    goto cleanup;
250
0
  }
251
0
  if (ntohl(hdr->version) != 1) {
252
0
    ret = error(_("reverse-index file %s has unsupported version %"PRIu32),
253
0
          revindex_name, ntohl(hdr->version));
254
0
    goto cleanup;
255
0
  }
256
0
  if (!(ntohl(hdr->hash_id) == 1 || ntohl(hdr->hash_id) == 2)) {
257
0
    ret = error(_("reverse-index file %s has unsupported hash id %"PRIu32),
258
0
          revindex_name, ntohl(hdr->hash_id));
259
0
    goto cleanup;
260
0
  }
261
262
0
cleanup:
263
0
  if (ret) {
264
0
    if (data)
265
0
      munmap(data, revindex_size);
266
0
  } else {
267
0
    *len_p = revindex_size;
268
0
    *data_p = (const uint32_t *)data;
269
0
  }
270
271
0
  if (fd >= 0)
272
0
    close(fd);
273
0
  return ret;
274
0
}
275
276
int load_pack_revindex_from_disk(struct packed_git *p)
277
0
{
278
0
  char *revindex_name;
279
0
  int ret;
280
0
  if (open_pack_index(p))
281
0
    return -1;
282
283
0
  revindex_name = pack_revindex_filename(p);
284
285
0
  ret = load_revindex_from_disk(p->repo->hash_algo,
286
0
              revindex_name,
287
0
              p->num_objects,
288
0
              &p->revindex_map,
289
0
              &p->revindex_size);
290
0
  if (ret)
291
0
    goto cleanup;
292
293
0
  p->revindex_data = (const uint32_t *)((const char *)p->revindex_map + RIDX_HEADER_SIZE);
294
295
0
cleanup:
296
0
  free(revindex_name);
297
0
  return ret;
298
0
}
299
300
int load_pack_revindex(struct repository *r, struct packed_git *p)
301
0
{
302
0
  if (p->revindex || p->revindex_data)
303
0
    return 0;
304
305
0
  prepare_repo_settings(r);
306
307
0
  if (r->settings.pack_read_reverse_index &&
308
0
      !load_pack_revindex_from_disk(p))
309
0
    return 0;
310
0
  else if (!create_pack_revindex_in_memory(p))
311
0
    return 0;
312
0
  return -1;
313
0
}
314
315
/*
316
 * verify_pack_revindex verifies that the on-disk rev-index for the given
317
 * pack-file is the same that would be created if written from scratch.
318
 *
319
 * A negative number is returned on error.
320
 */
321
int verify_pack_revindex(struct packed_git *p)
322
0
{
323
0
  int res = 0;
324
325
  /* Do not bother checking if not initialized. */
326
0
  if (!p->revindex_map || !p->revindex_data)
327
0
    return res;
328
329
0
  if (!hashfile_checksum_valid(p->repo->hash_algo,
330
0
             (const unsigned char *)p->revindex_map, p->revindex_size)) {
331
0
    error(_("invalid checksum"));
332
0
    res = -1;
333
0
  }
334
335
  /* This may fail due to a broken .idx. */
336
0
  if (create_pack_revindex_in_memory(p))
337
0
    return res;
338
339
0
  for (size_t i = 0; i < p->num_objects; i++) {
340
0
    uint32_t nr = p->revindex[i].nr;
341
0
    uint32_t rev_val = get_be32(p->revindex_data + i);
342
343
0
    if (nr != rev_val) {
344
0
      error(_("invalid rev-index position at %"PRIu64": %"PRIu32" != %"PRIu32""),
345
0
            (uint64_t)i, nr, rev_val);
346
0
      res = -1;
347
0
    }
348
0
  }
349
350
0
  return res;
351
0
}
352
353
static int can_use_midx_ridx_chunk(struct multi_pack_index *m)
354
0
{
355
0
  if (!m->chunk_revindex)
356
0
    return 0;
357
0
  if (m->chunk_revindex_len != st_mult(sizeof(uint32_t), m->num_objects)) {
358
0
    error(_("multi-pack-index reverse-index chunk is the wrong size"));
359
0
    return 0;
360
0
  }
361
0
  return 1;
362
0
}
363
364
int load_midx_revindex(struct multi_pack_index *m)
365
0
{
366
0
  struct strbuf revindex_name = STRBUF_INIT;
367
0
  int ret;
368
369
0
  if (m->revindex_data)
370
0
    return 0;
371
372
0
  if (can_use_midx_ridx_chunk(m)) {
373
    /*
374
     * If the MIDX `m` has a `RIDX` chunk, then use its contents for
375
     * the reverse index instead of trying to load a separate `.rev`
376
     * file.
377
     *
378
     * Note that we do *not* set `m->revindex_map` here, since we do
379
     * not want to accidentally call munmap() in the middle of the
380
     * MIDX.
381
     */
382
0
    trace2_data_string("load_midx_revindex", m->source->odb->repo,
383
0
           "source", "midx");
384
0
    m->revindex_data = (const uint32_t *)m->chunk_revindex;
385
0
    return 0;
386
0
  }
387
388
0
  trace2_data_string("load_midx_revindex", m->source->odb->repo,
389
0
         "source", "rev");
390
391
0
  if (m->has_chain)
392
0
    get_split_midx_filename_ext(m->source, &revindex_name,
393
0
              get_midx_checksum(m),
394
0
              MIDX_EXT_REV);
395
0
  else
396
0
    get_midx_filename_ext(m->source, &revindex_name,
397
0
              get_midx_checksum(m),
398
0
              MIDX_EXT_REV);
399
400
0
  ret = load_revindex_from_disk(m->source->odb->repo->hash_algo,
401
0
              revindex_name.buf,
402
0
              m->num_objects,
403
0
              &m->revindex_map,
404
0
              &m->revindex_len);
405
0
  if (ret)
406
0
    goto cleanup;
407
408
0
  m->revindex_data = (const uint32_t *)((const char *)m->revindex_map + RIDX_HEADER_SIZE);
409
410
0
cleanup:
411
0
  strbuf_release(&revindex_name);
412
0
  return ret;
413
0
}
414
415
int close_midx_revindex(struct multi_pack_index *m)
416
0
{
417
0
  if (!m || !m->revindex_map)
418
0
    return 0;
419
420
0
  munmap((void*)m->revindex_map, m->revindex_len);
421
422
0
  m->revindex_map = NULL;
423
0
  m->revindex_data = NULL;
424
0
  m->revindex_len = 0;
425
426
0
  return 0;
427
0
}
428
429
int offset_to_pack_pos(struct packed_git *p, off_t ofs, uint32_t *pos)
430
0
{
431
0
  unsigned lo, hi;
432
433
0
  if (load_pack_revindex(p->repo, p) < 0)
434
0
    return -1;
435
436
0
  lo = 0;
437
0
  hi = p->num_objects + 1;
438
439
0
  do {
440
0
    const unsigned mi = lo + (hi - lo) / 2;
441
0
    off_t got = pack_pos_to_offset(p, mi);
442
443
0
    if (got == ofs) {
444
0
      *pos = mi;
445
0
      return 0;
446
0
    } else if (ofs < got)
447
0
      hi = mi;
448
0
    else
449
0
      lo = mi + 1;
450
0
  } while (lo < hi);
451
452
0
  error("bad offset for revindex");
453
0
  return -1;
454
0
}
455
456
uint32_t pack_pos_to_index(struct packed_git *p, uint32_t pos)
457
0
{
458
0
  if (!(p->revindex || p->revindex_data))
459
0
    BUG("pack_pos_to_index: reverse index not yet loaded");
460
0
  if (p->num_objects <= pos)
461
0
    BUG("pack_pos_to_index: out-of-bounds object at %"PRIu32, pos);
462
463
0
  if (p->revindex)
464
0
    return p->revindex[pos].nr;
465
0
  else
466
0
    return get_be32(p->revindex_data + pos);
467
0
}
468
469
off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos)
470
0
{
471
0
  if (!(p->revindex || p->revindex_data))
472
0
    BUG("pack_pos_to_index: reverse index not yet loaded");
473
0
  if (p->num_objects < pos)
474
0
    BUG("pack_pos_to_offset: out-of-bounds object at %"PRIu32, pos);
475
476
0
  if (p->revindex)
477
0
    return p->revindex[pos].offset;
478
0
  else if (pos == p->num_objects)
479
0
    return p->pack_size - p->repo->hash_algo->rawsz;
480
0
  else
481
0
    return nth_packed_object_offset(p, pack_pos_to_index(p, pos));
482
0
}
483
484
uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos)
485
0
{
486
0
  while (m && pos < m->num_objects_in_base)
487
0
    m = m->base_midx;
488
0
  if (!m)
489
0
    BUG("NULL multi-pack-index for object position: %"PRIu32, pos);
490
0
  if (!m->revindex_data)
491
0
    BUG("pack_pos_to_midx: reverse index not yet loaded");
492
0
  if (m->num_objects + m->num_objects_in_base <= pos)
493
0
    BUG("pack_pos_to_midx: out-of-bounds object at %"PRIu32, pos);
494
0
  return get_be32(m->revindex_data + pos - m->num_objects_in_base);
495
0
}
496
497
struct midx_pack_key {
498
  uint32_t pack;
499
  off_t offset;
500
501
  uint32_t preferred_pack;
502
  struct multi_pack_index *midx;
503
};
504
505
static int midx_pack_order_cmp(const void *va, const void *vb)
506
0
{
507
0
  const struct midx_pack_key *key = va;
508
0
  struct multi_pack_index *midx = key->midx;
509
510
0
  size_t pos = (uint32_t *)vb - (const uint32_t *)midx->revindex_data;
511
0
  uint32_t versus = pack_pos_to_midx(midx, pos + midx->num_objects_in_base);
512
0
  uint32_t versus_pack = nth_midxed_pack_int_id(midx, versus);
513
0
  off_t versus_offset;
514
515
0
  uint32_t key_preferred = key->pack == key->preferred_pack;
516
0
  uint32_t versus_preferred = versus_pack == key->preferred_pack;
517
518
  /*
519
   * First, compare the preferred-ness, noting that the preferred pack
520
   * comes first.
521
   */
522
0
  if (key_preferred && !versus_preferred)
523
0
    return -1;
524
0
  else if (!key_preferred && versus_preferred)
525
0
    return 1;
526
527
  /* Then, break ties first by comparing the pack IDs. */
528
0
  if (key->pack < versus_pack)
529
0
    return -1;
530
0
  else if (key->pack > versus_pack)
531
0
    return 1;
532
533
  /* Finally, break ties by comparing offsets within a pack. */
534
0
  versus_offset = nth_midxed_offset(midx, versus);
535
0
  if (key->offset < versus_offset)
536
0
    return -1;
537
0
  else if (key->offset > versus_offset)
538
0
    return 1;
539
540
0
  return 0;
541
0
}
542
543
static int midx_key_to_pack_pos(struct multi_pack_index *m,
544
        struct midx_pack_key *key,
545
        uint32_t *pos)
546
0
{
547
0
  uint32_t *found;
548
549
0
  if (key->pack >= m->num_packs + m->num_packs_in_base)
550
0
    BUG("MIDX pack lookup out of bounds (%"PRIu32" >= %"PRIu32")",
551
0
        key->pack, m->num_packs + m->num_packs_in_base);
552
  /*
553
   * The preferred pack sorts first, so determine its identifier by
554
   * looking at the first object in pseudo-pack order.
555
   *
556
   * Note that if no --preferred-pack is explicitly given when writing a
557
   * multi-pack index, then whichever pack has the lowest identifier
558
   * implicitly is preferred (and includes all its objects, since ties are
559
   * broken first by pack identifier).
560
   */
561
0
  if (midx_preferred_pack(key->midx, &key->preferred_pack) < 0)
562
0
    return error(_("could not determine preferred pack"));
563
564
0
  found = bsearch(key, m->revindex_data, m->num_objects,
565
0
      sizeof(*m->revindex_data),
566
0
      midx_pack_order_cmp);
567
568
0
  if (!found)
569
0
    return -1;
570
571
0
  *pos = (found - m->revindex_data) + m->num_objects_in_base;
572
573
0
  return 0;
574
0
}
575
576
int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos)
577
0
{
578
0
  struct midx_pack_key key;
579
580
0
  while (m && at < m->num_objects_in_base)
581
0
    m = m->base_midx;
582
0
  if (!m)
583
0
    BUG("NULL multi-pack-index for object position: %"PRIu32, at);
584
0
  if (!m->revindex_data)
585
0
    BUG("midx_to_pack_pos: reverse index not yet loaded");
586
0
  if (m->num_objects + m->num_objects_in_base <= at)
587
0
    BUG("midx_to_pack_pos: out-of-bounds object at %"PRIu32, at);
588
589
0
  key.pack = nth_midxed_pack_int_id(m, at);
590
0
  key.offset = nth_midxed_offset(m, at);
591
0
  key.midx = m;
592
593
0
  return midx_key_to_pack_pos(m, &key, pos);
594
0
}
595
596
int midx_pair_to_pack_pos(struct multi_pack_index *m, uint32_t pack_int_id,
597
        off_t ofs, uint32_t *pos)
598
0
{
599
0
  struct midx_pack_key key = {
600
0
    .pack = pack_int_id,
601
0
    .offset = ofs,
602
0
    .midx = m,
603
0
  };
604
0
  return midx_key_to_pack_pos(m, &key, pos);
605
0
}