odb_loose.c 21.6 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8
 */

#include "common.h"
9
#include <zlib.h>
10
#include "git2/object.h"
11
#include "git2/oid.h"
12 13 14 15
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
Vicent Marti committed
16
#include "filebuf.h"
17

18
#include "git2/odb_backend.h"
Vicent Marti committed
19
#include "git2/types.h"
20

Vicent Marti committed
21 22 23
typedef struct { /* object header data */
	git_otype type; /* object type */
	size_t	size; /* object size */
24 25
} obj_hdr;

Vicent Marti committed
26 27 28 29 30
typedef struct {
	git_odb_stream stream;
	git_filebuf fbuf;
} loose_writestream;

31 32 33 34 35 36 37 38
typedef struct loose_backend {
	git_odb_backend parent;

	int object_zlib_level; /** loose object zlib compression level. */
	int fsync_object_files; /** loose object file fsync flag. */
	char *objects_dir;
} loose_backend;

39 40 41 42 43 44
/* State structure for exploring directories,
 * in order to locate objects matching a short oid.
 */
typedef struct {
	size_t dir_len;
	unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */
45
	size_t short_oid_len;
46 47 48 49 50 51 52
	int found;				/* number of matching
						 * objects already found */
	unsigned char res_oid[GIT_OID_HEXSZ];	/* hex formatted oid of
						 * the object found */
} loose_locate_object_state;


53 54 55 56 57 58
/***********************************************************
 *
 * MISCELANEOUS HELPER FUNCTIONS
 *
 ***********************************************************/

59
static int object_file_name(git_buf *name, const char *dir, const git_oid *id)
60
{
61
	git_buf_sets(name, dir);
62

63
	/* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */
nulltoken committed
64
	if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0)
65
		return -1;
66

67
	git_path_to_dir(name);
68 69

	/* loose object filename: aa/aaa... (41 bytes) */
nulltoken committed
70
	git_oid_pathfmt(name->ptr + git_buf_len(name), id);
71 72
	name->size += GIT_OID_HEXSZ + 1;
	name->ptr[name->size] = '\0';
73

74
	return 0;
75 76 77
}


78
static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj)
79 80
{
	unsigned char c;
81
	unsigned char *data = (unsigned char *)obj->ptr;
82 83
	size_t shift, size, used = 0;

nulltoken committed
84
	if (git_buf_len(obj) == 0)
85 86 87 88 89 90 91 92
		return 0;

	c = data[used++];
	hdr->type = (c >> 4) & 7;

	size = c & 15;
	shift = 4;
	while (c & 0x80) {
nulltoken committed
93
		if (git_buf_len(obj) <= used)
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
			return 0;
		if (sizeof(size_t) * 8 <= shift)
			return 0;
		c = data[used++];
		size += (c & 0x7f) << shift;
		shift += 7;
	}
	hdr->size = size;

	return used;
}

static size_t get_object_header(obj_hdr *hdr, unsigned char *data)
{
	char c, typename[10];
	size_t size, used = 0;

	/*
	 * type name string followed by space.
	 */
	while ((c = data[used]) != ' ') {
		typename[used++] = c;
		if (used >= sizeof(typename))
			return 0;
	}
	typename[used] = 0;
	if (used == 0)
		return 0;
122
	hdr->type = git_object_string2type(typename);
Vicent Marti committed
123
	used++; /* consume the space */
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

	/*
	 * length follows immediately in decimal (without
	 * leading zeros).
	 */
	size = data[used++] - '0';
	if (size > 9)
		return 0;
	if (size) {
		while ((c = data[used]) != '\0') {
			size_t d = c - '0';
			if (d > 9)
				break;
			used++;
			size = size * 10 + d;
		}
	}
	hdr->size = size;

	/*
	 * the length must be followed by a zero byte
	 */
	if (data[used++] != '\0')
		return 0;

	return used;
}



/***********************************************************
 *
 * ZLIB RELATED FUNCTIONS
 *
 ***********************************************************/

