]>
Commit | Line | Data |
---|---|---|
7d0cdf82 | 1 | /* |
5e0de328 | 2 | * Copyright (C) 2009-2012 the libgit2 contributors |
7d0cdf82 | 3 | * |
bb742ede VM |
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. | |
7d0cdf82 CMN |
6 | */ |
7 | ||
a15c550d | 8 | #include "common.h" |
7d0cdf82 CMN |
9 | #include "odb.h" |
10 | #include "pack.h" | |
11 | #include "delta-apply.h" | |
a070f152 | 12 | #include "sha1_lookup.h" |
a15c550d VM |
13 | #include "mwindow.h" |
14 | #include "fileops.h" | |
7d0cdf82 CMN |
15 | |
16 | #include "git2/oid.h" | |
0c3bae62 | 17 | #include <zlib.h> |
7d0cdf82 | 18 | |
a070f152 CMN |
19 | static int packfile_open(struct git_pack_file *p); |
20 | static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); | |
21 | int packfile_unpack_compressed( | |
22 | git_rawobj *obj, | |
23 | struct git_pack_file *p, | |
24 | git_mwindow **w_curs, | |
b5b474dd | 25 | off_t *curpos, |
a070f152 CMN |
26 | size_t size, |
27 | git_otype type); | |
28 | ||
29 | /* Can find the offset of an object given | |
30 | * a prefix of an identifier. | |
31 | * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid | |
32 | * is ambiguous within the pack. | |
33 | * This method assumes that len is between | |
34 | * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. | |
35 | */ | |
36 | static int pack_entry_find_offset( | |
37 | off_t *offset_out, | |
38 | git_oid *found_oid, | |
39 | struct git_pack_file *p, | |
40 | const git_oid *short_oid, | |
41 | unsigned int len); | |
42 | ||
43 | /*********************************************************** | |
44 | * | |
45 | * PACK INDEX METHODS | |
46 | * | |
47 | ***********************************************************/ | |
48 | ||
49 | static void pack_index_free(struct git_pack_file *p) | |
50 | { | |
51 | if (p->index_map.data) { | |
52 | git_futils_mmap_free(&p->index_map); | |
53 | p->index_map.data = NULL; | |
54 | } | |
55 | } | |
56 | ||
87d9869f | 57 | static int pack_index_check(const char *path, struct git_pack_file *p) |
a070f152 CMN |
58 | { |
59 | struct git_pack_idx_header *hdr; | |
60 | uint32_t version, nr, i, *index; | |
61 | ||
62 | void *idx_map; | |
63 | size_t idx_size; | |
64 | ||
65 | struct stat st; | |
66 | ||
67 | /* TODO: properly open the file without access time */ | |
68 | git_file fd = p_open(path, O_RDONLY /*| O_NOATIME */); | |
69 | ||
70 | int error; | |
71 | ||
72 | if (fd < 0) | |
73 | return git__throw(GIT_EOSERR, "Failed to check index. File missing or corrupted"); | |
74 | ||
75 | if (p_fstat(fd, &st) < GIT_SUCCESS) { | |
76 | p_close(fd); | |
77 | return git__throw(GIT_EOSERR, "Failed to check index. File appears to be corrupted"); | |
78 | } | |
79 | ||
80 | if (!git__is_sizet(st.st_size)) | |
81 | return GIT_ENOMEM; | |
82 | ||
83 | idx_size = (size_t)st.st_size; | |
84 | ||
85 | if (idx_size < 4 * 256 + 20 + 20) { | |
86 | p_close(fd); | |
87 | return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); | |
88 | } | |
89 | ||
90 | error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); | |
91 | p_close(fd); | |
92 | ||
93 | if (error < GIT_SUCCESS) | |
94 | return git__rethrow(error, "Failed to check index"); | |
95 | ||
96 | hdr = idx_map = p->index_map.data; | |
97 | ||
98 | if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { | |
99 | version = ntohl(hdr->idx_version); | |
100 | ||
101 | if (version < 2 || version > 2) { | |
102 | git_futils_mmap_free(&p->index_map); | |
103 | return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Unsupported index version"); | |
104 | } | |
105 | ||
106 | } else | |
107 | version = 1; | |
108 | ||
109 | nr = 0; | |
110 | index = idx_map; | |
111 | ||
112 | if (version > 1) | |
87d9869f | 113 | index += 2; /* skip index header */ |
a070f152 CMN |
114 | |
115 | for (i = 0; i < 256; i++) { | |
116 | uint32_t n = ntohl(index[i]); | |
117 | if (n < nr) { | |
118 | git_futils_mmap_free(&p->index_map); | |
119 | return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Index is non-monotonic"); | |
120 | } | |
121 | nr = n; | |
122 | } | |
123 | ||
124 | if (version == 1) { | |
125 | /* | |
126 | * Total size: | |
87d9869f VM |
127 | * - 256 index entries 4 bytes each |
128 | * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) | |
129 | * - 20-byte SHA1 of the packfile | |
130 | * - 20-byte SHA1 file checksum | |
a070f152 CMN |
131 | */ |
132 | if (idx_size != 4*256 + nr * 24 + 20 + 20) { | |
133 | git_futils_mmap_free(&p->index_map); | |
134 | return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); | |
135 | } | |
136 | } else if (version == 2) { | |
137 | /* | |
138 | * Minimum size: | |
87d9869f VM |
139 | * - 8 bytes of header |
140 | * - 256 index entries 4 bytes each | |
141 | * - 20-byte sha1 entry * nr | |
142 | * - 4-byte crc entry * nr | |
143 | * - 4-byte offset entry * nr | |
144 | * - 20-byte SHA1 of the packfile | |
145 | * - 20-byte SHA1 file checksum | |
a070f152 CMN |
146 | * And after the 4-byte offset table might be a |
147 | * variable sized table containing 8-byte entries | |
148 | * for offsets larger than 2^31. | |
149 | */ | |
150 | unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; | |
151 | unsigned long max_size = min_size; | |
152 | ||
153 | if (nr) | |
154 | max_size += (nr - 1)*8; | |
155 | ||
156 | if (idx_size < min_size || idx_size > max_size) { | |
157 | git_futils_mmap_free(&p->index_map); | |
158 | return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Wrong index size"); | |
159 | } | |
a070f152 CMN |
160 | } |
161 | ||
162 | p->index_version = version; | |
163 | p->num_objects = nr; | |
164 | return GIT_SUCCESS; | |
165 | } | |
166 | ||
167 | static int pack_index_open(struct git_pack_file *p) | |
168 | { | |
169 | char *idx_name; | |
170 | int error; | |
171 | ||
172 | if (p->index_map.data) | |
173 | return GIT_SUCCESS; | |
174 | ||
175 | idx_name = git__strdup(p->pack_name); | |
932669b8 | 176 | strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx"); |
a070f152 CMN |
177 | |
178 | error = pack_index_check(idx_name, p); | |
3286c408 | 179 | git__free(idx_name); |
a070f152 CMN |
180 | |
181 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to open index"); | |
182 | } | |
183 | ||
184 | static unsigned char *pack_window_open( | |
185 | struct git_pack_file *p, | |
7d0cdf82 CMN |
186 | git_mwindow **w_cursor, |
187 | off_t offset, | |
188 | unsigned int *left) | |
189 | { | |
190 | if (p->mwf.fd == -1 && packfile_open(p) < GIT_SUCCESS) | |
191 | return NULL; | |
192 | ||
193 | /* Since packfiles end in a hash of their content and it's | |
194 | * pointless to ask for an offset into the middle of that | |
195 | * hash, and the pack_window_contains function above wouldn't match | |
196 | * don't allow an offset too close to the end of the file. | |
197 | */ | |
198 | if (offset > (p->mwf.size - 20)) | |
199 | return NULL; | |
200 | ||
201 | return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); | |
202 | } | |
203 | ||
204 | static unsigned long packfile_unpack_header1( | |
205 | size_t *sizep, | |
206 | git_otype *type, | |
207 | const unsigned char *buf, | |
208 | unsigned long len) | |
209 | { | |
210 | unsigned shift; | |
211 | unsigned long size, c; | |
212 | unsigned long used = 0; | |
213 | ||
214 | c = buf[used++]; | |
215 | *type = (c >> 4) & 7; | |
216 | size = c & 15; | |
217 | shift = 4; | |
218 | while (c & 0x80) { | |
219 | if (len <= used || bitsizeof(long) <= shift) | |
220 | return 0; | |
221 | ||
222 | c = buf[used++]; | |
223 | size += (c & 0x7f) << shift; | |
224 | shift += 7; | |
225 | } | |
226 | ||
227 | *sizep = (size_t)size; | |
228 | return used; | |
229 | } | |
230 | ||
231 | int git_packfile_unpack_header( | |
232 | size_t *size_p, | |
233 | git_otype *type_p, | |
234 | git_mwindow_file *mwf, | |
235 | git_mwindow **w_curs, | |
236 | off_t *curpos) | |
237 | { | |
238 | unsigned char *base; | |
239 | unsigned int left; | |
240 | unsigned long used; | |
241 | ||
242 | /* pack_window_open() assures us we have [base, base + 20) available | |
87d9869f VM |
243 | * as a range that we can look at at. (Its actually the hash |
244 | * size that is assured.) With our object header encoding | |
7d0cdf82 CMN |
245 | * the maximum deflated object size is 2^137, which is just |
246 | * insane, so we know won't exceed what we have been given. | |
247 | */ | |
248 | // base = pack_window_open(p, w_curs, *curpos, &left); | |
249 | base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left); | |
250 | if (base == NULL) | |
251 | return GIT_ENOMEM; | |
252 | ||
253 | used = packfile_unpack_header1(size_p, type_p, base, left); | |
254 | ||
255 | if (used == 0) | |
256 | return git__throw(GIT_EOBJCORRUPTED, "Header length is zero"); | |
257 | ||
258 | *curpos += used; | |
259 | return GIT_SUCCESS; | |
260 | } | |
261 | ||
a070f152 | 262 | static int packfile_unpack_delta( |
7d0cdf82 | 263 | git_rawobj *obj, |
a070f152 | 264 | struct git_pack_file *p, |
7d0cdf82 | 265 | git_mwindow **w_curs, |
b5b474dd | 266 | off_t *curpos, |
7d0cdf82 CMN |
267 | size_t delta_size, |
268 | git_otype delta_type, | |
269 | off_t obj_offset) | |
270 | { | |
271 | off_t base_offset; | |
272 | git_rawobj base, delta; | |
273 | int error; | |
274 | ||
b5b474dd | 275 | base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); |
7d0cdf82 CMN |
276 | if (base_offset == 0) |
277 | return git__throw(GIT_EOBJCORRUPTED, "Delta offset is zero"); | |
97f40a0d CMN |
278 | if (base_offset < 0) |
279 | return git__rethrow(base_offset, "Failed to get delta base"); | |
7d0cdf82 CMN |
280 | |
281 | git_mwindow_close(w_curs); | |
b5b474dd | 282 | error = git_packfile_unpack(&base, p, &base_offset); |
7d0cdf82 CMN |
283 | |
284 | /* | |
285 | * TODO: git.git tries to load the base from other packfiles | |
286 | * or loose objects. | |
287 | * | |
288 | * We'll need to do this in order to support thin packs. | |
289 | */ | |
290 | if (error < GIT_SUCCESS) | |
291 | return git__rethrow(error, "Corrupted delta"); | |
292 | ||
293 | error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); | |
294 | if (error < GIT_SUCCESS) { | |
3286c408 | 295 | git__free(base.data); |
7d0cdf82 CMN |
296 | return git__rethrow(error, "Corrupted delta"); |
297 | } | |
298 | ||
299 | obj->type = base.type; | |
300 | error = git__delta_apply(obj, | |
301 | base.data, base.len, | |
302 | delta.data, delta.len); | |
303 | ||
3286c408 VM |
304 | git__free(base.data); |
305 | git__free(delta.data); | |
7d0cdf82 CMN |
306 | |
307 | /* TODO: we might want to cache this shit. eventually */ | |
308 | //add_delta_base_cache(p, base_offset, base, base_size, *type); | |
309 | return error; /* error set by git__delta_apply */ | |
310 | } | |
311 | ||
a070f152 | 312 | int git_packfile_unpack( |
7d0cdf82 | 313 | git_rawobj *obj, |
a070f152 | 314 | struct git_pack_file *p, |
b5b474dd | 315 | off_t *obj_offset) |
7d0cdf82 CMN |
316 | { |
317 | git_mwindow *w_curs = NULL; | |
b5b474dd | 318 | off_t curpos = *obj_offset; |
7d0cdf82 CMN |
319 | int error; |
320 | ||
321 | size_t size = 0; | |
322 | git_otype type; | |
323 | ||
324 | /* | |
325 | * TODO: optionally check the CRC on the packfile | |
326 | */ | |
327 | ||
328 | obj->data = NULL; | |
329 | obj->len = 0; | |
330 | obj->type = GIT_OBJ_BAD; | |
331 | ||
332 | error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); | |
333 | if (error < GIT_SUCCESS) | |
334 | return git__rethrow(error, "Failed to unpack packfile"); | |
335 | ||
336 | switch (type) { | |
337 | case GIT_OBJ_OFS_DELTA: | |
338 | case GIT_OBJ_REF_DELTA: | |
339 | error = packfile_unpack_delta( | |
b5b474dd CMN |
340 | obj, p, &w_curs, &curpos, |
341 | size, type, *obj_offset); | |
7d0cdf82 CMN |
342 | break; |
343 | ||
344 | case GIT_OBJ_COMMIT: | |
345 | case GIT_OBJ_TREE: | |
346 | case GIT_OBJ_BLOB: | |
347 | case GIT_OBJ_TAG: | |
348 | error = packfile_unpack_compressed( | |
b5b474dd | 349 | obj, p, &w_curs, &curpos, |
7d0cdf82 CMN |
350 | size, type); |
351 | break; | |
352 | ||
353 | default: | |
354 | error = GIT_EOBJCORRUPTED; | |
355 | break; | |
356 | } | |
357 | ||
358 | git_mwindow_close(&w_curs); | |
b5b474dd CMN |
359 | |
360 | if (error < GIT_SUCCESS) | |
361 | return git__rethrow(error, "Failed to unpack object"); | |
362 | ||
363 | *obj_offset = curpos; | |
364 | return GIT_SUCCESS; | |
7d0cdf82 CMN |
365 | } |
366 | ||
367 | int packfile_unpack_compressed( | |
368 | git_rawobj *obj, | |
a070f152 | 369 | struct git_pack_file *p, |
7d0cdf82 | 370 | git_mwindow **w_curs, |
b5b474dd | 371 | off_t *curpos, |
7d0cdf82 CMN |
372 | size_t size, |
373 | git_otype type) | |
374 | { | |
375 | int st; | |
376 | z_stream stream; | |
377 | unsigned char *buffer, *in; | |
378 | ||
379 | buffer = git__malloc(size + 1); | |
380 | memset(buffer, 0x0, size + 1); | |
381 | ||
382 | memset(&stream, 0, sizeof(stream)); | |
383 | stream.next_out = buffer; | |
1c3fac4d | 384 | stream.avail_out = (uInt)size + 1; |
7d0cdf82 CMN |
385 | |
386 | st = inflateInit(&stream); | |
387 | if (st != Z_OK) { | |
3286c408 | 388 | git__free(buffer); |
7d0cdf82 CMN |
389 | return git__throw(GIT_EZLIB, "Error in zlib"); |
390 | } | |
391 | ||
392 | do { | |
b5b474dd | 393 | in = pack_window_open(p, w_curs, *curpos, &stream.avail_in); |
7d0cdf82 CMN |
394 | stream.next_in = in; |
395 | st = inflate(&stream, Z_FINISH); | |
396 | ||
397 | if (!stream.avail_out) | |
398 | break; /* the payload is larger than it should be */ | |
399 | ||
b5b474dd | 400 | *curpos += stream.next_in - in; |
7d0cdf82 CMN |
401 | } while (st == Z_OK || st == Z_BUF_ERROR); |
402 | ||
403 | inflateEnd(&stream); | |
404 | ||
405 | if ((st != Z_STREAM_END) || stream.total_out != size) { | |
3286c408 | 406 | git__free(buffer); |
7d0cdf82 CMN |
407 | return git__throw(GIT_EZLIB, "Error in zlib"); |
408 | } | |
409 | ||
410 | obj->type = type; | |
411 | obj->len = size; | |
412 | obj->data = buffer; | |
413 | return GIT_SUCCESS; | |
414 | } | |
415 | ||
b5b474dd CMN |
416 | /* |
417 | * curpos is where the data starts, delta_obj_offset is the where the | |
418 | * header starts | |
419 | */ | |
7d0cdf82 | 420 | off_t get_delta_base( |
a070f152 | 421 | struct git_pack_file *p, |
7d0cdf82 CMN |
422 | git_mwindow **w_curs, |
423 | off_t *curpos, | |
424 | git_otype type, | |
425 | off_t delta_obj_offset) | |
426 | { | |
427 | unsigned char *base_info = pack_window_open(p, w_curs, *curpos, NULL); | |
428 | off_t base_offset; | |
429 | git_oid unused; | |
430 | ||
431 | /* pack_window_open() assured us we have [base_info, base_info + 20) | |
432 | * as a range that we can look at without walking off the | |
87d9869f VM |
433 | * end of the mapped window. Its actually the hash size |
434 | * that is assured. An OFS_DELTA longer than the hash size | |
7d0cdf82 CMN |
435 | * is stupid, as then a REF_DELTA would be smaller to store. |
436 | */ | |
437 | if (type == GIT_OBJ_OFS_DELTA) { | |
438 | unsigned used = 0; | |
439 | unsigned char c = base_info[used++]; | |
440 | base_offset = c & 127; | |
441 | while (c & 128) { | |
442 | base_offset += 1; | |
443 | if (!base_offset || MSB(base_offset, 7)) | |
87d9869f | 444 | return 0; /* overflow */ |
7d0cdf82 CMN |
445 | c = base_info[used++]; |
446 | base_offset = (base_offset << 7) + (c & 127); | |
447 | } | |
448 | base_offset = delta_obj_offset - base_offset; | |
449 | if (base_offset <= 0 || base_offset >= delta_obj_offset) | |
87d9869f | 450 | return 0; /* out of bound */ |
7d0cdf82 CMN |
451 | *curpos += used; |
452 | } else if (type == GIT_OBJ_REF_DELTA) { | |
c1af5a39 CMN |
453 | /* If we have the cooperative cache, search in it first */ |
454 | if (p->has_cache) { | |
455 | int pos; | |
456 | struct git_pack_entry key; | |
457 | ||
458 | git_oid_fromraw(&key.sha1, base_info); | |
459 | pos = git_vector_bsearch(&p->cache, &key); | |
460 | if (pos >= 0) { | |
461 | *curpos += 20; | |
462 | return ((struct git_pack_entry *)git_vector_get(&p->cache, pos))->offset; | |
463 | } | |
464 | } | |
7d0cdf82 CMN |
465 | /* The base entry _must_ be in the same pack */ |
466 | if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < GIT_SUCCESS) | |
061047cc | 467 | return git__rethrow(GIT_EPACKCORRUPTED, "Base entry delta is not in the same pack"); |
7d0cdf82 CMN |
468 | *curpos += 20; |
469 | } else | |
470 | return 0; | |
471 | ||
472 | return base_offset; | |
473 | } | |
a070f152 CMN |
474 | |
475 | /*********************************************************** | |
476 | * | |
477 | * PACKFILE METHODS | |
478 | * | |
479 | ***********************************************************/ | |
480 | ||
481 | static struct git_pack_file *packfile_alloc(int extra) | |
482 | { | |
483 | struct git_pack_file *p = git__malloc(sizeof(*p) + extra); | |
484 | memset(p, 0, sizeof(*p)); | |
485 | p->mwf.fd = -1; | |
486 | return p; | |
487 | } | |
488 | ||
489 | ||
490 | void packfile_free(struct git_pack_file *p) | |
491 | { | |
492 | assert(p); | |
493 | ||
494 | /* clear_delta_base_cache(); */ | |
495 | git_mwindow_free_all(&p->mwf); | |
496 | ||
497 | if (p->mwf.fd != -1) | |
498 | p_close(p->mwf.fd); | |
499 | ||
500 | pack_index_free(p); | |
501 | ||
3286c408 VM |
502 | git__free(p->bad_object_sha1); |
503 | git__free(p); | |
a070f152 CMN |
504 | } |
505 | ||
506 | static int packfile_open(struct git_pack_file *p) | |
507 | { | |
508 | struct stat st; | |
509 | struct git_pack_header hdr; | |
510 | git_oid sha1; | |
511 | unsigned char *idx_sha1; | |
512 | ||
513 | if (!p->index_map.data && pack_index_open(p) < GIT_SUCCESS) | |
514 | return git__throw(GIT_ENOTFOUND, "Failed to open packfile. File not found"); | |
515 | ||
516 | /* TODO: open with noatime */ | |
517 | p->mwf.fd = p_open(p->pack_name, O_RDONLY); | |
518 | if (p->mwf.fd < 0 || p_fstat(p->mwf.fd, &st) < GIT_SUCCESS) | |
519 | return git__throw(GIT_EOSERR, "Failed to open packfile. File appears to be corrupted"); | |
520 | ||
521 | if (git_mwindow_file_register(&p->mwf) < GIT_SUCCESS) { | |
522 | p_close(p->mwf.fd); | |
523 | return git__throw(GIT_ERROR, "Failed to register packfile windows"); | |
524 | } | |
525 | ||
526 | /* If we created the struct before we had the pack we lack size. */ | |
527 | if (!p->mwf.size) { | |
528 | if (!S_ISREG(st.st_mode)) | |
529 | goto cleanup; | |
530 | p->mwf.size = (off_t)st.st_size; | |
531 | } else if (p->mwf.size != st.st_size) | |
532 | goto cleanup; | |
533 | ||
534 | #if 0 | |
535 | /* We leave these file descriptors open with sliding mmap; | |
536 | * there is no point keeping them open across exec(), though. | |
537 | */ | |
538 | fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); | |
539 | if (fd_flag < 0) | |
540 | return error("cannot determine file descriptor flags"); | |
541 | ||
542 | fd_flag |= FD_CLOEXEC; | |
543 | if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) | |
544 | return GIT_EOSERR; | |
545 | #endif | |
546 | ||
547 | /* Verify we recognize this pack file format. */ | |
548 | if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < GIT_SUCCESS) | |
549 | goto cleanup; | |
550 | ||
551 | if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) | |
552 | goto cleanup; | |
553 | ||
554 | if (!pack_version_ok(hdr.hdr_version)) | |
555 | goto cleanup; | |
556 | ||
557 | /* Verify the pack matches its index. */ | |
558 | if (p->num_objects != ntohl(hdr.hdr_entries)) | |
559 | goto cleanup; | |
560 | ||
561 | if (p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1) | |
562 | goto cleanup; | |
563 | ||
564 | if (p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < GIT_SUCCESS) | |
565 | goto cleanup; | |
566 | ||
567 | idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; | |
568 | ||
569 | if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) != 0) | |
570 | goto cleanup; | |
571 | ||
572 | return GIT_SUCCESS; | |
573 | ||
574 | cleanup: | |
575 | p_close(p->mwf.fd); | |
576 | p->mwf.fd = -1; | |
577 | return git__throw(GIT_EPACKCORRUPTED, "Failed to open packfile. Pack is corrupted"); | |
578 | } | |
579 | ||
580 | int git_packfile_check(struct git_pack_file **pack_out, const char *path) | |
581 | { | |
582 | struct stat st; | |
583 | struct git_pack_file *p; | |
584 | size_t path_len; | |
585 | ||
586 | *pack_out = NULL; | |
587 | path_len = strlen(path); | |
588 | p = packfile_alloc(path_len + 2); | |
589 | ||
590 | /* | |
591 | * Make sure a corresponding .pack file exists and that | |
592 | * the index looks sane. | |
593 | */ | |
932669b8 | 594 | path_len -= strlen(".idx"); |
a070f152 | 595 | if (path_len < 1) { |
3286c408 | 596 | git__free(p); |
a070f152 CMN |
597 | return git__throw(GIT_ENOTFOUND, "Failed to check packfile. Wrong path name"); |
598 | } | |
599 | ||
600 | memcpy(p->pack_name, path, path_len); | |
601 | ||
602 | strcpy(p->pack_name + path_len, ".keep"); | |
1a481123 | 603 | if (git_path_exists(p->pack_name) == true) |
a070f152 CMN |
604 | p->pack_keep = 1; |
605 | ||
606 | strcpy(p->pack_name + path_len, ".pack"); | |
607 | if (p_stat(p->pack_name, &st) < GIT_SUCCESS || !S_ISREG(st.st_mode)) { | |
3286c408 | 608 | git__free(p); |
a070f152 CMN |
609 | return git__throw(GIT_ENOTFOUND, "Failed to check packfile. File not found"); |
610 | } | |
611 | ||
612 | /* ok, it looks sane as far as we can check without | |
613 | * actually mapping the pack file. | |
614 | */ | |
1af56d7d | 615 | p->mwf.size = st.st_size; |
a070f152 CMN |
616 | p->pack_local = 1; |
617 | p->mtime = (git_time_t)st.st_mtime; | |
618 | ||
619 | /* see if we can parse the sha1 oid in the packfile name */ | |
620 | if (path_len < 40 || | |
621 | git_oid_fromstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < GIT_SUCCESS) | |
622 | memset(&p->sha1, 0x0, GIT_OID_RAWSZ); | |
623 | ||
624 | *pack_out = p; | |
625 | return GIT_SUCCESS; | |
626 | } | |
627 | ||
628 | /*********************************************************** | |
629 | * | |
630 | * PACKFILE ENTRY SEARCH INTERNALS | |
631 | * | |
632 | ***********************************************************/ | |
633 | ||
634 | static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) | |
635 | { | |
636 | const unsigned char *index = p->index_map.data; | |
637 | index += 4 * 256; | |
638 | if (p->index_version == 1) { | |
639 | return ntohl(*((uint32_t *)(index + 24 * n))); | |
640 | } else { | |
641 | uint32_t off; | |
642 | index += 8 + p->num_objects * (20 + 4); | |
643 | off = ntohl(*((uint32_t *)(index + 4 * n))); | |
644 | if (!(off & 0x80000000)) | |
645 | return off; | |
646 | index += p->num_objects * 4 + (off & 0x7fffffff) * 8; | |
647 | return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | | |
87d9869f | 648 | ntohl(*((uint32_t *)(index + 4))); |
a070f152 CMN |
649 | } |
650 | } | |
651 | ||
652 | static int pack_entry_find_offset( | |
653 | off_t *offset_out, | |
654 | git_oid *found_oid, | |
655 | struct git_pack_file *p, | |
656 | const git_oid *short_oid, | |
657 | unsigned int len) | |
658 | { | |
659 | const uint32_t *level1_ofs = p->index_map.data; | |
660 | const unsigned char *index = p->index_map.data; | |
661 | unsigned hi, lo, stride; | |
662 | int pos, found = 0; | |
663 | const unsigned char *current = 0; | |
664 | ||
665 | *offset_out = 0; | |
666 | ||
667 | if (index == NULL) { | |
668 | int error; | |
669 | ||
670 | if ((error = pack_index_open(p)) < GIT_SUCCESS) | |
671 | return git__rethrow(error, "Failed to find offset for pack entry"); | |
672 | ||
673 | assert(p->index_map.data); | |
674 | ||
675 | index = p->index_map.data; | |
676 | level1_ofs = p->index_map.data; | |
677 | } | |
678 | ||
679 | if (p->index_version > 1) { | |
680 | level1_ofs += 2; | |
681 | index += 8; | |
682 | } | |
683 | ||
684 | index += 4 * 256; | |
685 | hi = ntohl(level1_ofs[(int)short_oid->id[0]]); | |
686 | lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); | |
687 | ||
688 | if (p->index_version > 1) { | |
689 | stride = 20; | |
690 | } else { | |
691 | stride = 24; | |
692 | index += 4; | |
693 | } | |
694 | ||
695 | #ifdef INDEX_DEBUG_LOOKUP | |
696 | printf("%02x%02x%02x... lo %u hi %u nr %d\n", | |
697 | short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); | |
698 | #endif | |
699 | ||
700 | /* Use git.git lookup code */ | |
87d9869f | 701 | pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); |
a070f152 CMN |
702 | |
703 | if (pos >= 0) { | |
704 | /* An object matching exactly the oid was found */ | |
705 | found = 1; | |
706 | current = index + pos * stride; | |
707 | } else { | |
708 | /* No object was found */ | |
709 | /* pos refers to the object with the "closest" oid to short_oid */ | |
710 | pos = - 1 - pos; | |
711 | if (pos < (int)p->num_objects) { | |
712 | current = index + pos * stride; | |
713 | ||
714 | if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) { | |
715 | found = 1; | |
716 | } | |
717 | } | |
718 | } | |
719 | ||
b2a2702d | 720 | if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { |
a070f152 CMN |
721 | /* Check for ambiguousity */ |
722 | const unsigned char *next = current + stride; | |
723 | ||
724 | if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) { | |
725 | found = 2; | |
726 | } | |
727 | } | |
728 | ||
729 | if (!found) { | |
730 | return git__throw(GIT_ENOTFOUND, "Failed to find offset for pack entry. Entry not found"); | |
731 | } else if (found > 1) { | |
732 | return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find offset for pack entry. Ambiguous sha1 prefix within pack"); | |
733 | } else { | |
734 | *offset_out = nth_packed_object_offset(p, pos); | |
735 | git_oid_fromraw(found_oid, current); | |
736 | ||
737 | #ifdef INDEX_DEBUG_LOOKUP | |
738 | unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; | |
739 | git_oid_fmt(hex_sha1, found_oid); | |
740 | hex_sha1[GIT_OID_HEXSZ] = '\0'; | |
741 | printf("found lo=%d %s\n", lo, hex_sha1); | |
742 | #endif | |
743 | return GIT_SUCCESS; | |
744 | } | |
745 | } | |
746 | ||
747 | int git_pack_entry_find( | |
748 | struct git_pack_entry *e, | |
749 | struct git_pack_file *p, | |
750 | const git_oid *short_oid, | |
751 | unsigned int len) | |
752 | { | |
753 | off_t offset; | |
754 | git_oid found_oid; | |
755 | int error; | |
756 | ||
757 | assert(p); | |
758 | ||
759 | if (len == GIT_OID_HEXSZ && p->num_bad_objects) { | |
760 | unsigned i; | |
761 | for (i = 0; i < p->num_bad_objects; i++) | |
762 | if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0) | |
763 | return git__throw(GIT_ERROR, "Failed to find pack entry. Bad object found"); | |
764 | } | |
765 | ||
766 | error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); | |
767 | if (error < GIT_SUCCESS) | |
768 | return git__rethrow(error, "Failed to find pack entry. Couldn't find offset"); | |
769 | ||
770 | /* we found a unique entry in the index; | |
771 | * make sure the packfile backing the index | |
772 | * still exists on disk */ | |
773 | if (p->mwf.fd == -1 && packfile_open(p) < GIT_SUCCESS) | |
774 | return git__throw(GIT_EOSERR, "Failed to find pack entry. Packfile doesn't exist on disk"); | |
775 | ||
776 | e->offset = offset; | |
777 | e->p = p; | |
778 | ||
779 | git_oid_cpy(&e->sha1, &found_oid); | |
780 | return GIT_SUCCESS; | |
781 | } |