fileops.c 26.8 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 "fileops.h"
9

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

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

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

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

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

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

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

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

49
	return fd;
Vicent Marti committed
50 51
}

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

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

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

	return fd;
66 67
}

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

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

	return fd;
87 88
}

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

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

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

105 106 107 108 109 110 111 112 113 114
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;
}

Vicent Marti committed
115
git_off_t git_futils_filesize(git_file fd)
116
{
117
	struct stat sb;
118 119

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

124 125
	return sb.st_size;
}
126

127 128 129
mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
130
		return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
131 132 133 134
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
135 136
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
137 138 139 140
	else
		return 0;
}

141 142
int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
143
	ssize_t read_size = 0;
144
	size_t alloc_len;
145 146 147

	git_buf_clear(buf);

148
	if (!git__is_ssizet(len)) {
149
		git_error_set(GIT_ERROR_INVALID, "read too large");
150 151 152
		return -1;
	}

153
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
154
	if (git_buf_grow(buf, alloc_len) < 0)
155 156
		return -1;

157 158
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
159

Vicent Marti committed
160
	if (read_size != (ssize_t)len) {
161
		git_error_set(GIT_ERROR_OS, "failed to read descriptor");
162
		git_buf_dispose(buf);
163
		return -1;
164 165
	}

166 167 168
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

169 170 171 172
	return 0;
}

int git_futils_readbuffer_updated(
173
	git_buf *out, const char *path, git_oid *checksum, int *updated)
174
{
175
	int error;
176
	git_file fd;
177
	struct stat st;
178
	git_buf buf = GIT_BUF_INIT;
179
	git_oid checksum_new;
180

181
	assert(out && path && *path);
182

183 184
	if (updated != NULL)
		*updated = 0;
185

186 187
	if (p_stat(path, &st) < 0)
		return git_path_set_error(errno, path, "stat");
188

189 190

	if (S_ISDIR(st.st_mode)) {
191
		git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
192 193 194 195
		return GIT_ENOTFOUND;
	}

	if (!git__is_sizet(st.st_size+1)) {
196
		git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
197
		return -1;
198
	}
199

200 201 202
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

203
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
204 205
		p_close(fd);
		return -1;
206 207
	}

Vicent Marti committed
208
	p_close(fd);
209

210 211
	if (checksum) {
		if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
212
			git_buf_dispose(&buf);
213 214
			return error;
		}
215

216 217 218 219
		/*
		 * If we were given a checksum, we only want to use it if it's different
		 */
		if (!git_oid__cmp(checksum, &checksum_new)) {
220
			git_buf_dispose(&buf);
221 222
			if (updated)
				*updated = 0;
223

224 225 226 227
			return 0;
		}

		git_oid_cpy(checksum, &checksum_new);
228 229 230 231 232
	}

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

236
	git_buf_swap(out, &buf);
237
	git_buf_dispose(&buf);
238

239
	return 0;
240 241
}

242
int git_futils_readbuffer(git_buf *buf, const char *path)
243
{
244
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
245 246
}

247 248 249
int git_futils_writebuffer(
	const git_buf *buf,	const char *path, int flags, mode_t mode)
{
250
	int fd, do_fsync = 0, error = 0;
251

252
	if (!flags)
253
		flags = O_CREAT | O_TRUNC | O_WRONLY;
254

255 256 257 258
	if ((flags & O_FSYNC) != 0)
		do_fsync = 1;

	flags &= ~O_FSYNC;
259 260 261 262 263

	if (!mode)
		mode = GIT_FILEMODE_BLOB;

	if ((fd = p_open(path, flags, mode)) < 0) {
264
		git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
265 266 267 268
		return fd;
	}

	if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
269
		git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
270
		(void)p_close(fd);
271
		return error;
272 273
	}

274
	if (do_fsync && (error = p_fsync(fd)) < 0) {
275
		git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
276 277 278 279
		p_close(fd);
		return error;
	}

280
	if ((error = p_close(fd)) < 0) {
281
		git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
282 283 284 285 286
		return error;
	}

	if (do_fsync && (flags & O_CREAT))
		error = git_futils_fsync_parent(path);
287 288 289 290

	return error;
}

291
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
292
{
293 294
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
295

296
	if (p_rename(from, to) < 0) {
297
		git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
298 299 300 301
		return -1;
	}

	return 0;
302 303
}

Vicent Marti committed
304
int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
305
{
Vicent Marti committed
306
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
307 308
}

309 310
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
311 312
	git_file fd = git_futils_open_ro(path);
	git_off_t len;
313
	int result;
314 315 316 317

	if (fd < 0)
		return fd;

318 319 320 321
	if ((len = git_futils_filesize(fd)) < 0) {
		result = -1;
		goto out;
	}
322

