]>
Commit | Line | Data |
---|---|---|
7d7cd885 | 1 | /* |
bb742ede | 2 | * Copyright (C) 2009-2011 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" | |
44908fe7 VM |
9 | #include "git2/zlib.h" |
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; | |
29 | int finished; | |
30 | } loose_writestream; | |
31 | ||
7d7cd885 VM |
32 | typedef struct loose_backend { |
33 | git_odb_backend parent; | |
34 | ||
35 | int object_zlib_level; /** loose object zlib compression level. */ | |
36 | int fsync_object_files; /** loose object file fsync flag. */ | |
37 | char *objects_dir; | |
38 | } loose_backend; | |
39 | ||
aea8a638 MP |
40 | /* State structure for exploring directories, |
41 | * in order to locate objects matching a short oid. | |
42 | */ | |
43 | typedef struct { | |
44 | size_t dir_len; | |
45 | unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ | |
46 | unsigned int short_oid_len; | |
47 | int found; /* number of matching | |
48 | * objects already found */ | |
49 | unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of | |
50 | * the object found */ | |
51 | } loose_locate_object_state; | |
52 | ||
53 | ||
7d7cd885 VM |
54 | |
55 | /*********************************************************** | |
56 | * | |
57 | * MISCELANEOUS HELPER FUNCTIONS | |
58 | * | |
59 | ***********************************************************/ | |
60 | ||
7d7cd885 VM |
61 | static size_t object_file_name(char *name, size_t n, char *dir, const git_oid *id) |
62 | { | |
63 | size_t len = strlen(dir); | |
64 | ||
65 | /* check length: 43 = 40 hex sha1 chars + 2 * '/' + '\0' */ | |
66 | if (len+43 > n) | |
67 | return len+43; | |
68 | ||
69 | /* the object dir: eg $GIT_DIR/objects */ | |
70 | strcpy(name, dir); | |
71 | if (name[len-1] != '/') | |
72 | name[len++] = '/'; | |
73 | ||
74 | /* loose object filename: aa/aaa... (41 bytes) */ | |
75 | git_oid_pathfmt(&name[len], id); | |
76 | name[len+41] = '\0'; | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | ||
f79026b4 | 82 | static size_t get_binary_object_header(obj_hdr *hdr, git_fbuffer *obj) |
7d7cd885 VM |
83 | { |
84 | unsigned char c; | |
85 | unsigned char *data = obj->data; | |
86 | size_t shift, size, used = 0; | |
87 | ||
88 | if (obj->len == 0) | |
89 | return 0; | |
90 | ||
91 | c = data[used++]; | |
92 | hdr->type = (c >> 4) & 7; | |
93 | ||
94 | size = c & 15; | |
95 | shift = 4; | |
96 | while (c & 0x80) { | |
97 | if (obj->len <= used) | |
98 | return 0; | |
99 | if (sizeof(size_t) * 8 <= shift) | |
100 | return 0; | |
101 | c = data[used++]; | |
102 | size += (c & 0x7f) << shift; | |
103 | shift += 7; | |
104 | } | |
105 | hdr->size = size; | |
106 | ||
107 | return used; | |
108 | } | |
109 | ||
110 | static size_t get_object_header(obj_hdr *hdr, unsigned char *data) | |
111 | { | |
112 | char c, typename[10]; | |
113 | size_t size, used = 0; | |
114 | ||
115 | /* | |
116 | * type name string followed by space. | |
117 | */ | |
118 | while ((c = data[used]) != ' ') { | |
119 | typename[used++] = c; | |
120 | if (used >= sizeof(typename)) | |
121 | return 0; | |
122 | } | |
123 | typename[used] = 0; | |
124 | if (used == 0) | |
125 | return 0; | |
d12299fe | 126 | hdr->type = git_object_string2type(typename); |
87d9869f | 127 | used++; /* consume the space */ |
7d7cd885 VM |
128 | |
129 | /* | |
130 | * length follows immediately in decimal (without | |
131 | * leading zeros). | |
132 | */ | |
133 | size = data[used++] - '0'; | |
134 | if (size > 9) | |
135 | return 0; | |
136 | if (size) { | |
137 | while ((c = data[used]) != '\0') { | |
138 | size_t d = c - '0'; | |
139 | if (d > 9) | |
140 | break; | |
141 | used++; | |
142 | size = size * 10 + d; | |
143 | } | |
144 | } | |
145 | hdr->size = size; | |
146 | ||
147 | /* | |
148 | * the length must be followed by a zero byte | |
149 | */ | |
150 | if (data[used++] != '\0') | |
151 | return 0; | |
152 | ||
153 | return used; | |
154 | } | |
155 | ||
156 | ||
157 | ||
158 | /*********************************************************** | |
159 | * | |
160 | * ZLIB RELATED FUNCTIONS | |
161 | * | |
162 | ***********************************************************/ | |
163 | ||
164 | static void init_stream(z_stream *s, void *out, size_t len) | |
165 | { | |
166 | memset(s, 0, sizeof(*s)); | |
87d9869f | 167 | s->next_out = out; |
1c3fac4d | 168 | s->avail_out = (uInt)len; |
7d7cd885 VM |
169 | } |
170 | ||
171 | static void set_stream_input(z_stream *s, void *in, size_t len) | |
172 | { | |
87d9869f | 173 | s->next_in = in; |
1c3fac4d | 174 | s->avail_in = (uInt)len; |
7d7cd885 VM |
175 | } |
176 | ||
177 | static void set_stream_output(z_stream *s, void *out, size_t len) | |
178 | { | |
87d9869f | 179 | s->next_out = out; |
1c3fac4d | 180 | s->avail_out = (uInt)len; |
7d7cd885 VM |
181 | } |
182 | ||
183 | ||
f79026b4 | 184 | static int start_inflate(z_stream *s, git_fbuffer *obj, void *out, size_t len) |
7d7cd885 VM |
185 | { |
186 | int status; | |
187 | ||
188 | init_stream(s, out, len); | |
189 | set_stream_input(s, obj->data, obj->len); | |
190 | ||
191 | if ((status = inflateInit(s)) < Z_OK) | |
192 | return status; | |
193 | ||
194 | return inflate(s, 0); | |
195 | } | |
196 | ||
197 | static int finish_inflate(z_stream *s) | |
198 | { | |
199 | int status = Z_OK; | |
200 | ||
201 | while (status == Z_OK) | |
202 | status = inflate(s, Z_FINISH); | |
203 | ||
204 | inflateEnd(s); | |
205 | ||
206 | if ((status != Z_STREAM_END) || (s->avail_in != 0)) | |
dfb12cd5 | 207 | return git__throw(GIT_ERROR, "Failed to finish inflation. Stream aborted prematurely"); |
7d7cd885 VM |
208 | |
209 | return GIT_SUCCESS; | |
210 | } | |
211 | ||
72a3fe42 | 212 | static int is_zlib_compressed_data(unsigned char *data) |
7d7cd885 | 213 | { |
72a3fe42 | 214 | unsigned int w; |
7d7cd885 | 215 | |
72a3fe42 VM |
216 | w = ((unsigned int)(data[0]) << 8) + data[1]; |
217 | return data[0] == 0x78 && !(w % 31); | |
7d7cd885 VM |
218 | } |
219 | ||
72a3fe42 | 220 | static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) |
7d7cd885 VM |
221 | { |
222 | z_stream zs; | |
72a3fe42 | 223 | int status = Z_OK; |
7d7cd885 | 224 | |
72a3fe42 | 225 | memset(&zs, 0x0, sizeof(zs)); |
7d7cd885 | 226 | |
87d9869f | 227 | zs.next_out = out; |
1c3fac4d | 228 | zs.avail_out = (uInt)outlen; |
7d7cd885 | 229 | |
87d9869f | 230 | zs.next_in = in; |
1c3fac4d | 231 | zs.avail_in = (uInt)inlen; |
7d7cd885 | 232 | |
72a3fe42 | 233 | if (inflateInit(&zs) < Z_OK) |
dfb12cd5 | 234 | return git__throw(GIT_ERROR, "Failed to inflate buffer"); |
7d7cd885 | 235 | |
72a3fe42 VM |
236 | while (status == Z_OK) |
237 | status = inflate(&zs, Z_FINISH); | |
7d7cd885 | 238 | |
72a3fe42 | 239 | inflateEnd(&zs); |
7d7cd885 | 240 | |
72a3fe42 | 241 | if ((status != Z_STREAM_END) /*|| (zs.avail_in != 0) */) |
dfb12cd5 | 242 | return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely"); |
7d7cd885 | 243 | |
72a3fe42 | 244 | if (zs.total_out != outlen) |
dfb12cd5 | 245 | return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely"); |
7d7cd885 VM |
246 | |
247 | return GIT_SUCCESS; | |
248 | } | |
249 | ||
7d7cd885 VM |
250 | static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) |
251 | { | |
252 | unsigned char *buf, *head = hb; | |
253 | size_t tail; | |
254 | ||
255 | /* | |
256 | * allocate a buffer to hold the inflated data and copy the | |
257 | * initial sequence of inflated data from the tail of the | |
258 | * head buffer, if any. | |
259 | */ | |
260 | if ((buf = git__malloc(hdr->size + 1)) == NULL) { | |
261 | inflateEnd(s); | |
262 | return NULL; | |
263 | } | |
264 | tail = s->total_out - used; | |
265 | if (used > 0 && tail > 0) { | |
266 | if (tail > hdr->size) | |
267 | tail = hdr->size; | |
268 | memcpy(buf, head + used, tail); | |
269 | } | |
270 | used = tail; | |
271 | ||
272 | /* | |
273 | * inflate the remainder of the object data, if any | |
274 | */ | |
275 | if (hdr->size < used) | |
276 | inflateEnd(s); | |
277 | else { | |
278 | set_stream_output(s, buf + used, hdr->size - used); | |
279 | if (finish_inflate(s)) { | |
280 | free(buf); | |
281 | return NULL; | |
282 | } | |
283 | } | |
284 | ||
285 | return buf; | |
286 | } | |
287 | ||
288 | /* | |
289 | * At one point, there was a loose object format that was intended to | |
290 | * mimic the format used in pack-files. This was to allow easy copying | |
291 | * of loose object data into packs. This format is no longer used, but | |
292 | * we must still read it. | |
293 | */ | |
f79026b4 | 294 | static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_fbuffer *obj) |
7d7cd885 VM |
295 | { |
296 | unsigned char *in, *buf; | |
297 | obj_hdr hdr; | |
298 | size_t len, used; | |
299 | ||
300 | /* | |
301 | * read the object header, which is an (uncompressed) | |
302 | * binary encoding of the object type and size. | |
303 | */ | |
304 | if ((used = get_binary_object_header(&hdr, obj)) == 0) | |
dfb12cd5 | 305 | return git__throw(GIT_ERROR, "Failed to inflate loose object. Object has no header"); |
7d7cd885 | 306 | |
d12299fe | 307 | if (!git_object_typeisloose(hdr.type)) |
dfb12cd5 | 308 | return git__throw(GIT_ERROR, "Failed to inflate loose object. Wrong object type"); |
7d7cd885 VM |
309 | |
310 | /* | |
311 | * allocate a buffer and inflate the data into it | |
312 | */ | |
313 | buf = git__malloc(hdr.size + 1); | |
314 | if (!buf) | |
f93f8ec5 | 315 | return GIT_ENOMEM; |
7d7cd885 | 316 | |
87d9869f | 317 | in = ((unsigned char *)obj->data) + used; |
7d7cd885 | 318 | len = obj->len - used; |
72a3fe42 | 319 | if (inflate_buffer(in, len, buf, hdr.size)) { |
7d7cd885 | 320 | free(buf); |
dfb12cd5 | 321 | return git__throw(GIT_ERROR, "Failed to inflate loose object. Could not inflate buffer"); |
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 | ||
329 | return GIT_SUCCESS; | |
330 | } | |
331 | ||
f79026b4 | 332 | static int inflate_disk_obj(git_rawobj *out, git_fbuffer *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 | */ | |
342 | if (!is_zlib_compressed_data(obj->data)) | |
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 | */ | |
d8e1d038 | 349 | if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK) |
dfb12cd5 | 350 | return git__throw(GIT_ERROR, "Failed to inflate disk object. Could not inflate buffer"); |
7d7cd885 VM |
351 | |
352 | if ((used = get_object_header(&hdr, head)) == 0) | |
dfb12cd5 | 353 | return git__throw(GIT_ERROR, "Failed to inflate disk object. Object has no header"); |
7d7cd885 | 354 | |
d12299fe | 355 | if (!git_object_typeisloose(hdr.type)) |
dfb12cd5 | 356 | return git__throw(GIT_ERROR, "Failed to inflate disk object. Wrong object type"); |
7d7cd885 VM |
357 | |
358 | /* | |
359 | * allocate a buffer and inflate the object data into it | |
360 | * (including the initial sequence in the head buffer). | |
361 | */ | |
362 | if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL) | |
f93f8ec5 | 363 | return GIT_ENOMEM; |
7d7cd885 VM |
364 | buf[hdr.size] = '\0'; |
365 | ||
366 | out->data = buf; | |
87d9869f | 367 | out->len = hdr.size; |
7d7cd885 VM |
368 | out->type = hdr.type; |
369 | ||
370 | return GIT_SUCCESS; | |
371 | } | |
372 | ||
373 | ||
374 | ||
375 | ||
376 | ||
377 | ||
378 | /*********************************************************** | |
379 | * | |
380 | * ODB OBJECT READING & WRITING | |
381 | * | |
382 | * Backend for the public API; read headers and full objects | |
383 | * from the ODB. Write raw data to the ODB. | |
384 | * | |
385 | ***********************************************************/ | |
386 | ||
387 | static int read_loose(git_rawobj *out, const char *loc) | |
388 | { | |
389 | int error; | |
f79026b4 | 390 | git_fbuffer obj = GIT_FBUFFER_INIT; |
7d7cd885 VM |
391 | |
392 | assert(out && loc); | |
393 | ||
394 | out->data = NULL; | |
87d9869f | 395 | out->len = 0; |
7d7cd885 VM |
396 | out->type = GIT_OBJ_BAD; |
397 | ||
f79026b4 | 398 | if (git_futils_readbuffer(&obj, loc) < 0) |
dfb12cd5 | 399 | return git__throw(GIT_ENOTFOUND, "Failed to read loose object. File not found"); |
7d7cd885 VM |
400 | |
401 | error = inflate_disk_obj(out, &obj); | |
f79026b4 | 402 | git_futils_freebuffer(&obj); |
7d7cd885 | 403 | |
dfb12cd5 | 404 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read loose object"); |
7d7cd885 VM |
405 | } |
406 | ||
407 | static int read_header_loose(git_rawobj *out, const char *loc) | |
408 | { | |
409 | int error = GIT_SUCCESS, z_return = Z_ERRNO, read_bytes; | |
410 | git_file fd; | |
411 | z_stream zs; | |
412 | obj_hdr header_obj; | |
413 | unsigned char raw_buffer[16], inflated_buffer[64]; | |
414 | ||
415 | assert(out && loc); | |
416 | ||
417 | out->data = NULL; | |
418 | ||
f79026b4 | 419 | if ((fd = p_open(loc, O_RDONLY)) < 0) |
dfb12cd5 | 420 | return git__throw(GIT_ENOTFOUND, "Failed to read loose object header. File not found"); |
7d7cd885 VM |
421 | |
422 | init_stream(&zs, inflated_buffer, sizeof(inflated_buffer)); | |
423 | ||
424 | if (inflateInit(&zs) < Z_OK) { | |
425 | error = GIT_EZLIB; | |
426 | goto cleanup; | |
427 | } | |
428 | ||
429 | do { | |
430 | if ((read_bytes = read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { | |
431 | set_stream_input(&zs, raw_buffer, read_bytes); | |
432 | z_return = inflate(&zs, 0); | |
a3ced637 SN |
433 | } else { |
434 | z_return = Z_STREAM_END; | |
435 | break; | |
7d7cd885 VM |
436 | } |
437 | } while (z_return == Z_OK); | |
438 | ||
439 | if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR) | |
440 | || get_object_header(&header_obj, inflated_buffer) == 0 | |
d12299fe | 441 | || git_object_typeisloose(header_obj.type) == 0) { |
7d7cd885 VM |
442 | error = GIT_EOBJCORRUPTED; |
443 | goto cleanup; | |
444 | } | |
445 | ||
87d9869f | 446 | out->len = header_obj.size; |
7d7cd885 VM |
447 | out->type = header_obj.type; |
448 | ||
449 | cleanup: | |
450 | finish_inflate(&zs); | |
f79026b4 | 451 | p_close(fd); |
60e1b49a VM |
452 | |
453 | if (error < GIT_SUCCESS) | |
454 | return git__throw(error, "Failed to read loose object header. Header is corrupted"); | |
455 | ||
456 | return GIT_SUCCESS; | |
7d7cd885 VM |
457 | } |
458 | ||
7d7cd885 VM |
459 | static int locate_object(char *object_location, loose_backend *backend, const git_oid *oid) |
460 | { | |
461 | object_file_name(object_location, GIT_PATH_MAX, backend->objects_dir, oid); | |
f79026b4 | 462 | return git_futils_exists(object_location); |
7d7cd885 VM |
463 | } |
464 | ||
aea8a638 | 465 | /* Explore an entry of a directory and see if it matches a short oid */ |
d568d585 | 466 | static int fn_locate_object_short_oid(void *state, char *pathbuf) { |
aea8a638 MP |
467 | loose_locate_object_state *sstate = (loose_locate_object_state *)state; |
468 | ||
469 | size_t pathbuf_len = strlen(pathbuf); | |
470 | if (pathbuf_len - sstate->dir_len != GIT_OID_HEXSZ - 2) { | |
471 | /* Entry cannot be an object. Continue to next entry */ | |
472 | return GIT_SUCCESS; | |
473 | } | |
474 | ||
f79026b4 | 475 | if (!git_futils_exists(pathbuf) && git_futils_isdir(pathbuf)) { |
f1d01851 VM |
476 | /* We are already in the directory matching the 2 first hex characters, |
477 | * compare the first ncmp characters of the oids */ | |
478 | if (!memcmp(sstate->short_oid + 2, | |
479 | (unsigned char *)pathbuf + sstate->dir_len, | |
480 | sstate->short_oid_len - 2)) { | |
481 | ||
aea8a638 MP |
482 | if (!sstate->found) { |
483 | sstate->res_oid[0] = sstate->short_oid[0]; | |
484 | sstate->res_oid[1] = sstate->short_oid[1]; | |
485 | memcpy(sstate->res_oid+2, pathbuf+sstate->dir_len, GIT_OID_HEXSZ-2); | |
486 | } | |
487 | sstate->found++; | |
488 | } | |
489 | } | |
d0323a5f | 490 | if (sstate->found > 1) |
aea8a638 | 491 | return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Ambiguous sha1 prefix within loose objects"); |
d0323a5f VM |
492 | |
493 | return GIT_SUCCESS; | |
aea8a638 MP |
494 | } |
495 | ||
496 | /* Locate an object matching a given short oid */ | |
497 | static int locate_object_short_oid(char *object_location, git_oid *res_oid, loose_backend *backend, const git_oid *short_oid, unsigned int len) | |
498 | { | |
499 | char *objects_dir = backend->objects_dir; | |
500 | size_t dir_len = strlen(objects_dir); | |
501 | loose_locate_object_state state; | |
502 | int error; | |
503 | ||
504 | if (dir_len+43 > GIT_PATH_MAX) | |
505 | return git__throw(GIT_ERROR, "Failed to locate object from short oid. Object path too long"); | |
506 | ||
507 | strcpy(object_location, objects_dir); | |
508 | ||
509 | /* Add a separator if not already there */ | |
510 | if (object_location[dir_len-1] != '/') | |
511 | object_location[dir_len++] = '/'; | |
512 | ||
513 | /* Convert raw oid to hex formatted oid */ | |
d0323a5f | 514 | git_oid_fmt((char *)state.short_oid, short_oid); |
aea8a638 MP |
515 | /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ |
516 | sprintf(object_location+dir_len, "%.2s/", state.short_oid); | |
517 | ||
518 | /* Check that directory exists */ | |
f79026b4 | 519 | if (git_futils_exists(object_location) || git_futils_isdir(object_location)) |
aea8a638 MP |
520 | return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); |
521 | ||
522 | state.dir_len = dir_len+3; | |
523 | state.short_oid_len = len; | |
524 | state.found = 0; | |
525 | /* Explore directory to find a unique object matching short_oid */ | |
f79026b4 | 526 | error = git_futils_direach(object_location, GIT_PATH_MAX, fn_locate_object_short_oid, &state); |
aea8a638 MP |
527 | if (error) { |
528 | return git__rethrow(error, "Failed to locate object from short oid"); | |
529 | } | |
530 | if (!state.found) { | |
531 | return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); | |
532 | } | |
533 | ||
534 | /* Convert obtained hex formatted oid to raw */ | |
fa48608e | 535 | error = git_oid_fromstr(res_oid, (char *)state.res_oid); |
aea8a638 MP |
536 | if (error) { |
537 | return git__rethrow(error, "Failed to locate object from short oid"); | |
538 | } | |
539 | ||
540 | /* Update the location according to the oid obtained */ | |
541 | git_oid_pathfmt(object_location+dir_len, res_oid); | |
542 | ||
543 | return GIT_SUCCESS; | |
544 | } | |
545 | ||
7d7cd885 VM |
546 | |
547 | ||
548 | ||
549 | ||
550 | ||
551 | ||
552 | ||
553 | ||
554 | /*********************************************************** | |
555 | * | |
556 | * LOOSE BACKEND PUBLIC API | |
557 | * | |
558 | * Implement the git_odb_backend API calls | |
559 | * | |
560 | ***********************************************************/ | |
561 | ||
d568d585 | 562 | static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) |
7d7cd885 VM |
563 | { |
564 | char object_path[GIT_PATH_MAX]; | |
72a3fe42 VM |
565 | git_rawobj raw; |
566 | int error; | |
7d7cd885 | 567 | |
72a3fe42 | 568 | assert(backend && oid); |
7d7cd885 | 569 | |
60e1b49a VM |
570 | raw.len = 0; |
571 | raw.type = GIT_OBJ_BAD; | |
572 | ||
7d7cd885 | 573 | if (locate_object(object_path, (loose_backend *)backend, oid) < 0) |
dfb12cd5 | 574 | return git__throw(GIT_ENOTFOUND, "Failed to read loose backend header. Object not found"); |
7d7cd885 | 575 | |
72a3fe42 | 576 | if ((error = read_header_loose(&raw, object_path)) < GIT_SUCCESS) |
60e1b49a | 577 | return error; |
7d7cd885 | 578 | |
72a3fe42 VM |
579 | *len_p = raw.len; |
580 | *type_p = raw.type; | |
581 | return GIT_SUCCESS; | |
582 | } | |
7d7cd885 | 583 | |
d568d585 | 584 | 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 VM |
585 | { |
586 | char object_path[GIT_PATH_MAX]; | |
72a3fe42 VM |
587 | git_rawobj raw; |
588 | int error; | |
7d7cd885 | 589 | |
72a3fe42 | 590 | assert(backend && oid); |
7d7cd885 VM |
591 | |
592 | if (locate_object(object_path, (loose_backend *)backend, oid) < 0) | |
dfb12cd5 | 593 | return git__throw(GIT_ENOTFOUND, "Failed to read loose backend. Object not found"); |
7d7cd885 | 594 | |
72a3fe42 | 595 | if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS) |
dfb12cd5 | 596 | return git__rethrow(error, "Failed to read loose backend"); |
72a3fe42 VM |
597 | |
598 | *buffer_p = raw.data; | |
599 | *len_p = raw.len; | |
600 | *type_p = raw.type; | |
601 | ||
602 | return GIT_SUCCESS; | |
7d7cd885 VM |
603 | } |
604 | ||
d568d585 | 605 | static int loose_backend__read_prefix( |
d0323a5f VM |
606 | git_oid *out_oid, |
607 | void **buffer_p, | |
608 | size_t *len_p, | |
609 | git_otype *type_p, | |
610 | git_odb_backend *backend, | |
611 | const git_oid *short_oid, | |
612 | unsigned int len) | |
ecd6fdf1 | 613 | { |
aea8a638 MP |
614 | if (len < GIT_OID_MINPREFIXLEN) |
615 | return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read loose backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); | |
616 | ||
ecd6fdf1 | 617 | if (len >= GIT_OID_HEXSZ) { |
aea8a638 | 618 | /* We can fall back to regular read method */ |
ecd6fdf1 MP |
619 | int error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); |
620 | if (error == GIT_SUCCESS) | |
621 | git_oid_cpy(out_oid, short_oid); | |
622 | ||
623 | return error; | |
aea8a638 MP |
624 | } else { |
625 | char object_path[GIT_PATH_MAX]; | |
626 | git_rawobj raw; | |
627 | int error; | |
628 | ||
629 | assert(backend && short_oid); | |
630 | ||
631 | if ((error = locate_object_short_oid(object_path, out_oid, (loose_backend *)backend, short_oid, len)) < 0) { | |
632 | return git__rethrow(error, "Failed to read loose backend"); | |
633 | } | |
634 | ||
635 | if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS) | |
636 | return git__rethrow(error, "Failed to read loose backend"); | |
637 | ||
638 | *buffer_p = raw.data; | |
639 | *len_p = raw.len; | |
640 | *type_p = raw.type; | |
ecd6fdf1 | 641 | } |
aea8a638 MP |
642 | |
643 | return GIT_SUCCESS; | |
ecd6fdf1 MP |
644 | } |
645 | ||
d568d585 | 646 | static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) |
7d7cd885 VM |
647 | { |
648 | char object_path[GIT_PATH_MAX]; | |
649 | ||
650 | assert(backend && oid); | |
651 | ||
652 | return locate_object(object_path, (loose_backend *)backend, oid) == GIT_SUCCESS; | |
653 | } | |
654 | ||
d568d585 | 655 | static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) |
72a3fe42 VM |
656 | { |
657 | loose_writestream *stream = (loose_writestream *)_stream; | |
658 | loose_backend *backend = (loose_backend *)_stream->backend; | |
659 | ||
660 | int error; | |
661 | char final_path[GIT_PATH_MAX]; | |
662 | ||
663 | if ((error = git_filebuf_hash(oid, &stream->fbuf)) < GIT_SUCCESS) | |
dfb12cd5 | 664 | return git__rethrow(error, "Failed to write loose backend"); |
72a3fe42 VM |
665 | |
666 | if (object_file_name(final_path, sizeof(final_path), backend->objects_dir, oid)) | |
667 | return GIT_ENOMEM; | |
7d7cd885 | 668 | |
f79026b4 | 669 | if ((error = git_futils_mkpath2file(final_path)) < GIT_SUCCESS) |
dfb12cd5 | 670 | return git__rethrow(error, "Failed to write loose backend"); |
72a3fe42 VM |
671 | |
672 | stream->finished = 1; | |
673 | return git_filebuf_commit_at(&stream->fbuf, final_path); | |
674 | } | |
675 | ||
d568d585 | 676 | static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) |
7d7cd885 | 677 | { |
72a3fe42 VM |
678 | loose_writestream *stream = (loose_writestream *)_stream; |
679 | return git_filebuf_write(&stream->fbuf, data, len); | |
680 | } | |
681 | ||
d568d585 | 682 | static void loose_backend__stream_free(git_odb_stream *_stream) |
72a3fe42 VM |
683 | { |
684 | loose_writestream *stream = (loose_writestream *)_stream; | |
685 | ||
686 | if (!stream->finished) | |
687 | git_filebuf_cleanup(&stream->fbuf); | |
688 | ||
689 | free(stream); | |
690 | } | |
691 | ||
692 | static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) | |
693 | { | |
694 | const char *type_str = git_object_type2string(obj_type); | |
695 | int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); | |
696 | ||
87d9869f VM |
697 | assert(len > 0); /* otherwise snprintf() is broken */ |
698 | assert(((size_t) len) < n); /* otherwise the caller is broken! */ | |
72a3fe42 VM |
699 | |
700 | if (len < 0 || ((size_t) len) >= n) | |
dfb12cd5 | 701 | return git__throw(GIT_ERROR, "Failed to format object header. Length is out of bounds"); |
72a3fe42 VM |
702 | return len+1; |
703 | } | |
704 | ||
d568d585 | 705 | static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) |
72a3fe42 VM |
706 | { |
707 | loose_backend *backend; | |
708 | loose_writestream *stream; | |
709 | ||
f6f72d7e | 710 | char hdr[64], tmp_path[GIT_PATH_MAX]; |
87d9869f | 711 | int hdrlen; |
7d7cd885 | 712 | int error; |
7d7cd885 | 713 | |
72a3fe42 | 714 | assert(_backend); |
7d7cd885 VM |
715 | |
716 | backend = (loose_backend *)_backend; | |
72a3fe42 | 717 | *stream_out = NULL; |
7d7cd885 | 718 | |
72a3fe42 VM |
719 | hdrlen = format_object_header(hdr, sizeof(hdr), length, type); |
720 | if (hdrlen < GIT_SUCCESS) | |
dfb12cd5 | 721 | return git__throw(GIT_EOBJCORRUPTED, "Failed to create loose backend stream. Object is corrupted"); |
72a3fe42 VM |
722 | |
723 | stream = git__calloc(1, sizeof(loose_writestream)); | |
724 | if (stream == NULL) | |
725 | return GIT_ENOMEM; | |
7d7cd885 | 726 | |
72a3fe42 VM |
727 | stream->stream.backend = _backend; |
728 | stream->stream.read = NULL; /* read only */ | |
729 | stream->stream.write = &loose_backend__stream_write; | |
730 | stream->stream.finalize_write = &loose_backend__stream_fwrite; | |
731 | stream->stream.free = &loose_backend__stream_free; | |
732 | stream->stream.mode = GIT_STREAM_WRONLY; | |
7d7cd885 | 733 | |
f79026b4 | 734 | git_path_join(tmp_path, backend->objects_dir, "tmp_object"); |
f6f72d7e VM |
735 | |
736 | error = git_filebuf_open(&stream->fbuf, tmp_path, | |
72a3fe42 | 737 | GIT_FILEBUF_HASH_CONTENTS | |
c103d7b4 VM |
738 | GIT_FILEBUF_TEMPORARY | |
739 | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)); | |
72a3fe42 VM |
740 | |
741 | if (error < GIT_SUCCESS) { | |
742 | free(stream); | |
dfb12cd5 | 743 | return git__rethrow(error, "Failed to create loose backend stream"); |
72a3fe42 | 744 | } |
7d7cd885 | 745 | |
72a3fe42 VM |
746 | error = stream->stream.write((git_odb_stream *)stream, hdr, hdrlen); |
747 | if (error < GIT_SUCCESS) { | |
748 | git_filebuf_cleanup(&stream->fbuf); | |
749 | free(stream); | |
dfb12cd5 | 750 | return git__rethrow(error, "Failed to create loose backend stream"); |
72a3fe42 | 751 | } |
7d7cd885 | 752 | |
72a3fe42 VM |
753 | *stream_out = (git_odb_stream *)stream; |
754 | return GIT_SUCCESS; | |
7d7cd885 VM |
755 | } |
756 | ||
d568d585 | 757 | static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) |
afeecf4f VM |
758 | { |
759 | int error, header_len; | |
760 | char final_path[GIT_PATH_MAX], header[64]; | |
761 | git_filebuf fbuf; | |
762 | loose_backend *backend; | |
763 | ||
764 | backend = (loose_backend *)_backend; | |
765 | ||
766 | /* prepare the header for the file */ | |
767 | { | |
768 | header_len = format_object_header(header, sizeof(header), len, type); | |
769 | if (header_len < GIT_SUCCESS) | |
770 | return GIT_EOBJCORRUPTED; | |
771 | } | |
772 | ||
773 | git_path_join(final_path, backend->objects_dir, "tmp_object"); | |
774 | ||
775 | error = git_filebuf_open(&fbuf, final_path, | |
776 | GIT_FILEBUF_HASH_CONTENTS | | |
c103d7b4 VM |
777 | GIT_FILEBUF_TEMPORARY | |
778 | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)); | |
afeecf4f VM |
779 | |
780 | if (error < GIT_SUCCESS) | |
781 | return error; | |
782 | ||
783 | git_filebuf_write(&fbuf, header, header_len); | |
784 | git_filebuf_write(&fbuf, data, len); | |
785 | git_filebuf_hash(oid, &fbuf); | |
786 | ||
787 | if ((error = object_file_name(final_path, sizeof(final_path), backend->objects_dir, oid)) < GIT_SUCCESS) | |
788 | goto cleanup; | |
789 | ||
790 | if ((error = git_futils_mkpath2file(final_path)) < GIT_SUCCESS) | |
791 | goto cleanup; | |
792 | ||
793 | return git_filebuf_commit_at(&fbuf, final_path); | |
794 | ||
795 | cleanup: | |
796 | git_filebuf_cleanup(&fbuf); | |
797 | return error; | |
798 | } | |
799 | ||
d568d585 | 800 | static void loose_backend__free(git_odb_backend *_backend) |
7d7cd885 VM |
801 | { |
802 | loose_backend *backend; | |
803 | assert(_backend); | |
804 | backend = (loose_backend *)_backend; | |
805 | ||
806 | free(backend->objects_dir); | |
807 | free(backend); | |
808 | } | |
809 | ||
8af4d074 VM |
810 | int git_odb_backend_loose( |
811 | git_odb_backend **backend_out, | |
812 | const char *objects_dir, | |
813 | int compression_level, | |
814 | int do_fsync) | |
7d7cd885 VM |
815 | { |
816 | loose_backend *backend; | |
817 | ||
818 | backend = git__calloc(1, sizeof(loose_backend)); | |
819 | if (backend == NULL) | |
820 | return GIT_ENOMEM; | |
821 | ||
822 | backend->objects_dir = git__strdup(objects_dir); | |
823 | if (backend->objects_dir == NULL) { | |
824 | free(backend); | |
825 | return GIT_ENOMEM; | |
826 | } | |
827 | ||
8af4d074 VM |
828 | if (compression_level < 0) |
829 | compression_level = Z_BEST_SPEED; | |
830 | ||
831 | backend->object_zlib_level = compression_level; | |
832 | backend->fsync_object_files = do_fsync; | |
7d7cd885 VM |
833 | |
834 | backend->parent.read = &loose_backend__read; | |
afeecf4f | 835 | backend->parent.write = &loose_backend__write; |
d0323a5f | 836 | backend->parent.read_prefix = &loose_backend__read_prefix; |
7d7cd885 | 837 | backend->parent.read_header = &loose_backend__read_header; |
72a3fe42 | 838 | backend->parent.writestream = &loose_backend__stream; |
7d7cd885 VM |
839 | backend->parent.exists = &loose_backend__exists; |
840 | backend->parent.free = &loose_backend__free; | |
841 | ||
7d7cd885 VM |
842 | *backend_out = (git_odb_backend *)backend; |
843 | return GIT_SUCCESS; | |
844 | } |