path.c 22.2 KB
Newer Older
Vicent Marti committed
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
Vicent Marti committed
3 4 5 6
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
Vicent Marti committed
7
#include "common.h"
8 9
#include "path.h"
#include "posix.h"
10
#ifdef GIT_WIN32
11
#include "win32/posix.h"
12 13 14
#else
#include <dirent.h>
#endif
Vicent Marti committed
15 16 17
#include <stdio.h>
#include <ctype.h>

18 19
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')

20
#ifdef GIT_WIN32
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
static bool looks_like_network_computer_name(const char *path, int pos)
{
	if (pos < 3)
		return false;

	if (path[0] != '/' || path[1] != '/')
		return false;

	while (pos-- > 2) {
		if (path[pos] == '/')
			return false;
	}

	return true;
}
36
#endif
37

Vicent Marti committed
38 39
/*
 * Based on the Android implementation, BSD licensed.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
 * http://android.git.kernel.org/
 *
 * Copyright (C) 2008 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
Vicent Marti committed
67
 */
68
int git_path_basename_r(git_buf *buffer, const char *path)
Vicent Marti committed
69 70 71 72 73 74
{
	const char *endp, *startp;
	int len, result;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
Vicent Marti committed
75 76
		startp = ".";
		len		= 1;
Vicent Marti committed
77 78 79 80 81 82 83 84 85 86 87
		goto Exit;
	}

	/* Strip trailing slashes */
	endp = path + strlen(path) - 1;
	while (endp > path && *endp == '/')
		endp--;

	/* All slashes becomes "/" */
	if (endp == path && *endp == '/') {
		startp = "/";
Vicent Marti committed
88
		len	= 1;
Vicent Marti committed
89 90 91 92 93 94 95 96
		goto Exit;
	}

	/* Find the start of the base */
	startp = endp;
	while (startp > path && *(startp - 1) != '/')
		startp--;

97 98
	/* Cast is safe because max path < max int */
	len = (int)(endp - startp + 1);
Vicent Marti committed
99 100 101 102

Exit:
	result = len;

103 104
	if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
		return -1;
105

Vicent Marti committed
106 107 108 109 110 111 112
	return result;
}

/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
113
int git_path_dirname_r(git_buf *buffer, const char *path)
Vicent Marti committed
114
{
Vicent Marti committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
	const char *endp;
	int result, len;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
		path = ".";
		len = 1;
		goto Exit;
	}

	/* Strip trailing slashes */
	endp = path + strlen(path) - 1;
	while (endp > path && *endp == '/')
		endp--;

	/* Find the start of the dir */
	while (endp > path && *endp != '/')
		endp--;

	/* Either the dir is "/" or there are no slashes */
	if (endp == path) {
		path = (*endp == '/') ? "/" : ".";
		len = 1;
		goto Exit;
	}

	do {
		endp--;
	} while (endp > path && *endp == '/');

145 146
	/* Cast is safe because max path < max int */
	len = (int)(endp - path + 1);
Vicent Marti committed
147

Jerome Lambourg committed
148
#ifdef GIT_WIN32
Vicent Marti committed
149 150
	/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
		'C:/' here */
Jerome Lambourg committed
151

152
	if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
Vicent Marti committed
153 154 155
		len = 3;
		goto Exit;
	}
156 157 158 159 160 161 162 163 164

	/* Similarly checks if we're dealing with a network computer name
		'//computername/.git' will return '//computername/' */

	if (looks_like_network_computer_name(path, len)) {
		len++;
		goto Exit;
	}

Jerome Lambourg committed
165 166
#endif

Vicent Marti committed
167
Exit:
Vicent Marti committed
168 169
	result = len;

170 171
	if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
		return -1;
Vicent Marti committed
172 173

	return result;
Vicent Marti committed
174 175 176 177 178
}


char *git_path_dirname(const char *path)
{
179 180
	git_buf buf = GIT_BUF_INIT;
	char *dirname;
Vicent Marti committed
181

182 183 184
	git_path_dirname_r(&buf, path);
	dirname = git_buf_detach(&buf);
	git_buf_free(&buf); /* avoid memleak if error occurs */
Vicent Marti committed
185

186
	return dirname;
Vicent Marti committed
187 188 189 190
}

