path.c 13 KB
Newer Older
Vicent Marti committed
1
/*
schu committed
2
 * Copyright (C) 2009-2012 the libgit2 contributors
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 11
#ifdef GIT_WIN32
#include "win32/dir.h"
12
#include "win32/posix.h"
13 14 15
#else
#include <dirent.h>
#endif
Vicent Marti committed
16 17 18 19
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>

20 21 22 23
#ifdef GIT_WIN32
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
#endif

Vicent Marti committed
24 25 26 27
/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
28
int git_path_basename_r(git_buf *buffer, const char *path)
Vicent Marti committed
29 30 31 32 33 34
{
	const char *endp, *startp;
	int len, result;

	/* Empty or NULL string gets treated as "." */
	if (path == NULL || *path == '\0') {
Vicent Marti committed
35 36
		startp = ".";
		len		= 1;
Vicent Marti committed
37 38 39 40 41 42 43 44 45 46 47
		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
48
		len	= 1;
Vicent Marti committed
49 50 51 52 53 54 55 56
		goto Exit;
	}

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

57 58
	/* Cast is safe because max path < max int */
	len = (int)(endp - startp + 1);
Vicent Marti committed
59 60 61 62

Exit:
	result = len;

63 64
	if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
		return -1;
65

Vicent Marti committed
66 67 68 69 70 71 72
	return result;
}

/*
 * Based on the Android implementation, BSD licensed.
 * Check http://android.git.kernel.org/
 */
73
int git_path_dirname_r(git_buf *buffer, const char *path)
Vicent Marti committed
74
{
Vicent Marti committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
	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 == '/');

105 106
	/* Cast is safe because max path < max int */
	len = (int)(endp - path + 1);
Vicent Marti committed
107

Jerome Lambourg committed
108
#ifdef GIT_WIN32
Vicent Marti committed
109 110
	/* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
		'C:/' here */
Jerome Lambourg committed
111

112
	if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
Vicent Marti committed
113 114 115
		len = 3;
		goto Exit;
	}
Jerome Lambourg committed
116 117
#endif

Vicent Marti committed
118
Exit:
Vicent Marti committed
119 120
	result = len;

121 122
	if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
		return -1;
Vicent Marti committed
123 124

	return result;
Vicent Marti committed
125 126 127 128 129
}


char *git_path_dirname(const char *path)
{
130 131
	git_buf buf = GIT_BUF_INIT;
	char *dirname;
Vicent Marti committed
132

133 134 135
	git_path_dirname_r(&buf, path);
	dirname = git_buf_detach(&buf);
	git_buf_free(&buf); /* avoid memleak if error occurs */
Vicent Marti committed
136

137
	return dirname;
Vicent Marti committed
138 139 140 141
}

char *git_path_basename(const char *path)
{
142 143
	git_buf buf = GIT_BUF_INIT;
	char *basename;
Vicent Marti committed
144

145 146 147
	git_path_basename_r(&buf, path);
	basename = git_buf_detach(&buf);
	git_buf_free(&buf); /* avoid memleak if error occurs */
Vicent Marti committed
148

149
	return basename;
Vicent Marti committed
150 151 152 153 154 155
}


const char *git_path_topdir(const char *path)
{
	size_t len;
156
	ssize_t i;
Vicent Marti committed
157 158 159 160 161 162 163

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

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

164
	for (i = (ssize_t)len - 2; i >= 0; --i)
Vicent Marti committed
165 166 167 168 169 170
		if (path[i] == '/')
			break;

	return &path[i + 1];
}

171 172 173 174 175 176
int git_path_root(const char *path)
{
	int offset = 0;

#ifdef GIT_WIN32
	/* Does the root of the path look like a windows drive ? */
177
	if (LOOKS_LIKE_DRIVE_PREFIX(path))
178
		offset += 2;
179

180 181 182 183
	/* Are we dealing with a windows network path? */
	else if ((path[0] == '/' && path[1] == '/') ||
		(path[0] == '\\' && path[1] == '\\'))
	{
184
		offset += 2;
185

186
		/* Skip the computer name segment */
187
		while (path[offset] && path[offset] != '/' && path[offset] != '\\')
188 189
			offset++;
	}
190 191
#endif

192
	if (path[offset] == '/' || path[offset] == '\\')
193 194
		return offset;

195
	return -1;	/* Not a real error - signals that path is not rooted */
196 197
}

