odb_loose.c 22.2 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/sys/odb_backend.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
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. */
36 37
	mode_t object_file_mode;
	mode_t object_dir_mode;
38 39 40

	size_t objects_dirlen;
	char objects_dir[GIT_FLEX_ARRAY];
41 42
} loose_backend;

43 44 45 46 47 48
/* 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 */
49
	size_t short_oid_len;
50 51 52 53 54 55 56
	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;


57 58 59 60 61 62
/***********************************************************
 *
 * MISCELANEOUS HELPER FUNCTIONS
 *
 ***********************************************************/

63 64
static int object_file_name(
	git_buf *name, const loose_backend *be, const git_oid *id)
65
{
66 67
	/* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */
	if (git_buf_grow(name, be->objects_dirlen + GIT_OID_HEXSZ + 3) < 0)
68
		return -1;
69

70
	git_buf_set(name, be->objects_dir, be->objects_dirlen);
71
	git_path_to_dir(name);
72 73

	/* loose object filename: aa/aaa... (41 bytes) */
74
	git_oid_pathfmt(name->ptr + name->size, id);
75 76
	name->size += GIT_OID_HEXSZ + 1;
	name->ptr[name->size] = '\0';
77

78
	return 0;
79 80
}

81 82 83
static int object_mkdir(const git_buf *name, const loose_backend *be)
{
	return git_futils_mkdir(
84
		name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode,
85 86
		GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
}
87

88
static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj)
89 90
{
	unsigned char c;
91
	unsigned char *data = (unsigned char *)obj->ptr;
92 93
	size_t shift, size, used = 0;

nulltoken committed
94
	if (git_buf_len(obj) == 0)
95 96 97 98 99 100 101 102
		return 0;

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

	size = c & 15;
	shift = 4;
	while (c & 0x80) {
nulltoken committed
103
		if (git_buf_len(obj) <= used)
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
			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;
132
	hdr->type = git_object_string2type(typename);
Vicent Marti committed
133
	used++; /* consume the space */
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 163 164 165 166 167 168 169 170 171 172

	/*
	 * 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
173
	s->next_out = out;
174
	s->avail_out = (uInt)len;
175 176 177 178
}

static void set_stream_input(z_stream *s, void *in, size_t len)
{
Vicent Marti committed
179
	s->next_in = in;
180
	s->avail_in = (uInt)len;
181 182 183 184
}

static void set_stream_output(z_stream *s, void *out, size_t len)
{
Vicent Marti committed
185
	s->next_out = out;
186
	s->avail_out = (uInt)len;
187 188 189
}


190
static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len)
191 192 193 194
{
	int status;

	init_stream(s, out, len);
nulltoken committed
195
	set_stream_input(s, obj->ptr, git_buf_len(obj));
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

	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);

212 213 214 215
	if ((status != Z_STREAM_END) || (s->avail_in != 0)) {
		giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely");
		return -1;
	}
216

217
	return 0;
218 219
}

Vicent Marti committed
220
static int is_zlib_compressed_data(unsigned char *data)
221
{
Vicent Marti committed
222
	unsigned int w;
223

Vicent Marti committed
224
	w = ((unsigned int)(data[0]) << 8) + data[1];
225
	return (data[0] & 0x8F) == 0x08 && !(w % 31);
226 227
}

Vicent Marti committed
228
static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen)
229 230
{
	z_stream zs;
Vicent Marti committed
231
	int status = Z_OK;
232

Vicent Marti committed
233
	memset(&zs, 0x0, sizeof(zs));
234

Vicent Marti committed
235
	zs.next_out = out;
236
	zs.avail_out = (uInt)outlen;
237

Vicent Marti committed
238
	zs.next_in = in;
239
	zs.avail_in = (uInt)inlen;
240

241 242 243 244
	if (inflateInit(&zs) < Z_OK) {
		giterr_set(GITERR_ZLIB, "Failed to inflate buffer");
		return -1;
	}
245

Vicent Marti committed
246 247
	while (status == Z_OK)
		status = inflate(&zs, Z_FINISH);
248

Vicent Marti committed
249
	inflateEnd(&zs);
250

251 252 253 254 255 256
	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;
	}
257

258
	return 0;
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
}

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)) {
291
			git__free(buf);
292 293 294 295 296 297 298 299 300 301 302 303 304
			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.
 */
305
static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj)
306 307 308 309 310 311 312 313 314
{
	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.
	 */
315 316 317 318 319
	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;
	}
320 321 322 323 324

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

327 328
	in = ((unsigned char *)obj->ptr) + used;
	len = obj->size - used;
329
	if (inflate_buffer(in, len, buf, hdr.size) < 0) {
330
		git__free(buf);
331
		return -1;
332 333 334 335
	}
	buf[hdr.size] = '\0';

	out->data = buf;
Vicent Marti committed
336
	out->len = hdr.size;
337 338
	out->type = hdr.type;

339
	return 0;
340 341
}

