futils.c 27 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.
 */
7

8
#include "futils.h"
9

10
#include "runtime.h"
11
#include "strmap.h"
12
#include "hash.h"
13
#include <ctype.h>
14 15 16
#if GIT_WIN32
#include "win32/findfile.h"
#endif
17

18
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
19
{
20
	return git_futils_mkdir(
21
		file_path, mode,
22
		GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
23 24
}

25
int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode)
Vicent Marti committed
26 27
{
	int fd;
28 29 30
	mode_t mask;

	p_umask(mask = p_umask(0));
Vicent Marti committed
31

32 33 34 35
	git_buf_sets(path_out, filename);
	git_buf_puts(path_out, "_git2_XXXXXX");

	if (git_buf_oom(path_out))
36
		return -1;
Vicent Marti committed
37

38
	if ((fd = p_mkstemp(path_out->ptr)) < 0) {
39
		git_error_set(GIT_ERROR_OS,
40
			"failed to create temporary file '%s'", path_out->ptr);
41 42
		return -1;
	}
Vicent Marti committed
43

44
	if (p_chmod(path_out->ptr, (mode & ~mask))) {
45
		git_error_set(GIT_ERROR_OS,
46
			"failed to set permissions on file '%s'", path_out->ptr);
47 48 49
		return -1;
	}

50
	return fd;
Vicent Marti committed
51 52
}

53
int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
54
{
55
	int fd;
56

57 58 59 60 61
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	fd = p_creat(path, mode);
	if (fd < 0) {
62
		git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path);
63 64 65 66
		return -1;
	}

	return fd;
67 68
}

69
int git_futils_creat_locked(const char *path, const mode_t mode)
70
{
71 72
	int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC,
		mode);
73

74
	if (fd < 0) {
75
		int error = errno;
76
		git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path);
77 78 79 80 81 82 83 84
		switch (error) {
		case EEXIST:
			return GIT_ELOCKED;
		case ENOENT:
			return GIT_ENOTFOUND;
		default:
			return -1;
		}
85 86 87
	}

	return fd;
88 89
}

90
int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
91
{
92 93
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;
94

Vicent Marti committed
95
	return git_futils_creat_locked(path, mode);
96 97
}

98 99 100
int git_futils_open_ro(const char *path)
{
	int fd = p_open(path, O_RDONLY);
101 102
	if (fd < 0)
		return git_path_set_error(errno, path, "open");
103 104 105
	return fd;
}

106 107 108 109 110 111 112 113 114 115
int git_futils_truncate(const char *path, int mode)
{
	int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
	if (fd < 0)
		return git_path_set_error(errno, path, "open");

	close(fd);
	return 0;
}

116
int git_futils_filesize(uint64_t *out, git_file fd)
117
{
118
	struct stat sb;
119 120

	if (p_fstat(fd, &sb)) {
121
		git_error_set(GIT_ERROR_OS, "failed to stat file descriptor");
122 123
		return -1;
	}
124

125 126 127 128 129 130 131
	if (sb.st_size < 0) {
		git_error_set(GIT_ERROR_INVALID, "invalid file size");
		return -1;
	}

	*out = sb.st_size;
	return 0;
132
}
133

134 135 136
mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
137
		return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
138 139 140 141
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
142 143
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
144 145 146 147
	else
		return 0;
}

148 149
int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
150
	ssize_t read_size = 0;
151
	size_t alloc_len;
152 153 154

	git_buf_clear(buf);

155
	if (!git__is_ssizet(len)) {
156
		git_error_set(GIT_ERROR_INVALID, "read too large");
157 158 159
		return -1;
	}

160
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
161
	if (git_buf_grow(buf, alloc_len) < 0)
162 163
		return -1;

164 165
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
166

Vicent Marti committed
167
	if (read_size != (ssize_t)len) {
168
		git_error_set(GIT_ERROR_OS, "failed to read descriptor");
169
		git_buf_dispose(buf);
170
		return -1;
171 172
	}

173 174 175
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

176 177 178 179
	return 0;
}

int git_futils_readbuffer_updated(
180
	git_buf *out, const char *path, git_oid *checksum, int *updated)