char *git_path_basename(const char *path)
{
191 192
	git_buf buf = GIT_BUF_INIT;
	char *basename;
Vicent Marti committed
193

194 195 196
	git_path_basename_r(&buf, path);
	basename = git_buf_detach(&buf);
	git_buf_free(&buf); /* avoid memleak if error occurs */
Vicent Marti committed
197

198
	return basename;
Vicent Marti committed
199 200
}

201 202 203 204 205 206 207 208 209 210 211 212 213 214
size_t git_path_basename_offset(git_buf *buffer)
{
	ssize_t slash;

	if (!buffer || buffer->size <= 0)
		return 0;

	slash = git_buf_rfind_next(buffer, '/');

	if (slash >= 0 && buffer->ptr[slash] == '/')
		return (size_t)(slash + 1);

	return 0;
}
Vicent Marti committed
215 216 217 218

const char *git_path_topdir(const char *path)
{
	size_t len;
219
	ssize_t i;
Vicent Marti committed
220 221 222 223 224 225 226

	assert(path);
	len = strlen(path);

	if (!len || path[len - 1] != '/')
		return NULL;

227
	for (i = (ssize_t)len - 2; i >= 0; --i)
Vicent Marti committed
228 229 230 231 232 233
		if (path[i] == '/')
			break;

	return &path[i + 1];
}

234 235 236 237 238
int git_path_root(const char *path)
{
	int offset = 0;

	/* Does the root of the path look like a windows drive ? */
239
	if (LOOKS_LIKE_DRIVE_PREFIX(path))
240
		offset += 2;
241

242
#ifdef GIT_WIN32
243
	/* Are we dealing with a windows network path? */
244 245
	else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') ||
		(path[0] == '\\' && path[1] == '\\' && path[2] != '\\'))
246
	{
247
		offset += 2;
248

249
		/* Skip the computer name segment */
250
		while (path[offset] && path[offset] != '/' && path[offset] != '\\')
251 252
			offset++;
	}
253 254
#endif

255
	if (path[offset] == '/' || path[offset] == '\\')
256 257
		return offset;