323
	if (!git__is_sizet(len)) {
324
		git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
325 326
		result = -1;
		goto out;
327
	}
328

329
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
330
out:
331 332 333 334
	p_close(fd);
	return result;
}

Vicent Marti committed
335
void git_futils_mmap_free(git_map *out)
336
{
Vicent Marti committed
337
	p_munmap(out);
338 339
}

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

354 355
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
356
		if (p_unlink(path) < 0) {
357
			git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
358
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
359 360 361
			return GIT_EEXISTS;
		}

362
		opts->perfdata.mkdir_calls++;
363

364
		if (p_mkdir(path, mode) < 0) {
365
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
366 367 368 369 370 371
			return GIT_EEXISTS;
		}
	}

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

374
		if (p_stat(path, st) < 0) {
375
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
376 377 378 379 380
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
381
		git_error_set(GIT_ERROR_FILESYSTEM,
382
			"failed to make directory '%s': directory exists", path);
383 384 385 386 387 388
		return GIT_EEXISTS;
	}

	return 0;
}

389 390 391 392 393 394 395 396 397 398 399 400 401 402
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) {
403
			git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
404 405 406 407 408 409
			return -1;
		}
	}

	return 0;
}
410

411 412 413
GIT_INLINE(int) mkdir_canonicalize(
	git_buf *path,
	uint32_t flags)
414
{
415
	ssize_t root_len;
416

417
	if (path->size == 0) {
418
		git_error_set(GIT_ERROR_OS, "attempt to create empty path");
419
		return -1;
420
	}
421

422
	/* Trim trailing slashes (except the root) */
423
	if ((root_len = git_path_root(path->ptr)) < 0)
424 425 426 427
		root_len = 0;
	else
		root_len++;

428 429
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
430

431
	/* if we are not supposed to made the last element, truncate it */
432
	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
433
		git_path_dirname_r(path, path->ptr);
434 435
		flags |= GIT_MKDIR_SKIP_LAST;
	}
436
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
437
		git_path_dirname_r(path, path->ptr);
438
	}
439

440
	/* We were either given the root path (or trimmed it to
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
	* 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;
459
	int len = 0, root_len, error;
460 461 462 463 464 465 466

	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;

467 468
	root_len = git_path_root(make_path.ptr);

469 470
	/* find the first parent directory that exists.  this will be used
	 * as the base to dirname_relative.
471
	 */
472 473 474 475 476 477
	for (relative = make_path.ptr; parent_path.size; ) {
		error = p_lstat(parent_path.ptr, &st);

		if (error == 0) {
			break;
		} else if (errno != ENOENT) {
478
			git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
479 480 481 482 483 484 485 486 487 488 489 490 491
			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;
		}

		assert(len);

492 493 494 495 496
		/*
		 * 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:/`.
497
		 */
498
		if ((len == 1 && parent_path.ptr[0] == '.') || len <= root_len) {
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
			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) {
514
		error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
515

516 517 518
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
519

520 521 522
		goto done;
	}

523 524 525 526 527 528 529 530 531
	/* 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:
532 533
	git_buf_dispose(&make_path);
	git_buf_dispose(&parent_path);
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
	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;

567 568 569 570
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

571 572 573 574
	/* 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
575
	while (root >= 0 && make_path.ptr[root] == '/')
576 577
		++root;

578
	/* clip root to make_path length */
579 580
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
581 582
	if (root < 0)
		root = 0;
583

584 585
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
586
		bool mkdir_attempted = false;
587

588 589 590 591 592 593 594 595 596
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

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

599 600 601
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

602
		/* See what's going on with this path component */
603
		opts->perfdata.stat_calls++;
604

605
retry_lstat:
606
		if (p_lstat(make_path.ptr, &st) < 0) {
607
			if (mkdir_attempted || errno != ENOENT) {
608
				git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
609
				error = -1;
610 611 612
				goto done;
			}

613
			git_error_clear();
614 615 616 617 618
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
619
				git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
620 621 622
				error = -1;
				goto done;
			}
623
		} else {
624 625
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
626
				goto done;
627
		}
628

629
		/* chmod if requested and necessary */
630 631 632
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
633 634

		if (opts->dir_map && opts->pool) {
635 636 637
			char *cache_path;
			size_t alloc_size;

638
			GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
639
			cache_path = git_pool_malloc(opts->pool, alloc_size);
640
			GIT_ERROR_CHECK_ALLOC(cache_path);
641 642 643

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

644
			if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
645 646
				goto done;
		}
647
	}
648

649 650 651
	error = 0;

	/* check that full path really is a directory if requested & needed */
652
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
653
		lastch != '\0') {
654
		opts->perfdata.stat_calls++;
655 656

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
657
			git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
658
				make_path.ptr);
659 660
			error = GIT_ENOTFOUND;
		}