342
static int inflate_disk_obj(git_rawobj *out, git_buf *obj)
343 344 345 346 347 348 349 350 351
{
	unsigned char head[64], *buf;
	z_stream zs;
	obj_hdr hdr;
	size_t used;

	/*
	 * check for a pack-like loose object
	 */
352
	if (!is_zlib_compressed_data((unsigned char *)obj->ptr))
353 354 355 356 357 358
		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).
	 */
359 360 361 362 363 364 365
	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;
	}
366 367 368 369 370 371

	/*
	 * 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)
372
		return -1;
373 374 375
	buf[hdr.size] = '\0';

	out->data = buf;
Vicent Marti committed
376
	out->len = hdr.size;
377 378
	out->type = hdr.type;

379
	return 0;
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
}






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

396
static int read_loose(git_rawobj *out, git_buf *loc)
397 398
{
	int error;
399
	git_buf obj = GIT_BUF_INIT;
400 401 402

	assert(out && loc);

403
	if (git_buf_oom(loc))
404
		return -1;
405

406
	out->data = NULL;
Vicent Marti committed
407
	out->len = 0;
408 409
	out->type = GIT_OBJ_BAD;

410 411
	if (!(error = git_futils_readbuffer(&obj, loc->ptr)))
		error = inflate_disk_obj(out, &obj);
412

413
	git_buf_free(&obj);
414

415
	return error;
416 417
}

418
static int read_header_loose(git_rawobj *out, git_buf *loc)
419
{
420
	int error = 0, z_return = Z_ERRNO, read_bytes;
421 422 423 424 425 426 427
	git_file fd;
	z_stream zs;
	obj_hdr header_obj;
	unsigned char raw_buffer[16], inflated_buffer[64];

	assert(out && loc);

428
	if (git_buf_oom(loc))
429
		return -1;
430

431 432
	out->data = NULL;

433 434
	if ((fd = git_futils_open_ro(loc->ptr)) < 0)
		return fd;
435 436 437

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

438
	z_return = inflateInit(&zs);
439

440 441
	while (z_return == Z_OK) {
		if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) {
442 443
			set_stream_input(&zs, raw_buffer, read_bytes);
			z_return = inflate(&zs, 0);
444
		} else
445
			z_return = Z_STREAM_END;
446
	}
447 448 449

	if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR)
		|| get_object_header(&header_obj, inflated_buffer) == 0
450 451 452 453 454 455 456
		|| 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;
457 458 459
	}

	finish_inflate(&zs);
Vicent Marti committed
460
	p_close(fd);
Vicent Marti committed
461

462
	return error;
463 464
}

465 466 467 468
static int locate_object(
	git_buf *object_location,
	loose_backend *backend,
	const git_oid *oid)
469
{
470
	int error = object_file_name(object_location, backend, oid);
471

472 473
	if (!error && !git_path_exists(object_location->ptr))
		return GIT_ENOTFOUND;
474 475

	return error;
476 477
}

478
/* Explore an entry of a directory and see if it matches a short oid */
479
static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) {
480 481
	loose_locate_object_state *sstate = (loose_locate_object_state *)state;

nulltoken committed
482
	if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) {
483
		/* Entry cannot be an object. Continue to next entry */
484
		return 0;
485 486
	}