static void init_stream(z_stream *s, void *out, size_t len)
{
	memset(s, 0, sizeof(*s));
Vicent Marti committed
163
	s->next_out = out;
164
	s->avail_out = (uInt)len;
165 166 167 168
}

static void set_stream_input(z_stream *s, void *in, size_t len)
{
Vicent Marti committed
169
	s->next_in = in;
170
	s->avail_in = (uInt)len;
171 172 173 174
}

static void set_stream_output(z_stream *s, void *out, size_t len)
{
Vicent Marti committed
175
	s->next_out = out;
176
	s->avail_out = (uInt)len;
177 178 179
}


180
static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len)
181 182 183 184
{
	int status;

	init_stream(s, out, len);
nulltoken committed
185
	set_stream_input(s, obj->ptr, git_buf_len(obj));
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

	if ((status = inflateInit(s)) < Z_OK)
		return status;

	return inflate(s, 0);
}

static int finish_inflate(z_stream *s)
{
	int status = Z_OK;

	while (status == Z_OK)
		status = inflate(s, Z_FINISH);

	inflateEnd(s);

202 203 204 205
	if ((status != Z_STREAM_END) || (s->avail_in != 0)) {
		giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely");
		return -1;
	}
206

207
	return 0;
208 209
}

Vicent Marti committed
210
static int is_zlib_compressed_data(unsigned char *data)
211
{
Vicent Marti committed
212
	unsigned int w;
213

Vicent Marti committed
214
	w = ((unsigned int)(data[0]) << 8) + data[1];
215
	return (data[0] & 0x8F) == 0x08 && !(w % 31);
216 217
}

Vicent Marti committed
218
static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen)
219 220
{
	z_stream zs;
Vicent Marti committed
221
	int status = Z_OK;
222

Vicent Marti committed
223
	memset(&zs, 0x0, sizeof(zs));
224

Vicent Marti committed
225
	zs.next_out = out;
226
	zs.avail_out = (uInt)outlen;
227

Vicent Marti committed
228
	zs.next_in = in;
229
	zs.avail_in = (uInt)inlen;
230

231 232 233 234
	if (inflateInit(&zs) < Z_OK) {
		giterr_set(GITERR_ZLIB, "Failed to inflate buffer");
		return -1;
	}
235

Vicent Marti committed
236 237
	while (status == Z_OK)
		status = inflate(&zs, Z_FINISH);
238

Vicent Marti committed
239
	inflateEnd(&zs);
240

241 242 243 244 245 246
	if (status != Z_STREAM_END /* || zs.avail_in != 0 */ ||
		zs.total_out != outlen)
	{
		giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely");
		return -1;
	}
247

248
	return 0;
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
}

static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr)
{
	unsigned char *buf, *head = hb;
	size_t tail;

	/*
	 * allocate a buffer to hold the inflated data and copy the
	 * initial sequence of inflated data from the tail of the
	 * head buffer, if any.
	 */
	if ((buf = git__malloc(hdr->size + 1)) == NULL) {
		inflateEnd(s);
		return NULL;
	}
	tail = s->total_out - used;
	if (used > 0 && tail > 0) {
		if (tail > hdr->size)
			tail = hdr->size;
		memcpy(buf, head + used, tail);
	}
	used = tail;

	/*
	 * inflate the remainder of the object data, if any
	 */
	if (hdr->size < used)
		inflateEnd(s);
	else {
		set_stream_output(s, buf + used, hdr->size - used);
		if (finish_inflate(s)) {
281
			git__free(buf);
282 283 284 285 286 287 288 289 290 291 292 293 294
			return NULL;
		}
	}

	return buf;
}

/*
 * At one point, there was a loose object format that was intended to
 * mimic the format used in pack-files. This was to allow easy copying
 * of loose object data into packs. This format is no longer used, but
 * we must still read it.
 */