181
{
182
	int error;
183
	git_file fd;
184
	struct stat st;
185
	git_buf buf = GIT_BUF_INIT;
186
	git_oid checksum_new;
187

188 189
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(path && *path);
190

191 192
	if (updated != NULL)
		*updated = 0;
193

194 195
	if (p_stat(path, &st) < 0)
		return git_path_set_error(errno, path, "stat");
196

197 198

	if (S_ISDIR(st.st_mode)) {
199
		git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
200 201 202 203
		return GIT_ENOTFOUND;
	}

	if (!git__is_sizet(st.st_size+1)) {
204
		git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
205
		return -1;
206
	}
207

208 209 210
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

211
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
212 213
		p_close(fd);
		return -1;
214 215
	}

Vicent Marti committed
216
	p_close(fd);
217

218 219
	if (checksum) {
		if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
220
			git_buf_dispose(&buf);
221 222
			return error;
		}
223

224 225 226 227
		/*
		 * If we were given a checksum, we only want to use it if it's different
		 */
		if (!git_oid__cmp(checksum, &checksum_new)) {
228
			git_buf_dispose(&buf);
229 230
			if (updated)
				*updated = 0;
231

232 233 234 235
			return 0;
		}

		git_oid_cpy(checksum, &checksum_new);
236 237 238 239 240
	}

	/*
	 * If we're here, the file did change, or the user didn't have an old version
	 */
241 242 243
	if (updated != NULL)
		*updated = 1;

244
	git_buf_swap(out, &buf);
245
	git_buf_dispose(&buf);
246

247
	return 0;
248 249
}

250
int git_futils_readbuffer(git_buf *buf, const char *path)
251
{
252
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
253 254
}

255 256 257
int git_futils_writebuffer(
	const git_buf *buf,	const char *path, int flags, mode_t mode)
{
258
	int fd, do_fsync = 0, error = 0;
259

260
	if (!flags)
261
		flags = O_CREAT | O_TRUNC | O_WRONLY;
262

263 264 265 266
	if ((flags & O_FSYNC) != 0)
		do_fsync = 1;

	flags &= ~O_FSYNC;
267 268 269 270 271

	if (!mode)
		mode = GIT_FILEMODE_BLOB;

	if ((fd = p_open(path, flags, mode)) < 0) {
272
		git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
273 274 275 276
		return fd;
	}

	if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
277
		git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
278
		(void)p_close(fd);
279
		return error;
280 281
	}

282
	if (do_fsync && (error = p_fsync(fd)) < 0) {
283
		git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
284 285 286 287
		p_close(fd);
		return error;
	}

288
	if ((error = p_close(fd)) < 0) {
289
		git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
290 291 292 293 294
		return error;
	}

	if (do_fsync && (flags & O_CREAT))
		error = git_futils_fsync_parent(path);
295 296 297 298

	return error;
}

299
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
300
{
301 302
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
303

304
	if (p_rename(from, to) < 0) {
305
		git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
306 307 308 309
		return -1;
	}

	return 0;
310 311
}

312
int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len)
313
{
Vicent Marti committed
314
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
315 316
}

317 318
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
319
	git_file fd = git_futils_open_ro(path);
320
	uint64_t len;
321
	int result;
322 323 324 325

	if (fd < 0)
		return fd;

326
	if ((result = git_futils_filesize(&len, fd)) < 0)
327
		goto out;
328

329
	if (!git__is_sizet(len)) {
330
		git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
331 332
		result = -1;
		goto out;
333
	}
334

335
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
336
out:
337 338 339 340
	p_close(fd);
	return result;
}

Vicent Marti committed
341
void git_futils_mmap_free(git_map *out)
342
{
Vicent Marti committed
343
	p_munmap(out);
344 345
}

346 347
GIT_INLINE(int) mkdir_validate_dir(
	const char *path,
348 349 350
	struct stat *st,
	mode_t mode,
	uint32_t flags,
351
	struct git_futils_mkdir_options *opts)