487
	if (git_path_isdir(pathbuf->ptr) == false) {
488 489 490
		/* 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,
491
			(unsigned char *)pathbuf->ptr + sstate->dir_len,
492 493
			sstate->short_oid_len - 2)) {

494 495 496
			if (!sstate->found) {
				sstate->res_oid[0] = sstate->short_oid[0];
				sstate->res_oid[1] = sstate->short_oid[1];
497
				memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2);
498 499 500 501
			}
			sstate->found++;
		}
	}
502

Vicent Marti committed
503
	if (sstate->found > 1)
504
		return GIT_EAMBIGUOUS;
Vicent Marti committed
505

506
	return 0;
507 508 509
}

/* Locate an object matching a given short oid */
510 511 512 513 514
static int locate_object_short_oid(
	git_buf *object_location,
	git_oid *res_oid,
	loose_backend *backend,
	const git_oid *short_oid,
515
	size_t len)
516 517 518 519 520 521
{
	char *objects_dir = backend->objects_dir;
	size_t dir_len = strlen(objects_dir);
	loose_locate_object_state state;
	int error;

522 523
	/* prealloc memory for OBJ_DIR/xx/xx..38x..xx */
	if (git_buf_grow(object_location, dir_len + 3 + GIT_OID_HEXSZ) < 0)
524
		return -1;
525

526
	git_buf_set(object_location, objects_dir, dir_len);
527
	git_path_to_dir(object_location);
528

529
	/* save adjusted position at end of dir so it can be restored later */
nulltoken committed
530
	dir_len = git_buf_len(object_location);
531 532

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

535
	/* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */
536
	if (git_buf_put(object_location, (char *)state.short_oid, 3) < 0)
537
		return -1;
538
	object_location->ptr[object_location->size - 1] = '/';
539 540

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

nulltoken committed
544
	state.dir_len = git_buf_len(object_location);
545 546
	state.short_oid_len = len;
	state.found = 0;
547

548
	/* Explore directory to find a unique object matching short_oid */
549
	error = git_path_direach(
550
		object_location, 0, fn_locate_object_short_oid, &state);
551
	if (error < 0 && error != GIT_EAMBIGUOUS)
552
		return error;
553

554
	if (!state.found)
Russell Belfer committed
555
		return git_odb__error_notfound("no matching loose object for prefix", short_oid);
556

557 558 559
	if (state.found > 1)
		return git_odb__error_ambiguous("multiple matches in loose objects");

560
	/* Convert obtained hex formatted oid to raw */
Vicent Marti committed
561
	error = git_oid_fromstr(res_oid, (char *)state.res_oid);
562 563
	if (error)
		return error;
564 565

	/* Update the location according to the oid obtained */
566 567

	git_buf_truncate(object_location, dir_len);
568 569
	if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0)
		return -1;
570 571 572 573 574

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

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

576
	return 0;
577 578
}

579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594








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

595
static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
596
{
597
	git_buf object_path = GIT_BUF_INIT;
Vicent Marti committed
598
	git_rawobj raw;
599
	int error;
600

Vicent Marti committed
601
	assert(backend && oid);
602

Vicent Marti committed
603 604 605
	raw.len = 0;
	raw.type = GIT_OBJ_BAD;

606
	if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
Russell Belfer committed
607
		error = git_odb__error_notfound("no matching loose object", oid);
608
	else if ((error = read_header_loose(&raw, &object_path)) == 0) {
609 610 611
		*len_p = raw.len;
		*type_p = raw.type;
	}
612

613
	git_buf_free(&object_path);
614

615
	return error;
Vicent Marti committed
616
}
617

618
static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
619
{
620
	git_buf object_path = GIT_BUF_INIT;
Vicent Marti committed
621
	git_rawobj raw;
622
	int error = 0;
623

Vicent Marti committed
624
	assert(backend && oid);
625

626
	if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
Russell Belfer committed
627
		error = git_odb__error_notfound("no matching loose object", oid);
628
	else if ((error = read_loose(&raw, &object_path)) == 0) {
629 630 631 632
		*buffer_p = raw.data;
		*len_p = raw.len;
		*type_p = raw.type;
	}
Vicent Marti committed
633

634
	git_buf_free(&object_path);
Vicent Marti committed
635

636
	return error;
637 638
}

639
static int loose_backend__read_prefix(
Vicent Marti committed
640 641 642 643 644 645
	git_oid *out_oid,
	void **buffer_p,
	size_t *len_p,
	git_otype *type_p,
	git_odb_backend *backend,
	const git_oid *short_oid,
646
	size_t len)