661
	}
662

663
done:
664
	git_buf_dispose(&make_path);
665
	return error;
666
}
667

668
typedef struct {
669
	const char *base;
670
	size_t baselen;
671
	uint32_t flags;
672
	int depth;
673 674
} futils__rmdir_data;

675 676
#define FUTILS_MAX_DEPTH 100

677
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
678
{
679
	if (filemsg)
680
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
681 682
				   path, filemsg);
	else
683
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
684

685 686
	return -1;
}
687

688 689 690 691 692 693 694 695 696 697
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;
698
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
699 700 701 702 703 704 705 706 707 708 709 710 711 712
			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;
}

713 714
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
715
	int error = 0;
716
	futils__rmdir_data *data = opaque;
717
	struct stat st;
718

719 720 721
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
722

723
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
724
		if (errno == ENOENT)
725
			error = 0;
726 727 728
		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)
729
				error = futils__rm_first_parent(path, data->base);
730 731 732 733 734
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
735
			error = git_path_set_error(errno, path->ptr, "rmdir");
736 737 738
	}

	else if (S_ISDIR(st.st_mode)) {
739 740
		data->depth++;

741
		error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
742 743 744

		data->depth--;

745
		if (error < 0)
746
			return error;
747

748
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
749
			return error;
750

751
		if ((error = p_rmdir(path->ptr)) < 0) {
752
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
753
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
754
				error = 0;
755
			else
756
				error = git_path_set_error(errno, path->ptr, "rmdir");
757
		}
758 759
	}

760
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
761 762
		if (p_unlink(path->ptr) < 0)
			error = git_path_set_error(errno, path->ptr, "remove");
763 764
	}

765
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
766
		error = futils__error_cannot_rmdir(path->ptr, "still present");
767

768
	return error;
769 770
}

771
static int futils__rmdir_empty_parent(void *opaque, const char *path)
772
{
773
	futils__rmdir_data *data = opaque;
774
	int error = 0;
775

776
	if (strlen(path) <= data->baselen)
777
		error = GIT_ITEROVER;
778

779
	else if (p_rmdir(path) < 0) {
780 781 782
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
783
			/* do nothing */
784 785 786
		} else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
			en == EBUSY) {
			error = git_path_set_error(errno, path, "rmdir");
787
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
788 789
			error = GIT_ITEROVER;
		} else {
790
			error = git_path_set_error(errno, path, "rmdir");
791 792 793
		}
	}

794
	return error;
795 796
}

797
int git_futils_rmdir_r(
798
	const char *path, const char *base, uint32_t flags)
799
{
800
	int error;
801
	git_buf fullpath = GIT_BUF_INIT;
802
	futils__rmdir_data data;
803 804 805 806 807

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

808
	memset(&data, 0, sizeof(data));
809 810 811
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
812 813 814 815

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
816
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
817 818 819
		error = git_path_walk_up(
			&fullpath, base, futils__rmdir_empty_parent, &data);

820
	if (error == GIT_ITEROVER) {
821
		git_error_clear();
822
		error = 0;
823
	}
824

825
	git_buf_dispose(&fullpath);
826 827

	return error;
828 829
}

830 831 832 833 834 835 836 837 838 839
int git_futils_fake_symlink(const char *old, const char *new)
{
	int retcode = GIT_ERROR;
	int fd = git_futils_creat_withpath(new, 0755, 0644);
	if (fd >= 0) {
		retcode = p_write(fd, old, strlen(old));
		p_close(fd);
	}
	return retcode;
}
840

841
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
842 843
{
	int error = 0;
844
	char buffer[FILEIO_BUFSIZE];
845 846 847 848 849 850 851 852 853
	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) {
854
		git_error_set(GIT_ERROR_OS, "read error while copying file");
855 856 857
		error = (int)len;
	}

858
	if (error < 0)
859
		git_error_set(GIT_ERROR_OS, "write error while copying file");
860

861
	if (close_fd_when_done) {
862 863 864 865 866 867 868
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

869
int git_futils_cp(const char *from, const char *to, mode_t filemode)
870 871 872 873 874 875 876 877
{
	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);
878
		return git_path_set_error(errno, to, "open for writing");
879 880
	}

881
	return cp_by_fd(ifd, ofd, true);
882 883
}

