odb_loose.c 22.3 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 759 760 761
	/* non-dir is some stray file, ignore it */
	if (!git_path_isdir(git_buf_cstr(path)))
		return 0;

762
	return git_path_direach(path, 0, foreach_object_dir_cb, state);
763 764
}

765
static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
766 767 768 769 770 771 772 773 774 775 776
{
	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;

777
	git_buf_sets(&buf, objects_dir);
778
	git_path_to_dir(&buf);
779 780
	if (git_buf_oom(&buf))
		return -1;
781

782
	memset(&state, 0, sizeof(state));
783 784 785 786
	state.cb = cb;
	state.data = data;
	state.dir_len = git_buf_len(&buf);

787
	error = git_path_direach(&buf, 0, foreach_cb, &state);
788

789 790
	git_buf_free(&buf);

791
	return error;
792 793
}

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

801
	if (object_file_name(&final_path, backend, oid) < 0 ||
802
		object_mkdir(&final_path, backend) < 0)
803 804 805
		error = -1;
	else
		error = git_filebuf_commit_at(
806
			&stream->fbuf, final_path.ptr);
807 808 809 810

	git_buf_free(&final_path);

	return error;
Vicent Marti committed
811 812
}

813
static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
814
{
Vicent Marti committed
815 816 817 818
	loose_writestream *stream = (loose_writestream *)_stream;
	return git_filebuf_write(&stream->fbuf, data, len);
}

819
static void loose_backend__stream_free(git_odb_stream *_stream)
Vicent Marti committed
820 821 822
{
	loose_writestream *stream = (loose_writestream *)_stream;

823
	git_filebuf_cleanup(&stream->fbuf);
824
	git__free(stream);
Vicent Marti committed
825 826
}

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

Vicent Marti committed
835
	assert(_backend);
836 837

	backend = (loose_backend *)_backend;
Vicent Marti committed
838
	*stream_out = NULL;
839

840
	hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type);
Vicent Marti committed
841 842

	stream = git__calloc(1, sizeof(loose_writestream));
843
	GITERR_CHECK_ALLOC(stream);
844

Vicent Marti committed
845 846 847 848 849 850
	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;
851

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

866
	return !stream ? -1 : 0;
867 868
}

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

	backend = (loose_backend *)_backend;

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

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

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

895 896
	if (object_file_name(&final_path, backend, oid) < 0 ||
		object_mkdir(&final_path, backend) < 0 ||
897
		git_filebuf_commit_at(&fbuf, final_path.ptr) < 0)
898
		error = -1;
899 900

cleanup:
901
	if (error < 0)
902 903
		git_filebuf_cleanup(&fbuf);
	git_buf_free(&final_path);
904 905 906
	return error;
}

907
static void loose_backend__free(git_odb_backend *_backend)
908 909 910 911 912
{
	loose_backend *backend;
	assert(_backend);
	backend = (loose_backend *)_backend;

913
	git__free(backend);
914 915
}

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

	assert(backend_out && objects_dir);

	objects_dirlen = strlen(objects_dir);
930

931
	backend = git__calloc(1, sizeof(loose_backend) + objects_dirlen + 2);
932
	GITERR_CHECK_ALLOC(backend);
933

934
	backend->parent.version = GIT_ODB_BACKEND_VERSION;
935 936 937 938
	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++] = '/';
939

940 941 942
	if (compression_level < 0)
		compression_level = Z_BEST_SPEED;

943 944 945 946 947 948
	if (dir_mode == 0)
		dir_mode = GIT_OBJECT_DIR_MODE;

	if (file_mode == 0)
		file_mode = GIT_OBJECT_FILE_MODE;

949 950
	backend->object_zlib_level = compression_level;
	backend->fsync_object_files = do_fsync;
951 952
	backend->object_dir_mode = dir_mode;
	backend->object_file_mode = file_mode;
953 954

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

	*backend_out = (git_odb_backend *)backend;
965
	return 0;
966
}