352
{
353 354
	/* with exclusive create, existing dir is an error */
	if ((flags & GIT_MKDIR_EXCL) != 0) {
355
		git_error_set(GIT_ERROR_FILESYSTEM,
356
			"failed to make directory '%s': directory exists", path);
357 358 359
		return GIT_EEXISTS;
	}

360 361
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
362
		if (p_unlink(path) < 0) {
363
			git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
364
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
365 366 367
			return GIT_EEXISTS;
		}

368
		opts->perfdata.mkdir_calls++;
369

370
		if (p_mkdir(path, mode) < 0) {
371
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
372 373 374 375 376 377
			return GIT_EEXISTS;
		}
	}

	else if (S_ISLNK(st->st_mode)) {
		/* Re-stat the target, make sure it's a directory */
378
		opts->perfdata.stat_calls++;
379

380
		if (p_stat(path, st) < 0) {
381
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
382 383 384 385 386
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
387
		git_error_set(GIT_ERROR_FILESYSTEM,
388
			"failed to make directory '%s': directory exists", path);
389 390 391 392 393 394
		return GIT_EEXISTS;
	}

	return 0;
}

395 396 397 398 399 400 401 402 403 404 405 406 407 408
GIT_INLINE(int) mkdir_validate_mode(
	const char *path,
	struct stat *st,
	bool terminal_path,
	mode_t mode,
	uint32_t flags,
	struct git_futils_mkdir_options *opts)
{
	if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) ||
		(flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) {

		opts->perfdata.chmod_calls++;

		if (p_chmod(path, mode) < 0) {
409
			git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
410 411 412 413 414 415
			return -1;
		}
	}

	return 0;
}
416

417 418 419
GIT_INLINE(int) mkdir_canonicalize(
	git_buf *path,
	uint32_t flags)
420
{
421
	ssize_t root_len;
422

423
	if (path->size == 0) {
424
		git_error_set(GIT_ERROR_OS, "attempt to create empty path");
425
		return -1;
426
	}
427

428
	/* Trim trailing slashes (except the root) */
429
	if ((root_len = git_path_root(path->ptr)) < 0)
430 431 432 433
		root_len = 0;
	else
		root_len++;

434 435
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
436

437
	/* if we are not supposed to made the last element, truncate it */
438
	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
439
		git_path_dirname_r(path, path->ptr);
440 441
		flags |= GIT_MKDIR_SKIP_LAST;
	}
442
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
443
		git_path_dirname_r(path, path->ptr);
444
	}
445

446
	/* We were either given the root path (or trimmed it to
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
	* the root), we don't have anything to do.
	*/
	if (path->size <= (size_t)root_len)
		git_buf_clear(path);

	return 0;
}

int git_futils_mkdir(
	const char *path,
	mode_t mode,
	uint32_t flags)
{
	git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT;
	const char *relative;
	struct git_futils_mkdir_options opts = { 0 };
	struct stat st;
	size_t depth = 0;
465
	int len = 0, root_len, error;
466 467 468 469 470 471 472

	if ((error = git_buf_puts(&make_path, path)) < 0 ||
		(error = mkdir_canonicalize(&make_path, flags)) < 0 ||
		(error = git_buf_puts(&parent_path, make_path.ptr)) < 0 ||
		make_path.size == 0)
		goto done;

473 474
	root_len = git_path_root(make_path.ptr);

475 476
	/* find the first parent directory that exists.  this will be used
	 * as the base to dirname_relative.
477
	 */
478 479 480 481 482 483
	for (relative = make_path.ptr; parent_path.size; ) {
		error = p_lstat(parent_path.ptr, &st);

		if (error == 0) {
			break;
		} else if (errno != ENOENT) {
484
			git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
485
			error = -1;
486 487 488 489 490 491 492 493 494 495 496
			goto done;
		}

		depth++;

		/* examine the parent of the current path */
		if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
			error = len;
			goto done;
		}

497
		GIT_ASSERT(len);
498

499 500 501 502 503
		/*
		 * We've walked all the given path's parents and it's either relative
		 * (the parent is simply '.') or rooted (the length is less than or
		 * equal to length of the root path).  The path may be less than the
		 * root path length on Windows, where `C:` == `C:/`.
504
		 */
505 506 507
		if ((len == 1 && parent_path.ptr[0] == '.') ||
		    (len == 1 && parent_path.ptr[0] == '/') ||
		    len <= root_len) {
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
			relative = make_path.ptr;
			break;
		}

		relative = make_path.ptr + len + 1;

		/* not recursive? just make this directory relative to its parent. */
		if ((flags & GIT_MKDIR_PATH) == 0)
			break;
	}

	/* we found an item at the location we're trying to create,
	 * validate it.
	 */
	if (depth == 0) {
523
		error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
524

525 526 527
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
528

529 530 531
		goto done;
	}