198
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
199
{
200
	char buf[GIT_PATH_MAX];
201

202
	assert(path && path_out);
203 204 205

	/* construct path if needed */
	if (base != NULL && git_path_root(path) < 0) {
206 207
		if (git_buf_joinpath(path_out, base, path) < 0)
			return -1;
208 209 210
		path = path_out->ptr;
	}

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

216
		git_buf_clear(path_out);
217

218
		return error;
219
	}
220

221
	return git_buf_sets(path_out, buf);
222 223
}

224
int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
225
{
226
	int error = git_path_prettify(path_out, path, base);
227
	return (error < 0) ? error : git_path_to_dir(path_out);
228
}
229

230 231 232
int git_path_to_dir(git_buf *path)
{
	if (path->asize > 0 &&
nulltoken committed
233 234
		git_buf_len(path) > 0 &&
		path->ptr[git_buf_len(path) - 1] != '/')
235
		git_buf_putc(path, '/');
236

237
	return git_buf_oom(path) ? -1 : 0;
238
}
239 240 241 242 243 244 245 246 247 248 249

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';
	}
}

250 251
int git__percent_decode(git_buf *decoded_out, const char *input)
{
252
	int len, hi, lo, i;
253 254
	assert(decoded_out && input);

255
	len = (int)strlen(input);
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	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:
278 279
		if (git_buf_putc(decoded_out, c) < 0)
			return -1;
280 281
	}

282 283 284 285 286 287 288
	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;
289
}
nulltoken committed
290 291 292

int git_path_fromurl(git_buf *local_path_out, const char *file_url)
{
293
	int offset = 0, len;
nulltoken committed
294 295 296 297

	assert(local_path_out && file_url);

	if (git__prefixcmp(file_url, "file://") != 0)
298
		return error_invalid_local_file_uri(file_url);
nulltoken committed
299 300

	offset += 7;
301
	len = (int)strlen(file_url);
nulltoken committed
302 303 304 305 306 307

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

	if (offset >= len || file_url[offset] == '/')
311
		return error_invalid_local_file_uri(file_url);
nulltoken committed
312 313 314 315 316 317 318

#ifndef _MSC_VER
	offset--;	/* A *nix absolute path starts with a forward slash */
#endif

	git_buf_clear(local_path_out);

319
	return git__percent_decode(local_path_out, file_url + offset);
nulltoken committed
320
}
321 322 323 324 325 326 327

int git_path_walk_up(
	git_buf *path,
	const char *ceiling,
	int (*cb)(void *data, git_buf *),
	void *data)
{
328
	int error = 0;
329 330 331 332 333 334 335
	git_buf iter;
	ssize_t stop = 0, scan;
	char oldc = '\0';

	assert(path && cb);

	if (ceiling != NULL) {
336
		if (git__prefixcmp(path->ptr, ceiling) == 0)
337 338
			stop = (ssize_t)strlen(ceiling);
		else
nulltoken committed
339
			stop = git_buf_len(path);
340
	}
nulltoken committed
341
	scan = git_buf_len(path);
342 343

	iter.ptr = path->ptr;
nulltoken committed
344
	iter.size = git_buf_len(path);
345
	iter.asize = path->asize;
346 347

	while (scan >= stop) {
348
		if ((error = cb(data, &iter)) < 0)
349 350 351 352 353 354 355 356 357 358 359
			break;
		iter.ptr[scan] = oldc;
		scan = git_buf_rfind_next(&iter, '/');
		if (scan >= 0) {
			scan++;
			oldc = iter.ptr[scan];
			iter.size = scan;
			iter.ptr[scan] = '\0';
		}
	}

360 361
	if (scan >= 0)
		iter.ptr[scan] = oldc;
362 363 364

	return error;
}
365