295
static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj)
296 297 298 299 300 301 302 303 304
{
	unsigned char *in, *buf;
	obj_hdr hdr;
	size_t len, used;

	/*
	 * read the object header, which is an (uncompressed)
	 * binary encoding of the object type and size.
	 */
305 306 307 308 309
	if ((used = get_binary_object_header(&hdr, obj)) == 0 ||
		!git_object_typeisloose(hdr.type)) {
		giterr_set(GITERR_ODB, "Failed to inflate loose object.");
		return -1;
	}
310 311 312 313 314

	/*
	 * allocate a buffer and inflate the data into it
	 */
	buf = git__malloc(hdr.size + 1);
315
	GITERR_CHECK_ALLOC(buf);
316

317 318
	in = ((unsigned char *)obj->ptr) + used;
	len = obj->size - used;
319
	if (inflate_buffer(in, len, buf, hdr.size) < 0) {
320
		git__free(buf);
321
		return -1;
322 323 324 325
	}
	buf[hdr.size] = '\0';

	out->data = buf;
Vicent Marti committed
326
	out->len = hdr.size;
327 328
	out->type = hdr.type;

329
	return 0;
330 331
}

332
static int inflate_disk_obj(git_rawobj *out, git_buf *obj)
333 334 335 336 337 338 339 340 341
{
	unsigned char head[64], *buf;
	z_stream zs;
	obj_hdr hdr;
	size_t used;

	/*
	 * check for a pack-like loose object
	 */
342
	if (!is_zlib_compressed_data((unsigned char *)obj->ptr))
343 344 345 346 347 348
		return inflate_packlike_loose_disk_obj(out, obj);

	/*
	 * inflate the initial part of the io buffer in order
	 * to parse the object header (type and size).
	 */
349 350 351 352 353 354 355
	if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK ||
		(used = get_object_header(&hdr, head)) == 0 ||
		!git_object_typeisloose(hdr.type))
	{
		giterr_set(GITERR_ODB, "Failed to inflate disk object.");
		return -1;
	}
356 357 358 359 360 361

	/*
	 * allocate a buffer and inflate the object data into it
	 * (including the initial sequence in the head buffer).
	 */
	if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL)
362
		return -1;
363 364 365
	buf[hdr.size] = '\0';

	out->data = buf;
Vicent Marti committed
366
	out->len = hdr.size;
367 368
	out->type = hdr.type;

369
	return 0;
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
}






/***********************************************************
 *
 * ODB OBJECT READING & WRITING
 *
 * Backend for the public API; read headers and full objects
 * from the ODB. Write raw data to the ODB.
 *
 ***********************************************************/

386
static int read_loose(git_rawobj *out, git_buf *loc)
387 388
{
	int error;
389
	git_buf obj = GIT_BUF_INIT;
390 391 392

	assert(out && loc);

393
	if (git_buf_oom(loc))
394
		return -1;
395

396
	out->data = NULL;
Vicent Marti committed
397
	out->len = 0;
398 399
	out->type = GIT_OBJ_BAD;

400 401
	if (!(error = git_futils_readbuffer(&obj, loc->ptr)))
		error = inflate_disk_obj(out, &obj);
402

403
	git_buf_free(&obj);
404

405
	return error;
406 407
}

408
static int read_header_loose(git_rawobj *out, git_buf *loc)
409
{
410
	int error = 0, z_return = Z_ERRNO, read_bytes;
411 412 413 414 415 416 417
	git_file fd;
	z_stream zs;
	obj_hdr header_obj;
	unsigned char raw_buffer[16], inflated_buffer[64];

	assert(out && loc);

418
	if (git_buf_oom(loc))
419
		return -1;
420

421 422
	out->data = NULL;

423 424
	if ((fd = git_futils_open_ro(loc->ptr)) < 0)
		return fd;
425 426 427

	init_stream(&zs, inflated_buffer, sizeof(inflated_buffer));

428
	z_return = inflateInit(&zs);
429

430 431
	while (z_return == Z_OK) {
		if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) {
432 433
			set_stream_input(&zs, raw_buffer, read_bytes);
			z_return = inflate(&zs, 0);
434
		} else
435
			z_return = Z_STREAM_END;
436
	}