258
	return -1;	/* Not a real error - signals that path is not rooted */
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
int git_path_join_unrooted(
	git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
{
	int error, root;

	assert(path && path_out);

	root = git_path_root(path);

	if (base != NULL && root < 0) {
		error = git_buf_joinpath(path_out, base, path);

		if (root_at)
			*root_at = (ssize_t)strlen(base);
	}
	else {
		error = git_buf_sets(path_out, path);

		if (root_at)
			*root_at = (root < 0) ? 0 : (ssize_t)root;
	}

	return error;
}

286
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
287
{
288
	char buf[GIT_PATH_MAX];
289

290
	assert(path && path_out);
291 292 293

	/* construct path if needed */
	if (base != NULL && git_path_root(path) < 0) {
294 295
		if (git_buf_joinpath(path_out, base, path) < 0)
			return -1;
296 297 298
		path = path_out->ptr;
	}

299
	if (p_realpath(path, buf) == NULL) {
300 301
		/* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
		int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
302
		giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
303

304
		git_buf_clear(path_out);
305

306
		return error;
307
	}
308

309
	return git_buf_sets(path_out, buf);
310 311
}

312
int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
313
{
314
	int error = git_path_prettify(path_out, path, base);
315
	return (error < 0) ? error : git_path_to_dir(path_out);
316
}
317

318 319 320
int git_path_to_dir(git_buf *path)
{
	if (path->asize > 0 &&
nulltoken committed
321 322
		git_buf_len(path) > 0 &&
		path->ptr[git_buf_len(path) - 1] != '/')
323
		git_buf_putc(path, '/');
324

325
	return git_buf_oom(path) ? -1 : 0;
326
}
327 328 329 330 331 332 333 334 335 336 337

void git_path_string_to_dir(char* path, size_t size)
{
	size_t end = strlen(path);

	if (end && path[end - 1] != '/' && end < size) {
		path[end] = '/';
		path[end + 1] = '\0';
	}
}

338 339
int git__percent_decode(git_buf *decoded_out, const char *input)
{
340
	int len, hi, lo, i;
341 342
	assert(decoded_out && input);

343
	len = (int)strlen(input);
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
	git_buf_clear(decoded_out);

	for(i = 0; i < len; i++)
	{
		char c = input[i];

		if (c != '%')
			goto append;

		if (i >= len - 2)
			goto append;

		hi = git__fromhex(input[i + 1]);
		lo = git__fromhex(input[i + 2]);

		if (hi < 0 || lo < 0)
			goto append;

		c = (char)(hi << 4 | lo);
		i += 2;

append:
366 367
		if (git_buf_putc(decoded_out, c) < 0)
			return -1;
368 369
	}

370 371 372 373 374 375 376
	return 0;
}

static int error_invalid_local_file_uri(const char *uri)
{
	giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri);
	return -1;
377
}
nulltoken committed
378 379 380

int git_path_fromurl(git_buf *local_path_out, const char *file_url)
{
381
	int offset = 0, len;
nulltoken committed
382 383 384 385

	assert(local_path_out && file_url);

	if (git__prefixcmp(file_url, "file://") != 0)
386
		return error_invalid_local_file_uri(file_url);
nulltoken committed
387 388

	offset += 7;
389
	len = (int)strlen(file_url);
nulltoken committed
390 391 392 393 394 395

	if (offset < len && file_url[offset] == '/')
		offset++;
	else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0)
		offset += 10;
	else
396
		return error_invalid_local_file_uri(file_url);
nulltoken committed
397 398

	if (offset >= len || file_url[offset] == '/')
399
		return error_invalid_local_file_uri(file_url);
nulltoken committed
400

401
#ifndef GIT_WIN32
nulltoken committed
402 403 404 405 406
	offset--;	/* A *nix absolute path starts with a forward slash */
#endif

	git_buf_clear(local_path_out);

407
	return git__percent_decode(local_path_out, file_url + offset);
nulltoken committed
408
}
409 410 411 412 413 414 415

int git_path_walk_up(
	git_buf *path,
	const char *ceiling,
	int (*cb)(void *data, git_buf *),
	void *data)
{
416
	int error = 0;
417 418 419 420 421 422 423
	git_buf iter;
	ssize_t stop = 0, scan;
	char oldc = '\0';

	assert(path && cb);

	if (ceiling != NULL) {
424
		if (git__prefixcmp(path->ptr, ceiling) == 0)
425 426
			stop = (ssize_t)strlen(ceiling);
		else
nulltoken committed
427
			stop = git_buf_len(path);
428
	}
nulltoken committed
429
	scan = git_buf_len(path);
430 431

	iter.ptr = path->ptr;
nulltoken committed
432
	iter.size = git_buf_len(path);
433
	iter.asize = path->asize;
434 435

	while (scan >= stop) {
436
		error = cb(data, &iter);
437
		iter.ptr[scan] = oldc;
438 439

		if (error) {
440
			giterr_set_after_callback(error);
441
			break;
442
		}
443

444 445 446 447 448 449 450 451 452
		scan = git_buf_rfind_next(&iter, '/');
		if (scan >= 0) {
			scan++;
			oldc = iter.ptr[scan];
			iter.size = scan;
			iter.ptr[scan] = '\0';
		}
	}

453 454
	if (scan >= 0)
		iter.ptr[scan] = oldc;
455 456 457

	return error;
}
458

459
bool git_path_exists(const char *path)
460 461
{
	assert(path);
462
	return p_access(path, F_OK) == 0;
463 464
}

465
bool git_path_isdir(const char *path)
466 467
{
	struct stat st;
468 469
	if (p_stat(path, &st) < 0)
		return false;
470

471
	return S_ISDIR(st.st_mode) != 0;
472 473
}

474
bool git_path_isfile(const char *path)
475 476 477 478
{
	struct stat st;

	assert(path);
479 480
	if (p_stat(path, &st) < 0)
		return false;
481

482
	return S_ISREG(st.st_mode) != 0;
483 484
}

Ben Straub committed
485 486 487 488 489
#ifdef GIT_WIN32

bool git_path_is_empty_dir(const char *path)
{
	HANDLE hFind = INVALID_HANDLE_VALUE;
490
	git_win32_path wbuf;
491
	int wbufsz;
Ben Straub committed
492 493 494
	WIN32_FIND_DATAW ffd;
	bool retval = true;

495 496
	if (!git_path_isdir(path))
		return false;
Ben Straub committed
497

498 499 500 501
	wbufsz = git_win32_path_from_c(wbuf, path);
	if (!wbufsz || wbufsz + 2 > GIT_WIN_PATH_UTF16)
		return false;
	memcpy(&wbuf[wbufsz - 1], L"\\*", 3 * sizeof(wchar_t));
502

Ben Straub committed
503
	hFind = FindFirstFileW(wbuf, &ffd);
504
	if (INVALID_HANDLE_VALUE == hFind)
505 506 507
		return false;

	do {
508
		if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {
509
			retval = false;
510
			break;
511 512 513 514
		}
	} while (FindNextFileW(hFind, &ffd) != 0);

	FindClose(hFind);
Ben Straub committed
515 516 517 518 519
	return retval;
}

#else

520
static int path_found_entry(void *payload, git_buf *path)
Ben Straub committed
521
{
522 523 524
	GIT_UNUSED(payload);
	return !git_path_is_dot_or_dotdot(path->ptr);
}
Ben Straub committed
525

526 527 528 529
bool git_path_is_empty_dir(const char *path)
{
	int error;
	git_buf dir = GIT_BUF_INIT;
Ben Straub committed
530

531
	if (!git_path_isdir(path))
Ben Straub committed
532 533
		return false;

534 535 536
	if ((error = git_buf_sets(&dir, path)) != 0)
		giterr_clear();
	else
537
		error = git_path_direach(&dir, 0, path_found_entry, NULL);
Ben Straub committed
538

539 540 541
	git_buf_free(&dir);

	return !error;
Ben Straub committed
542
}
543

Ben Straub committed
544 545
#endif

546
int git_path_set_error(int errno_value, const char *path, const char *action)
547
{
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
	switch (errno_value) {
	case ENOENT:
	case ENOTDIR:
		giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action);
		return GIT_ENOTFOUND;

	case EINVAL:
	case ENAMETOOLONG:
		giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path);
		return GIT_EINVALIDSPEC;

	case EEXIST:
		giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
		return GIT_EEXISTS;

	default:
		giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
		return -1;
566
	}
567 568 569 570 571 572
}