366
bool git_path_exists(const char *path)
367 368
{
	assert(path);
369
	return p_access(path, F_OK) == 0;
370 371
}

372
bool git_path_isdir(const char *path)
373 374
{
	struct stat st;
375 376
	if (p_stat(path, &st) < 0)
		return false;
377

378
	return S_ISDIR(st.st_mode) != 0;
379 380
}

381
bool git_path_isfile(const char *path)
382 383 384 385
{
	struct stat st;

	assert(path);
386 387
	if (p_stat(path, &st) < 0)
		return false;
388

389
	return S_ISREG(st.st_mode) != 0;
390 391
}

392 393 394
int git_path_lstat(const char *path, struct stat *st)
{
	int err = 0;
395

396 397 398 399 400 401
	if (p_lstat(path, st) < 0) {
		err = (errno == ENOENT) ? GIT_ENOTFOUND : -1;
		giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
	}

	return err;
402 403
}

404
static bool _check_dir_contents(
405 406
	git_buf *dir,
	const char *sub,
407
	bool (*predicate)(const char *))
408
{
409
	bool result;
nulltoken committed
410
	size_t dir_size = git_buf_len(dir);
411 412
	size_t sub_size = strlen(sub);

413 414 415 416 417
	/* leave base valid even if we could not make space for subdir */
	if (git_buf_try_grow(dir, dir_size + sub_size + 2) < 0)
		return false;

	/* save excursion */
418 419
	git_buf_joinpath(dir, dir->ptr, sub);

420
	result = predicate(dir->ptr);
421

422 423
	/* restore path */
	git_buf_truncate(dir, dir_size);
424
	return result;
425 426
}

427
bool git_path_contains(git_buf *dir, const char *item)
428
{
429
	return _check_dir_contents(dir, item, &git_path_exists);
430 431
}

432
bool git_path_contains_dir(git_buf *base, const char *subdir)
433
{
434
	return _check_dir_contents(base, subdir, &git_path_isdir);
435 436
}

437
bool git_path_contains_file(git_buf *base, const char *file)
438
{
439
	return _check_dir_contents(base, file, &git_path_isfile);
440 441 442 443
}

int git_path_find_dir(git_buf *dir, const char *path, const char *base)
{
444
	int error;
445 446 447 448 449 450

	if (base != NULL && git_path_root(path) < 0)
		error = git_buf_joinpath(dir, base, path);
	else
		error = git_buf_sets(dir, path);

451
	if (!error) {
452 453 454 455 456 457
		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 */
458 459
	if (!error && git_path_isdir(dir->ptr) == false)
		error = git_path_dirname_r(dir, dir->ptr);
460

461
	if (!error)
462 463 464 465 466
		error = git_path_to_dir(dir);

	return error;
}

467 468 469
int git_path_cmp(
	const char *name1, size_t len1, int isdir1,
	const char *name2, size_t len2, int isdir2)
470
{
471
	size_t len = len1 < len2 ? len1 : len2;
472 473 474 475 476 477
	int cmp;

	cmp = memcmp(name1, name2, len);
	if (cmp)
		return cmp;
	if (len1 < len2)
478 479
		return (!isdir1 && !isdir2) ? -1 :
			(isdir1 ? '/' - name2[len1] : name2[len1] - '/');
480
	if (len1 > len2)
481 482
		return (!isdir1 && !isdir2) ? 1 :
			(isdir2 ? name1[len2] - '/' : '/' - name1[len2]);
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
	return 0;
}

/* Taken from git.git */
GIT_INLINE(int) is_dot_or_dotdot(const char *name)
{
	return (name[0] == '.' &&
		(name[1] == '\0' ||
		 (name[1] == '.' && name[2] == '\0')));
}

int git_path_direach(
	git_buf *path,
	int (*fn)(void *, git_buf *),
	void *arg)
{
	ssize_t wd_len;
	DIR *dir;
501
	struct dirent *de, *de_buf;
502

503
	if (git_path_to_dir(path) < 0)
504
		return -1;
505

nulltoken committed
506
	wd_len = git_buf_len(path);
507

508 509
	if ((dir = opendir(path->ptr)) == NULL) {
		giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr);
510 511
		return -1;
	}
512

513 514 515 516 517 518 519
#ifdef __sun
	de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
#else
	de_buf = git__malloc(sizeof(struct dirent));
#endif

	while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
520 521 522 523 524
		int result;

		if (is_dot_or_dotdot(de->d_name))
			continue;

525 526 527
		if (git_buf_puts(path, de->d_name) < 0) {
			closedir(dir);
			git__free(de_buf);
528
			return -1;
529
		}
530 531 532 533 534

		result = fn(arg, path);

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

535
		if (result < 0) {
536
			closedir(dir);
537
			git__free(de_buf);
538
			return -1;
539 540 541 542
		}
	}

	closedir(dir);
