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