int git_path_lstat(const char *path, struct stat *st)
{
	if (p_lstat(path, st) == 0)
		return 0;
573

574
	return git_path_set_error(errno, path, "stat");
575 576
}

577
static bool _check_dir_contents(
578 579
	git_buf *dir,
	const char *sub,
580
	bool (*predicate)(const char *))
581
{
582
	bool result;
nulltoken committed
583
	size_t dir_size = git_buf_len(dir);
584 585
	size_t sub_size = strlen(sub);

586
	/* leave base valid even if we could not make space for subdir */
587
	if (git_buf_try_grow(dir, dir_size + sub_size + 2, false, false) < 0)
588 589 590
		return false;

	/* save excursion */
591 592
	git_buf_joinpath(dir, dir->ptr, sub);

593
	result = predicate(dir->ptr);
594

595 596
	/* restore path */
	git_buf_truncate(dir, dir_size);
597
	return result;
598 599
}

600
bool git_path_contains(git_buf *dir, const char *item)
601
{
602
	return _check_dir_contents(dir, item, &git_path_exists);
603 604
}

605
bool git_path_contains_dir(git_buf *base, const char *subdir)
606
{
607
	return _check_dir_contents(base, subdir, &git_path_isdir);
608 609
}

610
bool git_path_contains_file(git_buf *base, const char *file)
611
{
612
	return _check_dir_contents(base, file, &git_path_isfile);
613 614 615 616
}

