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