532 533 534 535 536 537 538 539 540
	/* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
	 * canonicalizing `make_path`.
	 */
	flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST);

	error = git_futils_mkdir_relative(relative,
		parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts);

done:
541 542
	git_buf_dispose(&make_path);
	git_buf_dispose(&parent_path);
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
	return error;
}

int git_futils_mkdir_r(const char *path, const mode_t mode)
{
	return git_futils_mkdir(path, mode, GIT_MKDIR_PATH);
}

int git_futils_mkdir_relative(
	const char *relative_path,
	const char *base,
	mode_t mode,
	uint32_t flags,
	struct git_futils_mkdir_options *opts)
{
	git_buf make_path = GIT_BUF_INIT;
	ssize_t root = 0, min_root_len;
	char lastch = '/', *tail;
	struct stat st;
	struct git_futils_mkdir_options empty_opts = {0};
	int error;

	if (!opts)
		opts = &empty_opts;

	/* build path and find "root" where we should start calling mkdir */
	if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
		return -1;

	if ((error = mkdir_canonicalize(&make_path, flags)) < 0 ||
		make_path.size == 0)
		goto done;

576 577 578 579
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

580 581 582 583
	/* advance root past drive name or network mount prefix */
	min_root_len = git_path_root(make_path.ptr);
	if (root < min_root_len)
		root = min_root_len;
yorah committed
584
	while (root >= 0 && make_path.ptr[root] == '/')
585 586
		++root;

587
	/* clip root to make_path length */
588 589
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
590 591
	if (root < 0)
		root = 0;
592

593 594
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
595
		bool mkdir_attempted = false;
596

597 598 599 600 601 602 603 604 605
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

		/* truncate path at next component */
		lastch = *tail;
		*tail = '\0';
606
		st.st_mode = 0;
607

608 609 610
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

611
		/* See what's going on with this path component */
612
		opts->perfdata.stat_calls++;
613

614
retry_lstat:
615
		if (p_lstat(make_path.ptr, &st) < 0) {
616
			if (mkdir_attempted || errno != ENOENT) {
617
				git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
618
				error = -1;
619 620 621
				goto done;
			}

622
			git_error_clear();
623 624 625 626 627
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
628
				git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
629 630 631
				error = -1;
				goto done;
			}
632
		} else {
633 634
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
635
				goto done;
636
		}
637

638
		/* chmod if requested and necessary */
639 640 641
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
642 643

		if (opts->dir_map && opts->pool) {
644 645 646
			char *cache_path;
			size_t alloc_size;

647
			GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
648
			cache_path = git_pool_malloc(opts->pool, alloc_size);
649
			GIT_ERROR_CHECK_ALLOC(cache_path);
650 651 652

			memcpy(cache_path, make_path.ptr, make_path.size + 1);

653
			if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
654 655
				goto done;
		}
656
	}
657

658 659 660
	error = 0;

	/* check that full path really is a directory if requested & needed */
661
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
662
		lastch != '\0') {
663
		opts->perfdata.stat_calls++;
664 665

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
666
			git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
667
				make_path.ptr);
668 669
			error = GIT_ENOTFOUND;
		}
670
	}
671

672
done:
673
	git_buf_dispose(&make_path);
674
	return error;
675
}
676

677
typedef struct {
678
	const char *base;
679
	size_t baselen;
680
	uint32_t flags;
681
	int depth;
682 683
} futils__rmdir_data;

684 685
#define FUTILS_MAX_DEPTH 100

686
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
687
{
688
	if (filemsg)
689
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
690 691
				   path, filemsg);
	else
692
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
693