884
int git_futils_touch(const char *path, time_t *when)
885 886 887 888
{
	struct p_timeval times[2];
	int ret;

889 890
	times[0].tv_sec =  times[1].tv_sec  = when ? *when : time(NULL);
	times[0].tv_usec = times[1].tv_usec = 0;
891 892 893 894 895 896

	ret = p_utimes(path, times);

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

897
static int cp_link(const char *from, const char *to, size_t link_size)
898 899 900
{
	int error = 0;
	ssize_t read_len;
901
	char *link_data;
902
	size_t alloc_size;
903

904
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
905
	link_data = git__malloc(alloc_size);
906
	GIT_ERROR_CHECK_ALLOC(link_data);
907

908 909
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
910
		git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
911 912 913 914 915 916
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
917
			git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
				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;

936 937 938 939 940 941 942 943 944
#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(
945
			info->to_root, info->dirmode,
946
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
947 948 949 950 951 952

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
953
		error = git_futils_mkdir_relative(
954
			from->ptr + info->from_prefix, info->to_root,
955
			info->dirmode, info->mkdir_flags, NULL);
956 957 958 959

	return error;
}

960 961
static int _cp_r_callback(void *ref, git_buf *from)
{
962
	int error = 0;
963 964 965 966 967 968 969 970
	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;

971 972
	if ((error = git_buf_joinpath(
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
973
		return error;
974

975
	if (!(error = git_path_lstat(info->to.ptr, &to_st)))
976
		exists = true;
977
	else if (error != GIT_ENOTFOUND)
978
		return error;
979
	else {
980
		git_error_clear();
981 982
		error = 0;
	}
983

984
	if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
985
		return error;
986 987 988 989 990

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

		/* if we are not chmod'ing, then overwrite dirmode */
991
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
992 993 994 995
			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)
996
			error = _cp_r_mkdir(info, from);
997 998

		/* recurse onto target directory */
999
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
1000
			error = git_path_direach(from, 0, _cp_r_callback, info);
1001 1002 1003 1004

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

1005
		return error;
1006 1007 1008 1009 1010 1011 1012
	}

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

		if (p_unlink(info->to.ptr) < 0) {
1013
			git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
1014
				info->to.ptr);
1015
			return GIT_EEXISTS;
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
		}
	}

	/* 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 &&
1027
		(error = _cp_r_mkdir(info, from)) < 0)
1028
		return error;
1029 1030

	/* make symlink or regular file */
1031
	if (info->flags & GIT_CPDIR_LINK_FILES) {
1032
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
1033
			git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
1034
	} else if (S_ISLNK(from_st.st_mode)) {
1035
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
1036
	} else {
1037 1038
		mode_t usemode = from_st.st_mode;

1039
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1040
			usemode = GIT_PERMS_FOR_WRITE(usemode);
1041 1042 1043 1044

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

1045
	return error;
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
}

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;

1058
	if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1059 1060
		return -1;

1061
	memset(&info, 0, sizeof(info));
1062 1063 1064 1065 1066 1067 1068 1069
	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) {
1070 1071 1072
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1073
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1074
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1075 1076
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1077
		/* otherwise, we will do simple mkdir as directories are encountered */
1078
		info.mkdir_flags =
1079
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1080 1081 1082 1083
	}

	error = _cp_r_callback(&info, &path);

1084 1085
	git_buf_dispose(&path);
	git_buf_dispose(&info.to);
1086 1087 1088

	return error;
}
1089

Vicent Marti committed
1090 1091
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1092 1093 1094
{
	struct stat st;

1095 1096
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1097 1098
		return 1;

1099
	if (p_stat(path, &st) < 0)
1100 1101
		return GIT_ENOTFOUND;

1102
	if (stamp->mtime.tv_sec == st.st_mtime &&
1103
#if defined(GIT_USE_NSEC)
1104
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1105
#endif
1106 1107
		stamp->size  == (git_off_t)st.st_size   &&
		stamp->ino   == (unsigned int)st.st_ino)
1108 1109
		return 0;

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

	return 1;
}

Vicent Marti committed
1120 1121
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1122 1123 1124 1125 1126 1127 1128 1129
{
	assert(target);

	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1130 1131 1132 1133 1134 1135


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1136 1137 1138 1139
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1140 1141
		stamp->mtime.tv_nsec = 0;
#endif
1142 1143 1144 1145 1146 1147
		stamp->size  = (git_off_t)st->st_size;
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}
1148 1149 1150

int git_futils_fsync_dir(const char *path)
{
1151 1152 1153 1154
#ifdef GIT_WIN32
	GIT_UNUSED(path);
	return 0;
#else
1155 1156 1157
	int fd, error = -1;

	if ((fd = p_open(path, O_RDONLY)) < 0) {
1158
		git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
1159 1160 1161 1162
		return -1;
	}

	if ((error = p_fsync(fd)) < 0)
1163
		git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
1164 1165 1166

	p_close(fd);
	return error;
1167
#endif
1168 1169 1170 1171
}

int git_futils_fsync_parent(const char *path)
{
1172 1173 1174 1175 1176
	char *parent;
	int error;

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

1178
	error = git_futils_fsync_dir(parent);
1179 1180 1181
	git__free(parent);
	return error;
}