int git_path_find_dir(git_buf *dir, const char *path, const char *base)
{
617
	int error = git_path_join_unrooted(dir, path, base, NULL);
618

619
	if (!error) {
620 621 622 623 624 625
		char buf[GIT_PATH_MAX];
		if (p_realpath(dir->ptr, buf) != NULL)
			error = git_buf_sets(dir, buf);
	}

	/* call dirname if this is not a directory */
626
	if (!error) /* && git_path_isdir(dir->ptr) == false) */
627
		error = git_path_dirname_r(dir, dir->ptr);
628

629
	if (!error)
630 631 632 633 634
		error = git_path_to_dir(dir);

	return error;
}

635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
int git_path_resolve_relative(git_buf *path, size_t ceiling)
{
	char *base, *to, *from, *next;
	size_t len;

	if (!path || git_buf_oom(path))
		return -1;

	if (ceiling > path->size)
		ceiling = path->size;

	/* recognize drive prefixes, etc. that should not be backed over */
	if (ceiling == 0)
		ceiling = git_path_root(path->ptr) + 1;

	/* recognize URL prefixes that should not be backed over */
	if (ceiling == 0) {
		for (next = path->ptr; *next && git__isalpha(*next); ++next);
		if (next[0] == ':' && next[1] == '/' && next[2] == '/')
			ceiling = (next + 3) - path->ptr;
	}

	base = to = from = path->ptr + ceiling;

	while (*from) {
		for (next = from; *next && *next != '/'; ++next);

		len = next - from;

		if (len == 1 && from[0] == '.')
			/* do nothing with singleton dot */;

		else if (len == 2 && from[0] == '.' && from[1] == '.') {
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
			/* error out if trying to up one from a hard base */
			if (to == base && ceiling != 0) {
				giterr_set(GITERR_INVALID,
					"Cannot strip root component off url");
				return -1;
			}

			/* no more path segments to strip,
			 * use '../' as a new base path */
			if (to == base) {
				if (*next == '/')
					len++;

				if (to != from)
					memmove(to, from, len);

				to += len;
				/* this is now the base, can't back up from a
				 * relative prefix */
				base = to;
			} else {
				/* back up a path segment */
				while (to > base && to[-1] == '/') to--;
				while (to > base && to[-1] != '/') to--;
			}
		} else {
			if (*next == '/' && *from != '/')
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
				len++;

			if (to != from)
				memmove(to, from, len);

			to += len;
		}

		from += len;

		while (*from == '/') from++;
	}

	*to = '\0';

	path->size = to - path->ptr;

	return 0;
}

int git_path_apply_relative(git_buf *target, const char *relpath)
{
	git_buf_joinpath(target, git_buf_cstr(target), relpath);
	return git_path_resolve_relative(target, 0);
}

721 722
int git_path_cmp(
	const char *name1, size_t len1, int isdir1,
723 724
	const char *name2, size_t len2, int isdir2,
	int (*compare)(const char *, const char *, size_t))
725
{
726
	unsigned char c1, c2;
727
	size_t len = len1 < len2 ? len1 : len2;
728 729
	int cmp;

730
	cmp = compare(name1, name2, len);
731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
	if (cmp)
		return cmp;

	c1 = name1[len];
	c2 = name2[len];

	if (c1 == '\0' && isdir1)
		c1 = '/';

	if (c2 == '\0' && isdir2)
		c2 = '/';

	return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}

746
bool git_path_has_non_ascii(const char *path, size_t pathlen)
747 748 749 750 751 752 753 754 755 756
{
	const uint8_t *scan = (const uint8_t *)path, *end;

	for (end = scan + pathlen; scan < end; ++scan)
		if (*scan & 0x80)
			return true;

	return false;
}

757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
#ifdef GIT_USE_ICONV

int git_path_iconv_init_precompose(git_path_iconv_t *ic)
{
	git_buf_init(&ic->buf, 0);
	ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
	return 0;
}

void git_path_iconv_clear(git_path_iconv_t *ic)
{
	if (ic) {
		if (ic->map != (iconv_t)-1)
			iconv_close(ic->map);
		git_buf_free(&ic->buf);
	}
}