694 695
	return -1;
}
696

697 698 699 700 701 702 703 704 705 706
static int futils__rm_first_parent(git_buf *path, const char *ceiling)
{
	int error = GIT_ENOTFOUND;
	struct stat st;

	while (error == GIT_ENOTFOUND) {
		git_buf_rtruncate_at_char(path, '/');

		if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
			error = 0;
707
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
708 709 710 711 712 713 714 715 716 717 718 719 720 721
			if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
				error = p_unlink(path->ptr);
			else if (!S_ISDIR(st.st_mode))
				error = -1; /* fail to remove non-regular file */
		} else if (errno != ENOTDIR)
			error = -1;
	}

	if (error)
		futils__error_cannot_rmdir(path->ptr, "cannot remove parent");

	return error;
}

722 723
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
724
	int error = 0;
725
	futils__rmdir_data *data = opaque;
726
	struct stat st;
727

728 729 730
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
731

732
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
733
		if (errno == ENOENT)
734
			error = 0;
735 736 737
		else if (errno == ENOTDIR) {
			/* asked to remove a/b/c/d/e and a/b is a normal file */
			if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
738
				error = futils__rm_first_parent(path, data->base);
739 740 741 742 743
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
744
			error = git_path_set_error(errno, path->ptr, "rmdir");
745 746 747
	}

	else if (S_ISDIR(st.st_mode)) {
748 749
		data->depth++;

750
		error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
751 752 753

		data->depth--;

754
		if (error < 0)
755
			return error;
756

757
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
758
			return error;
759

760
		if ((error = p_rmdir(path->ptr)) < 0) {
761
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
762
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
763
				error = 0;
764
			else
765
				error = git_path_set_error(errno, path->ptr, "rmdir");
766
		}
767 768
	}

769
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
770 771
		if (p_unlink(path->ptr) < 0)
			error = git_path_set_error(errno, path->ptr, "remove");
772 773
	}

774
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
775
		error = futils__error_cannot_rmdir(path->ptr, "still present");
776

777
	return error;
778 779
}

780
static int futils__rmdir_empty_parent(void *opaque, const char *path)
781
{
782
	futils__rmdir_data *data = opaque;
783
	int error = 0;
784

785
	if (strlen(path) <= data->baselen)
786
		error = GIT_ITEROVER;
787

788
	else if (p_rmdir(path) < 0) {
789 790 791
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
792
			/* do nothing */
793 794 795
		} else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
			en == EBUSY) {
			error = git_path_set_error(errno, path, "rmdir");
796
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
797 798
			error = GIT_ITEROVER;
		} else {
799
			error = git_path_set_error(errno, path, "rmdir");
800 801 802
		}
	}

803
	return error;
804 805
}

806
int git_futils_rmdir_r(
807
	const char *path, const char *base, uint32_t flags)
808
{
809
	int error;
810
	git_buf fullpath = GIT_BUF_INIT;
811
	futils__rmdir_data data;
812 813 814 815 816

	/* build path and find "root" where we should start calling mkdir */
	if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
		return -1;

817
	memset(&data, 0, sizeof(data));
818 819 820
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
821 822 823 824

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
825
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
826 827 828
		error = git_path_walk_up(
			&fullpath, base, futils__rmdir_empty_parent, &data);

829
	if (error == GIT_ITEROVER) {
830
		git_error_clear();
831
		error = 0;
832
	}
833

834
	git_buf_dispose(&fullpath);
835 836

	return error;
837 838
}

839
int git_futils_fake_symlink(const char *target, const char *path)
840 841
{
	int retcode = GIT_ERROR;
842
	int fd = git_futils_creat_withpath(path, 0755, 0644);
843
	if (fd >= 0) {
844
		retcode = p_write(fd, target, strlen(target));
845 846 847 848
		p_close(fd);
	}
	return retcode;
}
849

