File Coverage

deps/libgit2/src/libgit2/midx.c
Criterion Covered Total %
statement 9 507 1.7
branch 2 270 0.7
condition n/a
subroutine n/a
pod n/a
total 11 777 1.4


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
4             * This file is part of libgit2, distributed under the GNU GPL v2 with
5             * a Linking Exception. For full terms see the included COPYING file.
6             */
7              
8             #include "midx.h"
9              
10             #include "array.h"
11             #include "buf.h"
12             #include "filebuf.h"
13             #include "futils.h"
14             #include "hash.h"
15             #include "odb.h"
16             #include "pack.h"
17             #include "fs_path.h"
18             #include "repository.h"
19             #include "str.h"
20              
21             #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
22             #define MIDX_VERSION 1
23             #define MIDX_OBJECT_ID_VERSION 1
24             struct git_midx_header {
25             uint32_t signature;
26             uint8_t version;
27             uint8_t object_id_version;
28             uint8_t chunks;
29             uint8_t base_midx_files;
30             uint32_t packfiles;
31             };
32              
33             #define MIDX_PACKFILE_NAMES_ID 0x504e414d /* "PNAM" */
34             #define MIDX_OID_FANOUT_ID 0x4f494446 /* "OIDF" */
35             #define MIDX_OID_LOOKUP_ID 0x4f49444c /* "OIDL" */
36             #define MIDX_OBJECT_OFFSETS_ID 0x4f4f4646 /* "OOFF" */
37             #define MIDX_OBJECT_LARGE_OFFSETS_ID 0x4c4f4646 /* "LOFF" */
38              
39             struct git_midx_chunk {
40             off64_t offset;
41             size_t length;
42             };
43              
44             typedef int (*midx_write_cb)(const char *buf, size_t size, void *cb_data);
45              
46 0           static int midx_error(const char *message)
47             {
48 0           git_error_set(GIT_ERROR_ODB, "invalid multi-pack-index file - %s", message);
49 0           return -1;
50             }
51              
52 0           static int midx_parse_packfile_names(
53             git_midx_file *idx,
54             const unsigned char *data,
55             uint32_t packfiles,
56             struct git_midx_chunk *chunk)
57             {
58             int error;
59             uint32_t i;
60 0           char *packfile_name = (char *)(data + chunk->offset);
61 0           size_t chunk_size = chunk->length, len;
62 0 0         if (chunk->offset == 0)
63 0           return midx_error("missing Packfile Names chunk");
64 0 0         if (chunk->length == 0)
65 0           return midx_error("empty Packfile Names chunk");
66 0 0         if ((error = git_vector_init(&idx->packfile_names, packfiles, git__strcmp_cb)) < 0)
67 0           return error;
68 0 0         for (i = 0; i < packfiles; ++i) {
69 0           len = p_strnlen(packfile_name, chunk_size);
70 0 0         if (len == 0)
71 0           return midx_error("empty packfile name");
72 0 0         if (len + 1 > chunk_size)
73 0           return midx_error("unterminated packfile name");
74 0           git_vector_insert(&idx->packfile_names, packfile_name);
75 0 0         if (i && strcmp(git_vector_get(&idx->packfile_names, i - 1), packfile_name) >= 0)
    0          
76 0           return midx_error("packfile names are not sorted");
77 0 0         if (strlen(packfile_name) <= strlen(".idx") || git__suffixcmp(packfile_name, ".idx") != 0)
    0          
78 0           return midx_error("non-.idx packfile name");
79 0 0         if (strchr(packfile_name, '/') != NULL || strchr(packfile_name, '\\') != NULL)
    0          
80 0           return midx_error("non-local packfile");
81 0           packfile_name += len + 1;
82 0           chunk_size -= len + 1;
83             }
84 0           return 0;
85             }
86              
87 0           static int midx_parse_oid_fanout(
88             git_midx_file *idx,
89             const unsigned char *data,
90             struct git_midx_chunk *chunk_oid_fanout)
91             {
92             uint32_t i, nr;
93 0 0         if (chunk_oid_fanout->offset == 0)
94 0           return midx_error("missing OID Fanout chunk");
95 0 0         if (chunk_oid_fanout->length == 0)
96 0           return midx_error("empty OID Fanout chunk");
97 0 0         if (chunk_oid_fanout->length != 256 * 4)
98 0           return midx_error("OID Fanout chunk has wrong length");
99              
100 0           idx->oid_fanout = (const uint32_t *)(data + chunk_oid_fanout->offset);
101 0           nr = 0;
102 0 0         for (i = 0; i < 256; ++i) {
103 0           uint32_t n = ntohl(idx->oid_fanout[i]);
104 0 0         if (n < nr)
105 0           return midx_error("index is non-monotonic");
106 0           nr = n;
107             }
108 0           idx->num_objects = nr;
109 0           return 0;
110             }
111              
112 0           static int midx_parse_oid_lookup(
113             git_midx_file *idx,
114             const unsigned char *data,
115             struct git_midx_chunk *chunk_oid_lookup)
116             {
117             uint32_t i;
118 0           unsigned char *oid, *prev_oid, zero_oid[GIT_OID_RAWSZ] = {0};
119              
120 0 0         if (chunk_oid_lookup->offset == 0)
121 0           return midx_error("missing OID Lookup chunk");
122 0 0         if (chunk_oid_lookup->length == 0)
123 0           return midx_error("empty OID Lookup chunk");
124 0 0         if (chunk_oid_lookup->length != idx->num_objects * GIT_OID_RAWSZ)
125 0           return midx_error("OID Lookup chunk has wrong length");
126              
127 0           idx->oid_lookup = oid = (unsigned char *)(data + chunk_oid_lookup->offset);
128 0           prev_oid = zero_oid;
129 0 0         for (i = 0; i < idx->num_objects; ++i, oid += GIT_OID_RAWSZ) {
130 0 0         if (git_oid_raw_cmp(prev_oid, oid) >= 0)
131 0           return midx_error("OID Lookup index is non-monotonic");
132 0           prev_oid = oid;
133             }
134              
135 0           return 0;
136             }
137              
138 0           static int midx_parse_object_offsets(
139             git_midx_file *idx,
140             const unsigned char *data,
141             struct git_midx_chunk *chunk_object_offsets)
142             {
143 0 0         if (chunk_object_offsets->offset == 0)
144 0           return midx_error("missing Object Offsets chunk");
145 0 0         if (chunk_object_offsets->length == 0)
146 0           return midx_error("empty Object Offsets chunk");
147 0 0         if (chunk_object_offsets->length != idx->num_objects * 8)
148 0           return midx_error("Object Offsets chunk has wrong length");
149              
150 0           idx->object_offsets = data + chunk_object_offsets->offset;
151              
152 0           return 0;
153             }
154              
155 0           static int midx_parse_object_large_offsets(
156             git_midx_file *idx,
157             const unsigned char *data,
158             struct git_midx_chunk *chunk_object_large_offsets)
159             {
160 0 0         if (chunk_object_large_offsets->length == 0)
161 0           return 0;
162 0 0         if (chunk_object_large_offsets->length % 8 != 0)
163 0           return midx_error("malformed Object Large Offsets chunk");
164              
165 0           idx->object_large_offsets = data + chunk_object_large_offsets->offset;
166 0           idx->num_object_large_offsets = chunk_object_large_offsets->length / 8;
167              
168 0           return 0;
169             }
170              
171 0           int git_midx_parse(
172             git_midx_file *idx,
173             const unsigned char *data,
174             size_t size)
175             {
176             struct git_midx_header *hdr;
177             const unsigned char *chunk_hdr;
178             struct git_midx_chunk *last_chunk;
179             uint32_t i;
180             off64_t last_chunk_offset, chunk_offset, trailer_offset;
181             size_t checksum_size;
182             int error;
183 0           struct git_midx_chunk chunk_packfile_names = {0},
184 0           chunk_oid_fanout = {0},
185 0           chunk_oid_lookup = {0},
186 0           chunk_object_offsets = {0},
187 0           chunk_object_large_offsets = {0};
188              
189 0 0         GIT_ASSERT_ARG(idx);
190              
191 0 0         if (size < sizeof(struct git_midx_header) + GIT_OID_RAWSZ)
192 0           return midx_error("multi-pack index is too short");
193              
194 0           hdr = ((struct git_midx_header *)data);
195              
196 0 0         if (hdr->signature != htonl(MIDX_SIGNATURE) ||
    0          
197 0 0         hdr->version != MIDX_VERSION ||
198 0           hdr->object_id_version != MIDX_OBJECT_ID_VERSION) {
199 0           return midx_error("unsupported multi-pack index version");
200             }
201 0 0         if (hdr->chunks == 0)
202 0           return midx_error("no chunks in multi-pack index");
203              
204             /*
205             * The very first chunk's offset should be after the header, all the chunk
206             * headers, and a special zero chunk.
207             */
208 0           last_chunk_offset =
209 0           sizeof(struct git_midx_header) +
210 0           (1 + hdr->chunks) * 12;
211              
212 0           checksum_size = GIT_HASH_SHA1_SIZE;
213 0           trailer_offset = size - checksum_size;
214              
215 0 0         if (trailer_offset < last_chunk_offset)
216 0           return midx_error("wrong index size");
217 0           memcpy(idx->checksum, data + trailer_offset, checksum_size);
218              
219 0           chunk_hdr = data + sizeof(struct git_midx_header);
220 0           last_chunk = NULL;
221 0 0         for (i = 0; i < hdr->chunks; ++i, chunk_hdr += 12) {
222 0           uint32_t chunk_id = ntohl(*((uint32_t *)(chunk_hdr + 0)));
223 0           uint64_t high_offset = ((uint64_t)ntohl(*((uint32_t *)(chunk_hdr + 4)))) & 0xffffffffu;
224 0           uint64_t low_offset = ((uint64_t)ntohl(*((uint32_t *)(chunk_hdr + 8)))) & 0xffffffffu;
225              
226 0 0         if (high_offset >= INT32_MAX)
227 0           return midx_error("chunk offset out of range");
228 0           chunk_offset = (off64_t)(high_offset << 32 | low_offset);
229 0 0         if (chunk_offset < last_chunk_offset)
230 0           return midx_error("chunks are non-monotonic");
231 0 0         if (chunk_offset >= trailer_offset)
232 0           return midx_error("chunks extend beyond the trailer");
233 0 0         if (last_chunk != NULL)
234 0           last_chunk->length = (size_t)(chunk_offset - last_chunk_offset);
235 0           last_chunk_offset = chunk_offset;
236              
237 0           switch (chunk_id) {
238             case MIDX_PACKFILE_NAMES_ID:
239 0           chunk_packfile_names.offset = last_chunk_offset;
240 0           last_chunk = &chunk_packfile_names;
241 0           break;
242              
243             case MIDX_OID_FANOUT_ID:
244 0           chunk_oid_fanout.offset = last_chunk_offset;
245 0           last_chunk = &chunk_oid_fanout;
246 0           break;
247              
248             case MIDX_OID_LOOKUP_ID:
249 0           chunk_oid_lookup.offset = last_chunk_offset;
250 0           last_chunk = &chunk_oid_lookup;
251 0           break;
252              
253             case MIDX_OBJECT_OFFSETS_ID:
254 0           chunk_object_offsets.offset = last_chunk_offset;
255 0           last_chunk = &chunk_object_offsets;
256 0           break;
257              
258             case MIDX_OBJECT_LARGE_OFFSETS_ID:
259 0           chunk_object_large_offsets.offset = last_chunk_offset;
260 0           last_chunk = &chunk_object_large_offsets;
261 0           break;
262              
263             default:
264 0           return midx_error("unrecognized chunk ID");
265             }
266             }
267 0           last_chunk->length = (size_t)(trailer_offset - last_chunk_offset);
268              
269 0           error = midx_parse_packfile_names(
270             idx, data, ntohl(hdr->packfiles), &chunk_packfile_names);
271 0 0         if (error < 0)
272 0           return error;
273 0           error = midx_parse_oid_fanout(idx, data, &chunk_oid_fanout);
274 0 0         if (error < 0)
275 0           return error;
276 0           error = midx_parse_oid_lookup(idx, data, &chunk_oid_lookup);
277 0 0         if (error < 0)
278 0           return error;
279 0           error = midx_parse_object_offsets(idx, data, &chunk_object_offsets);
280 0 0         if (error < 0)
281 0           return error;
282 0           error = midx_parse_object_large_offsets(idx, data, &chunk_object_large_offsets);
283 0 0         if (error < 0)
284 0           return error;
285              
286 0           return 0;
287             }
288              
289 213           int git_midx_open(
290             git_midx_file **idx_out,
291             const char *path)
292             {
293             git_midx_file *idx;
294 213           git_file fd = -1;
295             size_t idx_size;
296             struct stat st;
297             int error;
298              
299             /* TODO: properly open the file without access time using O_NOATIME */
300 213           fd = git_futils_open_ro(path);
301 213 50         if (fd < 0)
302 213           return fd;
303              
304 0 0         if (p_fstat(fd, &st) < 0) {
305 0           p_close(fd);
306 0           git_error_set(GIT_ERROR_ODB, "multi-pack-index file not found - '%s'", path);
307 0           return -1;
308             }
309              
310 0 0         if (!S_ISREG(st.st_mode) || !git__is_sizet(st.st_size)) {
    0          
311 0           p_close(fd);
312 0           git_error_set(GIT_ERROR_ODB, "invalid pack index '%s'", path);
313 0           return -1;
314             }
315 0           idx_size = (size_t)st.st_size;
316              
317 0           idx = git__calloc(1, sizeof(git_midx_file));
318 0 0         GIT_ERROR_CHECK_ALLOC(idx);
319              
320 0           error = git_str_sets(&idx->filename, path);
321 0 0         if (error < 0)
322 0           return error;
323              
324 0           error = git_futils_mmap_ro(&idx->index_map, fd, 0, idx_size);
325 0           p_close(fd);
326 0 0         if (error < 0) {
327 0           git_midx_free(idx);
328 0           return error;
329             }
330              
331 0 0         if ((error = git_midx_parse(idx, idx->index_map.data, idx_size)) < 0) {
332 0           git_midx_free(idx);
333 0           return error;
334             }
335              
336 0           *idx_out = idx;
337 213           return 0;
338             }
339              
340 0           bool git_midx_needs_refresh(
341             const git_midx_file *idx,
342             const char *path)
343             {
344 0           git_file fd = -1;
345             struct stat st;
346             ssize_t bytes_read;
347             unsigned char checksum[GIT_HASH_SHA1_SIZE];
348             size_t checksum_size;
349              
350             /* TODO: properly open the file without access time using O_NOATIME */
351 0           fd = git_futils_open_ro(path);
352 0 0         if (fd < 0)
353 0           return true;
354              
355 0 0         if (p_fstat(fd, &st) < 0) {
356 0           p_close(fd);
357 0           return true;
358             }
359              
360 0           if (!S_ISREG(st.st_mode) ||
361 0 0         !git__is_sizet(st.st_size) ||
362 0           (size_t)st.st_size != idx->index_map.len) {
363 0           p_close(fd);
364 0           return true;
365             }
366              
367 0           checksum_size = GIT_HASH_SHA1_SIZE;
368 0           bytes_read = p_pread(fd, checksum, checksum_size, st.st_size - GIT_OID_RAWSZ);
369 0           p_close(fd);
370              
371 0 0         if (bytes_read != (ssize_t)checksum_size)
372 0           return true;
373              
374 0           return (memcmp(checksum, idx->checksum, checksum_size) != 0);
375             }
376              
377 0           int git_midx_entry_find(
378             git_midx_entry *e,
379             git_midx_file *idx,
380             const git_oid *short_oid,
381             size_t len)
382             {
383 0           int pos, found = 0;
384             size_t pack_index;
385             uint32_t hi, lo;
386 0           unsigned char *current = NULL;
387             const unsigned char *object_offset;
388             off64_t offset;
389              
390 0 0         GIT_ASSERT_ARG(idx);
391              
392 0           hi = ntohl(idx->oid_fanout[(int)short_oid->id[0]]);
393 0 0         lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(idx->oid_fanout[(int)short_oid->id[0] - 1]));
394              
395 0           pos = git_pack__lookup_sha1(idx->oid_lookup, GIT_OID_RAWSZ, lo, hi, short_oid->id);
396              
397 0 0         if (pos >= 0) {
398             /* An object matching exactly the oid was found */
399 0           found = 1;
400 0           current = idx->oid_lookup + (pos * GIT_OID_RAWSZ);
401             } else {
402             /* No object was found */
403             /* pos refers to the object with the "closest" oid to short_oid */
404 0           pos = -1 - pos;
405 0 0         if (pos < (int)idx->num_objects) {
406 0           current = idx->oid_lookup + (pos * GIT_OID_RAWSZ);
407              
408 0 0         if (!git_oid_raw_ncmp(short_oid->id, current, len))
409 0           found = 1;
410             }
411             }
412              
413 0 0         if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)idx->num_objects) {
    0          
    0          
414             /* Check for ambiguousity */
415 0           const unsigned char *next = current + GIT_OID_RAWSZ;
416              
417 0 0         if (!git_oid_raw_ncmp(short_oid->id, next, len))
418 0           found = 2;
419             }
420              
421 0 0         if (!found)
422 0           return git_odb__error_notfound("failed to find offset for multi-pack index entry", short_oid, len);
423 0 0         if (found > 1)
424 0           return git_odb__error_ambiguous("found multiple offsets for multi-pack index entry");
425              
426 0           object_offset = idx->object_offsets + pos * 8;
427 0           offset = ntohl(*((uint32_t *)(object_offset + 4)));
428 0 0         if (idx->object_large_offsets && offset & 0x80000000) {
    0          
429 0           uint32_t object_large_offsets_pos = (uint32_t) (offset ^ 0x80000000);
430 0           const unsigned char *object_large_offsets_index = idx->object_large_offsets;
431              
432             /* Make sure we're not being sent out of bounds */
433 0 0         if (object_large_offsets_pos >= idx->num_object_large_offsets)
434 0           return git_odb__error_notfound("invalid index into the object large offsets table", short_oid, len);
435              
436 0           object_large_offsets_index += 8 * object_large_offsets_pos;
437              
438 0           offset = (((uint64_t)ntohl(*((uint32_t *)(object_large_offsets_index + 0)))) << 32) |
439 0           ntohl(*((uint32_t *)(object_large_offsets_index + 4)));
440             }
441 0           pack_index = ntohl(*((uint32_t *)(object_offset + 0)));
442 0 0         if (pack_index >= git_vector_length(&idx->packfile_names))
443 0           return midx_error("invalid index into the packfile names table");
444 0           e->pack_index = pack_index;
445 0           e->offset = offset;
446 0           git_oid_fromraw(&e->sha1, current);
447 0           return 0;
448             }
449              
450 0           int git_midx_foreach_entry(
451             git_midx_file *idx,
452             git_odb_foreach_cb cb,
453             void *data)
454             {
455             git_oid oid;
456             size_t i;
457             int error;
458              
459 0 0         GIT_ASSERT_ARG(idx);
460              
461 0 0         for (i = 0; i < idx->num_objects; ++i) {
462 0 0         if ((error = git_oid_fromraw(&oid, &idx->oid_lookup[i * GIT_OID_RAWSZ])) < 0)
463 0           return error;
464              
465 0 0         if ((error = cb(&oid, data)) != 0)
466 0           return git_error_set_after_callback(error);
467             }
468              
469 0           return error;
470             }
471              
472 0           int git_midx_close(git_midx_file *idx)
473             {
474 0 0         GIT_ASSERT_ARG(idx);
475              
476 0 0         if (idx->index_map.data)
477 0           git_futils_mmap_free(&idx->index_map);
478              
479 0           git_vector_free(&idx->packfile_names);
480              
481 0           return 0;
482             }
483              
484 37           void git_midx_free(git_midx_file *idx)
485             {
486 37 50         if (!idx)
487 37           return;
488              
489 0           git_str_dispose(&idx->filename);
490 0           git_midx_close(idx);
491 0           git__free(idx);
492             }
493              
494 0           static int packfile__cmp(const void *a_, const void *b_)
495             {
496 0           const struct git_pack_file *a = a_;
497 0           const struct git_pack_file *b = b_;
498              
499 0           return strcmp(a->pack_name, b->pack_name);
500             }
501              
502 0           int git_midx_writer_new(
503             git_midx_writer **out,
504             const char *pack_dir)
505             {
506 0           git_midx_writer *w = git__calloc(1, sizeof(git_midx_writer));
507 0 0         GIT_ERROR_CHECK_ALLOC(w);
508              
509 0 0         if (git_str_sets(&w->pack_dir, pack_dir) < 0) {
510 0           git__free(w);
511 0           return -1;
512             }
513 0           git_fs_path_squash_slashes(&w->pack_dir);
514              
515 0 0         if (git_vector_init(&w->packs, 0, packfile__cmp) < 0) {
516 0           git_str_dispose(&w->pack_dir);
517 0           git__free(w);
518 0           return -1;
519             }
520              
521 0           *out = w;
522 0           return 0;
523             }
524              
525 0           void git_midx_writer_free(git_midx_writer *w)
526             {
527             struct git_pack_file *p;
528             size_t i;
529              
530 0 0         if (!w)
531 0           return;
532              
533 0 0         git_vector_foreach (&w->packs, i, p)
534 0           git_mwindow_put_pack(p);
535 0           git_vector_free(&w->packs);
536 0           git_str_dispose(&w->pack_dir);
537 0           git__free(w);
538             }
539              
540 0           int git_midx_writer_add(
541             git_midx_writer *w,
542             const char *idx_path)
543             {
544 0           git_str idx_path_buf = GIT_STR_INIT;
545             int error;
546             struct git_pack_file *p;
547              
548 0           error = git_fs_path_prettify(&idx_path_buf, idx_path, git_str_cstr(&w->pack_dir));
549 0 0         if (error < 0)
550 0           return error;
551              
552 0           error = git_mwindow_get_pack(&p, git_str_cstr(&idx_path_buf));
553 0           git_str_dispose(&idx_path_buf);
554 0 0         if (error < 0)
555 0           return error;
556              
557 0           error = git_vector_insert(&w->packs, p);
558 0 0         if (error < 0) {
559 0           git_mwindow_put_pack(p);
560 0           return error;
561             }
562              
563 0           return 0;
564             }
565              
566             typedef git_array_t(git_midx_entry) object_entry_array_t;
567              
568             struct object_entry_cb_state {
569             uint32_t pack_index;
570             object_entry_array_t *object_entries_array;
571             };
572              
573 0           static int object_entry__cb(const git_oid *oid, off64_t offset, void *data)
574             {
575 0           struct object_entry_cb_state *state = (struct object_entry_cb_state *)data;
576              
577 0 0         git_midx_entry *entry = git_array_alloc(*state->object_entries_array);
    0          
578 0 0         GIT_ERROR_CHECK_ALLOC(entry);
579              
580 0           git_oid_cpy(&entry->sha1, oid);
581 0           entry->offset = offset;
582 0           entry->pack_index = state->pack_index;
583              
584 0           return 0;
585             }
586              
587 0           static int object_entry__cmp(const void *a_, const void *b_)
588             {
589 0           const git_midx_entry *a = (const git_midx_entry *)a_;
590 0           const git_midx_entry *b = (const git_midx_entry *)b_;
591              
592 0           return git_oid_cmp(&a->sha1, &b->sha1);
593             }
594              
595 0           static int write_offset(off64_t offset, midx_write_cb write_cb, void *cb_data)
596             {
597             int error;
598             uint32_t word;
599              
600 0           word = htonl((uint32_t)((offset >> 32) & 0xffffffffu));
601 0           error = write_cb((const char *)&word, sizeof(word), cb_data);
602 0 0         if (error < 0)
603 0           return error;
604 0           word = htonl((uint32_t)((offset >> 0) & 0xffffffffu));
605 0           error = write_cb((const char *)&word, sizeof(word), cb_data);
606 0 0         if (error < 0)
607 0           return error;
608              
609 0           return 0;
610             }
611              
612 0           static int write_chunk_header(int chunk_id, off64_t offset, midx_write_cb write_cb, void *cb_data)
613             {
614 0           uint32_t word = htonl(chunk_id);
615 0           int error = write_cb((const char *)&word, sizeof(word), cb_data);
616 0 0         if (error < 0)
617 0           return error;
618 0           return write_offset(offset, write_cb, cb_data);
619              
620             return 0;
621             }
622              
623 0           static int midx_write_buf(const char *buf, size_t size, void *data)
624             {
625 0           git_str *b = (git_str *)data;
626 0           return git_str_put(b, buf, size);
627             }
628              
629             struct midx_write_hash_context {
630             midx_write_cb write_cb;
631             void *cb_data;
632             git_hash_ctx *ctx;
633             };
634              
635 0           static int midx_write_hash(const char *buf, size_t size, void *data)
636             {
637 0           struct midx_write_hash_context *ctx = (struct midx_write_hash_context *)data;
638             int error;
639              
640 0           error = git_hash_update(ctx->ctx, buf, size);
641 0 0         if (error < 0)
642 0           return error;
643              
644 0           return ctx->write_cb(buf, size, ctx->cb_data);
645             }
646              
647 0           static int midx_write(
648             git_midx_writer *w,
649             midx_write_cb write_cb,
650             void *cb_data)
651             {
652 0           int error = 0;
653             size_t i;
654             struct git_pack_file *p;
655 0           struct git_midx_header hdr = {0};
656             uint32_t oid_fanout_count;
657             uint32_t object_large_offsets_count;
658             uint32_t oid_fanout[256];
659             off64_t offset;
660 0           git_str packfile_names = GIT_STR_INIT,
661 0           oid_lookup = GIT_STR_INIT,
662 0           object_offsets = GIT_STR_INIT,
663 0           object_large_offsets = GIT_STR_INIT;
664             unsigned char checksum[GIT_HASH_SHA1_SIZE];
665             size_t checksum_size;
666             git_midx_entry *entry;
667 0           object_entry_array_t object_entries_array = GIT_ARRAY_INIT;
668 0           git_vector object_entries = GIT_VECTOR_INIT;
669             git_hash_ctx ctx;
670 0           struct midx_write_hash_context hash_cb_data = {0};
671              
672 0           hdr.signature = htonl(MIDX_SIGNATURE);
673 0           hdr.version = MIDX_VERSION;
674 0           hdr.object_id_version = MIDX_OBJECT_ID_VERSION;
675 0           hdr.base_midx_files = 0;
676              
677 0           hash_cb_data.write_cb = write_cb;
678 0           hash_cb_data.cb_data = cb_data;
679 0           hash_cb_data.ctx = &ctx;
680              
681 0           checksum_size = GIT_HASH_SHA1_SIZE;
682 0           error = git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1);
683 0 0         if (error < 0)
684 0           return error;
685 0           cb_data = &hash_cb_data;
686 0           write_cb = midx_write_hash;
687              
688 0           git_vector_sort(&w->packs);
689 0 0         git_vector_foreach (&w->packs, i, p) {
690 0           git_str relative_index = GIT_STR_INIT;
691 0           struct object_entry_cb_state state = {0};
692             size_t path_len;
693              
694 0           state.pack_index = (uint32_t)i;
695 0           state.object_entries_array = &object_entries_array;
696              
697 0           error = git_str_sets(&relative_index, p->pack_name);
698 0 0         if (error < 0)
699 0           goto cleanup;
700 0           error = git_fs_path_make_relative(&relative_index, git_str_cstr(&w->pack_dir));
701 0 0         if (error < 0) {
702 0           git_str_dispose(&relative_index);
703 0           goto cleanup;
704             }
705 0           path_len = git_str_len(&relative_index);
706 0 0         if (path_len <= strlen(".pack") || git__suffixcmp(git_str_cstr(&relative_index), ".pack") != 0) {
    0          
707 0           git_str_dispose(&relative_index);
708 0           git_error_set(GIT_ERROR_INVALID, "invalid packfile name: '%s'", p->pack_name);
709 0           error = -1;
710 0           goto cleanup;
711             }
712 0           path_len -= strlen(".pack");
713              
714 0           git_str_put(&packfile_names, git_str_cstr(&relative_index), path_len);
715 0           git_str_puts(&packfile_names, ".idx");
716 0           git_str_putc(&packfile_names, '\0');
717 0           git_str_dispose(&relative_index);
718              
719 0           error = git_pack_foreach_entry_offset(p, object_entry__cb, &state);
720 0 0         if (error < 0)
721 0           goto cleanup;
722             }
723              
724             /* Sort the object entries. */
725 0           error = git_vector_init(&object_entries, git_array_size(object_entries_array), object_entry__cmp);
726 0 0         if (error < 0)
727 0           goto cleanup;
728 0 0         git_array_foreach (object_entries_array, i, entry) {
    0          
729 0 0         if ((error = git_vector_set(NULL, &object_entries, i, entry)) < 0)
730 0           goto cleanup;
731             }
732 0           git_vector_set_sorted(&object_entries, 0);
733 0           git_vector_sort(&object_entries);
734 0           git_vector_uniq(&object_entries, NULL);
735              
736             /* Pad the packfile names so it is a multiple of four. */
737 0 0         while (git_str_len(&packfile_names) & 3)
738 0           git_str_putc(&packfile_names, '\0');
739              
740             /* Fill the OID Fanout table. */
741 0           oid_fanout_count = 0;
742 0 0         for (i = 0; i < 256; i++) {
743 0           while (oid_fanout_count < git_vector_length(&object_entries) &&
744 0           ((const git_midx_entry *)git_vector_get(&object_entries, oid_fanout_count))->sha1.id[0] <= i)
745 0           ++oid_fanout_count;
746 0           oid_fanout[i] = htonl(oid_fanout_count);
747             }
748              
749             /* Fill the OID Lookup table. */
750 0 0         git_vector_foreach (&object_entries, i, entry) {
751 0           error = git_str_put(&oid_lookup, (char *)&entry->sha1.id, GIT_OID_RAWSZ);
752 0 0         if (error < 0)
753 0           goto cleanup;
754             }
755              
756             /* Fill the Object Offsets and Object Large Offsets tables. */
757 0           object_large_offsets_count = 0;
758 0 0         git_vector_foreach (&object_entries, i, entry) {
759             uint32_t word;
760              
761 0           word = htonl((uint32_t)entry->pack_index);
762 0           error = git_str_put(&object_offsets, (const char *)&word, sizeof(word));
763 0 0         if (error < 0)
764 0           goto cleanup;
765 0 0         if (entry->offset >= 0x80000000l) {
766 0           word = htonl(0x80000000u | object_large_offsets_count++);
767 0 0         if ((error = write_offset(entry->offset, midx_write_buf, &object_large_offsets)) < 0)
768 0           goto cleanup;
769             } else {
770 0           word = htonl((uint32_t)entry->offset & 0x7fffffffu);
771             }
772              
773 0           error = git_str_put(&object_offsets, (const char *)&word, sizeof(word));
774 0 0         if (error < 0)
775 0           goto cleanup;
776             }
777              
778             /* Write the header. */
779 0           hdr.packfiles = htonl((uint32_t)git_vector_length(&w->packs));
780 0           hdr.chunks = 4;
781 0 0         if (git_str_len(&object_large_offsets) > 0)
782 0           hdr.chunks++;
783 0           error = write_cb((const char *)&hdr, sizeof(hdr), cb_data);
784 0 0         if (error < 0)
785 0           goto cleanup;
786              
787             /* Write the chunk headers. */
788 0           offset = sizeof(hdr) + (hdr.chunks + 1) * 12;
789 0           error = write_chunk_header(MIDX_PACKFILE_NAMES_ID, offset, write_cb, cb_data);
790 0 0         if (error < 0)
791 0           goto cleanup;
792 0           offset += git_str_len(&packfile_names);
793 0           error = write_chunk_header(MIDX_OID_FANOUT_ID, offset, write_cb, cb_data);
794 0 0         if (error < 0)
795 0           goto cleanup;
796 0           offset += sizeof(oid_fanout);
797 0           error = write_chunk_header(MIDX_OID_LOOKUP_ID, offset, write_cb, cb_data);
798 0 0         if (error < 0)
799 0           goto cleanup;
800 0           offset += git_str_len(&oid_lookup);
801 0           error = write_chunk_header(MIDX_OBJECT_OFFSETS_ID, offset, write_cb, cb_data);
802 0 0         if (error < 0)
803 0           goto cleanup;
804 0           offset += git_str_len(&object_offsets);
805 0 0         if (git_str_len(&object_large_offsets) > 0) {
806 0           error = write_chunk_header(MIDX_OBJECT_LARGE_OFFSETS_ID, offset, write_cb, cb_data);
807 0 0         if (error < 0)
808 0           goto cleanup;
809 0           offset += git_str_len(&object_large_offsets);
810             }
811 0           error = write_chunk_header(0, offset, write_cb, cb_data);
812 0 0         if (error < 0)
813 0           goto cleanup;
814              
815             /* Write all the chunks. */
816 0           error = write_cb(git_str_cstr(&packfile_names), git_str_len(&packfile_names), cb_data);
817 0 0         if (error < 0)
818 0           goto cleanup;
819 0           error = write_cb((const char *)oid_fanout, sizeof(oid_fanout), cb_data);
820 0 0         if (error < 0)
821 0           goto cleanup;
822 0           error = write_cb(git_str_cstr(&oid_lookup), git_str_len(&oid_lookup), cb_data);
823 0 0         if (error < 0)
824 0           goto cleanup;
825 0           error = write_cb(git_str_cstr(&object_offsets), git_str_len(&object_offsets), cb_data);
826 0 0         if (error < 0)
827 0           goto cleanup;
828 0           error = write_cb(git_str_cstr(&object_large_offsets), git_str_len(&object_large_offsets), cb_data);
829 0 0         if (error < 0)
830 0           goto cleanup;
831              
832             /* Finalize the checksum and write the trailer. */
833 0           error = git_hash_final(checksum, &ctx);
834 0 0         if (error < 0)
835 0           goto cleanup;
836 0           error = write_cb((char *)checksum, checksum_size, cb_data);
837 0 0         if (error < 0)
838 0           goto cleanup;
839              
840             cleanup:
841 0           git_array_clear(object_entries_array);
842 0           git_vector_free(&object_entries);
843 0           git_str_dispose(&packfile_names);
844 0           git_str_dispose(&oid_lookup);
845 0           git_str_dispose(&object_offsets);
846 0           git_str_dispose(&object_large_offsets);
847 0           git_hash_ctx_cleanup(&ctx);
848 0           return error;
849             }
850              
851 0           static int midx_write_filebuf(const char *buf, size_t size, void *data)
852             {
853 0           git_filebuf *f = (git_filebuf *)data;
854 0           return git_filebuf_write(f, buf, size);
855             }
856              
857 0           int git_midx_writer_commit(
858             git_midx_writer *w)
859             {
860             int error;
861 0           int filebuf_flags = GIT_FILEBUF_DO_NOT_BUFFER;
862 0           git_str midx_path = GIT_STR_INIT;
863 0           git_filebuf output = GIT_FILEBUF_INIT;
864              
865 0           error = git_str_joinpath(&midx_path, git_str_cstr(&w->pack_dir), "multi-pack-index");
866 0 0         if (error < 0)
867 0           return error;
868              
869 0 0         if (git_repository__fsync_gitdir)
870 0           filebuf_flags |= GIT_FILEBUF_FSYNC;
871 0           error = git_filebuf_open(&output, git_str_cstr(&midx_path), filebuf_flags, 0644);
872 0           git_str_dispose(&midx_path);
873 0 0         if (error < 0)
874 0           return error;
875              
876 0           error = midx_write(w, midx_write_filebuf, &output);
877 0 0         if (error < 0) {
878 0           git_filebuf_cleanup(&output);
879 0           return error;
880             }
881              
882 0           return git_filebuf_commit(&output);
883             }
884              
885 0           int git_midx_writer_dump(
886             git_buf *midx,
887             git_midx_writer *w)
888             {
889 0           git_str str = GIT_STR_INIT;
890             int error;
891              
892 0 0         if ((error = git_buf_tostr(&str, midx)) < 0 ||
    0          
893             (error = midx_write(w, midx_write_buf, &str)) == 0)
894 0           error = git_buf_fromstr(midx, &str);
895              
896 0           git_str_dispose(&str);
897 0           return error;
898             }