647
{
648
	int error = 0;
649

650
	assert(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ);
651

652
	if (len == GIT_OID_HEXSZ) {
653
		/* We can fall back to regular read method */
654
		error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
655
		if (!error)
656
			git_oid_cpy(out_oid, short_oid);
657
	} else {
658
		git_buf object_path = GIT_BUF_INIT;
659 660 661 662
		git_rawobj raw;

		assert(backend && short_oid);

663
		if ((error = locate_object_short_oid(&object_path, out_oid,
664 665 666
				(loose_backend *)backend, short_oid, len)) == 0 &&
			(error = read_loose(&raw, &object_path)) == 0)
		{
667 668 669
			*buffer_p = raw.data;
			*len_p = raw.len;
			*type_p = raw.type;
670 671
		}

672
		git_buf_free(&object_path);
673
	}
674

675
	return error;
676 677
}

678
static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
679
{
680 681
	git_buf object_path = GIT_BUF_INIT;
	int error;
682 683 684

	assert(backend && oid);

685 686 687 688
	error = locate_object(&object_path, (loose_backend *)backend, oid);

	git_buf_free(&object_path);

689
	return !error;
690 691
}

692 693 694 695 696 697
static int loose_backend__exists_prefix(
	git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
{
	git_buf object_path = GIT_BUF_INIT;
	int error;

698
	assert(backend && out && short_id && len >= GIT_OID_MINPREFIXLEN);
699 700 701 702 703 704 705 706 707

	error = locate_object_short_oid(
		&object_path, out, (loose_backend *)backend, short_id, len);

	git_buf_free(&object_path);

	return error;
}

708 709
struct foreach_state {
	size_t dir_len;
710
	git_odb_foreach_cb cb;
711 712 713
	void *data;
};

714
GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
{
	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;

750
	return giterr_set_after_callback_function(
751
		state->cb(&oid, state->data), "git_odb_foreach");
752 753 754 755 756 757
}

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

758
	return git_path_direach(path, 0, foreach_object_dir_cb, state);
759 760
}

761
static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
762 763 764 765 766 767 768 769 770 771 772
{
	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;

773
	git_buf_sets(&buf, objects_dir);
774
	git_path_to_dir(&buf);
775 776
	if (git_buf_oom(&buf))
		return -1;
777

778
	memset(&state, 0, sizeof(state));
779 780 781 782
	state.cb = cb;
	state.data = data;
	state.dir_len = git_buf_len(&buf);

783
	error = git_path_direach(&buf, 0, foreach_cb, &state);
784

785 786
	git_buf_free(&buf);

787
	return error;
788 789
}

790
static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *oid)
Vicent Marti committed
791 792 793
{
	loose_writestream *stream = (loose_writestream *)_stream;
	loose_backend *backend = (loose_backend *)_stream->backend;
794
	git_buf final_path = GIT_BUF_INIT;
795
	int error = 0;
Vicent Marti committed
796

797
	if (object_file_name(&final_path, backend, oid) < 0 ||
798
		object_mkdir(&final_path, backend) < 0)
799 800 801
		error = -1;
	else
		error = git_filebuf_commit_at(
802
			&stream->fbuf, final_path.ptr);
803 804 805 806

	git_buf_free(&final_path);

	return error;
Vicent Marti committed
807 808
}

809
static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
810
{
Vicent Marti committed
811 812 813 814
	loose_writestream *stream = (loose_writestream *)_stream;
	return git_filebuf_write(&stream->fbuf, data, len);
}

815
static void loose_backend__stream_free(git_odb_stream *_stream)
Vicent Marti committed
816 817 818
{
	loose_writestream *stream = (loose_writestream *)_stream;

819
	git_filebuf_cleanup(&stream->fbuf);
820
	git__free(stream);
Vicent Marti committed
821 822
}

823
static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type)
Vicent Marti committed
824 825
{
	loose_backend *backend;
826
	loose_writestream *stream = NULL;
827 828
	char hdr[64];
	git_buf tmp_path = GIT_BUF_INIT;
Vicent Marti committed
829
	int hdrlen;
830

Vicent Marti committed
831
	assert(_backend);
832 833

	backend = (loose_backend *)_backend;
Vicent Marti committed
834
	*stream_out = NULL;
835

836
	hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type);
Vicent Marti committed
837 838

	stream = git__calloc(1, sizeof(loose_writestream));
