]>
Commit | Line | Data |
---|---|---|
7d7cd885 | 1 | /* |
5e0de328 | 2 | * Copyright (C) 2009-2012 the libgit2 contributors |
7d7cd885 | 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. | |
7d7cd885 VM |
6 | */ |
7 | ||
8 | #include "common.h" | |
0c3bae62 | 9 | #include <zlib.h> |
44908fe7 | 10 | #include "git2/object.h" |
f1d01851 | 11 | #include "git2/oid.h" |
7d7cd885 VM |
12 | #include "fileops.h" |
13 | #include "hash.h" | |
14 | #include "odb.h" | |
15 | #include "delta-apply.h" | |
72a3fe42 | 16 | #include "filebuf.h" |
7d7cd885 | 17 | |
44908fe7 | 18 | #include "git2/odb_backend.h" |
72a3fe42 | 19 | #include "git2/types.h" |
7d7cd885 | 20 | |
87d9869f VM |
21 | typedef struct { /* object header data */ |
22 | git_otype type; /* object type */ | |
23 | size_t size; /* object size */ | |
7d7cd885 VM |
24 | } obj_hdr; |
25 | ||
72a3fe42 VM |
26 | typedef struct { |
27 | git_odb_stream stream; | |
28 | git_filebuf fbuf; | |
72a3fe42 VM |
29 | } loose_writestream; |
30 | ||
7d7cd885 VM |
31 | typedef struct loose_backend { |
32 | git_odb_backend parent; | |
33 | ||
34 | int object_zlib_level; /** loose object zlib compression level. */ | |
35 | int fsync_object_files; /** loose object file fsync flag. */ | |
36 | char *objects_dir; | |
37 | } loose_backend; | |
38 | ||
aea8a638 MP |
39 | /* State structure for exploring directories, |
40 | * in order to locate objects matching a short oid. | |
41 | */ | |
42 | typedef struct { | |
43 | size_t dir_len; | |
44 | unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ | |
b8457baa | 45 | size_t short_oid_len; |
aea8a638 MP |
46 | int found; /* number of matching |
47 | * objects already found */ | |
48 | unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of | |
49 | * the object found */ | |
50 | } loose_locate_object_state; | |
51 | ||
52 | ||
7d7cd885 VM |
53 | /*********************************************************** |
54 | * | |
55 | * MISCELANEOUS HELPER FUNCTIONS | |
56 | * | |
57 | ***********************************************************/ | |
58 | ||
97769280 | 59 | static int object_file_name(git_buf *name, const char *dir, const git_oid *id) |
7d7cd885 | 60 | { |
97769280 | 61 | git_buf_sets(name, dir); |
7d7cd885 | 62 | |
97769280 | 63 | /* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */ |
fa6420f7 | 64 | if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0) |
e1de726c | 65 | return -1; |
7d7cd885 | 66 | |
97769280 | 67 | git_path_to_dir(name); |
7d7cd885 VM |
68 | |
69 | /* loose object filename: aa/aaa... (41 bytes) */ | |
fa6420f7 | 70 | git_oid_pathfmt(name->ptr + git_buf_len(name), id); |
97769280 RB |
71 | name->size += GIT_OID_HEXSZ + 1; |
72 | name->ptr[name->size] = '\0'; | |
7d7cd885 | 73 | |
e1de726c | 74 | return 0; |
7d7cd885 VM |
75 | } |
76 | ||
77 | ||
13224ea4 | 78 | static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) |
7d7cd885 VM |
79 | { |
80 | unsigned char c; | |
13224ea4 | 81 | unsigned char *data = (unsigned char *)obj->ptr; |
7d7cd885 VM |
82 | size_t shift, size, used = 0; |
83 | ||
fa6420f7 | 84 | if (git_buf_len(obj) == 0) |
7d7cd885 VM |
85 | return 0; |
86 | ||
87 | c = data[used++]; | |
88 | hdr->type = (c >> 4) & 7; | |
89 | ||
90 | size = c & 15; | |
91 | shift = 4; | |
92 | while (c & 0x80) { | |
fa6420f7 | 93 | if (git_buf_len(obj) <= used) |
7d7cd885 VM |
94 | return 0; |
95 | if (sizeof(size_t) * 8 <= shift) | |
96 | return 0; | |
97 | c = data[used++]; | |
98 | size += (c & 0x7f) << shift; | |
99 | shift += 7; | |
100 | } | |
101 | hdr->size = size; | |
102 | ||
103 | return used; | |
104 | } | |
105 | ||
106 | static size_t get_object_header(obj_hdr *hdr, unsigned char *data) | |
107 | { | |
108 | char c, typename[10]; | |
109 | size_t size, used = 0; | |
110 | ||
111 | /* | |
112 | * type name string followed by space. | |
113 | */ | |
114 | while ((c = data[used]) != ' ') { | |
115 | typename[used++] = c; | |
116 | if (used >= sizeof(typename)) | |
117 | return 0; | |
118 | } | |
119 | typename[used] = 0; | |
120 | if (used == 0) | |
121 | return 0; | |
d12299fe | 122 | hdr->type = git_object_string2type(typename); |
87d9869f | 123 | used++; /* consume the space */ |
7d7cd885 VM |
124 | |
125 | /* | |
126 | * length follows immediately in decimal (without | |
127 | * leading zeros). | |
128 | */ | |
129 | size = data[used++] - '0'; | |
130 | if (size > 9) | |
131 | return 0; | |
132 | if (size) { | |
133 | while ((c = data[used]) != '\0') { | |
134 | size_t d = c - '0'; | |
135 | if (d > 9) | |
136 | break; | |
137 | used++; | |
138 | size = size * 10 + d; | |
139 | } | |
140 | } | |
141 | hdr->size = size; | |
142 | ||
143 | /* | |
144 | * the length must be followed by a zero byte | |
145 | */ | |
146 | if (data[used++] != '\0') | |
147 | return 0; | |
148 | ||
149 | return used; | |
150 | } | |
151 | ||
152 | ||
153 | ||
154 | /*********************************************************** | |
155 | * | |
156 | * ZLIB RELATED FUNCTIONS | |
157 | * | |
158 | ***********************************************************/ | |
159 | ||
160 | static void init_stream(z_stream *s, void *out, size_t len) | |
161 | { | |
162 | memset(s, 0, sizeof(*s)); | |
87d9869f | 163 | s->next_out = out; |
1c3fac4d | 164 | s->avail_out = (uInt)len; |
7d7cd885 VM |
165 | } |
166 | ||
167 | static void set_stream_input(z_stream *s, void *in, size_t len) | |
168 | { | |
87d9869f | 169 | s->next_in = in; |
1c3fac4d | 170 | s->avail_in = (uInt)len; |
7d7cd885 VM |
171 | } |
172 | ||
173 | static void set_stream_output(z_stream *s, void *out, size_t len) | |
174 | { | |
87d9869f | 175 | s->next_out = out; |
1c3fac4d | 176 | s->avail_out = (uInt)len; |
7d7cd885 VM |
177 | } |
178 | ||
179 | ||
13224ea4 | 180 | static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len) |
7d7cd885 VM |
181 | { |
182 | int status; | |
183 | ||
184 | init_stream(s, out, len); | |
fa6420f7 | 185 | set_stream_input(s, obj->ptr, git_buf_len(obj)); |
7d7cd885 VM |
186 | |
187 | if ((status = inflateInit(s)) < Z_OK) | |
188 | return status; | |
189 | ||
190 | return inflate(s, 0); | |
191 | } | |
192 | ||
193 | static int finish_inflate(z_stream *s) | |
194 | { | |
195 | int status = Z_OK; | |
196 | ||
197 | while (status == Z_OK) | |
198 | status = inflate(s, Z_FINISH); | |
199 | ||
200 | inflateEnd(s); | |
201 | ||
e1de726c RB |
202 | if ((status != Z_STREAM_END) || (s->avail_in != 0)) { |
203 | giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely"); | |
204 | return -1; | |
205 | } | |
7d7cd885 | 206 | |
e1de726c | 207 | return 0; |
7d7cd885 VM |
208 | } |
209 | ||
72a3fe42 | 210 | static int is_zlib_compressed_data(unsigned char *data) |
7d7cd885 | 211 | { |
72a3fe42 | 212 | unsigned int w; |
7d7cd885 | 213 | |
72a3fe42 | 214 | w = ((unsigned int)(data[0]) << 8) + data[1]; |
c51065e3 | 215 | return (data[0] & 0x8F) == 0x08 && !(w % 31); |
7d7cd885 VM |
216 | } |
217 | ||
72a3fe42 | 218 | static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) |
7d7cd885 VM |
219 | { |
220 | z_stream zs; | |
72a3fe42 | 221 | int status = Z_OK; |
7d7cd885 | 222 | |
72a3fe42 | 223 | memset(&zs, 0x0, sizeof(zs)); |
7d7cd885 | 224 | |
87d9869f | 225 | zs.next_out = out; |
1c3fac4d | 226 | zs.avail_out = (uInt)outlen; |
7d7cd885 | 227 | |
87d9869f | 228 | zs.next_in = in; |
1c3fac4d | 229 | zs.avail_in = (uInt)inlen; |
7d7cd885 | 230 | |
e1de726c RB |
231 | if (inflateInit(&zs) < Z_OK) { |
232 | giterr_set(GITERR_ZLIB, "Failed to inflate buffer"); | |
233 | return -1; | |
234 | } | |
7d7cd885 | 235 | |
72a3fe42 VM |
236 | while (status == Z_OK) |
237 | status = inflate(&zs, Z_FINISH); | |
7d7cd885 | 238 | |
72a3fe42 | 239 | inflateEnd(&zs); |
7d7cd885 | 240 | |
e1de726c RB |
241 | if (status != Z_STREAM_END /* || zs.avail_in != 0 */ || |
242 | zs.total_out != outlen) | |
243 | { | |
244 | giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely"); | |
245 | return -1; | |
246 | } | |
7d7cd885 | 247 | |
e1de726c | 248 | return 0; |
7d7cd885 VM |
249 | } |
250 | ||
7d7cd885 VM |
251 | static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) |
252 | { | |
253 | unsigned char *buf, *head = hb; | |
254 | size_t tail; | |
255 | ||
256 | /* | |
257 | * allocate a buffer to hold the inflated data and copy the | |
258 | * initial sequence of inflated data from the tail of the | |
259 | * head buffer, if any. | |
260 | */ | |
261 | if ((buf = git__malloc(hdr->size + 1)) == NULL) { | |
262 | inflateEnd(s); | |
263 | return NULL; | |
264 | } | |
265 | tail = s->total_out - used; | |
266 | if (used > 0 && tail > 0) { | |
267 | if (tail > hdr->size) | |
268 | tail = hdr->size; | |
269 | memcpy(buf, head + used, tail); | |
270 | } | |
271 | used = tail; | |
272 | ||
273 | /* | |
274 | * inflate the remainder of the object data, if any | |
275 | */ | |
276 | if (hdr->size < used) | |
277 | inflateEnd(s); | |
278 | else { | |
279 | set_stream_output(s, buf + used, hdr->size - used); | |
280 | if (finish_inflate(s)) { | |
3286c408 | 281 | git__free(buf); |
7d7cd885 VM |
282 | return NULL; |
283 | } | |
284 | } | |
285 | ||
286 | return buf; | |
287 | } | |
288 | ||
289 | /* | |
290 | * At one point, there was a loose object format that was intended to | |
291 | * mimic the format used in pack-files. This was to allow easy copying | |
292 | * of loose object data into packs. This format is no longer used, but | |
293 | * we must still read it. | |
294 | */ | |
13224ea4 | 295 | static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) |
7d7cd885 VM |
296 | { |
297 | unsigned char *in, *buf; | |
298 | obj_hdr hdr; | |
299 | size_t len, used; | |
300 | ||
301 | /* | |
302 | * read the object header, which is an (uncompressed) | |
303 | * binary encoding of the object type and size. | |
304 | */ | |
e1de726c RB |
305 | if ((used = get_binary_object_header(&hdr, obj)) == 0 || |
306 | !git_object_typeisloose(hdr.type)) { | |
307 | giterr_set(GITERR_ODB, "Failed to inflate loose object."); | |
308 | return -1; | |
309 | } | |
7d7cd885 VM |
310 | |
311 | /* | |
312 | * allocate a buffer and inflate the data into it | |
313 | */ | |
314 | buf = git__malloc(hdr.size + 1); | |
e1de726c | 315 | GITERR_CHECK_ALLOC(buf); |
7d7cd885 | 316 | |
13224ea4 VM |
317 | in = ((unsigned char *)obj->ptr) + used; |
318 | len = obj->size - used; | |
e1de726c | 319 | if (inflate_buffer(in, len, buf, hdr.size) < 0) { |
3286c408 | 320 | git__free(buf); |
e1de726c | 321 | return -1; |
7d7cd885 VM |
322 | } |
323 | buf[hdr.size] = '\0'; | |
324 | ||
325 | out->data = buf; | |
87d9869f | 326 | out->len = hdr.size; |
7d7cd885 VM |
327 | out->type = hdr.type; |
328 | ||
e1de726c | 329 | return 0; |
7d7cd885 VM |
330 | } |
331 | ||
13224ea4 | 332 | static int inflate_disk_obj(git_rawobj *out, git_buf *obj) |
7d7cd885 VM |
333 | { |
334 | unsigned char head[64], *buf; | |
335 | z_stream zs; | |
7d7cd885 VM |
336 | obj_hdr hdr; |
337 | size_t used; | |
338 | ||
339 | /* | |
340 | * check for a pack-like loose object | |
341 | */ | |
13224ea4 | 342 | if (!is_zlib_compressed_data((unsigned char *)obj->ptr)) |
7d7cd885 VM |
343 | return inflate_packlike_loose_disk_obj(out, obj); |
344 | ||
345 | /* | |
346 | * inflate the initial part of the io buffer in order | |
347 | * to parse the object header (type and size). | |
348 | */ | |
e1de726c RB |
349 | if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK || |
350 | (used = get_object_header(&hdr, head)) == 0 || | |
351 | !git_object_typeisloose(hdr.type)) | |
352 | { | |
353 | giterr_set(GITERR_ODB, "Failed to inflate disk object."); | |
354 | return -1; | |
355 | } | |
7d7cd885 VM |
356 | |
357 | /* | |
358 | * allocate a buffer and inflate the object data into it | |
359 | * (including the initial sequence in the head buffer). | |
360 | */ | |
361 | if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL) | |
e1de726c | 362 | return -1; |
7d7cd885 VM |
363 | buf[hdr.size] = '\0'; |
364 | ||
365 | out->data = buf; | |
87d9869f | 366 | out->len = hdr.size; |
7d7cd885 VM |
367 | out->type = hdr.type; |
368 | ||
e1de726c | 369 | return 0; |
7d7cd885 VM |
370 | } |
371 | ||
372 | ||
373 | ||
374 | ||
375 | ||
376 | ||
377 | /*********************************************************** | |
378 | * | |
379 | * ODB OBJECT READING & WRITING | |
380 | * | |
381 | * Backend for the public API; read headers and full objects | |
382 | * from the ODB. Write raw data to the ODB. | |
383 | * | |
384 | ***********************************************************/ | |
385 | ||
97769280 | 386 | static int read_loose(git_rawobj *out, git_buf *loc) |
7d7cd885 VM |
387 | { |
388 | int error; | |
13224ea4 | 389 | git_buf obj = GIT_BUF_INIT; |
7d7cd885 VM |
390 | |
391 | assert(out && loc); | |
392 | ||
cb8a7961 | 393 | if (git_buf_oom(loc)) |
e1de726c | 394 | return -1; |
97769280 | 395 | |
7d7cd885 | 396 | out->data = NULL; |
87d9869f | 397 | out->len = 0; |
7d7cd885 VM |
398 | out->type = GIT_OBJ_BAD; |
399 | ||
e1de726c RB |
400 | if (!(error = git_futils_readbuffer(&obj, loc->ptr))) |
401 | error = inflate_disk_obj(out, &obj); | |
7d7cd885 | 402 | |
13224ea4 | 403 | git_buf_free(&obj); |
7d7cd885 | 404 | |
e1de726c | 405 | return error; |
7d7cd885 VM |
406 | } |
407 | ||
97769280 | 408 | static int read_header_loose(git_rawobj *out, git_buf *loc) |
7d7cd885 | 409 | { |
e1de726c | 410 | int error = 0, z_return = Z_ERRNO, read_bytes; |
7d7cd885 VM |
411 | git_file fd; |
412 | z_stream zs; | |
413 | obj_hdr header_obj; | |
414 | unsigned char raw_buffer[16], inflated_buffer[64]; | |
415 | ||
416 | assert(out && loc); | |
417 | ||
cb8a7961 | 418 | if (git_buf_oom(loc)) |
e1de726c | 419 | return -1; |
97769280 | 420 | |
7d7cd885 VM |
421 | out->data = NULL; |
422 | ||
e1de726c RB |
423 | if ((fd = git_futils_open_ro(loc->ptr)) < 0) |
424 | return fd; | |
7d7cd885 VM |
425 | |
426 | init_stream(&zs, inflated_buffer, sizeof(inflated_buffer)); | |
427 | ||
e1de726c | 428 | z_return = inflateInit(&zs); |
7d7cd885 | 429 | |
e1de726c RB |
430 | while (z_return == Z_OK) { |
431 | if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { | |
7d7cd885 VM |
432 | set_stream_input(&zs, raw_buffer, read_bytes); |
433 | z_return = inflate(&zs, 0); | |
e1de726c | 434 | } else |
a3ced637 | 435 | z_return = Z_STREAM_END; |
e1de726c | 436 | } |
7d7cd885 VM |
437 | |
438 | if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR) | |
439 | || get_object_header(&header_obj, inflated_buffer) == 0 | |
e1de726c RB |
440 | || git_object_typeisloose(header_obj.type) == 0) |
441 | { | |
442 | giterr_set(GITERR_ZLIB, "Failed to read loose object header"); | |
443 | error = -1; | |
444 | } else { | |
445 | out->len = header_obj.size; | |
446 | out->type = header_obj.type; | |
7d7cd885 VM |
447 | } |
448 | ||
7d7cd885 | 449 | finish_inflate(&zs); |
f79026b4 | 450 | p_close(fd); |
60e1b49a | 451 | |
e1de726c | 452 | return error; |
7d7cd885 VM |
453 | } |
454 | ||
97769280 RB |
455 | static int locate_object( |
456 | git_buf *object_location, | |
457 | loose_backend *backend, | |
458 | const git_oid *oid) | |
7d7cd885 | 459 | { |
97769280 RB |
460 | int error = object_file_name(object_location, backend->objects_dir, oid); |
461 | ||
e1de726c RB |
462 | if (!error && !git_path_exists(object_location->ptr)) |
463 | return GIT_ENOTFOUND; | |
97769280 RB |
464 | |
465 | return error; | |
7d7cd885 VM |
466 | } |
467 | ||
aea8a638 | 468 | /* Explore an entry of a directory and see if it matches a short oid */ |
97769280 | 469 | static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { |
aea8a638 MP |
470 | loose_locate_object_state *sstate = (loose_locate_object_state *)state; |
471 | ||
fa6420f7 | 472 | if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { |
aea8a638 | 473 | /* Entry cannot be an object. Continue to next entry */ |
e1de726c | 474 | return 0; |
aea8a638 MP |
475 | } |
476 | ||
9d160ba8 | 477 | if (git_path_isdir(pathbuf->ptr) == false) { |
f1d01851 VM |
478 | /* We are already in the directory matching the 2 first hex characters, |
479 | * compare the first ncmp characters of the oids */ | |
480 | if (!memcmp(sstate->short_oid + 2, | |
97769280 | 481 | (unsigned char *)pathbuf->ptr + sstate->dir_len, |
f1d01851 VM |
482 | sstate->short_oid_len - 2)) { |
483 | ||
aea8a638 MP |
484 | if (!sstate->found) { |
485 | sstate->res_oid[0] = sstate->short_oid[0]; | |
486 | sstate->res_oid[1] = sstate->short_oid[1]; | |
97769280 | 487 | memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); |
aea8a638 MP |
488 | } |
489 | sstate->found++; | |
490 | } | |
491 | } | |
e1de726c | 492 | |
d0323a5f | 493 | if (sstate->found > 1) |
e1de726c | 494 | return git_odb__error_ambiguous("multiple matches in loose objects"); |
d0323a5f | 495 | |
e1de726c | 496 | return 0; |
aea8a638 MP |
497 | } |
498 | ||
499 | /* Locate an object matching a given short oid */ | |
97769280 RB |
500 | static int locate_object_short_oid( |
501 | git_buf *object_location, | |
502 | git_oid *res_oid, | |
503 | loose_backend *backend, | |
504 | const git_oid *short_oid, | |
b8457baa | 505 | size_t len) |
aea8a638 MP |
506 | { |
507 | char *objects_dir = backend->objects_dir; | |
508 | size_t dir_len = strlen(objects_dir); | |
509 | loose_locate_object_state state; | |
510 | int error; | |
511 | ||
97769280 | 512 | /* prealloc memory for OBJ_DIR/xx/ */ |
e1de726c RB |
513 | if (git_buf_grow(object_location, dir_len + 5) < 0) |
514 | return -1; | |
aea8a638 | 515 | |
97769280 RB |
516 | git_buf_sets(object_location, objects_dir); |
517 | git_path_to_dir(object_location); | |
aea8a638 | 518 | |
97769280 | 519 | /* save adjusted position at end of dir so it can be restored later */ |
fa6420f7 | 520 | dir_len = git_buf_len(object_location); |
aea8a638 MP |
521 | |
522 | /* Convert raw oid to hex formatted oid */ | |
d0323a5f | 523 | git_oid_fmt((char *)state.short_oid, short_oid); |
97769280 | 524 | |
aea8a638 | 525 | /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ |
e1de726c RB |
526 | if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0) |
527 | return -1; | |
aea8a638 MP |
528 | |
529 | /* Check that directory exists */ | |
1a481123 | 530 | if (git_path_isdir(object_location->ptr) == false) |
282283ac | 531 | return git_odb__error_notfound("no matching loose object for prefix", short_oid); |
aea8a638 | 532 | |
fa6420f7 | 533 | state.dir_len = git_buf_len(object_location); |
aea8a638 MP |
534 | state.short_oid_len = len; |
535 | state.found = 0; | |
97769280 | 536 | |
aea8a638 | 537 | /* Explore directory to find a unique object matching short_oid */ |
e1de726c RB |
538 | error = git_path_direach( |
539 | object_location, fn_locate_object_short_oid, &state); | |
97769280 | 540 | if (error) |
e1de726c | 541 | return error; |
97769280 | 542 | |
e1de726c | 543 | if (!state.found) |
282283ac | 544 | return git_odb__error_notfound("no matching loose object for prefix", short_oid); |
aea8a638 MP |
545 | |
546 | /* Convert obtained hex formatted oid to raw */ | |
fa48608e | 547 | error = git_oid_fromstr(res_oid, (char *)state.res_oid); |
e1de726c RB |
548 | if (error) |
549 | return error; | |
aea8a638 MP |
550 | |
551 | /* Update the location according to the oid obtained */ | |
97769280 RB |
552 | |
553 | git_buf_truncate(object_location, dir_len); | |
e1de726c RB |
554 | if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0) |
555 | return -1; | |
97769280 RB |
556 | |
557 | git_oid_pathfmt(object_location->ptr + dir_len, res_oid); | |
558 | ||
559 | object_location->size += GIT_OID_HEXSZ + 1; | |
560 | object_location->ptr[object_location->size] = '\0'; | |
aea8a638 | 561 | |
e1de726c | 562 | return 0; |
aea8a638 MP |
563 | } |
564 | ||
7d7cd885 VM |
565 | |
566 | ||
567 | ||
568 | ||
569 | ||
570 | ||
571 | ||
572 | ||
573 | /*********************************************************** | |
574 | * | |
575 | * LOOSE BACKEND PUBLIC API | |
576 | * | |
577 | * Implement the git_odb_backend API calls | |
578 | * | |
579 | ***********************************************************/ | |
580 | ||
d568d585 | 581 | static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) |
7d7cd885 | 582 | { |
97769280 | 583 | git_buf object_path = GIT_BUF_INIT; |
72a3fe42 | 584 | git_rawobj raw; |
0d0fa7c3 | 585 | int error; |
7d7cd885 | 586 | |
72a3fe42 | 587 | assert(backend && oid); |
7d7cd885 | 588 | |
60e1b49a VM |
589 | raw.len = 0; |
590 | raw.type = GIT_OBJ_BAD; | |
591 | ||
97769280 | 592 | if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) |
282283ac | 593 | error = git_odb__error_notfound("no matching loose object", oid); |
e1de726c | 594 | else if ((error = read_header_loose(&raw, &object_path)) == 0) { |
97769280 RB |
595 | *len_p = raw.len; |
596 | *type_p = raw.type; | |
597 | } | |
7d7cd885 | 598 | |
97769280 | 599 | git_buf_free(&object_path); |
7d7cd885 | 600 | |
97769280 | 601 | return error; |
72a3fe42 | 602 | } |
7d7cd885 | 603 | |
d568d585 | 604 | static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) |
7d7cd885 | 605 | { |
97769280 | 606 | git_buf object_path = GIT_BUF_INIT; |
72a3fe42 | 607 | git_rawobj raw; |
e1de726c | 608 | int error = 0; |
7d7cd885 | 609 | |
72a3fe42 | 610 | assert(backend && oid); |
7d7cd885 | 611 | |
97769280 | 612 | if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) |
282283ac | 613 | error = git_odb__error_notfound("no matching loose object", oid); |
e1de726c | 614 | else if ((error = read_loose(&raw, &object_path)) == 0) { |
97769280 RB |
615 | *buffer_p = raw.data; |
616 | *len_p = raw.len; | |
617 | *type_p = raw.type; | |
618 | } | |
72a3fe42 | 619 | |
97769280 | 620 | git_buf_free(&object_path); |
72a3fe42 | 621 | |
97769280 | 622 | return error; |
7d7cd885 VM |
623 | } |
624 | ||
d568d585 | 625 | static int loose_backend__read_prefix( |
d0323a5f VM |
626 | git_oid *out_oid, |
627 | void **buffer_p, | |
628 | size_t *len_p, | |
629 | git_otype *type_p, | |
630 | git_odb_backend *backend, | |
631 | const git_oid *short_oid, | |
b8457baa | 632 | size_t len) |
ecd6fdf1 | 633 | { |
e1de726c | 634 | int error = 0; |
97769280 | 635 | |
aea8a638 | 636 | if (len < GIT_OID_MINPREFIXLEN) |
e1de726c | 637 | error = git_odb__error_ambiguous("prefix length too short"); |
aea8a638 | 638 | |
e1de726c | 639 | else if (len >= GIT_OID_HEXSZ) { |
aea8a638 | 640 | /* We can fall back to regular read method */ |
97769280 | 641 | error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); |
e1de726c | 642 | if (!error) |
ecd6fdf1 | 643 | git_oid_cpy(out_oid, short_oid); |
aea8a638 | 644 | } else { |
97769280 | 645 | git_buf object_path = GIT_BUF_INIT; |
aea8a638 | 646 | git_rawobj raw; |
aea8a638 MP |
647 | |
648 | assert(backend && short_oid); | |
649 | ||
97769280 | 650 | if ((error = locate_object_short_oid(&object_path, out_oid, |
e1de726c RB |
651 | (loose_backend *)backend, short_oid, len)) == 0 && |
652 | (error = read_loose(&raw, &object_path)) == 0) | |
653 | { | |
97769280 RB |
654 | *buffer_p = raw.data; |
655 | *len_p = raw.len; | |
656 | *type_p = raw.type; | |
aea8a638 MP |
657 | } |
658 | ||
97769280 | 659 | git_buf_free(&object_path); |
ecd6fdf1 | 660 | } |
aea8a638 | 661 | |
97769280 | 662 | return error; |
ecd6fdf1 MP |
663 | } |
664 | ||
d568d585 | 665 | static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) |
7d7cd885 | 666 | { |
97769280 RB |
667 | git_buf object_path = GIT_BUF_INIT; |
668 | int error; | |
7d7cd885 VM |
669 | |
670 | assert(backend && oid); | |
671 | ||
97769280 RB |
672 | error = locate_object(&object_path, (loose_backend *)backend, oid); |
673 | ||
674 | git_buf_free(&object_path); | |
675 | ||
e1de726c | 676 | return !error; |
7d7cd885 VM |
677 | } |
678 | ||
521aedad CMN |
679 | struct foreach_state { |
680 | size_t dir_len; | |
c3fb7d04 | 681 | git_odb_foreach_cb cb; |
521aedad | 682 | void *data; |
5dca2010 | 683 | int cb_error; |
521aedad CMN |
684 | }; |
685 | ||
b7158c53 | 686 | GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr) |
521aedad CMN |
687 | { |
688 | int v, i = 0; | |
689 | if (strlen(ptr) != 41) | |
690 | return -1; | |
691 | ||
692 | if (ptr[2] != '/') { | |
693 | return -1; | |
694 | } | |
695 | ||
696 | v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]); | |
697 | if (v < 0) | |
698 | return -1; | |
699 | ||
700 | oid->id[0] = (unsigned char) v; | |
701 | ||
702 | ptr += 3; | |
703 | for (i = 0; i < 38; i += 2) { | |
704 | v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]); | |
705 | if (v < 0) | |
706 | return -1; | |
707 | ||
708 | oid->id[1 + i/2] = (unsigned char) v; | |
709 | } | |
710 | ||
711 | return 0; | |
712 | } | |
713 | ||
714 | static int foreach_object_dir_cb(void *_state, git_buf *path) | |
715 | { | |
716 | git_oid oid; | |
717 | struct foreach_state *state = (struct foreach_state *) _state; | |
718 | ||
719 | if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0) | |
720 | return 0; | |
721 | ||
5dca2010 RB |
722 | if (state->cb(&oid, state->data)) { |
723 | state->cb_error = GIT_EUSER; | |
521aedad | 724 | return -1; |
5dca2010 | 725 | } |
521aedad CMN |
726 | |
727 | return 0; | |
728 | } | |
729 | ||
730 | static int foreach_cb(void *_state, git_buf *path) | |
731 | { | |
732 | struct foreach_state *state = (struct foreach_state *) _state; | |
733 | ||
5dca2010 | 734 | return git_path_direach(path, foreach_object_dir_cb, state); |
521aedad CMN |
735 | } |
736 | ||
c3fb7d04 | 737 | static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) |
521aedad CMN |
738 | { |
739 | char *objects_dir; | |
740 | int error; | |
741 | git_buf buf = GIT_BUF_INIT; | |
742 | struct foreach_state state; | |
743 | loose_backend *backend = (loose_backend *) _backend; | |
744 | ||
745 | assert(backend && cb); | |
746 | ||
747 | objects_dir = backend->objects_dir; | |
748 | ||
749 | git_buf_sets(&buf, objects_dir); | |
750 | git_path_to_dir(&buf); | |
751 | ||
5dca2010 | 752 | memset(&state, 0, sizeof(state)); |
521aedad CMN |
753 | state.cb = cb; |
754 | state.data = data; | |
755 | state.dir_len = git_buf_len(&buf); | |
756 | ||
757 | error = git_path_direach(&buf, foreach_cb, &state); | |
5dca2010 | 758 | |
521aedad CMN |
759 | git_buf_free(&buf); |
760 | ||
5dca2010 | 761 | return state.cb_error ? state.cb_error : error; |
521aedad CMN |
762 | } |
763 | ||
d568d585 | 764 | static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) |
72a3fe42 VM |
765 | { |
766 | loose_writestream *stream = (loose_writestream *)_stream; | |
767 | loose_backend *backend = (loose_backend *)_stream->backend; | |
97769280 | 768 | git_buf final_path = GIT_BUF_INIT; |
e1de726c | 769 | int error = 0; |
72a3fe42 | 770 | |
e1de726c RB |
771 | if (git_filebuf_hash(oid, &stream->fbuf) < 0 || |
772 | object_file_name(&final_path, backend->objects_dir, oid) < 0 || | |
773 | git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0) | |
774 | error = -1; | |
472d4d85 CMN |
775 | /* |
776 | * Don't try to add an existing object to the repository. This | |
777 | * is what git does and allows us to sidestep the fact that | |
778 | * we're not allowed to overwrite a read-only file on Windows. | |
779 | */ | |
e1de726c | 780 | else if (git_path_exists(final_path.ptr) == true) |
472d4d85 | 781 | git_filebuf_cleanup(&stream->fbuf); |
e1de726c RB |
782 | else |
783 | error = git_filebuf_commit_at( | |
784 | &stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); | |
97769280 | 785 | |
97769280 RB |
786 | git_buf_free(&final_path); |
787 | ||
97769280 | 788 | return error; |
72a3fe42 VM |
789 | } |
790 | ||
d568d585 | 791 | static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) |
7d7cd885 | 792 | { |
72a3fe42 VM |
793 | loose_writestream *stream = (loose_writestream *)_stream; |
794 | return git_filebuf_write(&stream->fbuf, data, len); | |
795 | } | |
796 | ||
d568d585 | 797 | static void loose_backend__stream_free(git_odb_stream *_stream) |
72a3fe42 VM |
798 | { |
799 | loose_writestream *stream = (loose_writestream *)_stream; | |
800 | ||
97769280 | 801 | git_filebuf_cleanup(&stream->fbuf); |
3286c408 | 802 | git__free(stream); |
72a3fe42 VM |
803 | } |
804 | ||
805 | static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) | |
806 | { | |
807 | const char *type_str = git_object_type2string(obj_type); | |
808 | int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); | |
809 | ||
87d9869f | 810 | assert(len > 0); /* otherwise snprintf() is broken */ |
e1de726c | 811 | assert(((size_t)len) < n); /* otherwise the caller is broken! */ |
72a3fe42 | 812 | |
72a3fe42 VM |
813 | return len+1; |
814 | } | |
815 | ||
d568d585 | 816 | static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) |
72a3fe42 VM |
817 | { |
818 | loose_backend *backend; | |
e1de726c | 819 | loose_writestream *stream = NULL; |
97769280 RB |
820 | char hdr[64]; |
821 | git_buf tmp_path = GIT_BUF_INIT; | |
87d9869f | 822 | int hdrlen; |
7d7cd885 | 823 | |
72a3fe42 | 824 | assert(_backend); |
7d7cd885 VM |
825 | |
826 | backend = (loose_backend *)_backend; | |
72a3fe42 | 827 | *stream_out = NULL; |
7d7cd885 | 828 | |
72a3fe42 | 829 | hdrlen = format_object_header(hdr, sizeof(hdr), length, type); |
72a3fe42 VM |
830 | |
831 | stream = git__calloc(1, sizeof(loose_writestream)); | |
e1de726c | 832 | GITERR_CHECK_ALLOC(stream); |
7d7cd885 | 833 | |
72a3fe42 VM |
834 | stream->stream.backend = _backend; |
835 | stream->stream.read = NULL; /* read only */ | |
836 | stream->stream.write = &loose_backend__stream_write; | |
837 | stream->stream.finalize_write = &loose_backend__stream_fwrite; | |
838 | stream->stream.free = &loose_backend__stream_free; | |
839 | stream->stream.mode = GIT_STREAM_WRONLY; | |
7d7cd885 | 840 | |
e1de726c RB |
841 | if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || |
842 | git_filebuf_open(&stream->fbuf, tmp_path.ptr, | |
843 | GIT_FILEBUF_HASH_CONTENTS | | |
844 | GIT_FILEBUF_TEMPORARY | | |
845 | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0 || | |
846 | stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) | |
847 | { | |
848 | git_filebuf_cleanup(&stream->fbuf); | |
849 | git__free(stream); | |
850 | stream = NULL; | |
851 | } | |
97769280 | 852 | git_buf_free(&tmp_path); |
72a3fe42 | 853 | *stream_out = (git_odb_stream *)stream; |
97769280 | 854 | |
e1de726c | 855 | return !stream ? -1 : 0; |
7d7cd885 VM |
856 | } |
857 | ||
d568d585 | 858 | static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) |
afeecf4f | 859 | { |
8e8b6b01 | 860 | int error = 0, header_len; |
97769280 RB |
861 | git_buf final_path = GIT_BUF_INIT; |
862 | char header[64]; | |
b762e576 | 863 | git_filebuf fbuf = GIT_FILEBUF_INIT; |
afeecf4f VM |
864 | loose_backend *backend; |
865 | ||
866 | backend = (loose_backend *)_backend; | |
867 | ||
868 | /* prepare the header for the file */ | |
e1de726c | 869 | header_len = format_object_header(header, sizeof(header), len, type); |
afeecf4f | 870 | |
e1de726c RB |
871 | if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || |
872 | git_filebuf_open(&fbuf, final_path.ptr, | |
e1de726c RB |
873 | GIT_FILEBUF_TEMPORARY | |
874 | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0) | |
875 | { | |
876 | error = -1; | |
97769280 | 877 | goto cleanup; |
e1de726c | 878 | } |
afeecf4f VM |
879 | |
880 | git_filebuf_write(&fbuf, header, header_len); | |
881 | git_filebuf_write(&fbuf, data, len); | |
afeecf4f | 882 | |
e1de726c RB |
883 | if (object_file_name(&final_path, backend->objects_dir, oid) < 0 || |
884 | git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 || | |
885 | git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0) | |
886 | error = -1; | |
afeecf4f VM |
887 | |
888 | cleanup: | |
0d0fa7c3 | 889 | if (error < 0) |
97769280 RB |
890 | git_filebuf_cleanup(&fbuf); |
891 | git_buf_free(&final_path); | |
afeecf4f VM |
892 | return error; |
893 | } | |
894 | ||
d568d585 | 895 | static void loose_backend__free(git_odb_backend *_backend) |
7d7cd885 VM |
896 | { |
897 | loose_backend *backend; | |
898 | assert(_backend); | |
899 | backend = (loose_backend *)_backend; | |
900 | ||
3286c408 VM |
901 | git__free(backend->objects_dir); |
902 | git__free(backend); | |
7d7cd885 VM |
903 | } |
904 | ||
8af4d074 VM |
905 | int git_odb_backend_loose( |
906 | git_odb_backend **backend_out, | |
907 | const char *objects_dir, | |
908 | int compression_level, | |
909 | int do_fsync) | |
7d7cd885 VM |
910 | { |
911 | loose_backend *backend; | |
912 | ||
913 | backend = git__calloc(1, sizeof(loose_backend)); | |
e1de726c | 914 | GITERR_CHECK_ALLOC(backend); |
7d7cd885 | 915 | |
55f6f21b | 916 | backend->parent.version = GIT_ODB_BACKEND_VERSION; |
7d7cd885 | 917 | backend->objects_dir = git__strdup(objects_dir); |
e1de726c | 918 | GITERR_CHECK_ALLOC(backend->objects_dir); |
7d7cd885 | 919 | |
8af4d074 VM |
920 | if (compression_level < 0) |
921 | compression_level = Z_BEST_SPEED; | |
922 | ||
923 | backend->object_zlib_level = compression_level; | |
924 | backend->fsync_object_files = do_fsync; | |
7d7cd885 VM |
925 | |
926 | backend->parent.read = &loose_backend__read; | |
afeecf4f | 927 | backend->parent.write = &loose_backend__write; |
d0323a5f | 928 | backend->parent.read_prefix = &loose_backend__read_prefix; |
7d7cd885 | 929 | backend->parent.read_header = &loose_backend__read_header; |
72a3fe42 | 930 | backend->parent.writestream = &loose_backend__stream; |
7d7cd885 | 931 | backend->parent.exists = &loose_backend__exists; |
521aedad | 932 | backend->parent.foreach = &loose_backend__foreach; |
7d7cd885 VM |
933 | backend->parent.free = &loose_backend__free; |
934 | ||
7d7cd885 | 935 | *backend_out = (git_odb_backend *)backend; |
e1de726c | 936 | return 0; |
7d7cd885 | 937 | } |