437 438 439

	if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR)
		|| get_object_header(&header_obj, inflated_buffer) == 0
440 441 442 443 444 445 446
		|| git_object_typeisloose(header_obj.type) == 0)
	{
		giterr_set(GITERR_ZLIB, "Failed to read loose object header");
		error = -1;
	} else {
		out->len = header_obj.size;
		out->type = header_obj.type;
447 448 449
	}

	finish_inflate(&zs);
Vicent Marti committed
450
	p_close(fd);
Vicent Marti committed
451

452
	return error;
453 454
}

455 456 457 458
static int locate_object(
	git_buf *object_location,
	loose_backend *backend,
	const git_oid *oid)
459
{
460 461
	int error = object_file_name(object_location, backend->objects_dir, oid);

462 463
	if (!error && !git_path_exists(object_location->ptr))
		return GIT_ENOTFOUND;
464 465

	return error;
466 467
}

468
/* Explore an entry of a directory and see if it matches a short oid */
469
static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) {
470 471
	loose_locate_object_state *sstate = (loose_locate_object_state *)state;

nulltoken committed
472
	if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) {
473
		/* Entry cannot be an object. Continue to next entry */
474
		return 0;
475 476
	}

477
	if (git_path_isdir(pathbuf->ptr) == false) {
478 479 480
		/* We are already in the directory matching the 2 first hex characters,
		 * compare the first ncmp characters of the oids */
		if (!memcmp(sstate->short_oid + 2,
481
			(unsigned char *)pathbuf->ptr + sstate->dir_len,
482 483
			sstate->short_oid_len - 2)) {

484 485 486
			if (!sstate->found) {
				sstate->res_oid[0] = sstate->short_oid[0];
				sstate->res_oid[1] = sstate->short_oid[1];
487
				memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2);
488 489 490 491
			}
			sstate->found++;
		}
	}
492

Vicent Marti committed
493
	if (sstate->found > 1)
494
		return git_odb__error_ambiguous("multiple matches in loose objects");
Vicent Marti committed
495

496
	return 0;
497 498 499
}

/* Locate an object matching a given short oid */
500 501 502 503 504
static int locate_object_short_oid(
	git_buf *object_location,
	git_oid *res_oid,
	loose_backend *backend,
	const git_oid *short_oid,
505
	size_t len)
506 507 508 509 510 511
{
	char *objects_dir = backend->objects_dir;
	size_t dir_len = strlen(objects_dir);
	loose_locate_object_state state;
	int error;

512
	/* prealloc memory for OBJ_DIR/xx/ */
513 514
	if (git_buf_grow(object_location, dir_len + 5) < 0)
		return -1;
515

516 517
	git_buf_sets(object_location, objects_dir);
	git_path_to_dir(object_location);
518

519
	/* save adjusted position at end of dir so it can be restored later */
nulltoken committed
520
	dir_len = git_buf_len(object_location);
521 522

	/* Convert raw oid to hex formatted oid */
Vicent Marti committed
523
	git_oid_fmt((char *)state.short_oid, short_oid);
524

525
	/* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */
526 527
	if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0)
		return -1;
528 529

	/* Check that directory exists */
530
	if (git_path_isdir(object_location->ptr) == false)
Russell Belfer committed
531
		return git_odb__error_notfound("no matching loose object for prefix", short_oid);
532

nulltoken committed
533
	state.dir_len = git_buf_len(object_location);
534 535
	state.short_oid_len = len;
	state.found = 0;
536

537
	/* Explore directory to find a unique object matching short_oid */
538 539
	error = git_path_direach(
		object_location, fn_locate_object_short_oid, &state);
540
	if (error)
541
		return error;
542

543
	if (!state.found)
Russell Belfer committed
544
		return git_odb__error_notfound("no matching loose object for prefix", short_oid);
545 546

	/* Convert obtained hex formatted oid to raw */
Vicent Marti committed
547
	error = git_oid_fromstr(res_oid, (char *)state.res_oid);