543
	git__free(de_buf);
544
	return 0;
545
}
546 547 548 549 550 551 552 553 554

int git_path_dirload(
	const char *path,
	size_t prefix_len,
	size_t alloc_extra,
	git_vector *contents)
{
	int error, need_slash;
	DIR *dir;
555
	struct dirent *de, *de_buf;
556 557 558 559 560 561
	size_t path_len;

	assert(path != NULL && contents != NULL);
	path_len = strlen(path);
	assert(path_len > 0 && path_len >= prefix_len);

562 563 564 565
	if ((dir = opendir(path)) == NULL) {
		giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
		return -1;
	}
566

567 568 569 570 571 572
#ifdef __sun
	de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1);
#else
	de_buf = git__malloc(sizeof(struct dirent));
#endif

573 574 575 576
	path += prefix_len;
	path_len -= prefix_len;
	need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;

577
	while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
578 579 580 581 582 583 584 585 586 587
		char *entry_path;
		size_t entry_len;

		if (is_dot_or_dotdot(de->d_name))
			continue;

		entry_len = strlen(de->d_name);

		entry_path = git__malloc(
			path_len + need_slash + entry_len + 1 + alloc_extra);
588
		GITERR_CHECK_ALLOC(entry_path);
589 590 591 592 593 594 595 596

		if (path_len)
			memcpy(entry_path, path, path_len);
		if (need_slash)
			entry_path[path_len] = '/';
		memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len);
		entry_path[path_len + need_slash + entry_len] = '\0';

597 598 599
		if (git_vector_insert(contents, entry_path) < 0) {
			closedir(dir);
			git__free(de_buf);
600
			return -1;
601
		}
602 603 604
	}

	closedir(dir);
605
	git__free(de_buf);
606

607 608
	if (error != 0)
		giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
609

610
	return error;
611 612
}

613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
int git_path_with_stat_cmp(const void *a, const void *b)
{
	const git_path_with_stat *psa = a, *psb = b;
	return git__strcmp_cb(psa->path, psb->path);
}

int git_path_dirload_with_stat(
	const char *path,
	size_t prefix_len,
	git_vector *contents)
{
	int error;
	unsigned int i;
	git_path_with_stat *ps;
	git_buf full = GIT_BUF_INIT;

629 630
	if (git_buf_set(&full, path, prefix_len) < 0)
		return -1;
631

632 633 634
	error = git_path_dirload(
		path, prefix_len, sizeof(git_path_with_stat) + 1, contents);
	if (error < 0) {
635 636 637 638 639 640 641 642 643 644
		git_buf_free(&full);
		return error;
	}

	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;

645 646
		if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
			(error = git_path_lstat(full.ptr, &ps->st)) < 0)
647 648
			break;

649 650 651 652 653 654 655 656
		git_buf_truncate(&full, prefix_len);

		if (S_ISDIR(ps->st.st_mode)) {
			ps->path[path_len] = '/';
			ps->path[path_len + 1] = '\0';
		}
	}

657 658
	git_buf_free(&full);

659 660
	return error;
}