int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen)
776 777 778 779 780
{
	char *nfd = *in, *nfc;
	size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, rv;
	int retry = 1;

781 782
	if (!ic || ic->map == (iconv_t)-1 ||
		!git_path_has_non_ascii(*in, *inlen))
783 784 785
		return 0;

	while (1) {
786
		if (git_buf_grow(&ic->buf, wantlen + 1) < 0)
787 788
			return -1;

789 790
		nfc    = ic->buf.ptr   + ic->buf.size;
		nfclen = ic->buf.asize - ic->buf.size;
791

792
		rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
793

794
		ic->buf.size = (nfc - ic->buf.ptr);
795 796 797 798 799

		if (rv != (size_t)-1)
			break;

		if (errno != E2BIG)
800
			goto fail;
801 802 803 804

		/* make space for 2x the remaining data to be converted
		 * (with per retry overhead to avoid infinite loops)
		 */
805
		wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
806 807

		if (retry++ > 4)
808
			goto fail;
809 810
	}

811
	ic->buf.ptr[ic->buf.size] = '\0';
812

813 814
	*in    = ic->buf.ptr;
	*inlen = ic->buf.size;
815 816

	return 0;
817 818 819 820

fail:
	giterr_set(GITERR_OS, "Unable to convert unicode path data");
	return -1;
821
}
822

823 824 825 826 827 828 829 830
#endif

#if defined(__sun) || defined(__GNU__)
typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
#else
typedef struct dirent path_dirent_data;
#endif

831 832
int git_path_direach(
	git_buf *path,
833
	uint32_t flags,
834 835 836
	int (*fn)(void *, git_buf *),
	void *arg)
{
837
	int error = 0;
838 839
	ssize_t wd_len;
	DIR *dir;
840 841
	path_dirent_data de_data;
	struct dirent *de, *de_buf = (struct dirent *)&de_data;
842 843 844 845

	(void)flags;

#ifdef GIT_USE_ICONV
846
	git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
847
#endif
848

849
	if (git_path_to_dir(path) < 0)
850
		return -1;
851

nulltoken committed
852
	wd_len = git_buf_len(path);
853

854 855
	if ((dir = opendir(path->ptr)) == NULL) {
		giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr);
856 857 858
		if (errno == ENOENT)
			return GIT_ENOTFOUND;

859 860
		return -1;
	}
861

862
#ifdef GIT_USE_ICONV
863
	if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
864
		(void)git_path_iconv_init_precompose(&ic);
865
#endif
866 867

	while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
868 869
		char *de_path = de->d_name;
		size_t de_len = strlen(de_path);
870

871
		if (git_path_is_dot_or_dotdot(de_path))
872 873
			continue;

874 875 876 877
#ifdef GIT_USE_ICONV
		if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
			break;
#endif
878

879
		if ((error = git_buf_put(path, de_path, de_len)) < 0)
880 881
			break;

882
		error = fn(arg, path);
883 884 885

		git_buf_truncate(path, wd_len); /* restore path */

886
		if (error != 0) {
887
			giterr_set_after_callback(error);
888
			break;
889
		}
890 891 892
	}

	closedir(dir);
893 894

#ifdef GIT_USE_ICONV
895
	git_path_iconv_clear(&ic);
896
#endif
897 898

	return error;
899
}
900 901 902 903 904

int git_path_dirload(
	const char *path,
	size_t prefix_len,
	size_t alloc_extra,
905
	unsigned int flags,
906 907 908 909 910
	git_vector *contents)
{
	int error, need_slash;
	DIR *dir;
	size_t path_len;
911 912
	path_dirent_data de_data;
	struct dirent *de, *de_buf = (struct dirent *)&de_data;
913 914 915 916

	(void)flags;

#ifdef GIT_USE_ICONV
917
	git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
918
#endif
919 920

	assert(path && contents);
921 922 923

	path_len = strlen(path);

924 925 926 927
	if (!path_len || path_len < prefix_len) {
		giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path);
		return -1;
	}
928 929 930 931
	if ((dir = opendir(path)) == NULL) {
		giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
		return -1;
	}