548 549
	if (error)
		return error;
550 551

	/* Update the location according to the oid obtained */
552 553

	git_buf_truncate(object_location, dir_len);
554 555
	if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0)
		return -1;
556 557 558 559 560

	git_oid_pathfmt(object_location->ptr + dir_len, res_oid);

	object_location->size += GIT_OID_HEXSZ + 1;
	object_location->ptr[object_location->size] = '\0';
561

562
	return 0;
563 564
}

565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580








/***********************************************************
 *
 * LOOSE BACKEND PUBLIC API
 *
 * Implement the git_odb_backend API calls
 *
 ***********************************************************/

581
static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
582
{
583
	git_buf object_path = GIT_BUF_INIT;
Vicent Marti committed
584
	git_rawobj raw;
585
	int error;
586

Vicent Marti committed
587
	assert(backend && oid);
588

Vicent Marti committed
589 590 591
	raw.len = 0;
	raw.type = GIT_OBJ_BAD;

592
	if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
Russell Belfer committed
593
		error = git_odb__error_notfound("no matching loose object", oid);
594
	else if ((error = read_header_loose(&raw, &object_path)) == 0) {
595 596 597
		*len_p = raw.len;
		*type_p = raw.type;
	}
598

599
	git_buf_free(&object_path);
600

601
	return error;
Vicent Marti committed
602
}
603

604
static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
605
{
606
	git_buf object_path = GIT_BUF_INIT;
Vicent Marti committed
607
	git_rawobj raw;
608
	int error = 0;
609

Vicent Marti committed
610
	assert(backend && oid);
611

612
	if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
Russell Belfer committed
613
		error = git_odb__error_notfound("no matching loose object", oid);
614
	else if ((error = read_loose(&raw, &object_path)) == 0) {
615 616 617 618
		*buffer_p = raw.data;
		*len_p = raw.len;
		*type_p = raw.type;
	}
Vicent Marti committed
619

620
	git_buf_free(&object_path);
Vicent Marti committed
621

622
	return error;
623 624
}

625
static int loose_backend__read_prefix(
Vicent Marti committed
626 627 628 629 630 631
	git_oid *out_oid,
	void **buffer_p,
	size_t *len_p,
	git_otype *type_p,
	git_odb_backend *backend,
	const git_oid *short_oid,
632
	size_t len)
633
{
634
	int error = 0;
635

636
	if (len < GIT_OID_MINPREFIXLEN)
637
		error = git_odb__error_ambiguous("prefix length too short");
638

639
	else if (len >= GIT_OID_HEXSZ) {
640
		/* We can fall back to regular read method */
641
		error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
642
		if (!error)
643
			git_oid_cpy(out_oid, short_oid);
644
	} else {
645
		git_buf object_path = GIT_BUF_INIT;
646 647 648 649
		git_rawobj raw;

		assert(backend && short_oid);

650
		if ((error = locate_object_short_oid(&object_path, out_oid,
651 652 653
				(loose_backend *)backend, short_oid, len)) == 0 &&
			(error = read_loose(&raw, &object_path)) == 0)
		{
654 655 656
			*buffer_p = raw.data;
			*len_p = raw.len;
			*type_p = raw.type;
657 658
		}

659
		git_buf_free(&object_path);
660
	}
661

662
	return error;
663 664
}

665
static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
666
{
667 668
	git_buf object_path = GIT_BUF_INIT;
	int error;
669 670 671

	assert(backend && oid);

672 673 674 675
	error = locate_object(&object_path, (loose_backend *)backend, oid);

	git_buf_free(&object_path);

676
	return !error;
677 678
}

679 680
struct foreach_state {
	size_t dir_len;
681
	git_odb_foreach_cb cb;
682
	void *data;
683
	int cb_error;
684 685
};