839
	GITERR_CHECK_ALLOC(stream);
840

Vicent Marti committed
841 842 843 844 845 846
	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;
847

848 849 850
	if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 ||
		git_filebuf_open(&stream->fbuf, tmp_path.ptr,
			GIT_FILEBUF_TEMPORARY |
851 852
			(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT),
			backend->object_file_mode) < 0 ||
853 854 855 856 857 858
		stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0)
	{
		git_filebuf_cleanup(&stream->fbuf);
		git__free(stream);
		stream = NULL;
	}
859
	git_buf_free(&tmp_path);
Vicent Marti committed
860
	*stream_out = (git_odb_stream *)stream;
861

862
	return !stream ? -1 : 0;
863 864
}

865
static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type)
866
{
867
	int error = 0, header_len;
868 869
	git_buf final_path = GIT_BUF_INIT;
	char header[64];
870
	git_filebuf fbuf = GIT_FILEBUF_INIT;
871 872 873 874 875
	loose_backend *backend;

	backend = (loose_backend *)_backend;

	/* prepare the header for the file */
876
	header_len = git_odb__format_object_header(header, sizeof(header), len, type);
877

878 879 880
	if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 ||
		git_filebuf_open(&fbuf, final_path.ptr,
			GIT_FILEBUF_TEMPORARY |
881 882
			(backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT),
			backend->object_file_mode) < 0)
883 884
	{
		error = -1;
885
		goto cleanup;
886
	}
887 888 889 890

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

891 892
	if (object_file_name(&final_path, backend, oid) < 0 ||
		object_mkdir(&final_path, backend) < 0 ||
893
		git_filebuf_commit_at(&fbuf, final_path.ptr) < 0)
894
		error = -1;
895 896

cleanup:
897
	if (error < 0)
898 899
		git_filebuf_cleanup(&fbuf);
	git_buf_free(&final_path);
900 901 902
	return error;
}

903
static void loose_backend__free(git_odb_backend *_backend)
904 905 906 907 908
{
	loose_backend *backend;
	assert(_backend);
	backend = (loose_backend *)_backend;

909
	git__free(backend);
910 911
}

912 913 914 915
int git_odb_backend_loose(
	git_odb_backend **backend_out,
	const char *objects_dir,
	int compression_level,
916
	int do_fsync,
917 918
	unsigned int dir_mode,
	unsigned int file_mode)
919 920
{
	loose_backend *backend;
921 922 923 924 925
	size_t objects_dirlen;

	assert(backend_out && objects_dir);

	objects_dirlen = strlen(objects_dir);
926

927
	backend = git__calloc(1, sizeof(loose_backend) + objects_dirlen + 2);
928
	GITERR_CHECK_ALLOC(backend);
929

930
	backend->parent.version = GIT_ODB_BACKEND_VERSION;
931 932 933 934
	backend->objects_dirlen = objects_dirlen;
	memcpy(backend->objects_dir, objects_dir, objects_dirlen);
	if (backend->objects_dir[backend->objects_dirlen - 1] != '/')
		backend->objects_dir[backend->objects_dirlen++] = '/';
935

936 937 938
	if (compression_level < 0)
		compression_level = Z_BEST_SPEED;

939 940 941 942 943 944
	if (dir_mode == 0)
		dir_mode = GIT_OBJECT_DIR_MODE;

	if (file_mode == 0)
		file_mode = GIT_OBJECT_FILE_MODE;

945 946
	backend->object_zlib_level = compression_level;
	backend->fsync_object_files = do_fsync;
947 948
	backend->object_dir_mode = dir_mode;
	backend->object_file_mode = file_mode;
949 950

	backend->parent.read = &loose_backend__read;
951
	backend->parent.write = &loose_backend__write;
Vicent Marti committed
952
	backend->parent.read_prefix = &loose_backend__read_prefix;
953
	backend->parent.read_header = &loose_backend__read_header;
Vicent Marti committed
954
	backend->parent.writestream = &loose_backend__stream;
955
	backend->parent.exists = &loose_backend__exists;
956
	backend->parent.exists_prefix = &loose_backend__exists_prefix;
957
	backend->parent.foreach = &loose_backend__foreach;
958 959 960
	backend->parent.free = &loose_backend__free;

	*backend_out = (git_odb_backend *)backend;
961
	return 0;
962
}