850
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
851 852
{
	int error = 0;
853
	char buffer[FILEIO_BUFSIZE];
854 855 856 857 858 859 860 861 862
	ssize_t len = 0;

	while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
		/* p_write() does not have the same semantics as write().  It loops
		 * internally and will return 0 when it has completed writing.
		 */
		error = p_write(ofd, buffer, len);

	if (len < 0) {
863
		git_error_set(GIT_ERROR_OS, "read error while copying file");
864 865 866
		error = (int)len;
	}

867
	if (error < 0)
868
		git_error_set(GIT_ERROR_OS, "write error while copying file");
869

870
	if (close_fd_when_done) {
871 872 873 874 875 876 877
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

878
int git_futils_cp(const char *from, const char *to, mode_t filemode)
879 880 881 882 883 884 885 886
{
	int ifd, ofd;

	if ((ifd = git_futils_open_ro(from)) < 0)
		return ifd;

	if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
		p_close(ifd);
887
		return git_path_set_error(errno, to, "open for writing");
888 889
	}

890
	return cp_by_fd(ifd, ofd, true);
891 892
}

893
int git_futils_touch(const char *path, time_t *when)
894 895 896 897
{
	struct p_timeval times[2];
	int ret;

898 899
	times[0].tv_sec =  times[1].tv_sec  = when ? *when : time(NULL);
	times[0].tv_usec = times[1].tv_usec = 0;
900 901 902 903 904 905

	ret = p_utimes(path, times);

	return (ret < 0) ? git_path_set_error(errno, path, "touch") : 0;
}

906
static int cp_link(const char *from, const char *to, size_t link_size)
907 908 909
{
	int error = 0;
	ssize_t read_len;
910
	char *link_data;
911
	size_t alloc_size;
912

913
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
914
	link_data = git__malloc(alloc_size);
915
	GIT_ERROR_CHECK_ALLOC(link_data);
916

917 918
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
919
		git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
920 921 922 923 924 925
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
926
			git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944
				link_data, to);
			error = -1;
		}
	}

	git__free(link_data);
	return error;
}

typedef struct {
	const char *to_root;
	git_buf to;
	ssize_t from_prefix;
	uint32_t flags;
	uint32_t mkdir_flags;
	mode_t dirmode;
} cp_r_info;

945 946 947 948 949 950 951 952 953
#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)