686
GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
{
	int v, i = 0;
	if (strlen(ptr) != 41)
		return -1;

	if (ptr[2] != '/') {
		return -1;
	}

	v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]);
	if (v < 0)
		return -1;

	oid->id[0] = (unsigned char) v;

	ptr += 3;
	for (i = 0; i < 38; i += 2) {
		v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]);
		if (v < 0)
			return -1;

		oid->id[1 + i/2] = (unsigned char) v;
	}

	return 0;
}

static int foreach_object_dir_cb(void *_state, git_buf *path)
{
	git_oid oid;
	struct foreach_state *state = (struct foreach_state *) _state;

	if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0)
		return 0;

722 723
	if (state->cb(&oid, state->data)) {
		state->cb_error = GIT_EUSER;
724
		return -1;
725
	}
726 727 728 729 730 731 732 733

	return 0;
}

static int foreach_cb(void *_state, git_buf *path)
{
	struct foreach_state *state = (struct foreach_state *) _state;

734
	return git_path_direach(path, foreach_object_dir_cb, state);
735 736
}

737
static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
738 739 740 741 742 743 744 745 746 747 748 749 750 751
{
	char *objects_dir;
	int error;
	git_buf buf = GIT_BUF_INIT;
	struct foreach_state state;
	loose_backend *backend = (loose_backend *) _backend;

	assert(backend && cb);

	objects_dir = backend->objects_dir;

	git_buf_sets(&buf, objects_dir);
	git_path_to_dir(&buf);

752
	memset(&state, 0, sizeof(state));
753 754 755 756 757
	state.cb = cb;
	state.data = data;
	state.dir_len = git_buf_len(&buf);

	error = git_path_direach(&buf, foreach_cb, &state);
758

759 760
	git_buf_free(&buf);

761
	return state.cb_error ? state.cb_error : error;
762 763
}

764
static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream)
Vicent Marti committed
765 766 767
{
	loose_writestream *stream = (loose_writestream *)_stream;
	loose_backend *backend = (loose_backend *)_stream->backend;
768
	git_buf final_path = GIT_BUF_INIT;
769
	int error = 0;
Vicent Marti committed
770

771 772 773 774
	if (git_filebuf_hash(oid, &stream->fbuf) < 0 ||
		object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
		git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0)
		error = -1;
775 776 777 778 779
	/*
	 * Don't try to add an existing object to the repository. This
	 * is what git does and allows us to sidestep the fact that
	 * we're not allowed to overwrite a read-only file on Windows.
	 */
780
	else if (git_path_exists(final_path.ptr) == true)
781
		git_filebuf_cleanup(&stream->fbuf);
782 783 784
	else
		error = git_filebuf_commit_at(
			&stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE);
785 786 787 788

	git_buf_free(&final_path);

	return error;
Vicent Marti committed
789 790
}

791
static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
792
{
Vicent Marti committed
793 794 795 796
	loose_writestream *stream = (loose_writestream *)_stream;
	return git_filebuf_write(&stream->fbuf, data, len);
}

797
static void loose_backend__stream_free(git_odb_stream *_stream)
Vicent Marti committed
798 799 800
{
	loose_writestream *stream = (loose_writestream *)_stream;

801
	git_filebuf_cleanup(&stream->fbuf);
802
	git__free(stream);
Vicent Marti committed
803 804 805 806 807 808 809
}

static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
{
	const char *type_str = git_object_type2string(obj_type);
	int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);

Vicent Marti committed
810
	assert(len > 0);				/* otherwise snprintf() is broken */
811
	assert(((size_t)len) < n);		/* otherwise the caller is broken! */
Vicent Marti committed
812 813 814 815

	return len+1;
}