932

933
#ifdef GIT_USE_ICONV
934
	if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
935
		(void)git_path_iconv_init_precompose(&ic);
936
#endif
937

938 939 940 941
	path += prefix_len;
	path_len -= prefix_len;
	need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;

942
	while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
943 944
		char *entry_path, *de_path = de->d_name;
		size_t alloc_size, de_len = strlen(de_path);
945

946
		if (git_path_is_dot_or_dotdot(de_path))
947 948
			continue;

949
#ifdef GIT_USE_ICONV
950
		if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
951
			break;
952
#endif
953

954 955 956 957
		alloc_size = path_len + need_slash + de_len + 1 + alloc_extra;
		if ((entry_path = git__calloc(alloc_size, 1)) == NULL) {
			error = -1;
			break;
958 959
		}

960 961 962 963
		if (path_len)
			memcpy(entry_path, path, path_len);
		if (need_slash)
			entry_path[path_len] = '/';
964
		memcpy(&entry_path[path_len + need_slash], de_path, de_len);
965

966 967
		if ((error = git_vector_insert(contents, entry_path)) < 0)
			break;
968 969 970
	}

	closedir(dir);
971 972

#ifdef GIT_USE_ICONV
973
	git_path_iconv_clear(&ic);
974
#endif
975

976 977
	if (error != 0)
		giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
978

979
	return error;
980 981
}

982 983 984
int git_path_with_stat_cmp(const void *a, const void *b)
{
	const git_path_with_stat *psa = a, *psb = b;
985 986 987 988 989 990 991
	return strcmp(psa->path, psb->path);
}

int git_path_with_stat_cmp_icase(const void *a, const void *b)
{
	const git_path_with_stat *psa = a, *psb = b;
	return strcasecmp(psa->path, psb->path);
992 993 994 995 996
}

int git_path_dirload_with_stat(
	const char *path,
	size_t prefix_len,
997
	unsigned int flags,
998 999
	const char *start_stat,
	const char *end_stat,
1000 1001 1002 1003 1004 1005
	git_vector *contents)
{
	int error;
	unsigned int i;
	git_path_with_stat *ps;
	git_buf full = GIT_BUF_INIT;
1006 1007 1008
	int (*strncomp)(const char *a, const char *b, size_t sz);
	size_t start_len = start_stat ? strlen(start_stat) : 0;
	size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len;
1009

1010 1011
	if (git_buf_set(&full, path, prefix_len) < 0)
		return -1;
1012

1013
	error = git_path_dirload(
1014
		path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents);
1015
	if (error < 0) {
1016 1017 1018 1019
		git_buf_free(&full);
		return error;
	}

1020
	strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
1021
		git__strncasecmp : git__strncmp;
1022 1023

	/* stat struct at start of git_path_with_stat, so shift path text */
1024 1025 1026 1027
	git_vector_foreach(contents, i, ps) {
		size_t path_len = strlen((char *)ps);
		memmove(ps->path, ps, path_len + 1);
		ps->path_len = path_len;
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
	}

	git_vector_foreach(contents, i, ps) {
		/* skip if before start_stat or after end_stat */
		cmp_len = min(start_len, ps->path_len);
		if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)
			continue;
		cmp_len = min(end_len, ps->path_len);
		if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
			continue;
1038

1039 1040
		git_buf_truncate(&full, prefix_len);

1041
		if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
1042 1043 1044 1045 1046 1047 1048 1049
			(error = git_path_lstat(full.ptr, &ps->st)) < 0) {
			if (error == GIT_ENOTFOUND) {
				giterr_clear();
				error = 0;
				git_vector_remove(contents, i--);
				continue;
			}

1050
			break;
1051
		}
1052

1053
		if (S_ISDIR(ps->st.st_mode)) {
1054 1055
			ps->path[ps->path_len++] = '/';
			ps->path[ps->path_len] = '\0';
1056 1057 1058
		}
	}

1059 1060 1061
	/* sort now that directory suffix is added */
	git_vector_sort(contents);

1062 1063
	git_buf_free(&full);

1064 1065
	return error;
}