static int _cp_r_mkdir(cp_r_info *info, git_buf *from)
{
	int error = 0;

	/* create root directory the first time we need to create a directory */
	if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
		error = git_futils_mkdir(
954
			info->to_root, info->dirmode,
955
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
956 957 958 959 960 961

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
962
		error = git_futils_mkdir_relative(
963
			from->ptr + info->from_prefix, info->to_root,
964
			info->dirmode, info->mkdir_flags, NULL);
965 966 967 968

	return error;
}

969 970
static int _cp_r_callback(void *ref, git_buf *from)
{
971
	int error = 0;
972 973 974 975 976 977 978 979
	cp_r_info *info = ref;
	struct stat from_st, to_st;
	bool exists = false;

	if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
		from->ptr[git_path_basename_offset(from)] == '.')
		return 0;

980 981
	if ((error = git_buf_joinpath(
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
982
		return error;
983

984
	if (!(error = git_path_lstat(info->to.ptr, &to_st)))
985
		exists = true;
986
	else if (error != GIT_ENOTFOUND)
987
		return error;
988
	else {
989
		git_error_clear();
990 991
		error = 0;
	}
992

993
	if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
994
		return error;
995 996 997 998 999

	if (S_ISDIR(from_st.st_mode)) {
		mode_t oldmode = info->dirmode;

		/* if we are not chmod'ing, then overwrite dirmode */
1000
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
1001 1002 1003 1004
			info->dirmode = from_st.st_mode;

		/* make directory now if CREATE_EMPTY_DIRS is requested and needed */
		if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
1005
			error = _cp_r_mkdir(info, from);
1006 1007

		/* recurse onto target directory */
1008
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
1009
			error = git_path_direach(from, 0, _cp_r_callback, info);
1010 1011 1012 1013

		if (oldmode != 0)
			info->dirmode = oldmode;

1014
		return error;
1015 1016 1017 1018 1019 1020 1021
	}

	if (exists) {
		if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
			return 0;

		if (p_unlink(info->to.ptr) < 0) {
1022
			git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
1023
				info->to.ptr);
1024
			return GIT_EEXISTS;
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
		}
	}

	/* Done if this isn't a regular file or a symlink */
	if (!S_ISREG(from_st.st_mode) &&
		(!S_ISLNK(from_st.st_mode) ||
		 (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
		return 0;

	/* Make container directory on demand if needed */
	if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
1036
		(error = _cp_r_mkdir(info, from)) < 0)
1037
		return error;
1038 1039

	/* make symlink or regular file */
1040
	if (info->flags & GIT_CPDIR_LINK_FILES) {
1041
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
1042
			git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
1043
	} else if (S_ISLNK(from_st.st_mode)) {
1044
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
1045
	} else {
1046 1047
		mode_t usemode = from_st.st_mode;

1048
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1049
			usemode = GIT_PERMS_FOR_WRITE(usemode);
1050 1051 1052 1053

		error = git_futils_cp(from->ptr, info->to.ptr, usemode);
	}

1054
	return error;
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
}

int git_futils_cp_r(
	const char *from,
	const char *to,
	uint32_t flags,
	mode_t dirmode)
{
	int error;
	git_buf path = GIT_BUF_INIT;
	cp_r_info info;

1067
	if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1068 1069
		return -1;

1070
	memset(&info, 0, sizeof(info));
1071 1072 1073 1074 1075 1076 1077 1078
	info.to_root = to;
	info.flags   = flags;
	info.dirmode = dirmode;
	info.from_prefix = path.size;
	git_buf_init(&info.to, 0);

	/* precalculate mkdir flags */
	if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
1079 1080 1081
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1082
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1083
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1084 1085
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1086
		/* otherwise, we will do simple mkdir as directories are encountered */
1087
		info.mkdir_flags =
1088
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1089 1090 1091 1092
	}

	error = _cp_r_callback(&info, &path);

1093 1094
	git_buf_dispose(&path);
	git_buf_dispose(&info.to);
1095 1096 1097

	return error;
}
1098

Vicent Marti committed
1099 1100
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1101 1102 1103
{
	struct stat st;

1104 1105
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1106 1107
		return 1;

1108
	if (p_stat(path, &st) < 0)
1109 1110
		return GIT_ENOTFOUND;

1111
	if (stamp->mtime.tv_sec == st.st_mtime &&
1112
#if defined(GIT_USE_NSEC)
1113
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1114
#endif
1115
		stamp->size  == (uint64_t)st.st_size   &&
1116
		stamp->ino   == (unsigned int)st.st_ino)
1117 1118
		return 0;

1119
	stamp->mtime.tv_sec = st.st_mtime;
1120
#if defined(GIT_USE_NSEC)
1121
	stamp->mtime.tv_nsec = st.st_mtime_nsec;
1122
#endif
1123
	stamp->size  = (uint64_t)st.st_size;
1124
	stamp->ino   = (unsigned int)st.st_ino;
1125 1126 1127 1128

	return 1;
}

Vicent Marti committed
1129 1130
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1131 1132 1133 1134 1135 1136
{
	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1137 1138 1139 1140 1141 1142


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1143 1144 1145 1146
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1147 1148
		stamp->mtime.tv_nsec = 0;
#endif
1149
		stamp->size  = (uint64_t)st->st_size;
1150 1151 1152 1153 1154
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}
1155 1156 1157

int git_futils_fsync_dir(const char *path)
{
1158 1159 1160 1161
#ifdef GIT_WIN32
	GIT_UNUSED(path);
	return 0;
#else
1162 1163 1164
	int fd, error = -1;

	if ((fd = p_open(path, O_RDONLY)) < 0) {
1165
		git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
1166 1167 1168 1169
		return -1;
	}

	if ((error = p_fsync(fd)) < 0)
1170
		git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
1171 1172 1173

	p_close(fd);
	return error;
1174
#endif
1175 1176 1177 1178
}

int git_futils_fsync_parent(const char *path)
{
1179 1180 1181 1182 1183
	char *parent;
	int error;

	if ((parent = git_path_dirname(path)) == NULL)
		return -1;
1184

1185
	error = git_futils_fsync_dir(parent);
1186 1187 1188
	git__free(parent);
	return error;
}