816
static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type)
Vicent Marti committed
817 818
{
	loose_backend *backend;
819
	loose_writestream *stream = NULL;
820 821
	char hdr[64];
	git_buf tmp_path = GIT_BUF_INIT;
Vicent Marti committed
822
	int hdrlen;
823

Vicent Marti committed
824
	assert(_backend);
825 826

	backend = (loose_backend *)_backend;
Vicent Marti committed
827
	*stream_out = NULL;
828

Vicent Marti committed
829 830 831
	hdrlen = format_object_header(hdr, sizeof(hdr), length, type);

	stream = git__calloc(1, sizeof(loose_writestream));
832
	GITERR_CHECK_ALLOC(stream);
833

Vicent Marti committed
834 835 836 837 838 839
	stream->stream.backend = _backend;
	stream->stream.read = NULL; /* read only */
	stream->stream.write = &loose_backend__stream_write;
	stream->stream.finalize_write = &loose_backend__stream_fwrite;
	stream->stream.free = &loose_backend__stream_free;
	stream->stream.mode = GIT_STREAM_WRONLY;
840

841 842 843 844 845 846 847 848 849 850 851
	if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 ||
		git_filebuf_open(&stream->fbuf, tmp_path.ptr,
			GIT_FILEBUF_HASH_CONTENTS |
			GIT_FILEBUF_TEMPORARY |
			(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0 ||
		stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0)
	{
		git_filebuf_cleanup(&stream->fbuf);
		git__free(stream);
		stream = NULL;
	}
852
	git_buf_free(&tmp_path);
Vicent Marti committed
853
	*stream_out = (git_odb_stream *)stream;
854

855
	return !stream ? -1 : 0;
856 857
}

858
static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type)
859
{
860
	int error = 0, header_len;
861 862
	git_buf final_path = GIT_BUF_INIT;
	char header[64];
863
	git_filebuf fbuf = GIT_FILEBUF_INIT;
864 865 866 867 868
	loose_backend *backend;

	backend = (loose_backend *)_backend;

	/* prepare the header for the file */
869
	header_len = format_object_header(header, sizeof(header), len, type);
870

871 872 873 874 875 876
	if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 ||
		git_filebuf_open(&fbuf, final_path.ptr,
			GIT_FILEBUF_TEMPORARY |
			(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0)
	{
		error = -1;
877
		goto cleanup;
878
	}
879 880 881 882

	git_filebuf_write(&fbuf, header, header_len);
	git_filebuf_write(&fbuf, data, len);

883 884 885 886
	if (object_file_name(&final_path, backend->objects_dir, oid) < 0 ||
		git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 ||
		git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0)
		error = -1;
887 888

cleanup:
889
	if (error < 0)
890 891
		git_filebuf_cleanup(&fbuf);
	git_buf_free(&final_path);
892 893 894
	return error;
}

895
static void loose_backend__free(git_odb_backend *_backend)
896 897 898 899 900
{
	loose_backend *backend;
	assert(_backend);
	backend = (loose_backend *)_backend;

901 902
	git__free(backend->objects_dir);
	git__free(backend);
903 904
}

905 906 907 908 909
int git_odb_backend_loose(
	git_odb_backend **backend_out,
	const char *objects_dir,
	int compression_level,
	int do_fsync)
910 911 912 913
{
	loose_backend *backend;

	backend = git__calloc(1, sizeof(loose_backend));
914
	GITERR_CHECK_ALLOC(backend);
915

916
	backend->parent.version = GIT_ODB_BACKEND_VERSION;
917
	backend->objects_dir = git__strdup(objects_dir);
918
	GITERR_CHECK_ALLOC(backend->objects_dir);
919

920 921 922 923 924
	if (compression_level < 0)
		compression_level = Z_BEST_SPEED;

	backend->object_zlib_level = compression_level;
	backend->fsync_object_files = do_fsync;
925 926

	backend->parent.read = &loose_backend__read;
927
	backend->parent.write = &loose_backend__write;
Vicent Marti committed
928
	backend->parent.read_prefix = &loose_backend__read_prefix;
929
	backend->parent.read_header = &loose_backend__read_header;
Vicent Marti committed
930
	backend->parent.writestream = &loose_backend__stream;
931
	backend->parent.exists = &loose_backend__exists;
932
	backend->parent.foreach = &loose_backend__foreach;
933 934 935
	backend->parent.free = &loose_backend__free;

	*backend_out = (git_odb_backend *)backend;
936
	return 0;
937
}